diff options
Diffstat (limited to 'portmidi/pm_test/midithru.c')
| -rwxr-xr-x | portmidi/pm_test/midithru.c | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/portmidi/pm_test/midithru.c b/portmidi/pm_test/midithru.c new file mode 100755 index 0000000..94b4f13 --- /dev/null +++ b/portmidi/pm_test/midithru.c | |||
| @@ -0,0 +1,455 @@ | |||
| 1 | /* midithru.c -- example program implementing background thru processing */ | ||
| 2 | |||
| 3 | /* suppose you want low-latency midi-thru processing, but your | ||
| 4 | application wants to take advantage of the input buffer and | ||
| 5 | timestamped data so that it does not have to operate with very low | ||
| 6 | latency. | ||
| 7 | |||
| 8 | This program illustrates how to use a timer callback from PortTime | ||
| 9 | to implement a low-latency process that handles midi thru, | ||
| 10 | including correctly merging midi data from the application with | ||
| 11 | midi data from the input port. | ||
| 12 | |||
| 13 | The main application, which runs in the main program thread, will | ||
| 14 | use an interface similar to that of PortMidi, but since PortMidi | ||
| 15 | does not allow concurrent threads to share access to a stream, the | ||
| 16 | application will call private methods that transfer MIDI messages | ||
| 17 | to and from the timer thread using lock-free queues. All PortMidi | ||
| 18 | API calls are made from the timer thread. | ||
| 19 | */ | ||
| 20 | |||
| 21 | /* DESIGN | ||
| 22 | |||
| 23 | All setup will be done by the main thread. Then, all direct access to | ||
| 24 | PortMidi will be handed off to the timer callback thread. | ||
| 25 | |||
| 26 | After this hand-off, the main thread will get/send messages via a queue. | ||
| 27 | |||
| 28 | The goal is to send incoming messages to the midi output while merging | ||
| 29 | any midi data generated by the application. Sysex is a problem here | ||
| 30 | because you cannot insert (merge) a midi message while a sysex is in | ||
| 31 | progress. There are at least three ways to implement midi thru with | ||
| 32 | sysex messages: | ||
| 33 | |||
| 34 | 1) Turn them off. If your application does not need them, turn them off | ||
| 35 | with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will | ||
| 36 | not receive sysex (or active sensing messages), so you will not have | ||
| 37 | to handle them. | ||
| 38 | |||
| 39 | 2) Make them atomic. As you receive sysex messages, copy the data into | ||
| 40 | a (big) buffer. Ideally, expand the buffer as needed -- sysex messages | ||
| 41 | do not have any maximum length. Even more ideally, use a list structure | ||
| 42 | and real-time memory allocation to avoid latency in the timer thread. | ||
| 43 | When a full sysex message is received, send it to the midi output all | ||
| 44 | at once. | ||
| 45 | |||
| 46 | 3) Process sysex incrementally. Send sysex data to midi output as it | ||
| 47 | arrives. Block any non-real-time messages from the application until | ||
| 48 | the sysex message completes. There is the risk that an incomplete | ||
| 49 | sysex message will block messages forever, so implement a 5-second | ||
| 50 | timeout: if no sysex data is seen for 5 seconds, release the block, | ||
| 51 | possibly losing the rest of the sysex message. | ||
| 52 | |||
| 53 | Application messages must be processed similarly: once started, a | ||
| 54 | sysex message will block MIDI THRU processing. We will assume that | ||
| 55 | the application will not abort a sysex message, so timeouts are not | ||
| 56 | necessary here. | ||
| 57 | |||
| 58 | This code implements (3). | ||
| 59 | |||
| 60 | Latency is also an issue. PortMidi requires timestamps to be in | ||
| 61 | non-decreasing order. Since we'll be operating with a low-latency | ||
| 62 | timer thread, we can just set the latency to zero meaning timestamps | ||
| 63 | are ignored by PortMidi. This will allow thru to go through with | ||
| 64 | minimal latency. The application, however, needs to use timestamps | ||
| 65 | because we assume it is high latency (the whole purpose of this | ||
| 66 | example is to illustrate how to get low-latency thru with a high-latency | ||
| 67 | application.) So the callback thread will implement midi timing by | ||
| 68 | observing timestamps. The current timestamp will be available in the | ||
| 69 | global variable current_timestamp. | ||
| 70 | |||
| 71 | */ | ||
| 72 | |||
| 73 | |||
| 74 | #include "stdio.h" | ||
| 75 | #include "stdlib.h" | ||
| 76 | #include "string.h" | ||
| 77 | #include "assert.h" | ||
| 78 | #include "portmidi.h" | ||
| 79 | #include "pmutil.h" | ||
| 80 | #include "porttime.h" | ||
| 81 | |||
| 82 | #define MIDI_SYSEX 0xf0 | ||
| 83 | #define MIDI_EOX 0xf7 | ||
| 84 | #define STRING_MAX 80 /* used for console input */ | ||
| 85 | |||
| 86 | /* active is set true when midi processing should start, must be | ||
| 87 | * volatile to force thread to check for updates by other thread */ | ||
| 88 | int active = FALSE; | ||
| 89 | /* process_midi_exit_flag is set when the timer thread shuts down; | ||
| 90 | * must be volatile so it is re-read in the while loop that waits on it */ | ||
| 91 | volatile int process_midi_exit_flag; | ||
| 92 | |||
| 93 | PmStream *midi_in; | ||
| 94 | PmStream *midi_out; | ||
| 95 | |||
| 96 | /* shared queues */ | ||
| 97 | #define IN_QUEUE_SIZE 1024 | ||
| 98 | #define OUT_QUEUE_SIZE 1024 | ||
| 99 | PmQueue *in_queue; | ||
| 100 | PmQueue *out_queue; | ||
| 101 | /* this is volatile because it is set in the process_midi callback and | ||
| 102 | * the main thread reads it to sense elapsed time. Without volatile, the | ||
| 103 | * optimizer can put it in a register and not see the updates. | ||
| 104 | */ | ||
| 105 | volatile PmTimestamp current_timestamp = 0; | ||
| 106 | int thru_sysex_in_progress = FALSE; | ||
| 107 | int app_sysex_in_progress = FALSE; | ||
| 108 | PmTimestamp last_timestamp = 0; | ||
| 109 | |||
| 110 | |||
| 111 | static void prompt_and_exit(void) | ||
| 112 | { | ||
| 113 | printf("type ENTER..."); | ||
| 114 | while (getchar() != '\n') ; | ||
| 115 | /* this will clean up open ports: */ | ||
| 116 | exit(-1); | ||
| 117 | } | ||
| 118 | |||
| 119 | |||
| 120 | static PmError checkerror(PmError err) | ||
| 121 | { | ||
| 122 | if (err == pmHostError) { | ||
| 123 | /* it seems pointless to allocate memory and copy the string, | ||
| 124 | * so I will do the work of Pm_GetHostErrorText directly | ||
| 125 | */ | ||
| 126 | char errmsg[80]; | ||
| 127 | Pm_GetHostErrorText(errmsg, 80); | ||
| 128 | printf("PortMidi found host error...\n %s\n", errmsg); | ||
| 129 | prompt_and_exit(); | ||
| 130 | } else if (err < 0) { | ||
| 131 | printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); | ||
| 132 | prompt_and_exit(); | ||
| 133 | } | ||
| 134 | return err; | ||
| 135 | } | ||
| 136 | |||
| 137 | |||
| 138 | /* time proc parameter for Pm_MidiOpen */ | ||
| 139 | PmTimestamp midithru_time_proc(void *info) | ||
| 140 | { | ||
| 141 | return current_timestamp; | ||
| 142 | } | ||
| 143 | |||
| 144 | |||
| 145 | /* timer interrupt for processing midi data. | ||
| 146 | Incoming data is delivered to main program via in_queue. | ||
| 147 | Outgoing data from main program is delivered via out_queue. | ||
| 148 | Incoming data from midi_in is copied with low latency to midi_out. | ||
| 149 | Sysex messages from either source block messages from the other. | ||
| 150 | */ | ||
| 151 | void process_midi(PtTimestamp timestamp, void *userData) | ||
| 152 | { | ||
| 153 | PmError result; | ||
| 154 | PmEvent buffer; /* just one message at a time */ | ||
| 155 | |||
| 156 | current_timestamp++; /* update every millisecond */ | ||
| 157 | |||
| 158 | /* do nothing until initialization completes */ | ||
| 159 | if (!active) { | ||
| 160 | /* this flag signals that no more midi processing will be done */ | ||
| 161 | process_midi_exit_flag = TRUE; | ||
| 162 | return; | ||
| 163 | } | ||
| 164 | |||
| 165 | /* see if there is any midi input to process */ | ||
| 166 | if (!app_sysex_in_progress) { | ||
| 167 | do { | ||
| 168 | result = Pm_Poll(midi_in); | ||
| 169 | if (result) { | ||
| 170 | int status; | ||
| 171 | PmError rslt = Pm_Read(midi_in, &buffer, 1); | ||
| 172 | if (rslt == pmBufferOverflow) | ||
| 173 | continue; | ||
| 174 | assert(rslt == 1); | ||
| 175 | |||
| 176 | /* record timestamp of most recent data */ | ||
| 177 | last_timestamp = current_timestamp; | ||
| 178 | |||
| 179 | /* the data might be the end of a sysex message that | ||
| 180 | has timed out, in which case we must ignore it. | ||
| 181 | It's a continuation of a sysex message if status | ||
| 182 | is actually a data byte (high-order bit is zero). */ | ||
| 183 | status = Pm_MessageStatus(buffer.message); | ||
| 184 | if (((status & 0x80) == 0) && !thru_sysex_in_progress) { | ||
| 185 | continue; /* ignore this data */ | ||
| 186 | } | ||
| 187 | |||
| 188 | /* implement midi thru */ | ||
| 189 | /* note that you could output to multiple ports or do other | ||
| 190 | processing here if you wanted | ||
| 191 | */ | ||
| 192 | /* printf("thru: %x\n", buffer.message); */ | ||
| 193 | Pm_Write(midi_out, &buffer, 1); | ||
| 194 | |||
| 195 | /* send the message to the application */ | ||
| 196 | /* you might want to filter clock or active sense messages here | ||
| 197 | to avoid sending a bunch of junk to the application even if | ||
| 198 | you want to send it to MIDI THRU | ||
| 199 | */ | ||
| 200 | Pm_Enqueue(in_queue, &buffer); | ||
| 201 | |||
| 202 | /* sysex processing */ | ||
| 203 | if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE; | ||
| 204 | else if ((status & 0xF8) != 0xF8) { | ||
| 205 | /* not MIDI_SYSEX and not real-time, so */ | ||
| 206 | thru_sysex_in_progress = FALSE; | ||
| 207 | } | ||
| 208 | if (thru_sysex_in_progress && /* look for EOX */ | ||
| 209 | (((buffer.message & 0xFF) == MIDI_EOX) || | ||
| 210 | (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || | ||
| 211 | (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || | ||
| 212 | (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { | ||
| 213 | thru_sysex_in_progress = FALSE; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | } while (result); | ||
| 217 | } | ||
| 218 | |||
| 219 | |||
| 220 | /* see if there is application midi data to process */ | ||
| 221 | while (!Pm_QueueEmpty(out_queue)) { | ||
| 222 | /* see if it is time to output the next message */ | ||
| 223 | PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue); | ||
| 224 | assert(next); /* must be non-null because queue is not empty */ | ||
| 225 | if (next->timestamp <= current_timestamp) { | ||
| 226 | /* time to send a message, first make sure it's not blocked */ | ||
| 227 | int status = Pm_MessageStatus(next->message); | ||
| 228 | if ((status & 0xF8) == 0xF8) { | ||
| 229 | ; /* real-time messages are not blocked */ | ||
| 230 | } else if (thru_sysex_in_progress) { | ||
| 231 | /* maybe sysex has timed out (output becomes unblocked) */ | ||
| 232 | if (last_timestamp + 5000 < current_timestamp) { | ||
| 233 | thru_sysex_in_progress = FALSE; | ||
| 234 | } else break; /* output is blocked, so exit loop */ | ||
| 235 | } | ||
| 236 | Pm_Dequeue(out_queue, &buffer); | ||
| 237 | Pm_Write(midi_out, &buffer, 1); | ||
| 238 | |||
| 239 | /* inspect message to update app_sysex_in_progress */ | ||
| 240 | if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE; | ||
| 241 | else if ((status & 0xF8) != 0xF8) { | ||
| 242 | /* not MIDI_SYSEX and not real-time, so */ | ||
| 243 | app_sysex_in_progress = FALSE; | ||
| 244 | } | ||
| 245 | if (app_sysex_in_progress && /* look for EOX */ | ||
| 246 | (((buffer.message & 0xFF) == MIDI_EOX) || | ||
| 247 | (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || | ||
| 248 | (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || | ||
| 249 | (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { | ||
| 250 | app_sysex_in_progress = FALSE; | ||
| 251 | } | ||
| 252 | } else break; /* wait until indicated timestamp */ | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | |||
| 257 | void exit_with_message(char *msg) | ||
| 258 | { | ||
| 259 | #define STRING_MAX 80 | ||
| 260 | printf("%s\nType ENTER...", msg); | ||
| 261 | while (getchar() != '\n') ; | ||
| 262 | exit(1); | ||
| 263 | } | ||
| 264 | |||
| 265 | |||
| 266 | void initialize(int input, int output, int virtual) | ||
| 267 | /* set up midi processing thread and open midi streams */ | ||
| 268 | { | ||
| 269 | /* note that it is safe to call PortMidi from the main thread for | ||
| 270 | initialization and opening devices. You should not make any | ||
| 271 | calls to PortMidi from this thread once the midi thread begins. | ||
| 272 | to make PortMidi calls. | ||
| 273 | */ | ||
| 274 | |||
| 275 | /* note that this routine provides minimal error checking. If | ||
| 276 | you use the PortMidi library compiled with PM_CHECK_ERRORS, | ||
| 277 | then error messages will be printed and the program will exit | ||
| 278 | if an error is encountered. Otherwise, you should add some | ||
| 279 | error checking to this code. | ||
| 280 | */ | ||
| 281 | |||
| 282 | const PmDeviceInfo *info; | ||
| 283 | |||
| 284 | /* make the message queues */ | ||
| 285 | in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent)); | ||
| 286 | assert(in_queue != NULL); | ||
| 287 | out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent)); | ||
| 288 | assert(out_queue != NULL); | ||
| 289 | |||
| 290 | /* always start the timer before you start midi */ | ||
| 291 | Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */ | ||
| 292 | /* the timer will call our function, process_midi() every millisecond */ | ||
| 293 | |||
| 294 | Pm_Initialize(); | ||
| 295 | |||
| 296 | if (output < 0) { | ||
| 297 | if (!virtual) { | ||
| 298 | output = Pm_GetDefaultOutputDeviceID(); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | if (output >= 0) { | ||
| 302 | info = Pm_GetDeviceInfo(output); | ||
| 303 | if (info == NULL) { | ||
| 304 | printf("Could not open default output device (%d).", output); | ||
| 305 | exit_with_message(""); | ||
| 306 | } | ||
| 307 | |||
| 308 | printf("Opening output device %s %s\n", info->interf, info->name); | ||
| 309 | |||
| 310 | /* use zero latency because we want output to be immediate */ | ||
| 311 | Pm_OpenOutput(&midi_out, | ||
| 312 | output, | ||
| 313 | NULL /* driver info */, | ||
| 314 | OUT_QUEUE_SIZE, | ||
| 315 | &midithru_time_proc, | ||
| 316 | NULL /* time info */, | ||
| 317 | 0 /* Latency */); | ||
| 318 | } else { /* send to virtual port */ | ||
| 319 | int id; | ||
| 320 | printf("Opening virtual output device \"midithru\"\n"); | ||
| 321 | id = Pm_CreateVirtualOutput("midithru", NULL, NULL); | ||
| 322 | if (id < 0) checkerror(id); /* error reporting */ | ||
| 323 | checkerror(Pm_OpenOutput(&midi_out, id, NULL, OUT_QUEUE_SIZE, | ||
| 324 | &midithru_time_proc, NULL, 0)); | ||
| 325 | } | ||
| 326 | if (input < 0) { | ||
| 327 | if (!virtual) { | ||
| 328 | input = Pm_GetDefaultInputDeviceID(); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | if (input >= 0) { | ||
| 332 | info = Pm_GetDeviceInfo(input); | ||
| 333 | if (info == NULL) { | ||
| 334 | printf("Could not open default input device (%d).", input); | ||
| 335 | exit_with_message(""); | ||
| 336 | } | ||
| 337 | |||
| 338 | printf("Opening input device %s %s\n", info->interf, info->name); | ||
| 339 | Pm_OpenInput(&midi_in, | ||
| 340 | input, | ||
| 341 | NULL /* driver info */, | ||
| 342 | 0 /* use default input size */, | ||
| 343 | &midithru_time_proc, | ||
| 344 | NULL /* time info */); | ||
| 345 | } else { /* receive from virtual port */ | ||
| 346 | int id; | ||
| 347 | printf("Opening virtual input device \"midithru\"\n"); | ||
| 348 | id = Pm_CreateVirtualInput("midithru", NULL, NULL); | ||
| 349 | if (id < 0) checkerror(id); /* error reporting */ | ||
| 350 | checkerror(Pm_OpenInput(&midi_in, id, NULL, 0, | ||
| 351 | &midithru_time_proc, NULL)); | ||
| 352 | } | ||
| 353 | /* Note: if you set a filter here, then this will filter what goes | ||
| 354 | to the MIDI THRU port. You may not want to do this. | ||
| 355 | */ | ||
| 356 | Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK); | ||
| 357 | |||
| 358 | active = TRUE; /* enable processing in the midi thread -- yes, this | ||
| 359 | is a shared variable without synchronization, but | ||
| 360 | this simple assignment is safe */ | ||
| 361 | |||
| 362 | } | ||
| 363 | |||
| 364 | |||
| 365 | void finalize() | ||
| 366 | { | ||
| 367 | /* the timer thread could be in the middle of accessing PortMidi stuff */ | ||
| 368 | /* to detect that it is done, we first clear process_midi_exit_flag and | ||
| 369 | then wait for the timer thread to set it | ||
| 370 | */ | ||
| 371 | process_midi_exit_flag = FALSE; | ||
| 372 | active = FALSE; | ||
| 373 | /* busy wait for flag from timer thread that it is done */ | ||
| 374 | while (!process_midi_exit_flag) ; | ||
| 375 | /* at this point, midi thread is inactive and we need to shut down | ||
| 376 | * the midi input and output | ||
| 377 | */ | ||
| 378 | Pt_Stop(); /* stop the timer */ | ||
| 379 | Pm_QueueDestroy(in_queue); | ||
| 380 | Pm_QueueDestroy(out_queue); | ||
| 381 | |||
| 382 | Pm_Close(midi_in); | ||
| 383 | Pm_Close(midi_out); | ||
| 384 | |||
| 385 | Pm_Terminate(); | ||
| 386 | } | ||
| 387 | |||
| 388 | |||
| 389 | int main(int argc, char *argv[]) | ||
| 390 | { | ||
| 391 | PmTimestamp last_time = 0; | ||
| 392 | PmEvent buffer; | ||
| 393 | int i; | ||
| 394 | int input = -1, output = -1; | ||
| 395 | int virtual = FALSE; | ||
| 396 | int delay_enable = TRUE; | ||
| 397 | |||
| 398 | printf("Usage: midithru [-i input] [-o output] [-v] [-n]\n" | ||
| 399 | "where input and output are portmidi device numbers\n" | ||
| 400 | "if -v and input and/or output are not specified,\n" | ||
| 401 | "then virtual ports are created and used instead.\n" | ||
| 402 | "-n turns off the default MIDI delay effect.\n"); | ||
| 403 | for (i = 1; i < argc; i++) { | ||
| 404 | if (strcmp(argv[i], "-i") == 0) { | ||
| 405 | i++; | ||
| 406 | input = atoi(argv[i]); | ||
| 407 | printf("Input device number: %d\n", input); | ||
| 408 | } else if (strcmp(argv[i], "-o") == 0) { | ||
| 409 | i++; | ||
| 410 | output = atoi(argv[i]); | ||
| 411 | printf("Output device number: %d\n", output); | ||
| 412 | } else if (strcmp(argv[i], "-v") == 0) { | ||
| 413 | virtual = TRUE; | ||
| 414 | } else if (strcmp(argv[i], "-n") == 0) { | ||
| 415 | delay_enable = FALSE; | ||
| 416 | printf("delay_effect is disabled\n"); | ||
| 417 | } else { | ||
| 418 | return -1; | ||
| 419 | } | ||
| 420 | } | ||
| 421 | printf("begin PortMidi midithru program...\n"); | ||
| 422 | |||
| 423 | initialize(input, output, virtual); /* set up and start midi processing */ | ||
| 424 | |||
| 425 | printf("This program will run for 60 seconds, " | ||
| 426 | "or until you play B below middle C,\n" | ||
| 427 | "All input is sent immediately, implementing software MIDI THRU.\n" | ||
| 428 | "Also, all input is echoed with a 2 second delay.\n"); | ||
| 429 | |||
| 430 | while (current_timestamp < 60000) { | ||
| 431 | /* just to make the point that this is not a low-latency process, | ||
| 432 | spin until half a second has elapsed */ | ||
| 433 | last_time = last_time + 500; | ||
| 434 | while (last_time > current_timestamp) ; | ||
| 435 | |||
| 436 | /* now read data and send it after changing timestamps */ | ||
| 437 | while (Pm_Dequeue(in_queue, &buffer) == 1) { | ||
| 438 | /* printf("timestamp %d\n", buffer.timestamp); */ | ||
| 439 | /* printf("message %x\n", buffer.message); */ | ||
| 440 | if (delay_enable) { | ||
| 441 | buffer.timestamp = buffer.timestamp + 2000; /* delay */ | ||
| 442 | Pm_Enqueue(out_queue, &buffer); | ||
| 443 | } | ||
| 444 | /* play B3 to break out of loop */ | ||
| 445 | if (Pm_MessageStatus(buffer.message) == 0x90 && | ||
| 446 | Pm_MessageData1(buffer.message) == 59) { | ||
| 447 | goto quit_now; | ||
| 448 | } | ||
| 449 | } | ||
| 450 | } | ||
| 451 | quit_now: | ||
| 452 | finalize(); | ||
| 453 | exit_with_message("finished PortMidi midithru program."); | ||
| 454 | return 0; /* never executed, but keeps the compiler happy */ | ||
| 455 | } | ||
