From c4a5c1016c3b5fe653a96aab52f9760bb658b839 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Tue, 13 Feb 2024 02:18:33 -0500 Subject: [PATCH] Allow multiple Cables per Input in Engine. Cache list of Cables connected to each Input. --- src/engine/Engine.cpp | 135 +++++++++++++++++++++++++++--------------- 1 file changed, 87 insertions(+), 48 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index a1455671..439b7819 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -184,6 +184,10 @@ struct Engine::Internal { std::map cablesCache; // (moduleId, paramId) std::map, ParamHandle*> paramHandlesCache; + /** Cache of cables connected to each input + Only connected inputs are allowed. + */ + std::map> inputCablesCache; float sampleRate = 0.f; float sampleTime = 0.f; @@ -319,27 +323,6 @@ static void Engine_stepWorker(Engine* that, int threadId) { } -static void Cable_step(Cable* that) { - Output* output = &that->outputModule->outputs[that->outputId]; - Input* input = &that->inputModule->inputs[that->inputId]; - // Match number of polyphonic channels to output port - int channels = output->channels; - // Copy all voltages from output to input - for (int c = 0; c < channels; c++) { - float v = output->voltages[c]; - // Set 0V if infinite or NaN - if (!std::isfinite(v)) - v = 0.f; - input->voltages[c] = v; - } - // Set higher channel voltages to 0 - for (int c = channels; c < input->channels; c++) { - input->voltages[c] = 0.f; - } - input->channels = channels; -} - - /** Steps a single frame */ static void Engine_stepFrame(Engine* that) { @@ -372,9 +355,44 @@ static void Engine_stepFrame(Engine* that) { Engine_stepWorker(that, 0); internal->workerBarrier.wait(); - // Step cables - for (Cable* cable : that->internal->cables) { - Cable_step(cable); + // Step cables for each input + for (const auto& pair : internal->inputCablesCache) { + Input* input = pair.first; + const std::vector& cables = pair.second; + // Clear input voltages up to old number of input channels + for (int c = 0; c < input->channels; c++) { + input->voltages[c] = 0.f; + } + // Find max number of channels + uint8_t channels = 1; + for (Cable* cable : cables) { + Output* output = &cable->outputModule->outputs[cable->outputId]; + channels = std::max(channels, output->channels); + } + input->channels = channels; + // Sum all outputs to input value + for (Cable* cable : cables) { + Output* output = &cable->outputModule->outputs[cable->outputId]; + Input* input = &cable->inputModule->inputs[cable->inputId]; + + auto finitize = [](float x) { + return std::isfinite(x) ? x : 0.f; + }; + + // Sum monophonic value to all input channels + if (output->channels == 1) { + float value = finitize(output->voltages[0]); + for (int c = 0; c < channels; c++) { + input->voltages[c] += value; + } + } + // Sum polyphonic values to each input channel + else { + for (int c = 0; c < output->channels; c++) { + input->voltages[c] += finitize(output->voltages[c]); + } + } + } } // Flip messages for each module @@ -898,34 +916,39 @@ void Engine::addCable_NoLock(Cable* cable) { // Check cable properties assert(cable->inputModule); assert(cable->outputModule); + Input& input = cable->inputModule->inputs[cable->inputId]; + Output& output = cable->outputModule->outputs[cable->outputId]; + bool inputWasConnected = false; bool outputWasConnected = false; for (Cable* cable2 : internal->cables) { // Check that the cable is not already added assert(cable2 != cable); - // Check that the input is not already used by another cable - assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); + // Check if input is already connected to a cable + if (cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId) + inputWasConnected = true; // Check if output is already connected to a cable if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) outputWasConnected = true; } // Set ID if unset or collides with an existing ID while (cable->id < 0 || internal->cablesCache.find(cable->id) != internal->cablesCache.end()) { - // Randomly generate ID + // Generate random 52-bit ID cable->id = random::u64() % (1ull << 53); } // Add the cable internal->cables.push_back(cable); - internal->cablesCache[cable->id] = cable; - // Set input as connected - Input& input = cable->inputModule->inputs[cable->inputId]; - input.channels = 1; - // Set output as connected, which might already be connected - Output& output = cable->outputModule->outputs[cable->outputId]; - if (output.channels == 0) { + // Set default number of input/output channels + if (!inputWasConnected) { + input.channels = 1; + } + if (!outputWasConnected) { output.channels = 1; } + // Add caches + internal->cablesCache[cable->id] = cable; + internal->inputCablesCache[&input].push_back(cable); // Dispatch input port event - { + if (!inputWasConnected) { Module::PortChangeEvent e; e.connecting = true; e.type = Port::INPUT; @@ -951,42 +974,58 @@ void Engine::removeCable(Cable* cable) { void Engine::removeCable_NoLock(Cable* cable) { assert(cable); + Input& input = cable->inputModule->inputs[cable->inputId]; + Output& output = cable->outputModule->outputs[cable->outputId]; // Check that the cable is already added auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); assert(it != internal->cables.end()); - // Remove the cable + // Remove cable caches + { + auto v = internal->inputCablesCache[&input]; + auto it = std::find(v.begin(), v.end(), cable); + assert(it != v.end()); + v.erase(it); + // Remove input from cache if no cables are connected + if (v.empty()) { + internal->inputCablesCache.erase(&input); + } + } internal->cablesCache.erase(cable->id); + // Remove cable internal->cables.erase(it); - // Set input as disconnected - Input& input = cable->inputModule->inputs[cable->inputId]; - input.channels = 0; - // Clear input values - for (uint8_t c = 0; c < PORT_MAX_CHANNELS; c++) { - input.setVoltage(0.f, c); - } - // Check if output is still connected to a cable + // Check if input/output is still connected to a cable + bool inputIsConnected = false; bool outputIsConnected = false; for (Cable* cable2 : internal->cables) { + if (cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId) { + inputIsConnected = true; + } if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) { outputIsConnected = true; - break; + } + } + // Set input as disconnected if disconnected from all cables + if (!inputIsConnected) { + input.channels = 0; + // Clear input values + for (uint8_t c = 0; c < PORT_MAX_CHANNELS; c++) { + input.setVoltage(0.f, c); } } // Set output as disconnected if disconnected from all cables if (!outputIsConnected) { - Output& output = cable->outputModule->outputs[cable->outputId]; output.channels = 0; // Don't clear output values } // Dispatch input port event - { + if (!inputIsConnected) { Module::PortChangeEvent e; e.connecting = false; e.type = Port::INPUT; e.portId = cable->inputId; cable->inputModule->onPortChange(e); } - // Dispatch output port event if its state went from connected to disconnected. + // Dispatch output port event if (!outputIsConnected) { Module::PortChangeEvent e; e.connecting = false;