aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_test/sysex.c
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_test/sysex.c')
-rwxr-xr-xportmidi/pm_test/sysex.c556
1 files changed, 556 insertions, 0 deletions
diff --git a/portmidi/pm_test/sysex.c b/portmidi/pm_test/sysex.c
new file mode 100755
index 0000000..c2c7187
--- /dev/null
+++ b/portmidi/pm_test/sysex.c
@@ -0,0 +1,556 @@
1/* sysex.c -- example program showing how to send and receive sysex
2 messages
3
4 Messages are stored in a file using 2-digit hexadecimal numbers,
5 one per byte, separated by blanks, with up to 32 numbers per line:
6 F0 14 A7 4B ...
7
8 */
9
10#include "stdio.h"
11#include "stdlib.h"
12#include "assert.h"
13#include "portmidi.h"
14#include "porttime.h"
15#include "string.h"
16#ifdef WIN32
17// need to get declaration for Sleep()
18#include "windows.h"
19#else
20#include <unistd.h>
21#define Sleep(n) usleep(n * 1000)
22#endif
23
24// enable some extra printing
25#ifndef VERBOSE
26#define VERBOSE 0
27#endif
28
29#define MIDI_SYSEX 0xf0
30#define MIDI_EOX 0xf7
31
32#define STRING_MAX 80
33
34#ifndef true
35#define true 1
36#define false 0
37#endif
38
39int latency = 0;
40
41/* read a number from console */
42/**/
43int get_number(const char *prompt)
44{
45 int n = 0, i;
46 fputs(prompt, stdout);
47 while (n != 1) {
48 n = scanf("%d", &i);
49 while (getchar() != '\n') ;
50 }
51 return i;
52}
53
54
55/* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */
56/**/
57void loopback_test()
58{
59 int outp;
60 int inp;
61 PmStream *midi_in;
62 PmStream *midi_out;
63 unsigned char msg[1024];
64 int32_t len;
65 int i;
66 int data;
67 PmEvent event;
68 int shift;
69 long total_bytes = 0;
70 int32_t begin_time;
71
72 Pt_Start(1, 0, 0);
73
74 printf("Connect a midi cable from an output port to an input port.\n");
75 printf("This test will send random data via sysex message from output\n");
76 printf("to input and check that the correct data was received.\n");
77 outp = get_number("Type output device number: ");
78 /* Open output with 1ms latency -- when latency is non-zero, the Win32
79 implementation supports sending sysex messages incrementally in a
80 series of buffers. This is nicer than allocating a big buffer for the
81 message, and it also seems to work better. Either way works.
82 */
83 while ((latency = get_number(
84 "Latency in milliseconds (0 to send data immediatedly,\n"
85 " >0 to send timestamped messages): ")) < 0);
86 Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
87 inp = get_number("Type input device number: ");
88 /* since we are going to send and then receive, make sure the input buffer
89 is large enough for the entire message */
90 Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
91
92 srand((unsigned int) Pt_Time()); /* seed for random numbers */
93
94 begin_time = Pt_Time();
95 while (total_bytes < 100000) {
96 PmError count;
97 int32_t start_time;
98 int error_position = -1; /* 0; -1; -1 for continuous */
99 int expected = 0;
100 int actual = 0;
101 /* this modification will run until an error is detected */
102 /* set error_position above to 0 for interactive, -1 for */
103 /* continuous */
104 if (error_position >= 0) {
105 int c;
106 printf("Type return to send message, q to quit: ");
107 while ((c = getchar()) != '\n') {
108 if (c == 'q') goto cleanup;
109 }
110 }
111
112 /* compose the message */
113 len = rand() % 998 + 2; /* len only counts data bytes */
114 msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */
115 /* data bytes go from 1 to len */
116 for (i = 0; i < len; i++) {
117/* pick whether data is sequential or random... (docs say random) */
118#define DATA_EXPR (i+1)
119// #define DATA_EXPR rand()
120 msg[i + 1] = DATA_EXPR & 0x7f; /* MIDI data */
121 }
122 /* final EOX goes in len+1, total of len+2 bytes in msg */
123 msg[len + 1] = (char) MIDI_EOX;
124
125 /* sanity check: before we send, there should be no queued data */
126 count = Pm_Read(midi_in, &event, 1);
127
128 if (count != 0) {
129 printf("Before sending anything, a MIDI message was found in\n");
130 printf("the input buffer. Please try again.\n");
131 break;
132 }
133
134 /* send the message two ways: 1) Pm_WriteSysEx, 2) Pm_Write */
135 if (total_bytes & 1) {
136 printf("Sending %d byte sysex msg via Pm_WriteSysEx.\n", len + 2);
137 Pm_WriteSysEx(midi_out, 0, msg);
138 } else {
139 PmEvent event = {0, 0};
140 int bits = 0;
141 printf("Sending %d byte sysex msg via Pm_Write(s).\n", len + 2);
142 for (i = 0; i < len + 2; i++) {
143 event.message |= (msg[i] << bits);
144 bits += 8;
145 if (bits == 32) { /* full message - send it */
146 Pm_Write(midi_out, &event, 1);
147 bits = 0;
148 event.message = 0;
149 }
150 }
151 if (bits > 0) { /* last message is partially full */
152 Pm_Write(midi_out, &event, 1);
153 }
154 }
155
156 /* receive the message and compare to msg[] */
157 data = 0;
158 shift = 0;
159 i = 0;
160 start_time = Pt_Time();
161 if (VERBOSE) {
162 printf("start_time %d\n", start_time);
163 }
164 error_position = -1;
165 /* allow up to 2 seconds for transmission */
166 while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) {
167 count = Pm_Read(midi_in, &event, 1);
168 if (count == 0) {
169 Sleep(1); /* be nice: give some CPU time to the system */
170 continue; /* continue polling for input */
171 }
172 if (VERBOSE) {
173 printf("read %08x ", event.message);
174 fflush(stdout);
175 }
176 /* compare 4 bytes of data until you reach an eox */
177 for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
178 data = (event.message >> shift) & 0xFF;
179 if (data != msg[i] && error_position < 0) {
180 error_position = i;
181 expected = msg[i];
182 actual = data;
183 }
184 i++;
185 }
186 }
187 if (error_position >= 0) {
188 printf("Error at time %d byte %d: sent %x recd %x.\n", Pt_Time(),
189 error_position, expected, actual);
190 break;
191 } else if (i != len + 2) {
192 printf("Error at time %d: byte %d not received.\n", Pt_Time(), i);
193 break;
194 } else {
195 int seconds = (Pt_Time() - begin_time) / 1000;
196 if (seconds == 0) seconds = 1;
197 printf("Correctly received %d byte sysex message.\n", i);
198 total_bytes += i;
199 printf("Cummulative bytes/sec: %d, %d%% done.\n",
200 (int) (total_bytes / seconds),
201 (int) (100 * total_bytes / 100000));
202 }
203 }
204cleanup:
205 Pm_Close(midi_out);
206 Pm_Close(midi_in);
207 return;
208}
209
210
211/* send_multiple test -- send many sysex messages */
212/**/
213void send_multiple_test()
214{
215 int outp;
216 int length;
217 int num_msgs;
218 PmStream *midi_out;
219 unsigned char msg[1024];
220 int i;
221 PtTimestamp start_time;
222 PtTimestamp stop_time;
223
224 Pt_Start(1, 0, 0);
225
226 printf("This is for performance testing. You should be sending to this\n");
227 printf("program running the receive multiple test. Do NOT send to\n");
228 printf("a synthesizer or you risk reprogramming it\n");
229 outp = get_number("Type output device number: ");
230 while ((latency = get_number(
231 "Latency in milliseconds (0 to send data immediatedly,\n"
232 " >0 to send timestamped messages): ")) < 0);
233 Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
234 while ((length = get_number("Message length (7 - 1024): ")) < 7 ||
235 length > 1024) ;
236 while ((num_msgs = get_number("Number of messages: ")) < 1);
237 /* latency, length, and num_msgs should now all be valid */
238 /* compose the message except for sequence number in first 5 bytes */
239 msg[0] = (char) MIDI_SYSEX;
240 for (i = 6; i < length - 1; i++) {
241 msg[i] = i % 128; /* this is just filler */
242 }
243 msg[length - 1] = (char) MIDI_EOX;
244
245 start_time = Pt_Time();
246 /* send the messages */
247 for (i = num_msgs; i > 0; i--) {
248 /* insert sequence number into first 5 data bytes */
249 /* sequence counts down to zero */
250 int j;
251 int count = i;
252 /* 7 bits of message count i goes into each data byte */
253 for (j = 1; j <= 5; j++) {
254 msg[j] = count & 127;
255 count >>= 7;
256 }
257 /* send the message */
258 Pm_WriteSysEx(midi_out, 0, msg);
259 }
260 stop_time = Pt_Time();
261 Pm_Close(midi_out);
262 return;
263}
264
265#define MAX_MSG_LEN 1024
266static unsigned char receive_msg[MAX_MSG_LEN];
267static int receive_msg_index;
268static int receive_msg_length;
269static int receive_msg_count;
270static int receive_msg_error;
271static int receive_msg_messages;
272static PmStream *receive_msg_midi_in;
273static int receive_poll_running;
274
275/* receive_poll -- callback function to check for midi input */
276/**/
277void receive_poll(PtTimestamp timestamp, void *userData)
278{
279 PmError count;
280 PmEvent event;
281 int shift;
282 int data = 0;
283 int i;
284
285 if (!receive_poll_running) return; /* wait until midi device is opened */
286 shift = 0;
287 while (data != MIDI_EOX) {
288 count = Pm_Read(receive_msg_midi_in, &event, 1);
289 if (count == 0) return;
290
291 /* compare 4 bytes of data until you reach an eox */
292 for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
293 receive_msg[receive_msg_index++] = data =
294 (event.message >> shift) & 0xFF;
295 if (receive_msg_index >= MAX_MSG_LEN) {
296 printf("error: incoming sysex too long\n");
297 goto error;
298 }
299 }
300 }
301 /* check the message */
302 if (receive_msg_length == 0) {
303 receive_msg_length = receive_msg_index;
304 }
305 if (receive_msg_length != receive_msg_index) {
306 printf("error: incoming sysex wrong length\n");
307 goto error;
308 }
309 if (receive_msg[0] != MIDI_SYSEX) {
310 printf("error: incoming sysex missing status byte\n");
311 goto error;
312 }
313 /* get and check the count */
314 count = 0;
315 for (i = 0; i < 5; i++) {
316 count += receive_msg[i + 1] << (7 * i);
317 }
318 if (receive_msg_count == -1) {
319 receive_msg_count = count;
320 receive_msg_messages = count;
321 }
322 if (receive_msg_count != count) {
323 printf("error: incoming sysex has wrong count\n");
324 goto error;
325 }
326 for (i = 6; i < receive_msg_index - 1; i++) {
327 if (receive_msg[i] != i % 128) {
328 printf("error: incoming sysex has bad data\n");
329 goto error;
330 }
331 }
332 if (receive_msg[receive_msg_length - 1] != MIDI_EOX) goto error;
333 receive_msg_index = 0; /* get ready for next message */
334 receive_msg_count--;
335 return;
336 error:
337 receive_msg_error = 1;
338 return;
339}
340
341
342/* receive_multiple_test -- send/rcv from 2 to 1000 bytes of random midi data */
343/**/
344void receive_multiple_test()
345{
346 PmError err;
347 int inp;
348
349 printf("This test expects to receive data sent by the send_multiple test\n");
350 printf("The test will check that correct data is received.\n");
351
352 /* Important: start PortTime first -- if it is not started first, it will
353 be started by PortMidi, and then our attempt to open again will fail */
354 receive_poll_running = false;
355 if ((err = Pt_Start(1, receive_poll, 0))) {
356 printf("PortTime error code: %d\n", err);
357 goto cleanup;
358 }
359 inp = get_number("Type input device number: ");
360 Pm_OpenInput(&receive_msg_midi_in, inp, NULL, 512, NULL, NULL);
361 receive_msg_index = 0;
362 receive_msg_length = 0;
363 receive_msg_count = -1;
364 receive_msg_error = 0;
365 receive_poll_running = true;
366 while ((!receive_msg_error) && (receive_msg_count != 0)) {
367#ifdef WIN32
368 Sleep(1000);
369#else
370 sleep(1); /* block and wait */
371#endif
372 }
373 if (receive_msg_error) {
374 printf("Receive_multiple test encountered an error\n");
375 } else {
376 printf("Receive_multiple test successfully received %d sysex messages\n",
377 receive_msg_messages);
378 }
379cleanup:
380 receive_poll_running = false;
381 Pm_Close(receive_msg_midi_in);
382 Pt_Stop();
383 return;
384}
385
386
387#define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8)
388
389
390void receive_sysex()
391{
392 char line[80];
393 FILE *f;
394 PmStream *midi;
395 int shift = 0;
396 int data = 0;
397 int bytes_on_line = 0;
398 PmEvent msg;
399
400 /* determine which output device to use */
401 int i = get_number("Type input device number: ");
402
403 /* open input device */
404 Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL);
405 printf("Midi Input opened, type file for sysex data: ");
406
407 /* open file */
408 if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
409 /* remove the newline character */
410 if (strlen(line) > 0) line[strlen(line) - 1] = 0;
411 f = fopen(line, "w");
412 if (!f) {
413 printf("Could not open %s\n", line);
414 Pm_Close(midi);
415 return;
416 }
417
418 printf("Ready to receive a sysex message\n");
419
420 /* read data and write to file */
421 while (data != MIDI_EOX) {
422 PmError count;
423 count = Pm_Read(midi, &msg, 1);
424 /* CAUTION: this causes busy waiting. It would be better to
425 be in a polling loop to avoid being compute bound. PortMidi
426 does not support a blocking read since this is so seldom
427 useful.
428 */
429 if (count == 0) continue;
430 /* ignore real-time messages */
431 if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue;
432
433 /* write 4 bytes of data until you reach an eox */
434 for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
435 data = (msg.message >> shift) & 0xFF;
436 /* if this is a status byte that's not MIDI_EOX, the sysex
437 message is incomplete and there is no more sysex data */
438 if (data & 0x80 && data != MIDI_EOX) break;
439 fprintf(f, "%2x ", data);
440 if (++bytes_on_line >= 16) {
441 fprintf(f, "\n");
442 bytes_on_line = 0;
443 }
444 }
445 }
446 fclose(f);
447 Pm_Close(midi);
448}
449
450
451void send_sysex()
452{
453 char line[80];
454 FILE *f;
455 PmStream *midi;
456 int data;
457 int shift = 0;
458 PmEvent msg;
459
460 /* determine which output device to use */
461 int i = get_number("Type output device number: ");
462 while ((latency = get_number(
463 "Latency in milliseconds (0 to send data immediatedly,\n"
464 " >0 to send timestamped messages): ")) < 0);
465
466 msg.timestamp = 0; /* no need for timestamp */
467
468 /* open output device */
469 Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency);
470 printf("Midi Output opened, type file with sysex data: ");
471
472 /* open file */
473 if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
474 /* remove the newline character */
475 if (strlen(line) > 0) line[strlen(line) - 1] = 0;
476 f = fopen(line, "r");
477 if (!f) {
478 printf("Could not open %s\n", line);
479 Pm_Close(midi);
480 return;
481 }
482
483 /* read file and send data */
484 msg.message = 0;
485 while (1) {
486 /* get next byte from file */
487
488 if (fscanf(f, "%x", &data) == 1) {
489 /* printf("read %x, ", data); */
490 /* OR byte into message at proper offset */
491 msg.message |= (data << shift);
492 shift += 8;
493 }
494 /* send the message if it's full (shift == 32) or if we are at end */
495 if (shift == 32 || data == MIDI_EOX) {
496 /* this will send sysex data 4 bytes at a time -- it would
497 be much more efficient to send multiple PmEvents at once
498 but this method is simpler. See Pm_WriteSysEx for a more
499 efficient code example.
500 */
501 Pm_Write(midi, &msg, 1);
502 msg.message = 0;
503 shift = 0;
504 }
505 if (data == MIDI_EOX) { /* end of message */
506 fclose(f);
507 Pm_Close(midi);
508 return;
509 }
510 }
511}
512
513
514int main()
515{
516 int i;
517
518 /* list device information */
519 for (i = 0; i < Pm_CountDevices(); i++) {
520 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
521 printf("%d: %s, %s", i, info->interf, info->name);
522 if (info->input) printf(" (input)");
523 if (info->output) printf(" (output)");
524 printf("\n");
525 }
526 while (1) {
527 char cmd;
528 printf("Type r to receive sysex, s to send,"
529 " l for loopback test, m to send multiple,"
530 " n to receive multiple, q to quit: ");
531 cmd = getchar();
532 while (getchar() != '\n') ;
533 switch (cmd) {
534 case 'r':
535 receive_sysex();
536 break;
537 case 's':
538 send_sysex();
539 break;
540 case 'l':
541 loopback_test();
542 break;
543 case 'm':
544 send_multiple_test();
545 break;
546 case 'n':
547 receive_multiple_test();
548 break;
549 case 'q':
550 exit(0);
551 default:
552 break;
553 }
554 }
555 return 0;
556}