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.

372 lines
9.0KB

  1. #include <list>
  2. #include <algorithm>
  3. #include "core.hpp"
  4. #include "MidiIO.hpp"
  5. #include "dsp/digital.hpp"
  6. using namespace rack;
  7. struct MIDIClockToCVInterface : MidiIO, Module {
  8. enum ParamIds {
  9. NUM_PARAMS
  10. };
  11. enum InputIds {
  12. CLOCK1_RATIO,
  13. CLOCK2_RATIO,
  14. NUM_INPUTS
  15. };
  16. enum OutputIds {
  17. CLOCK1_PULSE,
  18. CLOCK2_PULSE,
  19. CONTINUE_PULSE,
  20. START_PULSE,
  21. STOP_PULSE,
  22. NUM_OUTPUTS
  23. };
  24. int clock1ratio = 0;
  25. int clock2ratio = 0;
  26. PulseGenerator clock1Pulse;
  27. PulseGenerator clock2Pulse;
  28. PulseGenerator continuePulse;
  29. PulseGenerator startPulse;
  30. PulseGenerator stopPulse;
  31. bool tick = false;
  32. bool running = false;
  33. bool start = false;
  34. bool stop = false;
  35. bool cont = false;
  36. int c_bar = 0;
  37. /* Note this is in relation to the Midi clock's Tick (6x per 16th note).
  38. * Therefore, e.g. the 2:3 is calculated:
  39. *
  40. * 24 (Ticks per quarter note) * 2 / 3 = 16
  41. *
  42. * Implying that every 16 midi clock ticks we need to send a pulse
  43. * */
  44. const int ratios[9] = {6, 8, 12, 16, 24, 32, 48, 96, 192};
  45. const int numratios = 9;
  46. /*
  47. * Length of clock pulse
  48. */
  49. const float pulseTime = 0.005;
  50. MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
  51. }
  52. ~MIDIClockToCVInterface() {
  53. }
  54. void step() override;
  55. void processMidi(std::vector<unsigned char> msg);
  56. void onDeviceChange() override;
  57. void resetMidi() override;
  58. json_t *toJson() override {
  59. json_t *rootJ = json_object();
  60. addBaseJson(rootJ);
  61. json_object_set_new(rootJ, "clock1ratio", json_integer(clock1ratio));
  62. json_object_set_new(rootJ, "clock2ratio", json_integer(clock2ratio));
  63. return rootJ;
  64. }
  65. void fromJson(json_t *rootJ) override {
  66. baseFromJson(rootJ);
  67. json_t *c1rJ = json_object_get(rootJ, "clock1ratio");
  68. if (c1rJ) {
  69. clock1ratio = json_integer_value(c1rJ);
  70. }
  71. json_t *c2rJ = json_object_get(rootJ, "clock2ratio");
  72. if (c2rJ) {
  73. clock2ratio = json_integer_value(c2rJ);
  74. }
  75. }
  76. };
  77. void MIDIClockToCVInterface::step() {
  78. float sampleRate = engineGetSampleRate();
  79. if (isPortOpen()) {
  80. std::vector<unsigned char> message;
  81. // midiIn->getMessage returns empty vector if there are no messages in the queue
  82. getMessage(&message);
  83. if (message.size() > 0) {
  84. processMidi(message);
  85. }
  86. }
  87. if (inputs[CLOCK1_RATIO].active) {
  88. clock1ratio = int(clampf(inputs[CLOCK1_RATIO].value, 0.0, 10.0) * (numratios - 1) / 10);
  89. }
  90. if (inputs[CLOCK2_RATIO].active) {
  91. clock2ratio = int(clampf(inputs[CLOCK2_RATIO].value, 0.0, 10.0) * (numratios - 1) / 10);
  92. }
  93. if (start) {
  94. start = false;
  95. running = true;
  96. startPulse.trigger(pulseTime);
  97. c_bar = 0;
  98. }
  99. if (stop) {
  100. stop = false;
  101. running = false;
  102. stopPulse.trigger(pulseTime);
  103. }
  104. if (cont) {
  105. cont = false;
  106. running = true;
  107. continuePulse.trigger(pulseTime);
  108. }
  109. if (tick) {
  110. tick = false;
  111. /* Note: At least for my midi clock, the clock ticks are sent
  112. * even if the midi clock is stopped.
  113. * Therefore, we need to keep track of ticks even when the clock
  114. * is stopped. Otherwise we can run into weird timing issues.
  115. */
  116. if (running) {
  117. if (c_bar % ratios[clock1ratio] == 0) {
  118. clock1Pulse.trigger(pulseTime);
  119. }
  120. if (c_bar % ratios[clock2ratio] == 0) {
  121. clock2Pulse.trigger(pulseTime);
  122. }
  123. }
  124. c_bar++;
  125. // One "midi bar" = 4 whole notes = (6 ticks per 16th) 6 * 16 *4 = 384
  126. if (c_bar >= 384) {
  127. c_bar = 0;
  128. }
  129. }
  130. bool pulse = clock1Pulse.process(1.0 / sampleRate);
  131. outputs[CLOCK1_PULSE].value = pulse ? 10.0 : 0.0;
  132. pulse = clock2Pulse.process(1.0 / sampleRate);
  133. outputs[CLOCK2_PULSE].value = pulse ? 10.0 : 0.0;
  134. pulse = continuePulse.process(1.0 / sampleRate);
  135. outputs[CONTINUE_PULSE].value = pulse ? 10.0 : 0.0;
  136. pulse = startPulse.process(1.0 / sampleRate);
  137. outputs[START_PULSE].value = pulse ? 10.0 : 0.0;
  138. pulse = stopPulse.process(1.0 / sampleRate);
  139. outputs[STOP_PULSE].value = pulse ? 10.0 : 0.0;
  140. }
  141. void MIDIClockToCVInterface::resetMidi() {
  142. outputs[CLOCK1_PULSE].value = 0.0;
  143. outputs[CLOCK2_PULSE].value = 0.0;
  144. }
  145. void MIDIClockToCVInterface::processMidi(std::vector<unsigned char> msg) {
  146. switch (msg[0]) {
  147. case 0xfa:
  148. start = true;
  149. break;
  150. case 0xfb:
  151. cont = true;
  152. break;
  153. case 0xfc:
  154. stop = true;
  155. break;
  156. case 0xf8:
  157. tick = true;
  158. break;
  159. }
  160. }
  161. void MIDIClockToCVInterface::onDeviceChange() {
  162. setIgnores(true, false);
  163. }
  164. struct ClockRatioItem : MenuItem {
  165. int ratio;
  166. int *clockRatio;
  167. void onAction(EventAction &e) override {
  168. *clockRatio = ratio;
  169. }
  170. };
  171. struct ClockRatioChoice : ChoiceButton {
  172. int *clockRatio;
  173. const std::vector<std::string> ratioNames = {"Sixteenth note (1:4 ratio)", "Eighth note triplet (1:3 ratio)",
  174. "Eighth note (1:2 ratio)", "Quarter note triplet (2:3 ratio)",
  175. "Quarter note (tap speed)", "Half note triplet (4:3 ratio)",
  176. "Half note (2:1 ratio)", "Whole note (4:1 ratio)",
  177. "Two whole notes (8:1 ratio)"
  178. };
  179. const std::vector<std::string> ratioNames_short = {"1:4 ratio", "1:3 ratio", "1:2 ratio", "2:3 ratio", "1:1 ratio",
  180. "4:3", "2:1 ratio", "4:1 ratio", "8:1 ratio"
  181. };
  182. void onAction(EventAction &e) override {
  183. Menu *menu = gScene->createMenu();
  184. menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
  185. menu->box.size.x = box.size.x;
  186. for (unsigned long ratio = 0; ratio < ratioNames.size(); ratio++) {
  187. ClockRatioItem *clockRatioItem = new ClockRatioItem();
  188. clockRatioItem->ratio = ratio;
  189. clockRatioItem->clockRatio = clockRatio;
  190. clockRatioItem->text = ratioNames[ratio];
  191. menu->addChild(clockRatioItem);
  192. }
  193. }
  194. void step() override {
  195. text = ratioNames_short[*clockRatio];
  196. }
  197. };
  198. MIDIClockToCVWidget::MIDIClockToCVWidget() {
  199. MIDIClockToCVInterface *module = new MIDIClockToCVInterface();
  200. setModule(module);
  201. box.size = Vec(15 * 9, 380);
  202. {
  203. Panel *panel = new LightPanel();
  204. panel->box.size = box.size;
  205. addChild(panel);
  206. }
  207. float margin = 5;
  208. float labelHeight = 15;
  209. float yPos = margin;
  210. addChild(createScrew<ScrewSilver>(Vec(15, 0)));
  211. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
  212. addChild(createScrew<ScrewSilver>(Vec(15, 365)));
  213. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
  214. {
  215. Label *label = new Label();
  216. label->box.pos = Vec(box.size.x - margin - 7 * 15, margin);
  217. label->text = "MIDI Clk-CV";
  218. addChild(label);
  219. yPos = labelHeight * 2;
  220. }
  221. {
  222. Label *label = new Label();
  223. label->box.pos = Vec(margin, yPos);
  224. label->text = "MIDI Interface";
  225. addChild(label);
  226. yPos += labelHeight + margin;
  227. MidiChoice *midiChoice = new MidiChoice();
  228. midiChoice->midiModule = dynamic_cast<MidiIO *>(module);
  229. midiChoice->box.pos = Vec(margin, yPos);
  230. midiChoice->box.size.x = box.size.x - 10;
  231. addChild(midiChoice);
  232. yPos += midiChoice->box.size.y + margin * 4;
  233. }
  234. {
  235. Label *label = new Label();
  236. label->box.pos = Vec(margin, yPos);
  237. label->text = "Start";
  238. addChild(label);
  239. addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::START_PULSE));
  240. yPos += labelHeight + margin * 4;
  241. }
  242. {
  243. Label *label = new Label();
  244. label->box.pos = Vec(margin, yPos);
  245. label->text = "Stop";
  246. addChild(label);
  247. addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::STOP_PULSE));
  248. yPos += labelHeight + margin * 4;
  249. }
  250. {
  251. Label *label = new Label();
  252. label->box.pos = Vec(margin, yPos);
  253. label->text = "Continue";
  254. addChild(label);
  255. addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CONTINUE_PULSE));
  256. yPos += labelHeight + margin * 6;
  257. }
  258. {
  259. addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO));
  260. ClockRatioChoice *ratioChoice = new ClockRatioChoice();
  261. ratioChoice->clockRatio = &module->clock1ratio;
  262. ratioChoice->box.pos = Vec(int(box.size.x / 3), yPos);
  263. ratioChoice->box.size.x = int(box.size.x / 1.5 - margin);
  264. addChild(ratioChoice);
  265. yPos += ratioChoice->box.size.y + margin * 3;
  266. }
  267. {
  268. Label *label = new Label();
  269. label->box.pos = Vec(margin, yPos);
  270. label->text = "C1 Pulse";
  271. addChild(label);
  272. addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_PULSE));
  273. yPos += margin * 10;
  274. }
  275. {
  276. addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO));
  277. ClockRatioChoice *ratioChoice = new ClockRatioChoice();
  278. ratioChoice->clockRatio = &module->clock2ratio;
  279. ratioChoice->box.pos = Vec(int(box.size.x / 3), yPos);
  280. ratioChoice->box.size.x = int(box.size.x / 1.5 - margin);
  281. addChild(ratioChoice);
  282. yPos += ratioChoice->box.size.y + margin * 3;
  283. }
  284. {
  285. Label *label = new Label();
  286. label->box.pos = Vec(margin, yPos);
  287. label->text = "C2 Pulse";
  288. addChild(label);
  289. addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_PULSE));
  290. yPos += labelHeight + margin * 3;
  291. }
  292. }
  293. void MIDIClockToCVWidget::step() {
  294. ModuleWidget::step();
  295. }