OK, I'll add to the laundry list then - and I'll do it by dumping the interface documentation from my current implementation (I'm happy to share code if it helps understand what I'm trying to do).
Things worth highlighting:
- I've derived my own AudioProcessor class to minimise boilerplate
- I need certain changes in the AudioProcessor (e.g. sample rate change) to trigger modification of parameter setting limits in the GUI. This is useful for things like a biquad frequency control where I'd like the lowest & highest allowable values to be dependent on sample rate.
- Inc/Dec buttons are tricky for non-linear mappings, you can't just use slider interval as the interval is linear. Instead you need to add the GUI interval to the current value and convert back from GUI value to normalised value.
- I provide myself with lots of constructor methods so I can easily set up parameters. I also provide methods and a structured approach for naming parameters and GUI controls so they can automatically link up when the GUI is constructed.
- I've created a mediator class to handle communication between the GUI and the AudioProcessor so that I can easily change my GUI update mechanism later (e.g. from timer refresh to async fifo or maybe some funky lambda stuff)
- I precompute things like dB conversions when automation values change so that I don't have to do them repeatedly in processBlock()
PluginParameter:
A class for managing a plugin parameter used by an ObliqueAudioProcessor.
Provides an array of values for a single parameter. The size of the array is set according to the number of preset programs.
Parameters have three types of values: automation, GUI & native. The GUI and native values are linearly related, but the mapping of automation to GUI/native may be a non-linear function.
The automation value is a double normalised to the 0..1 range and is used for communication with the host. This is the root value of the parameter that all others are derived from - thus it is always updated by the set routines for the other value types. Any GUI widgets should have their motion mapped linearly with the automation value, but with the value displayed being the GUI value which may or may not be linearly mapped (see ParameterSlider).
The native value is the value used by the DSP code. To save on processing, it is pre-calculated and set whenever the automation value is set (as by definition this will happen less often than reads of this value). If the native parameter type is dB, then the linear value of the variable is also calculated and stored (this saves processing time for DSP routines). Note that dB(Volts) is used.
The GUI value is the value displayed in the GUI or input from the GUI. Setting the GUI value triggers setting of the automation value (which in turn triggers setting of the native value). The GUI value is not stored, it is calculated on demand. The number of decimal places defined for the GUI value is enforced by rounding to the nearest value whenever conversions are made internally in this class.
If isEnumeration is set to true, then a string array may be input using setEnumeration() and the elements of this array will be returned when toStringWithUnits() is called. In this case, minGUIValue should be set to 0.0, maxGUIValue should correspond to the number of elements in the array, and intervalForGUI should be set to 1.0.
The parameter has a label, units and default value - these are all consistent for the whole array of values.
The parameter name is formed from a prefix specified in the constructor as well as a suffix specified in the ConstructorArgs. This name will be used to automatically link the PluginParameter to associated ParameterSliders. The parameter name will also be used in any saved preset program files. The parameter name must be safe for use as an XML attribute name ([reference](http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name)).
This class is not necessarily thread safe - however, most operations are atomic.
PluginParameters:
A class for managing a collection of plugin parameters used in an ObliqueAudioProcessor.
Parameters can be accessed via index or via name (which is slightly slower). Methods are provided for managing preset programs, including serialising to XML and de-serialising from XML.
MappingFunction:
This class is used for mapping an automation value to either a GUI or a native value. Methods are provided for conversions in either direction. The non abstract methods are declared final so that they may not be overridden. Note that the obLib unit tests only test the non abstract methods for the LinearMappingFunction class (as a proxy for MappingFunction).
A note on setting arbitrary limits for automation (these apply to getting parameter values):
- The initial implementation has code in ObliqueAudioProcessor::prepareToPlay() change these limits when sample rate changes
- At the same time, the GUI can listen for sample rate changes and take the necessary action to update itself appropriately
- If we changed to an asynchronous fifo signalling method, then prepareToPlay() routine would need to also trigger a resending of the newly limited get values (remember that MappingFunction doesn't link back to PluginParameter and hence can't do this work by itself)
- Maybe it would be better if changes to the MappingFunction propagated to the PluginParameter and changes from the PluginParameter propagated to the GUI
- This means the MappingFunction would need to hold a pointer to its PluginParameter
- In that case we would need to avoid recursive includes
ObliqueAudioProcessor:
A variation of juce::AudioProcessor which integrates with ob::PluginParameters.
Notes on usage:
===============
1. The constructor of the derived class must initialise all PluginParameters.
<snip>
ObliqueAudioProcessorMonitor:
A class which monitors status changes in an ObliqueAudioProcessor and sends changes to listeners.
Typically you would instantiate one object of this class in a parent GUI component and then have child components register as a monitor (similar to a Listener, but synchronous).
ParameterSlider:
This slider is customised for use with ob::GUI and ob::PluginParameters. It provides an optionally visible label component and a juce::Slider. The slider operates internally with a normalised 0..1 range so that GUI movement corresponds directly with automation movement. The GUI value can be mapped in any fashion - this would typically be linear, but could be logarithmic for frequency. Hence the "feel" of any control is the same whether it is varied in the plugin GUI or by host automation. This mapping is provided by the linked
ob::PluginParameter.
If the slider style is set to IncDecButtons, then this class implements custom inc/dec buttons that work correctly for non-linear mapping functions. The standard inc/dec implementation relies on the interval property (which of course isn't consistent along the slider's range for non-linear mappings). The custom buttons work by calculating what the final desired GUI value is and then setting the corresponding automation value.
If the slider is associated with a parameter that can be altered by sample rate changes (e.g. biquad filter cut-off frequency) then you will need to add it as sample rate monitor to an ObliqueAudioProcessorMonitor object. In that case, sample rate changes in the ObliqueAudioProcessor will automatically trigger a refresh of the slider limits.
Multiple ParameterSliders may control the same PluginParameter. To do this, simply create a new ParameterSlider with the same name as the PluginParameter you wish to link to (note that this will lead to multiple ParameterSliders with the same name).
ParameterSliderMediator:
Acts as an intermediary between a collection of ParameterSliders and a PluginParameters collection. It takes care of notifying the host of ParameterSlider value changes (the mediator calls setParameterNotifyingHost() for automatable parameters or setParameter() for non-automatable parameters) and of updating ParameterSliders when parameters change (the mediator calls updateValue()). The GUI needs to call updateGUI() to pick up
parameter changes and reflect them (typically in a timerCallback).
Different types of GUI panels (e.g. BiquadControl) should implement their own mediator which inherits from this. The parent GUI components should then declare a member variable of that type (e.g. BiquadControlMediator).
ParameterSliders are added to the mediator's management set via the abstract method nominateParameterSliders(). This method needs to be provided by all derived classes, but it is automatically called by initialise(). It is vital for the initialise() method to be called after construction of an
object of this class.
Each ParameterSlider is then mapped to its linked PluginParameter by calling PluginParameter::isLinkedSlider(). A PluginParameter may be linked to multiple ParameterSliders, but a ParameterSlider can only be linked to one PluginParameter.