diff options
Diffstat (limited to '3rdparty/portmidi/pm_test/midithru.c')
-rw-r--r-- | 3rdparty/portmidi/pm_test/midithru.c | 211 |
1 files changed, 150 insertions, 61 deletions
diff --git a/3rdparty/portmidi/pm_test/midithru.c b/3rdparty/portmidi/pm_test/midithru.c index 8ad723138e4..a9df5ccf5e6 100644 --- a/3rdparty/portmidi/pm_test/midithru.c +++ b/3rdparty/portmidi/pm_test/midithru.c @@ -1,18 +1,21 @@ /* midithru.c -- example program implementing background thru processing */ -/* suppose you want low-latency midi-thru processing, but your application - wants to take advantage of the input buffer and timestamped data so that - it does not have to operate with very low latency. - - This program illustrates how to use a timer callback from PortTime to - implement a low-latency process that handles midi thru, including correctly - merging midi data from the application with midi data from the input port. - - The main application, which runs in the main program thread, will use an - interface similar to that of PortMidi, but since PortMidi does not allow - concurrent threads to share access to a stream, the application will - call private methods that transfer MIDI messages to and from the timer - thread. All PortMidi API calls are made from the timer thread. +/* suppose you want low-latency midi-thru processing, but your + application wants to take advantage of the input buffer and + timestamped data so that it does not have to operate with very low + latency. + + This program illustrates how to use a timer callback from PortTime + to implement a low-latency process that handles midi thru, + including correctly merging midi data from the application with + midi data from the input port. + + The main application, which runs in the main program thread, will + use an interface similar to that of PortMidi, but since PortMidi + does not allow concurrent threads to share access to a stream, the + application will call private methods that transfer MIDI messages + to and from the timer thread using lock-free queues. All PortMidi + API calls are made from the timer thread. */ /* DESIGN @@ -78,11 +81,14 @@ global variable current_timestamp. #define MIDI_SYSEX 0xf0 #define MIDI_EOX 0xf7 +#define STRING_MAX 80 /* used for console input */ -/* active is set true when midi processing should start */ +/* active is set true when midi processing should start, must be + * volatile to force thread to check for updates by other thread */ int active = FALSE; -/* process_midi_exit_flag is set when the timer thread shuts down */ -int process_midi_exit_flag; +/* process_midi_exit_flag is set when the timer thread shuts down; + * must be volatile so it is re-read in the while loop that waits on it */ +volatile int process_midi_exit_flag; PmStream *midi_in; PmStream *midi_out; @@ -92,12 +98,43 @@ PmStream *midi_out; #define OUT_QUEUE_SIZE 1024 PmQueue *in_queue; PmQueue *out_queue; -PmTimestamp current_timestamp = 0; +/* this is volatile because it is set in the process_midi callback and + * the main thread reads it to sense elapsed time. Without volatile, the + * optimizer can put it in a register and not see the updates. + */ +volatile PmTimestamp current_timestamp = 0; int thru_sysex_in_progress = FALSE; int app_sysex_in_progress = FALSE; PmTimestamp last_timestamp = 0; +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError checkerror(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("PortMidi found host error...\n %s\n", errmsg); + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} + + /* time proc parameter for Pm_MidiOpen */ PmTimestamp midithru_time_proc(void *info) { @@ -117,8 +154,6 @@ void process_midi(PtTimestamp timestamp, void *userData) PmEvent buffer; /* just one message at a time */ current_timestamp++; /* update every millisecond */ - /* if (current_timestamp % 1000 == 0) - printf("time %d\n", current_timestamp); */ /* do nothing until initialization completes */ if (!active) { @@ -222,14 +257,13 @@ void process_midi(PtTimestamp timestamp, void *userData) void exit_with_message(char *msg) { #define STRING_MAX 80 - char line[STRING_MAX]; printf("%s\nType ENTER...", msg); - fgets(line, STRING_MAX, stdin); + while (getchar() != '\n') ; exit(1); } -void initialize() +void initialize(int input, int output, int virtual) /* set up midi processing thread and open midi streams */ { /* note that it is safe to call PortMidi from the main thread for @@ -246,7 +280,6 @@ void initialize() */ const PmDeviceInfo *info; - int id; /* make the message queues */ in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent)); @@ -260,36 +293,63 @@ void initialize() Pm_Initialize(); - id = Pm_GetDefaultOutputDeviceID(); - info = Pm_GetDeviceInfo(id); - if (info == NULL) { - printf("Could not open default output device (%d).", id); - exit_with_message(""); + if (output < 0) { + if (!virtual) { + output = Pm_GetDefaultOutputDeviceID(); + } } - printf("Opening output device %s %s\n", info->interf, info->name); - - /* use zero latency because we want output to be immediate */ - Pm_OpenOutput(&midi_out, - id, - NULL /* driver info */, - OUT_QUEUE_SIZE, - &midithru_time_proc, - NULL /* time info */, - 0 /* Latency */); - - id = Pm_GetDefaultInputDeviceID(); - info = Pm_GetDeviceInfo(id); - if (info == NULL) { - printf("Could not open default input device (%d).", id); - exit_with_message(""); + if (output >= 0) { + info = Pm_GetDeviceInfo(output); + if (info == NULL) { + printf("Could not open default output device (%d).", output); + exit_with_message(""); + } + + printf("Opening output device %s %s\n", info->interf, info->name); + + /* use zero latency because we want output to be immediate */ + Pm_OpenOutput(&midi_out, + output, + NULL /* driver info */, + OUT_QUEUE_SIZE, + &midithru_time_proc, + NULL /* time info */, + 0 /* Latency */); + } else { /* send to virtual port */ + int id; + printf("Opening virtual output device \"midithru\"\n"); + id = Pm_CreateVirtualOutput("midithru", NULL, NULL); + if (id < 0) checkerror(id); /* error reporting */ + checkerror(Pm_OpenOutput(&midi_out, id, NULL, OUT_QUEUE_SIZE, + &midithru_time_proc, NULL, 0)); + } + if (input < 0) { + if (!virtual) { + input = Pm_GetDefaultInputDeviceID(); + } + } + if (input >= 0) { + info = Pm_GetDeviceInfo(input); + if (info == NULL) { + printf("Could not open default input device (%d).", input); + exit_with_message(""); + } + + printf("Opening input device %s %s\n", info->interf, info->name); + Pm_OpenInput(&midi_in, + input, + NULL /* driver info */, + 0 /* use default input size */, + &midithru_time_proc, + NULL /* time info */); + } else { /* receive from virtual port */ + int id; + printf("Opening virtual input device \"midithru\"\n"); + id = Pm_CreateVirtualInput("midithru", NULL, NULL); + if (id < 0) checkerror(id); /* error reporting */ + checkerror(Pm_OpenInput(&midi_in, id, NULL, 0, + &midithru_time_proc, NULL)); } - printf("Opening input device %s %s\n", info->interf, info->name); - Pm_OpenInput(&midi_in, - id, - NULL /* driver info */, - 0 /* use default input size */, - &midithru_time_proc, - NULL /* time info */); /* Note: if you set a filter here, then this will filter what goes to the MIDI THRU port. You may not want to do this. */ @@ -330,15 +390,42 @@ int main(int argc, char *argv[]) { PmTimestamp last_time = 0; PmEvent buffer; - - /* determine what type of test to run */ + int i; + int input = -1, output = -1; + int virtual = FALSE; + int delay_enable = TRUE; + + printf("Usage: midithru [-i input] [-o output] [-v] [-n]\n" + "where input and output are portmidi device numbers\n" + "if -v and input and/or output are not specified,\n" + "then virtual ports are created and used instead.\n" + "-n turns off the default MIDI delay effect.\n"); + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-i") == 0) { + i++; + input = atoi(argv[i]); + printf("Input device number: %d\n", input); + } else if (strcmp(argv[i], "-o") == 0) { + i++; + output = atoi(argv[i]); + printf("Output device number: %d\n", output); + } else if (strcmp(argv[i], "-v") == 0) { + virtual = TRUE; + } else if (strcmp(argv[i], "-n") == 0) { + delay_enable = FALSE; + printf("delay_effect is disabled\n"); + } else { + return -1; + } + } printf("begin PortMidi midithru program...\n"); - initialize(); /* set up and start midi processing */ + initialize(input, output, virtual); /* set up and start midi processing */ - printf("%s\n%s\n", - "This program will run for 60 seconds, or until you play middle C,", - "echoing all input with a 2 second delay."); + printf("This program will run for 60 seconds, " + "or until you play B below middle C,\n" + "All input is sent immediately, implementing software MIDI THRU.\n" + "Also, all input is echoed with a 2 second delay.\n"); while (current_timestamp < 60000) { /* just to make the point that this is not a low-latency process, @@ -350,11 +437,13 @@ int main(int argc, char *argv[]) while (Pm_Dequeue(in_queue, &buffer) == 1) { /* printf("timestamp %d\n", buffer.timestamp); */ /* printf("message %x\n", buffer.message); */ - buffer.timestamp = buffer.timestamp + 2000; /* delay */ - Pm_Enqueue(out_queue, &buffer); - /* play middle C to break out of loop */ + if (delay_enable) { + buffer.timestamp = buffer.timestamp + 2000; /* delay */ + Pm_Enqueue(out_queue, &buffer); + } + /* play B3 to break out of loop */ if (Pm_MessageStatus(buffer.message) == 0x90 && - Pm_MessageData1(buffer.message) == 60) { + Pm_MessageData1(buffer.message) == 59) { goto quit_now; } } |