Here’s an example of how one would add polyphony to your Module class, say an oscillator. To isolate the problem, I’m wrapping all your custom functionality into an imaginary class VcoEngine.
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
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.) You can use 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.
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).
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.
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.
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.
@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 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” ?
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.
Typically you don’t need to adjust channel levels independently. You can just use Fundamental Sum to add all poly channels of an input into a mono output.