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.

283 lines
6.5KB

  1. #include <list>
  2. #include <algorithm>
  3. #include "rtmidi/RtMidi.h"
  4. #include "core.hpp"
  5. #include "MidiIO.hpp"
  6. #include "dsp/digital.hpp"
  7. using namespace rack;
  8. struct TriggerValue {
  9. int val = 0;
  10. int num;
  11. bool numInited = false;
  12. bool onFocus = false;
  13. };
  14. struct MIDITriggerToCVInterface : MidiIO, Module {
  15. enum ParamIds {
  16. NUM_PARAMS
  17. };
  18. enum InputIds {
  19. NUM_INPUTS
  20. };
  21. enum OutputIds {
  22. NUM_OUTPUTS = 16
  23. };
  24. TriggerValue trigger[NUM_OUTPUTS];
  25. MIDITriggerToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
  26. for (int i = 0; i < NUM_OUTPUTS; i++) {
  27. trigger[i].num = i;
  28. }
  29. }
  30. ~MIDITriggerToCVInterface() {
  31. }
  32. void step() override;
  33. void processMidi(std::vector<unsigned char> msg);
  34. void resetMidi() override;
  35. virtual json_t *toJson() override {
  36. json_t *rootJ = json_object();
  37. addBaseJson(rootJ);
  38. for (int i = 0; i < NUM_OUTPUTS; i++) {
  39. json_object_set_new(rootJ, std::to_string(i).c_str(), json_integer(trigger[i].num));
  40. }
  41. return rootJ;
  42. }
  43. void fromJson(json_t *rootJ) override {
  44. baseFromJson(rootJ);
  45. for (int i = 0; i < NUM_OUTPUTS; i++) {
  46. json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str());
  47. if (ccNumJ) {
  48. trigger[i].num = json_integer_value(ccNumJ);
  49. trigger[i].numInited = true;
  50. }
  51. }
  52. }
  53. void reset() override {
  54. resetMidi();
  55. }
  56. };
  57. void MIDITriggerToCVInterface::step() {
  58. if (isPortOpen()) {
  59. std::vector<unsigned char> message;
  60. // midiIn->getMessage returns empty vector if there are no messages in the queue
  61. getMessage(&message);
  62. while (message.size() > 0) {
  63. processMidi(message);
  64. getMessage(&message);
  65. }
  66. }
  67. for (int i = 0; i < NUM_OUTPUTS; i++) {
  68. // Note: Could have an option to select between gate and velocity
  69. // but trigger seams more useful
  70. // outputs[i].value = trigger[i] / 127.0 * 10;
  71. outputs[i].value = trigger[i].val > 0 ? 10.0 : 0.0;
  72. }
  73. }
  74. void MIDITriggerToCVInterface::resetMidi() {
  75. for (int i = 0; i < NUM_OUTPUTS; i++) {
  76. trigger[i].val = 0;
  77. }
  78. };
  79. void MIDITriggerToCVInterface::processMidi(std::vector<unsigned char> msg) {
  80. int channel = msg[0] & 0xf;
  81. int status = (msg[0] >> 4) & 0xf;
  82. int data1 = msg[1];
  83. int data2 = msg[2];
  84. //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2);
  85. // Filter channels
  86. if (this->channel >= 0 && this->channel != channel)
  87. return;
  88. if (status == 0x8) { // note off
  89. for (int i = 0; i < NUM_OUTPUTS; i++) {
  90. if (data1 == trigger[i].num) {
  91. trigger[i].val = 0;
  92. }
  93. }
  94. return;
  95. }
  96. if (status == 0x9) { // note on
  97. for (int i = 0; i < NUM_OUTPUTS; i++) {
  98. if (trigger[i].onFocus && data2 > 0) {
  99. trigger[i].num = data1;
  100. }
  101. if (data1 == trigger[i].num) {
  102. trigger[i].val = data2;
  103. }
  104. }
  105. }
  106. }
  107. struct TriggerTextField : TextField {
  108. void onTextChange() override;
  109. void draw(NVGcontext *vg) override;
  110. void onMouseDown(EventMouseDown &e) override;
  111. void onMouseUp(EventMouseUp &e) override;
  112. void onMouseLeave(EventMouseLeave &e) override;
  113. int outNum;
  114. MIDITriggerToCVInterface *module;
  115. };
  116. void TriggerTextField::draw(NVGcontext *vg) {
  117. /* This is necessary, since the save
  118. * file is loaded after constructing the widget*/
  119. if (module->trigger[outNum].numInited) {
  120. module->trigger[outNum].numInited = false;
  121. text = std::to_string(module->trigger[outNum].num);
  122. }
  123. if (module->trigger[outNum].onFocus) {
  124. text = std::to_string(module->trigger[outNum].num);
  125. }
  126. TextField::draw(vg);
  127. }
  128. void TriggerTextField::onTextChange() {
  129. if (text.size() > 0) {
  130. try {
  131. int num = std::stoi(text);
  132. // Only allow valid cc numbers
  133. if (num < 0 || num > 127 || text.size() > 3) {
  134. text = "";
  135. begin = end = 0;
  136. module->trigger[outNum].num = -1;
  137. }else {
  138. module->trigger[outNum].num = num;
  139. }
  140. } catch (...) {
  141. text = "";
  142. begin = end = 0;
  143. module->trigger[outNum].num = -1;
  144. }
  145. };
  146. }
  147. void TriggerTextField::onMouseUp(EventMouseUp &e) {
  148. if (e.button == 1) {
  149. module->trigger[outNum].onFocus = false;
  150. e.consumed = true;
  151. }
  152. TextField::onMouseUp(e);
  153. }
  154. void TriggerTextField::onMouseDown(EventMouseDown &e) {
  155. if (e.button == 1) {
  156. module->trigger[outNum].onFocus = true;
  157. e.consumed = true;
  158. }
  159. TextField::onMouseDown(e);
  160. }
  161. void TriggerTextField::onMouseLeave(EventMouseLeave &e) {
  162. module->trigger[outNum].onFocus = false;
  163. e.consumed = true;
  164. }
  165. MIDITriggerToCVWidget::MIDITriggerToCVWidget() {
  166. MIDITriggerToCVInterface *module = new MIDITriggerToCVInterface();
  167. setModule(module);
  168. box.size = Vec(16 * 15, 380);
  169. {
  170. Panel *panel = new LightPanel();
  171. panel->box.size = box.size;
  172. addChild(panel);
  173. }
  174. float margin = 5;
  175. float labelHeight = 15;
  176. float yPos = margin;
  177. addChild(createScrew<ScrewSilver>(Vec(15, 0)));
  178. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
  179. addChild(createScrew<ScrewSilver>(Vec(15, 365)));
  180. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
  181. {
  182. Label *label = new Label();
  183. label->box.pos = Vec(box.size.x - margin - 11 * 15, margin);
  184. label->text = "MIDI Trigger to CV";
  185. addChild(label);
  186. yPos = labelHeight * 2;
  187. }
  188. {
  189. Label *label = new Label();
  190. label->box.pos = Vec(margin, yPos);
  191. label->text = "MIDI Interface";
  192. addChild(label);
  193. MidiChoice *midiChoice = new MidiChoice();
  194. midiChoice->midiModule = dynamic_cast<MidiIO *>(module);
  195. midiChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos);
  196. midiChoice->box.size.x = (box.size.x / 2.0) - margin;
  197. addChild(midiChoice);
  198. yPos += midiChoice->box.size.y + margin;
  199. }
  200. {
  201. Label *label = new Label();
  202. label->box.pos = Vec(margin, yPos);
  203. label->text = "Channel";
  204. addChild(label);
  205. ChannelChoice *channelChoice = new ChannelChoice();
  206. channelChoice->midiModule = dynamic_cast<MidiIO *>(module);
  207. channelChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos);
  208. channelChoice->box.size.x = (box.size.x / 2.0) - margin;
  209. addChild(channelChoice);
  210. yPos += channelChoice->box.size.y + margin * 3;
  211. }
  212. for (int i = 0; i < MIDITriggerToCVInterface::NUM_OUTPUTS; i++) {
  213. TriggerTextField *triggerNumChoice = new TriggerTextField();
  214. triggerNumChoice->module = module;
  215. triggerNumChoice->outNum = i;
  216. triggerNumChoice->text = std::to_string(module->trigger[i].num);
  217. triggerNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos);
  218. triggerNumChoice->box.size.x = 29;
  219. addChild(triggerNumChoice);
  220. yPos += labelHeight + margin;
  221. addOutput(createOutput<PJ3410Port>(Vec((i % 4) * (63) + 10, yPos + 5), module, i));
  222. if ((i + 1) % 4 == 0) {
  223. yPos += 47 + margin;
  224. } else {
  225. yPos -= labelHeight + margin;
  226. }
  227. }
  228. }
  229. void MIDITriggerToCVWidget::step() {
  230. ModuleWidget::step();
  231. }