| @@ -184,6 +184,10 @@ struct Engine::Internal { | |||||
| std::map<int64_t, Cable*> cablesCache; | std::map<int64_t, Cable*> cablesCache; | ||||
| // (moduleId, paramId) | // (moduleId, paramId) | ||||
| std::map<std::tuple<int64_t, int>, ParamHandle*> paramHandlesCache; | std::map<std::tuple<int64_t, int>, ParamHandle*> paramHandlesCache; | ||||
| /** Cache of cables connected to each input | |||||
| Only connected inputs are allowed. | |||||
| */ | |||||
| std::map<Input*, std::vector<Cable*>> inputCablesCache; | |||||
| float sampleRate = 0.f; | float sampleRate = 0.f; | ||||
| float sampleTime = 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 | /** Steps a single frame | ||||
| */ | */ | ||||
| static void Engine_stepFrame(Engine* that) { | static void Engine_stepFrame(Engine* that) { | ||||
| @@ -372,9 +355,44 @@ static void Engine_stepFrame(Engine* that) { | |||||
| Engine_stepWorker(that, 0); | Engine_stepWorker(that, 0); | ||||
| internal->workerBarrier.wait(); | 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<Cable*>& 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 | // Flip messages for each module | ||||
| @@ -898,34 +916,39 @@ void Engine::addCable_NoLock(Cable* cable) { | |||||
| // Check cable properties | // Check cable properties | ||||
| assert(cable->inputModule); | assert(cable->inputModule); | ||||
| assert(cable->outputModule); | assert(cable->outputModule); | ||||
| Input& input = cable->inputModule->inputs[cable->inputId]; | |||||
| Output& output = cable->outputModule->outputs[cable->outputId]; | |||||
| bool inputWasConnected = false; | |||||
| bool outputWasConnected = false; | bool outputWasConnected = false; | ||||
| for (Cable* cable2 : internal->cables) { | for (Cable* cable2 : internal->cables) { | ||||
| // Check that the cable is not already added | // Check that the cable is not already added | ||||
| assert(cable2 != cable); | 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 | // Check if output is already connected to a cable | ||||
| if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) | if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) | ||||
| outputWasConnected = true; | outputWasConnected = true; | ||||
| } | } | ||||
| // Set ID if unset or collides with an existing ID | // Set ID if unset or collides with an existing ID | ||||
| while (cable->id < 0 || internal->cablesCache.find(cable->id) != internal->cablesCache.end()) { | 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); | cable->id = random::u64() % (1ull << 53); | ||||
| } | } | ||||
| // Add the cable | // Add the cable | ||||
| internal->cables.push_back(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; | output.channels = 1; | ||||
| } | } | ||||
| // Add caches | |||||
| internal->cablesCache[cable->id] = cable; | |||||
| internal->inputCablesCache[&input].push_back(cable); | |||||
| // Dispatch input port event | // Dispatch input port event | ||||
| { | |||||
| if (!inputWasConnected) { | |||||
| Module::PortChangeEvent e; | Module::PortChangeEvent e; | ||||
| e.connecting = true; | e.connecting = true; | ||||
| e.type = Port::INPUT; | e.type = Port::INPUT; | ||||
| @@ -951,42 +974,58 @@ void Engine::removeCable(Cable* cable) { | |||||
| void Engine::removeCable_NoLock(Cable* cable) { | void Engine::removeCable_NoLock(Cable* cable) { | ||||
| assert(cable); | assert(cable); | ||||
| Input& input = cable->inputModule->inputs[cable->inputId]; | |||||
| Output& output = cable->outputModule->outputs[cable->outputId]; | |||||
| // Check that the cable is already added | // Check that the cable is already added | ||||
| auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | ||||
| assert(it != internal->cables.end()); | 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); | internal->cablesCache.erase(cable->id); | ||||
| // Remove cable | |||||
| internal->cables.erase(it); | 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; | bool outputIsConnected = false; | ||||
| for (Cable* cable2 : internal->cables) { | 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) { | if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) { | ||||
| outputIsConnected = true; | 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 | // Set output as disconnected if disconnected from all cables | ||||
| if (!outputIsConnected) { | if (!outputIsConnected) { | ||||
| Output& output = cable->outputModule->outputs[cable->outputId]; | |||||
| output.channels = 0; | output.channels = 0; | ||||
| // Don't clear output values | // Don't clear output values | ||||
| } | } | ||||
| // Dispatch input port event | // Dispatch input port event | ||||
| { | |||||
| if (!inputIsConnected) { | |||||
| Module::PortChangeEvent e; | Module::PortChangeEvent e; | ||||
| e.connecting = false; | e.connecting = false; | ||||
| e.type = Port::INPUT; | e.type = Port::INPUT; | ||||
| e.portId = cable->inputId; | e.portId = cable->inputId; | ||||
| cable->inputModule->onPortChange(e); | cable->inputModule->onPortChange(e); | ||||
| } | } | ||||
| // Dispatch output port event if its state went from connected to disconnected. | |||||
| // Dispatch output port event | |||||
| if (!outputIsConnected) { | if (!outputIsConnected) { | ||||
| Module::PortChangeEvent e; | Module::PortChangeEvent e; | ||||
| e.connecting = false; | e.connecting = false; | ||||