How would you share a "global" variable for each plugin instance?

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

Hi all,

I'd like to share for every classes within the same plugin's instance scope a common/global variable. I'm using C++, IPlug.

Such as "gLastTriggeredVoiceIndex" within my VoiceManager class, accessible for each other classes that needs it (Filters, Envelopes, and so on), but of course without passing to them a reference/pointer (keeping all stuff separated).

Each instance of the plugin should have its own VoiceManager instance, so its own gLastTriggeredVoiceIndex (not shared across all instances so).

Basic example: let say I have a VoiceManager class (which got that index), which has 16 Voice object; each of them got 10 Envelopes and 4 LFOs. Right now I need to pass a reference/pointer to all Voice/Envelope/LFO class every time I instance them, creating a sort of "linking" between VoiceManager and Voice/Envelope/LFO classes.

This create a relationship that I'd like to avoid, since that variable is the same (for this purpose, customers will only "read" it); would be pretty fast make it global.

Do you know any way?
static (internal) or external linkage won't works, since it will be the same for every plugin intance.

I know this is most a C++ question, but in a DSP context maybe you could give to me some suggestions :)

Thanks

Post

This is a problem you unfortunately cannot really avoid, unless you redesign your object hierarchy - this can be an indication of a bad design, unless you have some sort of parent/child behaviour - very common in GUIs, for instance. There is no way to do what you propose (unless you make sketchy assumptions).

For your specific case.. I don't see why either the filter nor the envelope should know what voice was activated last. Rather, the voice should own and control these objects.

Post

Agreed, probably a bad design somewhere. If you need a "global" object per instance, then it should probably be owned by the VSTEffect object (or similar for IPlug, there is one parent object as well) and pass this object down when you are creating your hierarchy.

Post

You can create a singleton class, or just define the variable inside any convenient header, outside of the class (vanilla C stuff). Be sure to name well as you don't want to clutter up your namespace.

I use a small set of five values as a pasteboard for copying values between sections of my plugin and between other instances of the plugin. For this, it works very well and it's not going to get into any class' hair when doing it's job.

I also set up a singleton class for presets that monitors how many plugin instances are open and it doesn't save the preset bank to disk until the last instance is closed, but still allows changes to a preset in one instance to be reflected in all the others. Not the best solution (it's confusing to users, for one, I'm sure) but it's what I'm going with for now.

Of course, when you are able, it's better to pass pointers for interclass communication.
I started on Logic 5 with a PowerBook G4 550Mhz. I now have a MacBook Air M1 and it's ~165x faster! So, why is my music not proportionally better? :(

Post

Ack! Forgot to get to my point! Set up a class to act as a semaphore/message center/data repository/etc. and pass this pointer for "per instance" globals.
I started on Logic 5 with a PowerBook G4 550Mhz. I now have a MacBook Air M1 and it's ~165x faster! So, why is my music not proportionally better? :(

Post

Once I had met a guy who would reject any design that contained the word "Manager".
He disliked synchronization primitives, and would prefer messaging instead.
Sometimes it takes time to understand. I was "lucky" enough to understand after having a nightmarish experience in a project, then the "truth" was revealed, not only synchronization primitives were just primitives after all, and bad design was indeed bad design, there were other paradigms and not everything had to be object oriented.

Classes that communicate? Hmm, nowadays I think processes do communicate, and object orientation is just mainstream brainwashing, and the way they use the word "message" in their terminology is not what you often want.

Enough ranting for somebody who I didn't even understand the question.
If you want a global, you can have it. If you want to keep the data private to a class instance, you can have it, if you want a global that is specific to a plugin instance, well, why not, the one who has created all the plugin classes is you after all, just pass a pointer to a shared object to all of them; but I'd say pause for a moment and think about that class diagram first, because it's better to avoid shared state whenever you can, even when accessing it does not require any synchronization. Simply it might indicate that there is some problem with the responsibility assignment - i.e. logic that should go to a single class was separated or incorrectly divided; or perhaps some high level concept needed isn't a part of the design and became a cross-cutting feature.

To me it seems like the last one caused the problem described. One can enable/disable a whole set of filters or processors by organizing them into a processing pipeline, each representing a voice. Then you can keep track of which pipeline to assign a newly pressed note using a voice manager allocator. One can even have a voice stealer class somewhere, to keep those who dislike the word "manager" happy.
Last edited by stratum on Tue Feb 28, 2017 9:41 am, edited 3 times in total.
~stratum~

Post

As others have already suggested, you could easily create a singleton object to store common data like gLastTriggeredVoiceIndex. Though that solves the issue of having to pass a reference to every object that needs the data. Imho it doesn't reduce coupling and interdependence between the classes. For unit testing, for example, you would still need to mock up this global singleton in order to test the classes.

Personally I like your current approach better, where you pass a reference to the class, at least then you can make it available only to those classes that really need it instead of having it in global scope.

Instead I would focus on why envelopes and filters need to know for instance the gLastTriggeredVoiceIndex. Could you move some logic to the Voicemanager and make the voice classes themselves dumber and more independent?

Post

Is Singleton pattern "safe" with multiple plugin instances? It is created using "static", which is shared across the whole "scope" (a sort of external linkage, isn't?).

I mean: if I create a static variable, I can access to the same value on every plugin instance within the DAW. Which is not what I want...

Post

Such as "gLastTriggeredVoiceIndex" within my VoiceManager class, accessible for each other classes that needs it (Filters, Envelopes, and so on), but of course without passing to them a reference/pointer (keeping all stuff separated).
Why?
It's called a "context" usually.
Like:

Code: Select all

struct MySynthContext {
 int gLastTriggeredVoiceIndex;
 int gSomeOtherStuff;
}
You don't need to pass it arround as reference, there lots of other ways to do it. OpenGL stores it on the TLS (thread local storage), so same thread has same context.
Others indeed pass it as pointer to the function/classes and the let dev handle threading. Others add getter/setter method that do locking, so dev doesn't need to care. And then there are some that don't want TLS or pointer so they build a manager/factory/whatever class to manage the context-pool (like, get conext by IPlug*, or ID whatever common data is shared across your classes)
Just pick one solution you like.... :D

Post

Nowhk wrote:Is Singleton pattern "safe" with multiple plugin instances? It is created using "static", which is shared across the whole "scope" (a sort of external linkage, isn't?).

I mean: if I create a static variable, I can access to the same value on every plugin instance within the DAW. Which is not what I want...
The singleton pattern is not what you want, and the people who suggested it didn't understand what you want. As you emphasized in your OP, you want a globally accessible variable per instance of your VoiceManager. The messaging class would still require your child classes to have unique identifiers - so that's an associative mapping through shared state, or looking at it more generally, an indirect pointer.

You cannot do what you want within the realm of the language.

Post

Nowhk wrote:[...]
Basic example: let say I have a VoiceManager class (which got that index), which has 16 Voice object; each of them got 10 Envelopes and 4 LFOs. Right now I need to pass a reference/pointer to all Voice/Envelope/LFO class every time I instance them, creating a sort of "linking" between VoiceManager and Voice/Envelope/LFO classes.
You will need to pass a pointer/reference to one of your per-instance objects (with gLastTriggeredVoiceIndex in it) down to your per-voice children.

Possible options:
SetParent(ParentClass *parent) {m_parent = parent;}
Function in children that must be called after the constructor and variables that need to be accessed set as public (not very pretty and against the OOP religion due to breaking encapsulation pretty hard, but it does the job). It kinda looks dangerous since it has all those pointers going back up the object hierarchy, but irl your children per-voice object have a life-time that's strictly shorter than your per-instance container so it generally shouldn't blow up.

SetVoiceManager(VoiceManager *vm) {m_voiceManager = vm;}
Same thing but you're exposing only the voice manager as opposed to the whole parent class.

SetPerInstanceData(PerInstanceData *pid) {M_perInstanceData = pid;}
Same thing but you're selecting specifically only the data that you want to be shared around (you would move gLastTriggeredVoiceIndex into this struct). Then you can simply put PerInstanceData m_perInstanceData inside your main object.

void Generate(float *outBuffer, int nbSamples, const PerInstanceData &pid)
void CalcModulations(const PerInstanceData &pid) etc...

Same thing as above but instead of storing the pointer to per-instance-data in the children, you're passing it down in each function call that needs it. Making it a const reference helps limiting the number of ways you can manhandle the data. This looks a bit verbose but irl it's really not that bad at all since you can pass a pointer to a whole bunch of parameters in one go.
Nowhk wrote: This create a relationship that I'd like to avoid, since that variable is the same (for this purpose, customers will only "read" it); would be pretty fast make it global.

Do you know any way?
static (internal) or external linkage won't works, since it will be the same for every plugin intance.
[...]
That's the way... all per-instance data has to be contained in some plugin-instance object and its children. And the "key" to accessing this instance object or its children is going to be a C++ pointer/reference. So you're going to have to pass down something, sorry. :3

Post

Nowhk wrote:I mean: if I create a static variable, I can access to the same value on every plugin instance within the DAW. Which is not what I want...
No, Mayae is right. A static variable/object would be shared across instances of the plugin. I misunderstood you.

I think the best solution would be Madbrains third solution, where you pass a pointer to a PerInstanceData to every class that needs to know about global data for this instance.

Though, I'm still wondering why the voice class (and even more envelopes and lfos) needs to know this. Imo, it would be a better separation of responsibilities if the voice manager distributed note on/note off messages and kept track of which voices were active, while the voice objects themselves where unaware of each other.

Post

Though, I'm still wondering why the voice class (and even more envelopes and lfos) needs to know this. Imo, it would be a better separation of responsibilities if the voice manager distributed note on/note off messages and kept track of which voices were active, while the voice objects themselves where unaware of each other.
I'd organize each voice as a pipeline and preallocate a number of them. Each pipeline needs note on / off events, and the voice manager can keep track of which pipeline is free or allocated. If there is no remaining pipelines left and the user have pressed too many notes (perhaps with a sustain pedal), one of the pipelines need to be 'stolen' by the voice manager even if it is not free. Doing so isn't much problem, handling the pop/click sound caused by this might be.
~stratum~

Post

Mayae wrote:The singleton pattern is not what you want, and the people who suggested it didn't understand what you want. As you emphasized in your OP, you want a globally accessible variable per instance of your VoiceManager. The messaging class would still require your child classes to have unique identifiers - so that's an associative mapping through shared state, or looking at it more generally, an indirect pointer.

You cannot do what you want within the realm of the language.
I see! Thanks mate!
noizebox wrote: Though, I'm still wondering why the voice class (and even more envelopes and lfos) needs to know this. Imo, it would be a better separation of responsibilities if the voice manager distributed note on/note off messages and kept track of which voices were active, while the voice objects themselves where unaware of each other.
Mainly for GUI/Graphic stuff. When I play a note, I draw a cursor which represent the current postion of the (last) playing note.

At the moment I do as MadBrain suggested: I pass the "IPlugBase" class instance to the ControlKnob and EnvelopeMonitor classes, where I can derivate from top to down the VoiceManager, which own gLastTriggeredVoiceIndex.

The fact is that those two don't need at all any other variables, and it sounded odd. That's all :)

Post

The easiest solution is to put all "global" stuff into a single struct (which can be POD and a member of your plugin class) and then pass a reference to that everywhere you need it. While this doesn't really remove the requirement to pass around the reference, it saves you the trouble of having to pass around a lot of random stuff separately, while also avoiding all the potential problems with circular references that might happen if you try to pass around the actual plugin class (or some interface) and makes it simple to pass additional data: just move it to the struct and it's available everywhere you already have a reference.

Post Reply

Return to “DSP and Plugin Development”