Parameters spaghetti nightmare


#1

Soooooooo…

I managed to write my plugin and i was happy.
But i realised the vst automation wasn’t automagic.

Okay… i have to use parameters. fine.
I rewrite my code to user parameters, which wasn’t explained in the first bunch of tutorial.
Ok ok, done, i see my parameters in the DAW.
Of course they do nothing but it’s exposed.

I also managed to load & save state. first using Memory because that’s what the tutorial explained, then i rewrote it using XML because that what the tutorial explain a few line later.

all good, all good.

Except that i lost all way of comunicating between UI and processor.
I read more stuff, more code, more tutorial, each with their own way of handling this.

I more or less begin to understand but i can’t implement it.
I read more stuff and i learn about AudioProcessorValueTreeState and that’s the way to do it correctly and it seems i now have to rewrite all my parameters stuff… again.

My frustration knows no bound.

What’s the correct & clean way to expose parameters to both the DAW and the PluginEditor ?
I simply have no idea how to read/write parmeters from the PluginEditor.

On one side i read i should never ever update the UI from the PluginProcessor.
On the other side i read that the PluginProcessor should update the UI when it receive parameter automation (via a timer callback and virgin pixie dust).

How should i update the variable of my audio algorithm from the UI when a slider value is changed ?
The easy way explained in the first tutorial is simply to expose the variable in the header, as a public member, and the job is done.
But i want automation i need to use parameters and hell break loose.

my reference is : https://www.juce.com/doc/tutorial_audio_parameter
It’s not using AudioProcessorValueTreeState and i can’t find tutorial about it but when i search in the forum it’s what is recommended.

Well… i don’t know what to do really. I know it my fault, i’m not the first one trying to write a VST plugin with JUCE and somehow other people made it works. But i’d like to know how :smiley:


#2

It’s fine for both the processor and UI to hold a pointer or reference to each parameter.

When the UI wants to update a parameter it should call beginChangeGesture(); setValueNotifyingHost(value); endChangeGesture();

Every time process of your plugin is called, you should call AudioProcessorParameter::getValue() to get the latest value.

You should make a subclass of AudioProcessorParameter, in setValue() you should start a timer, trigger an async update, send a change message etc that your UI listens for and then cals getValue() to update itself.

Here is a plugin of mine that demonstrates it fairly simply. https://github.com/FigBug/Voc (Make sure you clone the submodules as well)


#3

While i appreciate the help i’m afraid it’s not going to help me since you use 3rd party plugin :frowning:


#4

The automation kind of work in my current plugin.
if i change param value from the DAW, the effect change.

However my UI in some kind of state and every time i try to change the slider it reset to its position and have no effect on the AudioProcessor.

I’m trying to find what’s happening.

i’m not usring setValueNotifyingHost(value); it’s not written anywhere in any tutorial i’ve read.
i’ll check that.


#5

I fixed some stuff and now i’m noticing a problem.

i have this :

addParameter(LPFparameter = new AudioParameterFloat("Low_Pass_Frequency", "Low Pass Frequency", NormalisableRange<float>(20,20000,1,0.5), 20000));
addParameter(LPRparameter = new AudioParameterFloat("Low_Pass_Resonance", "Low Pass Resonance", NormalisableRange<float>(-16,16,1), 0));

When i use the automation the parameters are in the correct range in the PluginProcessor.
However, in the UI, i get value 0->1 instead of the correct range.

EDIT : “You’ve reached the maximum number of replies a new user can create on their first day. Please wait 13 hours before trying again.”

so i can’t reply anymore :smiley:


#6

You need to use AudioParameterFloat::get() rather than AudioParameterFloat::getValue()


#7

I sorted most of the mess.

As far as i know i only have one problem left.

addParameter(LPFparameter = new AudioParameterFloat("Low_Pass_Frequency", "Low Pass Frequency", NormalisableRange<float>(20,20000,1,0.5), 20000));
addParameter(LPRparameter = new AudioParameterFloat("Low_Pass_Resonance", "Low Pass Resonance", NormalisableRange<float>(-16,16,1), 0));
  • i use normalizablerange.
  • when i change values from my DAW, it works in the audio processing and the UI is also updated accordingly.
  • however, when i try to notify the host that a value has changed from the UI all the parameters goes crazy.

When i retrieve value from daw the normalization works automagically
When i send value to daw, my guess is that the value aren’t normalized (why? it works one way and not the other?)

void KeruFilterAudioProcessorEditor::sliderValueChanged(Slider* slider)
{

const OwnedArray<AudioProcessorParameter>& params = AudioProcessorEditor::processor.getParameters();

for (int i = 0; i < params.size(); i++)
{
	if (params[i]->getName(18).compare(slider->getName()) == 0) {
		//params[i]->setValueNotifyingHost((float)slider->getValue()); //uncomment this to crash
	}
}
}

edit : i’m not using valuetreestate, not using genericeditor. i’m simply using the default plugin template and i created the slider programatically.

KeruFilterAudioProcessorEditor::KeruFilterAudioProcessorEditor (KeruFilterAudioProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{


LPFslider.setRange (20, 20000, 1);
LPFslider.setName("Low Pass Frequency");
LPFslider.setSliderStyle (Slider::Rotary);
LPFslider.setTextBoxStyle (Slider::TextBoxBelow, false, 80, 20);
LPFslider.setBounds(40, 40, 90, 120);
LPFslider.setSkewFactor(0.5);
LPFslider.setTextValueSuffix("Hz");
LPFslider.addListener (this);
addAndMakeVisible(LPFslider);

LPFlabel.setText("Low Pass", dontSendNotification);
LPFlabel.attachToComponent(&LPFslider, false);
LPFlabel.setJustificationType(Justification::centredTop);
addAndMakeVisible(LPFlabel);

LPRslider.setRange(-16, 16, 0.1);
LPRslider.setName("Low Pass Resonance");
LPRslider.setSliderStyle(Slider::Rotary);
LPRslider.setTextBoxStyle(Slider::TextBoxBelow, false, 80, 20);
LPRslider.setBounds(140, 40, 90, 120);
LPRslider.addListener(this);
addAndMakeVisible(LPRslider);

LPRlabel.setText("Resonance", dontSendNotification);
LPRlabel.attachToComponent(&LPRslider, false);
LPRlabel.setJustificationType(Justification::centredTop);
addAndMakeVisible(LPFlabel);

//parse processor param
const OwnedArray<AudioProcessorParameter>& params = AudioProcessorEditor::processor.getParameters();
for (int i = 0; i < params.size(); i++)
{
	if (params[i]->getName(18).compare("Low Pass Frequency") == 0) {
		LPFslider.setValue(params[i]->getValue());
	}
	else if (params[i]->getName(18).compare("Low Pass Resonance") == 0) {
		LPRslider.setValue(params[i]->getValue());
	}

}

startTimer(100);
// Make sure that before the constructor has finished, you've set the
// editor's size to whatever you need it to be.
setSize (270, 200);

}


#8

This needs to be normalised:

if (auto p = dynamic_cast<AudioParameterFloat*> (params[i]))
    p->setValueNotifyingHost (p->range.convertTo0to1 (slider->getValue());

HTH


#9

Woaaaaaaa that was so simple :wink:
THANK YOU SO MUCH !

Now it doesn’t work anymore from automation to the plugin but i guess that the fact that it worked before was blind luck and i have to deal with normalization in both emission and reception :smiley:


#10

it would have been to easy if it worked, right ?

void KeruFilterAudioProcessorEditor::timerCallback() 
{
const OwnedArray<AudioProcessorParameter>& params = AudioProcessorEditor::processor.getParameters();

for (int i = 0; i < params.size(); i++)
{
	if (params[i]->getName(18).compare("Low Pass Frequency") == 0) {
		if (auto p = dynamic_cast<AudioParameterFloat*> (params[i]))
			LPFslider.setValue(p->range.convertFrom0to1(*p));
	}
	else if (params[i]->getName(18).compare("Low Pass Resonance") == 0) {
		if (auto p = dynamic_cast<AudioParameterFloat*> (params[i]))
			LPRslider.setValue(p->range.convertFrom0to1(*p));
	}
}
}

this is my last step for a fully functional plugin. Unless fixeing this create another problem of course…

But i shouldn’t need to convert since it works in the audioprocessor …
automation work until i tried to modify the value from the (broken) UI


#11

OMG i found the problem !!!

i just realized that when i receive an automation, i set the slider accordingly but sent a notification, therefore ableton automatically switched to automation override mode…

i fixed it by adding a dontsendnotification in this case and boom !
it works <3


#12

Glad you solved it. A cosmetic side note: instead of creating the reference to the parameters array, you can use the new container iterators:

for (auto param : processor.getParameters())
{
    if (param->getName(18).compare("Low Pass Frequency") == 0) {
    // etc...

Reads a bit nicer…


#13

And you know that String has an operator==, right…?

…and you should exit the loop when you find the one you want, rather than wasting time iterating all the remaining items

…and using a string name to identify a parameter is really poor technique, even if you were to use a constant instead of a copy-pasted string literal (!)

…and why iterate? If you have a timer that’s constantly looking up these parameters and using them, why not just keep pointers to them in your object so there’s no overhead.

Sorry… I have difficulty switching off my inner code-review sometimes :slight_smile:


#14

Sorry, if that’s OT, but I realised, that the AudioProcessorParameter does not have the ID.
Is this class still used anywhere, or could it be melted with AudioProcessorParameterWithID, so that the AudioProcessor returns a list of parameters with ID?
The way how it is now, you will have to do a dynamic_cast every time…


#15

Yeah, that’s a good point, though I’d say that even a non-textual ID is a bad way to access the parameters, as you’d still need to iterate the list to find each one when you need it.

I think the best plan for params where you do actually need to use them often is to pre-cast them and store pointers to them, which avoids both the cast and the lookup.

Or use a std::unordered_map.

Or avoid this altogether by doing it the other way round and putting the logic into the parameter object itself, either with a special parameter subclass, or by having one that takes lambdas to use for custom getting/setting.

(…I think all I’m trying to say here is that of the 50 different ways you could use to access these objects, iterating the list and doing string comparisons is pretty much the worst possible, so any beginners reading this, please don’t copy the code above!)


#16

Please read my original post, as well as : Getting started in august 2017 with Juce 5.1

of course my code is a disaster.

  • Why iterate when i have a timer doing it already? Because i didn’t have a timer when i wrote this.
  • i have tons of redondant stuff everywhere
  • i didn’t refactor/rewrite because i wanted to keep what’s working alive and moderately independant of other possibly broken stuff until i fixed bugs.
  • i couldn’t find anything but string to identify parameters.
  • the tutorial have automagic at works using the genericeditor, but i don’t use this because the projucer template don’t do it and the first tutorial don’t mention it (or i missed it).
  • i started my code without using parameters, i added them afterward when i realized i would need them to handle automation. so my UI is built manually the old school way.
  • my code is extremely poorly architectured and duct taped becaise i learn as a write.
  • some tutorial and sample code use shortcut that only works specifically in the context of the tutorial with extremely simple app/plugin. (eg : some sample have only 1 slider so no need to iterate/identify)
  • i have a dislike for C++ (but i love C) and avoid using it as much as i possibly can, therefore i barely know anything about C++11 and didn’t know C++14 existed until 2 days ago. i’m not a professional coder, i’m a linux sysadmin a DBA. i know tons of different language, master of none. But that’s not the reason my code sux. it does because, while the tutorials are greatly appreciated, they’re a mess. (i can come up with suggestion)
  • a lot of opensource code i found use external module that simplify parameters handling, UI construction, and other kind of glue between processor, editor, host. i don’t, and it wouldn’t be wise to do it until i learn more about how the “core” juce works. i installed it for the first time ever 3 days ago.

it’s no excuse, i hate bad code as much as anyone (or perhaps more than anyone, since i’m a sysadmin and have to works around poorly written code that mess up with the uptime of my servers) and i greatly appreciate your help, input, criticism (as long as it’s not “you should learn more about the language” : i know, right ? i code since 30+ years and still learn and i will still have to learn in 30+ years :wink: ).

thank you <3


#17

I don’t think that waiting to fix bugs to refactor is the proper approach. With any good VCS, if you have an issue where you have to go back in time, you can. So no excuse not to refactor :wink:
But I agree that the tutorials are not always very clear, but it’s a difficult thing to do (writing tutorials).


#18

I understand and disagree :smiley:

In a normal situation, yes, i agree. when i bug appear, you fix it.
when you can refactor to make everything better, do it too.
But what if “it never worked as expected in the first place” ?

When you have something that is barely held together with black magic and random luck : “if it’s not broken, don’t fix it” is not a bad motto :blush:


#19

Great that you’re learning with JUCE!

When people are learning publicly on the forum, we do try to flag up any dodgy code snippets so that other beginners don’t assume it’s a good example to copy. No offence intended, hopefully it’s helpful feedback for you too!