Rack: Polyphonic cables

Created on 9 Oct 2018  路  31Comments  路  Source: VCVRack/Rack

Could allow up to 16 or so channels of voltage through a single cable. Is this a good maximum?

Could be implemented without API breakage with this pseudocode.

struct Input {
    union {
        float value;
        float values[MAX_CHANNELS];
    };
    int numChannels = 1;
    ...
};

Modules that don't support poly will just use the first value (value and values[0] are the same thing). Modules that do can read/write to inputs/outputs. numChannels is writable for Outputs and can be 0 to MAX_CHANNELS. However, a sane value (e.g. 0.f) should be written to values[0] if there are zero channels, for modules that do not support poly cables.

Port lights could appear in a unique color (blue) to specify polyphonic activity. Something like sum(value[i]**2, i) would be its value.

Users might be confused which modules support it. It could simply be added as a tag (POLY_TAG) which will appear in the Module Browser.

feature request

Most helpful comment

I opted against two different types of cables because it would be confusing which input and output ports can be used. The best way is certainly to extend normal cables to support an array of values.

All 31 comments

does numInputs refer to the number of inputs actually connected or to the number of inputs supported over that wire by the module?

@Rcomian The maximum number of channels in a cable is MAX_CHANNELS (in my example) which is equal to 16. numChannels is the number of values that are defined by the Output. This can change every frame.

As a module writing outputs, how do I inform the downstream module that only 6 values are relevant? As a module writing outputs, do I assume that any downstream module will accept all 16 values if I want to send them? Or can I tell how many voices the downstream module can accept?

For example, If I'm using 16 generators to create outputs, but the downstream module is only 8 voice poly, do I write all 16 values anyway? If I knew limitations of the downstream module I could close some generators.

As a module writing outputs, how do I inform the downstream module that only 6 values are relevant?

Set outputs[...].numChannels = 6;

As a module writing outputs, do I assume that any downstream module will accept all 16 values if I want to send them?

The input module might not support poly, so it would only use the first value. But generally if a module claims to support poly cables, it should support up to 16. I can't think of a good reason for it to support less.

Most plugin developers would do something like this.

int n = inputs[PITCH_INPUT].numChannels;

outputs[SINE_OUTPUT].numChannels = n;
for (int i = 0; i < n; i++) {
    // Process voice `i`
    outputs[SINE_OUTPUT].values[i] = oscillators[i].process(inputs[PITCH_INPUT].values[i]);
}

The oscillators array would simply be length MAX_CHANNELS.

I think the Poly capability should be indicated visually on the module itself, to help keep track of what's what while working on the patch, particularly if not all modules are Poly capable. Perhaps a coloured box with the letter "P" in a corner of the module?

I love the idea from a technical standpoint. One cable is better than 4, obviously.

But I can't help wondering how a simple and intuitive UI would inform the user about that situation.

Additionally, we would need multiplexing and de-multiplexing modules, to go from n-patch-cables to mono-patch-cables.

@AndrewBelt What was the exact use-case you had in mind? If the problem you are trying to solve is too many cables overlapping modules, this could also be solved by a core module that functions as a CV Bus. You might remember the idea from a recent facebook conversation.

42821470_10154897995222465_426001665603141632_o

I think the issue with a CV bus is that you're going to need some sort of multi-channel system unless you only want one bus. I think a polyphonic cable could be implemented nicely if it were a different style - perhaps thicker, or a different color, or maybe a texture of some sort. This would clearly distinguish it as being polyphonic, and would be much more intuitive, IMO. This also opens up the possibility for modules accepting polyphonic cables as inputs or outputting them. Imagine this - I polyphonic VCO, so it can play up to 8 pitches at once - take that output, put it through a polyphonic VCF, VCA, ADSR, etc.

Now, to achieve this, you can cycle through each channel, and run that channel as a cv through the module. I suggest adding an official "polyphonic" api in the plugin api, which would allow modules to store variables which change each step as an object, perhaps Rack::PolyFloat - This way, the engine can handle polyphony, so that if there are ever updates to it (i.e. more channels), modules won't break if they just used arrays or similar methods of computing polyphony. I also suggest adding Rack::POLY_CHANNELS as a constant for the number of channels, if plugins do decide to implement polyphony themselves.

I opted against two different types of cables because it would be confusing which input and output ports can be used. The best way is certainly to extend normal cables to support an array of values.

I personally disagree - If you were to implement a different port style, perhaps larger and with a different shape, it's pretty easy to distinguish between the 2. I think that one cable supporting polyphony is more confusing, as you then have to handle users attempting to plug polyphonic cables into non-polyphonic inputs. If you were to just take the first channel, there would be a lot of confusion as to what happened to the other channels. One potential solution to this is manually forcing polyphony among plugins - this works great for a VCF connected to nothing but the polyphonic cable... However, as soon as you get into any modules which store data between steps, you would need to run a separate instance of the plugin per every channel per every polyphonic cable. This overloads the engine pretty quickly. If you do just go with taking the single channel, I still do recommend some sort of visual distinction so that users know it is a polyphonic cable. The ability for rack to handle polyphony itself with Rack::PolyFloat etc. would be nice for new plugin devs - however, if you are planning on having devs manually implement it with arrays, or if a dev just wants to use arrays for a more advanced polyphonic plugin, I would still recommend some basic api just to let rack know that the module is, indeed, polyphonic.

If you were to just take the first channel, there would be a lot of confusion as to what happened to the other channels

I don't think so. If you plug a cable into a VCF and the input plug has a blue LED and the output plug has a red/green/orange LED, it's pretty clear that the VCF doesn't support polyphony.

manually forcing polyphony

Not doing that, out of the question.

I think I'll pass on these suggestions, but thanks.

Fair enough - Thank you for taking my suggestions into consideration! I still think polyphony will be awesome.

Does polyphony create a linear proportional increase in DSP requirements? Two voices = 2x DSP of One Voice?

That depends on what the plugin step() method does.

If you are planning to extend the normal cable functionality, does that mean you would allow multiple input cables?

An example being a module that supports poly on an input allowing connections from several different VCOs?

I think the feature would be Cool, given it's not abused by module makers. :p

@graehu Are you asking if multiple outputs can be merged into a single cable? You'd need a module to do that, which accepts for example, two inputs and copies the first 8 channels to output channels 1-8 and the second input's first 8 channels to 9-16.

The status of this issue is to wait on performance results. If real-world monophonic patches use less than 10% more CPU consumption, it will be approved.

@AndrewBelt I think you've answered my question but I'll restate it for verbosity. Right now Rack allows you to have multiple cables attached to any output. So I could have one LFO send CV to as many other modules as I want.

I'm talking about the opposite of that. Many different outputs sending to one input. To be clear I meant without an intermediate module routing all the channels into a single cable. But rather lots of cables into one pot.

Based on your reply, that's not something Rack would support out of the box. Unless I've misunderstood. :)

Right, one cable per input still.

Port lights aren't really standard, many modules don't use them, so this may not be enough to inform users. Could we still distinguish the cables visually at render time? Either thickness, color scheme, or possibly something like striped cables (poly) vs solid-colored cables (mono). I.e. if a cable links a poly-compatible module to another, it's rendered as a poly cable (event if it turns out that num_values will be 1, the cable can't know that until it transmits data). If either module only supports monophony, the cable is rendered as a mono cable.

That's still only really 1 type of cable - I click on any input, drag to any output, the same data structures are created and I don't have to pick as a user, but there's a visual cue on what's happening in that cable.

@korfuri Port lights are owned by the cables. They're not part of the module. You're thinking about LEDs some developers put on the panel to copy the port value.

Thick cables is the best option, and I will likely do that, but note that num_values can change per-sample, so the GUI might appear too weirdly dynamic if the size is rapidly changing.

Ah, right, I was thinking of the LEDs that sometimes go near the ports. The cable lights are a great solution. 馃憤

Great idea!
It might make sense to think about going to 48 (or 64) for MAX_CHANNELS. Rationale: Most multi-expression-per-finger instruments have three control dimensions per finger nowadays. For 16 midi channels this would make 3*16=48 channels. (Additionally some support one or two expression pedals, breath and one or two ribbons. So let's say about five more mono channels. 53 rounded up to the next 2^x member would be 64.)
That way we could build group and ungroup modules that e.g. combine and split xyz modulation on/from a single cable and modules that just have an "xyz" input/output.
It's a trade-off - presumably for the 90%+ case 16 voice cables are sufficient, so we would in most cases allocate more memory than necessary.

In any case: Allowing 16(+) channels per cable would be a big step into allowing to write poly modules for VCV!

@NothanUmber MAX_CHANNELS is the number of voices, not total number of parameters. If your controller generates X, Y, and Z, you'd use three polyphonic cables for that.

@AndrewBelt yepp, understood. Was thinking along the lines of "group/event busses" like you have e.g. in Max or Reaktor where you can further tidy up stuff that is often used together.
But you are right, some of the immediacy might get lost, having separate cables for poly x, y, z and mono pedal1, pedal2, ribbon1, ribbon2 and breath (as we have it e.g. for Eigenharps) is by leaps and bounds better than having to fiddle around with half-a-hundred cables per module :)

So yeah, 16 voices per cable should do the trick, thanks in advance for considering that!

i am for polyphonic connections, il'd also be happy with 8 voices.

Voices as in polyphony of a synth. Filter voices going into amp voices. Envelope voices going into filter and amp. But there also could be grouped control voltage busses for things other than polyphony. So it serves both purposes. Just wanted to clear up the fact that there are two separate conversations happening here.

Added polyphony to engine with f40d3343fb00d612fd6c837df96ee7a0dcc64530

When you hover over a port with a polyphonic cable attached and the cable highlights, I suggest a small number in the middle of the cable should appear that shows the number of channels.

Performance testing has been positive. I am confident that the overhead of having 16 channels per port is less than a 1% reduction for modules that don't take advantage of it (monophonic modules).

Awesome! Thanks for the early new year's gift!
Now hopefully a lot of devs will want to adapt their modules to poly. Fortunately quite a number of stuff is open source, so poly enthusiastic fans of modules coming from dedicated mono purists often have a second option.... :)

Finished polyphony in 6d86a82. I ask anyone interested in adding polyphony to their plugins to take a look at the new Port methods in https://github.com/VCVRack/Rack/blob/v1/include/engine/Port.hpp#L32-L68.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

oblivionratula picture oblivionratula  路  7Comments

antoniotuzzi picture antoniotuzzi  路  5Comments

AndrewBelt picture AndrewBelt  路  5Comments

synthi picture synthi  路  4Comments

Coirt picture Coirt  路  7Comments