Module expanders tutorial?

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

Thanks, I had the same thoughts about expanders and this is really helpful!
Just one question: where is the instance of MessageBus defined? Is it a global variable?

yes:

in this case, it’s defined at plugin instantiation - hence my comment about adding message tagging. you’ll note that thee message bus in this case is using an array of floats.

3 Likes

Thanks for sharing this Jerry, very interesting!

I have been thinking about the idea of a modular mixer, where each module (channel strip) is expander to its left one and mother to its right one … so to get a 4 channel mixer all you need to do is put 4 channel strips side by side, with probably a master to their right. Then you build your mixer whatever size you want. Though I have absolutely NO idea how to make an expander/mother (yet), this is what my ultimate goal would be. Do you guys, with more expander experience, see any problem with this? Feasible?

Should work. Has it not been done before?

@Squinky has a mixer module „Form“ and expanders ExFor and ExTwo.

I will check it out, thank you.

Yes, our modular mixers are very similar to what you are asking for. And they are open source so if you want to make you own you may find some info there. That said it was very difficult for us to get this to work, especially the rather trivial details around getting solo to work across the modules. don’t know if it’s cause or effect, but we have to confess our source code for the expander part of it is not super clear.

2 Likes

Just to be 100% sure … there should be nothing preventing a module to have expanders connected both to its left side and to its right side, right?

Correct.

1 Like

we put out mixer ex panders on the left, but that’s just because “real” mixers tend to be organized that way.

1 Like

I’m revisiting this thread because my bi-directional expander code isn’t working. It’s easy to get confused with this stuff and I could use an example to follow. However, the link is broken to Andrew’s compact example. Does anyone know if it’s still around somewhere?

The github issues were removed in that repo, so unless someone has a copy, it might be best to look at some code of an open source module that uses expanders. I have a few (as do many others), and I’m definitely not claiming to have the most elegant code, but perhaps some of my simpler modules like Clocked or ChordKey could be worth a look. In those cases, the buffers are declared as float arrays, but it can in fact be a struct to make management easier. I’ve done the struct method in MixMaster/AuxSpander, but that’s way more code to sift through perhaps than what is needed.

Drat, I’m having trouble. Maybe I can talk it out here.

My main module (GrooveBox) expects the expansion module (GrooveBoxExpander) to be on the left-hand side.

I’m using two different structs for passing around the data: One for the expandergroovebox communication, and a different struct for the grooveboxexpander communications…

I’ve been successful in sending data from the expander module to the groovebox module.

Here’s the structure used for sending information from the expander to the groovebox:

#pragma once

struct GrooveBoxExpanderMessage
{
  bool message_received = true;
  bool mutes[8];
  bool solos[8];
  float track_volumes[8];

  GrooveBoxExpanderMessage()
  {
    for(unsigned int i=0; i<8; i++)
    {
      mutes[i] = false;
      solos[i] = false;
      track_volumes[i] = 1.0;
    }
  }
};

In the Groovebox module, I define the following class variables:

  GrooveBoxExpanderMessage *producer_message = new GrooveBoxExpanderMessage;
  GrooveBoxExpanderMessage *consumer_message = new GrooveBoxExpanderMessage;

I’ve created a function that sends data from the Groovebox module to the expander which is called every frame:

  void processExpander()
  {
    if (leftExpander.module && leftExpander.module->model == modelGrooveBoxExpander)
    {
      // Receive message from expander
      GrooveBoxExpanderMessage *expander_message = (GrooveBoxExpanderMessage *) leftExpander.producerMessage;

      // Retrieve the data from the expander
      if(expander_message && expander_message->message_received == false)
      {
        // Copy the information in the structure to local variables.
        // This code was left out because it doesn't really pertain to
        // my question, and it's working fine.
        //
        // But saying that, here's just a taste:
        for(unsigned int i=0; i < NUMBER_OF_TRACKS; i++)
        {
           bool expander_mute_value = expander_message->mutes[i];
           // (I then store expander_mute_value locally)
        }

        // Set the received flag so we don't process the message every single frame
        expander_message->message_received = true;
      }

      leftExpander.messageFlipRequested = true;
    }
  }

So far, so good. That seems to work perfectly. However, now I wish to do the opposite: Send information from the groovebox module to the expander. First step, I created a struct to hold the data:

#pragma once

struct GrooveBoxMessage
{
  bool track_triggers[8];
  bool message_received = true;
  
  GrooveBoxMessage()
  {
    for(unsigned int i=0; i<8; i++)
    {
      track_triggers[i] = false;
    }
  }
};

And here’s my code in the GrooveBox that’s not working, with a clue to why:

It probably has something to do with this line:

GrooveBoxMessage *groovebox_message = (GrooveBoxMessage *) leftExpander.module->rightExpander.producerMessage;

And because I’m a bit confused about what’s going on. Thanks for any help you can muster!