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.3KB

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