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.

581 lines
16KB

  1. #include "AH.hpp"
  2. #include "Core.hpp"
  3. #include "UI.hpp"
  4. #include "componentlibrary.hpp"
  5. #include "dsp/digital.hpp"
  6. #include <iostream>
  7. namespace rack_plugin_AmalgamatedHarmonics {
  8. struct Sequence {
  9. int pDir = 0;
  10. int sDir = 0;
  11. int nStep = 0;
  12. int nDist = 0;
  13. int stepI = 0;
  14. int cycleI = 0;
  15. int stepsRemaining = 0;
  16. int cycleRemaining = 0;
  17. int currDist = 0;
  18. void advanceSequence() {
  19. stepI++;
  20. stepsRemaining--;
  21. }
  22. void advanceCycle() {
  23. cycleI++;
  24. cycleRemaining--;
  25. }
  26. void initSequence(int inputStep, int inputDist, int inputPDir, int inputSDir, bool locked) {
  27. if (!locked) {
  28. nStep = inputStep;
  29. nDist = inputDist;
  30. pDir = inputPDir;
  31. sDir = inputSDir;
  32. }
  33. stepsRemaining = nStep;
  34. stepI = 0;
  35. // At the beginning of the sequence (i.e. dist = 0)
  36. // currDist is the distance from the base note of the sequence, nDist controls the size of the increment
  37. currDist = 0;
  38. }
  39. void setCycle(int n) {
  40. cycleRemaining = n;
  41. cycleI = 0;
  42. }
  43. bool isCycleFinished() {
  44. return (cycleRemaining == 0);
  45. }
  46. bool isSequenceFinished() {
  47. return (stepsRemaining == 0);
  48. }
  49. bool isSequenceStarted() {
  50. return stepI;
  51. }
  52. };
  53. struct Arpeggiator : AHModule {
  54. const static int MAX_STEPS = 16;
  55. const static int MAX_DIST = 12; //Octave
  56. const static int NUM_PITCHES = 6;
  57. enum ParamIds {
  58. STEP_PARAM,
  59. DIST_PARAM,
  60. PDIR_PARAM,
  61. SDIR_PARAM,
  62. LOCK_PARAM,
  63. TRIGGER_PARAM,
  64. NUM_PARAMS
  65. };
  66. enum InputIds {
  67. CLOCK_INPUT,
  68. STEP_INPUT,
  69. DIST_INPUT,
  70. TRIG_INPUT,
  71. PITCH_INPUT,
  72. NUM_INPUTS = PITCH_INPUT + NUM_PITCHES
  73. };
  74. enum OutputIds {
  75. OUT_OUTPUT,
  76. GATE_OUTPUT,
  77. EOC_OUTPUT,
  78. EOS_OUTPUT,
  79. NUM_OUTPUTS
  80. };
  81. enum LightIds {
  82. LOCK_LIGHT,
  83. NUM_LIGHTS
  84. };
  85. Arpeggiator() : AHModule(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { }
  86. void step() override;
  87. SchmittTrigger clockTrigger; // for clock
  88. SchmittTrigger trigTrigger; // for step trigger
  89. SchmittTrigger lockTrigger;
  90. SchmittTrigger buttonTrigger;
  91. PulseGenerator triggerPulse;
  92. PulseGenerator gatePulse;
  93. PulseGenerator eosPulse;
  94. PulseGenerator eocPulse;
  95. float pitches[NUM_PITCHES];
  96. float inputPitches[NUM_PITCHES];
  97. bool pitchStatus[NUM_PITCHES];
  98. float pitchValue[NUM_PITCHES];
  99. int inputPDir;
  100. int inputSDir;
  101. int inputStep = 0;
  102. int inputDist = 0;
  103. bool locked = false;
  104. float outVolts;
  105. bool isRunning = false;
  106. bool freeRunning = false;
  107. Sequence seq;
  108. int newSequence = 0;
  109. int newCycle = 0;
  110. const static int LAUNCH = 1;
  111. const static int COUNTDOWN = 3;
  112. int nValidPitches = 0;
  113. int poll = 5000;
  114. };
  115. void Arpeggiator::step() {
  116. stepX++;
  117. // Wait a few steps for the inputs to flow through Rack
  118. if (stepX < 10) {
  119. return;
  120. }
  121. // Get the clock rate and semi-tone
  122. float semiTone = 1.0 / 12.0;
  123. // Get inputs from Rack
  124. float clockInput = inputs[CLOCK_INPUT].value;
  125. float trigInput = inputs[TRIG_INPUT].value;
  126. float trigActive = inputs[TRIG_INPUT].active;
  127. float lockInput = params[LOCK_PARAM].value;
  128. float buttonInput = params[TRIGGER_PARAM].value;
  129. float iPDir = params[PDIR_PARAM].value;
  130. float iSDir = params[SDIR_PARAM].value;
  131. float iStep;
  132. if (inputs[STEP_INPUT].active) {
  133. iStep = inputs[STEP_INPUT].value;
  134. inputStep = round(rescale(iStep, -10.0f, 10.0f, 0.0f, MAX_STEPS));
  135. } else {
  136. iStep = params[STEP_PARAM].value;
  137. inputStep = iStep;
  138. }
  139. float iDist;
  140. if (inputs[DIST_INPUT].active) {
  141. iDist = inputs[DIST_INPUT].value;
  142. inputDist = round(rescale(iDist, -10.0f, 10.0f, 0.0f, MAX_DIST));
  143. } else {
  144. iDist = params[DIST_PARAM].value;
  145. inputDist = iDist;
  146. }
  147. for (int p = 0; p < NUM_PITCHES; p++) {
  148. int index = PITCH_INPUT + p;
  149. pitchStatus[p] = inputs[index].active;
  150. pitchValue[p] = inputs[index].value;
  151. }
  152. // Process inputs
  153. bool clockStatus = clockTrigger.process(clockInput);
  154. bool triggerStatus = trigTrigger.process(trigInput);
  155. bool lockStatus = lockTrigger.process(lockInput);
  156. bool buttonStatus = buttonTrigger.process(buttonInput);
  157. inputPDir = iPDir;
  158. inputSDir = iSDir;
  159. int nValidPitches = 0;
  160. for (int p = 0; p < NUM_PITCHES; p++) {
  161. if (pitchStatus[p]) { //Plugged in
  162. inputPitches[nValidPitches] = pitchValue[p];
  163. nValidPitches++;
  164. }
  165. }
  166. // Check that we even have anything plugged in
  167. if (nValidPitches == 0) {
  168. return; // No inputs, no music
  169. }
  170. // Has the trigger input been fired
  171. if (triggerStatus) {
  172. triggerPulse.trigger(5e-5);
  173. if (debugEnabled()) { std::cout << stepX << " Triggered" << std::endl; }
  174. }
  175. // Update the trigger pulse and determine if it is still high
  176. bool triggerHigh = triggerPulse.process(delta);
  177. if (debugEnabled()) {
  178. if (triggerHigh) {
  179. std::cout << stepX << " Trigger is high" << std::endl;
  180. }
  181. }
  182. // Update lock
  183. if (lockStatus) {
  184. if (debugEnabled()) { std::cout << "Toggling lock: " << locked << std::endl; }
  185. locked = !locked;
  186. }
  187. if (newSequence) {
  188. newSequence--;
  189. if (debugEnabled()) { std::cout << stepX << " Countdown newSequence " << newSequence << std::endl; }
  190. }
  191. if (newCycle) {
  192. newCycle--;
  193. if (debugEnabled()) { std::cout << stepX << " Countdown newCycle " << newCycle << std::endl; }
  194. }
  195. // OK so the problem here might be that the clock gate is still high right after the trigger gate fired on the previous step
  196. // So we need to wait a while for the clock gate to go low
  197. // Has the clock input been fired
  198. bool isClocked = false;
  199. if (clockStatus && !triggerHigh) {
  200. if (debugEnabled()) { std::cout << stepX << " Clocked" << std::endl; }
  201. isClocked = true;
  202. }
  203. // Has the trigger input been fired, either on the input or button
  204. if (triggerStatus || buttonStatus) {
  205. newSequence = COUNTDOWN;
  206. newCycle = COUNTDOWN;
  207. if (debugEnabled()) { std::cout << stepX << " Triggered" << std::endl; }
  208. }
  209. // So this is where the free-running could be triggered
  210. if (isClocked && !isRunning) { // Must have a clock and not be already running
  211. if (!trigActive) { // If nothing plugged into the TRIG input
  212. if (debugEnabled()) { std::cout << stepX << " Free running sequence; starting" << std::endl; }
  213. freeRunning = true; // We're free-running
  214. newSequence = COUNTDOWN;
  215. newCycle = LAUNCH;
  216. } else {
  217. if (debugEnabled()) { std::cout << stepX << " Triggered sequence; wait for trigger" << std::endl; }
  218. freeRunning = false;
  219. }
  220. }
  221. // Detect cable being plugged in when free-running, stop free-running
  222. if (freeRunning && trigActive && isRunning) {
  223. if (debugEnabled()) { std::cout << stepX << " TRIG input re-connected" << std::endl; }
  224. freeRunning = false;
  225. }
  226. // Reached the end of the cycle
  227. if (isRunning && isClocked && seq.isCycleFinished()) {
  228. // Completed 1 step
  229. seq.advanceSequence();
  230. // Pulse the EOC gate
  231. eocPulse.trigger(5e-3);
  232. if (debugEnabled()) { std::cout << stepX << " Finished Cycle S: " << seq.stepI <<
  233. " C: " << seq.cycleI <<
  234. " sRemain: " << seq.stepsRemaining <<
  235. " cRemain: " << seq.cycleRemaining << std::endl;
  236. }
  237. // Reached the end of the sequence
  238. if (isRunning && seq.isSequenceFinished()) {
  239. // Free running, so start new seqeuence & cycle
  240. if (freeRunning) {
  241. newCycle = COUNTDOWN;
  242. newSequence = COUNTDOWN;
  243. }
  244. isRunning = false;
  245. // Pulse the EOS gate
  246. eosPulse.trigger(5e-3);
  247. if (debugEnabled()) { std::cout << stepX << " Finished sequence S: " << seq.stepI <<
  248. " C: " << seq.cycleI <<
  249. " sRemain: " << seq.stepsRemaining <<
  250. " cRemain: " << seq.cycleRemaining <<
  251. " flag:" << isRunning << std::endl;
  252. }
  253. } else {
  254. newCycle = LAUNCH;
  255. if (debugEnabled()) { std::cout << stepX << " Flagging new cycle" << std::endl; }
  256. }
  257. }
  258. // If we have been triggered, start a new sequence
  259. if (newSequence == LAUNCH) {
  260. // At the first step of the sequence
  261. if (debugEnabled()) { std::cout << stepX << " New Sequence" << std::endl; }
  262. if (!locked) {
  263. if (debugEnabled()) { std::cout << stepX << " Update sequence inputs" << std::endl; }
  264. }
  265. // So this is where we tweak the sequence parameters
  266. seq.initSequence(inputStep, inputDist, inputPDir, inputSDir, locked);
  267. // We're running now
  268. isRunning = true;
  269. }
  270. // Starting a new cycle
  271. if (newCycle == LAUNCH) {
  272. if (debugEnabled()) {
  273. std::cout << stepX << " Defining cycle: nStep: " << seq.nStep <<
  274. " nDist: " << seq.nDist <<
  275. " pDir: " << seq.pDir <<
  276. " sDir: " << seq.sDir <<
  277. " nValidPitches: " << nValidPitches <<
  278. " seqLen: " << seq.nStep * nValidPitches;
  279. for (int i = 0; i < nValidPitches; i++) {
  280. std::cout << " P" << i << " V: " << inputPitches[i];
  281. }
  282. std::cout << std::endl;
  283. }
  284. /// Reset the cycle counters
  285. seq.setCycle(nValidPitches);
  286. if (debugEnabled()) { std::cout << stepX << " New cycle" << std::endl; }
  287. // Deal with RND setting, when sDir == 1, force it up or down
  288. if (seq.sDir == 1) {
  289. if (rand() % 2 == 0) {
  290. seq.sDir = 0;
  291. } else {
  292. seq.sDir = 2;
  293. }
  294. }
  295. // Only starting moving after the first cycle
  296. if (seq.isSequenceStarted()) {
  297. switch (seq.sDir) {
  298. case 0: seq.currDist--; break;
  299. case 2: seq.currDist++; break;
  300. default: ;
  301. }
  302. }
  303. if (!locked) {// Pitches are locked, and so is the order. This keeps randomly generated arps fixed when locked
  304. // pitches[i] are offset from the input values according to the dist setting. Here we calculate the offsets
  305. for (int i = 0; i < nValidPitches; i++) {
  306. int target;
  307. // Read the pitches according to direction, but we should do this for the sequence?
  308. switch (seq.pDir) {
  309. case 0: target = nValidPitches - i - 1; break; // DOWN
  310. case 1: target = rand() % nValidPitches; break; // RANDOM
  311. case 2: target = i; break; // UP
  312. default: target = i; break; // For random case, read randomly from array, so order does not matter
  313. }
  314. // How many semi-tones do we need to shift
  315. float dV = semiTone * seq.nDist * seq.currDist;
  316. pitches[i] = clamp(inputPitches[target] + dV, -10.0, 10.0);
  317. if (debugEnabled()) {
  318. std::cout << stepX << " Pitch: " << i << " stepI: " << seq.stepI <<
  319. " dV:" << dV <<
  320. " target: " << target <<
  321. " in: " << inputPitches[target] <<
  322. " out: " << pitches[target] << std::endl;
  323. }
  324. }
  325. }
  326. if (debugEnabled()) {
  327. std::cout << stepX << " Output pitches: ";
  328. for (int i = 0; i < nValidPitches; i++) {
  329. std::cout << " P" << i << " V: " << pitches[i];
  330. }
  331. std::cout << std::endl;
  332. }
  333. }
  334. // Advance the sequence
  335. // Are we starting a sequence or are running and have been clocked; if so advance the sequence
  336. // Only advance from the clock
  337. if (isRunning && (isClocked || newCycle == LAUNCH)) {
  338. if (debugEnabled()) { std::cout << stepX << " Advance Cycle S: " << seq.stepI <<
  339. " C: " << seq.cycleI <<
  340. " sRemain: " << seq.stepsRemaining <<
  341. " cRemain: " << seq.cycleRemaining << std::endl;
  342. }
  343. // Finally set the out voltage
  344. outVolts = pitches[seq.cycleI];
  345. if (debugEnabled()) { std::cout << stepX << " Output V = " << outVolts << std::endl; }
  346. // Update counters
  347. seq.advanceCycle();
  348. // Pulse the output gate
  349. gatePulse.trigger(5e-4);
  350. }
  351. // Set the value
  352. lights[LOCK_LIGHT].value = locked ? 1.0 : 0.0;
  353. outputs[OUT_OUTPUT].value = outVolts;
  354. bool gPulse = gatePulse.process(delta);
  355. bool sPulse = eosPulse.process(delta);
  356. bool cPulse = eocPulse.process(delta);
  357. outputs[GATE_OUTPUT].value = gPulse ? 10.0 : 0.0;
  358. outputs[EOS_OUTPUT].value = sPulse ? 10.0 : 0.0;
  359. outputs[EOC_OUTPUT].value = cPulse ? 10.0 : 0.0;
  360. }
  361. struct ArpeggiatorDisplay : TransparentWidget {
  362. Arpeggiator *module;
  363. int frame = 0;
  364. std::shared_ptr<Font> font;
  365. ArpeggiatorDisplay() {
  366. font = Font::load(assetPlugin(plugin, "res/Roboto-Light.ttf"));
  367. }
  368. void draw(NVGcontext *vg) override {
  369. Vec pos = Vec(0, 20);
  370. nvgFontSize(vg, 20);
  371. nvgFontFaceId(vg, font->handle);
  372. nvgTextLetterSpacing(vg, -1);
  373. nvgFillColor(vg, nvgRGBA(212, 175, 55, 0xff));
  374. char text[128];
  375. snprintf(text, sizeof(text), "STEP: %d [%d]", module->seq.nStep, module->inputStep);
  376. nvgText(vg, pos.x + 10, pos.y + 5, text, NULL);
  377. snprintf(text, sizeof(text), "DIST: %d [%d]", module->seq.nDist, module->inputDist);
  378. nvgText(vg, pos.x + 10, pos.y + 25, text, NULL);
  379. if (module->seq.sDir == 0) {
  380. snprintf(text, sizeof(text), "SEQ: DSC");
  381. } else {
  382. snprintf(text, sizeof(text), "SEQ: ASC");
  383. }
  384. nvgText(vg, pos.x + 10, pos.y + 45, text, NULL);
  385. switch(module->seq.pDir) {
  386. case 0: snprintf(text, sizeof(text), "ARP: R-L"); break;
  387. case 1: snprintf(text, sizeof(text), "ARP: RND"); break;
  388. case 2: snprintf(text, sizeof(text), "ARP: L-R"); break;
  389. default: snprintf(text, sizeof(text), "ARP: ERR"); break;
  390. }
  391. nvgText(vg, pos.x + 10, pos.y + 65, text, NULL);
  392. std::string inputs ("IN: ");
  393. for (int p = 0; p < Arpeggiator::NUM_PITCHES; p++) {
  394. if (module->pitchStatus[p] && module->pitchValue[p] > -9.999) { //Plugged in or approx -10.0
  395. inputs = inputs + std::to_string(p + 1);
  396. }
  397. }
  398. nvgText(vg, pos.x + 10, pos.y + 85, inputs.c_str(), NULL);
  399. }
  400. };
  401. struct ArpeggiatorWidget : ModuleWidget {
  402. ArpeggiatorWidget(Arpeggiator *module);
  403. };
  404. ArpeggiatorWidget::ArpeggiatorWidget(Arpeggiator *module) : ModuleWidget(module) {
  405. UI ui;
  406. box.size = Vec(240, 380);
  407. {
  408. SVGPanel *panel = new SVGPanel();
  409. panel->box.size = box.size;
  410. panel->setBackground(SVG::load(assetPlugin(plugin, "res/Arpeggiator.svg")));
  411. addChild(panel);
  412. }
  413. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  414. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0)));
  415. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  416. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365)));
  417. {
  418. ArpeggiatorDisplay *display = new ArpeggiatorDisplay();
  419. display->module = module;
  420. display->box.pos = Vec(10, 95);
  421. display->box.size = Vec(100, 140);
  422. addChild(display);
  423. }
  424. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 0, false, false), Port::OUTPUT, module, Arpeggiator::OUT_OUTPUT));
  425. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 1, 0, false, false), Port::OUTPUT, module, Arpeggiator::GATE_OUTPUT));
  426. addParam(ParamWidget::create<AHButton>(ui.getPosition(UI::BUTTON, 2, 0, false, false), module, Arpeggiator::LOCK_PARAM, 0.0, 1.0, 0.0));
  427. addChild(ModuleLightWidget::create<MediumLight<GreenLight>>(ui.getPosition(UI::LIGHT, 2, 0, false, false), module, Arpeggiator::LOCK_LIGHT));
  428. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 3, 0, false, false), Port::OUTPUT, module, Arpeggiator::EOC_OUTPUT));
  429. addOutput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 0, false, false), Port::OUTPUT, module, Arpeggiator::EOS_OUTPUT));
  430. addParam(ParamWidget::create<BefacoPush>(Vec(127, 155), module, Arpeggiator::TRIGGER_PARAM, 0.0, 1.0, 0.0));
  431. for (int i = 0; i < Arpeggiator::NUM_PITCHES; i++) {
  432. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, i, 5, true, false), Port::INPUT, module, Arpeggiator::PITCH_INPUT + i));
  433. }
  434. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 0, 4, true, false), Port::INPUT, module, Arpeggiator::STEP_INPUT));
  435. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 1, 4, true, false), module, Arpeggiator::STEP_PARAM, 1.0, 16.0, 1.0));
  436. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 2, 4, true, false), Port::INPUT, module, Arpeggiator::DIST_INPUT));
  437. addParam(ParamWidget::create<AHKnobSnap>(ui.getPosition(UI::KNOB, 3, 4, true, false), module, Arpeggiator::DIST_PARAM, 0.0, 12.0, 0.0));
  438. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 4, 4, true, false), Port::INPUT, module, Arpeggiator::TRIG_INPUT));
  439. addInput(Port::create<PJ301MPort>(ui.getPosition(UI::PORT, 5, 4, true, false), Port::INPUT, module, Arpeggiator::CLOCK_INPUT));
  440. addParam(ParamWidget::create<BefacoSwitch>(Vec(178.5, 112.0), module, Arpeggiator::SDIR_PARAM, 0, 2, 0));
  441. addParam(ParamWidget::create<BefacoSwitch>(Vec(178.5, 187.0), module, Arpeggiator::PDIR_PARAM, 0, 2, 0));
  442. }
  443. } // namespace rack_plugin_AmalgamatedHarmonics
  444. using namespace rack_plugin_AmalgamatedHarmonics;
  445. RACK_PLUGIN_MODEL_INIT(AmalgamatedHarmonics, Arpeggiator) {
  446. Model *modelArpeggiator = Model::create<Arpeggiator, ArpeggiatorWidget>( "Amalgamated Harmonics", "Arpeggiator", "Arpeggiator (deprecated)", ARPEGGIATOR_TAG);
  447. return modelArpeggiator;
  448. }