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.

487 lines
12KB

  1. #include "plugin.hpp"
  2. namespace rack {
  3. namespace core {
  4. static const int MAX_CHANNELS = 128;
  5. struct MIDI_Map : Module {
  6. enum ParamIds {
  7. NUM_PARAMS
  8. };
  9. enum InputIds {
  10. NUM_INPUTS
  11. };
  12. enum OutputIds {
  13. NUM_OUTPUTS
  14. };
  15. enum LightIds {
  16. NUM_LIGHTS
  17. };
  18. midi::InputQueue midiInput;
  19. /** Number of maps */
  20. int mapLen = 0;
  21. /** The mapped CC number of each channel */
  22. int ccs[MAX_CHANNELS];
  23. /** The mapped param handle of each channel */
  24. ParamHandle paramHandles[MAX_CHANNELS];
  25. /** Channel ID of the learning session */
  26. int learningId;
  27. /** Whether the CC has been set during the learning session */
  28. bool learnedCc;
  29. /** Whether the param has been set during the learning session */
  30. bool learnedParam;
  31. /** The value of each CC number */
  32. int8_t values[128];
  33. /** The smoothing processor (normalized between 0 and 1) of each channel */
  34. dsp::ExponentialFilter valueFilters[MAX_CHANNELS];
  35. bool filterInitialized[MAX_CHANNELS] = {};
  36. dsp::ClockDivider divider;
  37. MIDI_Map() {
  38. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  39. for (int id = 0; id < MAX_CHANNELS; id++) {
  40. paramHandles[id].color = nvgRGB(0xff, 0xff, 0x40);
  41. APP->engine->addParamHandle(&paramHandles[id]);
  42. }
  43. for (int i = 0; i < MAX_CHANNELS; i++) {
  44. valueFilters[i].setTau(1 / 30.f);
  45. }
  46. divider.setDivision(32);
  47. onReset();
  48. }
  49. ~MIDI_Map() {
  50. for (int id = 0; id < MAX_CHANNELS; id++) {
  51. APP->engine->removeParamHandle(&paramHandles[id]);
  52. }
  53. }
  54. void onReset() override {
  55. learningId = -1;
  56. learnedCc = false;
  57. learnedParam = false;
  58. clearMaps();
  59. mapLen = 1;
  60. for (int i = 0; i < 128; i++) {
  61. values[i] = -1;
  62. }
  63. midiInput.reset();
  64. }
  65. void process(const ProcessArgs& args) override {
  66. if (divider.process()) {
  67. midi::Message msg;
  68. while (midiInput.shift(&msg)) {
  69. processMessage(msg);
  70. }
  71. // Step channels
  72. for (int id = 0; id < mapLen; id++) {
  73. int cc = ccs[id];
  74. if (cc < 0)
  75. continue;
  76. // Get Module
  77. Module* module = paramHandles[id].module;
  78. if (!module)
  79. continue;
  80. // Get ParamQuantity from ParamHandle
  81. int paramId = paramHandles[id].paramId;
  82. ParamQuantity* paramQuantity = module->paramQuantities[paramId];
  83. if (!paramQuantity)
  84. continue;
  85. if (!paramQuantity->isBounded())
  86. continue;
  87. // Set filter from param value if filter is uninitialized
  88. if (!filterInitialized[id]) {
  89. valueFilters[id].out = paramQuantity->getScaledValue();
  90. filterInitialized[id] = true;
  91. continue;
  92. }
  93. // Check if CC has been set by the MIDI device
  94. if (values[cc] < 0)
  95. continue;
  96. float value = values[cc] / 127.f;
  97. // Detect behavior from MIDI buttons.
  98. if (std::fabs(valueFilters[id].out - value) >= 1.f) {
  99. // Jump value
  100. valueFilters[id].out = value;
  101. }
  102. else {
  103. // Smooth value with filter
  104. valueFilters[id].process(args.sampleTime * divider.getDivision(), value);
  105. }
  106. paramQuantity->setScaledValue(valueFilters[id].out);
  107. }
  108. }
  109. }
  110. void processMessage(midi::Message msg) {
  111. // DEBUG("MIDI: %01x %01x %02x %02x", msg.getStatus(), msg.getChannel(), msg.getNote(), msg.getValue());
  112. switch (msg.getStatus()) {
  113. // cc
  114. case 0xb: {
  115. processCC(msg);
  116. } break;
  117. default: break;
  118. }
  119. }
  120. void processCC(midi::Message msg) {
  121. uint8_t cc = msg.getNote();
  122. int8_t value = msg.getValue();
  123. // Learn
  124. if (0 <= learningId && values[cc] != value) {
  125. ccs[learningId] = cc;
  126. valueFilters[learningId].reset();
  127. learnedCc = true;
  128. commitLearn();
  129. updateMapLen();
  130. refreshParamHandleText(learningId);
  131. }
  132. values[cc] = value;
  133. }
  134. void clearMap(int id) {
  135. learningId = -1;
  136. ccs[id] = -1;
  137. APP->engine->updateParamHandle(&paramHandles[id], -1, 0, true);
  138. valueFilters[id].reset();
  139. updateMapLen();
  140. refreshParamHandleText(id);
  141. }
  142. void clearMaps() {
  143. learningId = -1;
  144. for (int id = 0; id < MAX_CHANNELS; id++) {
  145. ccs[id] = -1;
  146. APP->engine->updateParamHandle(&paramHandles[id], -1, 0, true);
  147. valueFilters[id].reset();
  148. refreshParamHandleText(id);
  149. }
  150. mapLen = 0;
  151. }
  152. void updateMapLen() {
  153. // Find last nonempty map
  154. int id;
  155. for (id = MAX_CHANNELS - 1; id >= 0; id--) {
  156. if (ccs[id] >= 0 || paramHandles[id].moduleId >= 0)
  157. break;
  158. }
  159. mapLen = id + 1;
  160. // Add an empty "Mapping..." slot
  161. if (mapLen < MAX_CHANNELS)
  162. mapLen++;
  163. }
  164. void commitLearn() {
  165. if (learningId < 0)
  166. return;
  167. if (!learnedCc)
  168. return;
  169. if (!learnedParam)
  170. return;
  171. // Reset learned state
  172. learnedCc = false;
  173. learnedParam = false;
  174. // Find next incomplete map
  175. while (++learningId < MAX_CHANNELS) {
  176. if (ccs[learningId] < 0 || paramHandles[learningId].moduleId < 0)
  177. return;
  178. }
  179. learningId = -1;
  180. }
  181. void enableLearn(int id) {
  182. if (learningId != id) {
  183. learningId = id;
  184. learnedCc = false;
  185. learnedParam = false;
  186. }
  187. }
  188. void disableLearn(int id) {
  189. if (learningId == id) {
  190. learningId = -1;
  191. }
  192. }
  193. void learnParam(int id, int moduleId, int paramId) {
  194. APP->engine->updateParamHandle(&paramHandles[id], moduleId, paramId, true);
  195. learnedParam = true;
  196. commitLearn();
  197. updateMapLen();
  198. }
  199. void refreshParamHandleText(int id) {
  200. std::string text;
  201. if (ccs[id] >= 0)
  202. text = string::f("CC%02d", ccs[id]);
  203. else
  204. text = "MIDI-Map";
  205. paramHandles[id].text = text;
  206. }
  207. json_t* dataToJson() override {
  208. json_t* rootJ = json_object();
  209. json_t* mapsJ = json_array();
  210. for (int id = 0; id < mapLen; id++) {
  211. json_t* mapJ = json_object();
  212. json_object_set_new(mapJ, "cc", json_integer(ccs[id]));
  213. json_object_set_new(mapJ, "moduleId", json_integer(paramHandles[id].moduleId));
  214. json_object_set_new(mapJ, "paramId", json_integer(paramHandles[id].paramId));
  215. json_array_append_new(mapsJ, mapJ);
  216. }
  217. json_object_set_new(rootJ, "maps", mapsJ);
  218. json_object_set_new(rootJ, "midi", midiInput.toJson());
  219. return rootJ;
  220. }
  221. void dataFromJson(json_t* rootJ) override {
  222. clearMaps();
  223. json_t* mapsJ = json_object_get(rootJ, "maps");
  224. if (mapsJ) {
  225. json_t* mapJ;
  226. size_t mapIndex;
  227. json_array_foreach(mapsJ, mapIndex, mapJ) {
  228. json_t* ccJ = json_object_get(mapJ, "cc");
  229. json_t* moduleIdJ = json_object_get(mapJ, "moduleId");
  230. json_t* paramIdJ = json_object_get(mapJ, "paramId");
  231. if (!(ccJ && moduleIdJ && paramIdJ))
  232. continue;
  233. if (mapIndex >= MAX_CHANNELS)
  234. continue;
  235. ccs[mapIndex] = json_integer_value(ccJ);
  236. APP->engine->updateParamHandle(&paramHandles[mapIndex], json_integer_value(moduleIdJ), json_integer_value(paramIdJ), false);
  237. refreshParamHandleText(mapIndex);
  238. }
  239. }
  240. updateMapLen();
  241. json_t* midiJ = json_object_get(rootJ, "midi");
  242. if (midiJ)
  243. midiInput.fromJson(midiJ);
  244. }
  245. };
  246. struct MIDI_MapChoice : LedDisplayChoice {
  247. MIDI_Map* module;
  248. int id;
  249. int disableLearnFrames = -1;
  250. void setModule(MIDI_Map* module) {
  251. this->module = module;
  252. }
  253. void onButton(const event::Button& e) override {
  254. e.stopPropagating();
  255. if (!module)
  256. return;
  257. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  258. e.consume(this);
  259. }
  260. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  261. module->clearMap(id);
  262. e.consume(this);
  263. }
  264. }
  265. void onSelect(const event::Select& e) override {
  266. if (!module)
  267. return;
  268. ScrollWidget* scroll = getAncestorOfType<ScrollWidget>();
  269. scroll->scrollTo(box);
  270. // Reset touchedParam
  271. APP->scene->rack->touchedParam = NULL;
  272. module->enableLearn(id);
  273. }
  274. void onDeselect(const event::Deselect& e) override {
  275. if (!module)
  276. return;
  277. // Check if a ParamWidget was touched
  278. ParamWidget* touchedParam = APP->scene->rack->touchedParam;
  279. if (touchedParam) {
  280. APP->scene->rack->touchedParam = NULL;
  281. int moduleId = touchedParam->module->id;
  282. int paramId = touchedParam->paramId;
  283. module->learnParam(id, moduleId, paramId);
  284. }
  285. else {
  286. module->disableLearn(id);
  287. }
  288. }
  289. void step() override {
  290. if (!module)
  291. return;
  292. // Set bgColor and selected state
  293. if (module->learningId == id) {
  294. bgColor = color;
  295. bgColor.a = 0.15;
  296. // HACK
  297. if (APP->event->selectedWidget != this)
  298. APP->event->setSelected(this);
  299. }
  300. else {
  301. bgColor = nvgRGBA(0, 0, 0, 0);
  302. // HACK
  303. if (APP->event->selectedWidget == this)
  304. APP->event->setSelected(NULL);
  305. }
  306. // Set text
  307. text = "";
  308. if (module->ccs[id] >= 0) {
  309. text += string::f("CC%02d ", module->ccs[id]);
  310. }
  311. if (module->paramHandles[id].moduleId >= 0) {
  312. text += getParamName();
  313. }
  314. if (module->ccs[id] < 0 && module->paramHandles[id].moduleId < 0) {
  315. if (module->learningId == id) {
  316. text = "Mapping...";
  317. }
  318. else {
  319. text = "Unmapped";
  320. }
  321. }
  322. // Set text color
  323. if ((module->ccs[id] >= 0 && module->paramHandles[id].moduleId >= 0) || module->learningId == id) {
  324. color.a = 1.0;
  325. }
  326. else {
  327. color.a = 0.5;
  328. }
  329. }
  330. std::string getParamName() {
  331. if (!module)
  332. return "";
  333. if (id >= module->mapLen)
  334. return "";
  335. ParamHandle* paramHandle = &module->paramHandles[id];
  336. if (paramHandle->moduleId < 0)
  337. return "";
  338. ModuleWidget* mw = APP->scene->rack->getModule(paramHandle->moduleId);
  339. if (!mw)
  340. return "";
  341. // Get the Module from the ModuleWidget instead of the ParamHandle.
  342. // I think this is more elegant since this method is called in the app world instead of the engine world.
  343. Module* m = mw->module;
  344. if (!m)
  345. return "";
  346. int paramId = paramHandle->paramId;
  347. if (paramId >= (int) m->params.size())
  348. return "";
  349. ParamQuantity* paramQuantity = m->paramQuantities[paramId];
  350. std::string s;
  351. s += mw->model->name;
  352. s += " ";
  353. s += paramQuantity->name;
  354. return s;
  355. }
  356. };
  357. struct MIDI_MapDisplay : MidiWidget {
  358. MIDI_Map* module;
  359. ScrollWidget* scroll;
  360. MIDI_MapChoice* choices[MAX_CHANNELS];
  361. LedDisplaySeparator* separators[MAX_CHANNELS];
  362. void setModule(MIDI_Map* module) {
  363. this->module = module;
  364. scroll = new ScrollWidget;
  365. scroll->box.pos = channelChoice->box.getBottomLeft();
  366. scroll->box.size.x = box.size.x;
  367. scroll->box.size.y = box.size.y - scroll->box.pos.y;
  368. addChild(scroll);
  369. LedDisplaySeparator* separator = createWidget<LedDisplaySeparator>(scroll->box.pos);
  370. separator->box.size.x = box.size.x;
  371. addChild(separator);
  372. separators[0] = separator;
  373. Vec pos;
  374. for (int id = 0; id < MAX_CHANNELS; id++) {
  375. if (id > 0) {
  376. LedDisplaySeparator* separator = createWidget<LedDisplaySeparator>(pos);
  377. separator->box.size.x = box.size.x;
  378. scroll->container->addChild(separator);
  379. separators[id] = separator;
  380. }
  381. MIDI_MapChoice* choice = createWidget<MIDI_MapChoice>(pos);
  382. choice->box.size.x = box.size.x;
  383. choice->id = id;
  384. choice->setModule(module);
  385. scroll->container->addChild(choice);
  386. choices[id] = choice;
  387. pos = choice->box.getBottomLeft();
  388. }
  389. }
  390. void step() override {
  391. if (module) {
  392. int mapLen = module->mapLen;
  393. for (int id = 0; id < MAX_CHANNELS; id++) {
  394. choices[id]->visible = (id < mapLen);
  395. separators[id]->visible = (id < mapLen);
  396. }
  397. }
  398. MidiWidget::step();
  399. }
  400. };
  401. struct MIDI_MapWidget : ModuleWidget {
  402. MIDI_MapWidget(MIDI_Map* module) {
  403. setModule(module);
  404. setPanel(APP->window->loadSvg(asset::system("res/Core/MIDI-Map.svg")));
  405. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  406. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  407. addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  408. addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  409. MIDI_MapDisplay* midiWidget = createWidget<MIDI_MapDisplay>(mm2px(Vec(3.41891, 14.8373)));
  410. midiWidget->box.size = mm2px(Vec(43.999, 102.664));
  411. midiWidget->setMidiPort(module ? &module->midiInput : NULL);
  412. midiWidget->setModule(module);
  413. addChild(midiWidget);
  414. }
  415. };
  416. Model* modelMIDI_Map = createModel<MIDI_Map, MIDI_MapWidget>("MIDI-Map");
  417. } // namespace core
  418. } // namespace rack