diff --git a/Makefile b/Makefile index 4314a0d..11861e2 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,7 @@ ARCH ?= linux -CXXFLAGS = -MMD -fPIC -g -Wall -std=c++11 -O3 -ffast-math -DTEST \ +CXXFLAGS = -MMD -fPIC -g -Wall -std=c++11 -O3 -msse -mfpmath=sse -ffast-math -DTEST \ -I./src -I../../include -I./eurorack \ - -fasm \ - -finline \ -fshort-enums LDFLAGS = diff --git a/README.md b/README.md index 3fae5ce..f52a1f1 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,51 @@ Percentages are progress amounts, X means won't port #### [100%] Branches (Bernoulli Gate) #### [100%] Blinds (Quad VC-polarizer) #### [100%] Veils (Quad VCA) + + + + +### Sound sources +- [[Macro Oscillator]] (based on [Braids](http://mutable-instruments.net/modules/braids)) +- [[Modal Synthesizer]] (based on [Elements](http://mutable-instruments.net/modules/elements)) + +### Modulation sources +- [[Tidal Modulator]] (based on [Tides](http://mutable-instruments.net/modules/tides)) + +### Sound modifiers +- [[Texture Synthesizer]] (based on [Clouds](http://mutable-instruments.net/modules/clouds)) +- [[Meta Modulator]] (based on [Warps](http://mutable-instruments.net/modules/warps)) +- [[Resonator]] (based on [Rings](http://mutable-instruments.net/modules/rings)) + +### Plumbing +- [[Multiples]] (based on [Links](http://mutable-instruments.net/modules/links)) +- [[Utilities]] (based on [Kinks](http://mutable-instruments.net/modules/kinks)) +- [[Mixer]] (based on [Shades](http://mutable-instruments.net/modules/shades)) +- [[Bernoulli Gate]] (based on [Branches](http://mutable-instruments.net/modules/branches)) +- [[Quad VC-polarizer]] (based on [Blinds](http://mutable-instruments.net/modules/blinds)) +- [[Quad VCA]] (based on [Veils](http://mutable-instruments.net/modules/veils)) + + +![](http://virtuoso.audio/images/AudibleInstruments/macro oscillator.png) + +![](http://virtuoso.audio/images/AudibleInstruments/modal synthesizer.png) + +![](http://virtuoso.audio/images/AudibleInstruments/tidal modulator.png) + +![](http://virtuoso.audio/images/AudibleInstruments/texture synthesizer.png) + +![](http://virtuoso.audio/images/AudibleInstruments/meta modulator.png) + +![](http://virtuoso.audio/images/AudibleInstruments/resonator.png) + +![](http://virtuoso.audio/images/AudibleInstruments/multiples.png) + +![](http://virtuoso.audio/images/AudibleInstruments/utilities.png) + +![](http://virtuoso.audio/images/AudibleInstruments/mixer.png) + +![](http://virtuoso.audio/images/AudibleInstruments/bernoulli gate.png) + +![](http://virtuoso.audio/images/AudibleInstruments/quad VC-polarizer.png) + +![](http://virtuoso.audio/images/AudibleInstruments/quad VCA.png) diff --git a/src/Braids.cpp b/src/Braids.cpp index f8201f5..b1646bd 100644 --- a/src/Braids.cpp +++ b/src/Braids.cpp @@ -1,5 +1,6 @@ -#include "AudibleInstruments.hpp" #include +#include "AudibleInstruments.hpp" +#include "dsp.hpp" #include "braids/macro_oscillator.h" @@ -28,8 +29,8 @@ struct Braids : Module { }; braids::MacroOscillator *osc; - int bufferFrame = 0; - int16_t buffer[24] = {}; + SampleRateConverter<1> src; + DoubleRingBuffer outputBuffer; bool lastTrig = false; Braids(); @@ -54,9 +55,6 @@ Braids::~Braids() { } void Braids::step() { - // TODO Sample rate convert from 96000Hz - setf(outputs[OUT_OUTPUT], 5.0*(buffer[bufferFrame] / 32768.0)); - // Trigger bool trig = getf(inputs[TRIG_INPUT]) >= 1.0; if (!lastTrig && trig) { @@ -64,8 +62,8 @@ void Braids::step() { } lastTrig = trig; - if (++bufferFrame >= 24) { - bufferFrame = 0; + // Render frames + if (outputBuffer.empty()) { // Set shape int shape = roundf(params[SHAPE_PARAM]); osc->set_shape((braids::MacroOscillatorShape) shape); @@ -82,8 +80,28 @@ void Braids::step() { int16_t p = clampf((pitch * 12.0 + 60) * 128, 0, INT16_MAX); osc->set_pitch(p); + // TODO: add a sync input buffer (must be sample rate converted) uint8_t sync_buffer[24] = {}; - osc->Render(sync_buffer, buffer, 24); + + int16_t render_buffer[24]; + osc->Render(sync_buffer, render_buffer, 24); + + // Sample rate convert + float in[24]; + for (int i = 0; i < 24; i++) { + in[i] = render_buffer[i] / 32768.0; + } + src.setRatio(gRack->sampleRate / 96000.0); + + int inLen = 24; + int outLen = outputBuffer.capacity(); + src.process(in, &inLen, outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); + } + + // Output + if (!outputBuffer.empty()) { + setf(outputs[OUT_OUTPUT], 5.0 * outputBuffer.shift()); } } diff --git a/src/Branches.cpp b/src/Branches.cpp index ac44786..3be8475 100644 --- a/src/Branches.cpp +++ b/src/Branches.cpp @@ -45,8 +45,8 @@ static void computeChannel(const float *in, const float *p, float threshold, flo bool gate = (out >= 1.0); if (gate && !*lastGate) { // trigger - std::uniform_real_distribution dist(0.0, 1.0); - bool toss = (dist(rng) < threshold + getf(p)); + float r = randomf(); + bool toss = (r < threshold + getf(p)); if (mode < 0.5) { // direct mode *outcome = toss; diff --git a/src/Clouds.cpp b/src/Clouds.cpp index ecf728d..5553bf1 100644 --- a/src/Clouds.cpp +++ b/src/Clouds.cpp @@ -1,5 +1,6 @@ -#include "AudibleInstruments.hpp" #include +#include "AudibleInstruments.hpp" +#include "dsp.hpp" #include "clouds/dsp/granular_processor.h" @@ -33,14 +34,15 @@ struct Clouds : Module { NUM_OUTPUTS }; + SampleRateConverter<2> inputSrc; + SampleRateConverter<2> outputSrc; + DoubleRingBuffer, 256> inputBuffer; + DoubleRingBuffer, 256> outputBuffer; + uint8_t *block_mem; uint8_t *block_ccm; clouds::GranularProcessor *processor; - int bufferFrame = 0; - float inL[32] = {}; - float inR[32] = {}; - float outL[32] = {}; - float outR[32] = {}; + bool triggered = false; Clouds(); @@ -56,10 +58,8 @@ Clouds::Clouds() { const int memLen = 118784; const int ccmLen = 65536 - 128; - block_mem = new uint8_t[memLen]; - memset(block_mem, 0, memLen); - block_ccm = new uint8_t[ccmLen]; - memset(block_ccm, 0, ccmLen); + block_mem = new uint8_t[memLen](); + block_ccm = new uint8_t[ccmLen](); processor = new clouds::GranularProcessor(); memset(processor, 0, sizeof(*processor)); @@ -73,21 +73,42 @@ Clouds::~Clouds() { } void Clouds::step() { - // TODO Sample rate conversion from 32000 Hz - inL[bufferFrame] = getf(inputs[IN_L_INPUT]); - inR[bufferFrame] = getf(inputs[IN_R_INPUT]); - setf(outputs[OUT_L_OUTPUT], outL[bufferFrame]); - setf(outputs[OUT_R_OUTPUT], outR[bufferFrame]); + // Get input + if (!inputBuffer.full()) { + Frame<2> inputFrame; + inputFrame.samples[0] = getf(inputs[IN_L_INPUT]) * params[IN_GAIN_PARAM] / 5.0; + inputFrame.samples[1] = getf(inputs[IN_R_INPUT]) * params[IN_GAIN_PARAM] / 5.0; + inputBuffer.push(inputFrame); + } // Trigger if (getf(inputs[TRIG_INPUT]) >= 1.0) { triggered = true; } - if (++bufferFrame >= 32) { - bufferFrame = 0; + // Render frames + if (outputBuffer.empty()) { + clouds::ShortFrame input[32] = {}; + // Convert input buffer + { + inputSrc.setRatio(32000.0 / gRack->sampleRate); + Frame<2> inputFrames[32]; + int inLen = inputBuffer.size(); + int outLen = 32; + inputSrc.process((const float*) inputBuffer.startData(), &inLen, (float*) inputFrames, &outLen); + inputBuffer.startIncr(inLen); + + // We might not fill all of the input buffer if there is a deficiency, but this cannot be avoided due to imprecisions between the input and output SRC. + for (int i = 0; i < outLen; i++) { + input[i].l = clampf(inputFrames[i].samples[0] * 32767.0, -32768, 32767); + input[i].r = clampf(inputFrames[i].samples[1] * 32767.0, -32768, 32767); + } + } + + // Set up processor processor->set_num_channels(2); processor->set_low_fidelity(false); + // TODO Support the other modes processor->set_playback_mode(clouds::PLAYBACK_MODE_GRANULAR); processor->Prepare(); @@ -105,22 +126,33 @@ void Clouds::step() { p->feedback = 0.0f; p->reverb = 0.0f; - clouds::ShortFrame input[32]; clouds::ShortFrame output[32]; - for (int j = 0; j < 32; j++) { - input[j].l = clampf(inL[j] * params[IN_GAIN_PARAM] / 5.0, -1.0, 1.0) * 32767; - input[j].r = clampf(inR[j] * params[IN_GAIN_PARAM] / 5.0, -1.0, 1.0) * 32767; - } - processor->Process(input, output, 32); - for (int j = 0; j < 32; j++) { - outL[j] = (float)output[j].l / 32767 * 5.0; - outR[j] = (float)output[j].r / 32767 * 5.0; + // Convert output buffer + { + Frame<2> outputFrames[32]; + for (int i = 0; i < 32; i++) { + outputFrames[i].samples[0] = output[i].l / 32768.0; + outputFrames[i].samples[1] = output[i].r / 32768.0; + } + + outputSrc.setRatio(gRack->sampleRate / 32000.0); + int inLen = 32; + int outLen = outputBuffer.capacity(); + outputSrc.process((const float*) outputFrames, &inLen, (float*) outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); } triggered = false; } + + // Set output + if (!outputBuffer.empty()) { + Frame<2> outputFrame = outputBuffer.shift(); + setf(outputs[OUT_L_OUTPUT], 5.0 * outputFrame.samples[0]); + setf(outputs[OUT_R_OUTPUT], 5.0 * outputFrame.samples[1]); + } } diff --git a/src/Elements.cpp b/src/Elements.cpp index c28f5c2..66ea352 100644 --- a/src/Elements.cpp +++ b/src/Elements.cpp @@ -1,5 +1,6 @@ -#include "AudibleInstruments.hpp" #include +#include "AudibleInstruments.hpp" +#include "dsp.hpp" #include "elements/dsp/part.h" @@ -65,13 +66,13 @@ struct Elements : Module { NUM_OUTPUTS }; + SampleRateConverter<2> inputSrc; + SampleRateConverter<2> outputSrc; + DoubleRingBuffer, 256> inputBuffer; + DoubleRingBuffer, 256> outputBuffer; + uint16_t reverb_buffer[32768] = {}; elements::Part *part; - int bufferFrame = 0; - float blow[16] = {}; - float strike[16] = {}; - float main[16] = {}; - float aux[16] = {}; float lights[2] = {}; Elements(); @@ -99,14 +100,36 @@ Elements::~Elements() { } void Elements::step() { - // TODO Sample rate convert from 32000Hz - blow[bufferFrame] = getf(inputs[BLOW_INPUT]); - strike[bufferFrame] = getf(inputs[STRIKE_INPUT]); - setf(outputs[AUX_OUTPUT], 5.0*aux[bufferFrame]); - setf(outputs[MAIN_OUTPUT], 5.0*main[bufferFrame]); - - if (++bufferFrame >= 16) { - bufferFrame = 0; + // Get input + if (!inputBuffer.full()) { + Frame<2> inputFrame; + inputFrame.samples[0] = getf(inputs[BLOW_INPUT]) / 5.0; + inputFrame.samples[1] = getf(inputs[STRIKE_INPUT]) / 5.0; + inputBuffer.push(inputFrame); + } + + // Render frames + if (outputBuffer.empty()) { + float blow[16] = {}; + float strike[16] = {}; + float main[16]; + float aux[16]; + + // Convert input buffer + { + inputSrc.setRatio(32000.0 / gRack->sampleRate); + Frame<2> inputFrames[16]; + int inLen = inputBuffer.size(); + int outLen = 16; + inputSrc.process((float*) inputBuffer.startData(), &inLen, (float*) inputFrames, &outLen); + inputBuffer.startIncr(inLen); + + for (int i = 0; i < outLen; i++) { + blow[i] = inputFrames[i].samples[0]; + strike[i] = inputFrames[i].samples[1]; + } + } + // Set patch from parameters elements::Patch* p = part->mutable_patch(); p->exciter_envelope_shape = params[CONTOUR_PARAM]; @@ -114,7 +137,7 @@ void Elements::step() { p->exciter_blow_level = params[BLOW_PARAM]; p->exciter_strike_level = params[STRIKE_PARAM]; - #define BIND(_p, _m, _i) clampf(params[_p] + 3.3*quadraticBipolar(params[_m])*getf(inputs[_i])/5.0, 0.0, 0.9995) +#define BIND(_p, _m, _i) clampf(params[_p] + 3.3*quadraticBipolar(params[_m])*getf(inputs[_i])/5.0, 0.0, 0.9995) p->exciter_bow_timbre = BIND(BOW_TIMBRE_PARAM, BOW_TIMBRE_MOD_PARAM, BOW_TIMBRE_MOD_INPUT); p->exciter_blow_meta = BIND(FLOW_PARAM, FLOW_MOD_PARAM, FLOW_MOD_INPUT); @@ -137,10 +160,32 @@ void Elements::step() { // Generate audio part->Process(performance, blow, strike, main, aux, 16); + // Convert output buffer + { + Frame<2> outputFrames[16]; + for (int i = 0; i < 16; i++) { + outputFrames[i].samples[0] = main[i]; + outputFrames[i].samples[1] = aux[i]; + } + + outputSrc.setRatio(gRack->sampleRate / 32000.0); + int inLen = 16; + int outLen = outputBuffer.capacity(); + outputSrc.process((float*) outputFrames, &inLen, (float*) outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); + } + // Set lights lights[0] = part->exciter_level(); lights[1] = part->resonator_level(); } + + // Set output + if (!outputBuffer.empty()) { + Frame<2> outputFrame = outputBuffer.shift(); + setf(outputs[AUX_OUTPUT], 5.0 * outputFrame.samples[0]); + setf(outputs[MAIN_OUTPUT], 5.0 * outputFrame.samples[1]); + } } diff --git a/src/Kinks.cpp b/src/Kinks.cpp index f24c30a..d328ae5 100644 --- a/src/Kinks.cpp +++ b/src/Kinks.cpp @@ -28,7 +28,6 @@ struct Kinks : Module { NUM_OUTPUTS }; - std::normal_distribution dist; float lastTrig = 0.0; float sample = 0.0; float lights[3] = {}; @@ -38,7 +37,7 @@ struct Kinks : Module { }; -Kinks::Kinks() : dist(0.0, 1.0) { +Kinks::Kinks() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); @@ -46,11 +45,11 @@ Kinks::Kinks() : dist(0.0, 1.0) { void Kinks::step() { // Gaussian noise generator - float noise = 2.0 * dist(rng); + float noise = 2.0 * randomNormal(); // S&H float trig = getf(inputs[TRIG_INPUT]); - float dtrig = (trig - lastTrig) * SAMPLE_RATE; + float dtrig = (trig - lastTrig) * gRack->sampleRate; if (dtrig > DTRIG) { sample = getf(inputs[SH_INPUT], noise); } diff --git a/src/Rings.cpp b/src/Rings.cpp index ff8c31f..eab283d 100644 --- a/src/Rings.cpp +++ b/src/Rings.cpp @@ -1,5 +1,6 @@ -#include "AudibleInstruments.hpp" #include +#include "AudibleInstruments.hpp" +#include "dsp.hpp" #include "rings/dsp/part.h" #include "rings/dsp/strummer.h" #include "rings/dsp/string_synth_part.h" @@ -41,11 +42,12 @@ struct Rings : Module { NUM_OUTPUTS }; + SampleRateConverter<1> inputSrc; + SampleRateConverter<2> outputSrc; + DoubleRingBuffer inputBuffer; + DoubleRingBuffer, 256> outputBuffer; + uint16_t reverb_buffer[32768] = {}; - int bufferFrame = 0; - float in[24] = {}; - float out[24] = {}; - float aux[24] = {}; rings::Part part; rings::StringSynthPart string_synth; rings::Strummer strummer; @@ -76,33 +78,36 @@ Rings::~Rings() { } void Rings::step() { - // TODO Sample rate conversion from 48000 Hz // TODO // "Normalized to a pulse/burst generator that reacts to note changes on the V/OCT input." - in[bufferFrame] = clampf(getf(inputs[IN_INPUT])/5.0, -1.0, 1.0); - // "Note that you need to insert a jack into each output to split the signals: when only one jack is inserted, both signals are mixed together." - if (outputs[ODD_OUTPUT] && outputs[EVEN_OUTPUT]) { - setf(outputs[ODD_OUTPUT], clampf(out[bufferFrame], -1.0, 1.0)*5.0); - setf(outputs[EVEN_OUTPUT], clampf(aux[bufferFrame], -1.0, 1.0)*5.0); - } - else { - float v = clampf(out[bufferFrame] + aux[bufferFrame], -1.0, 1.0)*5.0; - setf(outputs[ODD_OUTPUT], v); - setf(outputs[EVEN_OUTPUT], v); + // Get input + if (!inputBuffer.full()) { + float inputFrame = getf(inputs[IN_INPUT]) / 5.0; + inputBuffer.push(inputFrame); } if (!strum) { strum = getf(inputs[STRUM_INPUT]) >= 1.0; } - if (++bufferFrame >= 24) { - bufferFrame = 0; + // Render frames + if (outputBuffer.empty()) { + float in[24] = {}; + // Convert input buffer + { + inputSrc.setRatio(48000.0 / gRack->sampleRate); + int inLen = inputBuffer.size(); + int outLen = 24; + inputSrc.process(inputBuffer.startData(), &inLen, (float*) in, &outLen); + inputBuffer.startIncr(inLen); + } // modes - int polyphony = 1; + int polyphony; switch ((int) roundf(params[POLYPHONY_PARAM])) { case 1: polyphony = 2; break; case 2: polyphony = 4; break; + default: polyphony = 1; } if (polyphony != part.polyphony()) { part.set_polyphony(polyphony); @@ -145,6 +150,8 @@ void Rings::step() { performance_state.chord = clampf(roundf(structure * (rings::kNumChords - 1)), 0, rings::kNumChords - 1); // Process audio + float out[24]; + float aux[24]; if (0) { // strummer.Process(NULL, 24, &performance_state); // string_synth.Process(performance_state, patch, in, out, aux, 24); @@ -153,7 +160,38 @@ void Rings::step() { strummer.Process(in, 24, &performance_state); part.Process(performance_state, patch, in, out, aux, 24); } + + // Convert output buffer + { + Frame<2> outputFrames[24]; + for (int i = 0; i < 24; i++) { + outputFrames[i].samples[0] = out[i]; + outputFrames[i].samples[1] = aux[i]; + } + + outputSrc.setRatio(gRack->sampleRate / 48000.0); + int inLen = 24; + int outLen = outputBuffer.capacity(); + outputSrc.process((const float*) outputFrames, &inLen, (float*) outputBuffer.endData(), &outLen); + outputBuffer.endIncr(outLen); + } } + + // Set output + if (!outputBuffer.empty()) { + Frame<2> outputFrame = outputBuffer.shift(); + // "Note that you need to insert a jack into each output to split the signals: when only one jack is inserted, both signals are mixed together." + if (outputs[ODD_OUTPUT] && outputs[EVEN_OUTPUT]) { + setf(outputs[ODD_OUTPUT], clampf(outputFrame.samples[0], -1.0, 1.0)*5.0); + setf(outputs[EVEN_OUTPUT], clampf(outputFrame.samples[1], -1.0, 1.0)*5.0); + } + else { + float v = clampf(outputFrame.samples[0] + outputFrame.samples[1], -1.0, 1.0)*5.0; + setf(outputs[ODD_OUTPUT], v); + setf(outputs[EVEN_OUTPUT], v); + } + } + } diff --git a/src/Tides.cpp b/src/Tides.cpp index 854f860..1a74dc4 100644 --- a/src/Tides.cpp +++ b/src/Tides.cpp @@ -82,6 +82,8 @@ void Tides::step() { pitch += 12.0*getf(inputs[PITCH_INPUT]); pitch += params[FM_PARAM] * getf(inputs[FM_INPUT], 0.1) / 5.0; pitch += 60.0; + // Scale to the global sample rate + pitch += log2f(48000.0 / gRack->sampleRate) * 12.0; generator.set_pitch(clampf(pitch*0x80, -0x8000, 0x7fff)); // Slope, smoothness, pitch diff --git a/src/Veils.cpp b/src/Veils.cpp index 44d891e..b00f108 100644 --- a/src/Veils.cpp +++ b/src/Veils.cpp @@ -108,10 +108,10 @@ VeilsWidget::VeilsWidget() : ModuleWidget(new Veils()) { addChild(createScrew(Vec(15, 365))); addChild(createScrew(Vec(150, 365))); - addParam(createParam(Vec(8, 52), module, Veils::GAIN1_PARAM, 0.0, 1.0, 0.5)); - addParam(createParam(Vec(8, 131), module, Veils::GAIN2_PARAM, 0.0, 1.0, 0.5)); - addParam(createParam(Vec(8, 210), module, Veils::GAIN3_PARAM, 0.0, 1.0, 0.5)); - addParam(createParam(Vec(8, 288), module, Veils::GAIN4_PARAM, 0.0, 1.0, 0.5)); + addParam(createParam(Vec(8, 52), module, Veils::GAIN1_PARAM, 0.0, 1.0, 0.0)); + addParam(createParam(Vec(8, 131), module, Veils::GAIN2_PARAM, 0.0, 1.0, 0.0)); + addParam(createParam(Vec(8, 210), module, Veils::GAIN3_PARAM, 0.0, 1.0, 0.0)); + addParam(createParam(Vec(8, 288), module, Veils::GAIN4_PARAM, 0.0, 1.0, 0.0)); addParam(createParam(Vec(72, 56), module, Veils::RESPONSE1_PARAM, 0.0, 1.0, 1.0)); addParam(createParam(Vec(72, 135), module, Veils::RESPONSE2_PARAM, 0.0, 1.0, 1.0)); diff --git a/src/Warps.cpp b/src/Warps.cpp index af56079..bd08181 100644 --- a/src/Warps.cpp +++ b/src/Warps.cpp @@ -61,6 +61,7 @@ void Warps::step() { p->frequency_shift_cv = clampf(getf(inputs[ALGORITHM_INPUT]) / 5.0, -1.0, 1.0); p->phase_shift = p->modulation_algorithm; p->note = 60.0 * params[LEVEL1_PARAM] + 12.0 * getf(inputs[LEVEL1_INPUT], 2.0) + 12.0; + p->note += log2f(96000.0 / gRack->sampleRate) * 12.0; float state = roundf(params[STATE_PARAM]); p->carrier_shape = (int32_t)state; lights[0] = state - 1.0;