Accessing subclassed `SynthesiserVoice` from `AudioProcessorEditor`

Possibly more of an OOP design question than a JUCE question.

I’m trying to plot some data (“stringState”) that lives inside my subclass of SynthesiserVoice. It’s a polyphonic synth, so the idea is to only display the latest voices’ stringState. stringState may change each sample, but obviously I’ll be displaying it at a much lower rate (I guess 60 Hz).

stringState only exists in my subclass of SynthesiserVoice, which we can call StringSynthesiserVoice.

So far, I’m using a Timer in my Editor to grab stringState from the Processor and plot it:

void StringSynthAudioProcessorEditor::timerCallback()
{
    std::vector<float> stringState = getProcessor().getLastPlayedStringState();
    stringGraphic.updateState(stringState);
}

The above snippet uses a member function of my AudioProcessor called getLastPlayedStringState:

std::vector<float> StringSynthAudioProcessor::getLastPlayedStringState()
{
    return synth.lastPlayedVoice->getStringState();
}

where synth.lastPlayedVoice is a member of my subclass of Synthesiser, set each time a note is played.

The problem is that my Synthesiser class doesn’t know about StringSynthesiserVoice, only its superclass SynthesiserVoice, so I can’t do return synth.lastPlayedVoice->getStringState() because synth.lastPlayedVoice is not a StringSynthesiserVoice and therefore doesn’t have a getStringState() member function. I’ve thought about checking the subclass of synth.lastPlayedVoice but it feels like there’s a deeper problem here.

Thanks! :control_knobs:

There’s lots of solution here by an easy one may be:

std::dynamic_cast<MY_VOICE_VOICE>(synth.lastPlayedVoice)->getStringState();

I don’t know much about Synthesiser and SynthesiserVoice, but from what you describe, I can say two things. If your Synthesiser subclass only ever uses StringSynthesiserVoices, you could just use static_cast<StringSynthesiserVoice*> (synth.lastPlayedVoice)->getStringState(). Otherwise (if the pointer is effectively polymorphic), you should check your cast:

if (auto voice = dynamic_cast<StringSynthesiserVoice*> (synth.lastPlayedVoice))
    return voice->getStringState();

return {}; // or whatever's appropriate

Of course, if the pointer is not effectively polymorphic it could point to the subclass directly, without the need of casting.

More serious is the fact that your voice or its state seem to be changed from the audio thread, and you’re making this query from the UI thread, so you need some kind of synchronization. I can’t say which kind without the details: maybe an atomic would suffice, maybe not.

Thanks @kamedin and @jakemumu. I’ve gone with a dynamic cast which is working.

I’ve done some reading about thread synchronisation and looks like maybe I should be using a Mutex. I’m a bit reluctant to get into this as it seems to be working well without one and I think the only symptom of not using one in my case will be some partly outdated data getting plotted for a fraction of a second, which wouldn’t be so bad… or is there something more serious I’m missing? I’ve done this sort of stuff for a multithreaded and multi-processed Python app in the past but that’s where I’ve been heavily involved in building the whole app including the IPC architecture between processes, and I’m struggling to fit that knowledge into the JUCE framework.

If you’re just reading you’re probably okay

If you have issues breaking it up into smaller reads of atomic types (float, int, etc) has never been a problem for me.

1 Like

You shouldn’t use a mutex in audio thread code, pretty much ever. Even if it’s taken for a short time you can’t escape the possibility of priority inversion.

Probably. You have to be sure that any object used in one thread is not destroyed or somehow invalidated in the other -that would be the worst case. For example, lastPlayedVoice could change while the UI thread is reading it, so the old data needs to be available after the change. You may still read inconsistent data -if it doesn’t break anything and it’s not noticeable, maybe you can get away with it.