From 44e68e4348f7ca86f4209f3f86ac7b6cb49acd52 Mon Sep 17 00:00:00 2001 From: Miller Puckette Date: Fri, 28 Dec 2007 03:28:31 +0000 Subject: 0.41-10 test 10 - many patches, plus work on callback scheduling svn path=/trunk/; revision=9107 --- pd/portmidi/pm_test/midithru.c | 364 ----------------------------------------- 1 file changed, 364 deletions(-) delete mode 100644 pd/portmidi/pm_test/midithru.c (limited to 'pd/portmidi/pm_test/midithru.c') diff --git a/pd/portmidi/pm_test/midithru.c b/pd/portmidi/pm_test/midithru.c deleted file mode 100644 index 270246fb..00000000 --- a/pd/portmidi/pm_test/midithru.c +++ /dev/null @@ -1,364 +0,0 @@ -/* 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. - */ - -/* DESIGN - -All setup will be done by the main thread. Then, all direct access to -PortMidi will be handed off to the timer callback thread. - -After this hand-off, the main thread will get/send messages via a queue. - -The goal is to send incoming messages to the midi output while merging -any midi data generated by the application. Sysex is a problem here -because you cannot insert (merge) a midi message while a sysex is in -progress. There are at least three ways to implement midi thru with -sysex messages: - -1) Turn them off. If your application does not need them, turn them off - with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will - not receive sysex (or active sensing messages), so you will not have - to handle them. - -2) Make them atomic. As you receive sysex messages, copy the data into - a (big) buffer. Ideally, expand the buffer as needed -- sysex messages - do not have any maximum length. Even more ideally, use a list structure - and real-time memory allocation to avoid latency in the timer thread. - When a full sysex message is received, send it to the midi output all - at once. - -3) Process sysex incrementally. Send sysex data to midi output as it - arrives. Block any non-real-time messages from the application until - the sysex message completes. There is the risk that an incomplete - sysex message will block messages forever, so implement a 5-second - timeout: if no sysex data is seen for 5 seconds, release the block, - possibly losing the rest of the sysex message. - - Application messages must be processed similarly: once started, a - sysex message will block MIDI THRU processing. We will assume that - the application will not abort a sysex message, so timeouts are not - necessary here. - -This code implements (3). - -Latency is also an issue. PortMidi requires timestamps to be in -non-decreasing order. Since we'll be operating with a low-latency -timer thread, we can just set the latency to zero meaning timestamps -are ignored by PortMidi. This will allow thru to go through with -minimal latency. The application, however, needs to use timestamps -because we assume it is high latency (the whole purpose of this -example is to illustrate how to get low-latency thru with a high-latency -application.) So the callback thread will implement midi timing by -observing timestamps. The current timestamp will be available in the -global variable current_timestamp. - -*/ - - -#include "stdio.h" -#include "stdlib.h" -#include "string.h" -#include "assert.h" -#include "portmidi.h" -#include "pmutil.h" -#include "porttime.h" - -#define MIDI_SYSEX 0xf0 -#define MIDI_EOX 0xf7 - -/* active is set true when midi processing should start */ -int active = FALSE; -/* process_midi_exit_flag is set when the timer thread shuts down */ -int process_midi_exit_flag; - -PmStream *midi_in; -PmStream *midi_out; - -/* shared queues */ -#define IN_QUEUE_SIZE 1024 -#define OUT_QUEUE_SIZE 1024 -PmQueue *in_queue; -PmQueue *out_queue; -PmTimestamp current_timestamp = 0; -int thru_sysex_in_progress = FALSE; -int app_sysex_in_progress = FALSE; -PmTimestamp last_timestamp = 0; - - -/* time proc parameter for Pm_MidiOpen */ -long midithru_time_proc(void *info) -{ - return current_timestamp; -} - - -/* timer interrupt for processing midi data. - Incoming data is delivered to main program via in_queue. - Outgoing data from main program is delivered via out_queue. - Incoming data from midi_in is copied with low latency to midi_out. - Sysex messages from either source block messages from the other. - */ -void process_midi(PtTimestamp timestamp, void *userData) -{ - PmError result; - 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) { - /* this flag signals that no more midi processing will be done */ - process_midi_exit_flag = TRUE; - return; - } - - /* see if there is any midi input to process */ - if (!app_sysex_in_progress) { - do { - result = Pm_Poll(midi_in); - if (result) { - long status; - if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow) - continue; - - /* record timestamp of most recent data */ - last_timestamp = current_timestamp; - - /* the data might be the end of a sysex message that - has timed out, in which case we must ignore it. - It's a continuation of a sysex message if status - is actually a data byte (high-order bit is zero). */ - status = Pm_MessageStatus(buffer.message); - if (((status & 0x80) == 0) && !thru_sysex_in_progress) { - continue; /* ignore this data */ - } - - /* implement midi thru */ - /* note that you could output to multiple ports or do other - processing here if you wanted - */ - /* printf("thru: %x\n", buffer.message); */ - Pm_Write(midi_out, &buffer, 1); - - /* send the message to the application */ - /* you might want to filter clock or active sense messages here - to avoid sending a bunch of junk to the application even if - you want to send it to MIDI THRU - */ - Pm_Enqueue(in_queue, &buffer); - - /* sysex processing */ - if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE; - else if ((status & 0xF8) != 0xF8) { - /* not MIDI_SYSEX and not real-time, so */ - thru_sysex_in_progress = FALSE; - } - if (thru_sysex_in_progress && /* look for EOX */ - (((buffer.message & 0xFF) == MIDI_EOX) || - (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || - (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || - (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { - thru_sysex_in_progress = FALSE; - } - } - } while (result); - } - - - /* see if there is application midi data to process */ - while (!Pm_QueueEmpty(out_queue)) { - /* see if it is time to output the next message */ - PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue); - assert(next); /* must be non-null because queue is not empty */ - if (next->timestamp <= current_timestamp) { - /* time to send a message, first make sure it's not blocked */ - long status = Pm_MessageStatus(buffer.message); - if ((status & 0xF8) == 0xF8) { - ; /* real-time messages are not blocked */ - } else if (thru_sysex_in_progress) { - /* maybe sysex has timed out (output becomes unblocked) */ - if (last_timestamp + 5000 < current_timestamp) { - thru_sysex_in_progress = FALSE; - } else break; /* output is blocked, so exit loop */ - } - Pm_Dequeue(out_queue, &buffer); - Pm_Write(midi_out, &buffer, 1); - - /* inspect message to update app_sysex_in_progress */ - if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE; - else if ((status & 0xF8) != 0xF8) { - /* not MIDI_SYSEX and not real-time, so */ - app_sysex_in_progress = FALSE; - } - if (app_sysex_in_progress && /* look for EOX */ - (((buffer.message & 0xFF) == MIDI_EOX) || - (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || - (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || - (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { - app_sysex_in_progress = FALSE; - } - } else break; /* wait until indicated timestamp */ - } -} - - -void exit_with_message(char *msg) -{ -#define STRING_MAX 80 - char line[STRING_MAX]; - printf("%s\nType ENTER...", msg); - fgets(line, STRING_MAX, stdin); - exit(1); -} - - -void initialize() -/* set up midi processing thread and open midi streams */ -{ - /* note that it is safe to call PortMidi from the main thread for - initialization and opening devices. You should not make any - calls to PortMidi from this thread once the midi thread begins. - to make PortMidi calls. - */ - - /* note that this routine provides minimal error checking. If - you use the PortMidi library compiled with PM_CHECK_ERRORS, - then error messages will be printed and the program will exit - if an error is encountered. Otherwise, you should add some - error checking to this code. - */ - - const PmDeviceInfo *info; - int id; - - /* make the message queues */ - in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent)); - assert(in_queue != NULL); - out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent)); - assert(out_queue != NULL); - - /* always start the timer before you start midi */ - Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */ - /* the timer will call our function, process_midi() every millisecond */ - - Pm_Initialize(); - - id = Pm_GetDefaultOutputDeviceID(); - info = Pm_GetDeviceInfo(id); - if (info == NULL) { - printf("Could not open default output device (%d).", id); - 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, - 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(""); - } - 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. - */ - Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK); - - active = TRUE; /* enable processing in the midi thread -- yes, this - is a shared variable without synchronization, but - this simple assignment is safe */ - -} - - -void finalize() -{ - /* the timer thread could be in the middle of accessing PortMidi stuff */ - /* to detect that it is done, we first clear process_midi_exit_flag and - then wait for the timer thread to set it - */ - process_midi_exit_flag = FALSE; - active = FALSE; - /* busy wait for flag from timer thread that it is done */ - while (!process_midi_exit_flag) ; - /* at this point, midi thread is inactive and we need to shut down - * the midi input and output - */ - Pt_Stop(); /* stop the timer */ - Pm_QueueDestroy(in_queue); - Pm_QueueDestroy(out_queue); - - Pm_Close(midi_in); - Pm_Close(midi_out); - - Pm_Terminate(); -} - - -int main(int argc, char *argv[]) -{ - PmTimestamp last_time = 0; - PmEvent buffer; - - /* determine what type of test to run */ - printf("begin PortMidi midithru program...\n"); - - initialize(); /* 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."); - - while (current_timestamp < 60000) { - /* just to make the point that this is not a low-latency process, - spin until half a second has elapsed */ - last_time = last_time + 500; - while (last_time > current_timestamp) ; - - /* now read data and send it after changing timestamps */ - 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 (Pm_MessageStatus(buffer.message) == 0x90 && - Pm_MessageData1(buffer.message) == 60) { - goto quit_now; - } - } - } -quit_now: - finalize(); - exit_with_message("finished PortMidi midithru program."); - return 0; /* never executed, but keeps the compiler happy */ -} -- cgit v1.2.1