@@ -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); | |||
} | |||
} | |||
} | |||