mm.c (19371B)
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 /**/ 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 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 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 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 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 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 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 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 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 569 private void showstatus(boolean flag) 570 { 571 printf(", now %s\n", flag ? "ON" : "OFF" ); 572 }