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.

375 lines
13KB

  1. //============================================================================================================
  2. //!
  3. //! \file Chord-G1.cpp
  4. //!
  5. //! \brief Chord-G1 genearates chords via a CV program selection and a fundamental bass note V/octave input.
  6. //!
  7. //============================================================================================================
  8. #include "Gratrix.hpp"
  9. #include "dsp/digital.hpp"
  10. namespace rack_plugin_Gratrix {
  11. //============================================================================================================
  12. //! \brief Some settings.
  13. enum Spec
  14. {
  15. E = 12, // ET
  16. T = 25, // ET
  17. N = GTX__N
  18. };
  19. //============================================================================================================
  20. //! \brief The module.
  21. struct GtxModule_Chord_G1 : Module
  22. {
  23. enum ParamIds {
  24. PROG_PARAM = 0,
  25. NOTE_PARAM = PROG_PARAM + 1,
  26. NUM_PARAMS = NOTE_PARAM + T
  27. };
  28. enum InputIds {
  29. PROG_INPUT, // 1
  30. GATE_INPUT, // 1
  31. VOCT_INPUT, // 1
  32. NUM_INPUTS,
  33. OFF_INPUTS = VOCT_INPUT
  34. };
  35. enum OutputIds {
  36. GATE_OUTPUT, // N
  37. VOCT_OUTPUT, // N
  38. NUM_OUTPUTS,
  39. OFF_OUTPUTS = GATE_OUTPUT
  40. };
  41. enum LightIds {
  42. PROG_LIGHT = 0,
  43. NOTE_LIGHT = PROG_LIGHT + 2*E,
  44. FUND_LIGHT = NOTE_LIGHT + 2*T,
  45. NUM_LIGHTS = FUND_LIGHT + E
  46. };
  47. struct Decode
  48. {
  49. /*static constexpr*/ float e = static_cast<float>(E); // Static constexpr gives
  50. /*static constexpr*/ float s = 1.0f / e; // link error on Mac build.
  51. float in = 0; //!< Raw input.
  52. float out = 0; //!< Input quantized.
  53. int note = 0; //!< Integer note (offset midi note).
  54. int key = 0; //!< C, C#, D, D#, etc.
  55. int oct = 0; //!< Octave (C4 = 0).
  56. void step(float input)
  57. {
  58. int safe, fnote;
  59. in = input;
  60. fnote = std::floor(in * e + 0.5f);
  61. out = fnote * s;
  62. note = static_cast<int>(fnote);
  63. safe = note + (E * 1000); // push away from negative numbers
  64. key = safe % E;
  65. oct = (safe / E) - 1000;
  66. }
  67. };
  68. Decode prg_prm;
  69. Decode prg_cv;
  70. Decode input;
  71. SchmittTrigger note_trigger[T];
  72. bool note_enable[E][T] = {};
  73. float gen[N] = {0,1,2,3,4,5};
  74. //--------------------------------------------------------------------------------------------------------
  75. //! \brief Constructor.
  76. GtxModule_Chord_G1()
  77. :
  78. Module(NUM_PARAMS, NUM_INPUTS, N * NUM_OUTPUTS, NUM_LIGHTS)
  79. {}
  80. //--------------------------------------------------------------------------------------------------------
  81. //! \brief Save data.
  82. json_t *toJson() override
  83. {
  84. json_t *rootJ = json_object();
  85. if (json_t *neJA = json_array())
  86. {
  87. for (std::size_t i = 0; i < E; ++i)
  88. {
  89. for (std::size_t j = 0; j < T; ++j)
  90. {
  91. if (json_t *neJI = json_integer((int) note_enable[i][j]))
  92. {
  93. json_array_append_new(neJA, neJI);
  94. }
  95. }
  96. }
  97. json_object_set_new(rootJ, "note_enable", neJA);
  98. }
  99. return rootJ;
  100. }
  101. //--------------------------------------------------------------------------------------------------------
  102. //! \brief Load data.
  103. void fromJson(json_t *rootJ) override
  104. {
  105. // Note enable
  106. if (json_t *neJA = json_object_get(rootJ, "note_enable"))
  107. {
  108. for (std::size_t i = 0; i < E; ++i)
  109. {
  110. for (std::size_t j = 0; j < T; ++j)
  111. {
  112. if (json_t *neJI = json_array_get(neJA, i*T+j))
  113. {
  114. note_enable[i][j] = !!json_integer_value(neJI);
  115. }
  116. }
  117. }
  118. }
  119. }
  120. //--------------------------------------------------------------------------------------------------------
  121. //! \brief Output map.
  122. static constexpr std::size_t omap(std::size_t port, std::size_t bank)
  123. {
  124. return port + bank * NUM_OUTPUTS;
  125. }
  126. //--------------------------------------------------------------------------------------------------------
  127. //! \brief Step function.
  128. void step() override
  129. {
  130. // Clear all lights
  131. float leds[NUM_LIGHTS] = {};
  132. // Decode inputs and params
  133. bool act_prm = false;
  134. if (params[PROG_PARAM].value < 12.0f)
  135. {
  136. prg_prm.step(params[PROG_PARAM].value / 12.0f);
  137. act_prm = true;
  138. }
  139. prg_cv.step(inputs[PROG_INPUT].value);
  140. input .step(inputs[VOCT_INPUT].value);
  141. float gate = clamp(inputs[GATE_INPUT].normalize(10.0f), 0.0f, 10.0f);
  142. // Input leds
  143. if (act_prm)
  144. {
  145. leds[PROG_LIGHT + prg_prm.key*2] = 1.0f; // Green
  146. }
  147. else
  148. {
  149. leds[PROG_LIGHT + prg_cv.key*2+1] = 1.0f; // Red
  150. }
  151. leds[FUND_LIGHT + input.key] = 1.0f; // Red
  152. // Chord bit
  153. if (act_prm)
  154. {
  155. // Detect buttons and deduce what's enabled
  156. for (std::size_t j = 0; j < T; ++j)
  157. {
  158. if (note_trigger[j].process(params[j + NOTE_PARAM].value))
  159. {
  160. note_enable[prg_prm.key][j] = !note_enable[prg_prm.key][j];
  161. }
  162. }
  163. for (std::size_t j = 0, b = 0; j < T; ++j)
  164. {
  165. if (note_enable[prg_prm.key][j])
  166. {
  167. if (b++ >= N)
  168. {
  169. note_enable[prg_prm.key][j] = false;
  170. }
  171. }
  172. }
  173. }
  174. // Based on what's enabled turn on leds
  175. if (act_prm)
  176. {
  177. for (std::size_t j = 0; j < T; ++j)
  178. {
  179. if (note_enable[prg_prm.key][j])
  180. {
  181. leds[NOTE_LIGHT + j*2] = 1.0f; // Green
  182. }
  183. }
  184. }
  185. else
  186. {
  187. for (std::size_t j = 0; j < T; ++j)
  188. {
  189. if (note_enable[prg_cv.key][j])
  190. {
  191. leds[NOTE_LIGHT + j*2+1] = 1.0f; // Red
  192. }
  193. }
  194. }
  195. // Based on what's enabled generate output
  196. {
  197. std::size_t i = 0;
  198. for (std::size_t j = 0; j < T; ++j)
  199. {
  200. if (note_enable[prg_cv.key][j])
  201. {
  202. outputs[omap(GATE_OUTPUT, i)].value = gate;
  203. outputs[omap(VOCT_OUTPUT, i)].value = input.out + static_cast<float>(j) / 12.0f;
  204. ++i;
  205. }
  206. }
  207. while (i < N)
  208. {
  209. outputs[omap(GATE_OUTPUT, i)].value = 0.0f;
  210. outputs[omap(VOCT_OUTPUT, i)].value = 0.0f; // is this a good value?
  211. ++i;
  212. }
  213. }
  214. // Write output in one go, seems to prevent flicker
  215. for (std::size_t i=0; i<NUM_LIGHTS; ++i)
  216. {
  217. lights[i].value = leds[i];
  218. }
  219. }
  220. };
  221. //============================================================================================================
  222. static double x0(double shift = 0) { return 6+6*15 + shift * 66; }
  223. static double xd(double i, double radius = 37.0, double spill = 1.65) { return (x0() + (radius + spill * i) * dx(i, E)); }
  224. static double yd(double i, double radius = 37.0, double spill = 1.65) { return (gy(1.5) + (radius + spill * i) * dy(i, E)); }
  225. //============================================================================================================
  226. //! \brief The widget.
  227. struct GtxWidget_Chord_G1 : ModuleWidget
  228. {
  229. GtxWidget_Chord_G1(GtxModule_Chord_G1 *module) : ModuleWidget(module)
  230. {
  231. GTX__WIDGET();
  232. box.size = Vec(18*15, 380);
  233. #if GTX__SAVE_SVG
  234. {
  235. PanelGen pg(assetPlugin(plugin, "build/res/Chord-G1.svg"), box.size, "CHORD-G1");
  236. pg.nob_med_raw(x0(), fy(-0.28), "PROGRAM");
  237. pg.prt_in_raw (x0(-1), fy(-0.28), "CV");
  238. pg.prt_in_raw (x0(+1), fy(-0.28), "SELECT");
  239. pg.nob_med_raw(x0(), fy(+0.28), "BASS NOTE");
  240. pg.prt_in_raw (x0(-1), fy(+0.28), "V/OCT");
  241. pg.prt_in_raw (x0(+1), fy(+0.28), "GATE");
  242. pg.bus_inx(0.5, 1, "CHORD NOTES");
  243. pg.bus_out(2.0, 1, "GATE");
  244. pg.bus_out(2.0, 2, "V/OCT");
  245. for (double i=0.0; i<T-1.0; i+=0.1)
  246. {
  247. pg.line(Vec(xd(i), yd(i)), Vec(xd(i+0.1), yd(i+0.1)), "fill:none;stroke:#7092BE;stroke-width:2");
  248. }
  249. pg.line(Vec(x0(), gy(1.5)), Vec(xd(24), yd(24)), "fill:none;stroke:#7092BE;stroke-width:4");
  250. pg.line(Vec(x0(), gy(1.5)), Vec(xd(16), yd(16)), "fill:none;stroke:#7092BE;stroke-width:4");
  251. pg.line(Vec(x0(), gy(1.5)), Vec(xd(19), yd(19)), "fill:none;stroke:#7092BE;stroke-width:4");
  252. pg.line(Vec(x0(), gy(1.5)), Vec(xd(22), yd(22)), "fill:none;stroke:#7092BE;stroke-width:4");
  253. }
  254. #endif
  255. setPanel(SVG::load(assetPlugin(plugin, "res/Chord-G1.svg")));
  256. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  257. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0)));
  258. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  259. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365)));
  260. addParam(createParamGTX<KnobSnapSml>(Vec(x0(+1), fy(-0.28)), module, GtxModule_Chord_G1::PROG_PARAM, 0.0f, 12.0f, 12.0f));
  261. addInput(createInputGTX<PortInMed> (Vec(x0(-1), fy(-0.28)), module, GtxModule_Chord_G1::PROG_INPUT));
  262. addInput(createInputGTX<PortInMed> (Vec(x0(+1), fy(+0.28)), module, GtxModule_Chord_G1::GATE_INPUT));
  263. addInput(createInputGTX<PortInMed> (Vec(x0(-1), fy(+0.28)), module, GtxModule_Chord_G1::VOCT_INPUT));
  264. for (std::size_t i=0; i<N; ++i)
  265. {
  266. addOutput(createOutputGTX<PortOutMed>(Vec(px(2, i), py(1, i)), module, GtxModule_Chord_G1::omap(GtxModule_Chord_G1::GATE_OUTPUT, i)));
  267. addOutput(createOutputGTX<PortOutMed>(Vec(px(2, i), py(2, i)), module, GtxModule_Chord_G1::omap(GtxModule_Chord_G1::VOCT_OUTPUT, i)));
  268. }
  269. for (std::size_t i=0; i<T; ++i)
  270. {
  271. addParam(ParamWidget::create<LEDButton>(but(xd(i), yd(i)), module, i + GtxModule_Chord_G1::NOTE_PARAM, 0.0f, 1.0f, 0.0f));
  272. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(l_m(xd(i), yd(i)), module, GtxModule_Chord_G1::NOTE_LIGHT + i*2));
  273. }
  274. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() - 30, fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 0*2)); // C
  275. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() - 25, fy(-0.28) - 5), module, GtxModule_Chord_G1::PROG_LIGHT + 1*2)); // C#
  276. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() - 20, fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 2*2)); // D
  277. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() - 15, fy(-0.28) - 5), module, GtxModule_Chord_G1::PROG_LIGHT + 3*2)); // Eb
  278. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() - 10, fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 4*2)); // E
  279. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() , fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 5*2)); // F
  280. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() + 5, fy(-0.28) - 5), module, GtxModule_Chord_G1::PROG_LIGHT + 6*2)); // Fs
  281. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() + 10, fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 7*2)); // G
  282. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() + 15, fy(-0.28) - 5), module, GtxModule_Chord_G1::PROG_LIGHT + 8*2)); // Ab
  283. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() + 20, fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 9*2)); // A
  284. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() + 25, fy(-0.28) - 5), module, GtxModule_Chord_G1::PROG_LIGHT + 10*2)); // Bb
  285. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(l_s(x0() + 30, fy(-0.28) + 5), module, GtxModule_Chord_G1::PROG_LIGHT + 11*2)); // B
  286. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() - 30, fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 0)); // C
  287. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() - 25, fy(+0.28) - 5), module, GtxModule_Chord_G1::FUND_LIGHT + 1)); // C#
  288. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() - 20, fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 2)); // D
  289. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() - 15, fy(+0.28) - 5), module, GtxModule_Chord_G1::FUND_LIGHT + 3)); // Eb
  290. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() - 10, fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 4)); // E
  291. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() , fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 5)); // F
  292. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() + 5, fy(+0.28) - 5), module, GtxModule_Chord_G1::FUND_LIGHT + 6)); // Fs
  293. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() + 10, fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 7)); // G
  294. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() + 15, fy(+0.28) - 5), module, GtxModule_Chord_G1::FUND_LIGHT + 8)); // Ab
  295. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() + 20, fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 9)); // A
  296. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() + 25, fy(+0.28) - 5), module, GtxModule_Chord_G1::FUND_LIGHT + 10)); // Bb
  297. addChild(ModuleLightWidget::create<SmallLight< RedLight>>(l_s(x0() + 30, fy(+0.28) + 5), module, GtxModule_Chord_G1::FUND_LIGHT + 11)); // B
  298. }
  299. };
  300. } // namespace rack_plugin_Gratrix
  301. using namespace rack_plugin_Gratrix;
  302. RACK_PLUGIN_MODEL_INIT(Gratrix, Chord_G1) {
  303. Model *model = Model::create<GtxModule_Chord_G1, GtxWidget_Chord_G1>("Gratrix", "Chord-G1", "Chord-G1", SYNTH_VOICE_TAG); // right tag?
  304. return model;
  305. }