1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
|
/* 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;
}
|