Signed-off-by: falkTX <falktx@falktx.com>tags/22.02
| @@ -46,27 +46,12 @@ struct Cable; | |||
| struct Port { | |||
| /** Voltage of the port. */ | |||
| /** NOTE Purposefully renamed in Cardinal as a way to catch plugins using it directly. */ | |||
| union { | |||
| /** Unstable API. Use getVoltage() and setVoltage() instead. */ | |||
| float cvoltages[PORT_MAX_CHANNELS] = {}; | |||
| float voltages[PORT_MAX_CHANNELS] = {}; | |||
| /** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */ | |||
| float cvalue; | |||
| float value; | |||
| }; | |||
| /** Special trickery for backwards compatibility with plugins using DEPRECATED APIs */ | |||
| struct BackwardsCompatPortValue { | |||
| Port* const port; | |||
| BackwardsCompatPortValue(Port* p) : port(p) {} | |||
| void operator=(float value) { port->setVoltage(value); } | |||
| void operator-=(float value) { port->setVoltage(port->cvalue - value); } | |||
| void operator+=(float value) { port->setVoltage(port->cvalue + value); } | |||
| void operator*=(float value) { port->setVoltage(port->cvalue * value); } | |||
| void operator/=(float value) { port->setVoltage(port->cvalue / value); } | |||
| operator float() const { return port->cvalue; } | |||
| } value; | |||
| Port() : value(this) {} | |||
| union { | |||
| /** Number of polyphonic channels. | |||
| DEPRECATED. Unstable API. Use set/getChannels() instead. | |||
| @@ -87,24 +72,19 @@ struct Port { | |||
| OUTPUT, | |||
| }; | |||
| /** Cables connected to this output port. */ | |||
| /** List of cables connected to this port (if output type). */ | |||
| std::list<Cable*> cables; | |||
| /** Step-through the cables. | |||
| Called whenever voltage changes, required for zero latency operation. */ | |||
| void stepCables(); | |||
| /** Sets the voltage of the given channel. */ | |||
| void setVoltage(float voltage, int channel = 0) { | |||
| cvoltages[channel] = voltage; | |||
| stepCables(); | |||
| voltages[channel] = voltage; | |||
| } | |||
| /** Returns the voltage of the given channel. | |||
| Because of proper bookkeeping, all channels higher than the input port's number of channels should be 0V. | |||
| */ | |||
| float getVoltage(int channel = 0) { | |||
| return cvoltages[channel]; | |||
| return voltages[channel]; | |||
| } | |||
| /** Returns the given channel's voltage if the port is polyphonic, otherwise returns the first voltage (channel 0). */ | |||
| @@ -124,15 +104,14 @@ struct Port { | |||
| /** Returns a pointer to the array of voltages beginning with firstChannel. | |||
| The pointer can be used for reading and writing. | |||
| */ | |||
| // TODO convert to const float* for zero-latency cable stuff and fix all plugins after | |||
| float* getVoltages(int firstChannel = 0) { | |||
| return &cvoltages[firstChannel]; | |||
| return &voltages[firstChannel]; | |||
| } | |||
| /** Copies the port's voltages to an array of size at least `channels`. */ | |||
| void readVoltages(float* v) { | |||
| for (int c = 0; c < channels; c++) { | |||
| v[c] = cvoltages[c]; | |||
| v[c] = voltages[c]; | |||
| } | |||
| } | |||
| @@ -141,24 +120,22 @@ struct Port { | |||
| */ | |||
| void writeVoltages(const float* v) { | |||
| for (int c = 0; c < channels; c++) { | |||
| cvoltages[c] = v[c]; | |||
| voltages[c] = v[c]; | |||
| } | |||
| stepCables(); | |||
| } | |||
| /** Sets all voltages to 0. */ | |||
| void clearVoltages() { | |||
| for (int c = 0; c < channels; c++) { | |||
| cvoltages[c] = 0.f; | |||
| voltages[c] = 0.f; | |||
| } | |||
| stepCables(); | |||
| } | |||
| /** Returns the sum of all voltages. */ | |||
| float getVoltageSum() { | |||
| float sum = 0.f; | |||
| for (int c = 0; c < channels; c++) { | |||
| sum += cvoltages[c]; | |||
| sum += voltages[c]; | |||
| } | |||
| return sum; | |||
| } | |||
| @@ -171,12 +148,12 @@ struct Port { | |||
| return 0.f; | |||
| } | |||
| else if (channels == 1) { | |||
| return std::fabs(cvoltages[0]); | |||
| return std::fabs(voltages[0]); | |||
| } | |||
| else { | |||
| float sum = 0.f; | |||
| for (int c = 0; c < channels; c++) { | |||
| sum += std::pow(cvoltages[c], 2); | |||
| sum += std::pow(voltages[c], 2); | |||
| } | |||
| return std::sqrt(sum); | |||
| } | |||
| @@ -184,7 +161,7 @@ struct Port { | |||
| template <typename T> | |||
| T getVoltageSimd(int firstChannel) { | |||
| return T::load(&cvoltages[firstChannel]); | |||
| return T::load(&voltages[firstChannel]); | |||
| } | |||
| template <typename T> | |||
| @@ -204,8 +181,7 @@ struct Port { | |||
| template <typename T> | |||
| void setVoltageSimd(T voltage, int firstChannel) { | |||
| voltage.store(&cvoltages[firstChannel]); | |||
| stepCables(); | |||
| voltage.store(&voltages[firstChannel]); | |||
| } | |||
| /** Sets the number of polyphony channels. | |||
| @@ -220,7 +196,7 @@ struct Port { | |||
| } | |||
| // Set higher channel voltages to 0 | |||
| for (int c = channels; c < this->channels; c++) { | |||
| cvoltages[c] = 0.f; | |||
| voltages[c] = 0.f; | |||
| } | |||
| // Don't allow caller to set port as disconnected | |||
| if (channels == 0) { | |||
| @@ -0,0 +1,31 @@ | |||
| /* | |||
| * 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. | |||
| */ | |||
| #pragma once | |||
| #include <engine/Module.hpp> | |||
| namespace rack { | |||
| namespace engine { | |||
| struct TerminalModule : Module { | |||
| virtual void processTerminalInput(const ProcessArgs& args) = 0; | |||
| virtual void processTerminalOutput(const ProcessArgs& args) = 0; | |||
| }; | |||
| } | |||
| } | |||
| @@ -1 +1 @@ | |||
| Subproject commit 988c2372a95d163b71d04b217080e612b767c539 | |||
| Subproject commit e55fcd2e1d7c0fef69d4919baac6f791172c89ca | |||
| @@ -24,7 +24,7 @@ | |||
| USE_NAMESPACE_DISTRHO; | |||
| template<int numIO> | |||
| struct HostAudio : Module { | |||
| struct HostAudio : TerminalModule { | |||
| CardinalPluginContext* const pcontext; | |||
| const int numParams; | |||
| const int numInputs; | |||
| @@ -74,45 +74,65 @@ struct HostAudio : Module { | |||
| dcFilters[i].setCutoffFreq(10.f * e.sampleTime); | |||
| } | |||
| void process(const ProcessArgs&) override | |||
| void processTerminalInput(const ProcessArgs&) override | |||
| { | |||
| const float* const* const dataIns = pcontext->dataIns; | |||
| float** const dataOuts = pcontext->dataOuts; | |||
| const int blockFrames = pcontext->engine->getBlockFrames(); | |||
| const int64_t blockFrame = pcontext->engine->getBlockFrame(); | |||
| // only checked on input | |||
| if (lastBlockFrame != blockFrame) | |||
| { | |||
| dataFrame = 0; | |||
| lastBlockFrame = blockFrame; | |||
| } | |||
| const int k = dataFrame++; | |||
| // only incremented on output | |||
| const int k = dataFrame; | |||
| DISTRHO_SAFE_ASSERT_INT2_RETURN(k < blockFrames, k, blockFrames,); | |||
| const float gain = numParams != 0 ? std::pow(params[0].getValue(), 2.f) : 1.0f; | |||
| // from host into cardinal, shows as output plug | |||
| if (dataIns != nullptr) | |||
| if (isBypassed()) | |||
| { | |||
| for (int i=0; i<numOutputs; ++i) | |||
| outputs[i].setVoltage(0.0f); | |||
| } | |||
| else if (dataIns != nullptr) | |||
| { | |||
| for (int i=0; i<numOutputs; ++i) | |||
| outputs[i].setVoltage(dataIns[i][k] * 10.0f); | |||
| } | |||
| } | |||
| void processTerminalOutput(const ProcessArgs&) override | |||
| { | |||
| float** const dataOuts = pcontext->dataOuts; | |||
| const int blockFrames = pcontext->engine->getBlockFrames(); | |||
| // only incremented on output | |||
| const int k = dataFrame++; | |||
| DISTRHO_SAFE_ASSERT_INT2_RETURN(k < blockFrames, k, blockFrames,); | |||
| const float gain = numParams != 0 ? std::pow(params[0].getValue(), 2.f) : 1.0f; | |||
| // from cardinal into host, shows as input plug | |||
| for (int i=0; i<numInputs; ++i) | |||
| if (! isBypassed()) | |||
| { | |||
| float v = inputs[i].getVoltageSum() * 0.1f; | |||
| // Apply DC filter | |||
| if (dcFilterEnabled) | |||
| for (int i=0; i<numInputs; ++i) | |||
| { | |||
| dcFilters[i].process(v); | |||
| v = dcFilters[i].highpass(); | |||
| } | |||
| float v = inputs[i].getVoltageSum() * 0.1f; | |||
| dataOuts[i][k] += clamp(v * gain, -1.0f, 1.0f); | |||
| // Apply DC filter | |||
| if (dcFilterEnabled) | |||
| { | |||
| dcFilters[i].process(v); | |||
| v = dcFilters[i].highpass(); | |||
| } | |||
| dataOuts[i][k] += clamp(v * gain, -1.0f, 1.0f); | |||
| } | |||
| } | |||
| if (numInputs == 2) | |||
| @@ -18,6 +18,7 @@ | |||
| #pragma once | |||
| #include "rack.hpp" | |||
| #include "engine/TerminalModule.hpp" | |||
| #ifdef NDEBUG | |||
| # undef DEBUG | |||
| @@ -35,6 +35,7 @@ | |||
| #include <pmmintrin.h> | |||
| #include <engine/Engine.hpp> | |||
| #include <engine/TerminalModule.hpp> | |||
| #include <settings.hpp> | |||
| #include <system.hpp> | |||
| #include <random.hpp> | |||
| @@ -49,12 +50,19 @@ | |||
| #include "DistrhoUtils.hpp" | |||
| // known terminal modules | |||
| extern rack::plugin::Model* modelHostAudio2; | |||
| extern rack::plugin::Model* modelHostAudio8; | |||
| namespace rack { | |||
| namespace engine { | |||
| struct Engine::Internal { | |||
| std::vector<Module*> modules; | |||
| std::vector<TerminalModule*> terminalModules; | |||
| std::vector<Cable*> cables; | |||
| std::set<ParamHandle*> paramHandles; | |||
| @@ -127,24 +135,64 @@ static void Cable_step(Cable* that) { | |||
| const int channels = output->channels; | |||
| // Copy all voltages from output to input | |||
| for (int c = 0; c < channels; c++) { | |||
| float v = output->cvoltages[c]; | |||
| float v = output->voltages[c]; | |||
| // Set 0V if infinite or NaN | |||
| if (!std::isfinite(v)) | |||
| v = 0.f; | |||
| input->cvoltages[c] = v; | |||
| input->voltages[c] = v; | |||
| } | |||
| // Set higher channel voltages to 0 | |||
| for (int c = channels; c < input->channels; c++) { | |||
| input->cvoltages[c] = 0.f; | |||
| input->voltages[c] = 0.f; | |||
| } | |||
| input->channels = channels; | |||
| } | |||
| void Port::stepCables() | |||
| { | |||
| for (Cable* cable : cables) | |||
| Cable_step(cable); | |||
| 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); | |||
| } | |||
| } | |||
| } | |||
| @@ -174,14 +222,6 @@ static void Engine_stepFrame(Engine* that) { | |||
| } | |||
| } | |||
| /* NOTE this is likely not needed in Cardinal, but needs testing. | |||
| * Leaving it as comment in case we need it bring it back | |||
| // Step cables | |||
| for (Cable* cable : internal->cables) { | |||
| Cable_step(cable); | |||
| } | |||
| */ | |||
| // Flip messages for each module | |||
| for (Module* module : internal->modules) { | |||
| if (module->leftExpander.messageFlipRequested) { | |||
| @@ -200,16 +240,25 @@ static void Engine_stepFrame(Engine* that) { | |||
| processArgs.sampleTime = internal->sampleTime; | |||
| processArgs.frame = internal->frame; | |||
| // Step each module | |||
| // 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); | |||
| // FIXME remove this section below after all modules can use zero-latency cable stuff | |||
| 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; | |||
| } | |||
| @@ -217,7 +266,7 @@ static void Engine_stepFrame(Engine* that) { | |||
| static void Port_setDisconnected(Port* that) { | |||
| that->channels = 0; | |||
| for (int c = 0; c < PORT_MAX_CHANNELS; c++) { | |||
| that->cvoltages[c] = 0.f; | |||
| that->voltages[c] = 0.f; | |||
| } | |||
| } | |||
| @@ -240,6 +289,14 @@ static void Engine_updateConnected(Engine* that) { | |||
| disconnectedPorts.insert(&output); | |||
| } | |||
| } | |||
| for (TerminalModule* terminalModule : that->internal->terminalModules) { | |||
| for (Input& input : terminalModule->inputs) { | |||
| disconnectedPorts.insert(&input); | |||
| } | |||
| for (Output& output : terminalModule->outputs) { | |||
| disconnectedPorts.insert(&output); | |||
| } | |||
| } | |||
| for (Cable* cable : that->internal->cables) { | |||
| // Connect input | |||
| Input& input = cable->inputModule->inputs[cable->inputId]; | |||
| @@ -287,6 +344,7 @@ Engine::~Engine() { | |||
| // 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()); | |||
| @@ -320,6 +378,11 @@ void Engine::clear_NoLock() { | |||
| removeModule_NoLock(module); | |||
| delete module; | |||
| } | |||
| std::vector<TerminalModule*> terminalModules = internal->terminalModules; | |||
| for (TerminalModule* terminalModule : terminalModules) { | |||
| removeModule_NoLock(terminalModule); | |||
| delete terminalModule; | |||
| } | |||
| } | |||
| @@ -404,6 +467,9 @@ void Engine::setSampleRate(float sampleRate) { | |||
| for (Module* module : internal->modules) { | |||
| module->onSampleRateChange(e); | |||
| } | |||
| for (TerminalModule* terminalModule : internal->terminalModules) { | |||
| terminalModule->onSampleRateChange(e); | |||
| } | |||
| } | |||
| @@ -474,7 +540,7 @@ double Engine::getMeterMax() { | |||
| size_t Engine::getNumModules() { | |||
| return internal->modules.size(); | |||
| return internal->modules.size() + internal->terminalModules.size(); | |||
| } | |||
| @@ -484,8 +550,12 @@ size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) { | |||
| 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; | |||
| } | |||
| @@ -494,27 +564,43 @@ size_t Engine::getModuleIds(int64_t* moduleIds, size_t len) { | |||
| 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 (model == modelHostAudio2 || model == modelHostAudio8) | |||
| return static_cast<TerminalModule*>(module); | |||
| return nullptr; | |||
| } | |||
| void Engine::addModule(Module* module) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| DISTRHO_SAFE_ASSERT_RETURN(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); | |||
| 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; | |||
| @@ -538,11 +624,7 @@ void Engine::removeModule(Module* 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(),); | |||
| 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,); | |||
| @@ -575,14 +657,31 @@ void Engine::removeModule_NoLock(Module* module) { | |||
| 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); | |||
| } | |||
| } | |||
| @@ -590,7 +689,8 @@ 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); | |||
| 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(); | |||
| } | |||
| @@ -678,6 +778,10 @@ void Engine::prepareSave() { | |||
| Module::SaveEvent e; | |||
| module->onSave(e); | |||
| } | |||
| for (TerminalModule* terminalModule : internal->terminalModules) { | |||
| Module::SaveEvent e; | |||
| terminalModule->onSave(e); | |||
| } | |||
| } | |||
| @@ -957,6 +1061,10 @@ json_t* Engine::toJson() { | |||
| 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 | |||
| @@ -1,5 +1,5 @@ | |||
| --- ../Rack/src/engine/Engine.cpp 2022-01-15 14:44:46.395281005 +0000 | |||
| +++ Engine.cpp 2022-01-23 17:13:03.200930905 +0000 | |||
| --- ../Rack/src/engine/Engine.cpp 2022-02-05 22:30:09.253393116 +0000 | |||
| +++ Engine.cpp 2022-02-08 02:48:26.045085405 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| @@ -31,7 +31,11 @@ | |||
| #include <algorithm> | |||
| #include <set> | |||
| #include <thread> | |||
| @@ -11,178 +38,25 @@ | |||
| @@ -8,181 +35,36 @@ | |||
| #include <pmmintrin.h> | |||
| #include <engine/Engine.hpp> | |||
| +#include <engine/TerminalModule.hpp> | |||
| #include <settings.hpp> | |||
| #include <system.hpp> | |||
| #include <random.hpp> | |||
| @@ -40,17 +44,15 @@ | |||
| #include <plugin.hpp> | |||
| #include <mutex.hpp> | |||
| +#include <helpers.hpp> | |||
| + | |||
| +#ifdef NDEBUG | |||
| +# undef DEBUG | |||
| +#endif | |||
| +#include "DistrhoUtils.hpp" | |||
| namespace rack { | |||
| namespace engine { | |||
| -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 | |||
| @@ -107,7 +109,8 @@ | |||
| - 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) { | |||
| @@ -126,8 +129,11 @@ | |||
| - } | |||
| - } | |||
| -}; | |||
| - | |||
| - | |||
| +// known terminal modules | |||
| +extern rack::plugin::Model* modelHostAudio2; | |||
| +extern rack::plugin::Model* modelHostAudio8; | |||
| -/** 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. | |||
| @@ -164,7 +170,7 @@ | |||
| - } | |||
| - return; | |||
| - } | |||
| - | |||
| - // Spin until the last thread begins waiting | |||
| - while (!yielded.load(std::memory_order_relaxed)) { | |||
| - if (step.load(std::memory_order_relaxed) != s) | |||
| @@ -206,17 +212,36 @@ | |||
| - | |||
| - 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; | |||
| @@ -217,22 +91,6 @@ | |||
| @@ -199,6 +81,7 @@ | |||
| double blockTime = 0.0; | |||
| int blockFrames = 0; | |||
| +#ifndef HEADLESS | |||
| // Meter | |||
| int meterCount = 0; | |||
| double meterTotal = 0.0; | |||
| @@ -206,6 +89,7 @@ | |||
| double meterLastTime = -INFINITY; | |||
| double meterLastAverage = 0.0; | |||
| double meterLastMax = 0.0; | |||
| +#endif | |||
| // Parameter smoothing | |||
| Module* smoothModule = NULL; | |||
| @@ -217,22 +101,6 @@ | |||
| Readers lock when using the engine's state. | |||
| */ | |||
| SharedMutex mutex; | |||
| @@ -239,7 +264,7 @@ | |||
| }; | |||
| @@ -260,71 +118,6 @@ | |||
| @@ -260,76 +128,11 @@ | |||
| } | |||
| @@ -311,22 +336,82 @@ | |||
| static void Cable_step(Cable* that) { | |||
| Output* output = &that->outputModule->outputs[that->outputId]; | |||
| Input* input = &that->inputModule->inputs[that->inputId]; | |||
| @@ -373,12 +166,12 @@ | |||
| } | |||
| // 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 +149,53 @@ | |||
| } | |||
| // Step cables | |||
| - for (Cable* cable : that->internal->cables) { | |||
| + for (Cable* cable : internal->cables) { | |||
| Cable_step(cable); | |||
| +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 +222,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 +182,18 @@ | |||
| @@ -389,13 +234,32 @@ | |||
| } | |||
| } | |||
| @@ -341,9 +426,23 @@ | |||
| + processArgs.sampleTime = internal->sampleTime; | |||
| + processArgs.frame = internal->frame; | |||
| + | |||
| + // Step each module | |||
| + // 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); | |||
| + } | |||
| + } | |||
| + | |||
| + // Process terminal outputs last | |||
| + for (TerminalModule* terminalModule : internal->terminalModules) { | |||
| + TerminalModule__doProcess(terminalModule, processArgs, false); | |||
| + } | |||
| - internal->frame++; | |||
| @@ -351,7 +450,30 @@ | |||
| } | |||
| @@ -460,37 +258,22 @@ | |||
| @@ -425,6 +289,14 @@ | |||
| disconnectedPorts.insert(&output); | |||
| } | |||
| } | |||
| + for (TerminalModule* terminalModule : that->internal->terminalModules) { | |||
| + for (Input& input : terminalModule->inputs) { | |||
| + disconnectedPorts.insert(&input); | |||
| + } | |||
| + for (Output& output : terminalModule->outputs) { | |||
| + disconnectedPorts.insert(&output); | |||
| + } | |||
| + } | |||
| for (Cable* cable : that->internal->cables) { | |||
| // Connect input | |||
| Input& input = cable->inputModule->inputs[cable->inputId]; | |||
| @@ -442,6 +314,7 @@ | |||
| // Disconnect ports that have no cable | |||
| for (Port* port : disconnectedPorts) { | |||
| Port_setDisconnected(port); | |||
| + DISTRHO_SAFE_ASSERT(port->cables.empty()); | |||
| } | |||
| } | |||
| @@ -460,37 +333,23 @@ | |||
| Engine::Engine() { | |||
| internal = new Internal; | |||
| @@ -388,6 +510,7 @@ | |||
| - 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()); | |||
| @@ -396,9 +519,23 @@ | |||
| delete internal; | |||
| } | |||
| @@ -526,11 +309,8 @@ | |||
| @@ -519,18 +378,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); | |||
| @@ -408,7 +545,7 @@ | |||
| random::init(); | |||
| internal->blockFrame = internal->frame; | |||
| @@ -543,16 +323,11 @@ | |||
| @@ -543,18 +406,14 @@ | |||
| Engine_updateExpander_NoLock(this, module, true); | |||
| } | |||
| @@ -424,14 +561,18 @@ | |||
| - | |||
| internal->block++; | |||
| +#ifndef HEADLESS | |||
| // Stop timer | |||
| @@ -572,47 +347,19 @@ | |||
| double endTime = system::getTime(); | |||
| double meter = (endTime - startTime) / (frames * internal->sampleTime); | |||
| @@ -572,47 +431,20 @@ | |||
| internal->meterTotal = 0.0; | |||
| internal->meterMax = 0.0; | |||
| } | |||
| - | |||
| - // Reset MXCSR back to original value | |||
| - _mm_setcsr(csr); | |||
| +#endif | |||
| } | |||
| @@ -474,7 +615,14 @@ | |||
| } | |||
| @@ -639,16 +386,6 @@ | |||
| @@ -635,20 +467,13 @@ | |||
| for (Module* module : internal->modules) { | |||
| module->onSampleRateChange(e); | |||
| } | |||
| + for (TerminalModule* terminalModule : internal->terminalModules) { | |||
| + terminalModule->onSampleRateChange(e); | |||
| + } | |||
| } | |||
| void Engine::setSuggestedSampleRate(float suggestedSampleRate) { | |||
| @@ -491,7 +639,7 @@ | |||
| } | |||
| @@ -658,7 +395,6 @@ | |||
| @@ -658,7 +483,6 @@ | |||
| void Engine::yieldWorkers() { | |||
| @@ -499,29 +647,106 @@ | |||
| } | |||
| @@ -738,10 +474,10 @@ | |||
| @@ -698,17 +522,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 +550,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 +564,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 (model == modelHostAudio2 || model == modelHostAudio8) | |||
| + 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,); | |||
| + 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 | |||
| @@ -773,10 +509,14 @@ | |||
| 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 +624,11 @@ | |||
| } | |||
| void Engine::removeModule_NoLock(Module* module) { | |||
| -void Engine::removeModule_NoLock(Module* module) { | |||
| - assert(module); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(module,); | |||
| // Check that the module actually exists | |||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| - // Check that the module actually exists | |||
| - auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| - assert(it != internal->modules.end()); | |||
| + DISTRHO_SAFE_ASSERT_RETURN(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,); | |||
| @@ -529,7 +754,7 @@ | |||
| // Dispatch RemoveEvent | |||
| Module::RemoveEvent eRemove; | |||
| module->onRemove(eRemove); | |||
| @@ -785,18 +525,14 @@ | |||
| @@ -785,18 +637,14 @@ | |||
| if (paramHandle->moduleId == module->id) | |||
| paramHandle->module = NULL; | |||
| } | |||
| @@ -550,7 +775,52 @@ | |||
| } | |||
| // Update expanders of other modules | |||
| for (Module* m : internal->modules) { | |||
| @@ -844,7 +580,7 @@ | |||
| @@ -809,14 +657,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 +689,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 +710,7 @@ | |||
| void Engine::resetModule(Module* module) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| @@ -559,7 +829,7 @@ | |||
| Module::ResetEvent eReset; | |||
| module->onReset(eReset); | |||
| @@ -853,7 +589,7 @@ | |||
| @@ -853,7 +719,7 @@ | |||
| void Engine::randomizeModule(Module* module) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| @@ -568,7 +838,7 @@ | |||
| Module::RandomizeEvent eRandomize; | |||
| module->onRandomize(eRandomize); | |||
| @@ -861,7 +597,7 @@ | |||
| @@ -861,7 +727,7 @@ | |||
| void Engine::bypassModule(Module* module, bool bypassed) { | |||
| @@ -577,7 +847,18 @@ | |||
| if (module->isBypassed() == bypassed) | |||
| return; | |||
| @@ -946,16 +682,16 @@ | |||
| @@ -912,6 +778,10 @@ | |||
| Module::SaveEvent e; | |||
| module->onSave(e); | |||
| } | |||
| + for (TerminalModule* terminalModule : internal->terminalModules) { | |||
| + Module::SaveEvent e; | |||
| + terminalModule->onSave(e); | |||
| + } | |||
| } | |||
| @@ -946,16 +816,16 @@ | |||
| void Engine::addCable(Cable* cable) { | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| @@ -599,7 +880,16 @@ | |||
| // 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) | |||
| @@ -996,10 +732,10 @@ | |||
| @@ -969,6 +839,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 +868,12 @@ | |||
| void Engine::removeCable_NoLock(Cable* cable) { | |||
| @@ -609,10 +899,12 @@ | |||
| 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 +821,11 @@ | |||
| @@ -1085,11 +959,11 @@ | |||
| std::lock_guard<SharedMutex> lock(internal->mutex); | |||
| // New ParamHandles must be blank. | |||
| // This means we don't have to refresh the cache. | |||
| @@ -626,7 +918,7 @@ | |||
| // Add it | |||
| internal->paramHandles.insert(paramHandle); | |||
| @@ -1106,7 +842,7 @@ | |||
| @@ -1106,7 +980,7 @@ | |||
| void Engine::removeParamHandle_NoLock(ParamHandle* paramHandle) { | |||
| // Check that the ParamHandle is already added | |||
| auto it = internal->paramHandles.find(paramHandle); | |||
| @@ -635,7 +927,7 @@ | |||
| // Remove it | |||
| paramHandle->module = NULL; | |||
| @@ -1143,7 +879,7 @@ | |||
| @@ -1143,7 +1017,7 @@ | |||
| void Engine::updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite) { | |||
| // Check that it exists | |||
| auto it = internal->paramHandles.find(paramHandle); | |||
| @@ -644,7 +936,18 @@ | |||
| // Set IDs | |||
| paramHandle->moduleId = moduleId; | |||
| @@ -1197,11 +933,6 @@ | |||
| @@ -1187,6 +1061,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 +1075,6 @@ | |||
| } | |||
| json_object_set_new(rootJ, "cables", cablesJ); | |||
| @@ -656,7 +959,7 @@ | |||
| return rootJ; | |||
| } | |||
| @@ -1225,14 +956,20 @@ | |||
| @@ -1225,14 +1098,20 @@ | |||
| } | |||
| catch (Exception& e) { | |||
| WARN("Cannot load model: %s", e.what()); | |||
| @@ -681,7 +984,7 @@ | |||
| try { | |||
| // This doesn't need a lock because the Module is not added to the Engine yet. | |||
| @@ -1248,7 +985,8 @@ | |||
| @@ -1248,7 +1127,8 @@ | |||
| } | |||
| catch (Exception& e) { | |||
| WARN("Cannot load module: %s", e.what()); | |||
| @@ -691,7 +994,7 @@ | |||
| delete module; | |||
| continue; | |||
| } | |||
| @@ -1285,67 +1023,10 @@ | |||
| @@ -1285,67 +1165,10 @@ | |||
| continue; | |||
| } | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| --- ../Rack/src/app/MenuBar.cpp 2022-01-15 14:44:46.391280963 +0000 | |||
| +++ MenuBar.cpp 2022-01-24 11:25:15.507061204 +0000 | |||
| --- ../Rack/src/app/MenuBar.cpp 2022-02-05 22:30:09.233392896 +0000 | |||
| +++ MenuBar.cpp 2022-02-05 18:08:00.272028714 +0000 | |||
| @@ -1,8 +1,33 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| @@ -48,7 +48,7 @@ | |||
| namespace rack { | |||
| namespace app { | |||
| @@ -48,79 +78,75 @@ | |||
| @@ -48,79 +78,79 @@ | |||
| }; | |||
| @@ -103,25 +103,34 @@ | |||
| - | |||
| menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() { | |||
| - APP->patch->saveDialog(); | |||
| - })); | |||
| + // NOTE: will do nothing if path is empty, intentionally | |||
| + patchUtils::saveDialog(APP->patch->path); | |||
| + }, APP->patch->path.empty())); | |||
| + | |||
| - menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| - APP->patch->saveAsDialog(); | |||
| + menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| + patchUtils::saveAsDialog(); | |||
| })); | |||
| - menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() { | |||
| - APP->patch->saveAsDialog(); | |||
| - })); | |||
| - menu->addChild(createMenuItem("Save a copy", "", []() { | |||
| - APP->patch->saveAsDialog(false); | |||
| + menu->addChild(createMenuItem("Export uncompressed json...", "", []() { | |||
| + patchUtils::saveAsDialogUncompressed(); | |||
| })); | |||
| - menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { | |||
| - APP->patch->revertDialog(); | |||
| - }, APP->patch->path == "")); | |||
| +#ifdef HAVE_LIBLO | |||
| + if (patchUtils::isRemoteConnected()) { | |||
| + menu->addChild(createMenuItem("Deploy to MOD", "F7", []() { | |||
| + patchUtils::deployToRemote(); | |||
| + })); | |||
| - menu->addChild(createMenuItem("Save a copy", "", []() { | |||
| - APP->patch->saveAsDialog(false); | |||
| - menu->addChild(createMenuItem("Overwrite template", "", []() { | |||
| - APP->patch->saveTemplateDialog(); | |||
| - })); | |||
| + const bool autoDeploy = patchUtils::isRemoteAutoDeployed(); | |||
| + menu->addChild(createCheckMenuItem("Auto deploy to MOD", "", | |||
| @@ -129,19 +138,13 @@ | |||
| + [=]() {patchUtils::setRemoteAutoDeploy(!autoDeploy);} | |||
| + )); | |||
| + } else { | |||
| + menu->addChild(createMenuItem("Connect to MOD", "", [this]() { | |||
| + menu->addChild(createMenuItem("Connect to MOD", "", []() { | |||
| + patchUtils::connectToRemote(); | |||
| + })); | |||
| + } | |||
| +#endif | |||
| menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { | |||
| - APP->patch->revertDialog(); | |||
| - }, APP->patch->path == "")); | |||
| - | |||
| - menu->addChild(createMenuItem("Overwrite template", "", []() { | |||
| - APP->patch->saveTemplateDialog(); | |||
| - })); | |||
| + | |||
| + menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { | |||
| + patchUtils::revertDialog(); | |||
| + }, APP->patch->path.empty())); | |||
| @@ -167,7 +170,7 @@ | |||
| } | |||
| }; | |||
| @@ -166,7 +192,7 @@ | |||
| @@ -166,7 +196,7 @@ | |||
| menu->addChild(new ui::MenuSeparator); | |||
| @@ -176,7 +179,7 @@ | |||
| } | |||
| }; | |||
| @@ -256,7 +282,7 @@ | |||
| @@ -256,7 +286,7 @@ | |||
| return settings::cableTension; | |||
| } | |||
| float getDefaultValue() override { | |||
| @@ -185,7 +188,7 @@ | |||
| } | |||
| float getDisplayValue() override { | |||
| return getValue() * 100; | |||
| @@ -421,28 +447,9 @@ | |||
| @@ -421,28 +451,9 @@ | |||
| haloBrightnessSlider->box.size.x = 250.0; | |||
| menu->addChild(haloBrightnessSlider); | |||
| @@ -215,7 +218,7 @@ | |||
| static const std::vector<std::string> knobModeLabels = { | |||
| "Linear", | |||
| @@ -467,6 +474,21 @@ | |||
| @@ -467,6 +478,21 @@ | |||
| menu->addChild(knobScrollSensitivitySlider); | |||
| menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules)); | |||
| @@ -237,7 +240,7 @@ | |||
| } | |||
| }; | |||
| @@ -476,47 +498,6 @@ | |||
| @@ -476,47 +502,6 @@ | |||
| //////////////////// | |||
| @@ -285,7 +288,7 @@ | |||
| struct EngineButton : MenuButton { | |||
| void onAction(const ActionEvent& e) override { | |||
| ui::Menu* menu = createMenu(); | |||
| @@ -529,269 +510,6 @@ | |||
| @@ -529,269 +514,6 @@ | |||
| menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() { | |||
| settings::cpuMeter ^= true; | |||
| })); | |||
| @@ -555,7 +558,7 @@ | |||
| } | |||
| }; | |||
| @@ -802,63 +520,24 @@ | |||
| @@ -802,63 +524,23 @@ | |||
| struct HelpButton : MenuButton { | |||
| @@ -614,17 +617,17 @@ | |||
| - menu->addChild(createMenuLabel(APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION)); | |||
| - } | |||
| + menu->addChild(createMenuLabel(APP_EDITION + " " + APP_EDITION_NAME)); | |||
| - | |||
| - void step() override { | |||
| - notification->box.pos = math::Vec(0, 0); | |||
| - notification->visible = library::isAppUpdateAvailable(); | |||
| - MenuButton::step(); | |||
| + menu->addChild(createMenuLabel("Cardinal " + APP_EDITION + " " + CARDINAL_VERSION)); | |||
| + menu->addChild(createMenuLabel("Rack " + APP_VERSION + " Compatible")); | |||
| } | |||
| }; | |||
| @@ -908,7 +587,9 @@ | |||
| @@ -908,7 +590,9 @@ | |||
| struct MenuBar : widget::OpaqueWidget { | |||
| MeterLabel* meterLabel; | |||
| @@ -635,7 +638,7 @@ | |||
| const float margin = 5; | |||
| box.size.y = BND_WIDGET_HEIGHT + 2 * margin; | |||
| @@ -917,7 +598,7 @@ | |||
| @@ -917,7 +601,7 @@ | |||
| layout->spacing = math::Vec(0, 0); | |||
| addChild(layout); | |||
| @@ -644,7 +647,7 @@ | |||
| fileButton->text = "File"; | |||
| layout->addChild(fileButton); | |||
| @@ -933,10 +614,6 @@ | |||
| @@ -933,10 +617,6 @@ | |||
| engineButton->text = "Engine"; | |||
| layout->addChild(engineButton); | |||
| @@ -655,7 +658,7 @@ | |||
| HelpButton* helpButton = new HelpButton; | |||
| helpButton->text = "Help"; | |||
| layout->addChild(helpButton); | |||
| @@ -971,7 +648,11 @@ | |||
| @@ -971,7 +651,11 @@ | |||
| widget::Widget* createMenuBar() { | |||
| @@ -1,5 +1,5 @@ | |||
| --- ../Rack/src/app/Scene.cpp 2021-12-14 21:35:44.414568198 +0000 | |||
| +++ Scene.cpp 2022-01-26 18:47:48.006168325 +0000 | |||
| +++ Scene.cpp 2022-02-06 14:11:59.259830276 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| @@ -108,13 +108,13 @@ | |||
| + | |||
| + void onEnter(const EnterEvent& e) override { | |||
| + glfwSetCursor(nullptr, (GLFWcursor*)0x1); | |||
| + } | |||
| + | |||
| + void onLeave(const LeaveEvent& e) override { | |||
| + glfwSetCursor(nullptr, nullptr); | |||
| } | |||
| - void onDragStart(const DragStartEvent& e) override { | |||
| + void onLeave(const LeaveEvent& e) override { | |||
| + glfwSetCursor(nullptr, nullptr); | |||
| + } | |||
| + | |||
| + void onDragStart(const DragStartEvent&) override { | |||
| size = APP->window->getSize(); | |||
| } | |||
| @@ -260,7 +260,23 @@ | |||
| e.consume(this); | |||
| } | |||
| if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| @@ -232,10 +326,8 @@ | |||
| @@ -220,10 +314,14 @@ | |||
| APP->scene->rackScroll->setZoom(std::pow(2.f, zoom)); | |||
| e.consume(this); | |||
| } | |||
| - if ((e.keyName == "0") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| + if ((e.keyName == "0" || e.keyName == "1") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| APP->scene->rackScroll->setZoom(1.f); | |||
| e.consume(this); | |||
| } | |||
| + if (e.keyName == "2" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| + APP->scene->rackScroll->setZoom(2.f); | |||
| + e.consume(this); | |||
| + } | |||
| if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { | |||
| system::openBrowser("https://vcvrack.com/manual/"); | |||
| e.consume(this); | |||
| @@ -232,10 +330,8 @@ | |||
| settings::cpuMeter ^= true; | |||
| e.consume(this); | |||
| } | |||
| @@ -273,7 +289,7 @@ | |||
| e.consume(this); | |||
| } | |||
| @@ -326,13 +418,6 @@ | |||
| @@ -326,13 +422,6 @@ | |||
| // Key commands that can be overridden by children | |||
| if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | |||
| @@ -287,7 +303,7 @@ | |||
| if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| rack->pasteClipboardAction(); | |||
| e.consume(this); | |||
| @@ -351,7 +436,7 @@ | |||
| @@ -351,7 +440,7 @@ | |||
| std::string extension = system::getExtension(path); | |||
| if (extension == ".vcv") { | |||
| @@ -296,7 +312,7 @@ | |||
| e.consume(this); | |||
| return; | |||
| } | |||
| @@ -368,3 +453,73 @@ | |||
| @@ -368,3 +457,77 @@ | |||
| } // namespace app | |||
| } // namespace rack | |||
| @@ -306,6 +322,7 @@ | |||
| + | |||
| + | |||
| +bool connectToRemote() { | |||
| +#ifdef HAVE_LIBLO | |||
| + rack::app::Scene::Internal* const internal = APP->scene->internal; | |||
| + | |||
| + if (internal->oscServer == nullptr) { | |||
| @@ -321,6 +338,9 @@ | |||
| + lo_address_free(addr); | |||
| + | |||
| + return true; | |||
| +#else | |||
| + return false; | |||
| +#endif | |||
| +} | |||
| + | |||
| + | |||
| @@ -1,5 +1,5 @@ | |||
| --- ../Rack/src/common.cpp 2021-11-23 19:57:23.719015894 +0000 | |||
| +++ common.cpp 2022-01-23 17:13:08.824652617 +0000 | |||
| +++ common.cpp 2022-01-31 13:24:14.558807713 +0000 | |||
| @@ -1,6 +1,38 @@ | |||
| +/* | |||
| + * DISTRHO Cardinal Plugin | |||
| @@ -1,4 +1,4 @@ | |||
| --- ../Rack/src/context.cpp 2022-01-15 14:44:46.391280963 +0000 | |||
| --- ../Rack/src/context.cpp 2022-02-05 22:30:09.253393116 +0000 | |||
| +++ context.cpp 2022-01-23 17:13:11.652514338 +0000 | |||
| @@ -1,3 +1,30 @@ | |||
| +/* | |||
| @@ -1,5 +1,5 @@ | |||
| --- ../Rack/src/plugin.cpp 2022-01-15 14:44:46.395281005 +0000 | |||
| +++ plugin.cpp 2022-01-24 20:38:11.436099651 +0000 | |||
| --- ../Rack/src/plugin.cpp 2022-02-05 22:30:09.265393248 +0000 | |||
| +++ plugin.cpp 2022-01-30 00:24:49.375329910 +0000 | |||
| @@ -1,308 +1,40 @@ | |||
| -#include <thread> | |||
| -#include <map> | |||
| @@ -337,7 +337,7 @@ | |||
| /** Given slug => fallback slug. | |||
| Correctly handles bidirectional fallbacks. | |||
| To request fallback slugs to be added to this list, open a GitHub issue. | |||
| @@ -352,6 +84,12 @@ | |||
| @@ -352,8 +84,19 @@ | |||
| */ | |||
| using PluginModuleSlug = std::tuple<std::string, std::string>; | |||
| static const std::map<PluginModuleSlug, PluginModuleSlug> moduleSlugFallbacks = { | |||
| @@ -345,12 +345,20 @@ | |||
| + {{"Core", "AudioInterface"}, {"Cardinal", "HostAudio8"}}, | |||
| + {{"Core", "AudioInterface16"}, {"Cardinal", "HostAudio8"}}, | |||
| + {{"Core", "MIDIToCVInterface"}, {"Cardinal", "HostMIDI"}}, | |||
| + {{"Core", "MIDICCToCVInterface"}, {"Cardinal", "HostMIDICC"}}, | |||
| + {{"Core", "MIDITriggerToCVInterface"}, {"Cardinal", "HostMIDIGate"}}, | |||
| + {{"Core", "CV-MIDI"}, {"Cardinal", "HostMIDI"}}, | |||
| + {{"Core", "CV-CC"}, {"Cardinal", "HostMIDICC"}}, | |||
| + {{"Core", "CV-Gate"}, {"Cardinal", "HostMIDIGate"}}, | |||
| + {{"Core", "MIDI-Map"}, {"Cardinal", "HostMIDIMap"}}, | |||
| + {{"Core", "Notes"}, {"Cardinal", "TextEditor"}}, | |||
| + {{"Core", "Blank"}, {"Cardinal", "Blank"}}, | |||
| {{"MindMeld-ShapeMasterPro", "ShapeMasterPro"}, {"MindMeldModular", "ShapeMaster"}}, | |||
| {{"MindMeldModular", "ShapeMaster"}, {"MindMeld-ShapeMasterPro", "ShapeMasterPro"}}, | |||
| - {{"MindMeldModular", "ShapeMaster"}, {"MindMeld-ShapeMasterPro", "ShapeMasterPro"}}, | |||
| // {{"", ""}, {"", ""}}, | |||
| @@ -441,7 +179,6 @@ | |||
| }; | |||
| @@ -441,7 +184,6 @@ | |||
| } | |||