Touching other Instances of Module in Rack

I’m implementing a common theme chooser for some modules. Ideally when a user changes to a new theme I want all instances of modules within my plugin to also change. I wonder if there is a preferred way to do it. Here are the two options I’ve considered:

  1. Each module regularly reads the .json file associated with the plugin and detects if the theme changes. I don’t like this because it feels like a lot of useless disk access and I don’t want to do it very often for this reason.

  2. Try to find modules that are of the correct type and send them some kind of signal. I noticed that the engine offers access to other modules by their ID but not any kind of iterator. Most of the IDs are small numbers but I assume this isn’t guaranteed. (they could just be random UIDs?) I could just scan the first n module IDs to see if they exist and are of my type… but this also feels wrong.

What is the correct way to send signals to other modules of a specific plugin type to cause them to perform some action. I don’t care if they just go and read the theme JSON file again… I just want to avoid them polling it.

Thanks!

1 Like

If the modules are all within the same plugin, you could make a plugin-scope event handler that each relevant module subscribes/unsubscribes from in its onAdd() / onRemove() methods. I do something similar in my modules using a global Settings object that each module registers itself with.

Psuedo-code:

extern SignalHandler* some_signal;

struct YourModule : rack::engine::Module {

    ...

    void onAdd() override {
        // add this module to the signal handler, using the id as a key
        some_signal->add(this->id, std::bind(&YourModule::onSomeSignal, this, std::placeholders::_1); 
    }

    void onRemove() override {
        // remove this module from the signal
        some_signal->remove(this->id);
    }

    void onSomeSignal() {
        // called when the signal is sent
    }

    void aDifferentMethod() {
        // send the signal to all subscribed modules
        some_signal->invoke();
    }
};

In this example, some_signal could be constructed in e.g. the init(rack::plugin::Plugin*) function, or be an object with global scope.

You might also be able to use Rack’s built-in event system (check out event.hpp), though I haven’t personally used it like this so I’m not sure.

Submarine modules share a theme. There is a global variable defined within the plugin, and each module checks it regularly, redrawing if the value has changed.

Okay thanks for the ideas guys! I figured there was probably a cleaner way of doing it… the framework is almost too flexible it seems so I don’t want to do something that will break in the future. I’ll check out both suggestions and report back!

There most certainly is a way to iterate all the modules! My Seq++ iterates all modules looking for the nearest clock source, for example. Here’s a very short excerpt:

Clocks::WidgetAndDescriptionS Clocks::findClocks()
{
    WidgetAndDescriptionS ret;
    auto rack = ::rack::appGet()->scene->rack;
    for (::rack::widget::Widget* w2 : rack->moduleContainer->children) {
        ModuleWidget* modwid = dynamic_cast<ModuleWidget *>(w2);
        if (modwid) {
            Model* model = modwid->model;
            for (int i=0; i< numDescriptors; ++i) {
                const ClockDescriptor* descriptor = descriptors + i;
                if (model->slug == descriptor->slug) {
                    ret.push_back( std::make_pair(modwid, descriptor));
                }
            }
        } else {
            WARN("was not a module widget");
        }
    }   
    return ret;
}

Ah okay! I wasn’t sure if I was supposed to use those objects directly… I was only looking at what was available in the engine API.

I have to confess I’m not sure how you know what you are “supposed” to use and what you are not. If Andrew were not so busy he would probably correct me here.

The real difference is that the stuff you are “supposed” so use doesn’t change, so the risk isn’t too huge…

That said, to make my own modules talk to each other I just use a global variable like everyone else, above.

I usually load settings on plugin initialization and save settings immediately when settings change.

namespace settings {

int panel = 0;

void load() {
	FILE* file = std::fopen(asset::user("PluginSlug.json").c_str(), "r");
	if (!file)
		return;
	DEFER({std::fclose(file);});

	json_error_t error;
	json_t* rootJ = json_loadf(file, 0, &error);
	if (!rootJ)
		return;
	DEFER({json_decref(rootJ);});
	fromJson(rootJ);
}


void save() {
	json_t* rootJ = toJson();
	DEFER({json_decref(rootJ);});

	FILE* file = std::fopen(asset::user("PluginSlug.json").c_str(), "w");
	if (!file)
		return;
	json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9));
	std::fclose(file);
}

} // namespace settings


void whenUserRequestsPanelChange() {
	settings::panel = 1;
	settings::save();
}


void init() {
	settings::load();
}

Rack v2 will have methods to query all modules in the rack. But typically you should never need to communicate with other modules other than cables and the expander API. For changing panels, you can make all your modules listen to some variable settings::panel and make them update themselves.

5 Likes

Thanks! This is great info. I figured that poking at other modules is probably bad but the shared variable within a plugin seems like the right way to me, even if just to trigger a reload of settings in other modules from the same plugin.

just be careful about threading issues. If you talk the that shared variable from a Module::process calls you should take into account that your other modules may be running on different threads. From the UI thread there’s no issue.

1 Like