How to add/override a ComboBox listener?

Here’s my main class definition. Notice there are no explicit declarations of any UI elements or internal UI variables here. There are a couple Faust UI classes. The Processor inherits from foleys::MagicProcessor. All my main class knows about directly is the JuceParameterUI which is basically the ValueTree.

    JuceStateUI fStateUI;
    JuceParameterUI fParameterUI;
class FaustPlugInAudioProcessor : public foleys::MagicProcessor, private juce::Timer
{
    
public:
#ifdef MAGIC_LEVEL_SOURCE
    foleys::MagicLevelSource* outputMeter = nullptr;
#endif
    FaustPlugInAudioProcessor();
    virtual ~FaustPlugInAudioProcessor() {}
    
    void prepareToPlay (double sampleRate, int samplesPerBlock) override;
    void FaustPlugInAudioProcessor::postSetStateInformation();
    bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
    
    void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) override
    {
        jassert (! isUsingDoublePrecision());
        process (buffer, midiMessages);
#ifdef MAGIC_LEVEL_SOURCE
	outputMeter->pushSamples(buffer);
#endif
    }
    
    void processBlock (juce::AudioBuffer<double>& buffer, juce::MidiBuffer& midiMessages) override
    {
        jassert (isUsingDoublePrecision());
        process (buffer, midiMessages);
    }
    
    const juce::String getName() const override;
    
    bool acceptsMidi() const override;
    bool producesMidi() const override;
    double getTailLengthSeconds() const override;
    
    int getNumPrograms() override;
    int getCurrentProgram() override;
    void setCurrentProgram (int index) override;
    const juce::String getProgramName (int index) override;
    void changeProgramName (int index, const juce::String& newName) override;
    
    void releaseResources() override
    {}
    
    void timerCallback() override;
    // GSW debugger added
    ScopedPointer <jcf::ValueTreeDebugger> valueTreeDebugger;
    ScopedPointer <jcf::ValueTreeDebugger> valueTreeDebugger2;
    juce::AudioProcessor::BusesProperties getBusesProperties();
    bool supportsDoublePrecisionProcessing() const override;
    
#ifdef JUCE_POLY
    std::unique_ptr<FaustSynthesiser> fSynth;
#else
#if defined(MIDICTRL)
    std::unique_ptr<juce_midi_handler> fMIDIHandler;
    std::unique_ptr<MidiUI> fMIDIUI;
#endif
    std::unique_ptr<faustdsp> fDSP;
#endif
    
#if defined(OSCCTRL)
    std::unique_ptr<JuceOSCUI> fOSCUI;
#endif
    
#if defined(SOUNDFILE)
    std::unique_ptr<SoundUI> fSoundUI;
#endif
    
    JuceStateUI fStateUI;
    JuceParameterUI fParameterUI;
    
private:
    
    template <typename FloatType>
    void process (juce::AudioBuffer<FloatType>& buffer, juce::MidiBuffer& midiMessages);
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FaustPlugInAudioProcessor)
    
};

Here’s what I’ve done. I’ve added the postSetStateInformation method.

void FaustPlugInAudioProcessor::postSetStateInformation()
{
    int j = 5;
    auto value1 = magicState.getPropertyAsValue(":Echo:Filter:Slope");
  
    valueTreeDebugger.get();
    valueTreeDebugger2.get();
// trying to figure out how to attach Values 
    //    fDSP->fVslider1.attachToValue(value1);
}

I’m also using ValueTree debugger so at the moment this routine isn’t doing anything important but calling get() on a couple ValueTreeDebuggers. I don’t know what I’m doing here either, just to be clear. In any case like I said I do not have the variables declared directly in my class so I have to figure out where they are located if I am going to attempt to attach them.

I get the impression that postSetStateInformation is used to synchronize component values with other things somehow. I actually think that the ValueTree entry for this control was created correctly. It would be nice to be able to confirm that somehow.

Here’s the order in which certain high level things get called.

This is in the main class constructor and builds the Juce Parameter ValueTree.

image

buildUserInterface() (JuceParameterUI)

postSetStateInformation()

image

MagicProcessor::createEditor()

image

MagicProcessor::createGUIValueTree

image

And there are no more breakpoints after that so then it just runs. It does not hit postSetStateInformation()
ever again even if I move the sliders up and down and change the ComboBox selection. When do we expect that postSetStateInformation() will get called?

OK, I found it by searching the entire solution for “valueChanged”. I found the routine that gets called when you change the value in a ComboBox. You’re never going to believe what it is called.

image

As usual, the underlying issue is not what I thought and it’s more complicated than my silly conjecture.

Now that I can see what the value being returned is, I see that the ComboBox returns “1” and “2” instead of “0” and “1” which I guess I’d just assumed it would.

What’s interesting about this is that I may be able to accommodate in the Faust code by setting these values explicitly, although they have to be done in order of appearance.

e.g. instead of:

filterdB = vslider("h:Filter/[03]Slope[style:menu{'12 dB/oct':0;'24 dB/oct':1}]", 0, 0, 1, 1);

I can write:

filterdB = vslider("h:Filter/[03]Slope[style:menu{'12 dB/oct':1;'24 dB/oct':2}]", 0, 0, 1, 1);

and maybe it will just work. Let’s see.

All right. The PGM ComboBox is “doing something”.

Here’s my revised Faust code.

import("stdfaust.lib");
s = vslider("/h:test/Signal[style:menu{'Noise':1;'Sawtooth':2; 'Sine':3}]",1,1,3,1);
process = select3(s - 1,no.noise,os.sawtooth(440),os.osc(440)) * 0.125;

faust2juce:
startup
image

Menu options:
image

The selections work. I wanted “noise” to be the default, though.

PGM currently with some local mods both in PGM and Faust:

default:
image

choices:

How it works:
Sine puts out Noise
Sawtooth doesn’t change what is being put out
Noise puts out a tone (the wrong thing, in any case)

image

lastCurrentID is

1 for noise
2 for sawtooth
3 for sine

I’ve just checked this and it’s the same for Juce vs. PGM. So then that kinda gets back to creating the connection between the ComboBox value and the DSP variable it’s supposed to control.

Here’s the “compute()” method for this.

iSlow0 is most likely “s - 1”.
We then set iSlow1 and iSlow2 as 0 and 1, if iSlow0 is 0 or 1 respectively.

So, if S = 1,
iSlow= 0
iSlow1 = 1
iSlow2 = 0

So, if S = 2,
iSlow= 1
iSlow1 = 0
iSlow2 = 1

So, if S = 3,
iSlow= 2
iSlow1 = 0
iSlow2 = 0

As for the rest of it, typical Faust generated DSP, it’s incomprehensible without a LOT of effort.

A note about ComboBox: Juce treats ID=0 as a special value, that’s why the Ids are not zero based but base one. IIRC you might not get a notification if Id = 0 was selected.

Yeah, generated code is rarely pretty. Good luck digging through. (Never used faust myself)

OK, well that’s good to know. In any case I now have the ability to compile a single source file as either straight Juce or PGM with a #define, so all other aspects are the same. So I will just compare them to see where the gap is. The PGM combobox itself has 3 distinct values, 1, 2, 3. The mapping to the DSP is strange because it does something but the wrong thing. At least I can trace it now.

Boy, being able to trace the ComboBox changes sure made a lot of difference! So I had to create a new type, FaustPlugInAudioParameterChoice, to match up with the AudioParameterChoice that PGM is expecting to find.

For the reflectZone() and setValue() functions I copied the expressions from the …Bool which maybe is not the right thing to do. Let me copy them from the Float. Oh, I can’t do that as it doesn’t support the range.start and range.end calls.

Anyway, here’s a current debug trace.

I am starting the app.

image

image

image

image

application runs.

Now, I change to noise (1).
image

image

This is WRONG. The value is being shown as 0.5.

image

image

Value is 0.

Now select Sawtooth (2).

image

newValue = 0.5, this is not correct.

Now, select Sine (3):

image

image

image

image

image

(runs until next change)

Ok this is very suspicious here.

image

newValue = numItems > ? selected / (float)(numItems - 1) : 0.0f;

So this takes the numItems, and if it’s not > 1 then newValue = 0;
So if you only have 1 item in your list it’s not much of a list and the value you get is 0. Not 1.
But then we divide by the number of items - 1.

So if the range for selected goes from 0 to items - 1, then this makes the output range go from 0.0 to 1.0 for however many steps that is.

First note: It calls getSelectedItemIndex, not Id. So it will be from zero to numItems.

Second: It sets the parameter as normalised value, so 0…1 is the expected outcome.

Okay, well somewhere along the way my code is not scaling the result properly. But at least I know the plumbing works, which is a relief. I’ll most likely locate the issue some time within the next decade. :wink:

Actually maybe it will be sooner.

Here’s my code where I create a (new type, since up to now Faust doesn’t use these) FaustAudioParameterChoice inherited from juce::AudioParameterChoice. Like I said earlier, I tried to copy the accompanying (reflect/setValue) code from FaustAudioParameterFloat, but ran into a member issue, so copied the code from AudioParameterBool instead (simply because it did not have this issue). But it has a different issue! I think I’m getting close.

Note that I call the juce::AudioParameterChoice with some parameters, but numerically I am only passing it the “init” value. Maybe I thought it could figure out how many there were by the number of strings in the array. It looks like maybe there are a variety of ways to call the constructor and I blame my lack of C++ experience in not understanding what is meant here.

However if I’m going to be working with this sort of code I’d better understand what this means. Back soon.

In the SignalGenerator example, this is added to intercept when ComboBox changes occur. It looks up what the ID of the selected item is and then directly enables the correct oscillator.

It does use value though, which I think of as somewhat odd. We are getting two values passed in which essentially refer to the same thing. I don’t know. I’m not a professional software developer. I’ll accept it for now.

I have to make sure that the actual numeric value returned is correctly scaled from 0 to (numItems - 1) because that value is used directly in the Faust-generated DSP code. I have to process the ComboBox value in a generic way as I cannot write a change listener for a specific control. I mean, sure, if I were desperate to finish a project I was getting paid for I’d work directly in C++ but I am trying to make it so that Faust can generate and work with these things automatically.

OK, I have traced my way through some fairly interesting parts of the code. I am pretty sure I know what the issue is, even if I don’t yet know the solution.

Initially I was using a ComboxBox with only two selections, and it never changed what the DSP code did, so I assumed “ComboBoxes don’t work”. I didn’t see where to put a change listener so I assumed there wasn’t one. However there is the default change listener which is in the Juce classes. So I traced that. And in fact it does work.

I made another test case with 3 selections.

What’s REALLY going on (I’m almost certain at this point) is that the Faust DSP code is expecting 1, 2, or 3 coming back from the ComboBox, but it’s getting 0, 0.5, 1.0. This most likely boils down to the code which I added to interface Faust to PGM (duh). But at least it’s not something else! So now I know where I should focus. It seems like such a simple and obvious issue too, but it sure took me a long time to get to the bottom of it.