1 /* mm.c -- midi monitor */
2 
3 /*****************************************************************************
4 *       Change Log
5 *  Date | Change
6 *-----------+-----------------------------------------------------------------
7 *  7-Apr-86 | Created changelog
8 * 31-Jan-90 | GWL : use new cmdline
9 *  5-Apr-91 | JDW : Further changes
10 * 16-Feb-92 | GWL : eliminate label mmexit:; add error recovery
11 * 18-May-92 | GWL : continuous clocks, etc.
12 * 17-Jan-94 | GWL : option to display notes
13 * 20-Nov-06 | RBD : port mm.c from CMU Midi Toolkit to PortMidi
14 *           |       mm.c -- revealing MIDI secrets for over 20 years!
15 *****************************************************************************/
16 
17 #include "stdlib.h"
18 #include "ctype.h"
19 #include "string.h"
20 #include "stdio.h"
21 #include "porttime.h"
22 #include "portmidi.h"
23 
24 #define STRING_MAX 80
25 
26 #define MIDI_CODE_MASK  0xf0
27 #define MIDI_CHN_MASK   0x0f
28 /*#define MIDI_REALTIME   0xf8
29   #define MIDI_CHAN_MODE  0xfa */
30 #define MIDI_OFF_NOTE   0x80
31 #define MIDI_ON_NOTE    0x90
32 #define MIDI_POLY_TOUCH 0xa0
33 #define MIDI_CTRL       0xb0
34 #define MIDI_CH_PROGRAM 0xc0
35 #define MIDI_TOUCH      0xd0
36 #define MIDI_BEND       0xe0
37 
38 #define MIDI_SYSEX      0xf0
39 #define MIDI_Q_FRAME	0xf1
40 #define MIDI_SONG_POINTER 0xf2
41 #define MIDI_SONG_SELECT 0xf3
42 #define MIDI_TUNE_REQ	0xf6
43 #define MIDI_EOX        0xf7
44 #define MIDI_TIME_CLOCK 0xf8
45 #define MIDI_START      0xfa
46 #define MIDI_CONTINUE	0xfb
47 #define MIDI_STOP       0xfc
48 #define MIDI_ACTIVE_SENSING 0xfe
49 #define MIDI_SYS_RESET  0xff
50 
51 #define MIDI_ALL_SOUND_OFF 0x78
52 #define MIDI_RESET_CONTROLLERS 0x79
53 #define MIDI_LOCAL	0x7a
54 #define MIDI_ALL_OFF	0x7b
55 #define MIDI_OMNI_OFF	0x7c
56 #define MIDI_OMNI_ON	0x7d
57 #define MIDI_MONO_ON	0x7e
58 #define MIDI_POLY_ON	0x7f
59 
60 
61 #define private static
62 
63 #ifndef false
64 #define false 0
65 #define true 1
66 #endif
67 
68 typedef int boolean;
69 
70 int debug = false;	/* never set, but referenced by userio.c */
71 PmStream *midi_in;      /* midi input */
72 boolean active = false;     /* set when midi_in is ready for reading */
73 boolean in_sysex = false;   /* we are reading a sysex message */
74 boolean inited = false;     /* suppress printing during command line parsing */
75 boolean done = false;       /* when true, exit */
76 boolean notes = true;       /* show notes? */
77 boolean controls = true;    /* show continuous controllers */
78 boolean bender = true;      /* record pitch bend etc.? */
79 boolean excldata = true;    /* record system exclusive data? */
80 boolean verbose = true;     /* show text representation? */
81 boolean realdata = true;    /* record real time messages? */
82 boolean clksencnt = true;   /* clock and active sense count on */
83 boolean chmode = true;      /* show channel mode messages */
84 boolean pgchanges = true;   /* show program changes */
85 boolean flush = false;	    /* flush all pending MIDI data */
86 
87 uint32_t filter = 0;            /* remember state of midi filter */
88 
89 uint32_t clockcount = 0;        /* count of clocks */
90 uint32_t actsensecount = 0;     /* cout of active sensing bytes */
91 uint32_t notescount = 0;        /* #notes since last request */
92 uint32_t notestotal = 0;        /* total #notes */
93 
94 char val_format[] = "    Val %d\n";
95 
96 /*****************************************************************************
97 *    Imported variables
98 *****************************************************************************/
99 
100 extern  int     abort_flag;
101 
102 /*****************************************************************************
103 *    Routines local to this module
104 *****************************************************************************/
105 
106 private    void    mmexit(int code);
107 private    void    output(PmMessage data);
108 private    int     put_pitch(int p);
109 private    void    showhelp();
110 private    void    showbytes(PmMessage data, int len, boolean newline);
111 private    void    showstatus(boolean flag);
112 private    void    doascii(char c);
113 private    int     get_number(char *prompt);
114 
115 
116 /* read a number from console */
117 /**/
get_number(char * prompt)118 int get_number(char *prompt)
119 {
120     char line[STRING_MAX];
121     int n = 0, i;
122     printf(prompt);
123     while (n != 1) {
124         n = scanf("%d", &i);
125         fgets(line, STRING_MAX, stdin);
126 
127     }
128     return i;
129 }
130 
131 
receive_poll(PtTimestamp timestamp,void * userData)132 void receive_poll(PtTimestamp timestamp, void *userData)
133 {
134     PmEvent event;
135     int count;
136     if (!active) return;
137     while ((count = Pm_Read(midi_in, &event, 1))) {
138         if (count == 1) output(event.message);
139         else            printf(Pm_GetErrorText(count));
140     }
141 }
142 
143 
144 /****************************************************************************
145 *               main
146 * Effect: prompts for parameters, starts monitor
147 ****************************************************************************/
148 
main(int argc,char ** argv)149 int main(int argc, char **argv)
150 {
151     char *argument;
152     int inp;
153     PmError err;
154     int i;
155     if (argc > 1) { /* first arg can change defaults */
156         argument = argv[1];
157         while (*argument) doascii(*argument++);
158     }
159     showhelp();
160     /* use porttime callback to empty midi queue and print */
161     Pt_Start(1, receive_poll, 0);
162     /* list device information */
163     printf("MIDI input devices:\n");
164     for (i = 0; i < Pm_CountDevices(); i++) {
165         const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
166         if (info->input) printf("%d: %s, %s\n", i, info->interf, info->name);
167     }
168     inp = get_number("Type input device number: ");
169     err = Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
170     if (err) {
171         printf(Pm_GetErrorText(err));
172         Pt_Stop();
173         mmexit(1);
174     }
175     Pm_SetFilter(midi_in, filter);
176     inited = true; /* now can document changes, set filter */
177     printf("Midi Monitor ready.\n");
178     active = true;
179     while (!done) {
180         char s[100];
181         if (fgets(s, 100, stdin)) {
182             doascii(s[0]);
183         }
184     }
185     active = false;
186     Pm_Close(midi_in);
187     Pt_Stop();
188     Pm_Terminate();
189     mmexit(0);
190     return 0; // make the compiler happy be returning a value
191 }
192 
193 
194 /****************************************************************************
195 *               doascii
196 * Inputs:
197 *    char c: input character
198 * Effect: interpret to revise flags
199 ****************************************************************************/
200 
doascii(char c)201 private void doascii(char c)
202 {
203     if (isupper(c)) c = tolower(c);
204     if (c == 'q') done = true;
205     else if (c == 'b') {
206         bender = !bender;
207         filter ^= PM_FILT_PITCHBEND;
208         if (inited)
209             printf("Pitch Bend, etc. %s\n", (bender ? "ON" : "OFF"));
210     } else if (c == 'c') {
211         controls = !controls;
212         filter ^= PM_FILT_CONTROL;
213         if (inited)
214             printf("Control Change %s\n", (controls ? "ON" : "OFF"));
215     } else if (c == 'h') {
216         pgchanges = !pgchanges;
217         filter ^= PM_FILT_PROGRAM;
218         if (inited)
219             printf("Program Changes %s\n", (pgchanges ? "ON" : "OFF"));
220     } else if (c == 'n') {
221         notes = !notes;
222         filter ^= PM_FILT_NOTE;
223         if (inited)
224             printf("Notes %s\n", (notes ? "ON" : "OFF"));
225     } else if (c == 'x') {
226         excldata = !excldata;
227         filter ^= PM_FILT_SYSEX;
228         if (inited)
229             printf("System Exclusive data %s\n", (excldata ? "ON" : "OFF"));
230     } else if (c == 'r') {
231         realdata = !realdata;
232         filter ^= (PM_FILT_PLAY | PM_FILT_RESET | PM_FILT_TICK | PM_FILT_UNDEFINED);
233         if (inited)
234             printf("Real Time messages %s\n", (realdata ? "ON" : "OFF"));
235     } else if (c == 'k') {
236         clksencnt = !clksencnt;
237         filter ^= PM_FILT_CLOCK;
238         if (inited)
239             printf("Clock and Active Sense Counting %s\n", (clksencnt ? "ON" : "OFF"));
240         if (!clksencnt) clockcount = actsensecount = 0;
241     } else if (c == 's') {
242         if (clksencnt) {
243             if (inited)
244                 printf("Clock Count %ld\nActive Sense Count %ld\n",
245                         (long) clockcount, (long) actsensecount);
246         } else if (inited) {
247             printf("Clock Counting not on\n");
248         }
249     } else if (c == 't') {
250         notestotal+=notescount;
251         if (inited)
252             printf("This Note Count %ld\nTotal Note Count %ld\n",
253                     (long) notescount, (long) notestotal);
254         notescount=0;
255     } else if (c == 'v') {
256         verbose = !verbose;
257         if (inited)
258             printf("Verbose %s\n", (verbose ? "ON" : "OFF"));
259     } else if (c == 'm') {
260         chmode = !chmode;
261         if (inited)
262             printf("Channel Mode Messages %s", (chmode ? "ON" : "OFF"));
263     } else {
264         if (inited) {
265             if (c == ' ') {
266                 PmEvent event;
267                 while (Pm_Read(midi_in, &event, 1)) ;	/* flush midi input */
268                 printf("...FLUSHED MIDI INPUT\n\n");
269             } else showhelp();
270         }
271     }
272     if (inited) Pm_SetFilter(midi_in, filter);
273 }
274 
275 
276 
mmexit(int code)277 private void mmexit(int code)
278 {
279     /* if this is not being run from a console, maybe we should wait for
280      * the user to read error messages before exiting
281      */
282     exit(code);
283 }
284 
285 
286 /****************************************************************************
287 *               output
288 * Inputs:
289 *    data: midi message buffer holding one command or 4 bytes of sysex msg
290 * Effect: format and print  midi data
291 ****************************************************************************/
292 
293 char vel_format[] = "    Vel %d\n";
294 
output(PmMessage data)295 private void output(PmMessage data)
296 {
297     int command;    /* the current command */
298     int chan;   /* the midi channel of the current event */
299     int len;    /* used to get constant field width */
300 
301     /* printf("output data %8x; ", data); */
302 
303     command = Pm_MessageStatus(data) & MIDI_CODE_MASK;
304     chan = Pm_MessageStatus(data) & MIDI_CHN_MASK;
305 
306     if (in_sysex || Pm_MessageStatus(data) == MIDI_SYSEX) {
307 #define sysex_max 16
308         int i;
309         PmMessage data_copy = data;
310         in_sysex = true;
311         /* look for MIDI_EOX in first 3 bytes
312          * if realtime messages are embedded in sysex message, they will
313          * be printed as if they are part of the sysex message
314          */
315         for (i = 0; (i < 4) && ((data_copy & 0xFF) != MIDI_EOX); i++)
316             data_copy >>= 8;
317         if (i < 4) {
318             in_sysex = false;
319             i++; /* include the EOX byte in output */
320         }
321         showbytes(data, i, verbose);
322         if (verbose) printf("System Exclusive\n");
323     } else if (command == MIDI_ON_NOTE && Pm_MessageData2(data) != 0) {
324         notescount++;
325         if (notes) {
326             showbytes(data, 3, verbose);
327             if (verbose) {
328                 printf("NoteOn  Chan %2d Key %3d ", chan, Pm_MessageData1(data));
329                 len = put_pitch(Pm_MessageData1(data));
330                 printf(vel_format + len, Pm_MessageData2(data));
331             }
332         }
333     } else if ((command == MIDI_ON_NOTE /* && Pm_MessageData2(data) == 0 */ ||
334                command == MIDI_OFF_NOTE) && notes) {
335         showbytes(data, 3, verbose);
336         if (verbose) {
337             printf("NoteOff Chan %2d Key %3d ", chan, Pm_MessageData1(data));
338             len = put_pitch(Pm_MessageData1(data));
339             printf(vel_format + len, Pm_MessageData2(data));
340         }
341     } else if (command == MIDI_CH_PROGRAM && pgchanges) {
342         showbytes(data, 2, verbose);
343         if (verbose) {
344             printf("  ProgChg Chan %2d Prog %2d\n", chan, Pm_MessageData1(data) + 1);
345         }
346     } else if (command == MIDI_CTRL) {
347                /* controls 121 (MIDI_RESET_CONTROLLER) to 127 are channel
348                 * mode messages. */
349         if (Pm_MessageData1(data) < MIDI_ALL_SOUND_OFF) {
350             showbytes(data, 3, verbose);
351             if (verbose) {
352                 printf("CtrlChg Chan %2d Ctrl %2d Val %2d\n",
353                        chan, Pm_MessageData1(data), Pm_MessageData2(data));
354             }
355         } else /* channel mode */ if (chmode) {
356             showbytes(data, 3, verbose);
357             if (verbose) {
358                 switch (Pm_MessageData1(data)) {
359                   case MIDI_ALL_SOUND_OFF:
360                       printf("All Sound Off, Chan %2d\n", chan);
361                     break;
362                   case MIDI_RESET_CONTROLLERS:
363                     printf("Reset All Controllers, Chan %2d\n", chan);
364                     break;
365                   case MIDI_LOCAL:
366                     printf("LocCtrl Chan %2d %s\n",
367                             chan, Pm_MessageData2(data) ? "On" : "Off");
368                     break;
369                   case MIDI_ALL_OFF:
370                     printf("All Off Chan %2d\n", chan);
371                     break;
372                   case MIDI_OMNI_OFF:
373                     printf("OmniOff Chan %2d\n", chan);
374                     break;
375                   case MIDI_OMNI_ON:
376                     printf("Omni On Chan %2d\n", chan);
377                     break;
378                   case MIDI_MONO_ON:
379                     printf("Mono On Chan %2d\n", chan);
380                     if (Pm_MessageData2(data))
381                         printf(" to %d received channels\n", Pm_MessageData2(data));
382                     else
383                         printf(" to all received channels\n");
384                     break;
385                   case MIDI_POLY_ON:
386                     printf("Poly On Chan %2d\n", chan);
387                     break;
388                 }
389             }
390         }
391     } else if (command == MIDI_POLY_TOUCH && bender) {
392         showbytes(data, 3, verbose);
393         if (verbose) {
394             printf("P.Touch Chan %2d Key %2d ", chan, Pm_MessageData1(data));
395             len = put_pitch(Pm_MessageData1(data));
396             printf(val_format + len, Pm_MessageData2(data));
397         }
398     } else if (command == MIDI_TOUCH && bender) {
399         showbytes(data, 2, verbose);
400         if (verbose) {
401             printf("  A.Touch Chan %2d Val %2d\n", chan, Pm_MessageData1(data));
402         }
403     } else if (command == MIDI_BEND && bender) {
404         showbytes(data, 3, verbose);
405         if (verbose) {
406             printf("P.Bend  Chan %2d Val %2d\n", chan,
407                     (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7)));
408         }
409     } else if (Pm_MessageStatus(data) == MIDI_SONG_POINTER) {
410         showbytes(data, 3, verbose);
411         if (verbose) {
412             printf("    Song Position %d\n",
413                     (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7)));
414         }
415     } else if (Pm_MessageStatus(data) == MIDI_SONG_SELECT) {
416         showbytes(data, 2, verbose);
417         if (verbose) {
418             printf("    Song Select %d\n", Pm_MessageData1(data));
419         }
420     } else if (Pm_MessageStatus(data) == MIDI_TUNE_REQ) {
421         showbytes(data, 1, verbose);
422         if (verbose) {
423             printf("    Tune Request\n");
424         }
425     } else if (Pm_MessageStatus(data) == MIDI_Q_FRAME && realdata) {
426         showbytes(data, 2, verbose);
427         if (verbose) {
428             printf("    Time Code Quarter Frame Type %d Values %d\n",
429                     (Pm_MessageData1(data) & 0x70) >> 4, Pm_MessageData1(data) & 0xf);
430         }
431     } else if (Pm_MessageStatus(data) == MIDI_START && realdata) {
432         showbytes(data, 1, verbose);
433         if (verbose) {
434             printf("    Start\n");
435         }
436     } else if (Pm_MessageStatus(data) == MIDI_CONTINUE && realdata) {
437         showbytes(data, 1, verbose);
438         if (verbose) {
439             printf("    Continue\n");
440         }
441     } else if (Pm_MessageStatus(data) == MIDI_STOP && realdata) {
442         showbytes(data, 1, verbose);
443         if (verbose) {
444             printf("    Stop\n");
445         }
446     } else if (Pm_MessageStatus(data) == MIDI_SYS_RESET && realdata) {
447         showbytes(data, 1, verbose);
448         if (verbose) {
449             printf("    System Reset\n");
450         }
451     } else if (Pm_MessageStatus(data) == MIDI_TIME_CLOCK) {
452         if (clksencnt) clockcount++;
453         else if (realdata) {
454             showbytes(data, 1, verbose);
455             if (verbose) {
456                 printf("    Clock\n");
457             }
458         }
459     } else if (Pm_MessageStatus(data) == MIDI_ACTIVE_SENSING) {
460         if (clksencnt) actsensecount++;
461         else if (realdata) {
462             showbytes(data, 1, verbose);
463             if (verbose) {
464                 printf("    Active Sensing\n");
465             }
466         }
467     } else showbytes(data, 3, verbose);
468     fflush(stdout);
469 }
470 
471 
472 /****************************************************************************
473 *               put_pitch
474 * Inputs:
475 *    int p: pitch number
476 * Effect: write out the pitch name for a given number
477 ****************************************************************************/
478 
put_pitch(int p)479 private int put_pitch(int p)
480 {
481     char result[8];
482     static char *ptos[] = {
483         "c", "cs", "d", "ef", "e", "f", "fs", "g",
484         "gs", "a", "bf", "b"    };
485     /* note octave correction below */
486     sprintf(result, "%s%d", ptos[p % 12], (p / 12) - 1);
487     printf(result);
488     return strlen(result);
489 }
490 
491 
492 /****************************************************************************
493 *               showbytes
494 * Effect: print hex data, precede with newline if asked
495 ****************************************************************************/
496 
497 char nib_to_hex[] = "0123456789ABCDEF";
498 
showbytes(PmMessage data,int len,boolean newline)499 private void showbytes(PmMessage data, int len, boolean newline)
500 {
501     int count = 0;
502     int i;
503 
504 /*    if (newline) {
505         putchar('\n');
506         count++;
507     } */
508     for (i = 0; i < len; i++) {
509         putchar(nib_to_hex[(data >> 4) & 0xF]);
510         putchar(nib_to_hex[data & 0xF]);
511         count += 2;
512         if (count > 72) {
513             putchar('.');
514             putchar('.');
515             putchar('.');
516             break;
517         }
518         data >>= 8;
519     }
520     putchar(' ');
521 }
522 
523 
524 
525 /****************************************************************************
526 *               showhelp
527 * Effect: print help text
528 ****************************************************************************/
529 
showhelp()530 private void showhelp()
531 {
532     printf("\n");
533     printf("   Item Reported  Range     Item Reported  Range\n");
534     printf("   -------------  -----     -------------  -----\n");
535     printf("   Channels       1 - 16    Programs       1 - 128\n");
536     printf("   Controllers    0 - 127   After Touch    0 - 127\n");
537     printf("   Loudness       0 - 127   Pitch Bend     0 - 16383, center = 8192\n");
538     printf("   Pitches        0 - 127, 60 = c4 = middle C\n");
539     printf(" \n");
540     printf("n toggles notes");
541     showstatus(notes);
542     printf("t displays noteon count since last t\n");
543     printf("b toggles pitch bend, aftertouch");
544     showstatus(bender);
545     printf("c toggles continuous control");
546     showstatus(controls);
547     printf("h toggles program changes");
548     showstatus(pgchanges);
549     printf("x toggles system exclusive");
550     showstatus(excldata);
551     printf("k toggles clock and sense counting only");
552     showstatus(clksencnt);
553     printf("r toggles other real time messages & SMPTE");
554     showstatus(realdata);
555     printf("s displays clock and sense count since last k\n");
556     printf("m toggles channel mode messages");
557     showstatus(chmode);
558     printf("v toggles verbose text");
559     showstatus(verbose);
560     printf("q quits\n");
561     printf("\n");
562 }
563 
564 /****************************************************************************
565 *               showstatus
566 * Effect: print status of flag
567 ****************************************************************************/
568 
showstatus(boolean flag)569 private void showstatus(boolean flag)
570 {
571     printf(", now %s\n", flag ? "ON" : "OFF" );
572 }
573