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.

388 lines
15KB

  1. /*
  2. COMPAIR
  3. Dual Window Comparator (inspired by Joranalogue Compare2)
  4. checks if an input voltage is between two values.
  5. compare window is set by two parameters: position and width.
  6. top knob on each channel (position) sets a center voltage of +/-5V.
  7. the other one changes the width around the center from near zero to 10V.
  8. so with both knobs at center the window is set at [-2.5..2.5].
  9. both parameters are also controllable via cv input.
  10. cv and knob values are added together when computing the window.
  11. whenever the input signal is within that window the GATE output on that
  12. channel will go from 0V to +5V and the NOT output will do the opposite.
  13. logic outputs at the bottom compare the output values of both channels.
  14. AND output goes high if A is high and B is high
  15. OR output goes high if A or B is high
  16. XOR output goes high if either A or B ar high and the other one is low.
  17. FLIP output changes whenever the XOR out goes high.
  18. channel B inputs are normalized to channel A inputs.
  19. channel output to the logic section can be inverted.
  20. all outputs are 0V-5V.
  21. TODO:
  22. -proper RGB Light...
  23. -code clean-up, optimization, simplification
  24. */////////////////////////////////////////////////////////////////////////////
  25. #include "pvc.hpp"
  26. #include "dsp/digital.hpp"
  27. namespace rack_plugin_PvC {
  28. struct Compair : Module {
  29. enum ParamIds {
  30. POS_A_PARAM,
  31. WIDTH_A_PARAM,
  32. POS_B_PARAM,
  33. WIDTH_B_PARAM,
  34. INVERT_A_PARAM,
  35. INVERT_B_PARAM,
  36. NUM_PARAMS
  37. };
  38. enum InputIds {
  39. AUDIO_A_IN,
  40. POS_A_IN,
  41. WIDTH_A_IN,
  42. AUDIO_B_IN,
  43. POS_B_IN,
  44. WIDTH_B_IN,
  45. NUM_INPUTS
  46. };
  47. enum OutputIds {
  48. GATE_A_OUT,
  49. NOT_A_OUT,
  50. GATE_B_OUT,
  51. NOT_B_OUT,
  52. AND_OUT,
  53. OR_OUT,
  54. XOR_OUT,
  55. FLIP_OUT,
  56. NUM_OUTPUTS
  57. };
  58. enum LightIds {
  59. GATE_A_LED,
  60. GATE_B_LED,
  61. AND_LED,
  62. OR_LED,
  63. XOR_LED,
  64. FLIP_LED,
  65. OVER_A_LED,
  66. BELOW_A_LED,
  67. OVER_B_LED,
  68. BELOW_B_LED,
  69. NUM_LIGHTS
  70. };
  71. bool outA = false;
  72. bool outB = false;
  73. bool flip = false;
  74. SchmittTrigger flipTrigger;
  75. enum OutputMode {
  76. ORIGINAL,
  77. BIPOLAR
  78. // INV_BIPOLAR,
  79. // INV_ORIGINAL
  80. };
  81. OutputMode outputMode = ORIGINAL;
  82. Compair() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  83. // reset();
  84. }
  85. void step() override;
  86. void reset() override {
  87. outA = false;
  88. outB = false;
  89. flip = false;
  90. outputMode = ORIGINAL;
  91. }
  92. // void randomize() override;
  93. json_t *toJson() override {
  94. json_t *rootJ = json_object();
  95. // outputMode
  96. json_t *outputModeJ = json_integer((int) outputMode);
  97. json_object_set_new(rootJ, "outputMode", outputModeJ);
  98. return rootJ;
  99. }
  100. void fromJson(json_t *rootJ) override {
  101. // outputMode
  102. json_t *outputModeJ = json_object_get(rootJ, "outputMode");
  103. if (outputModeJ)
  104. outputMode = (OutputMode)json_integer_value(outputModeJ);
  105. }
  106. };
  107. void Compair::step(){
  108. // get inputs and normalize B to A
  109. float inputA = inputs[AUDIO_A_IN].value;
  110. float inputB = inputs[AUDIO_B_IN].normalize(inputA);
  111. // get knob values
  112. float posA = params[POS_A_PARAM].value;
  113. float widthA = params[WIDTH_A_PARAM].value;
  114. float posB = params[POS_B_PARAM].value;
  115. float widthB = params[WIDTH_B_PARAM].value;;
  116. // testing bi-polar outputs
  117. float highVal = 5.0f;
  118. //float lowVal = 0.0f;
  119. float lowVal = outputMode == BIPOLAR ? -5.0f : 0.0f;
  120. // channel A CV inputs to knob values
  121. if (inputs[POS_A_IN].active)
  122. posA = (params[POS_A_PARAM].value + inputs[POS_A_IN].value);
  123. if (inputs[WIDTH_A_IN].active)
  124. widthA = (params[WIDTH_A_PARAM].value + inputs[WIDTH_A_IN].value);
  125. // compute window A
  126. float upperThreshA = posA + widthA*0.5f;
  127. float lowerThreshA = posA - widthA*0.5f;
  128. // check if input A is in window A
  129. outA = (inputA <= upperThreshA && inputA >= lowerThreshA) ? true : false;
  130. // channel B CV inputs to knob values and normalization
  131. if (inputs[POS_B_IN].active || inputs[POS_A_IN].active)
  132. posB = (params[POS_B_PARAM].value + inputs[POS_B_IN].normalize(inputs[POS_A_IN].value));
  133. if (inputs[WIDTH_B_IN].active || inputs[WIDTH_B_IN].active)
  134. widthB = (params[WIDTH_B_PARAM].value + inputs[WIDTH_B_IN].normalize(inputs[WIDTH_A_IN].value));
  135. // compute window B
  136. float upperThreshB = posB + widthB*0.5f;
  137. float lowerThreshB = posB - widthB*0.5f;
  138. // check if input B is in window B
  139. outB = (inputB <= upperThreshB && inputB >= lowerThreshB) ? true : false;
  140. // Gate/Not outputs and lights
  141. outputs[GATE_A_OUT].value = outA ? highVal : lowVal;
  142. outputs[NOT_A_OUT].value = !outA ? highVal : lowVal;
  143. lights[GATE_A_LED].setBrightness( outA ? 0.9f : (0.25f - clamp( fabsf(inputA-posA) * 0.025f, 0.0f, 0.4f) ) );
  144. lights[OVER_A_LED].setBrightness( (inputA > upperThreshA) ? (inputA - upperThreshA)*0.1f + 0.4f : 0.0f );
  145. lights[BELOW_A_LED].setBrightness( (inputA < lowerThreshA) ? (lowerThreshA - inputA)*0.1f + 0.4f : 0.0f );
  146. outputs[GATE_B_OUT].value = outB ? highVal : lowVal;
  147. outputs[NOT_B_OUT].value = !outB ? highVal : lowVal;
  148. lights[GATE_B_LED].setBrightness( outB ? 0.9f : (0.25f - clamp( fabsf(inputB-posB) * 0.025f, 0.0f, 0.4f) ) );
  149. lights[OVER_B_LED].setBrightness( (inputB > upperThreshB) ? (inputB - upperThreshB)*0.1f + 0.4f : 0.0f );
  150. lights[BELOW_B_LED].setBrightness( (inputB < lowerThreshB) ? (lowerThreshB - inputB)*0.1f + 0.4f : 0.0f );
  151. // logic input inverts
  152. if (params[INVERT_A_PARAM].value)
  153. outA = !outA;
  154. if (params[INVERT_B_PARAM].value)
  155. outB = !outB;
  156. // logic outputs and lights
  157. outputs[AND_OUT].value = (outA && outB) ? highVal : lowVal;
  158. lights[AND_LED].setBrightness( (outA && outB) );
  159. outputs[OR_OUT].value = (outA || outB) ? highVal : lowVal;
  160. lights[OR_LED].setBrightness( (outA || outB) );
  161. outputs[XOR_OUT].value = (outA != outB) ? highVal : lowVal;
  162. lights[XOR_LED].setBrightness( (outA != outB) );
  163. if (flipTrigger.process(outputs[XOR_OUT].value)) // trigger the FlipFlop
  164. flip = !flip;
  165. outputs[FLIP_OUT].value = flip ? highVal : lowVal;
  166. lights[FLIP_LED].setBrightness( flip );
  167. }
  168. //
  169. struct CompairToggle : SVGSwitch, ToggleSwitch {
  170. CompairToggle() {
  171. addFrame(SVG::load(assetPlugin(plugin, "res/components/CompairToggleUp.svg")));
  172. addFrame(SVG::load(assetPlugin(plugin, "res/components/CompairToggleDn.svg")));
  173. }
  174. };
  175. // backdrop for the compare LEDs
  176. struct CompairLightBg : SVGScrew {
  177. CompairLightBg() {
  178. sw->svg = SVG::load(assetPlugin(plugin, "res/components/CompairLightBg.svg"));
  179. sw->wrap();
  180. box.size = Vec(22,22);
  181. }
  182. };
  183. // LEDs
  184. template <typename BASE>
  185. struct CompairLight : BASE {
  186. CompairLight() {
  187. this->box.size = Vec(22, 22);
  188. this->bgColor = nvgRGBA(0x00, 0x00, 0x00, 0x00);
  189. }
  190. };
  191. struct CompairWidget : ModuleWidget {
  192. CompairWidget(Compair *module);
  193. Menu *createContextMenu() override;
  194. };
  195. CompairWidget::CompairWidget(Compair *module) : ModuleWidget(module) {
  196. setPanel(SVG::load(assetPlugin(plugin, "res/panels/Compair.svg")));
  197. // SCREWS
  198. addChild(Widget::create<ScrewHead1>(Vec(15, 0)));
  199. //addChild(Widget::create<ScrewHead2>(Vec(box.size.x - 15, 0)));
  200. addChild(Widget::create<ScrewHead3>(Vec(15, 365)));
  201. //addChild(Widget::create<ScrewHead4>(Vec(box.size.x - 30, 365)));
  202. // A Side
  203. addInput(Port::create<InPortAud>(Vec(4,22), Port::INPUT, module,Compair::AUDIO_A_IN));
  204. addInput(Port::create<InPortAud>(Vec(34,22), Port::INPUT, module,Compair::AUDIO_B_IN));
  205. addParam(ParamWidget::create<PvCKnob>(Vec(4,64), module, Compair::POS_A_PARAM, -5.0f , 5.0f, 0.0f));
  206. addInput(Port::create<InPortCtrl>(Vec(4,88), Port::INPUT, module,Compair::POS_A_IN));
  207. addParam(ParamWidget::create<PvCKnob>(Vec(34,64), module, Compair::POS_B_PARAM, -5.0f, 5.0f, 0.0f));
  208. addInput(Port::create<InPortCtrl>(Vec(34,88), Port::INPUT, module,Compair::POS_B_IN));
  209. addParam(ParamWidget::create<PvCKnob>(Vec(34,128), module, Compair::WIDTH_B_PARAM, 0.01f , 10.001f, 5.0f));
  210. addInput(Port::create<InPortCtrl>(Vec(34,152), Port::INPUT, module,Compair::WIDTH_B_IN));
  211. addParam(ParamWidget::create<PvCKnob>(Vec(4,128), module, Compair::WIDTH_A_PARAM, 0.01f , 10.001f, 5.0f));
  212. addInput(Port::create<InPortCtrl>(Vec(4,152), Port::INPUT, module,Compair::WIDTH_A_IN));
  213. addChild(Widget::create<CompairLightBg>(Vec(4, 190)));
  214. addChild(ModuleLightWidget::create<CompairLight<BlueLED>>(Vec(4,190),module,Compair::BELOW_A_LED));
  215. addChild(ModuleLightWidget::create<CompairLight<WhiteLED>>(Vec(4,190),module,Compair::GATE_A_LED));
  216. addChild(ModuleLightWidget::create<CompairLight<RedLED>>(Vec(4,190),module,Compair::OVER_A_LED));
  217. addParam(ParamWidget::create<CompairToggle>(Vec(4,190),module,Compair::INVERT_A_PARAM, 0, 1, 0));
  218. addChild(Widget::create<CompairLightBg>(Vec(34, 190)));
  219. addChild(ModuleLightWidget::create<CompairLight<BlueLED>>(Vec(34,190),module,Compair::BELOW_B_LED));
  220. addChild(ModuleLightWidget::create<CompairLight<WhiteLED>>(Vec(34,190),module,Compair::GATE_B_LED));
  221. addChild(ModuleLightWidget::create<CompairLight<RedLED>>(Vec(34,190),module,Compair::OVER_B_LED));
  222. addParam(ParamWidget::create<CompairToggle>(Vec(34,190),module,Compair::INVERT_B_PARAM, 0, 1, 0));
  223. addOutput(Port::create<OutPortBin>(Vec(4,230), Port::OUTPUT, module,Compair::GATE_A_OUT));
  224. addOutput(Port::create<OutPortBin>(Vec(4,254), Port::OUTPUT, module,Compair::NOT_A_OUT));
  225. addOutput(Port::create<OutPortBin>(Vec(34,230), Port::OUTPUT, module,Compair::GATE_B_OUT));
  226. addOutput(Port::create<OutPortBin>(Vec(34,254), Port::OUTPUT, module,Compair::NOT_B_OUT));
  227. addChild(ModuleLightWidget::create<FourPixLight<CyanLED>>(Vec(13,288),module,Compair::AND_LED));
  228. addOutput(Port::create<OutPortBin>(Vec(4,294), Port::OUTPUT, module,Compair::AND_OUT));
  229. addChild(ModuleLightWidget::create<FourPixLight<OrangeLED>>(Vec(43,288),module,Compair::OR_LED));
  230. addOutput(Port::create<OutPortBin>(Vec(34,294), Port::OUTPUT, module,Compair::OR_OUT));
  231. addChild(ModuleLightWidget::create<FourPixLight<YellowLED>>(Vec(13,330),module,Compair::XOR_LED));
  232. addOutput(Port::create<OutPortBin>(Vec(4,336), Port::OUTPUT, module,Compair::XOR_OUT));
  233. addChild(ModuleLightWidget::create<FourPixLight<GreenLED>>(Vec(43,330),module,Compair::FLIP_LED));
  234. addOutput(Port::create<OutPortBin>(Vec(34,336), Port::OUTPUT, module,Compair::FLIP_OUT));
  235. }
  236. /*CompairWidget::CompairWidget(){
  237. Compair *module = new Compair();
  238. setModule(module);
  239. box.size = Vec(8 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT);
  240. {
  241. SVGPanel *panel = new SVGPanel();
  242. panel->box.size = box.size;
  243. panel->setBackground(SVG::load(assetPlugin(plugin, "res/panels/Compair.svg")));
  244. addChild(panel);
  245. }
  246. // SCREWS
  247. addChild(Widget::create<ScrewHead1>(Vec(15, 0)));
  248. //addChild(Widget::create<ScrewHead2>(Vec(box.size.x - 15, 0)));
  249. addChild(Widget::create<ScrewHead3>(Vec(15, 365)));
  250. addChild(Widget::create<ScrewHead4>(Vec(box.size.x - 30, 365)));
  251. // A
  252. addInput(Port::create<InPortAud>(Vec(35,234), Port::INPUT, module,Compair::AUDIO_A_IN));
  253. addParam(ParamWidget::create<PvCKnob>(Vec(10,60), module, Compair::POS_A_PARAM, -5.0f , 5.0f, 0.0f));
  254. addInput(Port::create<InPortCtrl>(Vec(7,190), Port::INPUT, module,Compair::POS_A_IN));
  255. addParam(ParamWidget::create<PvCKnob>(Vec(10,128), module, Compair::WIDTH_A_PARAM, 0.01f , 10.001f, 5.0f));
  256. addInput(Port::create<InPortCtrl>(Vec(35,190), Port::INPUT, module,Compair::WIDTH_A_IN));
  257. addOutput(Port::create<OutPortBin>(Vec(7,278), Port::OUTPUT, module,Compair::GATE_A_OUT));
  258. addChild(Widget::create<LEDback>(Vec(7, 234)));
  259. addChild(ModuleLightWidget::create<CompairLight<BlueLED>>(Vec(8,235),module,Compair::BELOW_A_LED));
  260. addChild(ModuleLightWidget::create<CompairLight<WhiteLED>>(Vec(8,235),module,Compair::GATE_A_LED));
  261. addChild(ModuleLightWidget::create<CompairLight<RedLED>>(Vec(8,235),module,Compair::OVER_A_LED));
  262. addOutput(Port::create<OutPortBin>(Vec(35,278), Port::OUTPUT, module,Compair::NOT_A_OUT));
  263. // B
  264. addInput(Port::create<InPortAud>(Vec(63,234), Port::INPUT, module,Compair::AUDIO_B_IN));
  265. addParam(ParamWidget::create<PvCKnob>(Vec(66,60), module, Compair::POS_B_PARAM, -5.0f, 5.0f, 0.0f));
  266. addInput(Port::create<InPortCtrl>(Vec(90,190), Port::INPUT, module,Compair::POS_B_IN));
  267. addParam(ParamWidget::create<PvCKnob>(Vec(66,128), module, Compair::WIDTH_B_PARAM, 0.01f , 10.001f, 5.0f));
  268. addInput(Port::create<InPortCtrl>(Vec(63,190), Port::INPUT, module,Compair::WIDTH_B_IN));
  269. addOutput(Port::create<OutPortBin>(Vec(90,278), Port::OUTPUT, module,Compair::GATE_B_OUT));
  270. addChild(Widget::create<LEDback>(Vec(90, 234)));
  271. addChild(ModuleLightWidget::create<CompairLight<BlueLED>>(Vec(91,235),module,Compair::BELOW_B_LED));
  272. addChild(ModuleLightWidget::create<CompairLight<WhiteLED>>(Vec(91,235),module,Compair::GATE_B_LED));
  273. addChild(ModuleLightWidget::create<CompairLight<RedLED>>(Vec(91,235),module,Compair::OVER_B_LED));
  274. addOutput(Port::create<OutPortBin>(Vec(63,278), Port::OUTPUT, module,Compair::NOT_B_OUT));
  275. // Invert toggles
  276. addParam(ParamWidget::create<CompairToggle>(Vec(11,238),module,Compair::INVERT_A_PARAM, 0, 1, 0));
  277. addParam(ParamWidget::create<CompairToggle>(Vec(94,238),module,Compair::INVERT_B_PARAM, 0, 1, 0));
  278. // LOGIC
  279. addOutput(Port::create<OutPortBin>(Vec(7,324), Port::OUTPUT, module,Compair::AND_OUT));
  280. addChild(ModuleLightWidget::create<FourPixLight<CyanLED>>(Vec(16,318),module,Compair::AND_LED));
  281. addOutput(Port::create<OutPortBin>(Vec(35,324), Port::OUTPUT, module,Compair::OR_OUT));
  282. addChild(ModuleLightWidget::create<FourPixLight<OrangeLED>>(Vec(44,318),module,Compair::OR_LED));
  283. addOutput(Port::create<OutPortBin>(Vec(63,324), Port::OUTPUT, module,Compair::XOR_OUT));
  284. addChild(ModuleLightWidget::create<FourPixLight<YellowLED>>(Vec(72,318),module,Compair::XOR_LED));
  285. addOutput(Port::create<OutPortBin>(Vec(90,324), Port::OUTPUT, module,Compair::FLIP_OUT));
  286. addChild(ModuleLightWidget::create<FourPixLight<GreenLED>>(Vec(99,318),module,Compair::FLIP_LED));
  287. }*/
  288. struct CompairOutputModeItem : MenuItem {
  289. Compair *compair;
  290. Compair::OutputMode outputMode;
  291. void onAction(EventAction &e) override {
  292. compair->outputMode = outputMode;
  293. }
  294. void step() override {
  295. rightText = (compair->outputMode == outputMode) ? "✔" : "";
  296. }
  297. };
  298. Menu *CompairWidget::createContextMenu() {
  299. Menu *menu = ModuleWidget::createContextMenu();
  300. MenuLabel *spacerLabel = new MenuLabel();
  301. menu->addChild(spacerLabel);
  302. Compair *compair = dynamic_cast<Compair*>(module);
  303. assert(compair);
  304. MenuLabel *modeLabel = new MenuLabel();
  305. modeLabel->text = "Output Mode";
  306. menu->addChild(modeLabel);
  307. CompairOutputModeItem *originalItem = new CompairOutputModeItem();
  308. originalItem->text = "Original [0V..+5V]";
  309. originalItem->compair = compair;
  310. originalItem->outputMode = Compair::ORIGINAL;
  311. menu->addChild(originalItem);
  312. CompairOutputModeItem *bipolarItem = new CompairOutputModeItem();
  313. bipolarItem->text = "Bi-Polar [-5V..+5V]";
  314. bipolarItem->compair = compair;
  315. bipolarItem->outputMode = Compair::BIPOLAR;
  316. menu->addChild(bipolarItem);
  317. return menu;
  318. }
  319. } // namespace rack_plugin_PvC
  320. using namespace rack_plugin_PvC;
  321. RACK_PLUGIN_MODEL_INIT(PvC, Compair) {
  322. Model *modelCompair = Model::create<Compair, CompairWidget>(
  323. "PvC", "Compair", "Compair", LOGIC_TAG, DUAL_TAG, CLOCK_MODULATOR_TAG);
  324. return modelCompair;
  325. }