/* sysex.c -- example program showing how to send and receive sysex messages Messages are stored in a file using 2-digit hexadecimal numbers, one per byte, separated by blanks, with up to 32 numbers per line: F0 14 A7 4B ... */ #include "stdio.h" #include "stdlib.h" #include "assert.h" #include "portmidi.h" #include "porttime.h" #include "string.h" #ifdef WIN32 // need to get declaration for Sleep() #include "windows.h" #else #define Sleep(n) usleep(n * 1000) #endif #define MIDI_SYSEX 0xf0 #define MIDI_EOX 0xf7 #define STRING_MAX 80 #ifndef true #define true 1 #define false 0 #endif int latency = 0; /* read a number from console */ /**/ int get_number(char *prompt) { char line[STRING_MAX]; int n = 0, i; printf(prompt); while (n != 1) { n = scanf("%d", &i); fgets(line, STRING_MAX, stdin); } return i; } /* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */ /**/ void loopback_test() { int outp; int inp; PmStream *midi_in; PmStream *midi_out; unsigned char msg[1024]; char line[80]; long len; int i; int data; PmEvent event; int shift; Pt_Start(1, 0, 0); printf("Connect a midi cable from an output port to an input port.\n"); printf("This test will send random data via sysex message from output\n"); printf("to input and check that the correct data was received.\n"); outp = get_number("Type output device number: "); /* Open output with 1ms latency -- when latency is non-zero, the Win32 implementation supports sending sysex messages incrementally in a series of buffers. This is nicer than allocating a big buffer for the message, and it also seems to work better. Either way works. */ while ((latency = get_number( "Latency in milliseconds (0 to send data immediatedly,\n" " >0 to send timestamped messages): ")) < 0); Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency); inp = get_number("Type input device number: "); /* since we are going to send and then receive, make sure the input buffer is large enough for the entire message */ Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL); srand((unsigned int) Pt_Time()); /* seed for random numbers */ while (1) { PmError count; long start_time; long error_position = -1; /* 0; -1; -1 for continuous */ long expected = 0; long actual = 0; /* this modification will run until an error is detected */ /* set error_position above to 0 for interactive, -1 for */ /* continuous */ if (error_position >= 0) { printf("Type return to send message, q to quit: "); fgets(line, STRING_MAX, stdin); if (line[0] == 'q') goto cleanup; } /* compose the message */ len = rand() % 998 + 2; /* len only counts data bytes */ msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */ /* data bytes go from 1 to len */ for (i = 0; i < len; i++) { /* pick whether data is sequential or random... (docs say random) */ #define DATA_EXPR (i+1) // #define DATA_EXPR rand() msg[i + 1] = DATA_EXPR & 0x7f; /* MIDI data */ } /* final EOX goes in len+1, total of len+2 bytes in msg */ msg[len + 1] = (char) MIDI_EOX; /* sanity check: before we send, there should be no queued data */ count = Pm_Read(midi_in, &event, 1); if (count != 0) { printf("Before sending anything, a MIDI message was found in\n"); printf("the input buffer. Please try again.\n"); break; } /* send the message */ printf("Sending %ld byte sysex message.\n", len + 2); Pm_WriteSysEx(midi_out, 0, msg); /* receive the message and compare to msg[] */ data = 0; shift = 0; i = 0; start_time = Pt_Time(); error_position = -1; /* allow up to 2 seconds for transmission */ while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) { count = Pm_Read(midi_in, &event, 1); if (count == 0) { Sleep(1); /* be nice: give some CPU time to the system */ continue; /* continue polling for input */ } /* printf("read %lx ", event.message); fflush(stdout); */ /* compare 4 bytes of data until you reach an eox */ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) { data = (event.message >> shift) & 0xFF; if (data != msg[i] && error_position < 0) { error_position = i; expected = msg[i]; actual = data; } i++; } } if (error_position >= 0) { printf("Error at byte %ld: sent %lx recd %lx\n", error_position, expected, actual); } else if (i != len + 2) { printf("Error: byte %d not received\n", i); } else { printf("Correctly "); } printf("received %d byte sysex message.\n", i); } cleanup: Pm_Close(midi_out); Pm_Close(midi_in); return; } /* send_multiple test -- send many sysex messages */ /**/ void send_multiple_test() { int outp; int length; int num_msgs; PmStream *midi_out; unsigned char msg[1024]; int i; PtTimestamp start_time; PtTimestamp stop_time; Pt_Start(1, 0, 0); printf("This is for performance testing. You should be sending to this\n"); printf("program running the receive multiple test. Do NOT send to\n"); printf("a synthesizer or you risk reprogramming it\n"); outp = get_number("Type output device number: "); while ((latency = get_number( "Latency in milliseconds (0 to send data immediatedly,\n" " >0 to send timestamped messages): ")) < 0); Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency); while ((length = get_number("Message length (7 - 1024): ")) < 7 || length > 1024) ; while ((num_msgs = get_number("Number of messages: ")) < 1); /* latency, length, and num_msgs should now all be valid */ /* compose the message except for sequence number in first 5 bytes */ msg[0] = (char) MIDI_SYSEX; for (i = 6; i < length - 1; i++) { msg[i] = i % 128; /* this is just filler */ } msg[length - 1] = (char) MIDI_EOX; start_time = Pt_Time(); /* send the messages */ for (i = num_msgs; i > 0; i--) { /* insert sequence number into first 5 data bytes */ /* sequence counts down to zero */ int j; int count = i; /* 7 bits of message count i goes into each data byte */ for (j = 1; j <= 5; j++) { msg[j] = count & 127; count >>= 7; } /* send the message */ Pm_WriteSysEx(midi_out, 0, msg); } stop_time = Pt_Time(); Pm_Close(midi_out); return; } #define MAX_MSG_LEN 1024 static unsigned char receive_msg[MAX_MSG_LEN]; static long receive_msg_index; static long receive_msg_length; static long receive_msg_count; static long receive_msg_error; static long receive_msg_messages; static PmStream *receive_msg_midi_in; static int receive_poll_running; /* receive_poll -- callback function to check for midi input */ /**/ void receive_poll(PtTimestamp timestamp, void *userData) { PmError count; PmEvent event; int shift; int data = 0; int i; if (!receive_poll_running) return; /* wait until midi device is opened */ shift = 0; while (data != MIDI_EOX) { count = Pm_Read(receive_msg_midi_in, &event, 1); if (count == 0) return; /* compare 4 bytes of data until you reach an eox */ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) { receive_msg[receive_msg_index++] = data = (event.message >> shift) & 0xFF; if (receive_msg_index >= MAX_MSG_LEN) { printf("error: incoming sysex too long\n"); goto error; } } } /* check the message */ if (receive_msg_length == 0) { receive_msg_length = receive_msg_index; } if (receive_msg_length != receive_msg_index) { printf("error: incoming sysex wrong length\n"); goto error; } if (receive_msg[0] != MIDI_SYSEX) { printf("error: incoming sysex missing status byte\n"); goto error; } /* get and check the count */ count = 0; for (i = 0; i < 5; i++) { count += receive_msg[i + 1] << (7 * i); } if (receive_msg_count == -1) { receive_msg_count = count; receive_msg_messages = count; } if (receive_msg_count != count) { printf("error: incoming sysex has wrong count\n"); goto error; } for (i = 6; i < receive_msg_index - 1; i++) { if (receive_msg[i] != i % 128) { printf("error: incoming sysex has bad data\n"); goto error; } } if (receive_msg[receive_msg_length - 1] != MIDI_EOX) goto error; receive_msg_index = 0; /* get ready for next message */ receive_msg_count--; return; error: receive_msg_error = 1; return; } /* receive_multiple_test -- send/rcv from 2 to 1000 bytes of random midi data */ /**/ void receive_multiple_test() { PmError err; int inp; printf("This test expects to receive data sent by the send_multiple test\n"); printf("The test will check that correct data is received.\n"); /* Important: start PortTime first -- if it is not started first, it will be started by PortMidi, and then our attempt to open again will fail */ receive_poll_running = false; if (err = Pt_Start(1, receive_poll, 0)) { printf("PortTime error code: %d\n", err); goto cleanup; } inp = get_number("Type input device number: "); Pm_OpenInput(&receive_msg_midi_in, inp, NULL, 512, NULL, NULL); receive_msg_index = 0; receive_msg_length = 0; receive_msg_count = -1; receive_msg_error = 0; receive_poll_running = true; while ((!receive_msg_error) && (receive_msg_count != 0)) { #ifdef WIN32 Sleep(1000); #else sleep(1); /* block and wait */ #endif } if (receive_msg_error) { printf("Receive_multiple test encountered an error\n"); } else { printf("Receive_multiple test successfully received %d sysex messages\n", receive_msg_messages); } cleanup: receive_poll_running = false; Pm_Close(receive_msg_midi_in); Pt_Stop(); return; } #define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8) void receive_sysex() { char line[80]; FILE *f; PmStream *midi; int shift = 0; int data = 0; int bytes_on_line = 0; PmEvent msg; /* determine which output device to use */ int i = get_number("Type input device number: "); /* open input device */ Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL); printf("Midi Input opened, type file for sysex data: "); /* open file */ fgets(line, STRING_MAX, stdin); /* remove the newline character */ if (strlen(line) > 0) line[strlen(line) - 1] = 0; f = fopen(line, "w"); if (!f) { printf("Could not open %s\n", line); Pm_Close(midi); return; } printf("Ready to receive a sysex message\n"); /* read data and write to file */ while (data != MIDI_EOX) { PmError count; count = Pm_Read(midi, &msg, 1); /* CAUTION: this causes busy waiting. It would be better to be in a polling loop to avoid being compute bound. PortMidi does not support a blocking read since this is so seldom useful. */ if (count == 0) continue; /* ignore real-time messages */ if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue; /* write 4 bytes of data until you reach an eox */ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) { data = (msg.message >> shift) & 0xFF; /* if this is a status byte that's not MIDI_EOX, the sysex message is incomplete and there is no more sysex data */ if (data & 0x80 && data != MIDI_EOX) break; fprintf(f, "%2x ", data); if (++bytes_on_line >= 16) { fprintf(f, "\n"); bytes_on_line = 0; } } } fclose(f); Pm_Close(midi); } void send_sysex() { char line[80]; FILE *f; PmStream *midi; int data; int shift = 0; PmEvent msg; /* determine which output device to use */ int i = get_number("Type output device number: "); while ((latency = get_number( "Latency in milliseconds (0 to send data immediatedly,\n" " >0 to send timestamped messages): ")) < 0); msg.timestamp = 0; /* no need for timestamp */ /* open output device */ Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency); printf("Midi Output opened, type file with sysex data: "); /* open file */ fgets(line, STRING_MAX, stdin); /* remove the newline character */ if (strlen(line) > 0) line[strlen(line) - 1] = 0; f = fopen(line, "r"); if (!f) { printf("Could not open %s\n", line); Pm_Close(midi); return; } /* read file and send data */ msg.message = 0; while (1) { /* get next byte from file */ if (fscanf(f, "%x", &data) == 1) { /* printf("read %x, ", data); */ /* OR byte into message at proper offset */ msg.message |= (data << shift); shift += 8; } /* send the message if it's full (shift == 32) or if we are at end */ if (shift == 32 || data == MIDI_EOX) { /* this will send sysex data 4 bytes at a time -- it would be much more efficient to send multiple PmEvents at once but this method is simpler. See Pm_WriteSysEx for a more efficient code example. */ Pm_Write(midi, &msg, 1); msg.message = 0; shift = 0; } if (data == MIDI_EOX) { /* end of message */ fclose(f); Pm_Close(midi); return; } } } int main() { int i; char line[80]; /* list device information */ for (i = 0; i < Pm_CountDevices(); i++) { const PmDeviceInfo *info = Pm_GetDeviceInfo(i); printf("%d: %s, %s", i, info->interf, info->name); if (info->input) printf(" (input)"); if (info->output) printf(" (output)"); printf("\n"); } while (1) { printf("Type r to receive sysex, s to send," " l for loopback test, m to send multiple," " n to receive multiple, q to quit: "); fgets(line, STRING_MAX, stdin); switch (line[0]) { case 'r': receive_sysex(); break; case 's': send_sysex(); break; case 'l': loopback_test(); break; case 'm': send_multiple_test(); break; case 'n': receive_multiple_test(); break; case 'q': exit(0); default: break; } } return 0; }