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.

274 lines
5.7KB

  1. #include <assert.h>
  2. #include <list>
  3. #include <algorithm>
  4. #include <portmidi.h>
  5. #include "core.hpp"
  6. using namespace rack;
  7. void midiInit() {
  8. PmError err = Pm_Initialize();
  9. if (err) {
  10. printf("Failed to initialize PortMidi: %s\n", Pm_GetErrorText(err));
  11. return;
  12. }
  13. }
  14. struct MidiInterface : Module {
  15. enum ParamIds {
  16. NUM_PARAMS
  17. };
  18. enum InputIds {
  19. NUM_INPUTS
  20. };
  21. enum OutputIds {
  22. GATE_OUTPUT,
  23. PITCH_OUTPUT,
  24. NUM_OUTPUTS
  25. };
  26. PortMidiStream *stream = NULL;
  27. std::list<int> notes;
  28. bool pedal = false;
  29. int note = 64; // C4
  30. int pitchWheel = 64;
  31. bool retrigger = true;
  32. bool retriggered = false;
  33. MidiInterface();
  34. ~MidiInterface();
  35. void step();
  36. int getPortCount();
  37. std::string getPortName(int portId);
  38. // -1 will close the port
  39. void openPort(int portId);
  40. void pressNote(int note);
  41. void releaseNote(int note);
  42. void processMidi(long msg);
  43. };
  44. MidiInterface::MidiInterface() {
  45. params.resize(NUM_PARAMS);
  46. inputs.resize(NUM_INPUTS);
  47. outputs.resize(NUM_OUTPUTS);
  48. }
  49. MidiInterface::~MidiInterface() {
  50. openPort(-1);
  51. }
  52. void MidiInterface::step() {
  53. if (stream) {
  54. // Read MIDI events
  55. PmEvent event;
  56. while (Pm_Read(stream, &event, 1) > 0) {
  57. processMidi(event.message);
  58. }
  59. }
  60. if (outputs[GATE_OUTPUT]) {
  61. bool gate = pedal || !notes.empty();
  62. if (retrigger && retriggered) {
  63. gate = false;
  64. retriggered = false;
  65. }
  66. *outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0;
  67. }
  68. if (outputs[PITCH_OUTPUT]) {
  69. *outputs[PITCH_OUTPUT] = ((note - 64) + 2.0*(pitchWheel - 64) / 64.0) / 12.0;
  70. }
  71. }
  72. int MidiInterface::getPortCount() {
  73. return Pm_CountDevices();
  74. }
  75. std::string MidiInterface::getPortName(int portId) {
  76. const PmDeviceInfo *info = Pm_GetDeviceInfo(portId);
  77. if (!info)
  78. return "";
  79. char name[1024];
  80. snprintf(name, sizeof(name), "%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output");
  81. return name;
  82. }
  83. void MidiInterface::openPort(int portId) {
  84. PmError err;
  85. // Close existing port
  86. if (stream) {
  87. err = Pm_Close(stream);
  88. if (err) {
  89. printf("Failed to close MIDI port: %s\n", Pm_GetErrorText(err));
  90. }
  91. stream = NULL;
  92. }
  93. // Open new port
  94. if (portId >= 0) {
  95. err = Pm_OpenInput(&stream, portId, NULL, 128, NULL, NULL);
  96. if (err) {
  97. printf("Failed to open MIDI port: %s\n", Pm_GetErrorText(err));
  98. return;
  99. }
  100. }
  101. }
  102. void MidiInterface::pressNote(int note) {
  103. // Remove existing similar note
  104. auto it = std::find(notes.begin(), notes.end(), note);
  105. if (it != notes.end())
  106. notes.erase(it);
  107. // Push note
  108. notes.push_back(note);
  109. this->note = note;
  110. retriggered = true;
  111. }
  112. void MidiInterface::releaseNote(int note) {
  113. // Remove the note
  114. auto it = std::find(notes.begin(), notes.end(), note);
  115. if (it != notes.end())
  116. notes.erase(it);
  117. if (pedal) {
  118. // Don't release if pedal is held
  119. }
  120. else if (!notes.empty()) {
  121. // Play previous note
  122. auto it2 = notes.end();
  123. it2--;
  124. this->note = *it2;
  125. retriggered = true;
  126. }
  127. }
  128. void MidiInterface::processMidi(long msg) {
  129. int channel = msg & 0xf;
  130. int status = (msg >> 4) & 0xf;
  131. int data1 = (msg >> 8) & 0xff;
  132. int data2 = (msg >> 16) & 0xff;
  133. if (channel != 0)
  134. return;
  135. printf("channel %d status %d data1 %d data2 %d\n", channel, status, data1, data2);
  136. switch (status) {
  137. // note off
  138. case 0x8: {
  139. releaseNote(data1);
  140. } break;
  141. case 0x9: // note on
  142. if (data2 > 0) {
  143. pressNote(data1);
  144. }
  145. else {
  146. // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
  147. releaseNote(data1);
  148. }
  149. break;
  150. case 0xb: // cc
  151. switch (data1) {
  152. case 0x40:
  153. pedal = (data2 >= 64);
  154. releaseNote(-1);
  155. break;
  156. }
  157. break;
  158. case 0xe: // pitch wheel
  159. this->pitchWheel = data2;
  160. break;
  161. }
  162. }
  163. struct MidiItem : MenuItem {
  164. MidiInterface *midiInterface;
  165. int portId;
  166. void onAction() {
  167. midiInterface->openPort(portId);
  168. }
  169. };
  170. struct MidiChoice : ChoiceButton {
  171. MidiInterface *midiInterface;
  172. void onAction() {
  173. MenuOverlay *overlay = new MenuOverlay();
  174. Menu *menu = new Menu();
  175. menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y));
  176. int portCount = midiInterface->getPortCount();
  177. {
  178. MidiItem *midiItem = new MidiItem();
  179. midiItem->midiInterface = midiInterface;
  180. midiItem->portId = -1;
  181. midiItem->text = "No device";
  182. menu->pushChild(midiItem);
  183. }
  184. for (int portId = 0; portId < portCount; portId++) {
  185. MidiItem *midiItem = new MidiItem();
  186. midiItem->midiInterface = midiInterface;
  187. midiItem->portId = portId;
  188. midiItem->text = midiInterface->getPortName(portId);
  189. menu->pushChild(midiItem);
  190. }
  191. overlay->addChild(menu);
  192. gScene->setOverlay(overlay);
  193. }
  194. };
  195. MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) {
  196. box.size = Vec(15*8, 380);
  197. float margin = 5;
  198. float yPos = margin;
  199. {
  200. Label *label = new Label();
  201. label->box.pos = Vec(margin, yPos);
  202. label->text = "MIDI Interface";
  203. addChild(label);
  204. yPos += label->box.size.y + margin;
  205. }
  206. {
  207. MidiChoice *midiChoice = new MidiChoice();
  208. midiChoice->midiInterface = dynamic_cast<MidiInterface*>(module);
  209. midiChoice->text = "MIDI device";
  210. midiChoice->box.pos = Vec(margin, yPos);
  211. midiChoice->box.size.x = box.size.x - 10;
  212. addChild(midiChoice);
  213. yPos += midiChoice->box.size.y + margin;
  214. }
  215. yPos += 5;
  216. addOutput(createOutput(Vec(25, yPos), module, MidiInterface::PITCH_OUTPUT));
  217. addOutput(createOutput(Vec(75, yPos), module, MidiInterface::GATE_OUTPUT));
  218. yPos += 25 + margin;
  219. {
  220. Label *pitchLabel = new Label();
  221. pitchLabel->box.pos = Vec(25-12, yPos);
  222. pitchLabel->text = "Pitch";
  223. addChild(pitchLabel);
  224. Label *gateLabel = new Label();
  225. gateLabel->box.pos = Vec(75-12, yPos);
  226. gateLabel->text = "Gate";
  227. addChild(gateLabel);
  228. yPos += pitchLabel->box.size.y + margin;
  229. }
  230. }
  231. void MidiInterfaceWidget::draw(NVGcontext *vg) {
  232. bndBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y);
  233. ModuleWidget::draw(vg);
  234. }