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.

286 lines
6.4KB

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