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.

378 lines
14KB

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