How to reference a GEN parameter in JUCE app?
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"));
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.
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, ¶meterInfo);
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?
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.
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!