I had been using the sliderValueChanged() method to update my parameters, but apparently its better to use ParameterAttachement() or SliderAttachment() so the PluginProcessor can update the parameters, and pick up, for example, any automation of that parameter in track envelopes etc.
I’m fairly new to JUCE, I’ve been looking for example code etc., there’s very little out there on ParameterAttachement especially.
Anyway, here’s what I’ve got so far. How can I implement this properly in PluginProcessor?
In PluginProcessor.cpp and PluginProcessor.h…
this is where things get cloudy for me. How do I implement a simple apvts ?
Or I’d be open to advice on how to use ParameterAttachment().
that tutorial is a nice starting point, but not well executed. first of all it doesn’t even show that one should use a method call to create the parameter layout, and 2ndly it uses unique_ptr for the attachments instead of just explaining what an initializer list is good for, even though one was used for apvts itself.
creating parameters with a free function is not required, but just best practice, so that the constructor of the processor doesn’t look so cluttered and in case some intermediate variables need to be constructed before being passed on at parameter creation. generally a much more pleasant experience.
and unique_ptr is just overkill for that situation. even considering that it automatically handles its scope it’s slightly more of a burdon than something that is just normally instantiated on the stack and has you use the → operator all the time.
watch this tut and you hopefully know what i mean. don’t worry. it’s easy peasy: Member Initializer Lists in C++ (Constructor Initializer List) - YouTube
nice. you probably want to find a better way to access the parameter though. a string compare is expensive. i personally like to make an enum with all the parameters so i can index them with their names, but without having to use a string. some people prefer to just give each parameter its own member variable in the processor. all has their pros and cons
Hey, I found this interesting and looked under the hood. getRawParameterValue takes a StringRef and uses it to look up the pointer to the corresponding ParameterAdapter in a std::map (adapterTable). So it’s just performing a table look-up. Shouldn’t it be fast enough?
It’s still unnecessary overhead, you might not want to have if you have, say, hundreds of parameters in your plugin. I am currently working on a plugin with about 20 parameters and using getRawParameterValue, but I might look into changing that at some point. (The plugin seems to be one that is going to need a ton more parameters in the future…)
Would you say there’s something wrong in using getRawParameterValue as described in the tutorial mentioned above, that is, using it just to get a pointer to the value, and thereafter dereferencing that pointer?
you see it has both the normalisation and denormalisation functions so you can freely decide which value to use in your dsp processing. its base class is AudioProcessorParameter so you also have all the methods of this class available then:
I would advice against that. The reason is, that if you already upcast to RangedBasedAudioParameter, you might as well go the whole nine yards and store it in the actual type wherever you need it.
The reason is that the AudioParameterFloat has a get() method, that returns the parameter as float in the correct range. The AudioParameterInt get() method returns an int in the correct range etc. Don’t do this by hand. Also every type conversion is a potential mistake, just as well as range conversions.
A vector requires maintenance in different places: you need to memorize which parameter is what (by index), and if the vector contents changes all your code is messed up.
For me keeping a pointer to the parameter wherever you need it is the most readable and future proof way:
// member
juce::AudioParameterFloat* gain = nullptr;
// in the constructor once in a lifetime
gain = dynamic_cast<juce::AudioParameterFloat*>(apvts.getParameter ("gain"));
jassert (gain); // double check the parameter exists and has the correct type
According to the documentation, getRawParameterValue returns a pointer to a floating point representation of a particular parameter (a std::atomic<float>*) which “a realtime process can read”.
Can a juce::AudioParameterFloat* likewise be read by a realtime process?
i still really dislike this approach, because it means you can’t just have one big vector with the parameters, except with some ugly casting in processBlock. and who cares if some parameters are float that could also be bool? if you check for a bool being false or true or a float being smaller or greater than .5 doesn’t make a difference. most parameters, even int or bool are being used in some way where they have to be converted into a float type parameter anyway, for example if you wanna smoothen their value or when combining them with the output of other parameters. so actually having an advantage of parameters not being float all the time doesn’t happen very often.
edit: in fact checking for a float being greater than .5 is even better than checking for a bool being true or false, because true is everything except 0, so if you have something like FL studio’s parameter randomizer randomizing the bypass parameter you just completely knock out the entire plugin. a switch should always be a 50:50 thing
That is exactly my point, you don’t do it yourself, you use the framework and it’s potential. And fact-checking… yes, juce does the right thing, I don’t have to spend a thought
ok nice. but isn’t it kinda one conditional too much then?
let’s say i have a parameterBool for a polarity switch. so while getting the parameter value there is the value > .5 conditional returning to bool. then in processBlock i check if that bool is true and then multiply all audio with -1.
whereas when the parameter always just returns its float you can check for bigger than .5 yourself in processBlock and only have 1 conditional.
to me it just seems like wrapping a parameter around something that pretends like a parameter’s heart is not an atomic float doesn’t really help