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.

336 lines
7.9KB

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