Why is it that singletons don’t work like I expected in the context of plugins? Is it because static fields are going to be all the same because each plugin is under the same process (that being the DAW)?
Hmm interesting. I originally was doing that, but assumed it was a poor practice especially because how sharing data in a hierarchical context (GUI’s) gets tedious to manage. For example, having a GUI component that needs to access the data after being notified of a change would have to get the data store from its parent component. The data store would then have to be passed down the hierarchy until it reaches the plugin processor. When I did this, each component class had to implement a way of passing the plugin data to its children.
Is that the way to go about it? Is there a better, streamlined, “best practice” way of doing this without having redundant code for each GUI component?
Yes, static data is shared between all instances of a plugin if they are loaded in the same process and singletons are essentially static data. (They’re a static pointer to some heap memory which is shared between plugin instances).
This is a bit more complex because some hosts load plugins in separate processes so you can’t assume any static data will be shared between instances but that’s really the opposite of what you’re referring to here.
In general, singletons are a bad idea for many reasons but the simplest is that it makes it impossible to reason about ownership.
In brief, usually the best idea is to avoid shared state as much as possible. If you pass state to functions as you call them you decouple the classes so they don’t have to know about each other.
If you absolutely do need to pass some state down through your component hierarchy though the best bet is to have some kind of wrapper class that holds your “singleton” data and then you only have one thing for them all to reference.
However, this would mean that everything that references this data needs to know about it and all the functionality that comes along with that which again is generally bad practice. You’d tend to peel off bits of data so classes only reference what they absolutely have to.
One final word is to try and avoid sharing big blocks of data where possible and maximise a more MVC based approach.
But this is all a bit too complex to lay out in a few paragraphs. Just try and avoid singletons and reduce the amount of data that is shared as much as possible.
Jeez, that’s certainly good to know. Thanks! I’d wish that warning was in JUCE singleton header’s documentation!
I seeee. Is the need to only expose the data that classes only absolutely need simply to have a scalable codebase? In other words, won’t exposing only certain data will ensure future classes won’t write/read from data at the wrong time?
EDIT: This StackExchange post about why need private variables essentially answers my question.
Do you know of any example JUCE plugin that gives full example to the MVC pattern?
I’m not sure about this. It can make some code much more verbose and less descriptive of what it does. Sometimes a class exists to process and store some information that’s used in many places, and for some action the use of that information could be considered an implementation detail. You could say something like getPeakLevel (aRealtimeSignal), or getPeakLevel (theSignal, theEnvelopeParameters, someDataNeededForVectorization) -I don’t like the latter. I can think of all the rest as things that must be plugged to the peak level machine before turning it on and feeding it a signal. Also, when I’ve tried this no-ref-members all-argument approach, it hurt performance quite a bit.
I’m confused how this idea is applied to the shared data. You have to access and set the shared data somewhere in both the processor and the GUI to get stuff done (and show stuff to the user). Unless one puts every function that potentially works on that shared data in the shared data class, one would have to access that data directly in the classes that need them.
Of course if there are functions that need certain data from the shared data object, so the information could just be passed into the function with the member variables of the shared data, instead of the entire shared data object reference. But who calls the function and therefore grabs the information from shared data and passes it into the function?
An example plugin with this all fleshed out would certainly be useful.
From reading the thread I’m not entirely sure what kind of data you want to store and access, so the best solution to chose always depends on the use case.
An obvious choice when working with JUCE that proved to work quite well with complex real world plugin projects is the AudioProcessorValueTreeState. It serves multiple jobs:
Keeping your parameters in one place and managing to notify everything that needs notification, e.g. the DSP functions to be controlled, the GUI widgets that control those DSP functions and the host that wants to interact with the parameters through automation.
The public state member which is a ValueTree does not only hold the parameters data structure but allows you to story any kind of state data that can be converted to a var object. Examples from real world applications: GUI states like Plugin window size, Processing states like if some analysis phase has already passed (which should be reflected by a certain GUI widget) or the last BPM value reported by the playhead (which e.g. affects some tempo setting GUI widget)
Giving you an easy way to serialize the whole plugin state in the get or set state information callbacks
All you have to do is passing a reference to the APVTS to all classes that need it. As the instance should live in your plugin processor and the processor is the first thing that is gonna be constructed, this pattern works quite straightforward. You can even define your own baseclass for e.g. Components that should be able to attach to parameters and listen to state changes and just need a pointer to the component that constructed them (which should follow the same pattern) to do all this, which can significantly reduce the amount of boilerplate code even further.
But even if the APVTS is not the data structure of choice for your application keeping a senseful hirachy is always an important way to build any piece of code that can grow in a good way.
And singletons are not always completely bad. They should just refer to things that are really singleton in the scope of the users machine, e.g. a settings file accessed by multiple plugin instances
What @PluginPenguin wrote is a good example of how to effectively get MVC from JUCE. (I say “effectively” as it’s a kind of squashed MVC where the model and controller and kind of combined in to the ValueTree).
To be honest there is a good argument that in plugins singletons are always bad, for the reason that you don’t ever know for sure if you do have the same singleton instance or not. For example if your plugins are being run in separate processes you can’t rely on any singletons being shared so you’d have to write some kind of interprocess communication to keep your settings files in sync anyway. Doing this from the start avoids having to retrofit this afterwards in order to support out-of-process DAWs.
@kamedin obviously how far you go down this path will depend on the exact data that is being shared. My response was to a very generic original post about “shared data” and in general reducing state is a way to avoid having shared data. Moving towards this more functional approach can have drawbacks but is generally safer (in terms of thread safety), reduces coupling and interdependencies.
Performance will vary depending on the use case but for simple arguments shouldn’t be slower than member data and in many cases will be faster. The reasons for this is that small parameters are often passed in registers so incur no additional copies, you don’t have an indirection around the this pointer of the member variables and because of this the optimiser can see your code more clearly and in many cases do a better job.
This then leads performance being limited by the cache layout of whatever you’re passing to the function and if this is better than the way your data members are organised.
But again, this was a response to the question of how to avoid passing big objects of shared data to all your classes, not an advertisement to make your entire program a pure function.
I know, sorry -I took it to another place. I’ve thought a lot about this because I have a pretty tight web of dependencies and my student intuition tells me to reduce coupling, but sometimes it seems like overdesign -like, it complicates more than simplifies. I also thought arguments should be generally faster, but this only really works when they’re already in registers at the calling point. If I’m passing state, I’m just moving the indirection from the callee to the caller. It’s not black-and-white, of course. I’ve just tamed the impulse to avoid dependencies at any cost.
I have yet to find a DAW where our plugins are not sharing the singleton classes we use.
We’ve found them a great way to perform what you’d otherwise need inter-process communication for, and for running a single thread to perform tasks just once that are needed for all instances without each instance taking up CPU time to do them (and then having to deal with clashes between those instances, such as accessing files on the user’s system at the same time).
For the default JUCE singletons, Ableton and Bitwig by default have plugins under the same process as it seems.
Yeah, for sharing data between plugin instances (not for sharing data between the plugin processor and GUI), this sounds cool! Do you have the implementation of this available for others to see and tinker with?
I was thinking that using OSC for sharing data between instances would be good enough for me because Max MSP patches do that all the time. I’d assume the pro over using OSC over a thread-based approach would be the ability to share the data with computers all on the same network.
One of the larger things I’m trying to solve with my plugin is having complex data containers (i.e. Lists, Vectors) be available to the GUI.
For example, in JUCE’s plugin examples, they have an Arpeggiator plugin that stores the currently played MIDI notes (as integers) in a SortedSet to be able to do the arpeggiation. Imagine there was a custom piano GUI component that would display the currently played notes. How would one do this with AudioProcessorValueTreeState (APVTS)?
So to do the currently played notes GUI, APVTS with the integer SortedSet would be fine? As you said, looking at JUCE’s var definition, it seems containers like list, vectors, and SortedSets are not convertible to var.
I always thought APVTS was intended for parameters that are simple (bool, int, float) (as no example plugin I found uses anything more complex), parameters that are intended to be automated, and parameters that are intended for its state to be saved. If APVTS can’t be used for containers, would passing down the chain of GUI elements a struct containing this information, along with the pointer to APVTS for everything else, be the only option?
Then I could imagine then one could just use a ChangeBroadcaster to notify the piano GUI component that the set/stack has changed, so it can redraw, repaint, etc.
No, sorry. Our code is pretty tied up with private stuff we don’t want to share. It’s just a simple singleton pattern, though, using standard C++ with static getSingleton() and deleteSingleton() member functions. Not using JUCE’s version (which I don’t think even existed when I wrote this code originally).
They behave as separate processes when using that setting in Bitwig. Not a big problem for me, though, but it will use more resources and cause potential minor delays when multiple instances try to access the same file or resource. But those accesses only happen on a background thread anyway, so no big deal there, either. Basically, it’s the same as running our software in two different hosts at the same time.
In regards to a singleton, keep in mind that in addition to BitWig. AUv3 plugins are by default loaded in separate processes (unless the host specifies otherwise), and Reaper can be configured to load plugins in separate processes too.