I've been thinking about this today. Just in the middle of moving to a ValueTree for almost everything.
So, here's a sketch to consider. It just tackles getting parameter data into the processor, though could be adapted for other types of data. All feedback welcome. It's untested, and probably won't even compile yet. But hopefully will communicate the idea...
I might have a think about a more general value tree synchroniser. It'd be useful.
This one just deals with specialist parameter objects on the audio side (which handle interpolation, modulation and all sorts). On the GUI side there'll be a companion object which will register with this one which will handle updates to and from the ValueTree in a straightforward fashion.
LockfreeCallQueue handles the comms between the threads.
/** Contains plugin parameters.
Functions that should be called on the GUI/Message Thread are
marked "gui*" and those that should be called on the audio
thread "audio*"
*/
template <int paramCount>
class PluginParameterDatabase {
typedef std::function<void(int,float)> GuiCallbackFunctionType;
PluginParameterDatabase() {
for (int i = 0; i<paramCount; ++i)
clone[i].setSource(&data[i]);
callGui = false;
}
/** Call when the user changes something on the GUI. This puts
a notification of the change into a queue which will be
updated at a nice safe moment on the audio thread when you
call audioSynchronizeWithGui() */
void guiSetParameter(int parameterNumber, float value) {
changeQueueFromGui.callf(std::bind(&PluginParameterDatabase::setParameterNow, this,
parameterNumber, value));
}
/** Call when the host sends a new value for the parameter. */
void audioSetParameter(int parameterNumber, float value) {
setParameterNow(parameterNumber, value);
/* And now tell the GUI. */
changeQueueFromAudio.callf(guiCallback, guiCallbackObjectPtr, parameterNumber, value);
}
void audioSynchronizeWithGui() {
changeQueueFromGui.synchronize(); /* Call the shit in the queue. */
}
/** Set up the GUI callback function for change notification.
Unfortunately we need to tell the Audio Thread about this
so it's a little more complicated than it ought to be.
It puts the request to set the gui callback function into
the queue for the audio thread to set-up. */
void guiSetGuiCallback(GuiCallbackFunctionType f, void * objectPtr) {
changeQueueFromGui.callf(std::bind(&PluginParameterDatabase::audioSetGuiCallback, this, f, objectPtr));
}
void guiSynchronizeWithAudioThread() {
changeQueueFromAudio.synchronize();
}
void guiRemoveGuiCallback() {
/* Might as well stop putting things in the
the queue when the GUI gets deleted.
And better clear the queue too! */
#warning Shit! Where do we clear the queue to avoid some nasty race condition. Might need a new clear function on the FIFO.
changeQueueFromGui.callf(std::bind(&PluginParameterDatabase::audioCancelCallback, this));
}
/** Call when the host requests the value of a parameter.
Note: when you want the value of a parameter for processing
audio you should be using the values from your voice's instance
of PluginParameterClone which will have any needed modulation
and interpolation applied.
*/
void audioGetParameter(int parameterNumber) {
return clone[parameterNumber].getRaw(); /* return the unmodulated, uninterpolated value. */
}
/** Get total parameter count. */
int hostGetParameterCount();
private:
void setParameterNow(int parameterNumber, float value) {
/* This set will use interpolation. */
clone[parameterNumber].set(value);
}
void audioSetGuiCallback(GuiCallbackFunctionType f, void * objectPtr) {
callGui = true;
guiCallback = f;
guiCallbackObjectPtr = objectPtr;
}
void audioCancelCallback() { callGui = false; }
bool callGui;
GuiCallbackFunctionType guiCallback;
void * guiCallbackObjectPtr;
LockFreeCallQueue<2048> changeQueueFromGui;
LockFreeCallQueue<2048> changeQueueFromAudio;
PluginParameterClone<paramCount> clone;
std::vector<float> data(paramCount);
};