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.

302 lines
9.8KB

  1. #include "plugin.hpp"
  2. struct MotionMTR : Module {
  3. enum ParamId {
  4. MODE1_PARAM,
  5. CTRL_1_PARAM,
  6. MODE2_PARAM,
  7. CTRL_2_PARAM,
  8. MODE3_PARAM,
  9. CTRL_3_PARAM,
  10. PARAMS_LEN
  11. };
  12. enum InputId {
  13. IN1_INPUT,
  14. IN2_INPUT,
  15. IN3_INPUT,
  16. INPUTS_LEN
  17. };
  18. enum OutputId {
  19. OUT1_OUTPUT,
  20. OUT2_OUTPUT,
  21. OUT3_OUTPUT,
  22. OUTPUTS_LEN
  23. };
  24. enum LightDisplayType {
  25. CV_INV,
  26. CV_ATT,
  27. AUDIO
  28. };
  29. const std::vector<std::string> modeLabels = {"CV Inversion", "CV Attentuation", "Audio"};
  30. static const int NUM_LIGHTS_PER_DIAL = 20;
  31. enum LightId {
  32. ENUMS(LIGHT_1, NUM_LIGHTS_PER_DIAL * 3),
  33. ENUMS(LIGHT_2, NUM_LIGHTS_PER_DIAL * 3),
  34. ENUMS(LIGHT_3, NUM_LIGHTS_PER_DIAL * 3),
  35. LIGHTS_LEN
  36. };
  37. struct Map {
  38. float dbValue;
  39. long ledNumber;
  40. };
  41. const Map lut[20] = {
  42. { -30.5, 1}, { -30, 2}, { -30, 3}, { -26, 4}, { -25, 5}, { -24, 6}, { -23, 7 }, { -22, 8 }, { -21, 9}, { -20, 10},
  43. { -19, 11}, { -18, 12}, { -16, 13}, { -14, 14}, { -12, 15}, { -10, 16}, { -8, 17}, { -6, 18}, { -4, 19}, { -2, 20}
  44. };
  45. dsp::VuMeter2 vuBar[3];
  46. const int updateLEDRate = 16;
  47. dsp::ClockDivider sliderUpdate;
  48. bool startingUp = true;
  49. dsp::Timer startupTimer;
  50. MotionMTR() {
  51. config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
  52. configSwitch(MODE1_PARAM, 0.f, 2.f, 1.f, "Channel 1 mode", modeLabels);
  53. configParam(CTRL_1_PARAM, 0.f, 1.f, 0.f, "Channel 1 gain");
  54. configSwitch(MODE2_PARAM, 0.f, 2.f, 1.f, "Channel 2 mode", modeLabels);
  55. configParam(CTRL_2_PARAM, 0.f, 1.f, 0.f, "Channel 2 gain");
  56. configSwitch(MODE3_PARAM, 0.f, 2.f, 1.f, "Channel 3 mode", modeLabels);
  57. configParam(CTRL_3_PARAM, 0.f, 1.f, 0.f, "Channel 3 gain");
  58. auto in1 = configInput(IN1_INPUT, "Channel 1");
  59. in1->description = "Normalled to 10V (except in audio mode)";
  60. auto in2 = configInput(IN2_INPUT, "Channel 2");
  61. in2->description = "Normalled to 10V (except in audio mode)";
  62. auto in3 = configInput(IN3_INPUT, "Channel 3");
  63. in3->description = "Normalled to 10V (except in audio mode)";
  64. configOutput(OUT1_OUTPUT, "Channel 1");
  65. configOutput(OUT2_OUTPUT, "Channel 2");
  66. configOutput(OUT3_OUTPUT, "Channel 3 (Mix)");
  67. for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) {
  68. configLight(LIGHT_1 + i * 3, string::f("%g to %g dB", lut[i - 1].dbValue, lut[i].dbValue));
  69. configLight(LIGHT_2 + i * 3, string::f("%g to %g dB", lut[i - 1].dbValue, lut[i].dbValue));
  70. configLight(LIGHT_3 + i * 3, string::f("%g to %g dB", lut[i - 1].dbValue, lut[i].dbValue));
  71. }
  72. for (int i = 0; i < 3; ++i) {
  73. vuBar[i].mode = dsp::VuMeter2::PEAK;
  74. vuBar[i].lambda = 10.f * updateLEDRate;
  75. }
  76. // only poll EQ sliders every 16 samples
  77. sliderUpdate.setDivision(updateLEDRate);
  78. // timer for startup animation
  79. startupTimer.reset();
  80. }
  81. void onReset(const ResetEvent& e) override {
  82. startingUp = true;
  83. startupTimer.reset();
  84. Module::onReset(e);
  85. }
  86. void process(const ProcessArgs& args) override {
  87. const LightDisplayType mode1 = (LightDisplayType) params[MODE1_PARAM].getValue();
  88. const LightDisplayType mode2 = (LightDisplayType) params[MODE2_PARAM].getValue();
  89. const LightDisplayType mode3 = (LightDisplayType) params[MODE3_PARAM].getValue();
  90. const float in1 = inputs[IN1_INPUT].getNormalVoltage(10.f * (mode1 != AUDIO));
  91. const float in2 = inputs[IN2_INPUT].getNormalVoltage(10.f * (mode2 != AUDIO));
  92. const float in3 = inputs[IN3_INPUT].getNormalVoltage(10.f * (mode3 != AUDIO));
  93. const float out1 = in1 * params[CTRL_1_PARAM].getValue() * (mode1 == CV_INV ? -1 : +1);
  94. const float out2 = in2 * params[CTRL_2_PARAM].getValue() * (mode2 == CV_INV ? -1 : +1);
  95. float out3 = in3 * params[CTRL_3_PARAM].getValue() * (mode3 == CV_INV ? -1 : +1);
  96. if (!outputs[OUT1_OUTPUT].isConnected()) {
  97. out3 += out1;
  98. }
  99. if (!outputs[OUT2_OUTPUT].isConnected()) {
  100. out3 += out2;
  101. }
  102. // special light pattern when starting up :)
  103. if (startingUp) {
  104. processStartup(args);
  105. }
  106. // otherwise (periodically) update LEDS according to value
  107. else if (sliderUpdate.process()) {
  108. lightsForSignal(mode1, LIGHT_1, out1, args, 0);
  109. lightsForSignal(mode2, LIGHT_2, out2, args, 1);
  110. lightsForSignal(mode3, LIGHT_3, out3, args, 2);
  111. }
  112. outputs[OUT1_OUTPUT].setVoltage(out1);
  113. outputs[OUT2_OUTPUT].setVoltage(out2);
  114. outputs[OUT3_OUTPUT].setVoltage(out3);
  115. }
  116. void processStartup(const ProcessArgs& args) {
  117. float time = startupTimer.process(args.sampleTime);
  118. const float ringTime = 0.4;
  119. if (time < ringTime) {
  120. int light = floor(NUM_LIGHTS_PER_DIAL * time / ringTime);
  121. setLightHSBSmooth(LIGHT_1 + 3 * light, args, 360 * time / ringTime, 1., 1.);
  122. }
  123. else if (time < 2 * ringTime) {
  124. time -= ringTime;
  125. int light = floor(NUM_LIGHTS_PER_DIAL * time / ringTime);
  126. setLightHSBSmooth(LIGHT_2 + 3 * light, args, 360 * time / ringTime, 1., 1.);
  127. }
  128. else if (time < 3 * ringTime) {
  129. time -= 2 * ringTime;
  130. int light = floor(NUM_LIGHTS_PER_DIAL * time / ringTime);
  131. setLightHSBSmooth(LIGHT_3 + 3 * light, args, 360 * time / ringTime, 1., 1.);
  132. }
  133. else {
  134. startingUp = false;
  135. }
  136. }
  137. void setLightRGB(int lightId, float R, float G, float B) {
  138. lights[lightId + 0].setBrightness(R);
  139. lights[lightId + 1].setBrightness(G);
  140. lights[lightId + 2].setBrightness(B);
  141. }
  142. void setLightRGBSmooth(int lightId, const ProcessArgs& args, float R, float G, float B) {
  143. // inverse time constant for LED smoothing
  144. const float lambda = 10.f * updateLEDRate;
  145. lights[lightId + 0].setBrightnessSmooth(R, args.sampleTime, lambda);
  146. lights[lightId + 1].setBrightnessSmooth(G, args.sampleTime, lambda);
  147. lights[lightId + 2].setBrightnessSmooth(B, args.sampleTime, lambda);
  148. }
  149. // hue: 0 - 360
  150. void setLightHSBSmooth(int lightId, const ProcessArgs& args, float H, float S, float V) {
  151. float C = S * V;
  152. float X = C * (1 - std::abs(std::fmod(H / 60.0, 2) - 1));
  153. float m = V - C;
  154. float r, g, b;
  155. if (H >= 0 && H < 60) {
  156. r = C, g = X, b = 0;
  157. }
  158. else if (H >= 60 && H < 120) {
  159. r = X, g = C, b = 0;
  160. }
  161. else if (H >= 120 && H < 180) {
  162. r = 0, g = C, b = X;
  163. }
  164. else if (H >= 180 && H < 240) {
  165. r = 0, g = X, b = C;
  166. }
  167. else if (H >= 240 && H < 300) {
  168. r = X, g = 0, b = C;
  169. }
  170. else {
  171. r = C, g = 0, b = X;
  172. }
  173. float R = (r + m);
  174. float G = (g + m);
  175. float B = (b + m);
  176. setLightRGBSmooth(lightId, args, R, G, B);
  177. }
  178. void lightsForSignal(LightDisplayType type, const LightId lightId, float signal, const ProcessArgs& args, const int channel) {
  179. if (type == AUDIO) {
  180. setLightRGB(lightId, 0.f, 1.0f, 0.f);
  181. vuBar[channel].process(args.sampleTime * updateLEDRate, signal / 10.f);
  182. for (int i = 1; i < NUM_LIGHTS_PER_DIAL; i++) {
  183. const float value = vuBar[channel].getBrightness(lut[i - 1].dbValue, lut[i].dbValue);
  184. if (i < 15) {
  185. // green
  186. setLightRGB(lightId + 3 * i, 0.f, value, 0.f);
  187. }
  188. else if (i < NUM_LIGHTS_PER_DIAL - 1) {
  189. // yellow
  190. setLightRGB(lightId + 3 * i, value, 0.65 * value, 0.f);
  191. }
  192. else {
  193. // red
  194. setLightRGB(lightId + 3 * i, value, 0.f, 0.f);
  195. }
  196. }
  197. }
  198. else {
  199. setLightRGBSmooth(lightId, args, 0.82f, 0.0f, 0.82f);
  200. if (signal >= 0) {
  201. for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) {
  202. float value = 0.5f * (signal > (10 * (i + 1.) / (NUM_LIGHTS_PER_DIAL + 1)));
  203. // purple
  204. setLightRGBSmooth(lightId + 3 * i, args, 0.82f * value, 0.0f, 0.82f * value);
  205. }
  206. }
  207. else {
  208. for (int i = 1; i < NUM_LIGHTS_PER_DIAL; ++i) {
  209. float value = (signal < (-10 * (NUM_LIGHTS_PER_DIAL - i + 1.) / (NUM_LIGHTS_PER_DIAL + 1.)));
  210. setLightRGBSmooth(lightId + 3 * i, args, value, 0.65f * value, 0.f);
  211. }
  212. }
  213. }
  214. }
  215. };
  216. struct MotionMTRWidget : ModuleWidget {
  217. MotionMTRWidget(MotionMTR* module) {
  218. setModule(module);
  219. setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/MotionMTR.svg")));
  220. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
  221. addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  222. addParam(createParam<CKSSThree>(mm2px(Vec(1.298, 17.851)), module, MotionMTR::MODE1_PARAM));
  223. addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(18.217, 22.18)), module, MotionMTR::CTRL_1_PARAM));
  224. addParam(createParam<CKSSThree>(mm2px(Vec(23.762, 46.679)), module, MotionMTR::MODE2_PARAM));
  225. addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(11.777, 50.761)), module, MotionMTR::CTRL_2_PARAM));
  226. addParam(createParam<CKSSThree>(mm2px(Vec(1.34, 74.461)), module, MotionMTR::MODE3_PARAM));
  227. addParam(createParamCentered<Davies1900hBlackKnob>(mm2px(Vec(18.31, 78.89)), module, MotionMTR::CTRL_3_PARAM));
  228. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(5.008, 100.315)), module, MotionMTR::IN1_INPUT));
  229. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(14.993, 100.315)), module, MotionMTR::IN2_INPUT));
  230. addInput(createInputCentered<BefacoInputPort>(mm2px(Vec(24.978, 100.315)), module, MotionMTR::IN3_INPUT));
  231. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(5.0, 113.207)), module, MotionMTR::OUT1_OUTPUT));
  232. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(14.978, 113.185)), module, MotionMTR::OUT2_OUTPUT));
  233. addOutput(createOutputCentered<BefacoOutputPort>(mm2px(Vec(25.014, 113.207)), module, MotionMTR::OUT3_OUTPUT));
  234. struct LightRingDetails {
  235. MotionMTR::LightId startingId;
  236. float x, y; // centre
  237. } ;
  238. std::vector<LightRingDetails> ringDetails = {
  239. {MotionMTR::LIGHT_1, 18.217, 22.18},
  240. {MotionMTR::LIGHT_2, 11.777, 50.761},
  241. {MotionMTR::LIGHT_3, 18.217, 78.85}
  242. };
  243. const float R = 9.65; // mm
  244. for (auto detailForRing : ringDetails) {
  245. for (int i = 0; i < MotionMTR::NUM_LIGHTS_PER_DIAL; ++i) {
  246. float theta = 2 * M_PI * i / MotionMTR::NUM_LIGHTS_PER_DIAL;
  247. float x = detailForRing.x + sin(theta) * R;
  248. float y = detailForRing.y - cos(theta) * R;
  249. addChild(createLightCentered<SmallLight<RedGreenBlueLight>>(mm2px(Vec(x, y)), module, detailForRing.startingId + 3 * i));
  250. }
  251. }
  252. }
  253. };
  254. Model* modelMotionMTR = createModel<MotionMTR, MotionMTRWidget>("MotionMTR");