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.

256 lines
6.6KB

  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. /** NOTE alignas is required in some systems in order to allow SSE usage. */
  31. #ifndef ARCH_MAC
  32. #define SIMD_ALIGN alignas(32)
  33. #else
  34. #define SIMD_ALIGN
  35. #endif
  36. namespace rack {
  37. namespace engine {
  38. /** This is inspired by the number of MIDI channels. */
  39. static constexpr const int PORT_MAX_CHANNELS = 16;
  40. struct Cable;
  41. struct Port {
  42. /** Voltage of the port. */
  43. /** NOTE alignas is required in order to allow SSE usage.
  44. Consecutive data (like in a vector) would otherwise pack Ports in a way that breaks SSE. */
  45. union SIMD_ALIGN {
  46. /** Unstable API. Use getVoltage() and setVoltage() instead. */
  47. float voltages[PORT_MAX_CHANNELS] = {};
  48. /** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */
  49. float value;
  50. };
  51. union {
  52. /** Number of polyphonic channels.
  53. DEPRECATED. Unstable API. Use set/getChannels() instead.
  54. May be 0 to PORT_MAX_CHANNELS.
  55. 0 channels means disconnected.
  56. */
  57. uint8_t channels = 0;
  58. /** DEPRECATED. Unstable API. Use isConnected() instead. */
  59. uint8_t active;
  60. };
  61. /** For rendering plug lights on cables.
  62. Green for positive, red for negative, and blue for polyphonic.
  63. */
  64. Light plugLights[3];
  65. enum Type {
  66. INPUT,
  67. OUTPUT,
  68. };
  69. /** Sets the voltage of the given channel. */
  70. void setVoltage(float voltage, int channel = 0) {
  71. voltages[channel] = voltage;
  72. }
  73. /** Returns the voltage of the given channel.
  74. Because of proper bookkeeping, all channels higher than the input port's number of channels should be 0V.
  75. */
  76. float getVoltage(int channel = 0) {
  77. return voltages[channel];
  78. }
  79. /** Returns the given channel's voltage if the port is polyphonic, otherwise returns the first voltage (channel 0). */
  80. float getPolyVoltage(int channel) {
  81. return isMonophonic() ? getVoltage(0) : getVoltage(channel);
  82. }
  83. /** Returns the voltage if a cable is connected, otherwise returns the given normal voltage. */
  84. float getNormalVoltage(float normalVoltage, int channel = 0) {
  85. return isConnected() ? getVoltage(channel) : normalVoltage;
  86. }
  87. float getNormalPolyVoltage(float normalVoltage, int channel) {
  88. return isConnected() ? getPolyVoltage(channel) : normalVoltage;
  89. }
  90. /** Returns a pointer to the array of voltages beginning with firstChannel.
  91. The pointer can be used for reading and writing.
  92. */
  93. float* getVoltages(int firstChannel = 0) {
  94. return &voltages[firstChannel];
  95. }
  96. /** Copies the port's voltages to an array of size at least `channels`. */
  97. void readVoltages(float* v) {
  98. for (int c = 0; c < channels; c++) {
  99. v[c] = voltages[c];
  100. }
  101. }
  102. /** Copies an array of size at least `channels` to the port's voltages.
  103. Remember to set the number of channels *before* calling this method.
  104. */
  105. void writeVoltages(const float* v) {
  106. for (int c = 0; c < channels; c++) {
  107. voltages[c] = v[c];
  108. }
  109. }
  110. /** Sets all voltages to 0. */
  111. void clearVoltages() {
  112. for (int c = 0; c < channels; c++) {
  113. voltages[c] = 0.f;
  114. }
  115. }
  116. /** Returns the sum of all voltages. */
  117. float getVoltageSum() {
  118. float sum = 0.f;
  119. for (int c = 0; c < channels; c++) {
  120. sum += voltages[c];
  121. }
  122. return sum;
  123. }
  124. /** Returns the root-mean-square of all voltages.
  125. Uses sqrt() which is slow, so use a custom approximation if calling frequently.
  126. */
  127. float getVoltageRMS() {
  128. if (channels == 0) {
  129. return 0.f;
  130. }
  131. else if (channels == 1) {
  132. return std::fabs(voltages[0]);
  133. }
  134. else {
  135. float sum = 0.f;
  136. for (int c = 0; c < channels; c++) {
  137. sum += std::pow(voltages[c], 2);
  138. }
  139. return std::sqrt(sum);
  140. }
  141. }
  142. template <typename T>
  143. T getVoltageSimd(int firstChannel) {
  144. return T::load(&voltages[firstChannel]);
  145. }
  146. template <typename T>
  147. T getPolyVoltageSimd(int firstChannel) {
  148. return isMonophonic() ? getVoltage(0) : getVoltageSimd<T>(firstChannel);
  149. }
  150. template <typename T>
  151. T getNormalVoltageSimd(T normalVoltage, int firstChannel) {
  152. return isConnected() ? getVoltageSimd<T>(firstChannel) : normalVoltage;
  153. }
  154. template <typename T>
  155. T getNormalPolyVoltageSimd(T normalVoltage, int firstChannel) {
  156. return isConnected() ? getPolyVoltageSimd<T>(firstChannel) : normalVoltage;
  157. }
  158. template <typename T>
  159. void setVoltageSimd(T voltage, int firstChannel) {
  160. voltage.store(&voltages[firstChannel]);
  161. }
  162. /** Sets the number of polyphony channels.
  163. Also clears voltages of higher channels.
  164. If disconnected, this does nothing (`channels` remains 0).
  165. If 0 is given, `channels` is set to 1 but all voltages are cleared.
  166. */
  167. void setChannels(int channels) {
  168. // If disconnected, keep the number of channels at 0.
  169. if (this->channels == 0) {
  170. return;
  171. }
  172. // Set higher channel voltages to 0
  173. for (int c = channels; c < this->channels; c++) {
  174. voltages[c] = 0.f;
  175. }
  176. // Don't allow caller to set port as disconnected
  177. if (channels == 0) {
  178. channels = 1;
  179. }
  180. this->channels = channels;
  181. }
  182. /** Returns the number of channels.
  183. If the port is disconnected, it has 0 channels.
  184. */
  185. int getChannels() {
  186. return channels;
  187. }
  188. /** Returns whether a cable is connected to the Port.
  189. You can use this for skipping code that generates output voltages.
  190. */
  191. bool isConnected() {
  192. return channels > 0;
  193. }
  194. /** Returns whether the cable exists and has 1 channel. */
  195. bool isMonophonic() {
  196. return channels == 1;
  197. }
  198. /** Returns whether the cable exists and has more than 1 channel. */
  199. bool isPolyphonic() {
  200. return channels > 1;
  201. }
  202. /** Use getNormalVoltage() instead. */
  203. DEPRECATED float normalize(float normalVoltage) {
  204. return getNormalVoltage(normalVoltage);
  205. }
  206. };
  207. struct Output : Port {
  208. /** List of cables connected to this port. */
  209. std::list<Cable*> cables;
  210. };
  211. struct Input : Port {};
  212. } // namespace engine
  213. } // namespace rack