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 @@ | |||
} | |||