1097 lines
30 KiB
Diff
1097 lines
30 KiB
Diff
--- ../Rack/src/engine/Engine.cpp 2022-01-15 11:59:46.188414546 +0000
|
|
+++ Engine.cpp 2022-02-09 02:13:37.829435608 +0000
|
|
@@ -1,3 +1,30 @@
|
|
+/*
|
|
+ * DISTRHO Cardinal Plugin
|
|
+ * Copyright (C) 2021-2022 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-2021 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>
|
|
@@ -8,181 +35,35 @@
|
|
#include <pmmintrin.h>
|
|
|
|
#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>
|
|
|
|
+#ifdef NDEBUG
|
|
+# undef DEBUG
|
|
+#endif
|
|
|
|
-namespace rack {
|
|
-namespace engine {
|
|
-
|
|
-
|
|
-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);
|
|
-}
|
|
-
|
|
-
|
|
-/** 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;
|
|
- }
|
|
+#include "DistrhoUtils.hpp"
|
|
|
|
- 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;
|
|
- __builtin_ia32_pause();
|
|
- }
|
|
- }
|
|
-};
|
|
|
|
+// 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;
|
|
- __builtin_ia32_pause();
|
|
- }
|
|
-
|
|
- // 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;
|
|
-
|
|
- void start() {
|
|
- assert(!running);
|
|
- running = true;
|
|
- thread = std::thread([&] {
|
|
- run();
|
|
- });
|
|
- }
|
|
-
|
|
- void requestStop() {
|
|
- running = false;
|
|
- }
|
|
-
|
|
- void join() {
|
|
- assert(thread.joinable());
|
|
- thread.join();
|
|
- }
|
|
-
|
|
- void run();
|
|
-};
|
|
+namespace rack {
|
|
+namespace engine {
|
|
|
|
|
|
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;
|
|
@@ -199,6 +80,7 @@
|
|
double blockTime = 0.0;
|
|
int blockFrames = 0;
|
|
|
|
+#ifndef HEADLESS
|
|
// Meter
|
|
int meterCount = 0;
|
|
double meterTotal = 0.0;
|
|
@@ -206,6 +88,7 @@
|
|
double meterLastTime = -INFINITY;
|
|
double meterLastAverage = 0.0;
|
|
double meterLastMax = 0.0;
|
|
+#endif
|
|
|
|
// Parameter smoothing
|
|
Module* smoothModule = NULL;
|
|
@@ -217,22 +100,6 @@
|
|
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;
|
|
};
|
|
|
|
|
|
@@ -260,76 +127,11 @@
|
|
}
|
|
|
|
|
|
-static void Engine_relaunchWorkers(Engine* that, int threadCount) {
|
|
- Engine::Internal* internal = that->internal;
|
|
- if (threadCount == internal->threadCount)
|
|
- return;
|
|
-
|
|
- 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);
|
|
- }
|
|
-
|
|
- // 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 Engine_stepWorker(Engine* that, int threadId) {
|
|
- Engine::Internal* internal = that->internal;
|
|
-
|
|
- // int threadCount = internal->threadCount;
|
|
- int modulesLen = internal->modules.size();
|
|
-
|
|
- // 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);
|
|
- }
|
|
-}
|
|
-
|
|
-
|
|
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;
|
|
+ const int channels = output->channels;
|
|
// Copy all voltages from output to input
|
|
for (int c = 0; c < channels; c++) {
|
|
float v = output->voltages[c];
|
|
@@ -346,6 +148,53 @@
|
|
}
|
|
|
|
|
|
+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);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static void TerminalModule__doProcess(TerminalModule* 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);
|
|
+ }
|
|
+
|
|
+ // Iterate ports to step plug lights
|
|
+ if (args.frame % 7 /* PORT_DIVIDER */ == 0) {
|
|
+ float portTime = args.sampleTime * 7 /* PORT_DIVIDER */;
|
|
+ for (Input& input : terminalModule->inputs) {
|
|
+ Port_step(&input, portTime);
|
|
+ }
|
|
+ for (Output& output : terminalModule->outputs) {
|
|
+ Port_step(&output, portTime);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
/** Steps a single frame
|
|
*/
|
|
static void Engine_stepFrame(Engine* that) {
|
|
@@ -372,13 +221,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;
|
|
@@ -389,13 +233,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);
|
|
+ }
|
|
+
|
|
+ // Step each module and cables
|
|
+ for (Module* module : internal->modules) {
|
|
+ module->doProcess(processArgs);
|
|
+ for (Output& output : module->outputs) {
|
|
+ for (Cable* cable : output.cables)
|
|
+ Cable_step(cable);
|
|
+ }
|
|
+ }
|
|
|
|
- internal->frame++;
|
|
+ // Process terminal outputs last
|
|
+ for (TerminalModule* terminalModule : internal->terminalModules) {
|
|
+ TerminalModule__doProcess(terminalModule, processArgs, false);
|
|
+ }
|
|
+
|
|
+ ++internal->frame;
|
|
}
|
|
|
|
|
|
@@ -416,32 +279,45 @@
|
|
|
|
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());
|
|
}
|
|
}
|
|
|
|
@@ -460,37 +336,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;
|
|
}
|
|
@@ -519,18 +381,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
|
|
- uint32_t csr = _mm_getcsr();
|
|
- initMXCSR();
|
|
random::init();
|
|
|
|
internal->blockFrame = internal->frame;
|
|
@@ -543,18 +409,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);
|
|
@@ -572,47 +434,20 @@
|
|
internal->meterTotal = 0.0;
|
|
internal->meterMax = 0.0;
|
|
}
|
|
-
|
|
- // 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;
|
|
}
|
|
|
|
|
|
@@ -635,20 +470,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);
|
|
- }
|
|
}
|
|
|
|
|
|
@@ -658,7 +486,6 @@
|
|
|
|
|
|
void Engine::yieldWorkers() {
|
|
- internal->workerBarrier.yield();
|
|
}
|
|
|
|
|
|
@@ -698,17 +525,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();
|
|
}
|
|
|
|
|
|
@@ -718,8 +553,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;
|
|
}
|
|
@@ -728,27 +567,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;
|
|
@@ -772,11 +627,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);
|
|
@@ -785,18 +640,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) {
|
|
@@ -809,14 +660,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);
|
|
+ }
|
|
}
|
|
|
|
|
|
@@ -824,7 +692,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();
|
|
}
|
|
|
|
|
|
@@ -844,7 +713,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);
|
|
@@ -853,7 +722,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);
|
|
@@ -861,7 +730,7 @@
|
|
|
|
|
|
void Engine::bypassModule(Module* module, bool bypassed) {
|
|
- assert(module);
|
|
+ DISTRHO_SAFE_ASSERT_RETURN(module,);
|
|
if (module->isBypassed() == bypassed)
|
|
return;
|
|
|
|
@@ -912,6 +781,10 @@
|
|
Module::SaveEvent e;
|
|
module->onSave(e);
|
|
}
|
|
+ for (TerminalModule* terminalModule : internal->terminalModules) {
|
|
+ Module::SaveEvent e;
|
|
+ terminalModule->onSave(e);
|
|
+ }
|
|
}
|
|
|
|
|
|
@@ -946,16 +819,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)
|
|
@@ -969,6 +842,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
|
|
{
|
|
@@ -996,10 +871,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);
|
|
@@ -1085,11 +962,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);
|
|
@@ -1106,7 +983,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;
|
|
@@ -1143,7 +1020,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;
|
|
@@ -1187,6 +1064,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
|
|
@@ -1197,11 +1078,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;
|
|
}
|
|
|
|
@@ -1225,14 +1101,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.
|
|
@@ -1248,7 +1130,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;
|
|
}
|
|
@@ -1285,67 +1168,10 @@
|
|
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));
|
|
- initMXCSR();
|
|
- random::init();
|
|
-
|
|
- while (true) {
|
|
- engine->internal->engineBarrier.wait();
|
|
- if (!running)
|
|
- return;
|
|
- Engine_stepWorker(engine, id);
|
|
- engine->internal->workerBarrier.wait();
|
|
- }
|
|
-}
|
|
-
|
|
-
|
|
-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::startFallbackThread() {
|
|
- if (internal->fallbackThread.joinable())
|
|
- return;
|
|
-
|
|
- internal->fallbackRunning = true;
|
|
- internal->fallbackThread = std::thread(Engine_fallbackRun, this);
|
|
}
|
|
|
|
|