diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index 65bbdcd..237808a 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -314,8 +314,8 @@ class CardinalPlugin : public CardinalBasePlugin // for base/context handling bool fIsActive; CardinalAudioDevice* fCurrentAudioDevice; + CardinalMidiInputDevice* fCurrentMidiInput; CardinalMidiOutputDevice* fCurrentMidiOutput; - std::list fMidiInputs; uint64_t fPreviousFrame; Mutex fDeviceMutex; @@ -332,6 +332,7 @@ public: fAudioBufferOut(nullptr), fIsActive(false), fCurrentAudioDevice(nullptr), + fCurrentMidiInput(nullptr), fCurrentMidiOutput(nullptr), fPreviousFrame(0) { @@ -364,9 +365,17 @@ public: } } DISTRHO_SAFE_EXCEPTION("create unique temporary path"); + const float sampleRate = getSampleRate(); + rack::settings::sampleRate = sampleRate; + + context->bufferSize = getBufferSize(); + context->sampleRate = sampleRate; + const ScopedContext sc(this); context->engine = new rack::engine::Engine; + context->engine->setSampleRate(sampleRate); + context->history = new rack::history::State; context->patch = new rack::patch::Manager; context->patch->autosavePath = fAutosavePath; @@ -375,9 +384,9 @@ public: context->event = new rack::widget::EventState; context->scene = new rack::app::Scene; context->event->rootWidget = context->scene; + context->patch->loadTemplate(); context->scene->rackScroll->reset(); - context->engine->startFallbackThread(); #ifdef HAVE_LIBLO fInitializer->oscPlugin = this; @@ -400,13 +409,22 @@ public: fInitializer->oscPlugin = nullptr; #endif - rack::contextSet(context); + { + const MutexLocker cml(fDeviceMutex); + fCurrentAudioDevice = nullptr; + fCurrentMidiInput = nullptr; + fCurrentMidiOutput = nullptr; + } + + { + const ScopedContext sc(this); + #if defined(__MOD_DEVICES__) && !defined(HEADLESS) - delete context->window; - context->window = nullptr; + delete context->window; + context->window = nullptr; #endif - delete context; - rack::contextSet(nullptr); + delete context; + } if (! fAutosavePath.empty()) rack::system::removeRecursively(fAutosavePath); @@ -432,6 +450,12 @@ protected: return fCurrentAudioDevice == nullptr; } + bool canAssignMidiInputDevice() const noexcept override + { + const MutexLocker cml(fDeviceMutex); + return fCurrentMidiInput == nullptr; + } + bool canAssignMidiOutputDevice() const noexcept override { const MutexLocker cml(fDeviceMutex); @@ -446,6 +470,14 @@ protected: fCurrentAudioDevice = dev; } + void assignMidiInputDevice(CardinalMidiInputDevice* const dev) noexcept override + { + DISTRHO_SAFE_ASSERT_RETURN(fCurrentMidiInput == nullptr,); + + const MutexLocker cml(fDeviceMutex); + fCurrentMidiInput = dev; + } + void assignMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override { DISTRHO_SAFE_ASSERT_RETURN(fCurrentMidiOutput == nullptr,); @@ -465,31 +497,26 @@ protected: return true; } - bool clearMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override + bool clearMidiInputDevice(CardinalMidiInputDevice* const dev) noexcept override { const MutexLocker cml(fDeviceMutex); - if (fCurrentMidiOutput != dev) + if (fCurrentMidiInput != dev) return false; - fCurrentMidiOutput = nullptr; + fCurrentMidiInput = nullptr; return true; } - void addMidiInput(CardinalMidiInputDevice* const dev) override + bool clearMidiOutputDevice(CardinalMidiOutputDevice* const dev) noexcept override { - // NOTE this will xrun const MutexLocker cml(fDeviceMutex); - fMidiInputs.push_back(dev); - } - - void removeMidiInput(CardinalMidiInputDevice* const dev) override - { - // NOTE this will xrun - const MutexLocker cml(fDeviceMutex); + if (fCurrentMidiOutput != dev) + return false; - fMidiInputs.remove(dev); + fCurrentMidiOutput = nullptr; + return true; } /* -------------------------------------------------------------------------------------------------------- @@ -746,7 +773,10 @@ protected: rack::system::createDirectories(fAutosavePath); rack::system::unarchiveToDirectory(data, fAutosavePath); - context->patch->loadAutosave(); + try { + context->patch->loadAutosave(); + } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",); + // context->history->setSaved(); } @@ -798,28 +828,8 @@ protected: void run(const float** const inputs, float** const outputs, const uint32_t frames, const MidiEvent* const midiEvents, const uint32_t midiEventCount) override { - /* - context->engine->setFrame(getTimePosition().frame); - context->engine->stepBlock(frames); - */ - const MutexLocker cml(fDeviceMutex); - - if (fCurrentAudioDevice != nullptr) - { -#if DISTRHO_PLUGIN_NUM_INPUTS != 0 - for (uint32_t i=0, j=0; ihandleMessagesFromHost(midiEvents, midiEventCount); + if (fCurrentMidiInput != nullptr) + fCurrentMidiInput->handleMessagesFromHost(midiEvents, midiEventCount); if (fCurrentAudioDevice != nullptr) { -#if DISTRHO_PLUGIN_NUM_INPUTS == 0 - const float* const insPtr = nullptr; -#else - const float* const insPtr = fAudioBufferIn; +#if DISTRHO_PLUGIN_NUM_INPUTS != 0 + for (uint32_t i=0, j=0; iprocessInput(fAudioBufferIn, DISTRHO_PLUGIN_NUM_INPUTS, frames); #endif - fCurrentAudioDevice->processBuffer(insPtr, DISTRHO_PLUGIN_NUM_INPUTS, - fAudioBufferOut, DISTRHO_PLUGIN_NUM_OUTPUTS, frames); } - if (fCurrentMidiOutput != nullptr) - fCurrentMidiOutput->processMessages(); + context->engine->stepBlock(frames); - for (uint32_t i=0, j=0; iprocessOutput(fAudioBufferOut, DISTRHO_PLUGIN_NUM_OUTPUTS, frames); + + for (uint32_t i=0, j=0; iprocessMessages(); + } + + void bufferSizeChanged(const uint32_t newBufferSize) override + { + rack::contextSet(context); + context->bufferSize = newBufferSize; } void sampleRateChanged(const double newSampleRate) override { rack::contextSet(context); rack::settings::sampleRate = newSampleRate; + context->sampleRate = newSampleRate; context->engine->setSampleRate(newSampleRate); } diff --git a/src/Makefile b/src/Makefile index 55485f3..5036099 100644 --- a/src/Makefile +++ b/src/Makefile @@ -71,6 +71,7 @@ BUILD_C_FLAGS += -std=gnu11 # Rack files to build RACK_FILES += AsyncDialog.cpp +RACK_FILES += override/Engine.cpp RACK_FILES += override/asset.cpp RACK_FILES += override/context.cpp RACK_FILES += override/dep.cpp @@ -95,6 +96,7 @@ IGNORED_FILES += Rack/src/network.cpp IGNORED_FILES += Rack/src/rtaudio.cpp IGNORED_FILES += Rack/src/rtmidi.cpp IGNORED_FILES += Rack/src/app/MenuBar.cpp +IGNORED_FILES += Rack/src/engine/Engine.cpp IGNORED_FILES += Rack/src/window/Window.cpp RACK_FILES += $(wildcard Rack/src/*.c) diff --git a/src/PluginContext.hpp b/src/PluginContext.hpp index 9ec6e31..81c8739 100644 --- a/src/PluginContext.hpp +++ b/src/PluginContext.hpp @@ -80,25 +80,14 @@ public: ~CardinalBasePlugin() override {} virtual bool isActive() const noexcept = 0; virtual bool canAssignAudioDevice() const noexcept = 0; + virtual bool canAssignMidiInputDevice() const noexcept = 0; virtual bool canAssignMidiOutputDevice() const noexcept = 0; virtual void assignAudioDevice(CardinalAudioDevice* dev) noexcept = 0; + virtual void assignMidiInputDevice(CardinalMidiInputDevice* dev) noexcept = 0; virtual void assignMidiOutputDevice(CardinalMidiOutputDevice* dev) noexcept = 0; virtual bool clearAudioDevice(CardinalAudioDevice* dev) noexcept = 0; + virtual bool clearMidiInputDevice(CardinalMidiInputDevice* dev) noexcept = 0; virtual bool clearMidiOutputDevice(CardinalMidiOutputDevice* dev) noexcept = 0; - virtual void addMidiInput(CardinalMidiInputDevice* dev) = 0; - virtual void removeMidiInput(CardinalMidiInputDevice* dev) = 0; - -protected: - void bufferSizeChanged(const uint32_t newBufferSize) override - { - context->bufferSize = newBufferSize; - } - - void sampleRateChanged(const double newSampleRate) override - { - context->sampleRate = newSampleRate; - // context->engine->setSampleRate(newSampleRate); - } }; // ----------------------------------------------------------------------------------------------------------- diff --git a/src/PluginDriver.hpp b/src/PluginDriver.hpp index e6a936f..c75b4ef 100644 --- a/src/PluginDriver.hpp +++ b/src/PluginDriver.hpp @@ -67,6 +67,18 @@ struct CardinalAudioDevice : rack::audio::Device void setBlockSize(int) override {} void setSampleRate(float) override {} + + void processInput(const float* const input, const int inputStride, const int frames) + { + for (rack::audio::Port* port : subscribed) + port->processInput(input + port->inputOffset, inputStride, frames); + } + + void processOutput(float* const output, const int outputStride, const int frames) + { + for (rack::audio::Port* port : subscribed) + port->processOutput(output + port->outputOffset, outputStride, frames); + } }; // ----------------------------------------------------------------------------------------------------------- @@ -139,7 +151,9 @@ struct CardinalAudioDriver : rack::audio::Driver if (plugin->clearAudioDevice(device)) { - device->onStopStream(); + if (plugin->isActive()) + device->onStopStream(); + device->unsubscribe(port); delete device; } @@ -156,7 +170,7 @@ struct CardinalMidiInputDevice : rack::midi::InputDevice CardinalMidiInputDevice(CardinalBasePlugin* const plugin) : fPlugin(plugin) { - msg.bytes.reserve(0xff); + msg.bytes.resize(0xff); } std::string getName() override @@ -177,7 +191,7 @@ struct CardinalMidiInputDevice : rack::midi::InputDevice if (midiEvent.size > MidiEvent::kDataSize) { data = midiEvent.dataExt; - msg.bytes.reserve(midiEvent.size); + msg.bytes.resize(midiEvent.size); } else { @@ -287,10 +301,13 @@ struct CardinalMidiDriver : rack::midi::Driver CardinalBasePlugin* const plugin = reinterpret_cast(pluginContext->plugin); DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr, nullptr); + if (! plugin->canAssignMidiInputDevice()) + throw rack::Exception("Plugin driver only allows one midi input device to be used simultaneously"); + CardinalMidiInputDevice* const device = new CardinalMidiInputDevice(plugin); device->subscribe(input); - plugin->addMidiInput(device); + plugin->assignMidiInputDevice(device); return device; } @@ -323,9 +340,11 @@ struct CardinalMidiDriver : rack::midi::Driver CardinalBasePlugin* const plugin = reinterpret_cast(pluginContext->plugin); DISTRHO_SAFE_ASSERT_RETURN(plugin != nullptr,); - plugin->removeMidiInput(device); - device->unsubscribe(input); - delete device; + if (plugin->clearMidiInputDevice(device)) + { + device->unsubscribe(input); + delete device; + } } void unsubscribeOutput(int, rack::midi::Output* const output) override diff --git a/src/override/Engine.cpp b/src/override/Engine.cpp new file mode 100644 index 0000000..52f93a1 --- /dev/null +++ b/src/override/Engine.cpp @@ -0,0 +1,1057 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * 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.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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef NDEBUG +# undef DEBUG +#endif + +#include "DistrhoUtils.hpp" + +namespace rack { +namespace engine { + + +/** Allows multiple "reader" threads to obtain a lock simultaneously, but only one "writer" thread. +This implementation is currently just a wrapper for pthreads, which works on Linux/Mac/. +This is available in C++17 as std::shared_mutex, but unfortunately we're using C++11. +*/ +struct ReadWriteMutex { + pthread_rwlock_t rwlock; + + ReadWriteMutex() { + if (pthread_rwlock_init(&rwlock, NULL)) + throw Exception("pthread_rwlock_init failed"); + } + ~ReadWriteMutex() { + pthread_rwlock_destroy(&rwlock); + } + void lockReader() { + if (pthread_rwlock_rdlock(&rwlock)) + throw Exception("pthread_rwlock_rdlock failed"); + } + void unlockReader() { + if (pthread_rwlock_unlock(&rwlock)) + throw Exception("pthread_rwlock_unlock failed"); + } + void lockWriter() { + if (pthread_rwlock_wrlock(&rwlock)) + throw Exception("pthread_rwlock_wrlock failed"); + } + void unlockWriter() { + if (pthread_rwlock_unlock(&rwlock)) + throw Exception("pthread_rwlock_unlock failed"); + } +}; + +struct ReadLock { + ReadWriteMutex& m; + ReadLock(ReadWriteMutex& m) : m(m) { + m.lockReader(); + } + ~ReadLock() { + m.unlockReader(); + } +}; + +struct WriteLock { + ReadWriteMutex& m; + WriteLock(ReadWriteMutex& m) : m(m) { + m.lockWriter(); + } + ~WriteLock() { + m.unlockWriter(); + } +}; + + +struct Engine::Internal { + std::vector modules; + std::vector cables; + std::set paramHandles; + + // moduleId + std::map modulesCache; + // cableId + std::map cablesCache; + // (moduleId, paramId) + std::map, ParamHandle*> paramHandlesCache; + + float sampleRate = 0.f; + float sampleTime = 0.f; + int64_t block = 0; + int64_t frame = 0; + int64_t blockFrame = 0; + double blockTime = 0.0; + int blockFrames = 0; + + // Meter + int meterCount = 0; + double meterTotal = 0.0; + double meterMax = 0.0; + double meterLastTime = -INFINITY; + double meterLastAverage = 0.0; + double meterLastMax = 0.0; + + // Parameter smoothing + Module* smoothModule = NULL; + int smoothParamId = 0; + float smoothValue = 0.f; + + /** 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. + */ + ReadWriteMutex mutex; + + // For worker threads + Context* context; +}; + + +static void Engine_updateExpander(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(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 + int channels = output->channels; + // Copy all voltages from output to input + for (int c = 0; c < channels; c++) { + float v = output->voltages[c]; + // Set 0V if infinite or NaN + if (!std::isfinite(v)) + v = 0.f; + input->voltages[c] = v; + } + // Set higher channel voltages to 0 + for (int c = channels; c < input->channels; c++) { + input->voltages[c] = 0.f; + } + input->channels = channels; +} + + +/** Steps a single frame +*/ +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; + // Use decay rate of roughly 1 graphics frame + const float smoothLambda = 60.f; + float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; + if (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); + } + } + + // Step cables + for (Cable* cable : internal->cables) { + Cable_step(cable); + } + + // 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; + + // Step each module + for (Module* module : internal->modules) { + module->doProcess(processArgs); + } + + ++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; +} + + +static void Engine_updateConnected(Engine* that) { + // Find disconnected ports + std::set disconnectedPorts; + for (Module* module : that->internal->modules) { + for (Input& input : module->inputs) { + disconnectedPorts.insert(&input); + } + for (Output& output : module->outputs) { + disconnectedPorts.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); + Port_setConnected(&input); + // Connect output + Output& output = cable->outputModule->outputs[cable->outputId]; + auto outputIt = disconnectedPorts.find(&output); + if (outputIt != disconnectedPorts.end()) + disconnectedPorts.erase(outputIt); + Port_setConnected(&output); + } + // Disconnect ports that have no cable + for (Port* port : disconnectedPorts) { + Port_setDisconnected(port); + } +} + + +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; + + internal->context = contextGet(); + setSuggestedSampleRate(0.f); +} + + +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->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() { + WriteLock lock(internal->mutex); + clear_NoLock(); +} + + +void Engine::clear_NoLock() { + // Copy lists because we'll be removing while iterating + std::set paramHandles = internal->paramHandles; + for (ParamHandle* paramHandle : paramHandles) { + removeParamHandle_NoLock(paramHandle); + // Don't delete paramHandle because they're normally owned by Module subclasses + } + std::vector cables = internal->cables; + for (Cable* cable : cables) { + removeCable_NoLock(cable); + delete cable; + } + std::vector modules = internal->modules; + for (Module* module : modules) { + removeModule_NoLock(module); + delete module; + } +} + + +void Engine::stepBlock(int frames) { + // Start timer before locking + double startTime = system::getTime(); + + ReadLock 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(this, module, false); + Engine_updateExpander(this, module, true); + } + + // Step individual frames + for (int i = 0; i < frames; i++) { + Engine_stepFrame(this); + } + + internal->block++; + + // 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; + } +} + + +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; + WriteLock 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); + } +} + + +void Engine::setSuggestedSampleRate(float suggestedSampleRate) { +} + + +float Engine::getSampleTime() { + return internal->sampleTime; +} + + +void Engine::yieldWorkers() { +} + + +int64_t Engine::getBlock() { + return internal->block; +} + + +int64_t Engine::getFrame() { + return internal->frame; +} + + +void Engine::setFrame(int64_t frame) { + internal->frame = frame; +} + + +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() { + return internal->meterLastAverage; +} + + +double Engine::getMeterMax() { + return internal->meterLastMax; +} + + +size_t Engine::getNumModules() { + return internal->modules.size(); +} + + +size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) { + ReadLock lock(internal->mutex); + size_t i = 0; + for (Module* m : internal->modules) { + if (i >= len) + break; + moduleIds[i] = m->id; + i++; + } + return i; +} + + +std::vector Engine::getModuleIds() { + ReadLock lock(internal->mutex); + std::vector moduleIds; + moduleIds.reserve(internal->modules.size()); + for (Module* m : internal->modules) { + moduleIds.push_back(m->id); + } + return moduleIds; +} + + +void Engine::addModule(Module* module) { + WriteLock lock(internal->mutex); + DISTRHO_SAFE_ASSERT_RETURN(module,); + // 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(),); + // 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); + internal->modulesCache[module->id] = module; + // Dispatch AddEvent + Module::AddEvent eAdd; + module->onAdd(eAdd); + // Update ParamHandles' module pointers + for (ParamHandle* paramHandle : internal->paramHandles) { + if (paramHandle->moduleId == module->id) + paramHandle->module = module; + } +} + + +void Engine::removeModule(Module* module) { + WriteLock lock(internal->mutex); + removeModule_NoLock(module); +} + + +void Engine::removeModule_NoLock(Module* module) { + DISTRHO_SAFE_ASSERT_RETURN(module,); + // Check that the module actually exists + auto it = std::find(internal->modules.begin(), internal->modules.end(), module); + DISTRHO_SAFE_ASSERT_RETURN(it != internal->modules.end(),); + // 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; + } + } + // 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; +} + + +bool Engine::hasModule(Module* module) { + ReadLock 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(); +} + + +Module* Engine::getModule(int64_t moduleId) { + ReadLock lock(internal->mutex); + auto it = internal->modulesCache.find(moduleId); + if (it == internal->modulesCache.end()) + return NULL; + return it->second; +} + + +void Engine::resetModule(Module* module) { + WriteLock lock(internal->mutex); + DISTRHO_SAFE_ASSERT_RETURN(module,); + + Module::ResetEvent eReset; + module->onReset(eReset); +} + + +void Engine::randomizeModule(Module* module) { + WriteLock 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; + + WriteLock 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) { + ReadLock lock(internal->mutex); + return module->toJson(); +} + + +void Engine::moduleFromJson(Module* module, json_t* rootJ) { + WriteLock lock(internal->mutex); + module->fromJson(rootJ); +} + + +void Engine::prepareSave() { + ReadLock lock(internal->mutex); + for (Module* module : internal->modules) { + Module::SaveEvent e; + module->onSave(e); + } +} + + +size_t Engine::getNumCables() { + return internal->cables.size(); +} + + +size_t Engine::getCableIds(int64_t* cableIds, size_t len) { + ReadLock 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 Engine::getCableIds() { + ReadLock lock(internal->mutex); + std::vector cableIds; + cableIds.reserve(internal->cables.size()); + for (Cable* c : internal->cables) { + cableIds.push_back(c->id); + } + return cableIds; +} + + +void Engine::addCable(Cable* cable) { + WriteLock 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; + 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) { + WriteLock 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 + 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) { + ReadLock 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) { + ReadLock 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; + } + module->params[paramId].value = value; +} + + +float Engine::getParamValue(Module* module, int paramId) { + return module->params[paramId].value; +} + + +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].value = 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].value; +} + + +void Engine::addParamHandle(ParamHandle* paramHandle) { + WriteLock 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) { + WriteLock 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) { + ReadLock lock(internal->mutex); + 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) { + ReadLock lock(internal->mutex); + // 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 + // TODO Maybe call getParamHandle_NoLock()? + ParamHandle* oldParamHandle = getParamHandle(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) { + // TODO Maybe call getModule_NoLock()? + paramHandle->module = getModule(paramHandle->moduleId); + } + + Engine_refreshParamHandleCache(this); +} + + +json_t* Engine::toJson() { + ReadLock 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); + } + 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* module = model->createModule(); + DISTRHO_SAFE_ASSERT_RETURN(module,); + + 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()); + 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() { +} + + +} // namespace engine +} // namespace rack