Proposal for improved expander functionality

As some of you may know, I make extensive use of expanders - I think it is a great way to add functionality in a “modular” way :slight_smile: That said, the expander implementation is by Andrew’s own admission a quick hack, and I’d like to propose something more robust.

I don’t have the implementation details, but what I would love to see is some kind of local pub/sub model where modules could expose data and other modules could consume as needed.

Since I do a lot of chainable stuff, it gets to be a hassle allocating large blocks of floats, partitioning which range of values each module uses and then writing bi-directional pass through code.

Thoughts?

1 Like

I’m not sure how you could do something like without allocations on the audio thread.

The design of the expander API is not a “hack”. It is carefully designed to be the fastest possible method to communicate between expander modules at audio-rate. Sending a message involves a single pointer deference, a swap of two pointers, and receiving involves another pointer deference. In fact, it is better performance than sending a voltage over a cable. The API is designed to be low-level and general, so that anyone can implement any level of abstraction on top of it and not worry about the expander API they’re building upon restricting their performance.

1 Like

I see what you mean. But …

Since I do a lot of chainable stuff, it gets to be a hassle allocating large blocks of floats, partitioning which range of values each module uses and then writing bi-directional pass through code.

Well, so you’ve written this code before. Why not try and abstract this functionality into a utility class? I am not a Rack developer - I code all day long, I don’t want to do it in order to make music - but that sounds like the sort of thing that could be abstracted.

Even better, write it as a template class so it expands to what you would hand-code in order to handle those parameter arrays.

1 Like

short answer - there are many more issues than this, thus the proposal

I stand by my original statement Andrew. For one module and one expander, the current system works ok. If your module supports multiple expanders, especially ones that can be chained. it quickly becomes a PITA.

I guess I will roll my own solution. I apologize for wasting your time by suggesting an improvement

  1. You didn’t suggest a specific improvement. You just said “it should be improved”.
  2. Feature requests for VCV Rack should follow https://vcvrack.com/manual/Issues. Read the rules.

Maybe you want to take a look at this:

1 Like

Thanks Ben, very helpful

After reviewing this code again I wouldn’t recommend to use it: First, it uses a mutex, second, it allocates memory on the audio thread.

yeah, I think Jerry was concerned that built in messaging was’t thread safe. Not sure if he pursued it any further.

yes, please no allocations on the audio thread, and no mutexes.

I would be interested in a simple message bus, that was more flexible and easier to use, and in return did not work at audio rates. I totally agree with @Vortico that the existing protocol is hard to beat for its intended use.

My modular mixers had a message bus that’s used for sending the solo commands in both directions. The only problem - it is terrible.

My stuff is build on top of the existing protocol, or course. I would imagine that any new and improved protocol would be, too.

So, yeah, I’m interested.

The current expander mechanism is based on a float pointer (float array). Is there a way to perhaps keep this low overhead pointer-swaping and make it more general so that other types can be passed also between modules? As it is now, I’m casting 4 chars to a float in some places, mixed with other floats, which is probably not the most elegant method.

Would it be feasible (good practice) for me to perhaps make a struct with everything I want to communicate, and cast a pointer to that struct into a float pointer? Just wondering if there would be a way to build on what is there and perhaps make it a bit more general.

Perhaps if the expander mechanism was based on void*? Maybe this idea is blasphemy, since I really don’t know enough about standards and accepted practices, but I’m curious to know more though, and will be eagerly following this topic :slight_smile:

I’m not sure why you’re saying that. It uses void*, not float*. You can use floats if you want to, or char buffers, or structs, or polymorphic classes…

1 Like

Oh, I stand corrected, I just always saw floats in the examples and figured it was declared as float pointer; as always, looking deeper in the Rack code is something I should make a habit of always doing! Thanks Andrew.

In Module.hpp:

void* producerMessage = NULL;
void* consumerMessage = NULL;