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.

666 lines
16KB

  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 Galaxy : AHModule {
  8. const static int NUM_PITCHES = 6;
  9. const static int N_QUALITIES = 6;
  10. const static int N_NOTES = 12;
  11. const static int QMAP_SIZE = 20;
  12. std::string degNames[42] { // Degree * 3 + Quality
  13. "I",
  14. "I7",
  15. "im7",
  16. "IM7",
  17. "i",
  18. "i°",
  19. "II",
  20. "II7",
  21. "iim7",
  22. "IIM7",
  23. "ii",
  24. "ii°",
  25. "III",
  26. "III7",
  27. "iiim7",
  28. "IIIM7",
  29. "iii",
  30. "iii°",
  31. "IV",
  32. "IV7",
  33. "ivm7",
  34. "IVM7",
  35. "iv",
  36. "iv°",
  37. "V",
  38. "V7",
  39. "vm7",
  40. "VM7",
  41. "v",
  42. "v°",
  43. "VI",
  44. "VI7",
  45. "vim7",
  46. "VIM7",
  47. "vi",
  48. "vi°",
  49. "VII",
  50. "VII7",
  51. "viim7",
  52. "VIIM7",
  53. "vii",
  54. "vii°"
  55. };
  56. enum ParamIds {
  57. KEY_PARAM,
  58. MODE_PARAM,
  59. BAD_PARAM,
  60. NUM_PARAMS
  61. };
  62. enum InputIds {
  63. MOVE_INPUT,
  64. KEY_INPUT,
  65. MODE_INPUT,
  66. NUM_INPUTS
  67. };
  68. enum OutputIds {
  69. ENUMS(PITCH_OUTPUT,6),
  70. NUM_OUTPUTS
  71. };
  72. enum LightIds {
  73. ENUMS(NOTE_LIGHT,72),
  74. ENUMS(BAD_LIGHT,2),
  75. NUM_LIGHTS
  76. };
  77. Galaxy() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  78. void step() override;
  79. void getFromRandom();
  80. void getFromKey();
  81. void getFromKeyMode();
  82. json_t *toJson() override {
  83. json_t *rootJ = json_object();
  84. // offset
  85. json_t *offsetJ = json_integer((int) offset);
  86. json_object_set_new(rootJ, "offset", offsetJ);
  87. // mode
  88. json_t *modeJ = json_integer((int) mode);
  89. json_object_set_new(rootJ, "mode", modeJ);
  90. // inversions
  91. json_t *inversionsJ = json_integer((int) allowedInversions);
  92. json_object_set_new(rootJ, "inversions", inversionsJ);
  93. return rootJ;
  94. }
  95. void fromJson(json_t *rootJ) override {
  96. // offset
  97. json_t *offsetJ = json_object_get(rootJ, "offset");
  98. if (offsetJ)
  99. offset = json_integer_value(offsetJ);
  100. // mode
  101. json_t *modeJ = json_object_get(rootJ, "mode");
  102. if (modeJ)
  103. mode = json_integer_value(modeJ);
  104. // mode
  105. json_t *inversionsJ = json_object_get(rootJ, "inversions");
  106. if (inversionsJ)
  107. allowedInversions = json_integer_value(inversionsJ);
  108. }
  109. Core core;
  110. int ChordTable[N_QUALITIES] = { 1, 31, 78, 25, 71, 91 }; // M, 7, m7, M7, m, dim
  111. int QualityMap[3][QMAP_SIZE] = {
  112. {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,3,1},
  113. {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,2,2,1},
  114. {5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5}
  115. };
  116. int InversionMap[3][QMAP_SIZE] = {
  117. {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
  118. {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1},
  119. {0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,2,2},
  120. };
  121. float outVolts[NUM_PITCHES];
  122. int poll = 50000;
  123. SchmittTrigger moveTrigger;
  124. int degree = 0;
  125. int quality = 0;
  126. int noteIndex = 0;
  127. int inversion = 0;
  128. int lastQuality = 0;
  129. int lastNoteIndex = 0;
  130. int lastInversion = 0;
  131. int currRoot = 1;
  132. int currMode = 1;
  133. int light = 0;
  134. bool haveRoot = false;
  135. bool haveMode = false;
  136. int offset = 12; // 0 = random, 12 = lower octave, 24 = repeat, 36 = upper octave
  137. int mode = 1; // 0 = random chord, 1 = chord in key, 2 = chord in mode
  138. int allowedInversions = 0; // 0 = root only, 1 = root + first, 2 = root, first, second
  139. std::string rootName = "";
  140. std::string modeName = "";
  141. std::string chordName = "";
  142. std::string chordExtName = "";
  143. };
  144. void Galaxy::step() {
  145. AHModule::step();
  146. int badLight = 0;
  147. // Get inputs from Rack
  148. bool move = moveTrigger.process(inputs[MOVE_INPUT].value);
  149. if (inputs[MODE_INPUT].active) {
  150. float fMode = inputs[MODE_INPUT].value;
  151. currMode = CoreUtil().getModeFromVolts(fMode);
  152. } else {
  153. currMode = params[MODE_PARAM].value;
  154. }
  155. if (inputs[KEY_INPUT].active) {
  156. float fRoot = inputs[KEY_INPUT].value;
  157. currRoot = CoreUtil().getKeyFromVolts(fRoot);
  158. } else {
  159. currRoot = params[KEY_PARAM].value;
  160. }
  161. if (mode == 1) {
  162. rootName = CoreUtil().noteNames[currRoot];
  163. modeName = "";
  164. } else if (mode == 2) {
  165. rootName = CoreUtil().noteNames[currRoot];
  166. modeName = CoreUtil().modeNames[currMode];
  167. } else {
  168. rootName = "";
  169. modeName = "";
  170. chordExtName = "";
  171. }
  172. if (move) {
  173. bool changed = false;
  174. bool haveMode = false;
  175. // std::cout << "Str position: Root: " << currRoot <<
  176. // " Mode: " << currMode <<
  177. // " degree: " << degree <<
  178. // " quality: " << quality <<
  179. // " noteIndex: " << noteIndex << std::endl;
  180. if (mode == 0) {
  181. getFromRandom();
  182. } else if (mode == 1) {
  183. if (randomUniform() < params[BAD_PARAM].value) {
  184. badLight = 2;
  185. getFromRandom();
  186. } else {
  187. getFromKey();
  188. }
  189. } else if (mode == 2) {
  190. float excess = params[BAD_PARAM].value - randomUniform();
  191. if (excess < 0.0) {
  192. getFromKeyMode();
  193. haveMode = true;
  194. } else {
  195. if (excess < 0.2) {
  196. badLight = 1;
  197. getFromKey();
  198. } else {
  199. badLight = 2;
  200. getFromRandom();
  201. }
  202. }
  203. }
  204. inversion = InversionMap[allowedInversions][rand() % QMAP_SIZE];
  205. int chord = ChordTable[quality];
  206. // Determine which chord corresponds to the grid position
  207. int *chordArray;
  208. switch(inversion) {
  209. case 0: chordArray = CoreUtil().ChordTable[chord].root; break;
  210. case 1: chordArray = CoreUtil().ChordTable[chord].first; break;
  211. case 2: chordArray = CoreUtil().ChordTable[chord].second; break;
  212. default: chordArray = CoreUtil().ChordTable[chord].root;
  213. }
  214. // std::cout << "End position: Root: " << currRoot <<
  215. // " Mode: " << currMode <<
  216. // " Degree: " << degree <<
  217. // " Quality: " << quality <<
  218. // " Inversion: " << inversion << " " << chordArray <<
  219. // " NoteIndex: " << noteIndex << std::endl << std::endl;
  220. if (quality != lastQuality) {
  221. changed = true;
  222. lastQuality = quality;
  223. }
  224. if (noteIndex != lastNoteIndex) {
  225. changed = true;
  226. lastNoteIndex = noteIndex;
  227. }
  228. if (inversion != lastInversion) {
  229. changed = true;
  230. lastInversion = inversion;
  231. }
  232. // Determine which notes corresponds to the chord
  233. for (int j = 0; j < NUM_PITCHES; j++) {
  234. // Set the pitches for this step. If the chord has less than 6 notes, the empty slots are
  235. // filled with repeated notes. These notes are identified by a 24 semi-tome negative
  236. // offset. We correct for that offset now, pitching thaem back into the original octave.
  237. // They could be pitched into the octave above (or below)
  238. if (chordArray[j] < 0) {
  239. int off = offset;
  240. if (off == 0) {
  241. off = (rand() % 3 + 1) * 12;
  242. }
  243. outVolts[j] = CoreUtil().getVoltsFromPitch(chordArray[j] + off, noteIndex);
  244. } else {
  245. outVolts[j] = CoreUtil().getVoltsFromPitch(chordArray[j], noteIndex);
  246. }
  247. }
  248. int newlight = noteIndex + (quality * N_NOTES);
  249. if (changed) {
  250. int chordIndex = ChordTable[quality];
  251. chordName =
  252. CoreUtil().noteNames[noteIndex] +
  253. CoreUtil().ChordTable[chordIndex].quality + " " +
  254. CoreUtil().inversionNames[inversion];
  255. if (mode == 2) {
  256. if (haveMode) {
  257. chordExtName = degNames[degree * 6 + quality];
  258. } else {
  259. chordExtName = "";
  260. }
  261. }
  262. lights[NOTE_LIGHT + light].value = 0.0f;
  263. lights[NOTE_LIGHT + newlight].value = 1.0f;
  264. light = newlight;
  265. }
  266. }
  267. if (badLight == 1) { // Green (scale->key)
  268. lights[BAD_LIGHT].setBrightnessSmooth(1.0f);
  269. lights[BAD_LIGHT + 1].setBrightnessSmooth(0.0f);
  270. } else if (badLight == 2) { // Red (->random)
  271. lights[BAD_LIGHT].setBrightnessSmooth(0.0f);
  272. lights[BAD_LIGHT + 1].setBrightnessSmooth(1.0f);
  273. } else { // No change
  274. lights[BAD_LIGHT].setBrightnessSmooth(0.0f);
  275. lights[BAD_LIGHT + 1].setBrightnessSmooth(0.0f);
  276. }
  277. // Set the output pitches and lights
  278. for (int i = 0; i < NUM_PITCHES; i++) {
  279. outputs[PITCH_OUTPUT + i].value = outVolts[i];
  280. }
  281. }
  282. void Galaxy::getFromRandom() {
  283. int rotSign = rand() % 2 ? 1 : -1;
  284. int rotateInput = rotSign * (rand() % 1 + 1); // -2 to 2
  285. int radSign = rand() % 2 ? 1 : -1;
  286. int radialInput = radSign * (rand() % 2 + 1); // -2 to 2
  287. // std::cout << "Rotate: " << rotateInput << " Radial: " << radialInput << std::endl;
  288. // Determine move around the grid
  289. quality += rotateInput;
  290. if (quality < 0) {
  291. quality += N_QUALITIES;
  292. } else if (quality >= N_QUALITIES) {
  293. quality -= N_QUALITIES;
  294. }
  295. noteIndex += radialInput;
  296. if (noteIndex < 0) {
  297. noteIndex += N_NOTES;
  298. } else if (noteIndex >= N_NOTES) {
  299. noteIndex -= N_NOTES;
  300. }
  301. }
  302. void Galaxy::getFromKey() {
  303. int rotSign = rand() % 2 ? 1 : -1;
  304. int rotateInput = rotSign * (rand() % 1 + 1); // -2 to 2
  305. int radSign = rand() % 2 ? 1 : -1;
  306. int radialInput = radSign * (rand() % 2 + 1); // -2 to 2
  307. // std::cout << "Rotate: " << rotateInput << " Radial: " << radialInput << std::endl;
  308. // Determine move around the grid
  309. quality += rotateInput;
  310. if (quality < 0) {
  311. quality += N_QUALITIES;
  312. } else if (quality >= N_QUALITIES) {
  313. quality -= N_QUALITIES;
  314. }
  315. // Just major scale
  316. int *curScaleArr = CoreUtil().ASCALE_IONIAN;
  317. int notesInScale = LENGTHOF(CoreUtil().ASCALE_IONIAN);
  318. // Determine move through the scale
  319. degree += radialInput;
  320. if (degree < 0) {
  321. degree += notesInScale;
  322. } else if (degree >= notesInScale) {
  323. degree -= notesInScale;
  324. }
  325. noteIndex = (currRoot + curScaleArr[degree]) % 12;
  326. }
  327. void Galaxy::getFromKeyMode() {
  328. int rotSign = rand() % 2 ? 1 : -1;
  329. int rotateInput = rotSign * (rand() % 1 + 1); // -2 to 2
  330. // Determine move through the scale
  331. degree += rotateInput;
  332. if (degree < 0) {
  333. degree += Core::NUM_DEGREES;
  334. } else if (degree >= Core::NUM_DEGREES) {
  335. degree -= Core::NUM_DEGREES;
  336. }
  337. // From the input root, mode and degree, we can get the root chord note and quality (Major,Minor,Diminshed)
  338. int q;
  339. CoreUtil().getRootFromMode(currMode,currRoot,degree,&noteIndex,&q);
  340. quality = QualityMap[q][rand() % QMAP_SIZE];
  341. }
  342. struct GalaxyDisplay : TransparentWidget {
  343. Galaxy *module;
  344. int frame = 0;
  345. std::shared_ptr<Font> font;
  346. GalaxyDisplay() {
  347. font = Font::load(assetPlugin(plugin, "res/EurostileBold.ttf"));
  348. }
  349. void draw(NVGcontext *vg) override {
  350. nvgFontSize(vg, 12);
  351. nvgFontFaceId(vg, font->handle);
  352. nvgFillColor(vg, nvgRGBA(255, 0, 0, 0xff));
  353. nvgTextLetterSpacing(vg, -1);
  354. char text[128];
  355. snprintf(text, sizeof(text), "%s", module->chordName.c_str());
  356. nvgText(vg, box.pos.x + 5, box.pos.y, text, NULL);
  357. snprintf(text, sizeof(text), "%s", module->chordExtName.c_str());
  358. nvgText(vg, box.pos.x + 5, box.pos.y + 11, text, NULL);
  359. nvgTextAlign(vg, NVG_ALIGN_RIGHT);
  360. snprintf(text, sizeof(text), "%s", module->rootName.c_str());
  361. nvgText(vg, box.size.x - 5, box.pos.y, text, NULL);
  362. snprintf(text, sizeof(text), "%s", module->modeName.c_str());
  363. nvgText(vg, box.size.x - 5, box.pos.y + 11, text, NULL);
  364. }
  365. };
  366. struct GalaxyWidget : ModuleWidget {
  367. Menu *createContextMenu() override;
  368. GalaxyWidget(Galaxy *module) : ModuleWidget(module) {
  369. UI ui;
  370. box.size = Vec(240, 380);
  371. {
  372. SVGPanel *panel = new SVGPanel();
  373. panel->box.size = box.size;
  374. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Galaxy.svg")));
  375. addChild(panel);
  376. }
  377. {
  378. GalaxyDisplay *display = new GalaxyDisplay();
  379. display->module = module;
  380. display->box.pos = Vec(0, 20);
  381. display->box.size = Vec(240, 230);
  382. addChild(display);
  383. }
  384. float div = (M_PI * 2) / (float)Galaxy::N_QUALITIES;
  385. float div2 = (M_PI * 2) / (float)(Galaxy::N_QUALITIES * Galaxy::N_QUALITIES);
  386. for (int q = 0; q < Galaxy::N_QUALITIES; q++) {
  387. for (int n = 0; n < Galaxy::N_NOTES; n++) {
  388. float cosDiv = cos(div * q + div2 * n);
  389. float sinDiv = sin(div * q + div2 * n);
  390. float xPos = sinDiv * (32.5 + (7.5 * n));
  391. float yPos = cosDiv * (32.5 + (7.5 * n));
  392. int l = n + (q * Galaxy::N_NOTES);
  393. addChild(ModuleLightWidget::create<SmallLight<GreenLight>>(Vec(xPos + 110.5, 149.5 - yPos), module, Galaxy::NOTE_LIGHT + l));
  394. }
  395. }
  396. for (int i = 0; i < 6; i++) {
  397. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, i, 5, true, false), Port::OUTPUT, module, Galaxy::PITCH_OUTPUT + i));
  398. }
  399. addInput(Port::create<PJ301MPort>(Vec(102, 140), Port::INPUT, module, Galaxy::MOVE_INPUT));
  400. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 0, 4, true, false), module, Galaxy::KEY_PARAM, 0.0, 11.0, 0.0));
  401. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, 4, true, false), Port::INPUT, module, Galaxy::KEY_INPUT));
  402. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 4, 4, true, false), module, Galaxy::MODE_PARAM, 0.0, 6.0, 0.0));
  403. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 4, true, false), Port::INPUT, module, Galaxy::MODE_INPUT));
  404. Vec trim = ui.getPosition(UI::TRIMPOT, 5, 3, true, false);
  405. trim.x += 15;
  406. trim.y += 25;
  407. addParam(ParamWidget::create<AHTrimpotNoSnap>(trim, module, Galaxy::BAD_PARAM, 0.0, 1.0, 0.0));
  408. trim.x += 15;
  409. trim.y += 20;
  410. addChild(ModuleLightWidget::create<SmallLight<GreenRedLight>>(trim, module, Galaxy::BAD_LIGHT));
  411. }
  412. };
  413. struct GalOffsetItem : MenuItem {
  414. Galaxy *gal;
  415. int offset;
  416. void onAction(EventAction &e) override {
  417. gal->offset = offset;
  418. }
  419. void step() override {
  420. rightText = (gal->offset == offset) ? "✔" : "";
  421. }
  422. };
  423. struct GalModeItem : MenuItem {
  424. Galaxy *gal;
  425. int mode;
  426. void onAction(EventAction &e) override {
  427. gal->mode = mode;
  428. }
  429. void step() override {
  430. rightText = (gal->mode == mode) ? "✔" : "";
  431. }
  432. };
  433. struct GalInversionsItem : MenuItem {
  434. Galaxy *gal;
  435. int allowedInversions;
  436. void onAction(EventAction &e) override {
  437. gal->allowedInversions = allowedInversions;
  438. }
  439. void step() override {
  440. rightText = (gal->allowedInversions == allowedInversions) ? "✔" : "";
  441. }
  442. };
  443. Menu *GalaxyWidget::createContextMenu() {
  444. Menu *menu = ModuleWidget::createContextMenu();
  445. MenuLabel *spacerLabel = new MenuLabel();
  446. menu->addChild(spacerLabel);
  447. Galaxy *gal = dynamic_cast<Galaxy*>(module);
  448. assert(gal);
  449. MenuLabel *offsetLabel = new MenuLabel();
  450. offsetLabel->text = "Repeat Notes";
  451. menu->addChild(offsetLabel);
  452. GalOffsetItem *offsetLowerItem = new GalOffsetItem();
  453. offsetLowerItem->text = "Lower";
  454. offsetLowerItem->gal = gal;
  455. offsetLowerItem->offset = 12;
  456. menu->addChild(offsetLowerItem);
  457. GalOffsetItem *offsetRepeatItem = new GalOffsetItem();
  458. offsetRepeatItem->text = "Repeat";
  459. offsetRepeatItem->gal = gal;
  460. offsetRepeatItem->offset = 24;
  461. menu->addChild(offsetRepeatItem);
  462. GalOffsetItem *offsetUpperItem = new GalOffsetItem();
  463. offsetUpperItem->text = "Upper";
  464. offsetUpperItem->gal = gal;
  465. offsetUpperItem->offset = 36;
  466. menu->addChild(offsetUpperItem);
  467. GalOffsetItem *offsetRandomItem = new GalOffsetItem();
  468. offsetRandomItem->text = "Random";
  469. offsetRandomItem->gal = gal;
  470. offsetRandomItem->offset = 0;
  471. menu->addChild(offsetRandomItem);
  472. MenuLabel *modeLabel = new MenuLabel();
  473. modeLabel->text = "Chord Selection";
  474. menu->addChild(modeLabel);
  475. GalModeItem *modeRandomItem = new GalModeItem();
  476. modeRandomItem->text = "Random";
  477. modeRandomItem->gal = gal;
  478. modeRandomItem->mode = 0;
  479. menu->addChild(modeRandomItem);
  480. GalModeItem *modeKeyItem = new GalModeItem();
  481. modeKeyItem->text = "in Key";
  482. modeKeyItem->gal = gal;
  483. modeKeyItem->mode = 1;
  484. menu->addChild(modeKeyItem);
  485. GalModeItem *modeModeItem = new GalModeItem();
  486. modeModeItem->text = "in Mode";
  487. modeModeItem->gal = gal;
  488. modeModeItem->mode = 2;
  489. menu->addChild(modeModeItem);
  490. MenuLabel *invLabel = new MenuLabel();
  491. invLabel->text = "Allowed Chord Inversions";
  492. menu->addChild(invLabel);
  493. GalInversionsItem *invRootItem = new GalInversionsItem();
  494. invRootItem->text = "Root only";
  495. invRootItem->gal = gal;
  496. invRootItem->allowedInversions = 0;
  497. menu->addChild(invRootItem);
  498. GalInversionsItem *invFirstItem = new GalInversionsItem();
  499. invFirstItem->text = "Root and First";
  500. invFirstItem->gal = gal;
  501. invFirstItem->allowedInversions = 1;
  502. menu->addChild(invFirstItem);
  503. GalInversionsItem *invSecondItem = new GalInversionsItem();
  504. invSecondItem->text = "Root, First and Second";
  505. invSecondItem->gal = gal;
  506. invSecondItem->allowedInversions = 2;
  507. menu->addChild(invSecondItem);
  508. return menu;
  509. }
  510. } // namespace rack_plugin_AmalgamatedHarmonics
  511. using namespace rack_plugin_AmalgamatedHarmonics;
  512. RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Galaxy) {
  513. Model *modelGalaxy = Model::create<Galaxy, GalaxyWidget>( "Amalgamated Harmonics", "Galaxy", "Galaxy", SEQUENCER_TAG);
  514. return modelGalaxy;
  515. }
  516. // ♯♭