| @@ -174,6 +174,7 @@ struct EngineWorker { | |||
| struct Engine::Internal { | |||
| std::vector<Module*> modules; | |||
| /** Sorted by (inputModule, inputId) tuple */ | |||
| std::vector<Cable*> cables; | |||
| std::set<ParamHandle*> paramHandles; | |||
| Module* masterModule = NULL; | |||
| @@ -184,10 +185,6 @@ struct Engine::Internal { | |||
| std::map<int64_t, Cable*> cablesCache; | |||
| // (moduleId, paramId) | |||
| 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 sampleTime = 0.f; | |||
| @@ -318,6 +315,80 @@ static void Engine_stepWorker(Engine* that, int threadId) { | |||
| } | |||
| static void Engine_stepFrameCables(Engine* that) { | |||
| auto finitize = [](float x) { | |||
| return std::isfinite(x) ? x : 0.f; | |||
| }; | |||
| // Iterate each cable input group, since `cables` is sorted by input | |||
| auto firstIt = that->internal->cables.begin(); | |||
| while (firstIt != that->internal->cables.end()) { | |||
| Cable* firstCable = *firstIt; | |||
| Input* input = &firstCable->inputModule->inputs[firstCable->inputId]; | |||
| // Find end of input group | |||
| auto endIt = firstIt; | |||
| while (++endIt != that->internal->cables.end()) { | |||
| Cable* endCable = *endIt; | |||
| // Check inputId first since it changes more frequently between cables | |||
| if (!(endCable->inputId == firstCable->inputId && endCable->inputModule == firstCable->inputModule)) | |||
| break; | |||
| } | |||
| // Since stackable inputs are uncommon, only use stackable input logic if there are multiple cables in input group. | |||
| if (endIt - firstIt == 1) { | |||
| Output* output = &firstCable->outputModule->outputs[firstCable->outputId]; | |||
| // Copy all voltages from output to input | |||
| for (uint8_t c = 0; c < output->channels; c++) { | |||
| input->voltages[c] = finitize(output->voltages[c]); | |||
| } | |||
| // Set higher channel voltages to 0 | |||
| for (uint8_t c = output->channels; c < input->channels; c++) { | |||
| input->voltages[c] = 0.f; | |||
| } | |||
| input->channels = output->channels; | |||
| } | |||
| else { | |||
| // Calculate max output channels | |||
| uint8_t channels = 0; | |||
| for (auto it = firstIt; it < endIt; ++it) { | |||
| Cable* cable = *it; | |||
| Output* output = &cable->outputModule->outputs[cable->outputId]; | |||
| channels = std::max(channels, output->channels); | |||
| } | |||
| // Clear input channels, including old channels | |||
| for (uint8_t c = 0; c < std::max(channels, input->channels); c++) { | |||
| input->voltages[c] = 0.f; | |||
| } | |||
| input->channels = channels; | |||
| // Sum outputs of cables | |||
| for (auto it = firstIt; it < endIt; ++it) { | |||
| Cable* cable = *it; | |||
| Output* output = &cable->outputModule->outputs[cable->outputId]; | |||
| // Sum monophonic value to all input channels | |||
| if (output->channels == 1) { | |||
| float value = finitize(output->voltages[0]); | |||
| for (uint8_t c = 0; c < channels; c++) { | |||
| input->voltages[c] += value; | |||
| } | |||
| } | |||
| // Sum polyphonic values to each input channel | |||
| else { | |||
| for (uint8_t c = 0; c < output->channels; c++) { | |||
| input->voltages[c] += finitize(output->voltages[c]); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| firstIt = endIt; | |||
| } | |||
| } | |||
| /** Steps a single frame | |||
| */ | |||
| static void Engine_stepFrame(Engine* that) { | |||
| @@ -350,44 +421,7 @@ static void Engine_stepFrame(Engine* that) { | |||
| Engine_stepWorker(that, 0); | |||
| internal->workerBarrier.wait(); | |||
| // 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]; | |||
| 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]); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| Engine_stepFrameCables(that); | |||
| // Flip messages for each module | |||
| for (Module* module : that->internal->modules) { | |||
| @@ -937,6 +971,10 @@ void Engine::addCable_NoLock(Cable* cable) { | |||
| } | |||
| // Add the cable | |||
| internal->cables.push_back(cable); | |||
| // Sort cable by input so they are grouped in stepFrame() | |||
| std::sort(internal->cables.begin(), internal->cables.end(), [](Cable* a, Cable* b) { | |||
| return std::make_tuple(a->inputModule, a->inputId) < std::make_tuple(b->inputModule, b->inputId); | |||
| }); | |||
| // Set default number of input/output channels | |||
| if (!inputWasConnected) { | |||
| input.channels = 1; | |||
| @@ -946,7 +984,6 @@ void Engine::addCable_NoLock(Cable* cable) { | |||
| } | |||
| // Add caches | |||
| internal->cablesCache[cable->id] = cable; | |||
| internal->inputCablesCache[&input].push_back(cable); | |||
| // Dispatch input port event | |||
| if (!inputWasConnected) { | |||
| Module::PortChangeEvent e; | |||
| @@ -980,16 +1017,6 @@ void Engine::removeCable_NoLock(Cable* cable) { | |||
| auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | |||
| assert(it != internal->cables.end()); | |||
| // 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); | |||