Module expanders tutorial?

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 expander ā†’ groovebox communication, and a different struct for the groovebox ā†’ expander 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!

I think it would be easier to combine the structures. Rely on one of the modules, either the parent, or the expander; to own those structures and coordinate the swap.

To keep things as clear as they are now; enclose one of each of your structures into a new structure.

I cant see anything in your code to set message received to false. But that is what you are testing for.

Also i notice that you have dynamically allocated your message structures. Are you disposing of them correctly? (Not related to your current issue)

You can avoid the dynamic allocation but using a static array of two structures.

Perhaps Iā€™m understanding the code wrong and my comment will be besides the mark, but I think for a given direction of communication you always have to declare an array of 2 elements of the buffer you wish to use. For example, if I want to share 6 floats in one direction, it has to be declared as a double buffer:

And when using a struct, it looks like this:

The whole mechanism has to be diligently followed, and for each direction the double buffer is really needed.

You are trying to do 2 way communication between a module and an expander; am I understanding that right?

Yes, correct!