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.

320 lines
7.3KB

  1. #include <list>
  2. #include <algorithm>
  3. #include "rtmidi/RtMidi.h"
  4. #include "core.hpp"
  5. #include "MidiIO.hpp"
  6. struct CCValue {
  7. int val = 0; // Controller value
  8. TransitionSmoother tSmooth;
  9. int num = 0; // Controller number
  10. bool numInited = false; // Num inited by config file
  11. bool numSelected = false; // Text field selected for midi learn
  12. bool changed = false; // Value has been changed by midi message (only if it is in sync!)
  13. int sync = 0; // Output value sync (implies diff)
  14. bool syncFirst = true; // First value after sync was reset
  15. void resetSync() {
  16. sync = 0;
  17. syncFirst = true;
  18. }
  19. };
  20. struct MIDICCToCVInterface : MidiIO, Module {
  21. enum ParamIds {
  22. NUM_PARAMS
  23. };
  24. enum InputIds {
  25. NUM_INPUTS
  26. };
  27. enum OutputIds {
  28. NUM_OUTPUTS = 16
  29. };
  30. enum LightIds {
  31. NUM_LIGHTS = 16
  32. };
  33. CCValue cc[NUM_OUTPUTS];
  34. MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  35. for (int i = 0; i < NUM_OUTPUTS; i++) {
  36. cc[i].num = i;
  37. }
  38. }
  39. ~MIDICCToCVInterface() {}
  40. void step();
  41. void processMidi(std::vector<unsigned char> msg);
  42. void resetMidi();
  43. json_t *toJson() {
  44. json_t *rootJ = json_object();
  45. addBaseJson(rootJ);
  46. for (int i = 0; i < NUM_OUTPUTS; i++) {
  47. json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(cc[i].num));
  48. if (outputs[i].active) {
  49. json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i].val));
  50. }
  51. }
  52. return rootJ;
  53. }
  54. void fromJson(json_t *rootJ) {
  55. baseFromJson(rootJ);
  56. for (int i = 0; i < NUM_OUTPUTS; i++) {
  57. json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str());
  58. if (ccNumJ) {
  59. cc[i].num = json_integer_value(ccNumJ);
  60. cc[i].numInited = true;
  61. }
  62. json_t *ccValJ = json_object_get(rootJ, ("ccVal" + std::to_string(i)).c_str());
  63. if (ccValJ) {
  64. cc[i].val = json_integer_value(ccValJ);
  65. cc[i].tSmooth.set((cc[i].val / 127.0 * 10.0), (cc[i].val / 127.0 * 10.0));
  66. cc[i].resetSync();
  67. }
  68. }
  69. }
  70. void reset() {
  71. resetMidi();
  72. }
  73. };
  74. void MIDICCToCVInterface::step() {
  75. if (isPortOpen()) {
  76. std::vector<unsigned char> message;
  77. // midiIn->getMessage returns empty vector if there are no messages in the queue
  78. getMessage(&message);
  79. while (message.size() > 0) {
  80. processMidi(message);
  81. getMessage(&message);
  82. }
  83. }
  84. for (int i = 0; i < NUM_OUTPUTS; i++) {
  85. lights[i].setBrightness(cc[i].sync / 127.0);
  86. if (cc[i].changed) {
  87. cc[i].tSmooth.set(outputs[i].value, (cc[i].val / 127.0 * 10.0), int(engineGetSampleRate() / 32));
  88. cc[i].changed = false;
  89. }
  90. outputs[i].value = cc[i].tSmooth.next();
  91. }
  92. }
  93. void MIDICCToCVInterface::resetMidi() {
  94. for (int i = 0; i < NUM_OUTPUTS; i++) {
  95. cc[i].val = 0;
  96. cc[i].resetSync();
  97. cc[i].tSmooth.set(0,0);
  98. }
  99. };
  100. void MIDICCToCVInterface::processMidi(std::vector<unsigned char> msg) {
  101. int channel = msg[0] & 0xf;
  102. int status = (msg[0] >> 4) & 0xf;
  103. int data1 = msg[1];
  104. int data2 = msg[2];
  105. //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2);
  106. // Filter channels
  107. if (this->channel >= 0 && this->channel != channel)
  108. return;
  109. if (status == 0xb) {
  110. for (int i = 0; i < NUM_OUTPUTS; i++) {
  111. if (cc[i].numSelected) {
  112. cc[i].resetSync();
  113. cc[i].num = data1;
  114. }
  115. if (data1 == cc[i].num) {
  116. /* If the first value we received after sync was reset is +/- 1 of
  117. * the output value the values are in sync*/
  118. if (cc[i].syncFirst) {
  119. cc[i].syncFirst = false;
  120. if (data2 < cc[i].val + 2 && data2 > cc[i].val - 2) {
  121. cc[i].sync = 0;
  122. }else {
  123. cc[i].sync = absi(data2 - cc[i].val);
  124. }
  125. return;
  126. }
  127. if (cc[i].sync == 0) {
  128. cc[i].val = data2;
  129. cc[i].changed = true;
  130. } else {
  131. cc[i].sync = absi(data2 - cc[i].val);
  132. }
  133. }
  134. }
  135. }
  136. }
  137. struct CCTextField : TextField {
  138. void onTextChange();
  139. void draw(NVGcontext *vg);
  140. void onMouseDownOpaque(int button);
  141. void onMouseUpOpaque(int button);
  142. void onMouseLeave();
  143. int outNum;
  144. MIDICCToCVInterface *module;
  145. };
  146. void CCTextField::draw(NVGcontext *vg) {
  147. /* This is necessary, since the save
  148. * file is loaded after constructing the widget*/
  149. if (module->cc[outNum].numInited) {
  150. module->cc[outNum].numInited = false;
  151. text = std::to_string(module->cc[outNum].num);
  152. }
  153. /* If number is selected for midi learn*/
  154. if (module->cc[outNum].numSelected) {
  155. text = std::to_string(module->cc[outNum].num);
  156. }
  157. TextField::draw(vg);
  158. }
  159. void CCTextField::onMouseUpOpaque(int button) {
  160. if (button == 1) {
  161. module->cc[outNum].numSelected = false;
  162. }
  163. }
  164. void CCTextField::onMouseDownOpaque(int button) {
  165. if (button == 1) {
  166. module->cc[outNum].numSelected = true;
  167. }
  168. }
  169. void CCTextField::onMouseLeave() {
  170. module->cc[outNum].numSelected = false;
  171. }
  172. void CCTextField::onTextChange() {
  173. if (text.size() > 0) {
  174. try {
  175. int num = std::stoi(text);
  176. // Only allow valid cc numbers
  177. if (num < 0 || num > 127 || text.size() > 3) {
  178. text = "";
  179. begin = end = 0;
  180. module->cc[outNum].num = -1;
  181. } else {
  182. module->cc[outNum].num = num;
  183. module->cc[outNum].resetSync();
  184. }
  185. } catch (...) {
  186. text = "";
  187. begin = end = 0;
  188. module->cc[outNum].num = -1;
  189. }
  190. };
  191. }
  192. MIDICCToCVWidget::MIDICCToCVWidget() {
  193. MIDICCToCVInterface *module = new MIDICCToCVInterface();
  194. setModule(module);
  195. box.size = Vec(16 * 15, 380);
  196. {
  197. Panel *panel = new LightPanel();
  198. panel->box.size = box.size;
  199. addChild(panel);
  200. }
  201. float margin = 5;
  202. float labelHeight = 15;
  203. float yPos = margin;
  204. addChild(createScrew<ScrewSilver>(Vec(15, 0)));
  205. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 0)));
  206. addChild(createScrew<ScrewSilver>(Vec(15, 365)));
  207. addChild(createScrew<ScrewSilver>(Vec(box.size.x - 30, 365)));
  208. {
  209. Label *label = new Label();
  210. label->box.pos = Vec(box.size.x - margin - 11 * 15, margin);
  211. label->text = "MIDI CC to CV";
  212. addChild(label);
  213. yPos = labelHeight * 2;
  214. }
  215. {
  216. Label *label = new Label();
  217. label->box.pos = Vec(margin, yPos);
  218. label->text = "MIDI Interface";
  219. addChild(label);
  220. MidiChoice *midiChoice = new MidiChoice();
  221. midiChoice->midiModule = dynamic_cast<MidiIO *>(module);
  222. midiChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos);
  223. midiChoice->box.size.x = (box.size.x / 2.0) - margin;
  224. addChild(midiChoice);
  225. yPos += midiChoice->box.size.y + margin;
  226. }
  227. {
  228. Label *label = new Label();
  229. label->box.pos = Vec(margin, yPos);
  230. label->text = "Channel";
  231. addChild(label);
  232. ChannelChoice *channelChoice = new ChannelChoice();
  233. channelChoice->midiModule = dynamic_cast<MidiIO *>(module);
  234. channelChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos);
  235. channelChoice->box.size.x = (box.size.x / 2.0) - margin;
  236. addChild(channelChoice);
  237. yPos += channelChoice->box.size.y + margin * 3;
  238. }
  239. for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) {
  240. CCTextField *ccNumChoice = new CCTextField();
  241. ccNumChoice->module = module;
  242. ccNumChoice->outNum = i;
  243. ccNumChoice->text = std::to_string(module->cc[i].num);
  244. ccNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos);
  245. ccNumChoice->box.size.x = 29;
  246. addChild(ccNumChoice);
  247. yPos += labelHeight + margin;
  248. addOutput(createOutput<PJ3410Port>(Vec((i % 4) * (63) + 10, yPos + 5), module, i));
  249. addChild(createLight<SmallLight<RedLight>>(Vec((i % 4) * (63) + 32, yPos + 5), module, i));
  250. if ((i + 1) % 4 == 0) {
  251. yPos += 47 + margin;
  252. } else {
  253. yPos -= labelHeight + margin;
  254. }
  255. }
  256. }
  257. void MIDICCToCVWidget::step() {
  258. ModuleWidget::step();
  259. }