| @@ -130,9 +130,13 @@ struct Timer { | |||
| }; | |||
| /** Counts the number of `process()` calls. | |||
| If `period > 0`, `count` is reset to 0 when that number is reached. | |||
| Useful for clock dividing and waiting to fill a fixed buffer. | |||
| */ | |||
| struct Counter { | |||
| int count; | |||
| int period; | |||
| int period = 0; | |||
| Counter() { | |||
| reset(); | |||
| @@ -140,16 +144,17 @@ struct Counter { | |||
| void reset() { | |||
| count = 0; | |||
| period = 1; | |||
| } | |||
| void setPeriod(int period) { | |||
| this->period = period; | |||
| reset(); | |||
| } | |||
| /** Returns true if the counter reaches `period` and resets. */ | |||
| /** Returns true when the counter reaches `period` and resets. */ | |||
| bool process() { | |||
| if (++count >= period) { | |||
| count++; | |||
| if (count == period) { | |||
| count = 0; | |||
| return true; | |||
| } | |||
| @@ -38,9 +38,16 @@ struct Module { | |||
| void reset(); | |||
| void randomize(); | |||
| struct ProcessContext { | |||
| float sampleRate; | |||
| float sampleTime; | |||
| }; | |||
| /** Advances the module by one audio sample. | |||
| Override this method to read Inputs and Params and to write Outputs and Lights. | |||
| */ | |||
| virtual void process(const ProcessContext &ctx) {} | |||
| /** Deprecated. Override process() instead. */ | |||
| virtual void step() {} | |||
| /** Called when the engine sample rate is changed. */ | |||
| @@ -122,11 +122,10 @@ struct AudioInterface : Module { | |||
| onSampleRateChange(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| // Update SRC states | |||
| int sampleRate = (int) APP->engine->getSampleRate(); | |||
| inputSrc.setRates(port.sampleRate, sampleRate); | |||
| outputSrc.setRates(sampleRate, port.sampleRate); | |||
| inputSrc.setRates(port.sampleRate, ctx.sampleRate); | |||
| outputSrc.setRates(ctx.sampleRate, port.sampleRate); | |||
| inputSrc.setChannels(port.numInputs); | |||
| outputSrc.setChannels(port.numOutputs); | |||
| @@ -62,9 +62,9 @@ struct CV_CC : Module { | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| const float rateLimiterPeriod = 0.010f; | |||
| rateLimiterPhase += APP->engine->getSampleTime() / rateLimiterPeriod; | |||
| rateLimiterPhase += ctx.sampleTime / rateLimiterPeriod; | |||
| if (rateLimiterPhase >= 1.f) { | |||
| rateLimiterPhase -= 1.f; | |||
| } | |||
| @@ -89,7 +89,7 @@ struct CV_Gate : Module { | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| for (int i = 0; i < 16; i++) { | |||
| int note = learnedNotes[i]; | |||
| if (velocityMode) { | |||
| @@ -241,9 +241,9 @@ struct CV_MIDI : Module { | |||
| midiOutput.midi::Output::reset(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| const float rateLimiterPeriod = 0.005f; | |||
| rateLimiterPhase += APP->engine->getSampleTime() / rateLimiterPeriod; | |||
| rateLimiterPhase += ctx.sampleTime / rateLimiterPeriod; | |||
| if (rateLimiterPhase >= 1.f) { | |||
| rateLimiterPhase -= 1.f; | |||
| } | |||
| @@ -42,14 +42,12 @@ struct MIDI_CC : Module { | |||
| midiInput.reset(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| midi::Message msg; | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| float deltaTime = APP->engine->getSampleTime(); | |||
| for (int i = 0; i < 16; i++) { | |||
| if (!outputs[CC_OUTPUT + i].isConnected()) | |||
| continue; | |||
| @@ -65,7 +63,7 @@ struct MIDI_CC : Module { | |||
| } | |||
| else { | |||
| // Smooth value with filter | |||
| valueFilters[i].process(deltaTime, value); | |||
| valueFilters[i].process(ctx.sampleTime, value); | |||
| } | |||
| lastValues[i] = values[cc]; | |||
| outputs[CC_OUTPUT + i].setVoltage(valueFilters[i].out); | |||
| @@ -103,12 +103,11 @@ struct MIDI_CV : Module { | |||
| heldNotes.clear(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| midi::Message msg; | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| float deltaTime = APP->engine->getSampleTime(); | |||
| outputs[CV_OUTPUT].setChannels(channels); | |||
| outputs[GATE_OUTPUT].setChannels(channels); | |||
| @@ -120,29 +119,29 @@ struct MIDI_CV : Module { | |||
| outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c); | |||
| outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[c], 0, 127, 0.f, 10.f), c); | |||
| outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c); | |||
| outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(deltaTime) ? 10.f : 0.f, c); | |||
| outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(ctx.sampleTime) ? 10.f : 0.f, c); | |||
| } | |||
| if (polyMode == MPE_MODE) { | |||
| for (int c = 0; c < channels; c++) { | |||
| outputs[PITCH_OUTPUT].setChannels(channels); | |||
| outputs[MOD_OUTPUT].setChannels(channels); | |||
| outputs[PITCH_OUTPUT].setVoltage(pitchFilters[c].process(deltaTime, rescale(pitches[c], 0, 1<<14, -5.f, 5.f)), c); | |||
| outputs[MOD_OUTPUT].setVoltage(modFilters[c].process(deltaTime, rescale(mods[c], 0, 127, 0.f, 10.f)), c); | |||
| outputs[PITCH_OUTPUT].setVoltage(pitchFilters[c].process(ctx.sampleTime, rescale(pitches[c], 0, 1<<14, -5.f, 5.f)), c); | |||
| outputs[MOD_OUTPUT].setVoltage(modFilters[c].process(ctx.sampleTime, rescale(mods[c], 0, 127, 0.f, 10.f)), c); | |||
| } | |||
| } | |||
| else { | |||
| outputs[PITCH_OUTPUT].setChannels(1); | |||
| outputs[MOD_OUTPUT].setChannels(1); | |||
| outputs[PITCH_OUTPUT].setVoltage(pitchFilters[0].process(deltaTime, rescale(pitches[0], 0, 1<<14, -5.f, 5.f))); | |||
| outputs[MOD_OUTPUT].setVoltage(modFilters[0].process(deltaTime, rescale(mods[0], 0, 127, 0.f, 10.f))); | |||
| outputs[PITCH_OUTPUT].setVoltage(pitchFilters[0].process(ctx.sampleTime, rescale(pitches[0], 0, 1<<14, -5.f, 5.f))); | |||
| outputs[MOD_OUTPUT].setVoltage(modFilters[0].process(ctx.sampleTime, rescale(mods[0], 0, 127, 0.f, 10.f))); | |||
| } | |||
| outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(deltaTime) ? 10.f : 0.f); | |||
| outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(deltaTime) ? 10.f : 0.f); | |||
| outputs[START_OUTPUT].setVoltage(startPulse.process(deltaTime) ? 10.f : 0.f); | |||
| outputs[STOP_OUTPUT].setVoltage(stopPulse.process(deltaTime) ? 10.f : 0.f); | |||
| outputs[CONTINUE_OUTPUT].setVoltage(continuePulse.process(deltaTime) ? 10.f : 0.f); | |||
| outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(ctx.sampleTime) ? 10.f : 0.f); | |||
| outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(ctx.sampleTime) ? 10.f : 0.f); | |||
| outputs[START_OUTPUT].setVoltage(startPulse.process(ctx.sampleTime) ? 10.f : 0.f); | |||
| outputs[STOP_OUTPUT].setVoltage(stopPulse.process(ctx.sampleTime) ? 10.f : 0.f); | |||
| outputs[CONTINUE_OUTPUT].setVoltage(continuePulse.process(ctx.sampleTime) ? 10.f : 0.f); | |||
| } | |||
| void processMessage(midi::Message msg) { | |||
| @@ -46,12 +46,11 @@ struct MIDI_Gate : Module { | |||
| } | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| midi::Message msg; | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| float deltaTime = APP->engine->getSampleTime(); | |||
| for (int i = 0; i < 16; i++) { | |||
| if (gateTimes[i] > 0.f) { | |||
| @@ -59,7 +58,7 @@ struct MIDI_Gate : Module { | |||
| // If the gate is off, wait 1 ms before turning the pulse off. | |||
| // This avoids drum controllers sending a pulse with 0 ms duration. | |||
| if (!gates[i]) { | |||
| gateTimes[i] -= deltaTime; | |||
| gateTimes[i] -= ctx.sampleTime; | |||
| } | |||
| } | |||
| else { | |||
| @@ -65,14 +65,12 @@ struct MIDI_Map : Module { | |||
| midiInput.reset(); | |||
| } | |||
| void step() override { | |||
| void process(const ProcessContext &ctx) override { | |||
| midi::Message msg; | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| float deltaTime = APP->engine->getSampleTime(); | |||
| // Step channels | |||
| for (int id = 0; id < mapLen; id++) { | |||
| int cc = ccs[id]; | |||
| @@ -92,7 +90,7 @@ struct MIDI_Map : Module { | |||
| continue; | |||
| // Set param | |||
| float v = rescale(values[cc], 0, 127, 0.f, 1.f); | |||
| v = valueFilters[id].process(deltaTime, v); | |||
| v = valueFilters[id].process(ctx.sampleTime, v); | |||
| v = rescale(v, 0.f, 1.f, param->minValue, param->maxValue); | |||
| APP->engine->setParam(module, paramId, v); | |||
| } | |||
| @@ -190,7 +190,11 @@ static void Engine_stepModules(Engine *engine, int threadId) { | |||
| // int threadCount = internal->threadCount; | |||
| int modulesLen = internal->modules.size(); | |||
| float deltaTime = internal->sampleTime; | |||
| float sampleTime = internal->sampleTime; | |||
| Module::ProcessContext processCtx; | |||
| processCtx.sampleRate = internal->sampleRate; | |||
| processCtx.sampleTime = internal->sampleTime; | |||
| // Step each module | |||
| // for (int i = threadId; i < modulesLen; i += threadCount) { | |||
| @@ -206,25 +210,28 @@ static void Engine_stepModules(Engine *engine, int threadId) { | |||
| if (settings.cpuMeter) { | |||
| auto startTime = std::chrono::high_resolution_clock::now(); | |||
| module->process(processCtx); | |||
| module->step(); | |||
| auto stopTime = std::chrono::high_resolution_clock::now(); | |||
| float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | |||
| // Smooth CPU time | |||
| const float cpuTau = 2.f /* seconds */; | |||
| module->cpuTime += (cpuTime - module->cpuTime) * deltaTime / cpuTau; | |||
| module->cpuTime += (cpuTime - module->cpuTime) * sampleTime / cpuTau; | |||
| } | |||
| else { | |||
| module->process(processCtx); | |||
| // Call deprecated method | |||
| module->step(); | |||
| } | |||
| } | |||
| // Iterate ports to step plug lights | |||
| for (Input &input : module->inputs) { | |||
| input.process(deltaTime); | |||
| input.process(sampleTime); | |||
| } | |||
| for (Output &output : module->outputs) { | |||
| output.process(deltaTime); | |||
| output.process(sampleTime); | |||
| } | |||
| } | |||
| } | |||