diff --git a/include/engine/Port.hpp b/include/engine/Port.hpp new file mode 100644 index 0000000..957e680 --- /dev/null +++ b/include/engine/Port.hpp @@ -0,0 +1,268 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +/** + * This file is an edited version of VCVRack's engine/Port.hpp + * Copyright (C) 2016-2021 VCV. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + */ + +#pragma once + +#include +#include + +#include + + +namespace rack { +namespace engine { + + +/** This is inspired by the number of MIDI channels. */ +static const int PORT_MAX_CHANNELS = 16; + + +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] = {}; + /** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */ + float cvalue; + }; + + /** 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. + May be 0 to PORT_MAX_CHANNELS. + 0 channels means disconnected. + */ + uint8_t channels = 0; + /** DEPRECATED. Unstable API. Use isConnected() instead. */ + uint8_t active; + }; + /** For rendering plug lights on cables. + Green for positive, red for negative, and blue for polyphonic. + */ + Light plugLights[3]; + + enum Type { + INPUT, + OUTPUT, + }; + + /** Cables connected to this output port. */ + std::list 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(); + } + + /** 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]; + } + + /** Returns the given channel's voltage if the port is polyphonic, otherwise returns the first voltage (channel 0). */ + float getPolyVoltage(int channel) { + return isMonophonic() ? getVoltage(0) : getVoltage(channel); + } + + /** Returns the voltage if a cable is connected, otherwise returns the given normal voltage. */ + float getNormalVoltage(float normalVoltage, int channel = 0) { + return isConnected() ? getVoltage(channel) : normalVoltage; + } + + float getNormalPolyVoltage(float normalVoltage, int channel) { + return isConnected() ? getPolyVoltage(channel) : normalVoltage; + } + + /** Returns a pointer to the array of voltages beginning with firstChannel. + The pointer can be used for reading and writing. + */ + float* getVoltages(int firstChannel = 0) { + return &cvoltages[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]; + } + } + + /** Copies an array of size at least `channels` to the port's voltages. + Remember to set the number of channels *before* calling this method. + */ + void writeVoltages(const float* v) { + for (int c = 0; c < channels; c++) { + cvoltages[c] = v[c]; + } + stepCables(); + } + + /** Sets all voltages to 0. */ + void clearVoltages() { + for (int c = 0; c < channels; c++) { + cvoltages[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]; + } + return sum; + } + + /** Returns the root-mean-square of all voltages. + Uses sqrt() which is slow, so use a custom approximation if calling frequently. + */ + float getVoltageRMS() { + if (channels == 0) { + return 0.f; + } + else if (channels == 1) { + return std::fabs(cvoltages[0]); + } + else { + float sum = 0.f; + for (int c = 0; c < channels; c++) { + sum += std::pow(cvoltages[c], 2); + } + return std::sqrt(sum); + } + } + + template + T getVoltageSimd(int firstChannel) { + return T::load(&cvoltages[firstChannel]); + } + + template + T getPolyVoltageSimd(int firstChannel) { + return isMonophonic() ? getVoltage(0) : getVoltageSimd(firstChannel); + } + + template + T getNormalVoltageSimd(T normalVoltage, int firstChannel) { + return isConnected() ? getVoltageSimd(firstChannel) : normalVoltage; + } + + template + T getNormalPolyVoltageSimd(T normalVoltage, int firstChannel) { + return isConnected() ? getPolyVoltageSimd(firstChannel) : normalVoltage; + } + + template + void setVoltageSimd(T voltage, int firstChannel) { + voltage.store(&cvoltages[firstChannel]); + stepCables(); + } + + /** Sets the number of polyphony channels. + Also clears voltages of higher channels. + If disconnected, this does nothing (`channels` remains 0). + If 0 is given, `channels` is set to 1 but all voltages are cleared. + */ + void setChannels(int channels) { + // If disconnected, keep the number of channels at 0. + if (this->channels == 0) { + return; + } + // Set higher channel voltages to 0 + for (int c = channels; c < this->channels; c++) { + cvoltages[c] = 0.f; + } + // Don't allow caller to set port as disconnected + if (channels == 0) { + channels = 1; + } + this->channels = channels; + } + + /** Returns the number of channels. + If the port is disconnected, it has 0 channels. + */ + int getChannels() { + return channels; + } + + /** Returns whether a cable is connected to the Port. + You can use this for skipping code that generates output voltages. + */ + bool isConnected() { + return channels > 0; + } + + /** Returns whether the cable exists and has 1 channel. */ + bool isMonophonic() { + return channels == 1; + } + + /** Returns whether the cable exists and has more than 1 channel. */ + bool isPolyphonic() { + return channels > 1; + } + + /** Use getNormalVoltage() instead. */ + DEPRECATED float normalize(float normalVoltage) { + return getNormalVoltage(normalVoltage); + } +}; + + +struct Output : Port {}; + +struct Input : Port {}; + + +} // namespace engine +} // namespace rack diff --git a/plugins/Bidoo b/plugins/Bidoo index e55fcd2..988c237 160000 --- a/plugins/Bidoo +++ b/plugins/Bidoo @@ -1 +1 @@ -Subproject commit e55fcd2e1d7c0fef69d4919baac6f791172c89ca +Subproject commit 988c2372a95d163b71d04b217080e612b767c539 diff --git a/src/override/Engine.cpp b/src/override/Engine.cpp index 68a6fd0..d1f1b1e 100644 --- a/src/override/Engine.cpp +++ b/src/override/Engine.cpp @@ -124,23 +124,30 @@ static void Cable_step(Cable* that) { Output* output = &that->outputModule->outputs[that->outputId]; Input* input = &that->inputModule->inputs[that->inputId]; // Match number of polyphonic channels to output port - int channels = output->channels; + const int channels = output->channels; // Copy all voltages from output to input for (int c = 0; c < channels; c++) { - float v = output->voltages[c]; + float v = output->cvoltages[c]; // Set 0V if infinite or NaN if (!std::isfinite(v)) v = 0.f; - input->voltages[c] = v; + input->cvoltages[c] = v; } // Set higher channel voltages to 0 for (int c = channels; c < input->channels; c++) { - input->voltages[c] = 0.f; + input->cvoltages[c] = 0.f; } input->channels = channels; } +void Port::stepCables() +{ + for (Cable* cable : cables) + Cable_step(cable); +} + + /** Steps a single frame */ static void Engine_stepFrame(Engine* that) { @@ -167,10 +174,13 @@ 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) { @@ -202,7 +212,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->voltages[c] = 0.f; + that->cvoltages[c] = 0.f; } } @@ -242,6 +252,7 @@ static void Engine_updateConnected(Engine* that) { // Disconnect ports that have no cable for (Port* port : disconnectedPorts) { Port_setDisconnected(port); + DISTRHO_SAFE_ASSERT(port->cables.empty()); } } @@ -719,6 +730,8 @@ void Engine::addCable(Cable* cable) { // 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 { @@ -750,6 +763,8 @@ void Engine::removeCable_NoLock(Cable* cable) { // Check that the cable is already added auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); DISTRHO_SAFE_ASSERT_RETURN(it != internal->cables.end(),); + // Remove the cable's zero-latency shortcut + cable->outputModule->outputs[cable->outputId].cables.remove(cable); // Remove the cable internal->cablesCache.erase(cable->id); internal->cables.erase(it);