`SOULPatchAudioProcessor::sendInputEvent` can't find endpoints

I’m probably missing something obvious here, but I can’t seem to get SOULPatchAudioProcessor::sendInputEvent to work. This application “should” play a 440Hz tone, but instead produces no sound. Debugging in sendInputEvent shows that inputEventDetails.size() == 0, so no events are delivered to the loaded patch, despite it having an input event int noteIn endpoint.

1 Like

It’s hard to unpick exactly what’s going on there, but most likely you’re somehow using the inputs before the compiler has finished building the code?

I just edited my code in the original post to call sendInputEvent on a Timer — but still, when I run it, no sound is produced. I verified that patches are loading properly by changing the SineOsc.soulpatch path to point to ClassicRingtone.soulpatch, and the jingle plays just fine. Also debugging shows that patch->getDescription()->name is indeed SineOsc, and SineOsc.soulpatch loads without errors using soul play.

:thinking::thinking::thinking:🤷🏻

When I run your patch using soul play SineOsc.soulpatch I get no sound.
The processor SineOsc.soul uses 2 variables currentNote and amplitude which are never initialized, therefore amplitude is always 0 and you output 0 to audioOut.

Edit the note() method to set currentNote to noteNumber and amplitude to 1.0f so that your processor can output some sound.

Then you need a main graph somewhere in your code. Right now you only have a patch and a processor, but no graph to describe the audio path and to tell SOUL where your note comes from. So check the examples, make it work using soul play and try to generate JUCE code once you have a working app.

Ahh thanks, I just added in those changes. Strangely, moving the note slider in the GUI generated by soul play does nothing, but if I replace input event int noteIn with input event soul::midi::Message noteIn and change its callback to

event noteIn (soul::midi::Message msg)
{
    let noteNumber = soul::midi::getByte2(msg);
    phaseIncrement = float (twoPi * processor.period * soul::noteNumberToFrequency (noteNumber));
    amplitude = 0.8f;
}

then I hear the proper beeps when I play MIDI notes.

But again, debugging in sendInputEvent shows that inputEventDetails.size() == 0, so the player->sendInputEvent call is never reached in my program.

I thought that a processor could be labeled [[ main ]] as well as a graph, no? It shouldn’t make a difference to sendInputEvent if I wrap SineOsc in a graph like this…

graph SineOscGraph [[ main ]]
{
    input event int noteIn;
    output stream float audioOut;

    let osc = SineOsc;

    connection
    {
        noteIn -> osc.noteIn;
        osc.audioOut -> audioOut;
    }
}

… right?

With the amplitude set and the noteIn event converted to soul::midi::Message the patch works now. And you are right, the graph is not necessary when there is only one processor labelled main.

For the C++ part I can’t help much because I only use SOUL as a standalone language at the moment. I could not find the documentation of some C++ classes, and if it is not documented I don’t touch it :smiley:

You could try to replace:

sendInputEvent("noteIn", 69);

with sendMIDIMessage if this method exists. I suppose MIDI inputs and event inputs are handled differently so it may works even though inputEventDetails.size() == 0.

There is SOULPatchAudioProcessor::injectMIDIMessage, but I need to be able to send non-integer input events for my application, so this won’t do.

Thanks for your help btw :slight_smile:

Okay I did some more digging…

When I place a breakpoint at line 892 of include/soul/patch/helper_classes/soul_patch_AudioProcessor.h, I can see that the newly-created PatchPlayer has recognized noteIn as a parameter, but not as an input endpoint —

Meaning — PatchPlayer::getParameters returns an array of length 1 and PatchPlayer::getInputEventEndpoints returns an empty array. This is true both with and without the annotation on input event int noteIn in SineOsc.soul.

Perhaps I’m missing a step in the initialization of SOULPatchAudioProcessor… Surely all parameters should also be input endpoints, right?

Peeling back yet another layer of the onion…

It seems like PatchPlayer constructs its inputEventEndpointSpan by calling AudioMIDIWrapper::getInputEventEndpoints,

which gets all the input event endpoints of type InputEndpointType::event.

But input endpoints are classified as either InputEndpointType::event OR InputEndpointType::parameter,

so it looks like input endpoints that are patch parameters are not accessible via PatchPlayer::sendInputEvent.

Is there a reason why an input endpoint can’t be both a parameter and an event input?

yes, good point, getEventInputEndpoints should return everything that’s an event input. I’ll sort that out. I don’t think I’ll need to change anything other than making that list return all the endpoints, as the mechanism for posting an event to a parameter input should already work if you have the handle for it.

1 Like

hmm… I would say though…

If you’ve got an input which is being used as a parameter and you start shoving other values into it without the wrapper knowing about that, then you’re going to get some odd behaviour: the value that the host thinks the parameter has won’t match the value it’s actually using.

So, thinking about it, I’m not 100% sure whether it’d be a good idea to change this. It might be better to force you to make up your mind about whether you’re going to control an input yourself, or leave it to the host to control as a parameter, to avoid confusing situations.

Does that sound sensible, or am I missing a valid reason for needing to control it both ways?

Thanks for looking into it :slight_smile:

am I missing a valid reason for needing to control it both ways?

It would be nice to be able to send unbounded values to float and int endpoints. Currently those are interpreted as parameter endpoints — and parameters values are clamped to the parameter’s range, which is [0,1] unless a min/ max are specified in the endpoint annotation. Of course, one can always set min/max to insanely large values, or wrap the float/int in a struct so that it’s interpreted as an event endpoint rather than a parameter, but both of those options feel like workarounds.

Or have I missed another way to send a float/int input event without bounds checking?

Ahh… so for my particular use case I was hoping to pass the output of juce::Time::getMillisecondcounterHiRes into a patch to synchronize a timeline across reloads. But now I see you’ve written the SOULPatchAudioProcessor::PlayheadState class for just that!

Off the top of my head, I can’t think of another scenario that requires unbounded input…

1 Like