1
Fork 0
cardinal/include/engine/Port.hpp

249 lines
6.4 KiB
C++

/*
* 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 constexpr const int PORT_MAX_CHANNELS = 16;
struct Cable;
struct Port {
/** Voltage of the port. */
/** NOTE alignas is required in order to allow SSE usage.
Consecutive data (like in a vector) would otherwise pack Ports in a way that breaks SSE. */
union alignas(32) {
/** Unstable API. Use getVoltage() and setVoltage() instead. */
float voltages[PORT_MAX_CHANNELS] = {};
/** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */
float value;
};
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,
};
/** Sets the voltage of the given channel. */
void setVoltage(float voltage, int channel = 0) {
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 voltages[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 &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] = voltages[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++) {
voltages[c] = v[c];
}
}
/** Sets all voltages to 0. */
void clearVoltages() {
for (int c = 0; c < channels; c++) {
voltages[c] = 0.f;
}
}
/** Returns the sum of all voltages. */
float getVoltageSum() {
float sum = 0.f;
for (int c = 0; c < channels; c++) {
sum += voltages[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(voltages[0]);
}
else {
float sum = 0.f;
for (int c = 0; c < channels; c++) {
sum += std::pow(voltages[c], 2);
}
return std::sqrt(sum);
}
}
template <typename T>
T getVoltageSimd(int firstChannel) {
return T::load(&voltages[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(&voltages[firstChannel]);
}
/** 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++) {
voltages[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 {
/** List of cables connected to this port. */
std::list<Cable*> cables;
};
struct Input : Port {};
} // namespace engine
} // namespace rack