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.

574 lines
17KB

  1. #include "AH.hpp"
  2. #include "Core.hpp"
  3. #include "UI.hpp"
  4. #include "dsp/digital.hpp"
  5. #include <iostream>
  6. namespace rack_plugin_AmalgamatedHarmonics {
  7. struct Progress : AHModule {
  8. const static int NUM_PITCHES = 6;
  9. enum ParamIds {
  10. CLOCK_PARAM,
  11. RUN_PARAM,
  12. RESET_PARAM,
  13. STEPS_PARAM,
  14. ENUMS(ROOT_PARAM,8),
  15. ENUMS(CHORD_PARAM,8),
  16. ENUMS(INV_PARAM,8),
  17. ENUMS(GATE_PARAM,8),
  18. NUM_PARAMS
  19. };
  20. enum InputIds {
  21. KEY_INPUT,
  22. MODE_INPUT,
  23. CLOCK_INPUT,
  24. EXT_CLOCK_INPUT,
  25. RESET_INPUT,
  26. STEPS_INPUT,
  27. NUM_INPUTS
  28. };
  29. enum OutputIds {
  30. GATES_OUTPUT,
  31. ENUMS(PITCH_OUTPUT,6),
  32. ENUMS(GATE_OUTPUT,8),
  33. NUM_OUTPUTS
  34. };
  35. enum LightIds {
  36. RUNNING_LIGHT,
  37. RESET_LIGHT,
  38. GATES_LIGHT,
  39. ENUMS(GATE_LIGHTS,16),
  40. NUM_LIGHTS
  41. };
  42. Progress() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { }
  43. void step() override;
  44. enum ParamType {
  45. ROOT_TYPE,
  46. CHORD_TYPE,
  47. INV_TYPE
  48. };
  49. void receiveEvent(ParamEvent e) override {
  50. if (receiveEvents && e.pType != -1) { // AHParamWidgets that are no config through set<>() have a pType of -1
  51. if (modeMode) {
  52. paramState = "> " +
  53. CoreUtil().noteNames[currRoot[e.pId]] +
  54. CoreUtil().ChordTable[currChord[e.pId]].quality + " " +
  55. CoreUtil().inversionNames[currInv[e.pId]] + " " + "[" +
  56. CoreUtil().degreeNames[currDegree[e.pId] * 3 + currQuality[e.pId]] + "]";
  57. } else {
  58. paramState = "> " +
  59. CoreUtil().noteNames[currRoot[e.pId]] +
  60. CoreUtil().ChordTable[currChord[e.pId]].quality + " " +
  61. CoreUtil().inversionNames[currInv[e.pId]];
  62. }
  63. }
  64. keepStateDisplay = 0;
  65. }
  66. json_t *toJson() override {
  67. json_t *rootJ = json_object();
  68. // running
  69. json_object_set_new(rootJ, "running", json_boolean(running));
  70. // gates
  71. json_t *gatesJ = json_array();
  72. for (int i = 0; i < 8; i++) {
  73. json_t *gateJ = json_integer((int) gates[i]);
  74. json_array_append_new(gatesJ, gateJ);
  75. }
  76. json_object_set_new(rootJ, "gates", gatesJ);
  77. // gateMode
  78. json_t *gateModeJ = json_integer((int) gateMode);
  79. json_object_set_new(rootJ, "gateMode", gateModeJ);
  80. return rootJ;
  81. }
  82. void fromJson(json_t *rootJ) override {
  83. // running
  84. json_t *runningJ = json_object_get(rootJ, "running");
  85. if (runningJ)
  86. running = json_is_true(runningJ);
  87. // gates
  88. json_t *gatesJ = json_object_get(rootJ, "gates");
  89. if (gatesJ) {
  90. for (int i = 0; i < 8; i++) {
  91. json_t *gateJ = json_array_get(gatesJ, i);
  92. if (gateJ)
  93. gates[i] = !!json_integer_value(gateJ);
  94. }
  95. }
  96. // gateMode
  97. json_t *gateModeJ = json_object_get(rootJ, "gateMode");
  98. if (gateModeJ)
  99. gateMode = (GateMode)json_integer_value(gateModeJ);
  100. }
  101. bool running = true;
  102. // for external clock
  103. SchmittTrigger clockTrigger;
  104. // For buttons
  105. SchmittTrigger runningTrigger;
  106. SchmittTrigger resetTrigger;
  107. SchmittTrigger gateTriggers[8];
  108. PulseGenerator gatePulse;
  109. /** Phase of internal LFO */
  110. float phase = 0.0f;
  111. // Step index
  112. int index = 0;
  113. bool gates[8] = {true,true,true,true,true,true,true,true};
  114. float resetLight = 0.0f;
  115. float gateLight = 0.0f;
  116. float stepLights[8] = {};
  117. enum GateMode {
  118. TRIGGER,
  119. RETRIGGER,
  120. CONTINUOUS,
  121. };
  122. GateMode gateMode = CONTINUOUS;
  123. bool modeMode = false;
  124. bool prevModeMode = false;
  125. int offset = 24; // Repeated notes in chord and expressed in the chord definition as being transposed 2 octaves lower.
  126. // When played this offset needs to be removed (or the notes removed, or the notes transposed to an octave higher)
  127. float prevRootInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0};
  128. float prevChrInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0};
  129. float prevDegreeInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0};
  130. float prevQualityInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0};
  131. float prevInvInput[8] = {-100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0, -100.0};
  132. float currRootInput[8];
  133. float currChrInput[8];
  134. float currDegreeInput[8];
  135. float currQualityInput[8];
  136. float currInvInput[8];
  137. int currMode;
  138. int currKey;
  139. int prevMode = -1;
  140. int prevKey = -1;
  141. int currRoot[8];
  142. int currChord[8];
  143. int currInv[8];
  144. int currDegree[8];
  145. int currQuality[8];
  146. float pitches[8][6];
  147. float oldPitches[6];
  148. void reset() override {
  149. for (int i = 0; i < 8; i++) {
  150. gates[i] = true;
  151. }
  152. }
  153. void setIndex(int index, int nSteps) {
  154. phase = 0.0f;
  155. this->index = index;
  156. if (this->index >= nSteps) {
  157. this->index = 0;
  158. }
  159. this->gatePulse.trigger(Core::TRIGGER);
  160. }
  161. };
  162. void Progress::step() {
  163. AHModule::step();
  164. // Run
  165. if (runningTrigger.process(params[RUN_PARAM].value)) {
  166. running = !running;
  167. }
  168. int numSteps = (int) clamp(roundf(params[STEPS_PARAM].value + inputs[STEPS_INPUT].value), 1.0f, 8.0f);
  169. if (running) {
  170. if (inputs[EXT_CLOCK_INPUT].active) {
  171. // External clock
  172. if (clockTrigger.process(inputs[EXT_CLOCK_INPUT].value)) {
  173. setIndex(index + 1, numSteps);
  174. }
  175. }
  176. else {
  177. // Internal clock
  178. float clockTime = powf(2.0f, params[CLOCK_PARAM].value + inputs[CLOCK_INPUT].value);
  179. phase += clockTime * delta;
  180. if (phase >= 1.0f) {
  181. setIndex(index + 1, numSteps);
  182. }
  183. }
  184. }
  185. // Reset
  186. if (resetTrigger.process(params[RESET_PARAM].value + inputs[RESET_INPUT].value)) {
  187. setIndex(0, numSteps);
  188. }
  189. bool haveRoot = false;
  190. bool haveMode = false;
  191. // index is our current step
  192. if (inputs[KEY_INPUT].active) {
  193. float fRoot = inputs[KEY_INPUT].value;
  194. currKey = CoreUtil().getKeyFromVolts(fRoot);
  195. haveRoot = true;
  196. }
  197. if (inputs[MODE_INPUT].active) {
  198. float fMode = inputs[MODE_INPUT].value;
  199. currMode = CoreUtil().getModeFromVolts(fMode);
  200. haveMode = true;
  201. }
  202. modeMode = haveRoot && haveMode;
  203. if (modeMode && ((prevMode != currMode) || (prevKey != currKey))) { // Input changes so force re-read
  204. for (int step = 0; step < 8; step++) {
  205. prevDegreeInput[step] = -100.0;
  206. prevQualityInput[step] = -100.0;
  207. }
  208. prevMode = currMode;
  209. prevKey = currKey;
  210. }
  211. // Read inputs
  212. for (int step = 0; step < 8; step++) {
  213. if (modeMode) {
  214. currDegreeInput[step] = params[CHORD_PARAM + step].value;
  215. currQualityInput[step] = params[ROOT_PARAM + step].value;
  216. if (prevModeMode != modeMode) { // Switching mode, so reset history to ensure re-read on return
  217. prevChrInput[step] = -100.0;
  218. prevRootInput[step] = -100.0;
  219. }
  220. } else {
  221. currChrInput[step] = params[CHORD_PARAM + step].value;
  222. currRootInput[step] = params[ROOT_PARAM + step].value;
  223. if (prevModeMode != modeMode) { // Switching mode, so reset history to ensure re-read on return
  224. prevDegreeInput[step] = -100.0;
  225. prevQualityInput[step] = -100.0;
  226. }
  227. }
  228. currInvInput[step] = params[INV_PARAM + step].value;
  229. }
  230. // Remember mode
  231. prevModeMode = modeMode;
  232. // Check for changes on all steps
  233. for (int step = 0; step < 8; step++) {
  234. bool update = false;
  235. if (modeMode) {
  236. currDegreeInput[step] = params[ROOT_PARAM + step].value;
  237. currQualityInput[step] = params[CHORD_PARAM + step].value;
  238. if (prevDegreeInput[step] != currDegreeInput[step]) {
  239. prevDegreeInput[step] = currDegreeInput[step];
  240. update = true;
  241. }
  242. if (prevQualityInput[step] != currQualityInput[step]) {
  243. prevQualityInput[step] = currQualityInput[step];
  244. update = true;
  245. }
  246. if (update) {
  247. // Get Degree (I- VII)
  248. currDegree[step] = round(rescale(fabs(currDegreeInput[step]), 0.0f, 10.0f, 0.0f, Core::NUM_DEGREES - 1));
  249. // From the input root, mode and degree, we can get the root chord note and quality (Major,Minor,Diminshed)
  250. CoreUtil().getRootFromMode(currMode,currKey,currDegree[step],&currRoot[step],&currQuality[step]);
  251. // Now get the actual chord from the main list
  252. switch(currQuality[step]) {
  253. case Core::MAJ:
  254. currChord[step] = round(rescale(fabs(currQualityInput[step]), 0.0f, 10.0f, 1.0f, 70.0f));
  255. break;
  256. case Core::MIN:
  257. currChord[step] = round(rescale(fabs(currQualityInput[step]), 0.0f, 10.0f, 71.0f, 90.0f));
  258. break;
  259. case Core::DIM:
  260. currChord[step] = round(rescale(fabs(currQualityInput[step]), 0.0f, 10.0f, 91.0f, 98.0f));
  261. break;
  262. }
  263. }
  264. } else {
  265. // Chord Mode
  266. // If anything has changed, recalculate output for that step
  267. if (prevRootInput[step] != currRootInput[step]) {
  268. prevRootInput[step] = currRootInput[step];
  269. currRoot[step] = round(rescale(fabs(currRootInput[step]), 0.0f, 10.0f, 0.0f, Core::NUM_NOTES - 1)); // Param range is 0 to 10, mapped to 0 to 11
  270. update = true;
  271. }
  272. if (prevChrInput[step] != currChrInput[step]) {
  273. prevChrInput[step] = currChrInput[step];
  274. currChord[step] = round(rescale(fabs(currChrInput[step]), 0.0f, 10.0f, 1.0f, 98.0f)); // Param range is 0 to 10
  275. update = true;
  276. }
  277. }
  278. // Inversions remain the same between Chord and Mode mode
  279. if (prevInvInput[step] != currInvInput[step]) {
  280. prevInvInput[step] = currInvInput[step];
  281. currInv[step] = currInvInput[step];
  282. update = true;
  283. }
  284. // So, after all that, we calculate the pitch output
  285. if (update) {
  286. int *chordArray;
  287. // Get the array of pitches based on the inversion
  288. switch(currInv[step]) {
  289. case Core::ROOT: chordArray = CoreUtil().ChordTable[currChord[step]].root; break;
  290. case Core::FIRST_INV: chordArray = CoreUtil().ChordTable[currChord[step]].first; break;
  291. case Core::SECOND_INV: chordArray = CoreUtil().ChordTable[currChord[step]].second; break;
  292. default: chordArray = CoreUtil().ChordTable[currChord[step]].root;
  293. }
  294. for (int j = 0; j < NUM_PITCHES; j++) {
  295. // Set the pitches for this step. If the chord has less than 6 notes, the empty slots are
  296. // filled with repeated notes. These notes are identified by a 24 semi-tome negative
  297. // offset. We correct for that offset now, pitching thaem back into the original octave.
  298. // They could be pitched into the octave above (or below)
  299. if (chordArray[j] < 0) {
  300. pitches[step][j] = CoreUtil().getVoltsFromPitch(chordArray[j] + offset,currRoot[step]);
  301. } else {
  302. pitches[step][j] = CoreUtil().getVoltsFromPitch(chordArray[j],currRoot[step]);
  303. }
  304. }
  305. }
  306. }
  307. bool pulse = gatePulse.process(delta);
  308. // Gate buttons
  309. for (int i = 0; i < 8; i++) {
  310. if (gateTriggers[i].process(params[GATE_PARAM + i].value)) {
  311. gates[i] = !gates[i];
  312. }
  313. bool gateOn = (running && i == index && gates[i]);
  314. if (gateMode == TRIGGER) {
  315. gateOn = gateOn && pulse;
  316. } else if (gateMode == RETRIGGER) {
  317. gateOn = gateOn && !pulse;
  318. }
  319. outputs[GATE_OUTPUT + i].value = gateOn ? 10.0f : 0.0f;
  320. if (i == index) {
  321. if (gates[i]) {
  322. // Gate is on and active = flash green
  323. lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(1.0f);
  324. lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(0.0f);
  325. } else {
  326. // Gate is off and active = flash dull yellow
  327. lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(0.20f);
  328. lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(0.20f);
  329. }
  330. } else {
  331. if (gates[i]) {
  332. // Gate is on and not active = red
  333. lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(0.0f);
  334. lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(1.0f);
  335. } else {
  336. // Gate is off and not active = black
  337. lights[GATE_LIGHTS + i * 2].setBrightnessSmooth(0.0f);
  338. lights[GATE_LIGHTS + i * 2 + 1].setBrightnessSmooth(0.0f);
  339. }
  340. }
  341. }
  342. bool gatesOn = (running && gates[index]);
  343. if (gateMode == TRIGGER) {
  344. gatesOn = gatesOn && pulse;
  345. } else if (gateMode == RETRIGGER) {
  346. gatesOn = gatesOn && !pulse;
  347. }
  348. // Outputs
  349. outputs[GATES_OUTPUT].value = gatesOn ? 10.0f : 0.0f;
  350. lights[RUNNING_LIGHT].value = (running);
  351. lights[RESET_LIGHT].setBrightnessSmooth(resetTrigger.isHigh());
  352. lights[GATES_LIGHT].setBrightnessSmooth(pulse);
  353. for (int i = 0; i < NUM_PITCHES; i++) {
  354. outputs[PITCH_OUTPUT + i].value = pitches[index][i];
  355. }
  356. }
  357. struct ProgressWidget : ModuleWidget {
  358. ProgressWidget(Progress *module);
  359. Menu *createContextMenu() override;
  360. };
  361. ProgressWidget::ProgressWidget(Progress *module) : ModuleWidget(module) {
  362. UI ui;
  363. box.size = Vec(15*26, 380);
  364. {
  365. SVGPanel *panel = new SVGPanel();
  366. panel->box.size = box.size;
  367. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Progress.svg")));
  368. addChild(panel);
  369. }
  370. {
  371. StateDisplay *display = new StateDisplay();
  372. display->module = module;
  373. display->box.pos = Vec(0, 135);
  374. display->box.size = Vec(100, 140);
  375. addChild(display);
  376. }
  377. addParam(ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, 0, 0, true, false), module, Progress::CLOCK_PARAM, -2.0, 6.0, 2.0));
  378. addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, 1, 0, true, false), module, Progress::RUN_PARAM, 0.0, 1.0, 0.0));
  379. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 1, 0, true, false), module, Progress::RUNNING_LIGHT));
  380. addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, 2, 0, true, false), module, Progress::RESET_PARAM, 0.0, 1.0, 0.0));
  381. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 2, 0, true, false), module, Progress::RESET_LIGHT));
  382. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 3, 0, true, false), module, Progress::STEPS_PARAM, 1.0, 8.0, 8.0));
  383. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 4, 0, true, false), module, Progress::GATES_LIGHT));
  384. // static const float portX[13] = {20, 58, 96, 135, 173, 212, 250, 288, 326, 364, 402, 440, 478};
  385. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 1, true, false), Port::INPUT, module, Progress::CLOCK_INPUT));
  386. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, 1, true, false), Port::INPUT, module, Progress::EXT_CLOCK_INPUT));
  387. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 1, true, false), Port::INPUT, module, Progress::RESET_INPUT));
  388. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, 1, true, false), Port::INPUT, module, Progress::STEPS_INPUT));
  389. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 1, true, false), Port::INPUT, module, Progress::KEY_INPUT));
  390. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 1, true, false), Port::INPUT, module, Progress::MODE_INPUT));
  391. for (int i = 0; i < 3; i++) {
  392. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 7 + i, 0, true, false), Port::OUTPUT, module, Progress::PITCH_OUTPUT + i));
  393. }
  394. for (int i = 0; i < 3; i++) {
  395. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 7 + i, 1, true, false), Port::OUTPUT, module, Progress::PITCH_OUTPUT + 3 + i));
  396. }
  397. for (int i = 0; i < 8; i++) {
  398. AHKnobNoSnap *rootW = ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, i + 1, 4, true, true), module, Progress::ROOT_PARAM + i, 0.0, 10.0, 0.0);
  399. AHParamWidget::set<AHKnobNoSnap>(rootW, Progress::ROOT_TYPE, i);
  400. addParam(rootW);
  401. AHKnobNoSnap *chordW = ParamWidget::create<AHKnobNoSnap>(ui.getPosition(UI::KNOB, i + 1, 5, true, true), module, Progress::CHORD_PARAM + i, 0.0, 10.0, 0.0);
  402. AHParamWidget::set<AHKnobNoSnap>(chordW, Progress::CHORD_TYPE, i);
  403. addParam(chordW);
  404. AHKnobSnap *invW = ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, i + 1, 6, true, true), module, Progress::INV_PARAM + i, 0.0, 2.0, 0.0);
  405. AHParamWidget::set<AHKnobSnap>(invW, Progress::INV_TYPE, i);
  406. addParam(invW);
  407. addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, i + 1, 7, true, true), module, Progress::GATE_PARAM + i, 0.0, 1.0, 0.0));
  408. addChild(ModuleLightWidget::create<MediumLight<GreenRedLight>>(ui.getPosition(UI::LIGHT, i + 1, 7, true, true), module, Progress::GATE_LIGHTS + i * 2));
  409. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, i + 1, 5, true, false), Port::OUTPUT, module, Progress::GATE_OUTPUT + i));
  410. }
  411. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 9, 5, true, false), Port::OUTPUT, module, Progress::GATES_OUTPUT));
  412. }
  413. struct ProgressGateModeItem : MenuItem {
  414. Progress *progress;
  415. Progress::GateMode gateMode;
  416. void onAction(EventAction &e) override {
  417. progress->gateMode = gateMode;
  418. }
  419. void step() override {
  420. rightText = (progress->gateMode == gateMode) ? "✔" : "";
  421. }
  422. };
  423. Menu *ProgressWidget::createContextMenu() {
  424. Menu *menu = ModuleWidget::createContextMenu();
  425. MenuLabel *spacerLabel = new MenuLabel();
  426. menu->addChild(spacerLabel);
  427. Progress *progress = dynamic_cast<Progress*>(module);
  428. assert(progress);
  429. MenuLabel *modeLabel = new MenuLabel();
  430. modeLabel->text = "Gate Mode";
  431. menu->addChild(modeLabel);
  432. ProgressGateModeItem *triggerItem = new ProgressGateModeItem();
  433. triggerItem->text = "Trigger";
  434. triggerItem->progress = progress;
  435. triggerItem->gateMode = Progress::TRIGGER;
  436. menu->addChild(triggerItem);
  437. ProgressGateModeItem *retriggerItem = new ProgressGateModeItem();
  438. retriggerItem->text = "Retrigger";
  439. retriggerItem->progress = progress;
  440. retriggerItem->gateMode = Progress::RETRIGGER;
  441. menu->addChild(retriggerItem);
  442. ProgressGateModeItem *continuousItem = new ProgressGateModeItem();
  443. continuousItem->text = "Continuous";
  444. continuousItem->progress = progress;
  445. continuousItem->gateMode = Progress::CONTINUOUS;
  446. menu->addChild(continuousItem);
  447. return menu;
  448. }
  449. } // namespace rack_plugin_AmalgamatedHarmonics
  450. using namespace rack_plugin_AmalgamatedHarmonics;
  451. RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Progress) {
  452. Model *modelProgress = Model::create<Progress, ProgressWidget>( "Amalgamated Harmonics", "Progress", "Progress", SEQUENCER_TAG);
  453. return modelProgress;
  454. }