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.

289 lines
12KB

  1. #include "plugin.hpp"
  2. using simd::float_4;
  3. // equal sum crossfade, -1 <= p <= 1
  4. template <typename T>
  5. inline T equalSumCrossfade(T a, T b, const float p) {
  6. return a * (0.5f * (1.f - p)) + b * (0.5f * (1.f + p));
  7. }
  8. // equal power crossfade, -1 <= p <= 1
  9. template <typename T>
  10. inline T equalPowerCrossfade(T a, T b, const float p) {
  11. //return std::min(std::exp(4.f * p), 1.f) * b + std::min(std::exp(4.f * -p), 1.f) * a;
  12. return std::min(exponentialBipolar80Pade_5_4(p + 1), 1.f) * b + std::min(exponentialBipolar80Pade_5_4(1 - p), 1.f) * a;
  13. }
  14. // TExponentialSlewLimiter doesn't appear to work as is required for this application.
  15. // I think it is due to the absence of the logic that stops the output rising / falling too quickly,
  16. // i.e. faster than the original signal? For now, we use this implementation (essentialy the same as
  17. // SlewLimiter.cpp)
  18. struct ExpLogSlewLimiter {
  19. float out = 0.f;
  20. float slew = 0.f;
  21. void reset() {
  22. out = 0.f;
  23. }
  24. void setSlew(float slew) {
  25. this->slew = slew;
  26. }
  27. float process(float deltaTime, float in) {
  28. if (in > out) {
  29. out += slew * (in - out) * deltaTime;
  30. if (out > in) {
  31. out = in;
  32. }
  33. }
  34. else if (in < out) {
  35. out += slew * (in - out) * deltaTime;
  36. if (out < in) {
  37. out = in;
  38. }
  39. }
  40. return out;
  41. }
  42. };
  43. struct Morphader : Module {
  44. enum ParamIds {
  45. CV_PARAM,
  46. ENUMS(A_LEVEL, 4),
  47. ENUMS(B_LEVEL, 4),
  48. ENUMS(MODE, 4),
  49. FADER_LAG_PARAM,
  50. FADER_PARAM,
  51. NUM_PARAMS
  52. };
  53. enum InputIds {
  54. ENUMS(CV_INPUT, 4),
  55. ENUMS(A_INPUT, 4),
  56. ENUMS(B_INPUT, 4),
  57. NUM_INPUTS
  58. };
  59. enum OutputIds {
  60. ENUMS(OUT, 4),
  61. NUM_OUTPUTS
  62. };
  63. enum LightIds {
  64. ENUMS(A_LED, 4),
  65. ENUMS(B_LED, 4),
  66. NUM_LIGHTS
  67. };
  68. enum CrossfadeMode {
  69. AUDIO_MODE,
  70. CV_MODE
  71. };
  72. static const int NUM_MIXER_CHANNELS = 4;
  73. const float_4 normal10VSimd = {10.f};
  74. ExpLogSlewLimiter slewLimiter;
  75. // minimum and maximum slopes in volts per second, they specify the time to get
  76. // from A (-1) to B (+1)
  77. constexpr static float slewMin = 2.0 / 15.f;
  78. constexpr static float slewMax = 2.0 / 0.01f;
  79. Morphader() {
  80. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  81. configParam(CV_PARAM, 0.f, 1.f, 1.f, "CV attenuator");
  82. for (int i = 0; i < NUM_MIXER_CHANNELS; i++) {
  83. configParam(A_LEVEL + i, 0.f, 1.f, 0.f, string::f("A level %d", i + 1));
  84. configInput(A_INPUT + i, string::f("A%d", i + 1));
  85. }
  86. for (int i = 0; i < NUM_MIXER_CHANNELS; i++) {
  87. configParam(B_LEVEL + i, 0.f, 1.f, 0.f, string::f("B level %d", i + 1));
  88. configInput(B_INPUT + i, string::f("B%d", i + 1));
  89. }
  90. for (int i = 0; i < NUM_MIXER_CHANNELS; i++) {
  91. configSwitch(MODE + i, AUDIO_MODE, CV_MODE, AUDIO_MODE, string::f("Mode %d", i + 1), {"Audio", "CV"});
  92. configInput(CV_INPUT + i, string::f("CV channel %d", i + 1));
  93. }
  94. configParam(FADER_LAG_PARAM, 2.0f / slewMax, 2.0f / slewMin, 2.0f / slewMax, "Fader lag", "s");
  95. configParam(FADER_PARAM, -1.f, 1.f, 0.f, "Fader");
  96. }
  97. // determine the cross-fade between -1 (A) and +1 (B) for each of the 4 channels
  98. float_4 determineChannelCrossfades(const float deltaTime) {
  99. float_4 channelCrossfades = {};
  100. const float slewLambda = 2.0f / params[FADER_LAG_PARAM].getValue();
  101. slewLimiter.setSlew(slewLambda);
  102. const float masterCrossfadeValue = slewLimiter.process(deltaTime, params[FADER_PARAM].getValue());
  103. for (int i = 0; i < NUM_MIXER_CHANNELS; i++) {
  104. if (i == 0) {
  105. // CV will be added to master for channel 1, and if not connected, the normalled value of 5.0V will correspond to the midpoint
  106. const float crossfadeCV = clamp(inputs[CV_INPUT + i].getVoltage(), 0.f, 10.f);
  107. channelCrossfades[i] = params[CV_PARAM].getValue() * rescale(crossfadeCV, 0.f, 10.f, 0.f, +2.f) + masterCrossfadeValue;
  108. }
  109. else {
  110. // if present for the current channel, CV has total control (crossfader is ignored)
  111. if (inputs[CV_INPUT + i].isConnected()) {
  112. const float crossfadeCV = clamp(inputs[CV_INPUT + i].getVoltage(), 0.f, 10.f);
  113. channelCrossfades[i] = rescale(crossfadeCV, 0.f, 10.f, -1.f, +1.f);
  114. }
  115. // if channel 1 is plugged in, but this channel isn't, channel 1 is normalled - in
  116. // this scenario, however the CV is summed with the crossfader
  117. else if (inputs[CV_INPUT + 0].isConnected()) {
  118. const float crossfadeCV = clamp(inputs[CV_INPUT + 0].getVoltage(), 0.f, 10.f);
  119. channelCrossfades[i] = params[CV_PARAM].getValue() * rescale(crossfadeCV, 0.f, 10.f, 0.f, +2.f) + masterCrossfadeValue;
  120. }
  121. else {
  122. channelCrossfades[i] = masterCrossfadeValue;
  123. }
  124. }
  125. channelCrossfades[i] = clamp(channelCrossfades[i], -1.f, +1.f);
  126. }
  127. return channelCrossfades;
  128. }
  129. void process(const ProcessArgs& args) override {
  130. int maxChannels = 1;
  131. float_4 mix[4] = {};
  132. const float_4 channelCrossfades = determineChannelCrossfades(args.sampleTime);
  133. for (int i = 0; i < NUM_MIXER_CHANNELS; i++) {
  134. const int channels = std::max(std::max(inputs[A_INPUT + i].getChannels(), inputs[B_INPUT + i].getChannels()), 1);
  135. // keep track of the max number of channels for the mix output, noting that if channels are taken out of the mix
  136. // (i.e. they're connected) they shouldn't contribute to the mix polyphony calculation
  137. if (!outputs[OUT + i].isConnected()) {
  138. maxChannels = std::max(maxChannels, channels);
  139. }
  140. float_4 out[4] = {};
  141. for (int c = 0; c < channels; c += 4) {
  142. float_4 inA = inputs[A_INPUT + i].getNormalVoltageSimd(normal10VSimd, c) * params[A_LEVEL + i].getValue();
  143. float_4 inB = inputs[B_INPUT + i].getNormalVoltageSimd(normal10VSimd, c) * params[B_LEVEL + i].getValue();
  144. switch (static_cast<CrossfadeMode>(params[MODE + i].getValue())) {
  145. case CV_MODE: {
  146. out[c / 4] = equalSumCrossfade(inA, inB, channelCrossfades[i]);
  147. break;
  148. }
  149. case AUDIO_MODE: {
  150. // in audio mode, close to the centre point it is possible to get large voltages
  151. // (e.g. if A and B are both 10V const). however according to the standard, it is
  152. // better not to clip this https://vcvrack.com/manual/VoltageStandards#Output-Saturation
  153. out[c / 4] = equalPowerCrossfade(inA, inB, channelCrossfades[i]);
  154. break;
  155. }
  156. default: {
  157. out[c / 4] = 0.f;
  158. }
  159. }
  160. }
  161. // if output is patched, the channel is taken out of the mix
  162. if (outputs[OUT + i].isConnected() && i != NUM_MIXER_CHANNELS - 1) {
  163. outputs[OUT + i].setChannels(channels);
  164. for (int c = 0; c < channels; c += 4) {
  165. outputs[OUT + i].setVoltageSimd(out[c / 4], c);
  166. }
  167. }
  168. else {
  169. for (int c = 0; c < channels; c += 4) {
  170. mix[c / 4] += out[c / 4];
  171. }
  172. }
  173. if (i == NUM_MIXER_CHANNELS - 1) {
  174. outputs[OUT + i].setChannels(maxChannels);
  175. for (int c = 0; c < maxChannels; c += 4) {
  176. outputs[OUT + i].setVoltageSimd(mix[c / 4], c);
  177. }
  178. }
  179. switch (static_cast<CrossfadeMode>(params[MODE + i].getValue())) {
  180. case AUDIO_MODE: {
  181. lights[A_LED + i].setBrightness(equalPowerCrossfade(1.f, 0.f, channelCrossfades[i]));
  182. lights[B_LED + i].setBrightness(equalPowerCrossfade(0.f, 1.f, channelCrossfades[i]));
  183. break;
  184. }
  185. case CV_MODE: {
  186. lights[A_LED + i].setBrightness(equalSumCrossfade(1.f, 0.f, channelCrossfades[i]));
  187. lights[B_LED + i].setBrightness(equalSumCrossfade(0.f, 1.f, channelCrossfades[i]));
  188. break;
  189. }
  190. default: {
  191. lights[A_LED + i].setBrightness(0.f);
  192. lights[B_LED + i].setBrightness(0.f);
  193. break;
  194. }
  195. }
  196. } // end loop over mixer channels
  197. }
  198. };
  199. struct MorphaderWidget : ModuleWidget {
  200. MorphaderWidget(Morphader* module) {
  201. setModule(module);
  202. setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/panels/Morphader.svg")));
  203. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  204. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  205. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  206. addChild(createWidget<Knurlie>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  207. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(10.817, 15.075)), module, Morphader::CV_PARAM));
  208. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(30.243, 30.537)), module, Morphader::A_LEVEL + 0));
  209. addParam(createParamCentered<BefacoTinyKnobLightGrey>(mm2px(Vec(30.243, 48.017)), module, Morphader::A_LEVEL + 1));
  210. addParam(createParamCentered<BefacoTinyKnobDarkGrey>(mm2px(Vec(30.243, 65.523)), module, Morphader::A_LEVEL + 2));
  211. addParam(createParamCentered<BefacoTinyKnobBlack>(mm2px(Vec(30.243, 83.051)), module, Morphader::A_LEVEL + 3));
  212. addParam(createParamCentered<BefacoTinyKnob>(mm2px(Vec(52.696, 30.537)), module, Morphader::B_LEVEL + 0));
  213. addParam(createParamCentered<BefacoTinyKnobLightGrey>(mm2px(Vec(52.696, 48.017)), module, Morphader::B_LEVEL + 1));
  214. addParam(createParamCentered<BefacoTinyKnobDarkGrey>(mm2px(Vec(52.696, 65.523)), module, Morphader::B_LEVEL + 2));
  215. addParam(createParamCentered<BefacoTinyKnobBlack>(mm2px(Vec(52.696, 83.051)), module, Morphader::B_LEVEL + 3));
  216. addParam(createParam<CKSSNarrow>(mm2px(Vec(39.775, 28.107)), module, Morphader::MODE + 0));
  217. addParam(createParam<CKSSNarrow>(mm2px(Vec(39.775, 45.627)), module, Morphader::MODE + 1));
  218. addParam(createParam<CKSSNarrow>(mm2px(Vec(39.775, 63.146)), module, Morphader::MODE + 2));
  219. addParam(createParam<CKSSNarrow>(mm2px(Vec(39.775, 80.666)), module, Morphader::MODE + 3));
  220. addParam(createParamCentered<BefacoTinyKnobRed>(mm2px(Vec(10.817, 99.242)), module, Morphader::FADER_LAG_PARAM));
  221. addParam(createParamCentered<Crossfader>(mm2px(Vec(30., 114.25)), module, Morphader::FADER_PARAM));
  222. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(25.214, 14.746)), module, Morphader::CV_INPUT + 0));
  223. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(35.213, 14.746)), module, Morphader::CV_INPUT + 1));
  224. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(45.236, 14.746)), module, Morphader::CV_INPUT + 2));
  225. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(55.212, 14.746)), module, Morphader::CV_INPUT + 3));
  226. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.812, 32.497)), module, Morphader::A_INPUT + 0));
  227. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.812, 48.017)), module, Morphader::A_INPUT + 1));
  228. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.812, 65.523)), module, Morphader::A_INPUT + 2));
  229. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.812, 81.185)), module, Morphader::A_INPUT + 3));
  230. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.791, 32.497)), module, Morphader::B_INPUT + 0));
  231. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.791, 48.017)), module, Morphader::B_INPUT + 1));
  232. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.791, 65.523)), module, Morphader::B_INPUT + 2));
  233. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(15.791, 81.185)), module, Morphader::B_INPUT + 3));
  234. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.177, 100.5)), module, Morphader::OUT + 0));
  235. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(35.177, 100.5)), module, Morphader::OUT + 1));
  236. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(45.177, 100.5)), module, Morphader::OUT + 2));
  237. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(55.176, 100.5)), module, Morphader::OUT + 3));
  238. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.594, 24.378)), module, Morphader::A_LED + 0));
  239. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.594, 41.908)), module, Morphader::A_LED + 1));
  240. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.594, 59.488)), module, Morphader::A_LED + 2));
  241. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(37.594, 76.918)), module, Morphader::A_LED + 3));
  242. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(45.332, 24.378)), module, Morphader::B_LED + 0));
  243. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(45.332, 41.908)), module, Morphader::B_LED + 1));
  244. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(45.332, 59.488)), module, Morphader::B_LED + 2));
  245. addChild(createLightCentered<SmallLight<RedLight>>(mm2px(Vec(45.332, 76.918)), module, Morphader::B_LED + 3));
  246. }
  247. };
  248. Model* modelMorphader = createModel<Morphader, MorphaderWidget>("Morphader");