You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
7.3KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. /**
  18. * This file is an edited version of VCVRack's engine/Port.hpp
  19. * Copyright (C) 2016-2021 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #pragma once
  27. #include <common.hpp>
  28. #include <engine/Light.hpp>
  29. #include <list>
  30. namespace rack {
  31. namespace engine {
  32. /** This is inspired by the number of MIDI channels. */
  33. static const int PORT_MAX_CHANNELS = 16;
  34. struct Cable;
  35. struct Port {
  36. /** Voltage of the port. */
  37. /** NOTE Purposefully renamed in Cardinal as a way to catch plugins using it directly. */
  38. union {
  39. /** Unstable API. Use getVoltage() and setVoltage() instead. */
  40. float cvoltages[PORT_MAX_CHANNELS] = {};
  41. /** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */
  42. float cvalue;
  43. };
  44. /** Special trickery for backwards compatibility with plugins using DEPRECATED APIs */
  45. struct BackwardsCompatPortValue {
  46. Port* const port;
  47. BackwardsCompatPortValue(Port* p) : port(p) {}
  48. void operator=(float value) { port->setVoltage(value); }
  49. void operator-=(float value) { port->setVoltage(port->cvalue - value); }
  50. void operator+=(float value) { port->setVoltage(port->cvalue + value); }
  51. void operator*=(float value) { port->setVoltage(port->cvalue * value); }
  52. void operator/=(float value) { port->setVoltage(port->cvalue / value); }
  53. operator float() const { return port->cvalue; }
  54. } value;
  55. Port() : value(this) {}
  56. union {
  57. /** Number of polyphonic channels.
  58. DEPRECATED. Unstable API. Use set/getChannels() instead.
  59. May be 0 to PORT_MAX_CHANNELS.
  60. 0 channels means disconnected.
  61. */
  62. uint8_t channels = 0;
  63. /** DEPRECATED. Unstable API. Use isConnected() instead. */
  64. uint8_t active;
  65. };
  66. /** For rendering plug lights on cables.
  67. Green for positive, red for negative, and blue for polyphonic.
  68. */
  69. Light plugLights[3];
  70. enum Type {
  71. INPUT,
  72. OUTPUT,
  73. };
  74. /** Cables connected to this output port. */
  75. std::list<Cable*> cables;
  76. /** Step-through the cables.
  77. Called whenever voltage changes, required for zero latency operation. */
  78. void stepCables();
  79. /** Sets the voltage of the given channel. */
  80. void setVoltage(float voltage, int channel = 0) {
  81. cvoltages[channel] = voltage;
  82. stepCables();
  83. }
  84. /** Returns the voltage of the given channel.
  85. Because of proper bookkeeping, all channels higher than the input port's number of channels should be 0V.
  86. */
  87. float getVoltage(int channel = 0) {
  88. return cvoltages[channel];
  89. }
  90. /** Returns the given channel's voltage if the port is polyphonic, otherwise returns the first voltage (channel 0). */
  91. float getPolyVoltage(int channel) {
  92. return isMonophonic() ? getVoltage(0) : getVoltage(channel);
  93. }
  94. /** Returns the voltage if a cable is connected, otherwise returns the given normal voltage. */
  95. float getNormalVoltage(float normalVoltage, int channel = 0) {
  96. return isConnected() ? getVoltage(channel) : normalVoltage;
  97. }
  98. float getNormalPolyVoltage(float normalVoltage, int channel) {
  99. return isConnected() ? getPolyVoltage(channel) : normalVoltage;
  100. }
  101. /** Returns a pointer to the array of voltages beginning with firstChannel.
  102. The pointer can be used for reading and writing.
  103. */
  104. // TODO convert to const float* for zero-latency cable stuff and fix all plugins after
  105. float* getVoltages(int firstChannel = 0) {
  106. return &cvoltages[firstChannel];
  107. }
  108. /** Copies the port's voltages to an array of size at least `channels`. */
  109. void readVoltages(float* v) {
  110. for (int c = 0; c < channels; c++) {
  111. v[c] = cvoltages[c];
  112. }
  113. }
  114. /** Copies an array of size at least `channels` to the port's voltages.
  115. Remember to set the number of channels *before* calling this method.
  116. */
  117. void writeVoltages(const float* v) {
  118. for (int c = 0; c < channels; c++) {
  119. cvoltages[c] = v[c];
  120. }
  121. stepCables();
  122. }
  123. /** Sets all voltages to 0. */
  124. void clearVoltages() {
  125. for (int c = 0; c < channels; c++) {
  126. cvoltages[c] = 0.f;
  127. }
  128. stepCables();
  129. }
  130. /** Returns the sum of all voltages. */
  131. float getVoltageSum() {
  132. float sum = 0.f;
  133. for (int c = 0; c < channels; c++) {
  134. sum += cvoltages[c];
  135. }
  136. return sum;
  137. }
  138. /** Returns the root-mean-square of all voltages.
  139. Uses sqrt() which is slow, so use a custom approximation if calling frequently.
  140. */
  141. float getVoltageRMS() {
  142. if (channels == 0) {
  143. return 0.f;
  144. }
  145. else if (channels == 1) {
  146. return std::fabs(cvoltages[0]);
  147. }
  148. else {
  149. float sum = 0.f;
  150. for (int c = 0; c < channels; c++) {
  151. sum += std::pow(cvoltages[c], 2);
  152. }
  153. return std::sqrt(sum);
  154. }
  155. }
  156. template <typename T>
  157. T getVoltageSimd(int firstChannel) {
  158. return T::load(&cvoltages[firstChannel]);
  159. }
  160. template <typename T>
  161. T getPolyVoltageSimd(int firstChannel) {
  162. return isMonophonic() ? getVoltage(0) : getVoltageSimd<T>(firstChannel);
  163. }
  164. template <typename T>
  165. T getNormalVoltageSimd(T normalVoltage, int firstChannel) {
  166. return isConnected() ? getVoltageSimd<T>(firstChannel) : normalVoltage;
  167. }
  168. template <typename T>
  169. T getNormalPolyVoltageSimd(T normalVoltage, int firstChannel) {
  170. return isConnected() ? getPolyVoltageSimd<T>(firstChannel) : normalVoltage;
  171. }
  172. template <typename T>
  173. void setVoltageSimd(T voltage, int firstChannel) {
  174. voltage.store(&cvoltages[firstChannel]);
  175. stepCables();
  176. }
  177. /** Sets the number of polyphony channels.
  178. Also clears voltages of higher channels.
  179. If disconnected, this does nothing (`channels` remains 0).
  180. If 0 is given, `channels` is set to 1 but all voltages are cleared.
  181. */
  182. void setChannels(int channels) {
  183. // If disconnected, keep the number of channels at 0.
  184. if (this->channels == 0) {
  185. return;
  186. }
  187. // Set higher channel voltages to 0
  188. for (int c = channels; c < this->channels; c++) {
  189. cvoltages[c] = 0.f;
  190. }
  191. // Don't allow caller to set port as disconnected
  192. if (channels == 0) {
  193. channels = 1;
  194. }
  195. this->channels = channels;
  196. }
  197. /** Returns the number of channels.
  198. If the port is disconnected, it has 0 channels.
  199. */
  200. int getChannels() {
  201. return channels;
  202. }
  203. /** Returns whether a cable is connected to the Port.
  204. You can use this for skipping code that generates output voltages.
  205. */
  206. bool isConnected() {
  207. return channels > 0;
  208. }
  209. /** Returns whether the cable exists and has 1 channel. */
  210. bool isMonophonic() {
  211. return channels == 1;
  212. }
  213. /** Returns whether the cable exists and has more than 1 channel. */
  214. bool isPolyphonic() {
  215. return channels > 1;
  216. }
  217. /** Use getNormalVoltage() instead. */
  218. DEPRECATED float normalize(float normalVoltage) {
  219. return getNormalVoltage(normalVoltage);
  220. }
  221. };
  222. struct Output : Port {};
  223. struct Input : Port {};
  224. } // namespace engine
  225. } // namespace rack