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.

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