Module expanders - sharing a minimal example

Hi everyone,

I’ve spent some time recently trying to implement expander modules (with bi-directional communication) for a project I’m working on. Thanks to the Module expanders tutorial thread and a fair amount of experimentation and reading others’ code, it’s working fine. Many thanks to @clone45, @CountModula, and @marc_boule!

Because things are somewhat buried in that thread, I wanted to share a simple example in the hopes that it can save somebody else some time. Here’s a little module that is an expander for itself - slide as many together as you like. Each displays its own position and the total number in the group.

The widget implements the beautiful seamless expander behaviour seen in MindMeld’s MixMaster & AuxSpander.

This is what it looks like.

The module code is:

struct expMessage { // contains both directions
	unsigned int numModulesSoFar = 0; // This will travel rightward
	unsigned int numModulesTotal = 0; // This will travel leftward
};

struct TwoWayExpander : Module {
	expMessage leftMessages[2][1]; // messages to & from left module
	expMessage rightMessages[2][1]; // messages to & from right module
	unsigned int numMe = 0;
	unsigned int numModules = 0;

	TwoWayExpander() {
		leftExpander.producerMessage = leftMessages[0];
		leftExpander.consumerMessage = leftMessages[1];	
		rightExpander.producerMessage = rightMessages[0];
		rightExpander.consumerMessage = rightMessages[1];
	} // module constructor
	
	void process(const ProcessArgs &args) override {
		bool expandsLeftward = leftExpander.module && leftExpander.module->model == modelTwoWayExpander;
		bool expandsRightward = rightExpander.module && rightExpander.module->model == modelTwoWayExpander;

		expMessage* leftSink = expandsLeftward ? (expMessage*)(leftExpander.module->rightExpander.producerMessage) : nullptr; // this is the left module's; I write to it and request flip
		expMessage* leftSource = (expMessage*)(leftExpander.consumerMessage); // this is mine; my leftExpander.producer message is written by the left module, which requests flip
		expMessage* rightSink = expandsRightward ? (expMessage*)(rightExpander.module->leftExpander.producerMessage) : nullptr; // this is the right module's; I write to it and request flip
		expMessage* rightSource = (expMessage*)(rightExpander.consumerMessage); // this is mine; my rightExpander.producer message is written by the right module, which requests flip
		
		if (expandsLeftward) { // Add to the number of modules
			numMe = leftSource->numModulesSoFar + 1;
		} else { // I'm the leftmost. Count myself.
			numMe = 1;
		}

		if (expandsRightward) {
			rightSink->numModulesSoFar = numMe; // current count
			numModules = rightSource->numModulesTotal; // total from right
			rightExpander.module->leftExpander.messageFlipRequested = true; // tell the right module to flip its leftExpander, putting the producer I wrote to into its consumer
		} else { // I'm the rightmost. Close the count loop.
			numModules = numMe;
		}

		if (expandsLeftward) {
			leftSink->numModulesTotal = numModules; // total goes left
			leftExpander.module->rightExpander.messageFlipRequested = true; // tell the left module to flip its rightExpander, putting the producer I wrote to into its consumer
		}
	} // process
}; // TwoWayExpander

The repo can be found at https://github.com/landgrvi/VGLabs-TwoWayExpander. Thanks again, and enjoy!

12 Likes

Very cool! I like the example of “melding” of connected panels like MindMeld does.

Thanks! This is a very helpful example.

It would be really great if we could somehow link this up to the Plugin API Guide on the VCV tutorial page. The examples they point to currently (Greyscale) are closed source.

Thanks, I’ve been looking exactly for such succinct example of expander implementation. Bookmarked.

In my experience VCV does not link to “other” stuff. ymmv, but they have never linked to any of my stuff.