How to reference a GEN parameter in JUCE app?

Ruslan's icon

Hello!

I would like to build an app in Projucer out of gen~ code. I already exported the code and prepared a project taken Main.cpp MainComponent.cpp UIComponent.cpp UIComponent.h from Gen Plugin Export 2.1.0 package. I started to add elements of GUI in Projucer, however I don't know how to reference a parameter of gen~ in JUCE GUI code.

void addSliders()
    {
        Component *sliderHolder = m_uiComponent->getSliderHolder();
        
        if (sliderHolder) {
            for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
                Slider *slider = new Slider();
                double min = 0;
                double max = 1;
                
                if (C74_GENPLUGIN::getparameterhasminmax(m_C74PluginState, i)) {
                    min = C74_GENPLUGIN::getparametermin(m_C74PluginState, i);
                    max = C74_GENPLUGIN::getparametermax(m_C74PluginState, i);
                }

                slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
                slider->setRange(min, max);
                slider->setName(String((int)i));
                slider->addListener(this);
                
                sliderHolder->addAndMakeVisible(slider);
            }
        }
        
        Component *sliderLabelHolder = m_uiComponent->getSliderLabelHolder();
        
        if (sliderLabelHolder) {
            for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
                Label *sliderLabel = new Label();
                sliderLabel->setText(String(C74_GENPLUGIN::getparametername(m_C74PluginState, i)), NotificationType::dontSendNotification);
                sliderLabelHolder->addAndMakeVisible(sliderLabel);
            }
        }
        
        resized();
    }
    
    void sliderValueChanged (Slider* slider)
    {
        long index = atoi(slider->getName().getCharPointer());
        C74_GENPLUGIN::setparameter(m_C74PluginState, index, slider->getValue(), NULL);
    }

From the code above I can read that there is an index of parameters, does it mean I need to name a parameter in UIComponent.cpp the same way as it named in gen~ code?

So for example:

// initialize parameter 5 ("m_bl_64")
    pi = self->__commonstate.params + 5;
    pi->name = "bl";
    pi->paramtype = GENLIB_PARAMTYPE_FLOAT;
    pi->defaultvalue = self->m_bl_64;
    pi->defaultref = 0;
    pi->hasinputminmax = false;
    pi->inputmin = 0;
    pi->inputmax = 1;
    pi->hasminmax = true;
    pi->outputmin = 6798.014921;
    pi->outputmax = 17657.181612;
    pi->exp = 0;
    pi->units = "";        // no units defined

This parameter name is "bl", so do I need to put "bl" in the name of JUCE rotary slider? Is it enough to do so?

So it should be named:

lambda__slider.reset (new juce::Slider ("bl"));
Ruslan's icon

Really no-one? I think there should be a Constructor for such a purpose at hand, really tedious task to program all the Combobox, Toggle, Rotary Slider relationships.. between Gen~ parameters and JUCE GUI elements.

Ruslan's icon

Ok.

Here is the guide for RNBO:

Where the same technic used to inject parameters:

//[MiscUserCode] You can add your own definitions of your custom methods or any other code here...
void RootComponent::setAudioProcessor(RNBO::JuceAudioProcessor *p)
{
    processor = p;

    RNBO::ParameterInfo parameterInfo;
    RNBO::CoreObject& coreObject = processor->getRnboObject();

    for (unsigned long i = 0; i < coreObject.getNumParameters(); i++) {
        auto parameterName = coreObject.getParameterId(i);
        RNBO::ParameterValue value = coreObject.getParameterValue(i);
        Slider *slider = nullptr;
        if (juce::String(parameterName) == juce__slider.get()->getName()) {
            slider = juce__slider.get();
        } else if (juce::String(parameterName) == juce__slider2.get()->getName()) {
            slider = juce__slider2.get();
        } else if (juce::String(parameterName) == juce__slider3.get()->getName()) {
            slider = juce__slider3.get();
        }

        if (slider) {
            slidersByParameterIndex.set(i, slider);
            coreObject.getParameterInfo(i, &parameterInfo);
            slider->setRange(parameterInfo.min, parameterInfo.max);
            slider->setValue(value);
        }
    }
}
//[/MiscUserCode]

As in gen~ example:

void addSliders()
    {
        Component *sliderHolder = m_uiComponent->getSliderHolder();
        
        if (sliderHolder) {
            for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
                Slider *slider = new Slider();
                double min = 0;
                double max = 1;
                
                if (C74_GENPLUGIN::getparameterhasminmax(m_C74PluginState, i)) {
                    min = C74_GENPLUGIN::getparametermin(m_C74PluginState, i);
                    max = C74_GENPLUGIN::getparametermax(m_C74PluginState, i);
                }

                slider->setSliderStyle(Slider::SliderStyle::LinearHorizontal);
                slider->setRange(min, max);
                slider->setName(String((int)i));
                slider->addListener(this);
                
                sliderHolder->addAndMakeVisible(slider);
            }
        }
        
        Component *sliderLabelHolder = m_uiComponent->getSliderLabelHolder();
        
        if (sliderLabelHolder) {
            for (long i = 0; i < C74_GENPLUGIN::num_params(); i++) {
                Label *sliderLabel = new Label();
                sliderLabel->setText(String(C74_GENPLUGIN::getparametername(m_C74PluginState, i)), NotificationType::dontSendNotification);
                sliderLabelHolder->addAndMakeVisible(sliderLabel);
            }
        }
        
        resized();
    }

The only difference I see is in implementation, for example there is no Gen~ tutorial and I don't know how to realize this parts of code from RNBO example:

//[UserMethods]     -- You can add your own custom methods in this section.
void setAudioProcessor(RNBO::JuceAudioProcessor *p);
void updateSliderForParam(unsigned long index, double value);
//[/UserMethods]

And:

//[UserVariables]   -- You can add your own custom variables in this section.
RNBO::JuceAudioProcessor *processor = nullptr;
HashMap<int, Slider *> slidersByParameterIndex; // used to map parameter index to slider we want to control
//[/UserVariables]

Besides I need to create GUI reference like the one in RNBO:

void RootComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved)
{
    //[UsersliderValueChanged_Pre]
    if (processor == nullptr) return;
    RNBO::CoreObject& coreObject = processor->getRnboObject();
    auto parameters = processor->getParameters();
    //[/UsersliderValueChanged_Pre]

    if (sliderThatWasMoved == juce__slider.get())
    {
        //[UserSliderCode_juce__slider] -- add your slider handling code here..
        //[/UserSliderCode_juce__slider]
    }
    else if (sliderThatWasMoved == juce__slider2.get())
    {
        //[UserSliderCode_juce__slider2] -- add your slider handling code here..
        //[/UserSliderCode_juce__slider2]
    }
    else if (sliderThatWasMoved == juce__slider3.get())
    {
        //[UserSliderCode_juce__slider3] -- add your slider handling code here..
        //[/UserSliderCode_juce__slider3]
    }

    //[UsersliderValueChanged_Post]
    RNBO::ParameterIndex index = coreObject.getParameterIndexForID(sliderThatWasMoved->getName().toRawUTF8());
    if (index != -1) {
        const auto param = processor->getParameters()[index];
        auto newVal = sliderThatWasMoved->getValue();

        if (param && param->getValue() != newVal)
        {
            param->beginChangeGesture();
            param->setValueNotifyingHost(newVal);
            param->endChangeGesture();
        }
    }
    //[/UsersliderValueChanged_Post]
}

Instead of:

void sliderValueChanged (Slider* slider)
    {
        long index = atoi(slider->getName().getCharPointer());
        C74_GENPLUGIN::setparameter(m_C74PluginState, index, slider->getValue(), NULL);
    }


I don't mind about dynamically updating parameters at this point, I just wonder why there is no such guide for Gen~ part?

Ruslan's icon

In case anyone are interested I was able to connect my sliders to an App, using the help of guys from JUCE forum. Here is what I've done to generic code in Source-App of Gen Plugin Export 2.1.0:

I declared several functions in UIComponent.h:

//[UserMethods]     -- You can add your own custom methods in this section.
    Component *getSliderHolder();
    Component *getSliderLabelHolder();
    int getNumParameters();
    float getParameter (int index);
    const String getParameterName (int index);
    const String getParameterText (int index);
    std::function<void (int, float)> setParameter;
    //[/UserMethods]

I added this functions into MainComponent.cpp:

long getNumInputChannels() const { return 2; }
    long getNumOutputChannels() const { return 2; }

int getNumParameters()
{
    return C74_GENPLUGIN::num_params();
}

float getParameter (int index)
{
    t_param value;
    t_param min = C74_GENPLUGIN::getparametermin(m_C74PluginState, index);
    t_param range = fabs(C74_GENPLUGIN::getparametermax(m_C74PluginState, index) - min);
    
    C74_GENPLUGIN::getparameter(m_C74PluginState, index, &value);
    
    value = (value - min) / range;
    
    return value;
}

void setParameter (int index, float newValue)
{
    t_param min = C74_GENPLUGIN::getparametermin(m_C74PluginState, index);
    t_param range = fabs(C74_GENPLUGIN::getparametermax(m_C74PluginState, index) - min);
    t_param value = newValue * range + min;
    
    C74_GENPLUGIN::setparameter(m_C74PluginState, index, value, NULL);
}

const String getParameterName (int index)
{
    return String(C74_GENPLUGIN::getparametername(m_C74PluginState, index));
}

const String getParameterText (int index)
{
    String text = String(getParameter(index));
    text += String(" ");
    text += String(C74_GENPLUGIN::getparameterunits(m_C74PluginState, index));

    return text;
}
    
    void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
    {
        // This function will be called when the audio device is started, or when
        // its settings (i.e. sample rate, block size, etc) are changed.

        // You can use this function to initialise any resources you might need,
        // but be careful - it will be called on the audio thread, not the GUI thread.

        // For more details, see the help for AudioProcessor::prepareToPlay()
        
        // initialize samplerate and vectorsize with the correct values
        m_C74PluginState->sr = sampleRate;
        m_C74PluginState->vs = samplesPerBlockExpected;
        
        assureBufferSize(samplesPerBlockExpected);
    }


I added m_uiComponent pointer to:

m_uiComponent = new UIComponent();
    addAndMakeVisible(m_uiComponent);
        m_uiComponent->setParameter = [this] (int index, float value) { setParameter (index, value); };

I added this rows in the custom slider definitions:

void UIComponent::sliderValueChanged (juce::Slider* sliderThatWasMoved)
{
    //[UsersliderValueChanged_Pre]
    //[/UsersliderValueChanged_Pre]

    if (sliderThatWasMoved == tempo__slider.get())
    {
        //[UserSliderCode_tempo__slider] -- add your slider handling code here..
        sliderThatWasMoved->getValue();
        setParameter(6, sliderThatWasMoved->getValue());

        //[/UserSliderCode_tempo__slider]
    }
    else if (sliderThatWasMoved == lambda__slider.get())
    {
        //[UserSliderCode_lambda__slider] -- add your slider handling code here..
        sliderThatWasMoved->getValue();
        setParameter(5, sliderThatWasMoved->getValue());
        //[/UserSliderCode_lambda__slider]
    }
    ...


That's all for now, will proceed with comboboxes and toggles tomorrow.

Ruslan's icon

Why wait tomorrow if today is going so good? Here is two other wrappers:

void UIComponent::buttonClicked (juce::Button* buttonThatWasClicked)
{
    //[UserbuttonClicked_Pre]
    //[/UserbuttonClicked_Pre]

    if (buttonThatWasClicked == auto__toggleButton.get())
    {
        //[UserButtonCode_auto__toggleButton] -- add your button handler code here..
        buttonThatWasClicked->getToggleState();
        setParameter(3, buttonThatWasClicked->getToggleState());
        //[/UserButtonCode_auto__toggleButton]
    }
    else if (buttonThatWasClicked == selb__toggleButton.get())
    {
        //[UserButtonCode_selb__toggleButton] -- add your button handler code here..
        invBut1 = (1 - buttonThatWasClicked->getToggleState());
        setParameter(17, invBut1);
        //[/UserButtonCode_selb__toggleButton]
    }
    else if (buttonThatWasClicked == gre__toggleButton.get())
    {
        //[UserButtonCode_gre__toggleButton] -- add your button handler code here..
        invBut2 = (1 - buttonThatWasClicked->getToggleState());
        setParameter(15, invBut2);
        //[/UserButtonCode_gre__toggleButton]
    }

    //[UserbuttonClicked_Post]
    //[/UserbuttonClicked_Post]
}

For toggle boxes and their inversion (note you need to declare int invBut1 and int invBut2 in .h file).

And for comboboxes:

void UIComponent::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged)
{
    //[UsercomboBoxChanged_Pre]
    //[/UsercomboBoxChanged_Pre]

    if (comboBoxThatHasChanged == phase__comboBox.get())
    {
        //[UserComboBoxCode_phase__comboBox] -- add your combo box handling code here..
        comboBoxThatHasChanged->getSelectedItemIndex();
        setParameter(12, comboBoxThatHasChanged->getSelectedItemIndex());
        //[/UserComboBoxCode_phase__comboBox]
    }
    else if (comboBoxThatHasChanged == constel__comboBox.get())
    {
        //[UserComboBoxCode_constel__comboBox] -- add your combo box handling code here..
        comboBoxThatHasChanged->getSelectedItemIndex();
        setParameter(7, comboBoxThatHasChanged->getSelectedItemIndex());
        //[/UserComboBoxCode_constel__comboBox]
    }
    else if (comboBoxThatHasChanged == ntpall__comboBox.get())
    {
        //[UserComboBoxCode_ntpall__comboBox] -- add your combo box handling code here..
        comboBoxThatHasChanged->getSelectedItemIndex();
        setParameter(23, comboBoxThatHasChanged->getSelectedItemIndex());
        //[/UserComboBoxCode_ntpall__comboBox]
    }
    ...

Here it is! I'm done, now to beta-testing the app!