How polyphonic cables will work in Rack v1

Not to derail this thread completely, but I thought this was relevant:

2 Likes

Not really special since there are dozens of multi-channel modules like that.

I’ve only heard of one real polyphonic hardware module, the P3000, which never existed. It was designed to use Mini-DIN-8 connectors. You could also use HDMI or mini-HDMI, 8P8C (ethernet), USB-C, D-sub, or any other type of multi-core cable for carrying polyphonic signals.

Agreed. This was mainly in response to jay.goodman00’s comment on monophony and polyphony in modular synths. Apparently, it IS a relevant topic.

The P3000 looks interesting. Hadn’t see that interface with the Mini-DIN-8 connectors.

I sure will. I probably would have been skeptical of the horseless carriage back then. :wink:

Hi all and @Vortico.
How far we are from testing VCV 1.0 polyphony features?
As far as i know there are still some Fundamental stuff to be made polyphonic.
Am i right?
thanks!

Making your monophonic module polyphonic

Here’s an example of how one would add polyphony to your Module class, say an oscillator.

Monophonic:

VcoEngine vcoEngine;

void process(const ProcessArgs &args) override {
	float pitch = inputs[PITCH_INPUT].getVoltage();
	pitch += params[PITCH_PARAM].getValue();
	vcoEngine.setPitch(pitch);
	vcoEngine.process(args.sampleTime);
	outputs[SINE_OUTPUT].setVoltage(vcoEngine.getSine());
}

Polyphonic:

VcoEngine vcoEngine[16];

void process(const ProcessArgs &args) override {
	int channels = inputs[PITCH_INPUT].getChannels();
	for (int c = 0; c < channels; c++) {
		float pitch = inputs[PITCH_INPUT].getVoltage(c);
		pitch += params[PITCH_PARAM].getValue();
		vcoEngine[c].setPitch(pitch);
		vcoEngine[c].process(args.sampleTime);
		outputs[SINE_OUTPUT].setVoltage(vcoEngine[c].getSine(), c);
	}
	// Don't forget to set the number of output channels
	outputs[SINE_OUTPUT].setChannels(channels);
}

This is all you need to get polyphony working.

Advanced: using SIMD

But using 16 voices would take roughly 16x the CPU compared to the monophonic version for the 16 calls to the expensive VcoEngine::process() method. What if I told you it was possible to use only a bit more than 4x the CPU? (And ~1x for using 4 voices.) Enter 128-bit SIMD, using Rack’s simd/vector.hpp header.

using namespace simd;
// 4 engines processing 4 floats simultaneously
VcoEngine<float_4> vcoEngine[4];

void process(const ProcessArgs &args) override {
	int channels = inputs[PITCH_INPUT].getChannels();
	// Advance by 4 channels each time
	for (int c = 0; c < channels; c += 4) {
		float_4 pitch = float_4::load(inputs[PITCH_INPUT].getVoltages(c));
		// The simd API allows you to add single floats to vectors
		pitch += params[PITCH_PARAM].getValue();
		vcoEngine[c / 4].setPitch(pitch);
		vcoEngine[c / 4].process(args.sampleTime);
		float_4 sine = vcoEngine[c / 4].getSine();
		sine.store(outputs[SINE_OUTPUT].getVoltages(c));
	}
	outputs[SINE_OUTPUT].setChannels(channels);
}

Note that this requires the hypothetical VcoEngine code to be written to support simd::float_4. Here I’ve used a template (template <typename T> struct VcoEngine {...), but you can use the type directly if you want.

6 Likes

Do you plan to eventually make dsp::RCFilter and company SIMD also? Just wondering since some mixing consoles have many filters (mscHack comes to mind), and those could be imrproved also if they used SIMD filter code. I may also want an SIMD RCFilter for a Geodesics module, and I will probably parallelize your RCFilter, but I would ideally like to avoid the code duplication if possible (I could even send you my parallel version of your RCFilter if ever you don’t plan on making it and are interested).

1 Like

Yes, one day I’ll turn all classes and functions in dsp:: into template <typename T = float>. The difficulty is handling things like logic and branching.

1 Like

Went ahead and made RCFilter and a few others SIMD-ified, since I’ll need them for Fundamental modules. More SIMD coming soon.

5 Likes

Logic is handled with float_4 masks (e.g. x > y returns either 0x00000000 or 0xffffffff for each of the 4 f32’s), which you can use with the float_4 ifelse(float_4 mask, float_4 a, float_4 b) function. You can do stuff like

template <typename T>
T sinc(T x) {
	x *= M_PI;
	T y = simd::sin(x) / x;
	return simd::ifelse(x == 0.f, 1.f, x);
}

and it’ll automatically work for both float and float_4.

Use TRCFilter<float_4> filter; for creating a filter that processes a float_4.

1 Like

Awesome, thanks, I’ll give it a try! Do you know offhand if there is much of an overhead associated with loading into SIMD space, so to speak, and then storing back to normal float array space? Just trying to see if there are instances, perhaps short calculations, that would be better to just do normally instead of SIMD, or is it always a gain in performance? Profiling would give a definite answer, but just wondering in your experience what you have noticed regarding this.

_mm_storeu_ps/_mm_loadu_ps take about 1 CPU cycle, plus overhead of copying the scalars and SIMD vectors between cache and registers, which depends on where your scalars are.

1 Like

@Vortico
What is the status of polyphony for Fundamental VCO.
I tried it in the Linux dev version, that is linked in the other thread.
But seems it is not polyphonic yet?
I could be wrong?

I’ve updated Fundamental’s manifest with Polyphonic tags for the current poly-supporting modules. https://github.com/VCVRack/Fundamental/blob/v1/plugin.json

Ah ok! :+1:t4:

Unrelated, but I was wondering if Octave is deprected or it will be ported to v1 at a later time?

Thanks, added.

2 Likes

i have like a basic polyphony question, cause i think i’m not approaching it correctly yet.
does it work like this: “send 4 CV pitch signals to the MUX module, then patch the poly out of the MUX module into a polyphonic VCO v/oct input” ?

Here’s polyphony working with Blamsoft XFX Wave

I guess there are some different ways of approaching it, since at first try I was expecting the XFX to mix all the channels into a single output wire on its output, but it actually outputs all the channels separately in a poly cable, and we have to split them to mix them. I guess it offers more control this way.

4 Likes

owwww ok, I also need to use a split afterwards!
thanks Marc!
I will try that out tonight after work. :wink::+1:t4: