Cardinal/src/override/diffs/Engine.cpp.diff
falkTX 1262f318da
Update and adapt to Rack 2.3
Signed-off-by: falkTX <falktx@falktx.com>
2023-05-20 19:38:29 +02:00

1337 lines
38 KiB
Diff

--- ../Rack/src/engine/Engine.cpp 2023-05-20 17:03:33.006081772 +0200
+++ Engine.cpp 2023-05-20 19:35:00.711346791 +0200
@@ -1,3 +1,30 @@
+/*
+ * DISTRHO Cardinal Plugin
+ * Copyright (C) 2021-2023 Filipe Coelho <falktx@falktx.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 3 of
+ * the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * For a full copy of the GNU General Public License see the LICENSE file.
+ */
+
+/**
+ * This file is an edited version of VCVRack's engine/Engine.cpp
+ * Copyright (C) 2016-2023 VCV.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 3 of
+ * the License, or (at your option) any later version.
+ */
+
#include <algorithm>
#include <set>
#include <thread>
@@ -5,192 +32,47 @@
#include <mutex>
#include <atomic>
#include <tuple>
-#if defined ARCH_X64
- #include <pmmintrin.h>
-#endif
+#include <pmmintrin.h>
+#include <unordered_map>
#include <engine/Engine.hpp>
+#include <engine/TerminalModule.hpp>
#include <settings.hpp>
#include <system.hpp>
#include <random.hpp>
-#include <context.hpp>
#include <patch.hpp>
#include <plugin.hpp>
#include <mutex.hpp>
+#include <helpers.hpp>
-
-namespace rack {
-namespace engine {
-
-
-#if defined ARCH_X64
-static void initMXCSR() {
- // Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode
- // https://software.intel.com/en-us/node/682949
- _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
- _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
- // Reset other flags
- _MM_SET_ROUNDING_MODE(_MM_ROUND_NEAREST);
-}
+#ifdef NDEBUG
+# undef DEBUG
#endif
+#include "../CardinalRemote.hpp"
+#include "DistrhoUtils.hpp"
-/** Barrier based on mutexes.
-Not finished or tested, do not use.
-*/
-struct Barrier {
- int count = 0;
- uint8_t step = 0;
- int threads = 0;
-
- std::mutex mutex;
- std::condition_variable cv;
-
- void setThreads(int threads) {
- this->threads = threads;
- }
-
- void wait() {
- std::unique_lock<std::mutex> lock(mutex);
- uint8_t s = step;
- if (++count >= threads) {
- // We're the last thread. Reset next phase.
- count = 0;
- // Allow other threads to exit wait()
- step++;
- cv.notify_all();
- return;
- }
-
- cv.wait(lock, [&] {
- return step != s;
- });
- }
-};
-
-
-/** 2-phase barrier based on spin-locking.
-*/
-struct SpinBarrier {
- std::atomic<int> count{0};
- std::atomic<uint8_t> step{0};
- int threads = 0;
-
- /** Must be called when no threads are calling wait().
- */
- void setThreads(int threads) {
- this->threads = threads;
- }
-
- void wait() {
- uint8_t s = step;
- if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) {
- // We're the last thread. Reset next phase.
- count = 0;
- // Allow other threads to exit wait()
- step++;
- return;
- }
-
- // Spin until the last thread begins waiting
- while (true) {
- if (step.load(std::memory_order_relaxed) != s)
- return;
-#if defined ARCH_X64
- __builtin_ia32_pause();
-#endif
- }
- }
-};
+// known terminal modules
+extern std::vector<rack::plugin::Model*> hostTerminalModels;
-/** Barrier that spin-locks until yield() is called, and then all threads switch to a mutex.
-yield() should be called if it is likely that all threads will block for a while and continuing to spin-lock is unnecessary.
-Saves CPU power after yield is called.
-*/
-struct HybridBarrier {
- std::atomic<int> count{0};
- std::atomic<uint8_t> step{0};
- int threads = 0;
-
- std::atomic<bool> yielded{false};
- std::mutex mutex;
- std::condition_variable cv;
-
- void setThreads(int threads) {
- this->threads = threads;
- }
-
- void yield() {
- yielded = true;
- }
-
- void wait() {
- uint8_t s = step;
- if (count.fetch_add(1, std::memory_order_acquire) + 1 >= threads) {
- // We're the last thread. Reset next phase.
- count = 0;
- bool wasYielded = yielded;
- yielded = false;
- // Allow other threads to exit wait()
- step++;
- if (wasYielded) {
- std::unique_lock<std::mutex> lock(mutex);
- cv.notify_all();
- }
- return;
- }
- // Spin until the last thread begins waiting
- while (!yielded.load(std::memory_order_relaxed)) {
- if (step.load(std::memory_order_relaxed) != s)
- return;
-#if defined ARCH_X64
- __builtin_ia32_pause();
-#endif
- }
-
- // Wait on mutex CV
- std::unique_lock<std::mutex> lock(mutex);
- cv.wait(lock, [&] {
- return step != s;
- });
- }
-};
-
-
-struct EngineWorker {
- Engine* engine;
- int id;
- std::thread thread;
- bool running = false;
+namespace rack {
+namespace engine {
- void start() {
- assert(!running);
- running = true;
- thread = std::thread([&] {
- run();
- });
- }
- void requestStop() {
- running = false;
- }
-
- void join() {
- assert(thread.joinable());
- thread.join();
- }
-
- void run();
-};
+static constexpr const int PORT_DIVIDER = 7;
+// Arbitrary prime number so it doesn't over- or under-estimate time of buffered processors.
+static constexpr const int METER_DIVIDER = 37;
+static constexpr const int METER_BUFFER_LEN = 32;
+static constexpr const float METER_TIME = 1.f;
struct Engine::Internal {
std::vector<Module*> modules;
+ std::vector<TerminalModule*> terminalModules;
std::vector<Cable*> cables;
std::set<ParamHandle*> paramHandles;
- Module* masterModule = NULL;
// moduleId
std::map<int64_t, Module*> modulesCache;
@@ -206,7 +88,9 @@
int64_t blockFrame = 0;
double blockTime = 0.0;
int blockFrames = 0;
+ bool aboutToClose = false;
+#ifndef HEADLESS
// Meter
int meterCount = 0;
double meterTotal = 0.0;
@@ -214,33 +98,32 @@
double meterLastTime = -INFINITY;
double meterLastAverage = 0.0;
double meterLastMax = 0.0;
+#endif
// Parameter smoothing
Module* smoothModule = NULL;
int smoothParamId = 0;
float smoothValue = 0.f;
+ // Remote control
+ remoteUtils::RemoteDetails* remoteDetails = nullptr;
+
/** Mutex that guards the Engine state, such as settings, Modules, and Cables.
Writers lock when mutating the engine's state or stepping the block.
Readers lock when using the engine's state.
*/
SharedMutex mutex;
- /** Mutex that guards stepBlock() so it's not called simultaneously.
- */
- std::mutex blockMutex;
+};
+
- int threadCount = 0;
- std::vector<EngineWorker> workers;
- HybridBarrier engineBarrier;
- HybridBarrier workerBarrier;
- std::atomic<int> workerModuleIndex;
- // For worker threads
- Context* context;
-
- bool fallbackRunning = false;
- std::thread fallbackThread;
- std::mutex fallbackMutex;
- std::condition_variable fallbackCv;
+struct Module::Internal {
+ bool bypassed = false;
+
+ int meterSamples = 0;
+ float meterDurationTotal = 0.f;
+
+ float meterBuffer[METER_BUFFER_LEN] = {};
+ int meterIndex = 0;
};
@@ -268,89 +151,134 @@
}
-static void Engine_relaunchWorkers(Engine* that, int threadCount) {
- Engine::Internal* internal = that->internal;
- if (threadCount == internal->threadCount)
- return;
+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
+ const int channels = output->channels;
+ // Copy all voltages from output to input
+ for (int c = 0; c < channels; c++) {
+ if (!std::isfinite(output->voltages[c]))
+ __builtin_unreachable();
+ input->voltages[c] = output->voltages[c];
+ }
+ // Set higher channel voltages to 0
+ for (int c = channels; c < input->channels; c++) {
+ input->voltages[c] = 0.f;
+ }
+ input->channels = channels;
+}
- if (internal->threadCount > 0) {
- // Stop engine workers
- for (EngineWorker& worker : internal->workers) {
- worker.requestStop();
- }
- internal->engineBarrier.wait();
- // Join and destroy engine workers
- for (EngineWorker& worker : internal->workers) {
- worker.join();
- }
- internal->workers.resize(0);
+#ifndef HEADLESS
+static void Port_step(Port* that, float deltaTime) {
+ // Set plug lights
+ if (that->channels == 0) {
+ that->plugLights[0].setBrightness(0.f);
+ that->plugLights[1].setBrightness(0.f);
+ that->plugLights[2].setBrightness(0.f);
+ }
+ else if (that->channels == 1) {
+ float v = that->getVoltage() / 10.f;
+ that->plugLights[0].setSmoothBrightness(-v, deltaTime);
+ that->plugLights[1].setSmoothBrightness(v, deltaTime);
+ that->plugLights[2].setBrightness(0.f);
}
+ else {
+ float v = that->getVoltageRMS() / 10.f;
+ that->plugLights[0].setBrightness(0.f);
+ that->plugLights[1].setBrightness(0.f);
+ that->plugLights[2].setSmoothBrightness(v, deltaTime);
+ }
+}
+#endif
- // Configure engine
- internal->threadCount = threadCount;
-
- // Set barrier counts
- internal->engineBarrier.setThreads(threadCount);
- internal->workerBarrier.setThreads(threadCount);
- if (threadCount > 0) {
- // Create and start engine workers
- internal->workers.resize(threadCount - 1);
- for (int id = 1; id < threadCount; id++) {
- EngineWorker& worker = internal->workers[id - 1];
- worker.id = id;
- worker.engine = that;
- worker.start();
+static void TerminalModule__doProcess(TerminalModule* const terminalModule, const Module::ProcessArgs& args, bool input) {
+ // Step module
+ if (input) {
+ terminalModule->processTerminalInput(args);
+ for (Output& output : terminalModule->outputs) {
+ for (Cable* cable : output.cables)
+ Cable_step(cable);
+ }
+ } else {
+ terminalModule->processTerminalOutput(args);
+ }
+
+#ifndef HEADLESS
+ // Iterate ports to step plug lights
+ if (args.frame % PORT_DIVIDER == 0) {
+ float portTime = args.sampleTime * PORT_DIVIDER;
+ for (Input& input : terminalModule->inputs) {
+ Port_step(&input, portTime);
+ }
+ for (Output& output : terminalModule->outputs) {
+ Port_step(&output, portTime);
}
}
+#endif
}
-static void Engine_stepWorker(Engine* that, int threadId) {
- Engine::Internal* internal = that->internal;
-
- // int threadCount = internal->threadCount;
- int modulesLen = internal->modules.size();
+static void Module__doProcess(Module* const module, const Module::ProcessArgs& args) {
+ Module::Internal* const internal = module->internal;
- // Build ProcessArgs
- Module::ProcessArgs processArgs;
- processArgs.sampleRate = internal->sampleRate;
- processArgs.sampleTime = internal->sampleTime;
- processArgs.frame = internal->frame;
-
- // Step each module
- while (true) {
- // Choose next module
- // First-come-first serve module-to-thread allocation algorithm
- int i = internal->workerModuleIndex++;
- if (i >= modulesLen)
- break;
-
- Module* module = internal->modules[i];
- module->doProcess(processArgs);
+#ifndef HEADLESS
+ // This global setting can change while the function is running, so use a local variable.
+ bool meterEnabled = settings::cpuMeter && (args.frame % METER_DIVIDER == 0);
+
+ // Start CPU timer
+ double startTime;
+ if (meterEnabled) {
+ startTime = system::getTime();
}
-}
-
+#endif
-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;
+ // Step module
+ if (!internal->bypassed)
+ module->process(args);
+ else
+ module->processBypass(args);
+
+#ifndef HEADLESS
+ // Stop CPU timer
+ if (meterEnabled) {
+ double endTime = system::getTime();
+ // Subtract call time of getTime() itself, since we only want to measure process() time.
+ double endTime2 = system::getTime();
+ float duration = (endTime - startTime) - (endTime2 - endTime);
+
+ internal->meterSamples++;
+ internal->meterDurationTotal += duration;
+
+ // Seconds we've been measuring
+ float meterTime = internal->meterSamples * METER_DIVIDER * args.sampleTime;
+
+ if (meterTime >= METER_TIME) {
+ // Push time to buffer
+ if (internal->meterSamples > 0) {
+ internal->meterIndex++;
+ internal->meterIndex %= METER_BUFFER_LEN;
+ internal->meterBuffer[internal->meterIndex] = internal->meterDurationTotal / internal->meterSamples;
+ }
+ // Reset total
+ internal->meterSamples = 0;
+ internal->meterDurationTotal = 0.f;
+ }
}
- // Set higher channel voltages to 0
- for (int c = channels; c < input->channels; c++) {
- input->voltages[c] = 0.f;
+
+ // Iterate ports to step plug lights
+ if (args.frame % PORT_DIVIDER == 0) {
+ float portTime = args.sampleTime * PORT_DIVIDER;
+ for (Input& input : module->inputs) {
+ Port_step(&input, portTime);
+ }
+ for (Output& output : module->outputs) {
+ Port_step(&output, portTime);
+ }
}
- input->channels = channels;
+#endif
}
@@ -366,10 +294,16 @@
float smoothValue = internal->smoothValue;
Param* smoothParam = &smoothModule->params[smoothParamId];
float value = smoothParam->value;
- // Use decay rate of roughly 1 graphics frame
- const float smoothLambda = 60.f;
- float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime;
- if (value == newValue) {
+ float newValue;
+ if (internal->remoteDetails != nullptr) {
+ newValue = value;
+ sendParamChangeToRemote(internal->remoteDetails, smoothModule->id, smoothParamId, value);
+ } else {
+ // Use decay rate of roughly 1 graphics frame
+ const float smoothLambda = 60.f;
+ newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime;
+ }
+ if (d_isEqual(value, newValue)) {
// Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats)
smoothParam->setValue(smoothValue);
internal->smoothModule = NULL;
@@ -380,13 +314,8 @@
}
}
- // Step cables
- for (Cable* cable : that->internal->cables) {
- Cable_step(cable);
- }
-
// Flip messages for each module
- for (Module* module : that->internal->modules) {
+ for (Module* module : internal->modules) {
if (module->leftExpander.messageFlipRequested) {
std::swap(module->leftExpander.producerMessage, module->leftExpander.consumerMessage);
module->leftExpander.messageFlipRequested = false;
@@ -397,13 +326,32 @@
}
}
- // Step modules along with workers
- internal->workerModuleIndex = 0;
- internal->engineBarrier.wait();
- Engine_stepWorker(that, 0);
- internal->workerBarrier.wait();
+ // Build ProcessArgs
+ Module::ProcessArgs processArgs;
+ processArgs.sampleRate = internal->sampleRate;
+ processArgs.sampleTime = internal->sampleTime;
+ processArgs.frame = internal->frame;
+
+ // Process terminal inputs first
+ for (TerminalModule* terminalModule : internal->terminalModules) {
+ TerminalModule__doProcess(terminalModule, processArgs, true);
+ }
- internal->frame++;
+ // Step each module and cables
+ for (Module* module : internal->modules) {
+ Module__doProcess(module, processArgs);
+ for (Output& output : module->outputs) {
+ for (Cable* cable : output.cables)
+ Cable_step(cable);
+ }
+ }
+
+ // Process terminal outputs last
+ for (TerminalModule* terminalModule : internal->terminalModules) {
+ TerminalModule__doProcess(terminalModule, processArgs, false);
+ }
+
+ ++internal->frame;
}
@@ -422,35 +370,119 @@
}
+template<typename T>
+using IdentityDictionary = std::unordered_map<T, T>;
+
+template<typename T>
+inline bool dictContains(IdentityDictionary<T>& dict, T key) {
+ return dict.find(key) != dict.end();
+}
+
+template<typename T>
+inline void dictAdd(IdentityDictionary<T>& dict, T key) {
+ dict[key] = key;
+}
+
+static void Engine_storeTerminalModulesIDs(std::vector<TerminalModule*> terminalModules, IdentityDictionary<int64_t>& terminalModulesIDs) {
+ for (TerminalModule* terminalModule : terminalModules)
+ dictAdd(terminalModulesIDs, terminalModule->id);
+}
+
+static void Engine_orderModule(Module* module, IdentityDictionary<Module*>& touchedModules, std::vector<Module*>& orderedModules, IdentityDictionary<int64_t>& terminalModulesIDs) {
+ if (!dictContains(touchedModules, module) && !dictContains(terminalModulesIDs, module->id)) { // Ignore feedback loops and terminal modules
+ dictAdd(touchedModules, module);
+ for (Output& output : module->outputs) {
+ for (Cable* cable : output.cables) {
+ Module* receiver = cable->inputModule; // The input to the cable is the receiving module
+ Engine_orderModule(receiver, touchedModules, orderedModules, terminalModulesIDs);
+ }
+ }
+ orderedModules.push_back(module);
+ }
+}
+
+static void Engine_assignOrderedModules(std::vector<Module*>& modules, std::vector<Module*>& orderedModules) {
+ std::reverse(orderedModules.begin(), orderedModules.end()); // These are stored bottom up
+ if (orderedModules.size() == modules.size()) {
+ for (unsigned int i = 0; i < orderedModules.size(); i++)
+ modules[i] = orderedModules[i];
+ }
+}
+
+#if DEBUG_ORDERED_MODULES
+static void Engine_debugOrderedModules(std::vector<Module*>& modules) {
+ printf("\n--- Ordered modules ---\n");
+ for (unsigned int i = 0; i < modules.size(); i++)
+ printf("%d) %s - %ld\n", i, modules[i]->model->getFullName().c_str(), modules[i]->id);
+}
+#endif
+
+/** Order the modules so that they always read the most recent sample from their inputs
+*/
+static void Engine_orderModules(Engine* that) {
+ Engine::Internal* internal = that->internal;
+
+ IdentityDictionary<int64_t> terminalModulesIDs;
+ Engine_storeTerminalModulesIDs(internal->terminalModules, terminalModulesIDs);
+
+ IdentityDictionary<Module*> touchedModules;
+ std::vector<Module*> orderedModules;
+ orderedModules.reserve(internal->modules.size());
+ for (Module* module : internal->modules)
+ Engine_orderModule(module, touchedModules, orderedModules, terminalModulesIDs);
+
+ Engine_assignOrderedModules(internal->modules, orderedModules);
+
+#if DEBUG_ORDERED_MODULES
+ Engine_debugOrderedModules(internal->modules);
+#endif
+}
+
+
static void Engine_updateConnected(Engine* that) {
// Find disconnected ports
- std::set<Port*> disconnectedPorts;
+ std::set<Input*> disconnectedInputs;
+ std::set<Output*> disconnectedOutputs;
for (Module* module : that->internal->modules) {
for (Input& input : module->inputs) {
- disconnectedPorts.insert(&input);
+ disconnectedInputs.insert(&input);
}
for (Output& output : module->outputs) {
- disconnectedPorts.insert(&output);
+ disconnectedOutputs.insert(&output);
+ }
+ }
+ for (TerminalModule* terminalModule : that->internal->terminalModules) {
+ for (Input& input : terminalModule->inputs) {
+ disconnectedInputs.insert(&input);
+ }
+ for (Output& output : terminalModule->outputs) {
+ disconnectedOutputs.insert(&output);
}
}
for (Cable* cable : that->internal->cables) {
// Connect input
Input& input = cable->inputModule->inputs[cable->inputId];
- auto inputIt = disconnectedPorts.find(&input);
- if (inputIt != disconnectedPorts.end())
- disconnectedPorts.erase(inputIt);
+ auto inputIt = disconnectedInputs.find(&input);
+ if (inputIt != disconnectedInputs.end())
+ disconnectedInputs.erase(inputIt);
Port_setConnected(&input);
// Connect output
Output& output = cable->outputModule->outputs[cable->outputId];
- auto outputIt = disconnectedPorts.find(&output);
- if (outputIt != disconnectedPorts.end())
- disconnectedPorts.erase(outputIt);
+ auto outputIt = disconnectedOutputs.find(&output);
+ if (outputIt != disconnectedOutputs.end())
+ disconnectedOutputs.erase(outputIt);
Port_setConnected(&output);
}
// Disconnect ports that have no cable
- for (Port* port : disconnectedPorts) {
- Port_setDisconnected(port);
+ for (Input* input : disconnectedInputs) {
+ Port_setDisconnected(input);
}
+ for (Output* output : disconnectedOutputs) {
+ Port_setDisconnected(output);
+ DISTRHO_SAFE_ASSERT(output->cables.empty());
+ }
+ // Order the modules according to their connections
+ Engine_orderModules(that);
}
@@ -468,37 +500,23 @@
Engine::Engine() {
internal = new Internal;
-
- internal->context = contextGet();
- setSuggestedSampleRate(0.f);
}
Engine::~Engine() {
- // Stop fallback thread if running
- {
- std::lock_guard<std::mutex> lock(internal->fallbackMutex);
- internal->fallbackRunning = false;
- internal->fallbackCv.notify_all();
- }
- if (internal->fallbackThread.joinable())
- internal->fallbackThread.join();
-
- // Shut down workers
- Engine_relaunchWorkers(this, 0);
-
// Clear modules, cables, etc
clear();
// Make sure there are no cables or modules in the rack on destruction.
// If this happens, a module must have failed to remove itself before the RackWidget was destroyed.
- assert(internal->cables.empty());
- assert(internal->modules.empty());
- assert(internal->paramHandles.empty());
-
- assert(internal->modulesCache.empty());
- assert(internal->cablesCache.empty());
- assert(internal->paramHandlesCache.empty());
+ DISTRHO_SAFE_ASSERT(internal->cables.empty());
+ DISTRHO_SAFE_ASSERT(internal->modules.empty());
+ DISTRHO_SAFE_ASSERT(internal->terminalModules.empty());
+ DISTRHO_SAFE_ASSERT(internal->paramHandles.empty());
+
+ DISTRHO_SAFE_ASSERT(internal->modulesCache.empty());
+ DISTRHO_SAFE_ASSERT(internal->cablesCache.empty());
+ DISTRHO_SAFE_ASSERT(internal->paramHandlesCache.empty());
delete internal;
}
@@ -527,20 +545,22 @@
removeModule_NoLock(module);
delete module;
}
+ std::vector<TerminalModule*> terminalModules = internal->terminalModules;
+ for (TerminalModule* terminalModule : terminalModules) {
+ removeModule_NoLock(terminalModule);
+ delete terminalModule;
+ }
}
void Engine::stepBlock(int frames) {
+#ifndef HEADLESS
// Start timer before locking
double startTime = system::getTime();
+#endif
- std::lock_guard<std::mutex> stepLock(internal->blockMutex);
SharedLock<SharedMutex> lock(internal->mutex);
// Configure thread
-#if defined ARCH_X64
- uint32_t csr = _mm_getcsr();
- initMXCSR();
-#endif
random::init();
internal->blockFrame = internal->frame;
@@ -553,18 +573,14 @@
Engine_updateExpander_NoLock(this, module, true);
}
- // Launch workers
- Engine_relaunchWorkers(this, settings::threadCount);
-
// Step individual frames
for (int i = 0; i < frames; i++) {
Engine_stepFrame(this);
}
- yieldWorkers();
-
internal->block++;
+#ifndef HEADLESS
// Stop timer
double endTime = system::getTime();
double meter = (endTime - startTime) / (frames * internal->sampleTime);
@@ -582,49 +598,20 @@
internal->meterTotal = 0.0;
internal->meterMax = 0.0;
}
-
-#if defined ARCH_X64
- // Reset MXCSR back to original value
- _mm_setcsr(csr);
#endif
}
void Engine::setMasterModule(Module* module) {
- if (module == internal->masterModule)
- return;
- std::lock_guard<SharedMutex> lock(internal->mutex);
- setMasterModule_NoLock(module);
}
void Engine::setMasterModule_NoLock(Module* module) {
- if (module == internal->masterModule)
- return;
-
- if (internal->masterModule) {
- // Dispatch UnsetMasterEvent
- Module::UnsetMasterEvent e;
- internal->masterModule->onUnsetMaster(e);
- }
-
- internal->masterModule = module;
-
- if (internal->masterModule) {
- // Dispatch SetMasterEvent
- Module::SetMasterEvent e;
- internal->masterModule->onSetMaster(e);
- }
-
- // Wake up fallback thread if master module was unset
- if (!internal->masterModule) {
- internal->fallbackCv.notify_all();
- }
}
Module* Engine::getMasterModule() {
- return internal->masterModule;
+ return nullptr;
}
@@ -647,20 +634,13 @@
for (Module* module : internal->modules) {
module->onSampleRateChange(e);
}
+ for (TerminalModule* terminalModule : internal->terminalModules) {
+ terminalModule->onSampleRateChange(e);
+ }
}
void Engine::setSuggestedSampleRate(float suggestedSampleRate) {
- if (settings::sampleRate > 0) {
- setSampleRate(settings::sampleRate);
- }
- else if (suggestedSampleRate > 0) {
- setSampleRate(suggestedSampleRate);
- }
- else {
- // Fallback sample rate
- setSampleRate(44100.f);
- }
}
@@ -670,7 +650,6 @@
void Engine::yieldWorkers() {
- internal->workerBarrier.yield();
}
@@ -705,17 +684,25 @@
double Engine::getMeterAverage() {
+#ifndef HEADLESS
return internal->meterLastAverage;
+#else
+ return 0.0;
+#endif
}
double Engine::getMeterMax() {
+#ifndef HEADLESS
return internal->meterLastMax;
+#else
+ return 0.0;
+#endif
}
size_t Engine::getNumModules() {
- return internal->modules.size();
+ return internal->modules.size() + internal->terminalModules.size();
}
@@ -725,8 +712,12 @@
for (Module* m : internal->modules) {
if (i >= len)
break;
- moduleIds[i] = m->id;
- i++;
+ moduleIds[i++] = m->id;
+ }
+ for (TerminalModule* m : internal->terminalModules) {
+ if (i >= len)
+ break;
+ moduleIds[i++] = m->id;
}
return i;
}
@@ -735,27 +726,43 @@
std::vector<int64_t> Engine::getModuleIds() {
SharedLock<SharedMutex> lock(internal->mutex);
std::vector<int64_t> moduleIds;
- moduleIds.reserve(internal->modules.size());
+ moduleIds.reserve(getNumModules());
for (Module* m : internal->modules) {
moduleIds.push_back(m->id);
}
+ for (TerminalModule* tm : internal->terminalModules) {
+ moduleIds.push_back(tm->id);
+ }
return moduleIds;
}
+static TerminalModule* asTerminalModule(Module* const module) {
+ const plugin::Model* const model = module->model;
+ if (std::find(hostTerminalModels.begin(), hostTerminalModels.end(), model) != hostTerminalModels.end())
+ return static_cast<TerminalModule*>(module);
+ return nullptr;
+}
+
+
void Engine::addModule(Module* module) {
std::lock_guard<SharedMutex> lock(internal->mutex);
- assert(module);
+ DISTRHO_SAFE_ASSERT_RETURN(module != nullptr,);
// Check that the module is not already added
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
- assert(it == internal->modules.end());
+ DISTRHO_SAFE_ASSERT_RETURN(it == internal->modules.end(),);
+ auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module);
+ DISTRHO_SAFE_ASSERT_RETURN(tit == internal->terminalModules.end(),);
// Set ID if unset or collides with an existing ID
while (module->id < 0 || internal->modulesCache.find(module->id) != internal->modulesCache.end()) {
// Randomly generate ID
module->id = random::u64() % (1ull << 53);
}
// Add module
- internal->modules.push_back(module);
+ if (TerminalModule* const terminalModule = asTerminalModule(module))
+ internal->terminalModules.push_back(terminalModule);
+ else
+ internal->modules.push_back(module);
internal->modulesCache[module->id] = module;
// Dispatch AddEvent
Module::AddEvent eAdd;
@@ -770,6 +777,9 @@
if (paramHandle->moduleId == module->id)
paramHandle->module = module;
}
+#if DEBUG_ORDERED_MODULES
+ printf("New module: %s - %ld\n", module->model->getFullName().c_str(), module->id);
+#endif
}
@@ -779,11 +789,11 @@
}
-void Engine::removeModule_NoLock(Module* module) {
- assert(module);
- // Check that the module actually exists
- auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
- assert(it != internal->modules.end());
+static void removeModule_NoLock_common(Engine::Internal* internal, Module* module) {
+ // Remove from widgets cache
+ CardinalPluginModelHelper* const helper = dynamic_cast<CardinalPluginModelHelper*>(module->model);
+ DISTRHO_SAFE_ASSERT_RETURN(helper != nullptr,);
+ helper->removeCachedModuleWidget(module);
// Dispatch RemoveEvent
Module::RemoveEvent eRemove;
module->onRemove(eRemove);
@@ -792,18 +802,14 @@
if (paramHandle->moduleId == module->id)
paramHandle->module = NULL;
}
- // Unset master module
- if (getMasterModule() == module) {
- setMasterModule_NoLock(NULL);
- }
// If a param is being smoothed on this module, stop smoothing it immediately
if (module == internal->smoothModule) {
internal->smoothModule = NULL;
}
// Check that all cables are disconnected
for (Cable* cable : internal->cables) {
- assert(cable->inputModule != module);
- assert(cable->outputModule != module);
+ DISTRHO_SAFE_ASSERT(cable->inputModule != module);
+ DISTRHO_SAFE_ASSERT(cable->outputModule != module);
}
// Update expanders of other modules
for (Module* m : internal->modules) {
@@ -816,14 +822,31 @@
m->rightExpander.module = NULL;
}
}
- // Remove module
- internal->modulesCache.erase(module->id);
- internal->modules.erase(it);
// Reset expanders
module->leftExpander.moduleId = -1;
module->leftExpander.module = NULL;
module->rightExpander.moduleId = -1;
module->rightExpander.module = NULL;
+ // Remove module
+ internal->modulesCache.erase(module->id);
+}
+
+
+void Engine::removeModule_NoLock(Module* module) {
+ DISTRHO_SAFE_ASSERT_RETURN(module,);
+ // Check that the module actually exists
+ if (TerminalModule* const terminalModule = asTerminalModule(module)) {
+ auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), terminalModule);
+ DISTRHO_SAFE_ASSERT_RETURN(tit != internal->terminalModules.end(),);
+ removeModule_NoLock_common(internal, module);
+ internal->terminalModules.erase(tit);
+ }
+ else {
+ auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
+ DISTRHO_SAFE_ASSERT_RETURN(it != internal->modules.end(),);
+ removeModule_NoLock_common(internal, module);
+ internal->modules.erase(it);
+ }
}
@@ -831,7 +854,8 @@
SharedLock<SharedMutex> lock(internal->mutex);
// TODO Performance could be improved by searching modulesCache, but more testing would be needed to make sure it's always valid.
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
- return it != internal->modules.end();
+ auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module);
+ return it != internal->modules.end() && tit != internal->terminalModules.end();
}
@@ -851,7 +875,7 @@
void Engine::resetModule(Module* module) {
std::lock_guard<SharedMutex> lock(internal->mutex);
- assert(module);
+ DISTRHO_SAFE_ASSERT_RETURN(module,);
Module::ResetEvent eReset;
module->onReset(eReset);
@@ -860,7 +884,7 @@
void Engine::randomizeModule(Module* module) {
std::lock_guard<SharedMutex> lock(internal->mutex);
- assert(module);
+ DISTRHO_SAFE_ASSERT_RETURN(module,);
Module::RandomizeEvent eRandomize;
module->onRandomize(eRandomize);
@@ -868,7 +892,7 @@
void Engine::bypassModule(Module* module, bool bypassed) {
- assert(module);
+ DISTRHO_SAFE_ASSERT_RETURN(module,);
if (module->isBypassed() == bypassed)
return;
@@ -914,11 +938,17 @@
void Engine::prepareSave() {
+ if (internal->aboutToClose)
+ return;
SharedLock<SharedMutex> lock(internal->mutex);
for (Module* module : internal->modules) {
Module::SaveEvent e;
module->onSave(e);
}
+ for (TerminalModule* terminalModule : internal->terminalModules) {
+ Module::SaveEvent e;
+ terminalModule->onSave(e);
+ }
}
@@ -953,16 +983,16 @@
void Engine::addCable(Cable* cable) {
std::lock_guard<SharedMutex> lock(internal->mutex);
- assert(cable);
+ DISTRHO_SAFE_ASSERT_RETURN(cable,);
// Check cable properties
- assert(cable->inputModule);
- assert(cable->outputModule);
+ DISTRHO_SAFE_ASSERT_RETURN(cable->inputModule,);
+ DISTRHO_SAFE_ASSERT_RETURN(cable->outputModule,);
bool outputWasConnected = false;
for (Cable* cable2 : internal->cables) {
// Check that the cable is not already added
- assert(cable2 != cable);
+ DISTRHO_SAFE_ASSERT_RETURN(cable2 != cable,);
// Check that the input is not already used by another cable
- assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId));
+ DISTRHO_SAFE_ASSERT_RETURN(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId),);
// Get connected status of output, to decide whether we need to call a PortChangeEvent.
// It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()`
if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId)
@@ -976,6 +1006,8 @@
// Add the cable
internal->cables.push_back(cable);
internal->cablesCache[cable->id] = cable;
+ // Add the cable's zero-latency shortcut
+ cable->outputModule->outputs[cable->outputId].cables.push_back(cable);
Engine_updateConnected(this);
// Dispatch input port event
{
@@ -1003,10 +1035,12 @@
void Engine::removeCable_NoLock(Cable* cable) {
- assert(cable);
+ DISTRHO_SAFE_ASSERT_RETURN(cable,);
// Check that the cable is already added
auto it = std::find(internal->cables.begin(), internal->cables.end(), cable);
- assert(it != internal->cables.end());
+ DISTRHO_SAFE_ASSERT_RETURN(it != internal->cables.end(),);
+ // Remove the cable's zero-latency shortcut
+ cable->outputModule->outputs[cable->outputId].cables.remove(cable);
// Remove the cable
internal->cablesCache.erase(cable->id);
internal->cables.erase(it);
@@ -1060,6 +1094,9 @@
internal->smoothModule = NULL;
internal->smoothParamId = 0;
}
+ if (internal->remoteDetails != nullptr) {
+ sendParamChangeToRemote(internal->remoteDetails, module->id, paramId, value);
+ }
module->params[paramId].setValue(value);
}
@@ -1092,11 +1129,11 @@
std::lock_guard<SharedMutex> lock(internal->mutex);
// New ParamHandles must be blank.
// This means we don't have to refresh the cache.
- assert(paramHandle->moduleId < 0);
+ DISTRHO_SAFE_ASSERT_RETURN(paramHandle->moduleId < 0,);
// Check that the ParamHandle is not already added
auto it = internal->paramHandles.find(paramHandle);
- assert(it == internal->paramHandles.end());
+ DISTRHO_SAFE_ASSERT_RETURN(it == internal->paramHandles.end(),);
// Add it
internal->paramHandles.insert(paramHandle);
@@ -1113,7 +1150,7 @@
void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) {
// Check that the ParamHandle is already added
auto it = internal->paramHandles.find(paramHandle);
- assert(it != internal->paramHandles.end());
+ DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),);
// Remove it
paramHandle->module = NULL;
@@ -1150,7 +1187,7 @@
void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) {
// Check that it exists
auto it = internal->paramHandles.find(paramHandle);
- assert(it != internal->paramHandles.end());
+ DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),);
// Set IDs
paramHandle->moduleId = moduleId;
@@ -1194,6 +1231,10 @@
json_t* moduleJ = module->toJson();
json_array_append_new(modulesJ, moduleJ);
}
+ for (TerminalModule* terminalModule : internal->terminalModules) {
+ json_t* terminalModuleJ = terminalModule->toJson();
+ json_array_append_new(modulesJ, terminalModuleJ);
+ }
json_object_set_new(rootJ, "modules", modulesJ);
// cables
@@ -1204,11 +1245,6 @@
}
json_object_set_new(rootJ, "cables", cablesJ);
- // masterModule
- if (internal->masterModule) {
- json_object_set_new(rootJ, "masterModuleId", json_integer(internal->masterModule->id));
- }
-
return rootJ;
}
@@ -1232,14 +1268,20 @@
}
catch (Exception& e) {
WARN("Cannot load model: %s", e.what());
- APP->patch->log(e.what());
+ // APP->patch->log(e.what());
continue;
}
// Create module
- INFO("Creating module %s", model->getFullName().c_str());
- Module* module = model->createModule();
- assert(module);
+ Module* const module = model->createModule();
+ DISTRHO_SAFE_ASSERT_CONTINUE(module != nullptr);
+
+ // Create the widget too, needed by a few modules
+ CardinalPluginModelHelper* const helper = dynamic_cast<CardinalPluginModelHelper*>(model);
+ DISTRHO_SAFE_ASSERT_CONTINUE(helper != nullptr);
+
+ app::ModuleWidget* const moduleWidget = helper->createModuleWidgetFromEngineLoad(module);
+ DISTRHO_SAFE_ASSERT_CONTINUE(moduleWidget != nullptr);
try {
// This doesn't need a lock because the Module is not added to the Engine yet.
@@ -1255,7 +1297,8 @@
}
catch (Exception& e) {
WARN("Cannot load module: %s", e.what());
- APP->patch->log(e.what());
+ // APP->patch->log(e.what());
+ helper->removeCachedModuleWidget(module);
delete module;
continue;
}
@@ -1292,69 +1335,20 @@
continue;
}
}
-
- // masterModule
- json_t* masterModuleIdJ = json_object_get(rootJ, "masterModuleId");
- if (masterModuleIdJ) {
- Module* masterModule = getModule(json_integer_value(masterModuleIdJ));
- setMasterModule(masterModule);
- }
}
-void EngineWorker::run() {
- // Configure thread
- contextSet(engine->internal->context);
- system::setThreadName(string::f("Worker %d", id));
-#if defined ARCH_X64
- initMXCSR();
-#endif
- random::init();
-
- while (true) {
- engine->internal->engineBarrier.wait();
- if (!running)
- return;
- Engine_stepWorker(engine, id);
- engine->internal->workerBarrier.wait();
- }
+void Engine::startFallbackThread() {
}
-static void Engine_fallbackRun(Engine* that) {
- system::setThreadName("Engine fallback");
- contextSet(that->internal->context);
-
- while (that->internal->fallbackRunning) {
- if (!that->getMasterModule()) {
- // Step blocks and wait
- double start = system::getTime();
- int frames = std::floor(that->getSampleRate() / 60);
- that->stepBlock(frames);
- double end = system::getTime();
-
- double duration = frames * that->getSampleTime() - (end - start);
- if (duration > 0.0) {
- std::this_thread::sleep_for(std::chrono::duration<double>(duration));
- }
- }
- else {
- // Wait for master module to be unset, or for the request to stop running
- std::unique_lock<std::mutex> lock(that->internal->fallbackMutex);
- that->internal->fallbackCv.wait(lock, [&]() {
- return !that->internal->fallbackRunning || !that->getMasterModule();
- });
- }
- }
+void Engine_setAboutToClose(Engine* const engine) {
+ engine->internal->aboutToClose = true;
}
-void Engine::startFallbackThread() {
- if (internal->fallbackThread.joinable())
- return;
-
- internal->fallbackRunning = true;
- internal->fallbackThread = std::thread(Engine_fallbackRun, this);
+void Engine_setRemoteDetails(Engine* const engine, remoteUtils::RemoteDetails* const remoteDetails) {
+ engine->internal->remoteDetails = remoteDetails;
}