Hey everyone. Sorry for the basic question! I’m just trying to wrap my head around ValueTrees and APVTS and when we should use one or the other. My current understanding is that APVTS makes the process of adding ranged parameters, as well as connecting parameters to the processor and to the UI a bit easier, but with the drawback of not being able to copy it around like a regular ValueTree. From a distance, the standard ValueTree type seems to be simpler and more versatile on account that I can pass it around like a shared pointer.
Would anyone be able to provide a bit of clarity on the differences and uses between them, and maybe some examples of when each would be better than the other?
APVTS uses a regular ValueTree, which you can access through its public
state property. You could pass this around if you wanted, as you would any other ValueTree. You can’t create an APVTS from a ValueTree though so once you’ve done that you lose the ability to get pointers to parameters, add attachments, etc.
I typically just pass the APVTS around as a reference to anywhere that needs it, there’s not really any harm in that. You could also use a
shared_pointer<APVTS> if you really want.
It seems that APVTS is designed to encourage a flat layout of parameters. Even though I can create parameter groups and add those to my layout, it doesn’t seem like there’s anyway to access those groups through the APVTS. Is there a reason for this restriction?
Also, one thing that’s bugging me is having to use strings to get access to my parameters. I know I can #define a bunch of string ids or something. But is there a common technique to reduce how much we need to use string searches to get parameters? I’m thinking maybe extending or wrapping the APVTS and caching each parameter in a member variable.
No, because there is no restriction. The Parameter and ParameterGroup have a common base class, so in the constructor you can easily create a parameter tree.
For the strings you can use e.g.:
static constexpr auto gain="gain";
Or for the fancy ones:
#define DECLARE_ID(Arg) static constexpr auto *Arg = #Arg;
That is advisable, have a member and ideally dynamic_cast to the right type, that way you can use this to get the right range and type:
juce::AudioParameterFloat* parameter = nullptr;
// resolve in constructor:
parameter = dynamic_cast<juce::AudioParameterFloat*>(apts->getParameter(IDs::gain));
// use it when needed:
auto value = parameter->get();
I suppose what I was getting was that theres no ‘getParameterGroup()’ method for the APVTS, so actually making any real use of the parameter groups feels really cumbersome and therefore discouraged.
What I’ve found myself doing is assigning the parameter member in the createParameterLayout() methods by getting the raw pointer from the unique pointer as follows…
//Declare parameter point in header
std::unique_ptr<juce::AudioParameterFloat> gainTemp = std::make_unique<juce::AudioParameterFloat>("Blah Blah Blah");
gainParam = gainTemp.get();
Seems to be working for me so far. Any reason not to do it this way?
Also is there ever a time when the pointers to the parameters would be invalidated? Perhaps when the state is changed through setStateInformation()?
The last question connects the first:
Originally the hosts and JUCE didn’t support changing parameter layouts. There is good reason to do so, because the recorded automations will be messy, a changing range changes the meaning of the normalised automations. And in some plugin APIs the parameters are simply indexed in order of appearance, which is also messed up when adding or removing a parameter in the middle.
So especially at runtime, it is a bad idea to change the parameters. But since some hosts allow it, there is now the setParameterTree, which would allow that scenario.
If you ever call that function, all parameter pointers you keep anywhere in the code will be invalidated (unless you manage to reuse them from the existing version, but I think you won’t get the unique_ptrs out, so it will discard the old parameters).
The APVTS doesn’t support changing the parameter layout. I haven’t checked the sources, but I expect things to break when you call setParameterTree().
Thats good to know! It took me a while to figure out that the parameter layout and the value tree are two seperate things. I was getting confused because I thought that calling apvts.replaceState() meant replacing the parameter layout.
parameters are owned by the processor. apvts only has pointers to them in order to give you nice extra functions like serialization via its valueTree. you can add any information to the valueTree. useful for state that should not be a parameter, but still be instance-specific. for example you could use it for saving the oversampling state or which modulators are currently used and where they are mapped at which modulation depth. usually the difference between a parameter and other state is either that the other state is not supposed to be automatable, or because there’s some dynamic stuff going on, like the amount of knots in a multi-segment envelope generator