mirror of
				https://github.com/cookiengineer/audacity
				synced 2025-11-03 23:53:55 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			367 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* 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;
 | 
						|
                PmError rslt = Pm_Read(midi_in, &buffer, 1);
 | 
						|
                if (rslt == pmBufferOverflow) 
 | 
						|
                    continue;
 | 
						|
                assert(rslt == 1);
 | 
						|
 | 
						|
                /* 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(next->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 */
 | 
						|
}
 |