|
- /*
- * 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>
- #include <condition_variable>
- #include <mutex>
- #include <atomic>
- #include <tuple>
- #include <pmmintrin.h>
- #include <unordered_map>
-
- #include <engine/Engine.hpp>
- #include <engine/TerminalModule.hpp>
- #include <settings.hpp>
- #include <system.hpp>
- #include <random.hpp>
- #include <patch.hpp>
- #include <plugin.hpp>
- #include <mutex.hpp>
- #include <helpers.hpp>
-
- #ifdef NDEBUG
- # undef DEBUG
- #endif
-
- #include "../CardinalRemote.hpp"
- #include "DistrhoUtils.hpp"
-
-
- // known terminal modules
- extern std::vector<rack::plugin::Model*> hostTerminalModels;
-
-
- namespace rack {
- namespace engine {
-
-
- 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;
-
- // moduleId
- std::map<int64_t, Module*> modulesCache;
- // cableId
- std::map<int64_t, Cable*> cablesCache;
- // (moduleId, paramId)
- std::map<std::tuple<int64_t, int>, ParamHandle*> paramHandlesCache;
-
- float sampleRate = 0.f;
- float sampleTime = 0.f;
- int64_t frame = 0;
- int64_t block = 0;
- int64_t blockFrame = 0;
- double blockTime = 0.0;
- int blockFrames = 0;
- bool aboutToClose = false;
-
- #ifndef HEADLESS
- // Meter
- int meterCount = 0;
- double meterTotal = 0.0;
- double meterMax = 0.0;
- 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;
- };
-
-
- struct Module::Internal {
- bool bypassed = false;
-
- int meterSamples = 0;
- float meterDurationTotal = 0.f;
-
- float meterBuffer[METER_BUFFER_LEN] = {};
- int meterIndex = 0;
- };
-
-
- static void Engine_updateExpander_NoLock(Engine* that, Module* module, bool side) {
- Module::Expander& expander = side ? module->rightExpander : module->leftExpander;
- Module* oldExpanderModule = expander.module;
-
- if (expander.moduleId >= 0) {
- if (!expander.module || expander.module->id != expander.moduleId) {
- expander.module = that->getModule_NoLock(expander.moduleId);
- }
- }
- else {
- if (expander.module) {
- expander.module = NULL;
- }
- }
-
- if (expander.module != oldExpanderModule) {
- // Dispatch ExpanderChangeEvent
- Module::ExpanderChangeEvent e;
- e.side = side;
- module->onExpanderChange(e);
- }
- }
-
-
- 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;
- }
-
-
- #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
-
-
- 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 Module__doProcess(Module* const module, const Module::ProcessArgs& args) {
- Module::Internal* const internal = module->internal;
-
- #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
-
- // 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;
- }
- }
-
- // 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);
- }
- }
- #endif
- }
-
-
- /** Steps a single frame
- */
- static void Engine_stepFrame(Engine* that) {
- Engine::Internal* internal = that->internal;
-
- // Param smoothing
- Module* smoothModule = internal->smoothModule;
- if (smoothModule) {
- int smoothParamId = internal->smoothParamId;
- float smoothValue = internal->smoothValue;
- Param* smoothParam = &smoothModule->params[smoothParamId];
- float value = smoothParam->value;
- 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;
- internal->smoothParamId = 0;
- }
- else {
- smoothParam->setValue(newValue);
- }
- }
-
- // Flip messages for each module
- for (Module* module : internal->modules) {
- if (module->leftExpander.messageFlipRequested) {
- std::swap(module->leftExpander.producerMessage, module->leftExpander.consumerMessage);
- module->leftExpander.messageFlipRequested = false;
- }
- if (module->rightExpander.messageFlipRequested) {
- std::swap(module->rightExpander.producerMessage, module->rightExpander.consumerMessage);
- module->rightExpander.messageFlipRequested = false;
- }
- }
-
- // 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(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;
- }
-
-
- static void Port_setDisconnected(Port* that) {
- that->channels = 0;
- for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
- that->voltages[c] = 0.f;
- }
- }
-
-
- static void Port_setConnected(Port* that) {
- if (that->channels > 0)
- return;
- that->channels = 1;
- }
-
-
- 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<Input*> disconnectedInputs;
- std::set<Output*> disconnectedOutputs;
- for (Module* module : that->internal->modules) {
- for (Input& input : module->inputs) {
- disconnectedInputs.insert(&input);
- }
- for (Output& output : module->outputs) {
- 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 = disconnectedInputs.find(&input);
- if (inputIt != disconnectedInputs.end())
- disconnectedInputs.erase(inputIt);
- Port_setConnected(&input);
- // Connect output
- Output& output = cable->outputModule->outputs[cable->outputId];
- auto outputIt = disconnectedOutputs.find(&output);
- if (outputIt != disconnectedOutputs.end())
- disconnectedOutputs.erase(outputIt);
- Port_setConnected(&output);
- }
- // Disconnect ports that have no cable
- 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);
- }
-
-
- static void Engine_refreshParamHandleCache(Engine* that) {
- // Clear cache
- that->internal->paramHandlesCache.clear();
- // Add active ParamHandles to cache
- for (ParamHandle* paramHandle : that->internal->paramHandles) {
- if (paramHandle->moduleId >= 0) {
- that->internal->paramHandlesCache[std::make_tuple(paramHandle->moduleId, paramHandle->paramId)] = paramHandle;
- }
- }
- }
-
-
- Engine::Engine() {
- internal = new Internal;
- }
-
-
- Engine::~Engine() {
- // 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.
- 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;
- }
-
-
- void Engine::clear() {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- clear_NoLock();
- }
-
-
- void Engine::clear_NoLock() {
- // Copy lists because we'll be removing while iterating
- std::set<ParamHandle*> paramHandles = internal->paramHandles;
- for (ParamHandle* paramHandle : paramHandles) {
- removeParamHandle_NoLock(paramHandle);
- // Don't delete paramHandle because they're normally owned by Module subclasses
- }
- std::vector<Cable*> cables = internal->cables;
- for (Cable* cable : cables) {
- removeCable_NoLock(cable);
- delete cable;
- }
- std::vector<Module*> modules = internal->modules;
- for (Module* module : modules) {
- 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
-
- SharedLock<SharedMutex> lock(internal->mutex);
- // Configure thread
- random::init();
-
- internal->blockFrame = internal->frame;
- internal->blockTime = system::getTime();
- internal->blockFrames = frames;
-
- // Update expander pointers
- for (Module* module : internal->modules) {
- Engine_updateExpander_NoLock(this, module, false);
- Engine_updateExpander_NoLock(this, module, true);
- }
-
- // Step individual frames
- for (int i = 0; i < frames; i++) {
- Engine_stepFrame(this);
- }
-
- internal->block++;
-
- #ifndef HEADLESS
- // Stop timer
- double endTime = system::getTime();
- double meter = (endTime - startTime) / (frames * internal->sampleTime);
- internal->meterTotal += meter;
- internal->meterMax = std::fmax(internal->meterMax, meter);
- internal->meterCount++;
-
- // Update meter values
- const double meterUpdateDuration = 1.0;
- if (startTime - internal->meterLastTime >= meterUpdateDuration) {
- internal->meterLastAverage = internal->meterTotal / internal->meterCount;
- internal->meterLastMax = internal->meterMax;
- internal->meterLastTime = startTime;
- internal->meterCount = 0;
- internal->meterTotal = 0.0;
- internal->meterMax = 0.0;
- }
- #endif
- }
-
-
- void Engine::setMasterModule(Module* module) {
- }
-
-
- void Engine::setMasterModule_NoLock(Module* module) {
- }
-
-
- Module* Engine::getMasterModule() {
- return nullptr;
- }
-
-
- float Engine::getSampleRate() {
- return internal->sampleRate;
- }
-
-
- void Engine::setSampleRate(float sampleRate) {
- if (sampleRate == internal->sampleRate)
- return;
- std::lock_guard<SharedMutex> lock(internal->mutex);
-
- internal->sampleRate = sampleRate;
- internal->sampleTime = 1.f / sampleRate;
- // Dispatch SampleRateChangeEvent
- Module::SampleRateChangeEvent e;
- e.sampleRate = internal->sampleRate;
- e.sampleTime = internal->sampleTime;
- for (Module* module : internal->modules) {
- module->onSampleRateChange(e);
- }
- for (TerminalModule* terminalModule : internal->terminalModules) {
- terminalModule->onSampleRateChange(e);
- }
- }
-
-
- void Engine::setSuggestedSampleRate(float suggestedSampleRate) {
- }
-
-
- float Engine::getSampleTime() {
- return internal->sampleTime;
- }
-
-
- void Engine::yieldWorkers() {
- }
-
-
- int64_t Engine::getFrame() {
- return internal->frame;
- }
-
-
- int64_t Engine::getBlock() {
- return internal->block;
- }
-
-
- int64_t Engine::getBlockFrame() {
- return internal->blockFrame;
- }
-
-
- double Engine::getBlockTime() {
- return internal->blockTime;
- }
-
-
- int Engine::getBlockFrames() {
- return internal->blockFrames;
- }
-
-
- double Engine::getBlockDuration() {
- return internal->blockFrames * internal->sampleTime;
- }
-
-
- 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() + internal->terminalModules.size();
- }
-
-
- size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) {
- SharedLock<SharedMutex> lock(internal->mutex);
- size_t i = 0;
- for (Module* m : internal->modules) {
- if (i >= len)
- break;
- moduleIds[i++] = m->id;
- }
- for (TerminalModule* m : internal->terminalModules) {
- if (i >= len)
- break;
- moduleIds[i++] = m->id;
- }
- return i;
- }
-
-
- std::vector<int64_t> Engine::getModuleIds() {
- SharedLock<SharedMutex> lock(internal->mutex);
- std::vector<int64_t> moduleIds;
- 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);
- 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);
- 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
- 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;
- module->onAdd(eAdd);
- // Dispatch SampleRateChangeEvent
- Module::SampleRateChangeEvent eSrc;
- eSrc.sampleRate = internal->sampleRate;
- eSrc.sampleTime = internal->sampleTime;
- module->onSampleRateChange(eSrc);
- // Update ParamHandles' module pointers
- for (ParamHandle* paramHandle : internal->paramHandles) {
- 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
- }
-
-
- void Engine::removeModule(Module* module) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- removeModule_NoLock(module);
- }
-
-
- 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);
- // Update ParamHandles' module pointers
- for (ParamHandle* paramHandle : internal->paramHandles) {
- if (paramHandle->moduleId == module->id)
- paramHandle->module = 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) {
- DISTRHO_SAFE_ASSERT(cable->inputModule != module);
- DISTRHO_SAFE_ASSERT(cable->outputModule != module);
- }
- // Update expanders of other modules
- for (Module* m : internal->modules) {
- if (m->leftExpander.module == module) {
- m->leftExpander.moduleId = -1;
- m->leftExpander.module = NULL;
- }
- if (m->rightExpander.module == module) {
- m->rightExpander.moduleId = -1;
- m->rightExpander.module = NULL;
- }
- }
- // 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);
- }
- }
-
-
- bool Engine::hasModule(Module* module) {
- 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);
- auto tit = std::find(internal->terminalModules.begin(), internal->terminalModules.end(), module);
- return it != internal->modules.end() && tit != internal->terminalModules.end();
- }
-
-
- Module* Engine::getModule(int64_t moduleId) {
- SharedLock<SharedMutex> lock(internal->mutex);
- return getModule_NoLock(moduleId);
- }
-
-
- Module* Engine::getModule_NoLock(int64_t moduleId) {
- auto it = internal->modulesCache.find(moduleId);
- if (it == internal->modulesCache.end())
- return NULL;
- return it->second;
- }
-
-
- void Engine::resetModule(Module* module) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- DISTRHO_SAFE_ASSERT_RETURN(module,);
-
- Module::ResetEvent eReset;
- module->onReset(eReset);
- }
-
-
- void Engine::randomizeModule(Module* module) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- DISTRHO_SAFE_ASSERT_RETURN(module,);
-
- Module::RandomizeEvent eRandomize;
- module->onRandomize(eRandomize);
- }
-
-
- void Engine::bypassModule(Module* module, bool bypassed) {
- DISTRHO_SAFE_ASSERT_RETURN(module,);
- if (module->isBypassed() == bypassed)
- return;
-
- std::lock_guard<SharedMutex> lock(internal->mutex);
-
- // Clear outputs and set to 1 channel
- for (Output& output : module->outputs) {
- // This zeros all voltages, but the channel is set to 1 if connected
- output.setChannels(0);
- }
- // Set bypassed state
- module->setBypassed(bypassed);
- if (bypassed) {
- // Dispatch BypassEvent
- Module::BypassEvent eBypass;
- module->onBypass(eBypass);
- }
- else {
- // Dispatch UnBypassEvent
- Module::UnBypassEvent eUnBypass;
- module->onUnBypass(eUnBypass);
- }
- }
-
-
- json_t* Engine::moduleToJson(Module* module) {
- SharedLock<SharedMutex> lock(internal->mutex);
- return module->toJson();
- }
-
-
- void Engine::moduleFromJson(Module* module, json_t* rootJ) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- module->fromJson(rootJ);
- }
-
-
- void Engine::prepareSaveModule(Module* module) {
- SharedLock<SharedMutex> lock(internal->mutex);
- Module::SaveEvent e;
- module->onSave(e);
- }
-
-
- 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);
- }
- }
-
-
- size_t Engine::getNumCables() {
- return internal->cables.size();
- }
-
-
- size_t Engine::getCableIds(int64_t* cableIds, size_t len) {
- SharedLock<SharedMutex> lock(internal->mutex);
- size_t i = 0;
- for (Cable* c : internal->cables) {
- if (i >= len)
- break;
- cableIds[i] = c->id;
- i++;
- }
- return i;
- }
-
-
- std::vector<int64_t> Engine::getCableIds() {
- SharedLock<SharedMutex> lock(internal->mutex);
- std::vector<int64_t> cableIds;
- cableIds.reserve(internal->cables.size());
- for (Cable* c : internal->cables) {
- cableIds.push_back(c->id);
- }
- return cableIds;
- }
-
-
- void Engine::addCable(Cable* cable) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- DISTRHO_SAFE_ASSERT_RETURN(cable,);
- // Check cable properties
- 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
- DISTRHO_SAFE_ASSERT_RETURN(cable2 != cable,);
- // Check that the input is not already used by another cable
- 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)
- 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
- cable->id = random::u64() % (1ull << 53);
- }
- // 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
- {
- Module::PortChangeEvent e;
- e.connecting = true;
- e.type = Port::INPUT;
- e.portId = cable->inputId;
- cable->inputModule->onPortChange(e);
- }
- // Dispatch output port event if its state went from disconnected to connected.
- if (!outputWasConnected) {
- Module::PortChangeEvent e;
- e.connecting = true;
- e.type = Port::OUTPUT;
- e.portId = cable->outputId;
- cable->outputModule->onPortChange(e);
- }
- }
-
-
- void Engine::removeCable(Cable* cable) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- removeCable_NoLock(cable);
- }
-
-
- void Engine::removeCable_NoLock(Cable* cable) {
- DISTRHO_SAFE_ASSERT_RETURN(cable,);
- // Check that the cable is already added
- auto it = std::find(internal->cables.begin(), internal->cables.end(), cable);
- 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);
- Engine_updateConnected(this);
- bool outputIsConnected = false;
- for (Cable* cable2 : internal->cables) {
- // 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)
- outputIsConnected = true;
- }
- // Dispatch input port event
- {
- 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.
- if (!outputIsConnected) {
- Module::PortChangeEvent e;
- e.connecting = false;
- e.type = Port::OUTPUT;
- e.portId = cable->outputId;
- cable->outputModule->onPortChange(e);
- }
- }
-
-
- bool Engine::hasCable(Cable* cable) {
- SharedLock<SharedMutex> lock(internal->mutex);
- // TODO Performance could be improved by searching cablesCache, but more testing would be needed to make sure it's always valid.
- auto it = std::find(internal->cables.begin(), internal->cables.end(), cable);
- return it != internal->cables.end();
- }
-
-
- Cable* Engine::getCable(int64_t cableId) {
- SharedLock<SharedMutex> lock(internal->mutex);
- auto it = internal->cablesCache.find(cableId);
- if (it == internal->cablesCache.end())
- return NULL;
- return it->second;
- }
-
-
- void Engine::setParamValue(Module* module, int paramId, float value) {
- // If param is being smoothed, cancel smoothing.
- if (internal->smoothModule == module && internal->smoothParamId == paramId) {
- internal->smoothModule = NULL;
- internal->smoothParamId = 0;
- }
- if (internal->remoteDetails != nullptr) {
- sendParamChangeToRemote(internal->remoteDetails, module->id, paramId, value);
- }
- module->params[paramId].setValue(value);
- }
-
-
- float Engine::getParamValue(Module* module, int paramId) {
- return module->params[paramId].getValue();
- }
-
-
- void Engine::setParamSmoothValue(Module* module, int paramId, float value) {
- // If another param is being smoothed, jump value
- if (internal->smoothModule && !(internal->smoothModule == module && internal->smoothParamId == paramId)) {
- internal->smoothModule->params[internal->smoothParamId].setValue(internal->smoothValue);
- }
- internal->smoothParamId = paramId;
- internal->smoothValue = value;
- // Set this last so the above values are valid as soon as it is set
- internal->smoothModule = module;
- }
-
-
- float Engine::getParamSmoothValue(Module* module, int paramId) {
- if (internal->smoothModule == module && internal->smoothParamId == paramId)
- return internal->smoothValue;
- return module->params[paramId].getValue();
- }
-
-
- void Engine::addParamHandle(ParamHandle* paramHandle) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- // New ParamHandles must be blank.
- // This means we don't have to refresh the cache.
- DISTRHO_SAFE_ASSERT_RETURN(paramHandle->moduleId < 0,);
-
- // Check that the ParamHandle is not already added
- auto it = internal->paramHandles.find(paramHandle);
- DISTRHO_SAFE_ASSERT_RETURN(it == internal->paramHandles.end(),);
-
- // Add it
- internal->paramHandles.insert(paramHandle);
- // No need to refresh the cache because the moduleId is not set.
- }
-
-
- void Engine::removeParamHandle(ParamHandle* paramHandle) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- removeParamHandle_NoLock(paramHandle);
- }
-
-
- void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) {
- // Check that the ParamHandle is already added
- auto it = internal->paramHandles.find(paramHandle);
- DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),);
-
- // Remove it
- paramHandle->module = NULL;
- internal->paramHandles.erase(it);
- Engine_refreshParamHandleCache(this);
- }
-
-
- ParamHandle* Engine::getParamHandle(int64_t moduleId, int paramId) {
- SharedLock<SharedMutex> lock(internal->mutex);
- return getParamHandle_NoLock(moduleId, paramId);
- }
-
-
- ParamHandle* Engine::getParamHandle_NoLock(int64_t moduleId, int paramId) {
- auto it = internal->paramHandlesCache.find(std::make_tuple(moduleId, paramId));
- if (it == internal->paramHandlesCache.end())
- return NULL;
- return it->second;
- }
-
-
- ParamHandle* Engine::getParamHandle(Module* module, int paramId) {
- return getParamHandle(module->id, paramId);
- }
-
-
- void Engine::updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) {
- std::lock_guard<SharedMutex> lock(internal->mutex);
- updateParamHandle_NoLock(paramHandle, moduleId, paramId, overwrite);
- }
-
-
- void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) {
- // Check that it exists
- auto it = internal->paramHandles.find(paramHandle);
- DISTRHO_SAFE_ASSERT_RETURN(it != internal->paramHandles.end(),);
-
- // Set IDs
- paramHandle->moduleId = moduleId;
- paramHandle->paramId = paramId;
- paramHandle->module = NULL;
- // At this point, the ParamHandle cache might be invalid.
-
- if (paramHandle->moduleId >= 0) {
- // Replace old ParamHandle, or reset the current ParamHandle
- ParamHandle* oldParamHandle = getParamHandle_NoLock(moduleId, paramId);
- if (oldParamHandle) {
- if (overwrite) {
- oldParamHandle->moduleId = -1;
- oldParamHandle->paramId = 0;
- oldParamHandle->module = NULL;
- }
- else {
- paramHandle->moduleId = -1;
- paramHandle->paramId = 0;
- paramHandle->module = NULL;
- }
- }
- }
-
- // Set module pointer if the above block didn't reset it
- if (paramHandle->moduleId >= 0) {
- paramHandle->module = getModule_NoLock(paramHandle->moduleId);
- }
-
- Engine_refreshParamHandleCache(this);
- }
-
-
- json_t* Engine::toJson() {
- SharedLock<SharedMutex> lock(internal->mutex);
- json_t* rootJ = json_object();
-
- // modules
- json_t* modulesJ = json_array();
- for (Module* module : internal->modules) {
- 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
- json_t* cablesJ = json_array();
- for (Cable* cable : internal->cables) {
- json_t* cableJ = cable->toJson();
- json_array_append_new(cablesJ, cableJ);
- }
- json_object_set_new(rootJ, "cables", cablesJ);
-
- return rootJ;
- }
-
-
- void Engine::fromJson(json_t* rootJ) {
- // Don't write-lock the entire method because most of it doesn't need it.
-
- // Write-locks
- clear();
- // modules
- json_t* modulesJ = json_object_get(rootJ, "modules");
- if (!modulesJ)
- return;
- size_t moduleIndex;
- json_t* moduleJ;
- json_array_foreach(modulesJ, moduleIndex, moduleJ) {
- // Get model
- plugin::Model* model;
- try {
- model = plugin::modelFromJson(moduleJ);
- }
- catch (Exception& e) {
- WARN("Cannot load model: %s", e.what());
- // APP->patch->log(e.what());
- continue;
- }
-
- // Create 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.
- module->fromJson(moduleJ);
-
- // Before 1.0, the module ID was the index in the "modules" array
- if (module->id < 0) {
- module->id = moduleIndex;
- }
-
- // Write-locks
- addModule(module);
- }
- catch (Exception& e) {
- WARN("Cannot load module: %s", e.what());
- // APP->patch->log(e.what());
- helper->removeCachedModuleWidget(module);
- delete module;
- continue;
- }
- }
-
- // cables
- json_t* cablesJ = json_object_get(rootJ, "cables");
- // Before 1.0, cables were called wires
- if (!cablesJ)
- cablesJ = json_object_get(rootJ, "wires");
- if (!cablesJ)
- return;
- size_t cableIndex;
- json_t* cableJ;
- json_array_foreach(cablesJ, cableIndex, cableJ) {
- // cable
- Cable* cable = new Cable;
-
- try {
- cable->fromJson(cableJ);
-
- // Before 1.0, the cable ID was the index in the "cables" array
- if (cable->id < 0) {
- cable->id = cableIndex;
- }
-
- // Write-locks
- addCable(cable);
- }
- catch (Exception& e) {
- WARN("Cannot load cable: %s", e.what());
- delete cable;
- // Don't log exceptions because missing modules create unnecessary complaining when cables try to connect to them.
- continue;
- }
- }
- }
-
-
- void Engine::startFallbackThread() {
- }
-
-
- void Engine_setAboutToClose(Engine* const engine) {
- engine->internal->aboutToClose = true;
- }
-
-
- void Engine_setRemoteDetails(Engine* const engine, remoteUtils::RemoteDetails* const remoteDetails) {
- engine->internal->remoteDetails = remoteDetails;
- }
-
-
- } // namespace engine
- } // namespace rack
|