aboutsummaryrefslogtreecommitdiff
path: root/pd/portmidi/pm_test/midithru.c
diff options
context:
space:
mode:
Diffstat (limited to 'pd/portmidi/pm_test/midithru.c')
-rw-r--r--pd/portmidi/pm_test/midithru.c364
1 files changed, 0 insertions, 364 deletions
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 */
-}