Module expanders tutorial?

Hi everyone!
Is there a tutorial or a simple example to check out for module expanders?
I have one module that could use an expander with some extra knobs only, so nothing too fancy.

Thanks!

3 Likes

Stoermelder Strip is probably a good example to go on as it manipulates all modules connected to it.

Impromptu, Squinky-Labs and Frozen Wasteland also have expander’s

1 Like

This is also a compact example, which had been updated by Andrew as the code evolved when he was designing expanders:

3 Likes

Yes, that’s the source of truth, and much easier to understand than looking in our mixer-expander code! Thanks @marc_boule

1 Like

Definitely! My expander code is horrible to look at in particular, brain cramps are guaranteed :slight_smile:

2 Likes

Nice, thanks for the info!
Clocked looks like a nice example too Marc :smiley:

Btw. Any thoughts about:

  • Left vs Right expander (also, can it be made to work on both sides?)
  • Having the expander to work alone and as a helper of other module vs only as a helper
1 Like
  • Left vs right, I thought about this, but for me the expander concept is expander-on-right-side, and to keep the code simpler I decided right-only, but making left/right support is definitely possible, just a pain because of the code bloating in my view.
  • I imagine this is completely possible, and I think one of the upcoming Grayscale modules will actually work like this if I remember correclty. Stages could also be an example, where they are self contained workable modules, but can be cascaded.
  • I’d be curious to hear thoughts from others as to how they support .isConnected() vs .getVoltage() when using floats to pass data in the expander mechanism. When I need to show a cable is not connected, I use a voltage of std::numeric_limits<float>::quiet_NaN(). Using another float to indicate a boolean state would be possible also. How are others doing this?
1 Like

Couldn‘t you pass a pointer to the port and work directly with its member functions?

Yes, but then when you want to also have lights, or switches, or other stuff on the same expander, then the types would not work. Perhaps casting it all to void pointers would work, but doesn’t seem really elegant. Float, covers pretty much all (except for a current project I’m working on where I have to cast 4 chars per float since I’m mixing signals and text to an expander, ouch!).

You may be right on that, I haven’t enough experience with development in C++ to know what’s elegant and what isn’t. I suggested it because of this comment in Module.hpp:

If you intend to receive messages from an expander, allocate both message buffers with identical blocks of memory (arrays, structs, etc). Remember to free the buffer in the Module destructor. Example:

rightExpander.producerMessage = new MyExpanderMessage; rightExpander.consumerMessage = new MyExpanderMessage;

You must check the expander module’s model before attempting to write its message buffer. Once the module is checked, you can reinterpret_cast its producerMessage at no performance cost.

After writing my own expander I have to say this was a bad suggestion: You shouldn’t access members directly from other modules because you’ll get threading-issues sooner or later. At least when the engine uses more than one thread.

1 Like

I just ended up implementing a thread-safe message bus - technically still true to the eurorack format, since the 16 pin connector carries 2 buses.

Our modular mixers send data in both directions. Audio always goes left to right, but solo information travels in both directions. You can totally do whatever you want. And hope that people find the resulting thing useful.

2 Likes

So can you just randomly mess with another module? That seems like it might be a bad thing if an expander has undocumented functionality. On the other hand the potential to randomly mess up an otherwise boring patch might be interesting.

I don’t have the programming skills to do this but could someone make a quantussy cell that was an expander? So that i could have a quantussy ring that doesn’t take ages to connect up? I love the Frozen Wasteland Quantussy cell, if you haven’t tried it you should.

Well, yes and no. You can “see” the modules on both sides, but you can really only mess with them if you know what is inside, which in practice means that you made them and they are in the same plugin. You could try to reach into “other” modules and mess with them, but it would be very fragile and difficult.

Also, the “other” module probably won’t talk to you if it doesn’t recognize you.

The expander protocol proper really wants you to send/receive data, not do arbitrary munging.

So, without getting too crazy, arbitrary modules can’t do crazy things to other modules.

Also, anything you do with the Module pointer is not thread-safe unless you write to its producerMessage or read from its consumerMessage and then set messageFlipRequested = true. This is important when using >1 thread.

I’d be interested to know more about the thread-safe bus you mention, if ever you have it in your github or can post more info here, I might need to do something like that in my current project, since I may need to daisy chain a dozen (or two) small expanders that will carry audio, and want to avoid a signal having to ripple through an expander chain when communicating with the mother module. Cheers!

I just went ahead and pushed some of the work I’ve been doing:

I hadn’t pushed it previously as I’m not ready for release of this module yet, but this should give you a good idea of what I’m doing.

a module registers itself:

with a message being sent as so:

my plan is to expand it before actual release, to include message tags, but it works fairly well as-is.

it uses a mutex guard around the vector that stores the data, leaving only the most recent message from each publisher. any subscriber can get a copy of the most recent messages.

2 Likes