@@ -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 { | struct Counter { | ||||
int count; | int count; | ||||
int period; | |||||
int period = 0; | |||||
Counter() { | Counter() { | ||||
reset(); | reset(); | ||||
@@ -140,16 +144,17 @@ struct Counter { | |||||
void reset() { | void reset() { | ||||
count = 0; | count = 0; | ||||
period = 1; | |||||
} | } | ||||
void setPeriod(int period) { | void setPeriod(int period) { | ||||
this->period = 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() { | bool process() { | ||||
if (++count >= period) { | |||||
count++; | |||||
if (count == period) { | |||||
count = 0; | count = 0; | ||||
return true; | return true; | ||||
} | } | ||||
@@ -38,9 +38,16 @@ struct Module { | |||||
void reset(); | void reset(); | ||||
void randomize(); | void randomize(); | ||||
struct ProcessContext { | |||||
float sampleRate; | |||||
float sampleTime; | |||||
}; | |||||
/** Advances the module by one audio sample. | /** Advances the module by one audio sample. | ||||
Override this method to read Inputs and Params and to write Outputs and Lights. | 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() {} | virtual void step() {} | ||||
/** Called when the engine sample rate is changed. */ | /** Called when the engine sample rate is changed. */ | ||||
@@ -122,11 +122,10 @@ struct AudioInterface : Module { | |||||
onSampleRateChange(); | onSampleRateChange(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
// Update SRC states | // 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); | inputSrc.setChannels(port.numInputs); | ||||
outputSrc.setChannels(port.numOutputs); | outputSrc.setChannels(port.numOutputs); | ||||
@@ -62,9 +62,9 @@ struct CV_CC : Module { | |||||
midiOutput.midi::Output::reset(); | midiOutput.midi::Output::reset(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
const float rateLimiterPeriod = 0.010f; | const float rateLimiterPeriod = 0.010f; | ||||
rateLimiterPhase += APP->engine->getSampleTime() / rateLimiterPeriod; | |||||
rateLimiterPhase += ctx.sampleTime / rateLimiterPeriod; | |||||
if (rateLimiterPhase >= 1.f) { | if (rateLimiterPhase >= 1.f) { | ||||
rateLimiterPhase -= 1.f; | rateLimiterPhase -= 1.f; | ||||
} | } | ||||
@@ -89,7 +89,7 @@ struct CV_Gate : Module { | |||||
midiOutput.midi::Output::reset(); | midiOutput.midi::Output::reset(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
int note = learnedNotes[i]; | int note = learnedNotes[i]; | ||||
if (velocityMode) { | if (velocityMode) { | ||||
@@ -241,9 +241,9 @@ struct CV_MIDI : Module { | |||||
midiOutput.midi::Output::reset(); | midiOutput.midi::Output::reset(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
const float rateLimiterPeriod = 0.005f; | const float rateLimiterPeriod = 0.005f; | ||||
rateLimiterPhase += APP->engine->getSampleTime() / rateLimiterPeriod; | |||||
rateLimiterPhase += ctx.sampleTime / rateLimiterPeriod; | |||||
if (rateLimiterPhase >= 1.f) { | if (rateLimiterPhase >= 1.f) { | ||||
rateLimiterPhase -= 1.f; | rateLimiterPhase -= 1.f; | ||||
} | } | ||||
@@ -42,14 +42,12 @@ struct MIDI_CC : Module { | |||||
midiInput.reset(); | midiInput.reset(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
midi::Message msg; | midi::Message msg; | ||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float deltaTime = APP->engine->getSampleTime(); | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
if (!outputs[CC_OUTPUT + i].isConnected()) | if (!outputs[CC_OUTPUT + i].isConnected()) | ||||
continue; | continue; | ||||
@@ -65,7 +63,7 @@ struct MIDI_CC : Module { | |||||
} | } | ||||
else { | else { | ||||
// Smooth value with filter | // Smooth value with filter | ||||
valueFilters[i].process(deltaTime, value); | |||||
valueFilters[i].process(ctx.sampleTime, value); | |||||
} | } | ||||
lastValues[i] = values[cc]; | lastValues[i] = values[cc]; | ||||
outputs[CC_OUTPUT + i].setVoltage(valueFilters[i].out); | outputs[CC_OUTPUT + i].setVoltage(valueFilters[i].out); | ||||
@@ -103,12 +103,11 @@ struct MIDI_CV : Module { | |||||
heldNotes.clear(); | heldNotes.clear(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
midi::Message msg; | midi::Message msg; | ||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float deltaTime = APP->engine->getSampleTime(); | |||||
outputs[CV_OUTPUT].setChannels(channels); | outputs[CV_OUTPUT].setChannels(channels); | ||||
outputs[GATE_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[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[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[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) { | if (polyMode == MPE_MODE) { | ||||
for (int c = 0; c < channels; c++) { | for (int c = 0; c < channels; c++) { | ||||
outputs[PITCH_OUTPUT].setChannels(channels); | outputs[PITCH_OUTPUT].setChannels(channels); | ||||
outputs[MOD_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 { | else { | ||||
outputs[PITCH_OUTPUT].setChannels(1); | outputs[PITCH_OUTPUT].setChannels(1); | ||||
outputs[MOD_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) { | 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; | midi::Message msg; | ||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float deltaTime = APP->engine->getSampleTime(); | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
if (gateTimes[i] > 0.f) { | 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. | // If the gate is off, wait 1 ms before turning the pulse off. | ||||
// This avoids drum controllers sending a pulse with 0 ms duration. | // This avoids drum controllers sending a pulse with 0 ms duration. | ||||
if (!gates[i]) { | if (!gates[i]) { | ||||
gateTimes[i] -= deltaTime; | |||||
gateTimes[i] -= ctx.sampleTime; | |||||
} | } | ||||
} | } | ||||
else { | else { | ||||
@@ -65,14 +65,12 @@ struct MIDI_Map : Module { | |||||
midiInput.reset(); | midiInput.reset(); | ||||
} | } | ||||
void step() override { | |||||
void process(const ProcessContext &ctx) override { | |||||
midi::Message msg; | midi::Message msg; | ||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float deltaTime = APP->engine->getSampleTime(); | |||||
// Step channels | // Step channels | ||||
for (int id = 0; id < mapLen; id++) { | for (int id = 0; id < mapLen; id++) { | ||||
int cc = ccs[id]; | int cc = ccs[id]; | ||||
@@ -92,7 +90,7 @@ struct MIDI_Map : Module { | |||||
continue; | continue; | ||||
// Set param | // Set param | ||||
float v = rescale(values[cc], 0, 127, 0.f, 1.f); | 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); | v = rescale(v, 0.f, 1.f, param->minValue, param->maxValue); | ||||
APP->engine->setParam(module, paramId, v); | APP->engine->setParam(module, paramId, v); | ||||
} | } | ||||
@@ -190,7 +190,11 @@ static void Engine_stepModules(Engine *engine, int threadId) { | |||||
// int threadCount = internal->threadCount; | // int threadCount = internal->threadCount; | ||||
int modulesLen = internal->modules.size(); | 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 | // Step each module | ||||
// for (int i = threadId; i < modulesLen; i += threadCount) { | // for (int i = threadId; i < modulesLen; i += threadCount) { | ||||
@@ -206,25 +210,28 @@ static void Engine_stepModules(Engine *engine, int threadId) { | |||||
if (settings.cpuMeter) { | if (settings.cpuMeter) { | ||||
auto startTime = std::chrono::high_resolution_clock::now(); | auto startTime = std::chrono::high_resolution_clock::now(); | ||||
module->process(processCtx); | |||||
module->step(); | module->step(); | ||||
auto stopTime = std::chrono::high_resolution_clock::now(); | auto stopTime = std::chrono::high_resolution_clock::now(); | ||||
float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | ||||
// Smooth CPU time | // Smooth CPU time | ||||
const float cpuTau = 2.f /* seconds */; | const float cpuTau = 2.f /* seconds */; | ||||
module->cpuTime += (cpuTime - module->cpuTime) * deltaTime / cpuTau; | |||||
module->cpuTime += (cpuTime - module->cpuTime) * sampleTime / cpuTau; | |||||
} | } | ||||
else { | else { | ||||
module->process(processCtx); | |||||
// Call deprecated method | |||||
module->step(); | module->step(); | ||||
} | } | ||||
} | } | ||||
// Iterate ports to step plug lights | // Iterate ports to step plug lights | ||||
for (Input &input : module->inputs) { | for (Input &input : module->inputs) { | ||||
input.process(deltaTime); | |||||
input.process(sampleTime); | |||||
} | } | ||||
for (Output &output : module->outputs) { | for (Output &output : module->outputs) { | ||||
output.process(deltaTime); | |||||
output.process(sampleTime); | |||||
} | } | ||||
} | } | ||||
} | } | ||||