Zero latency cables \o/ (please test)
Signed-off-by: falkTX <falktx@falktx.com>
This commit is contained in:
parent
0f66ba9b5b
commit
f78879f3e2
3 changed files with 289 additions and 6 deletions
268
include/engine/Port.hpp
Normal file
268
include/engine/Port.hpp
Normal file
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 <common.hpp>
|
||||
#include <engine/Light.hpp>
|
||||
|
||||
#include <list>
|
||||
|
||||
|
||||
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<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();
|
||||
}
|
||||
|
||||
/** 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 <typename T>
|
||||
T getVoltageSimd(int firstChannel) {
|
||||
return T::load(&cvoltages[firstChannel]);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T getPolyVoltageSimd(int firstChannel) {
|
||||
return isMonophonic() ? getVoltage(0) : getVoltageSimd<T>(firstChannel);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T getNormalVoltageSimd(T normalVoltage, int firstChannel) {
|
||||
return isConnected() ? getVoltageSimd<T>(firstChannel) : normalVoltage;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T getNormalPolyVoltageSimd(T normalVoltage, int firstChannel) {
|
||||
return isConnected() ? getPolyVoltageSimd<T>(firstChannel) : normalVoltage;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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
|
|
@ -1 +1 @@
|
|||
Subproject commit e55fcd2e1d7c0fef69d4919baac6f791172c89ca
|
||||
Subproject commit 988c2372a95d163b71d04b217080e612b767c539
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue