A JUCE module to automatically handle plugin parameters


#1

Hi,

This is a library that I started writing on March 2012 and I have been using ever since in my plugins. Hopefully it will be useful to some of you too, specially those who don’t have the time or the resources to create a similar system.

PluginParameters is a JUCE module which manages automatically different types of plugin parameters. It intends to help automating things in the development of cross-platform plugins taking it from where JUCE leaves you in the wild. It should be simple to use with only a few lines doing the job (see an example below).

This version of the PluginParameters module is released under the GPLv.2. Please donate if you find it useful. Alternatively, a Commercial License (available for 200eur) allows you to use this module in closed-source projects and provides you full compatibility with the latest release of JUCE, support for XCode and Visual Studio and extra features.

Links: [Download] [Reference] [Doxygen Documentation]

What it does

  • It can be used to build a VST/AU/RTAS plugin or built-in plugins (AudioPluginInstance’s).
  • Includes methods to map any integer, float or bool parameter types to the host float range of [0,1] back and forth linearly, logarithmically (w/out a 0, w/out a sign). Custom mappings can also be defined.
  • Parameters can be grouped as ParamGroups, which may be nested. This allows to create quite easily independent plugin building blocks.
  • Arrays and Matrices are defined for each defined parameter type (they are both a subclass of ParamGroup).
  • Allows parameters to be registered at the host (automated by default) or to be left unregistered (non-automated) but saved/restored anyway internally at the end/beginning of each session.
  • Provides methods to load/save selected parameters from/to the host session automatically.
  • Should compile correctly in Visual Studio and Xcode.

Only with a commercial license

  • Includes Undo/Redo’s from parameter changes made in the UI.
  • Preset Manager.

The Preset manager is a class which provides utilities to load, save, rename, delete, reset XML preset files that store all parameter values of a ParamGroup recursively. It marks files that are loaded as read-only so that they cannot loaded twice or modified outside the plugin, supports preset “folder-files” (where the actual preset file is created inside a folder of the same name) and tracks unsaved changes with an “*” next to the preset file name.

A possible user interface for this class (PresetsComponent) is available in the PluginParametersGroupsDemo example. In this project you may find too (commented) code to setup the Preset Manager.

Please refer to the last section of the Reference (Advanced Usages), to see how Undos/Redos, custom float parameter mappings and parameters with other float and int types are supported.

Example plugins

  • PluginParametersDemo implements a gain which can be set linearly or logarithmically. It includes examples of the following parameter types: FloatParam, LogParam. LogWith0Param, LogWithSignParam, IntParam, BoolParam.
float floatVar;
float logVar;
float logWith0Var;
float symSignedLogVar;
float asymSignedLogVar;
int intVar;
bool boolVar;
bool boolButtonVar;

enum Params{        
  floatIndex=0,
  logIndex,
  logWith0Index,
  symSignedLogIndex,
  asymSignedLogIndex,
  intIndex,
  boolIndex,
  boolButtonIndex   
};
  
void initParameters(){        
  addFloatParam(floatIndex,"float",true,true,&floatVar,-6.f,6.f);
  addLogParam(logIndex,"log",true,true,&logVar,0.001f,6.f);
  addLogWith0Param(logWith0Index,"logWith0",true,true,&logWith0Var,0.001f,6.f);
  addLogWithSignParam(symSignedLogIndex,"symSignedLog",true,true,&symSignedLogVar,-6.f,6.f,0.001f);
  addLogWithSignParam(asymSignedLogIndex,"asymSignedLog",true,true,&asymSignedLogVar,-4.f,3.f,0.001f);        
  addIntParam(intIndex,"int",true,true,&intVar,0,4);
  addBoolParam(boolIndex,"bool",true,true,&boolVar);
  addBoolParam(boolButtonIndex,"boolButton",true,false,&boolButtonVar);
  }

  • A sustain effect.
  • A delay effect.
  • A “note gain” effect (every note velocity is multiplied by a different gain).

The code is structured in three independent modules contained in the following folders: MidiSustain, MidiDelay, MidiNoteGain and it is meant to show how the ParamGroup class works. Additionally an example of the pre-defined IntParamArray class is included in the “note gain” effect.

Remember too that a non-functional/demo PresetsComponent is included in this project.

Enjoy!


#2

Sounds cool! Too busy to check it out right now but will try to find time soon!


#3

Looks useful but I’d advise organising the project a little bit. The header file has over 3000 lines!


#5

No not really just that I was just reading through it to see how it works but that can be difficult with such large files.

Anyways, fair play on the work!


#8

hi,

It’s funny how many variants of parameter handling classes one can come up with. Sooner or later, after getting tired of writing boring repeating stuff dispatched in several places in the code I guess everyone tries to build some kind of declarative reflexion mechanism to handle parameter handling / serialization, automatic GUI… IMHO there’s no definitive answer to this, just different design choices and technical implications.
It would be much easier if most of this could be provided at the language level using meta-reflexion like in python, java…

Here are some remarks from what I’ve seen by quickly reading through your code:
[list]
[]Mapping is just a facet of a parameter, it’s more flexible to parametrize your classes by a Mapping policy than having lots of classes derived from a single base[/]
[]Parameter display and serialisation could also be policies[/]
[]pointer to members are great but you’ll often find that you need a setters and getters to handle some situations, std::function/boost::function allows to abstract that kind of details[/]
[]templates are great and helps to avoid repeating code.[/]
[]it can be handy to think about the Boolean, Integral, Floating-point concepts as being distinct than their concrete implementation: float, double, bool, int, uint, int64… etc.[/]
[]there are great ideas to borrow from boost::python, luabind[/]
[/list]

In the end it’s just a compromise between the time you want to invest, the productivity gains and the number of edge cases you want to deal with.


#10

Not at compile time, but most of this should be static data initialized only once. Only [b]this[/b] should be bound when calling parameter delegates.

[code]If you had to define the mapping alone, you would probably declare as well a class for it, so why not putting everything together?[/code]

Exactly, mappings can be standalone classes that provide bidirectional mapping betwen natural and normalized ranges. Some mapping may have some préconditions on the kind of arguments that are accepted (float) or the range of its parameter (like being strictly positive for a logarithmic mapping)

Serialization and display are no more than another kind of mapping between natural value and text / binary

Composition is more flexible than inheritance. You could pack all together at compile time like this (this is just some pseudo code):
[code]typedef Parameter<float, LogMapping, DefaultSerializer<float>> LogParam;[/code]
or at runtime like this if you find it more convenient:
[code]Parameter<float> p(1000, 20, 20000, LogMapping(20, 20000), DefaultSerializer<float>());[/code]

Ultimately the more elegant solutions are template-heavy and can quickly become a nightmare to implement:

Here's a reflection library inspired by luabind / boost::python:
https://github.com/tegesoft/camp

good continuation on this project.

Not at compile time, but most of this should be static data initialized only once. Only this should be bound when calling parameter delegates.

Exactly, mappings can be standalone classes that provide bidirectional mapping betwen natural and normalized ranges. Some mapping may have some préconditions on the kind of arguments that are accepted (float) or the range of its parameter (like being strictly positive for a logarithmic mapping)

Serialization and display are no more than another kind of mapping between natural value and text / binary

Composition is more flexible than inheritance. You could pack all together at compile time like this (this is just some pseudo code):

or at runtime like this if you find it more convenient:

Ultimately the more elegant solutions are template-heavy and can quickly become a nightmare to implement:

Here’s a reflection library inspired by luabind / boost::python:

good continuation on this project.


#14

Just wanted to chime in and say that this looks fantastic. Going to give it a spin, and I may well end up contacting you about a commercial license. I was going to build something like this myself, but this looks even better than what I had planned, and will save me a ton of time.


#15

I just built this as a 64bit AU to test a common issue i have seen in Logic Pro with plug-ins that have more than one control that change a parameters value. Since the demo you provide already has a situation like this with parameters controlling the value of each other (float/log sliders in the non-group demo), I did not have to change, add or modify your code at all. I just built it as a 64 bit AU in Juce 2.0.11, Xcode 4.6.3, OSX 10.7.5.

 

Well, I start up Logic Pro X (and Pro 9), and the AU does not validate. Here's the important part from AUVal...

# # # 8 Global Scope Parameters:
WARNING: retrievedValue = 0.000000 (was 0.000000), Parameter did not retain minimum value when set
WARNING: retrievedValue = 0.000000 (was 0.000000), Parameter did not retain default value when set
ERROR: Parameter values are different since last set - probable cause: a Meta Param Flag is NOT set on a parameter that will change values of other parameters.

* * FAIL
--------------------------------------------------
AU VALIDATION FAILED: CORRECT THE ERRORS ABOVE.
--------------------------------------------------

It looks like it needs the metaParamFlag info, which is in the Juce Documentation for the audioProcessor class, but not accounted for in your project (only the isAutomatedFlag is). I'm not really sure what AUVal is expecting for it's 'retained' values, but I am assume it just runs through each parameter, sets it, and at the end of all of them, checks the value again and if they no longer match (because a different control reset that value), then it throws the warnings and error.

 

The same AU however, loads and works fine in Live 9. hmmm!?

 


#16

Here's some more info on the issue on the JUCE forum: http://www.juce.com/forum/topic/audio-unit-validation-failed-needs-meta-param-flag

 

 


#21

Hi, I've just installed this module in Juce.

I can't find the way to work with the automation by the host.  I implemented all the code suggested in the wiki.

What can I check in order to ensure that the calls from the host are correctly handled?

thank you.

 

 


#22

Now I'm sure that the method runAfterParamChange is called when the UI changes, but not when there's an automation on the knob.

Any suggestion?

 

P.S.:

I'm using Logic


#23

I solved!

The problem was I still had the setParameter implemented in my processor. I forgot that the method is overridden in the library.

 

Sorry for the useless question..


#31

Hi 

I found an issue when updgrading to the latest version v1.1.10 (the issue seems to be in the master branch as well). 


/ParamGroups.h:1315:3: warning: 'PluginParameters::TypeParamArray<juce::String>::saveOnlyNonDefaultValuesFlag' is initialized with itself [-Winit-self]
TypeParamArray(const String &name, const bool registerAtHostFlag, const LoadSaveOptions loadSaveOptions, Type* const values, int *const size, const int maxSize, bool saveOnlySizedArrayFlag = true, bool updateOnlySizedArrayFlag = true)

To fix the warning I simply removed the "saveOnlyNonDefaultValuesFlag" member becuse it does'nt seem to be used anywhere.

Best,

Anton


#33

I'm not seeing updates in the GUI when tweaking Maschine controls in the pluginParamtersDemo. Machine gui knobs reflects tweaks in the plugin GUI but the plugin GUI doesn't relfect changes from the Mashine GUI. 

 

Can anyone provide any assitance.?