Expander questions

Hello,

For my sequencer I’m trying to understand how expander connections and communication work.

Using the API for expanders, is communication always with one module to the left and one to the right, or can I communicate with several modules at once without messages going through each one serially?

Are there well defined circumstances where I can just hold a pointer to a module and read its members inside process() without locking or do I always have to use that messaging protocol provided by the API?

Thanks.

From within your own module, you have the leftExpander and rightExpander members which will automatically be updated by Rack with the latest references based on position in the Rack.

However, if you wanted to communicate with distant modules, such that those modules are in line with your module, then you can indeed traverse the leftExpander / rightExpander chain to find the module you’re interested in. Since the members are public, the modules in between do not necessarily need to cooperate. This constrains who your module can talk to, but again, the spacial relations are automatically maintained by Rack which is nice.

That said you can always get a complete list of the modules that are on the Rack, iterate, filter, and other wise interact with them however you’d like. The primary thing to keep in mind is that unless the remote modules are your own, you’ll have to treat them as ModuleWidget.

Regarding locking, I’ve come to learn that the times when it is necessary are those times when it is necessary. That’s to say, you’ll just have to think carefully about whether the accessors to state will conflict or not. If they could, you’ll want to synchronize.

Yes, you can take a pointer to other modules. All the normal caveats apply.

1 Like

you can never, ever, take a lock in the audio process function.

I think paul told me there are some mutex implementations that work, but I was speaking more generally to the need to coordinate / synchronize rather than use of std::mutex or something. But yeah, good to mention.

yeah, for sure. I think I knew what you meant anyway. There may be some std::mutex version that never lock the caller. @baconpaul is really into all the nooks and crannies of std and c++99, which is fine. But there are super easy ways to do this with normal atomic variables and there are tons of ways to get into trouble using the wrong kind of mutex so — ?

This ancient paper mentions that issue: Demo/efficient-plugins.md at main · squinkylabs/Demo · GitHub

3 Likes

@squinky Yea, I got the message about the “no locking in process()” policy loud and clear from your educational stuff and other threads. Looking at lock-free synchronization techniques, I gather I need to learn about atomic variables. Thanks.

@ldlework Thank you for your clear explanation. I’m learning to code here, and your answer helped me search in the right direction. Much appreciated.


So I figured I’ll use the API and serial message bus to pass pointers around and use direct accessing members or atomic variables for passing information that needs synchronization.

I have more questions, but I need to get my hands dirtier before I can formulate them correctly.

1 Like

I was just suggesting a lock free queue and std atomics. Nothing magical.

If you only communicate directly with the expanders to either side, then a ‘lock free’ safe method already exists, because the you can request that VCVRack flips the buffers between samples.

I use this when I need sample accurate information from the devices. And I don’t necessarily use it, when I know that concurrency is not an issue for the data that I’m after.

If you need sample accurate data from devices further afield, I would recommend passing the data from device to device, one sample window at a time and if necessary compensate for the predictable delay.