1 /************************** BEGIN MidiUI.h **************************/ 2 /************************************************************************ 3 FAUST Architecture File 4 Copyright (C) 2003-2017 GRAME, Centre National de Creation Musicale 5 --------------------------------------------------------------------- 6 This Architecture section is free software; you can redistribute it 7 and/or modify it under the terms of the GNU General Public License 8 as published by the Free Software Foundation; either version 3 of 9 the License, or (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; If not, see <http://www.gnu.org/licenses/>. 18 19 EXCEPTION : As a special exception, you may create a larger work 20 that contains this FAUST architecture section and distribute 21 that work under terms of your choice, so long as this FAUST 22 architecture section is not modified. 23 ************************************************************************/ 24 25 #ifndef FAUST_MIDIUI_H 26 #define FAUST_MIDIUI_H 27 28 #include <vector> 29 #include <string> 30 #include <utility> 31 #include <cstdlib> 32 #include <cmath> 33 34 #include "faust/dsp/dsp.h" 35 #include "faust/gui/meta.h" 36 #include "faust/gui/GUI.h" 37 #include "faust/gui/JSONUI.h" 38 #include "faust/gui/MapUI.h" 39 #include "faust/gui/MetaDataUI.h" 40 #include "faust/midi/midi.h" 41 #include "faust/gui/ValueConverter.h" 42 43 #ifdef _MSC_VER 44 #define gsscanf sscanf_s 45 #else 46 #define gsscanf sscanf 47 #endif 48 49 /** 50 * Helper code for MIDI meta and polyphonic 'nvoices' parsing. 51 */ 52 struct MidiMeta : public Meta, public std::map<std::string, std::string> { 53 declareMidiMeta54 void declare(const char* key, const char* value) 55 { 56 (*this)[key] = value; 57 } 58 getMidiMeta59 const std::string get(const char* key, const char* def) 60 { 61 return (this->find(key) != this->end()) ? (*this)[key] : def; 62 } 63 analyseMidiMeta64 static void analyse(dsp* mono_dsp, bool& midi_sync, int& nvoices) 65 { 66 JSONUI jsonui; 67 mono_dsp->buildUserInterface(&jsonui); 68 std::string json = jsonui.JSON(); 69 midi_sync = ((json.find("midi") != std::string::npos) && 70 ((json.find("start") != std::string::npos) || 71 (json.find("stop") != std::string::npos) || 72 (json.find("clock") != std::string::npos) || 73 (json.find("timestamp") != std::string::npos))); 74 75 #if defined(NVOICES) && NVOICES!=NUM_VOICES 76 nvoices = NVOICES; 77 #else 78 MidiMeta meta; 79 mono_dsp->metadata(&meta); 80 bool found_voices = false; 81 // If "options" metadata is used 82 std::string options = meta.get("options", ""); 83 if (options != "") { 84 std::map<std::string, std::string> metadata; 85 std::string res; 86 MetaDataUI::extractMetadata(options, res, metadata); 87 if (metadata.find("nvoices") != metadata.end()) { 88 nvoices = std::atoi(metadata["nvoices"].c_str()); 89 found_voices = true; 90 } 91 } 92 // Otherwise test for "nvoices" metadata 93 if (!found_voices) { 94 std::string numVoices = meta.get("nvoices", "0"); 95 nvoices = std::atoi(numVoices.c_str()); 96 } 97 nvoices = std::max<int>(0, nvoices); 98 #endif 99 } 100 checkPolyphonyMidiMeta101 static bool checkPolyphony(dsp* mono_dsp) 102 { 103 MapUI map_ui; 104 mono_dsp->buildUserInterface(&map_ui); 105 bool has_freq = false; 106 bool has_gate = false; 107 bool has_gain = false; 108 for (int i = 0; i < map_ui.getParamsCount(); i++) { 109 std::string path = map_ui.getParamAddress(i); 110 has_freq |= MapUI::endsWith(path, "/freq"); 111 has_gate |= MapUI::endsWith(path, "/gate"); 112 has_gain |= MapUI::endsWith(path, "/gain"); 113 } 114 return (has_freq && has_gate && has_gain); 115 } 116 117 }; 118 119 /** 120 * uiMidi : Faust User Interface 121 * This class decodes MIDI meta data and maps incoming MIDI messages to them. 122 * Currently ctrlChange, keyOn/keyOff, keyPress, progChange, chanPress, pitchWheel/pitchBend 123 * start/stop/clock meta data is handled. 124 * MIDI channel is numbered in [1..16] in this layer. 125 * Channel 0 means "all channels" when receiving or sending. 126 */ 127 class uiMidi { 128 129 friend class MidiUI; 130 131 protected: 132 133 midi* fMidiOut; 134 bool fInputCtrl; 135 int fChan; 136 inRange(FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT v)137 bool inRange(FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT v) { return (min <= v && v <= max); } 138 139 public: 140 fMidiOut(midi_out)141 uiMidi(midi* midi_out, bool input, int chan = 0):fMidiOut(midi_out), fInputCtrl(input), fChan(chan) 142 {} ~uiMidi()143 virtual ~uiMidi() 144 {} 145 146 }; 147 148 /** 149 * Base class for MIDI aware UI items. 150 */ 151 class uiMidiItem : public uiMidi, public uiItem { 152 153 public: 154 155 uiMidiItem(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true, int chan = 0) uiMidi(midi_out,input,chan)156 :uiMidi(midi_out, input, chan), uiItem(ui, zone) 157 {} ~uiMidiItem()158 virtual ~uiMidiItem() 159 {} 160 reflectZone()161 virtual void reflectZone() {} 162 163 }; 164 165 /** 166 * Base class for MIDI aware UI items with timestamp support. 167 */ 168 class uiMidiTimedItem : public uiMidi, public uiTimedItem { 169 170 public: 171 172 uiMidiTimedItem(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true, int chan = 0) uiMidi(midi_out,input,chan)173 :uiMidi(midi_out, input, chan), uiTimedItem(ui, zone) 174 {} ~uiMidiTimedItem()175 virtual ~uiMidiTimedItem() 176 {} 177 reflectZone()178 virtual void reflectZone() {} 179 180 }; 181 182 /** 183 * MIDI sync. 184 */ 185 class uiMidiStart : public uiMidiTimedItem 186 { 187 188 public: 189 190 uiMidiStart(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true) uiMidiTimedItem(midi_out,ui,zone,input)191 :uiMidiTimedItem(midi_out, ui, zone, input) 192 {} ~uiMidiStart()193 virtual ~uiMidiStart() 194 {} 195 reflectZone()196 virtual void reflectZone() 197 { 198 FAUSTFLOAT v = *fZone; 199 fCache = v; 200 if (v != FAUSTFLOAT(0)) { 201 fMidiOut->startSync(0); 202 } 203 } modifyZone(double date,FAUSTFLOAT v)204 void modifyZone(double date, FAUSTFLOAT v) 205 { 206 if (fInputCtrl) { 207 uiItem::modifyZone(FAUSTFLOAT(v)); 208 } 209 } 210 211 }; 212 213 class uiMidiStop : public uiMidiTimedItem { 214 215 public: 216 217 uiMidiStop(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true) uiMidiTimedItem(midi_out,ui,zone,input)218 :uiMidiTimedItem(midi_out, ui, zone, input) 219 {} ~uiMidiStop()220 virtual ~uiMidiStop() 221 {} 222 reflectZone()223 virtual void reflectZone() 224 { 225 FAUSTFLOAT v = *fZone; 226 fCache = v; 227 if (v != FAUSTFLOAT(1)) { 228 fMidiOut->stopSync(0); 229 } 230 } 231 modifyZone(double date,FAUSTFLOAT v)232 void modifyZone(double date, FAUSTFLOAT v) 233 { 234 if (fInputCtrl) { 235 uiItem::modifyZone(FAUSTFLOAT(v)); 236 } 237 } 238 }; 239 240 class uiMidiClock : public uiMidiTimedItem { 241 242 private: 243 244 bool fState; 245 246 public: 247 248 uiMidiClock(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, bool input = true) uiMidiTimedItem(midi_out,ui,zone,input)249 :uiMidiTimedItem(midi_out, ui, zone, input), fState(false) 250 {} ~uiMidiClock()251 virtual ~uiMidiClock() 252 {} 253 reflectZone()254 virtual void reflectZone() 255 { 256 FAUSTFLOAT v = *fZone; 257 fCache = v; 258 fMidiOut->clock(0); 259 } 260 modifyZone(double date,FAUSTFLOAT v)261 void modifyZone(double date, FAUSTFLOAT v) 262 { 263 if (fInputCtrl) { 264 fState = !fState; 265 uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fState)); 266 } 267 } 268 269 }; 270 271 /** 272 * Standard MIDI events. 273 */ 274 275 /** 276 * uiMidiProgChange uses the [min...max] range. 277 */ 278 class uiMidiProgChange : public uiMidiTimedItem { 279 280 public: 281 282 FAUSTFLOAT fMin, fMax; 283 284 uiMidiProgChange(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, 285 FAUSTFLOAT min, FAUSTFLOAT max, 286 bool input = true, int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)287 :uiMidiTimedItem(midi_out, ui, zone, input, chan), fMin(min), fMax(max) 288 {} ~uiMidiProgChange()289 virtual ~uiMidiProgChange() 290 {} 291 reflectZone()292 virtual void reflectZone() 293 { 294 FAUSTFLOAT v = *fZone; 295 fCache = v; 296 if (inRange(fMin, fMax, v)) { 297 if (fChan == 0) { 298 // Send on [0..15] channels on the MIDI layer 299 for (int chan = 0; chan < 16; chan++) { 300 fMidiOut->progChange(chan, v); 301 } 302 } else { 303 fMidiOut->progChange(fChan - 1, v); 304 } 305 } 306 } 307 modifyZone(FAUSTFLOAT v)308 void modifyZone(FAUSTFLOAT v) 309 { 310 if (fInputCtrl && inRange(fMin, fMax, v)) { 311 uiItem::modifyZone(v); 312 } 313 } 314 modifyZone(double date,FAUSTFLOAT v)315 void modifyZone(double date, FAUSTFLOAT v) 316 { 317 if (fInputCtrl && inRange(fMin, fMax, v)) { 318 uiMidiTimedItem::modifyZone(date, v); 319 } 320 } 321 322 }; 323 324 /** 325 * uiMidiChanPress. 326 */ 327 class uiMidiChanPress : public uiMidiTimedItem, public uiConverter { 328 329 public: 330 331 uiMidiChanPress(midi* midi_out, GUI* ui, 332 FAUSTFLOAT* zone, 333 FAUSTFLOAT min, FAUSTFLOAT max, 334 bool input = true, 335 MetaDataUI::Scale scale = MetaDataUI::kLin, 336 int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)337 :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max) 338 {} ~uiMidiChanPress()339 virtual ~uiMidiChanPress() 340 {} 341 reflectZone()342 virtual void reflectZone() 343 { 344 FAUSTFLOAT v = *fZone; 345 fCache = v; 346 if (fChan == 0) { 347 // Send on [0..15] channels on the MIDI layer 348 for (int chan = 0; chan < 16; chan++) { 349 fMidiOut->chanPress(chan, fConverter->faust2ui(v)); 350 } 351 } else { 352 fMidiOut->chanPress(fChan - 1, fConverter->faust2ui(v)); 353 } 354 } 355 modifyZone(FAUSTFLOAT v)356 void modifyZone(FAUSTFLOAT v) 357 { 358 if (fInputCtrl) { 359 uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); 360 } 361 } 362 modifyZone(double date,FAUSTFLOAT v)363 void modifyZone(double date, FAUSTFLOAT v) 364 { 365 if (fInputCtrl) { 366 uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); 367 } 368 } 369 370 }; 371 372 /** 373 * uiMidiCtrlChange does scale (kLin/kLog/kExp) mapping. 374 */ 375 class uiMidiCtrlChange : public uiMidiTimedItem, public uiConverter { 376 377 private: 378 379 int fCtrl; 380 381 public: 382 383 uiMidiCtrlChange(midi* midi_out, int ctrl, GUI* ui, 384 FAUSTFLOAT* zone, 385 FAUSTFLOAT min, FAUSTFLOAT max, 386 bool input = true, 387 MetaDataUI::Scale scale = MetaDataUI::kLin, 388 int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)389 :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fCtrl(ctrl) 390 {} ~uiMidiCtrlChange()391 virtual ~uiMidiCtrlChange() 392 {} 393 reflectZone()394 virtual void reflectZone() 395 { 396 FAUSTFLOAT v = *fZone; 397 fCache = v; 398 if (fChan == 0) { 399 // Send on [0..15] channels on the MIDI layer 400 for (int chan = 0; chan < 16; chan++) { 401 fMidiOut->ctrlChange(chan, fCtrl, fConverter->faust2ui(v)); 402 } 403 } else { 404 fMidiOut->ctrlChange(fChan - 1, fCtrl, fConverter->faust2ui(v)); 405 } 406 } 407 modifyZone(FAUSTFLOAT v)408 void modifyZone(FAUSTFLOAT v) 409 { 410 if (fInputCtrl) { 411 uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); 412 } 413 } 414 modifyZone(double date,FAUSTFLOAT v)415 void modifyZone(double date, FAUSTFLOAT v) 416 { 417 if (fInputCtrl) { 418 uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); 419 } 420 } 421 }; 422 423 class uiMidiPitchWheel : public uiMidiTimedItem { 424 425 private: 426 427 LinearValueConverter2 fConverter; 428 429 public: 430 431 uiMidiPitchWheel(midi* midi_out, GUI* ui, FAUSTFLOAT* zone, 432 FAUSTFLOAT min, FAUSTFLOAT max, 433 bool input = true, int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)434 :uiMidiTimedItem(midi_out, ui, zone, input, chan) 435 { 436 if (min <= 0 && max >= 0) { 437 fConverter = LinearValueConverter2(0., 8191., 16383., double(min), 0., double(max)); 438 } else { 439 // Degenerated case... 440 fConverter = LinearValueConverter2(0., 8191., 16383., double(min),double(min + (max - min)/FAUSTFLOAT(2)), double(max)); 441 } 442 } 443 ~uiMidiPitchWheel()444 virtual ~uiMidiPitchWheel() 445 {} 446 reflectZone()447 virtual void reflectZone() 448 { 449 FAUSTFLOAT v = *fZone; 450 fCache = v; 451 if (fChan == 0) { 452 // Send on [0..15] channels on the MIDI layer 453 for (int chan = 0; chan < 16; chan++) { 454 fMidiOut->pitchWheel(chan, fConverter.faust2ui(v)); 455 } 456 } else { 457 fMidiOut->pitchWheel(fChan - 1, fConverter.faust2ui(v)); 458 } 459 } 460 modifyZone(FAUSTFLOAT v)461 void modifyZone(FAUSTFLOAT v) 462 { 463 if (fInputCtrl) { 464 uiItem::modifyZone(FAUSTFLOAT(fConverter.ui2faust(v))); 465 } 466 } 467 modifyZone(double date,FAUSTFLOAT v)468 void modifyZone(double date, FAUSTFLOAT v) 469 { 470 if (fInputCtrl) { 471 uiMidiTimedItem::modifyZone(FAUSTFLOAT(fConverter.ui2faust(v))); 472 } 473 } 474 setRange(int val)475 void setRange(int val) 476 { 477 double semi = (val / 128) + ((val % 128) / 100.); 478 fConverter.setMappingValues(0., 8191., 16383., -semi, 0., semi); 479 } 480 481 }; 482 483 /** 484 * uiMidiKeyOn does scale (kLin/kLog/kExp) mapping for velocity. 485 */ 486 class uiMidiKeyOn : public uiMidiTimedItem, public uiConverter { 487 488 private: 489 490 int fKeyOn; 491 492 public: 493 494 uiMidiKeyOn(midi* midi_out, int key, GUI* ui, 495 FAUSTFLOAT* zone, 496 FAUSTFLOAT min, FAUSTFLOAT max, 497 bool input = true, 498 MetaDataUI::Scale scale = MetaDataUI::kLin, 499 int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)500 :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fKeyOn(key) 501 {} ~uiMidiKeyOn()502 virtual ~uiMidiKeyOn() 503 {} 504 reflectZone()505 virtual void reflectZone() 506 { 507 FAUSTFLOAT v = *fZone; 508 fCache = v; 509 if (fChan == 0) { 510 // Send on [0..15] channels on the MIDI layer 511 for (int chan = 0; chan < 16; chan++) { 512 fMidiOut->keyOn(chan, fKeyOn, fConverter->faust2ui(v)); 513 } 514 } else { 515 fMidiOut->keyOn(fChan - 1, fKeyOn, fConverter->faust2ui(v)); 516 } 517 } 518 modifyZone(FAUSTFLOAT v)519 void modifyZone(FAUSTFLOAT v) 520 { 521 if (fInputCtrl) { 522 uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); 523 } 524 } 525 modifyZone(double date,FAUSTFLOAT v)526 void modifyZone(double date, FAUSTFLOAT v) 527 { 528 if (fInputCtrl) { 529 uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); 530 } 531 } 532 533 }; 534 535 /** 536 * uiMidiKeyOff does scale (kLin/kLog/kExp) mapping for velocity. 537 */ 538 class uiMidiKeyOff : public uiMidiTimedItem, public uiConverter { 539 540 private: 541 542 int fKeyOff; 543 544 public: 545 546 uiMidiKeyOff(midi* midi_out, int key, GUI* ui, 547 FAUSTFLOAT* zone, 548 FAUSTFLOAT min, FAUSTFLOAT max, 549 bool input = true, 550 MetaDataUI::Scale scale = MetaDataUI::kLin, 551 int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)552 :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fKeyOff(key) 553 {} ~uiMidiKeyOff()554 virtual ~uiMidiKeyOff() 555 {} 556 reflectZone()557 virtual void reflectZone() 558 { 559 FAUSTFLOAT v = *fZone; 560 fCache = v; 561 if (fChan == 0) { 562 // Send on [0..15] channels on the MIDI layer 563 for (int chan = 0; chan < 16; chan++) { 564 fMidiOut->keyOn(chan, fKeyOff, fConverter->faust2ui(v)); 565 } 566 } else { 567 fMidiOut->keyOn(fChan - 1, fKeyOff, fConverter->faust2ui(v)); 568 } 569 } 570 modifyZone(FAUSTFLOAT v)571 void modifyZone(FAUSTFLOAT v) 572 { 573 if (fInputCtrl) { 574 uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); 575 } 576 } 577 modifyZone(double date,FAUSTFLOAT v)578 void modifyZone(double date, FAUSTFLOAT v) 579 { 580 if (fInputCtrl) { 581 uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); 582 } 583 } 584 585 }; 586 587 /** 588 * uiMidiKeyPress does scale (kLin/kLog/kExp) mapping for velocity. 589 */ 590 class uiMidiKeyPress : public uiMidiTimedItem, public uiConverter { 591 592 private: 593 594 int fKey; 595 596 public: 597 598 uiMidiKeyPress(midi* midi_out, int key, GUI* ui, 599 FAUSTFLOAT* zone, 600 FAUSTFLOAT min, FAUSTFLOAT max, 601 bool input = true, 602 MetaDataUI::Scale scale = MetaDataUI::kLin, 603 int chan = 0) uiMidiTimedItem(midi_out,ui,zone,input,chan)604 :uiMidiTimedItem(midi_out, ui, zone, input, chan), uiConverter(scale, 0., 127., min, max), fKey(key) 605 {} ~uiMidiKeyPress()606 virtual ~uiMidiKeyPress() 607 {} 608 reflectZone()609 virtual void reflectZone() 610 { 611 FAUSTFLOAT v = *fZone; 612 fCache = v; 613 if (fChan == 0) { 614 // Send on [0..15] channels on the MIDI layer 615 for (int chan = 0; chan < 16; chan++) { 616 fMidiOut->keyOn(chan, fKey, fConverter->faust2ui(v)); 617 } 618 } else { 619 fMidiOut->keyOn(fChan - 1, fKey, fConverter->faust2ui(v)); 620 } 621 } 622 modifyZone(FAUSTFLOAT v)623 void modifyZone(FAUSTFLOAT v) 624 { 625 if (fInputCtrl) { 626 uiItem::modifyZone(FAUSTFLOAT(fConverter->ui2faust(v))); 627 } 628 } 629 modifyZone(double date,FAUSTFLOAT v)630 void modifyZone(double date, FAUSTFLOAT v) 631 { 632 if (fInputCtrl) { 633 uiMidiTimedItem::modifyZone(date, FAUSTFLOAT(fConverter->ui2faust(v))); 634 } 635 } 636 637 }; 638 639 /****************************************************************************************** 640 * MidiUI : Faust User Interface 641 * This class decodes MIDI metadata and maps incoming MIDI messages to them. 642 * Currently ctrlChange, keyOn/keyOff, keyPress, progChange, chanPress, pitchWheel/pitchBend 643 * start/stop/clock meta data are handled. 644 * 645 * Maps associating MIDI event ID (like each ctrl number) with all MIDI aware UI items 646 * are defined and progressively filled when decoding MIDI related metadata. 647 * MIDI aware UI items are used in both directions: 648 * - modifying their internal state when receving MIDI input events 649 * - sending their internal state as MIDI output events 650 *******************************************************************************************/ 651 652 class MidiUI : public GUI, public midi, public midi_interface, public MetaDataUI { 653 654 // Add uiItem subclasses objects are deallocated by the inherited GUI class 655 typedef std::map <int, std::vector<uiMidiCtrlChange*> > TCtrlChangeTable; 656 typedef std::vector<uiMidiProgChange*> TProgChangeTable; 657 typedef std::vector<uiMidiChanPress*> TChanPressTable; 658 typedef std::map <int, std::vector<uiMidiKeyOn*> > TKeyOnTable; 659 typedef std::map <int, std::vector<uiMidiKeyOff*> > TKeyOffTable; 660 typedef std::map <int, std::vector<uiMidiKeyPress*> > TKeyPressTable; 661 typedef std::vector<uiMidiPitchWheel*> TPitchWheelTable; 662 663 protected: 664 665 TCtrlChangeTable fCtrlChangeTable; 666 TProgChangeTable fProgChangeTable; 667 TChanPressTable fChanPressTable; 668 TKeyOnTable fKeyOnTable; 669 TKeyOffTable fKeyOffTable; 670 TKeyOnTable fKeyTable; 671 TKeyPressTable fKeyPressTable; 672 TPitchWheelTable fPitchWheelTable; 673 674 std::vector<uiMidiStart*> fStartTable; 675 std::vector<uiMidiStop*> fStopTable; 676 std::vector<uiMidiClock*> fClockTable; 677 678 std::vector<std::pair <std::string, std::string> > fMetaAux; 679 680 midi_handler* fMidiHandler; 681 bool fDelete; 682 bool fTimeStamp; 683 684 void addGenericZone(FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, bool input = true) 685 { 686 if (fMetaAux.size() > 0) { 687 for (size_t i = 0; i < fMetaAux.size(); i++) { 688 unsigned num; 689 unsigned chan; 690 if (fMetaAux[i].first == "midi") { 691 if (gsscanf(fMetaAux[i].second.c_str(), "ctrl %u %u", &num, &chan) == 2) { 692 fCtrlChangeTable[num].push_back(new uiMidiCtrlChange(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); 693 } else if (gsscanf(fMetaAux[i].second.c_str(), "ctrl %u", &num) == 1) { 694 fCtrlChangeTable[num].push_back(new uiMidiCtrlChange(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); 695 } else if (gsscanf(fMetaAux[i].second.c_str(), "keyon %u %u", &num, &chan) == 2) { 696 fKeyOnTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); 697 } else if (gsscanf(fMetaAux[i].second.c_str(), "keyon %u", &num) == 1) { 698 fKeyOnTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); 699 } else if (gsscanf(fMetaAux[i].second.c_str(), "keyoff %u %u", &num, &chan) == 2) { 700 fKeyOffTable[num].push_back(new uiMidiKeyOff(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); 701 } else if (gsscanf(fMetaAux[i].second.c_str(), "keyoff %u", &num) == 1) { 702 fKeyOffTable[num].push_back(new uiMidiKeyOff(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); 703 } else if (gsscanf(fMetaAux[i].second.c_str(), "key %u %u", &num, &chan) == 2) { 704 fKeyTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); 705 } else if (gsscanf(fMetaAux[i].second.c_str(), "key %u", &num) == 1) { 706 fKeyTable[num].push_back(new uiMidiKeyOn(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); 707 } else if (gsscanf(fMetaAux[i].second.c_str(), "keypress %u %u", &num, &chan) == 2) { 708 fKeyPressTable[num].push_back(new uiMidiKeyPress(fMidiHandler, num, this, zone, min, max, input, getScale(zone), chan)); 709 } else if (gsscanf(fMetaAux[i].second.c_str(), "keypress %u", &num) == 1) { 710 fKeyPressTable[num].push_back(new uiMidiKeyPress(fMidiHandler, num, this, zone, min, max, input, getScale(zone))); 711 } else if (gsscanf(fMetaAux[i].second.c_str(), "pgm %u", &chan) == 1) { 712 fProgChangeTable.push_back(new uiMidiProgChange(fMidiHandler, this, zone, min, max, input, chan)); 713 } else if (strcmp(fMetaAux[i].second.c_str(), "pgm") == 0) { 714 fProgChangeTable.push_back(new uiMidiProgChange(fMidiHandler, this, zone, min, max, input)); 715 } else if (gsscanf(fMetaAux[i].second.c_str(), "chanpress %u", &chan) == 1) { 716 fChanPressTable.push_back(new uiMidiChanPress(fMidiHandler, this, zone, min, max, input, getScale(zone), chan)); 717 } else if ((fMetaAux[i].second == "chanpress")) { 718 fChanPressTable.push_back(new uiMidiChanPress(fMidiHandler, this, zone, min, max, input, getScale(zone))); 719 } else if ((gsscanf(fMetaAux[i].second.c_str(), "pitchwheel %u", &chan) == 1) || (gsscanf(fMetaAux[i].second.c_str(), "pitchbend %u", &chan) == 1)) { 720 fPitchWheelTable.push_back(new uiMidiPitchWheel(fMidiHandler, this, zone, min, max, input, chan)); 721 } else if ((fMetaAux[i].second == "pitchwheel") || (fMetaAux[i].second == "pitchbend")) { 722 fPitchWheelTable.push_back(new uiMidiPitchWheel(fMidiHandler, this, zone, min, max, input)); 723 // MIDI sync 724 } else if (fMetaAux[i].second == "start") { 725 fStartTable.push_back(new uiMidiStart(fMidiHandler, this, zone, input)); 726 } else if (fMetaAux[i].second == "stop") { 727 fStopTable.push_back(new uiMidiStop(fMidiHandler, this, zone, input)); 728 } else if (fMetaAux[i].second == "clock") { 729 fClockTable.push_back(new uiMidiClock(fMidiHandler, this, zone, input)); 730 // Explicit metadata to activate 'timestamp' mode 731 } else if (fMetaAux[i].second == "timestamp") { 732 fTimeStamp = true; 733 } 734 } 735 } 736 } 737 fMetaAux.clear(); 738 } 739 740 template <typename TABLE> updateTable1(TABLE & table,double date,int channel,int val1)741 void updateTable1(TABLE& table, double date, int channel, int val1) 742 { 743 for (size_t i = 0; i < table.size(); i++) { 744 int channel_aux = table[i]->fChan; 745 // channel_aux == 0 means "all channels" 746 if (channel_aux == 0 || channel == channel_aux - 1) { 747 if (fTimeStamp) { 748 table[i]->modifyZone(date, FAUSTFLOAT(val1)); 749 } else { 750 table[i]->modifyZone(FAUSTFLOAT(val1)); 751 } 752 } 753 } 754 } 755 756 template <typename TABLE> updateTable2(TABLE & table,double date,int channel,int val1,int val2)757 void updateTable2(TABLE& table, double date, int channel, int val1, int val2) 758 { 759 if (table.find(val1) != table.end()) { 760 for (size_t i = 0; i < table[val1].size(); i++) { 761 int channel_aux = table[val1][i]->fChan; 762 // channel_aux == 0 means "all channels" 763 if (channel_aux == 0 || channel == channel_aux - 1) { 764 if (fTimeStamp) { 765 table[val1][i]->modifyZone(date, FAUSTFLOAT(val2)); 766 } else { 767 table[val1][i]->modifyZone(FAUSTFLOAT(val2)); 768 } 769 } 770 } 771 } 772 } 773 774 public: 775 776 MidiUI(midi_handler* midi_handler, bool delete_handler = false) 777 { 778 fMidiHandler = midi_handler; 779 fMidiHandler->addMidiIn(this); 780 // TODO: use shared_ptr based implementation 781 fDelete = delete_handler; 782 fTimeStamp = false; 783 } 784 ~MidiUI()785 virtual ~MidiUI() 786 { 787 // Remove from fMidiHandler 788 fMidiHandler->removeMidiIn(this); 789 // TODO: use shared_ptr based implementation 790 if (fDelete) delete fMidiHandler; 791 } 792 run()793 bool run() { return fMidiHandler->startMidi(); } stop()794 void stop() { fMidiHandler->stopMidi(); } 795 addMidiIn(midi * midi_dsp)796 void addMidiIn(midi* midi_dsp) { fMidiHandler->addMidiIn(midi_dsp); } removeMidiIn(midi * midi_dsp)797 void removeMidiIn(midi* midi_dsp) { fMidiHandler->removeMidiIn(midi_dsp); } 798 799 // -- active widgets 800 addButton(const char * label,FAUSTFLOAT * zone)801 virtual void addButton(const char* label, FAUSTFLOAT* zone) 802 { 803 addGenericZone(zone, FAUSTFLOAT(0), FAUSTFLOAT(1)); 804 } addCheckButton(const char * label,FAUSTFLOAT * zone)805 virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) 806 { 807 addGenericZone(zone, FAUSTFLOAT(0), FAUSTFLOAT(1)); 808 } 809 addVerticalSlider(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)810 virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) 811 { 812 addGenericZone(zone, min, max); 813 } addHorizontalSlider(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)814 virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) 815 { 816 addGenericZone(zone, min, max); 817 } addNumEntry(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)818 virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) 819 { 820 addGenericZone(zone, min, max); 821 } 822 823 // -- passive widgets 824 addHorizontalBargraph(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max)825 virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) 826 { 827 addGenericZone(zone, min, max, false); 828 } addVerticalBargraph(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max)829 virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) 830 { 831 addGenericZone(zone, min, max, false); 832 } 833 834 // -- metadata declarations 835 declare(FAUSTFLOAT * zone,const char * key,const char * val)836 virtual void declare(FAUSTFLOAT* zone, const char* key, const char* val) 837 { 838 MetaDataUI::declare(zone, key, val); 839 fMetaAux.push_back(std::make_pair(key, val)); 840 } 841 842 // -- MIDI API 843 key(double date,int channel,int note,int velocity)844 void key(double date, int channel, int note, int velocity) 845 { 846 updateTable2<TKeyOnTable>(fKeyTable, date, channel, note, velocity); 847 } 848 keyOn(double date,int channel,int note,int velocity)849 MapUI* keyOn(double date, int channel, int note, int velocity) 850 { 851 updateTable2<TKeyOnTable>(fKeyOnTable, date, channel, note, velocity); 852 // If note is in fKeyTable, handle it as a keyOn 853 key(date, channel, note, velocity); 854 return nullptr; 855 } 856 keyOff(double date,int channel,int note,int velocity)857 void keyOff(double date, int channel, int note, int velocity) 858 { 859 updateTable2<TKeyOffTable>(fKeyOffTable, date, channel, note, velocity); 860 // If note is in fKeyTable, handle it as a keyOff with a 0 velocity 861 key(date, channel, note, 0); 862 } 863 ctrlChange(double date,int channel,int ctrl,int value)864 void ctrlChange(double date, int channel, int ctrl, int value) 865 { 866 updateTable2<TCtrlChangeTable>(fCtrlChangeTable, date, channel, ctrl, value); 867 } 868 rpn(double date,int channel,int ctrl,int value)869 void rpn(double date, int channel, int ctrl, int value) 870 { 871 if (ctrl == midi::PITCH_BEND_RANGE) { 872 for (size_t i = 0; i < fPitchWheelTable.size(); i++) { 873 // channel_aux == 0 means "all channels" 874 int channel_aux = fPitchWheelTable[i]->fChan; 875 if (channel_aux == 0 || channel == channel_aux - 1) { 876 fPitchWheelTable[i]->setRange(value); 877 } 878 } 879 } 880 } 881 progChange(double date,int channel,int pgm)882 void progChange(double date, int channel, int pgm) 883 { 884 updateTable1<TProgChangeTable>(fProgChangeTable, date, channel, pgm); 885 } 886 pitchWheel(double date,int channel,int wheel)887 void pitchWheel(double date, int channel, int wheel) 888 { 889 updateTable1<TPitchWheelTable>(fPitchWheelTable, date, channel, wheel); 890 } 891 keyPress(double date,int channel,int pitch,int press)892 void keyPress(double date, int channel, int pitch, int press) 893 { 894 updateTable2<TKeyPressTable>(fKeyPressTable, date, channel, pitch, press); 895 } 896 chanPress(double date,int channel,int press)897 void chanPress(double date, int channel, int press) 898 { 899 updateTable1<TChanPressTable>(fChanPressTable, date, channel, press); 900 } 901 ctrlChange14bits(double date,int channel,int ctrl,int value)902 void ctrlChange14bits(double date, int channel, int ctrl, int value) {} 903 904 // MIDI sync 905 startSync(double date)906 void startSync(double date) 907 { 908 for (size_t i = 0; i < fStartTable.size(); i++) { 909 fStartTable[i]->modifyZone(date, FAUSTFLOAT(1)); 910 } 911 } 912 stopSync(double date)913 void stopSync(double date) 914 { 915 for (size_t i = 0; i < fStopTable.size(); i++) { 916 fStopTable[i]->modifyZone(date, FAUSTFLOAT(0)); 917 } 918 } 919 clock(double date)920 void clock(double date) 921 { 922 for (size_t i = 0; i < fClockTable.size(); i++) { 923 fClockTable[i]->modifyZone(date, FAUSTFLOAT(1)); 924 } 925 } 926 }; 927 928 #endif // FAUST_MIDIUI_H 929 /************************** END MidiUI.h **************************/ 930