diff --git a/include/engine.hpp b/include/engine.hpp index 2df7be79..6df8b664 100644 --- a/include/engine.hpp +++ b/include/engine.hpp @@ -7,14 +7,34 @@ namespace rack { +struct Param { + float value = 0.0; +}; + +struct Input { + /** Voltage of the port, zero if not plugged in. Read-only by Module */ + float value = 0.0; + /** Whether a wire is plugged in */ + bool active = false; + /** Returns the value if a wire is plugged in, otherwise returns the given default value */ + float normalize(float normalValue) { + return active ? value : normalValue; + } +}; + +struct Output { + /** Voltage of the port. Write-only by Module */ + float value = 0.0; + /** Whether a wire is plugged in */ + bool active = false; +}; + + struct Module { - std::vector params; - /** Pointers to voltage values at each port - If value is NULL, the input/output is disconnected - */ - std::vector inputs; - std::vector outputs; - /** For CPU usage */ + std::vector params; + std::vector inputs; + std::vector outputs; + /** For CPU usage meter */ float cpuTime = 0.0; /** Deprecated, use constructor below this one */ @@ -34,6 +54,7 @@ struct Module { virtual json_t *toJson() { return NULL; } virtual void fromJson(json_t *root) {} + /** Override these to implement behavior when user clicks Initialize and Randomize */ virtual void initialize() {} virtual void randomize() {} }; @@ -43,10 +64,7 @@ struct Wire { int outputId; Module *inputModule = NULL; int inputId; - /** The voltage connected to input ports */ - float inputValue = 0.0; - /** The voltage connected to output ports */ - float outputValue = 0.0; + void step(); }; void engineInit(); @@ -60,6 +78,7 @@ void engineRemoveModule(Module *module); /** Does not transfer pointer ownership */ void engineAddWire(Wire *wire); void engineRemoveWire(Wire *wire); +void engineSetParam(Module *module, int paramId, float value); void engineSetParamSmooth(Module *module, int paramId, float value); extern float gSampleRate; diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index c13c1b45..bad76137 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -28,7 +28,7 @@ void ParamWidget::onChange() { if (!module) return; - module->params[paramId] = value; + engineSetParam(module, paramId, value); } diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index b9cbec93..d87c6cd8 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -145,7 +145,7 @@ void AudioInterface::step() { if (!inputBuffer.full()) { Frame<8> f; for (int i = 0; i < 8; i++) { - f.samples[i] = getf(inputs[AUDIO1_INPUT + i]) / 5.0; + f.samples[i] = inputs[AUDIO1_INPUT + i].value / 5.0; } inputBuffer.push(f); } @@ -166,7 +166,7 @@ void AudioInterface::step() { if (!outputBuffer.empty()) { Frame<8> f = outputBuffer.shift(); for (int i = 0; i < 8; i++) { - setf(outputs[AUDIO1_OUTPUT + i], 5.0 * f.samples[i]); + outputs[AUDIO1_OUTPUT + i].value = 5.0 * f.samples[i]; } } } diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 9ffb50d4..904e092b 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -115,23 +115,16 @@ void MidiInterface::step() { } } - if (outputs[PITCH_OUTPUT]) { - *outputs[PITCH_OUTPUT] = ((note - 60)) / 12.0; - } - if (outputs[GATE_OUTPUT]) { - bool gate = pedal || !notes.empty(); - if (retrigger && retriggered) { - gate = false; - retriggered = false; - } - *outputs[GATE_OUTPUT] = gate ? 10.0 : 0.0; - } - if (outputs[MOD_OUTPUT]) { - *outputs[MOD_OUTPUT] = mod / 127.0 * 10.0; - } - if (outputs[PITCHWHEEL_OUTPUT]) { - *outputs[PITCHWHEEL_OUTPUT] = (pitchWheel - 64) / 64.0 * 10.0; + outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; + + bool gate = pedal || !notes.empty(); + if (retrigger && retriggered) { + gate = false; + retriggered = false; } + outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; + outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0; + outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0; } int MidiInterface::getPortCount() { diff --git a/src/engine.cpp b/src/engine.cpp index a70145e6..a8581fbe 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -33,6 +33,11 @@ static int smoothParamId; static float smoothValue; +void Wire::step() { + float value = outputModule->outputs[outputId].value; + inputModule->inputs[inputId].value = value; +} + void engineInit() { gSampleRate = 44100.0; } @@ -46,28 +51,28 @@ void engineDestroy() { static void engineStep() { // Param interpolation if (smoothModule) { - float value = smoothModule->params[smoothParamId]; + float value = smoothModule->params[smoothParamId].value; const float lambda = 60.0; // decay rate is 1 graphics frame const float snap = 0.0001; float delta = smoothValue - value; if (fabsf(delta) < snap) { - smoothModule->params[smoothParamId] = smoothValue; + smoothModule->params[smoothParamId].value = smoothValue; smoothModule = NULL; } else { value += delta * lambda / gSampleRate; - smoothModule->params[smoothParamId] = value; + smoothModule->params[smoothParamId].value = value; } } + // Step modules - for (size_t i = 0; i < modules.size(); i++) { - Module *module = modules[i]; + for (Module *module : modules) { module->step(); } + // Step cables by moving their output values to inputs for (Wire *wire : wires) { - wire->inputValue = wire->outputValue; - wire->outputValue = 0.0; + wire->step(); } } @@ -133,7 +138,7 @@ void engineRemoveModule(Module *module) { assert(module); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); - // If a param is being smoothed on this module, remove it immediately + // If a param is being smoothed on this module, stop smoothing it immediately if (module == smoothModule) { smoothModule = NULL; } @@ -142,45 +147,63 @@ void engineRemoveModule(Module *module) { assert(wire->outputModule != module); assert(wire->inputModule != module); } + // Check that the module actually exists auto it = std::find(modules.begin(), modules.end(), module); assert(it != modules.end()); + // Remove it modules.erase(it); } +static void updateActive() { + // Set everything to inactive + for (Module *module : modules) { + for (Input &input : module->inputs) { + input.active = false; + } + for (Output &output : module->outputs) { + output.active = false; + } + } + // Set inputs/outputs to active + for (Wire *wire : wires) { + wire->outputModule->outputs[wire->outputId].active = true; + wire->inputModule->outputs[wire->inputId].active = true; + } +} + void engineAddWire(Wire *wire) { assert(wire); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); - // Check that the wire is not already added - auto it = std::find(wires.begin(), wires.end(), wire); - assert(it == wires.end()); + // Check wire properties assert(wire->outputModule); assert(wire->inputModule); - // Check that the inputs/outputs are not already used by another cable + // Check that the wire is not already added, and that the input is not already used by another cable for (Wire *wire2 : wires) { assert(wire2 != wire); - assert(!(wire2->outputModule == wire->outputModule && wire2->outputId == wire->outputId)); assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); } // Add the wire wires.push_back(wire); - // Connect the wire to inputModule - wire->inputModule->inputs[wire->inputId] = &wire->inputValue; - wire->outputModule->outputs[wire->outputId] = &wire->outputValue; + updateActive(); } void engineRemoveWire(Wire *wire) { assert(wire); VIPLock vipLock(vipMutex); std::lock_guard lock(mutex); - // Disconnect wire from inputModule - wire->inputModule->inputs[wire->inputId] = NULL; - wire->outputModule->outputs[wire->outputId] = NULL; - - // Remove the wire + // Check that the wire is already added auto it = std::find(wires.begin(), wires.end(), wire); assert(it != wires.end()); + // Set input to 0V + wire->inputModule->inputs[wire->inputId].value = 0.0; + // Remove the wire wires.erase(it); + updateActive(); +} + +void engineSetParam(Module *module, int paramId, float value) { + module->params[paramId].value = value; } void engineSetParamSmooth(Module *module, int paramId, float value) { @@ -188,7 +211,7 @@ void engineSetParamSmooth(Module *module, int paramId, float value) { std::lock_guard lock(mutex); // Since only one param can be smoothed at a time, if another param is currently being smoothed, skip to its final state if (smoothModule && !(smoothModule == module && smoothParamId == paramId)) { - smoothModule->params[smoothParamId] = smoothValue; + smoothModule->params[smoothParamId].value = smoothValue; } smoothModule = module; smoothParamId = paramId;