/* * 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. */ // TODO convert to const float* for zero-latency cable stuff and fix all plugins after 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