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