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