From 90d5b8b4a064420d74678654e94ea4755b377f21 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 Dec 2005 00:57:02 +0000 Subject: checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip svn path=/trunk/; revision=4216 --- pd/portmidi/pm_common/pminternal.h | 173 +++++++ pd/portmidi/pm_common/pmutil.c | 132 +++++ pd/portmidi/pm_common/pmutil.h | 56 +++ pd/portmidi/pm_common/portmidi.c | 980 +++++++++++++++++++++++++++++++++++++ pd/portmidi/pm_common/portmidi.h | 692 ++++++++++++++++++++++++++ 5 files changed, 2033 insertions(+) create mode 100644 pd/portmidi/pm_common/pminternal.h create mode 100644 pd/portmidi/pm_common/pmutil.c create mode 100644 pd/portmidi/pm_common/pmutil.h create mode 100644 pd/portmidi/pm_common/portmidi.c create mode 100644 pd/portmidi/pm_common/portmidi.h (limited to 'pd/portmidi/pm_common') diff --git a/pd/portmidi/pm_common/pminternal.h b/pd/portmidi/pm_common/pminternal.h new file mode 100644 index 00000000..20677308 --- /dev/null +++ b/pd/portmidi/pm_common/pminternal.h @@ -0,0 +1,173 @@ +/* pminternal.h -- header for interface implementations */ + +/* this file is included by files that implement library internals */ +/* Here is a guide to implementers: + provide an initialization function similar to pm_winmm_init() + add your initialization function to pm_init() + Note that your init function should never require not-standard + libraries or fail in any way. If the interface is not available, + simply do not call pm_add_device. This means that non-standard + libraries should try to do dynamic linking at runtime using a DLL + and return without error if the DLL cannot be found or if there + is any other failure. + implement functions as indicated in pm_fns_type to open, read, write, + close, etc. + call pm_add_device() for each input and output device, passing it a + pm_fns_type structure. + assumptions about pm_fns_type functions are given below. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* these are defined in system-specific file */ +void *pm_alloc(size_t s); +void pm_free(void *ptr); + +/* if an error occurs while opening or closing a midi stream, set these: */ +extern int pm_hosterror; +extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; + +struct pm_internal_struct; + +/* these do not use PmInternal because it is not defined yet... */ +typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, + PmEvent *buffer); +typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi, + PmTimestamp timestamp); +typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi, + PmTimestamp timestamp); +typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, + unsigned char byte, PmTimestamp timestamp); +typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi, + PmEvent *buffer); +typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi); +typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi); +/* pm_open_fn should clean up all memory and close the device if any part + of the open fails */ +typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi, + void *driverInfo); +typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi); +/* pm_close_fn should clean up all memory and close the device if any + part of the close fails. */ +typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi); +typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi); +typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg, + unsigned int len); +typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi); + +typedef struct { + pm_write_short_fn write_short; /* output short MIDI msg */ + pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */ + pm_end_sysex_fn end_sysex; /* marks end of sysex message */ + pm_write_byte_fn write_byte; /* accumulate one more sysex byte */ + pm_write_realtime_fn write_realtime; /* send real-time message within sysex */ + pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ + pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */ + pm_open_fn open; /* open MIDI device */ + pm_abort_fn abort; /* abort */ + pm_close_fn close; /* close device */ + pm_poll_fn poll; /* read pending midi events into portmidi buffer */ + pm_has_host_error_fn has_host_error; /* true when device has had host + error message */ + pm_host_error_fn host_error; /* provide text readable host error message + for device (clears and resets) */ +} pm_fns_node, *pm_fns_type; + + +/* when open fails, the dictionary gets this set of functions: */ +extern pm_fns_node pm_none_dictionary; + +typedef struct { + PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic + device closing (see PmDeviceInfo struct) */ + void *descriptor; /* ID number passed to win32 multimedia API open */ + void *internalDescriptor; /* points to PmInternal device, allows automatic + device closing */ + pm_fns_type dictionary; +} descriptor_node, *descriptor_type; + +extern int pm_descriptor_max; +extern descriptor_type descriptors; +extern int pm_descriptor_index; + +typedef unsigned long (*time_get_proc_type)(void *time_info); + +typedef struct pm_internal_struct { + int device_id; /* which device is open (index to descriptors) */ + short write_flag; /* MIDI_IN, or MIDI_OUT */ + + PmTimeProcPtr time_proc; /* where to get the time */ + void *time_info; /* pass this to get_time() */ + + long buffer_len; /* how big is the buffer */ + PmEvent *buffer; /* storage for: + - midi input + - midi output w/latency != 0 */ + long head; + long tail; + + long latency; /* time delay in ms between timestamps and actual output */ + /* set to zero to get immediate, simple blocking output */ + /* if latency is zero, timestamps will be ignored; */ + /* if midi input device, this field ignored */ + + int overflow; /* set to non-zero if input is dropped */ + int flush; /* flag to drop incoming sysex data because of overflow */ + int sysex_in_progress; /* use for overflow management */ + PmMessage sysex_message; /* buffer for 4 bytes of sysex data */ + int sysex_message_count; /* how many bytes in sysex_message so far */ + + long filters; /* flags that filter incoming message classes */ + int channel_mask; /* filter incoming messages based on channel */ + PmTimestamp last_msg_time; /* timestamp of last message */ + PmTimestamp sync_time; /* time of last synchronization */ + PmTimestamp now; /* set by PmWrite to current time */ + int first_message; /* initially true, used to run first synchronization */ + pm_fns_type dictionary; /* implementation functions */ + void *descriptor; /* system-dependent state */ + +} PmInternal; + +typedef struct { + long head; + long tail; + long len; + long msg_size; + long overflow; + char *buffer; +} PmQueueRep; + +/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */ +void pm_init(void); +void pm_term(void); + +/* defined by portMidi, used by pmwinmm */ +PmError none_write_short(PmInternal *midi, PmEvent *buffer); +PmError none_sysex(PmInternal *midi, PmTimestamp timestamp); +PmError none_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp); +PmTimestamp none_synchronize(PmInternal *midi); + +PmError pm_fail_fn(PmInternal *midi); +PmError pm_success_fn(PmInternal *midi); +PmError pm_add_device(char *interf, char *name, int input, void *descriptor, + pm_fns_type dictionary); +void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp); +void pm_begin_sysex(PmInternal *midi); +void pm_end_sysex(PmInternal *midi); +void pm_read_short(PmInternal *midi, PmEvent *event); + +#define none_write_flush pm_fail_fn +#define none_poll pm_fail_fn +#define success_poll pm_success_fn + +#define MIDI_REALTIME_MASK 0xf8 +#define is_real_time(msg) \ + ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK) + +#ifdef __cplusplus +} +#endif + diff --git a/pd/portmidi/pm_common/pmutil.c b/pd/portmidi/pm_common/pmutil.c new file mode 100644 index 00000000..1178b80b --- /dev/null +++ b/pd/portmidi/pm_common/pmutil.c @@ -0,0 +1,132 @@ +/* pmutil.c -- some helpful utilities for building midi + applications that use PortMidi + */ +#include "stdlib.h" +#include "memory.h" +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + + +PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) +{ + PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); + + /* arg checking */ + if (!queue) + return NULL; + + queue->len = num_msgs * bytes_per_msg; + queue->buffer = pm_alloc(queue->len); + if (!queue->buffer) { + pm_free(queue); + return NULL; + } + queue->head = 0; + queue->tail = 0; + queue->msg_size = bytes_per_msg; + queue->overflow = FALSE; + return queue; +} + + +PmError Pm_QueueDestroy(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if (!queue || !queue->buffer) + return pmBadPtr; + + pm_free(queue->buffer); + pm_free(queue); + return pmNoError; +} + + +PmError Pm_Dequeue(PmQueue *q, void *msg) +{ + long head; + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if(!queue) + return pmBadPtr; + + if (queue->overflow) { + queue->overflow = FALSE; + return pmBufferOverflow; + } + + head = queue->head; /* make sure this is written after access */ + if (head == queue->tail) return 0; + memcpy(msg, queue->buffer + head, queue->msg_size); + head += queue->msg_size; + if (head == queue->len) head = 0; + queue->head = head; + return 1; /* success */ +} + + +/* source should not enqueue data if overflow is set */ +/**/ +PmError Pm_Enqueue(PmQueue *q, void *msg) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + + /* arg checking */ + if (!queue) + return pmBadPtr; + + tail = queue->tail; + memcpy(queue->buffer + tail, msg, queue->msg_size); + tail += queue->msg_size; + if (tail == queue->len) tail = 0; + if (tail == queue->head) { + queue->overflow = TRUE; + /* do not update tail, so message is lost */ + return pmBufferOverflow; + } + queue->tail = tail; + return pmNoError; +} + +int Pm_QueueEmpty(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + if (!queue) return TRUE; + return (queue->head == queue->tail); +} + +int Pm_QueueFull(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + + /* arg checking */ + if(!queue) + return pmBadPtr; + + tail = queue->tail; + tail += queue->msg_size; + if (tail == queue->len) { + tail = 0; + } + return (tail == queue->head); +} + +void *Pm_QueuePeek(PmQueue *q) +{ + long head; + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if(!queue) + return NULL; + + head = queue->head; /* make sure this is written after access */ + if (head == queue->tail) return NULL; + return queue->buffer + head; +} + diff --git a/pd/portmidi/pm_common/pmutil.h b/pd/portmidi/pm_common/pmutil.h new file mode 100644 index 00000000..a7fb3ebf --- /dev/null +++ b/pd/portmidi/pm_common/pmutil.h @@ -0,0 +1,56 @@ +/* pmutil.h -- some helpful utilities for building midi + applications that use PortMidi + */ + +typedef void PmQueue; + +/* + A single-reader, single-writer queue is created by + Pm_QueueCreate(), which takes the number of messages and + the message size as parameters. The queue only accepts + fixed sized messages. Returns NULL if memory cannot be allocated. + + Pm_QueueDestroy() destroys the queue and frees its storage. + */ + +PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg); +PmError Pm_QueueDestroy(PmQueue *queue); + +/* + Pm_Dequeue() removes one item from the queue, copying it into msg. + Returns 1 if successful, and 0 if the queue is empty. + Returns pmBufferOverflow, clears the overflow flag, and does not + return a data item if the overflow flag is set. (This protocol + ensures that the reader will be notified when data is lost due + to overflow.) + */ +PmError Pm_Dequeue(PmQueue *queue, void *msg); + + +/* + Pm_Enqueue() inserts one item into the queue, copying it from msg. + Returns pmNoError if successful and pmBufferOverflow if the queue was + already full. If pmBufferOverflow is returned, the overflow flag is set. + */ +PmError Pm_Enqueue(PmQueue *queue, void *msg); + + +/* + Pm_QueueFull() returns non-zero if the queue is full + Pm_QueueEmpty() returns non-zero if the queue is empty + + Either condition may change immediately because a parallel + enqueue or dequeue operation could be in progress. + */ +int Pm_QueueFull(PmQueue *queue); +int Pm_QueueEmpty(PmQueue *queue); + + +/* + Pm_QueuePeek() returns a pointer to the item at the head of the queue, + or NULL if the queue is empty. The item is not removed from the queue. + If queue is in an overflow state, a valid pointer is returned and the + queue remains in the overflow state. + */ +void *Pm_QueuePeek(PmQueue *queue); + diff --git a/pd/portmidi/pm_common/portmidi.c b/pd/portmidi/pm_common/portmidi.c new file mode 100644 index 00000000..e1b962d7 --- /dev/null +++ b/pd/portmidi/pm_common/portmidi.c @@ -0,0 +1,980 @@ +#include "stdlib.h" +#include "string.h" +#include "portmidi.h" +#include "porttime.h" +#include "pminternal.h" +#include + +#define MIDI_CLOCK 0xf8 +#define MIDI_ACTIVE 0xfe +#define MIDI_STATUS_MASK 0x80 +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#define MIDI_START 0xFA +#define MIDI_STOP 0xFC +#define MIDI_CONTINUE 0xFB +#define MIDI_F9 0xF9 +#define MIDI_FD 0xFD +#define MIDI_RESET 0xFF +#define MIDI_NOTE_ON 0x90 +#define MIDI_NOTE_OFF 0x80 +#define MIDI_CHANNEL_AT 0xD0 +#define MIDI_POLY_AT 0xA0 +#define MIDI_PROGRAM 0xC0 +#define MIDI_CONTROL 0xB0 +#define MIDI_PITCHBEND 0xE0 +#define MIDI_MTC 0xF1 +#define MIDI_SONGPOS 0xF2 +#define MIDI_SONGSEL 0xF3 +#define MIDI_TUNE 0xF6 + +#define is_empty(midi) ((midi)->tail == (midi)->head) + +static int pm_initialized = FALSE; +int pm_hosterror = FALSE; +char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN]; + +#ifdef PM_CHECK_ERRORS + +#include + +#define STRING_MAX 80 + +static void prompt_and_exit(void) +{ + char line[STRING_MAX]; + printf("type ENTER..."); + fgets(line, STRING_MAX, stdin); + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError pm_errmsg(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 + */ + printf("PortMidi found host error...\n %s\n", pm_hosterror_text); + pm_hosterror = FALSE; + pm_hosterror_text[0] = 0; /* clear the message */ + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} +#else +#define pm_errmsg(err) err +#endif + +/* +==================================================================== +system implementation of portmidi interface +==================================================================== +*/ + +int pm_descriptor_max = 0; +int pm_descriptor_index = 0; +descriptor_type descriptors = NULL; + +/* pm_add_device -- describe interface/device pair to library + * + * This is called at intialization time, once for each + * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1) + * The strings are retained but NOT COPIED, so do not destroy them! + * + * returns pmInvalidDeviceId if device memory is exceeded + * otherwise returns pmNoError + */ +PmError pm_add_device(char *interf, char *name, int input, + void *descriptor, pm_fns_type dictionary) { + if (pm_descriptor_index >= pm_descriptor_max) { + // expand descriptors + descriptor_type new_descriptors = + pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32)); + if (!new_descriptors) return pmInsufficientMemory; + if (descriptors) { + memcpy(new_descriptors, descriptors, + sizeof(descriptor_node) * pm_descriptor_max); + free(descriptors); + } + pm_descriptor_max += 32; + descriptors = new_descriptors; + } + descriptors[pm_descriptor_index].pub.interf = interf; + descriptors[pm_descriptor_index].pub.name = name; + descriptors[pm_descriptor_index].pub.input = input; + descriptors[pm_descriptor_index].pub.output = !input; + + /* default state: nothing to close (for automatic device closing) */ + descriptors[pm_descriptor_index].pub.opened = FALSE; + + /* ID number passed to win32 multimedia API open */ + descriptors[pm_descriptor_index].descriptor = descriptor; + + /* points to PmInternal, allows automatic device closing */ + descriptors[pm_descriptor_index].internalDescriptor = NULL; + + descriptors[pm_descriptor_index].dictionary = dictionary; + + pm_descriptor_index++; + + return pmNoError; +} + + +/* +==================================================================== +portmidi implementation +==================================================================== +*/ + +int Pm_CountDevices( void ) +{ + PmError err = Pm_Initialize(); + if (err) + return pm_errmsg(err); + return pm_descriptor_index; +} + + +const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) +{ + PmError err = Pm_Initialize(); + if (err) + return NULL; + if (id >= 0 && id < pm_descriptor_index) { + return &descriptors[id].pub; + } + return NULL; +} + +/* pm_success_fn -- "noop" function pointer */ +PmError pm_success_fn(PmInternal *midi) { + return pmNoError; +} + +/* none_write -- returns an error if called */ +PmError none_write_short(PmInternal *midi, PmEvent *buffer) +{ + return pmBadPtr; +} + +/* none_sysex -- placeholder for begin_sysex and end_sysex */ +PmError none_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + return pmBadPtr; +} + +PmError none_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) +{ + return pmBadPtr; +} + +/* pm_fail_fn -- generic function, returns error if called */ +PmError pm_fail_fn(PmInternal *midi) +{ + return pmBadPtr; +} + +static PmError none_open(PmInternal *midi, void *driverInfo) +{ + return pmBadPtr; +} +static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) { + strcpy(msg,""); +} +static unsigned int none_has_host_error(PmInternal * midi) { + return FALSE; +} +PmTimestamp none_synchronize(PmInternal *midi) { + return 0; +} + +#define none_abort pm_fail_fn +#define none_close pm_fail_fn + +pm_fns_node pm_none_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + none_synchronize, + none_open, + none_abort, + none_close, + none_poll, + none_has_host_error, + none_get_host_error +}; + + +const char *Pm_GetErrorText( PmError errnum ) { + const char *msg; + + switch(errnum) + { + case pmNoError: + msg = ""; + break; + case pmHostError: + msg = "PortMidi: `Host error'"; + break; + case pmInvalidDeviceId: + msg = "PortMidi: `Invalid device ID'"; + break; + case pmInsufficientMemory: + msg = "PortMidi: `Insufficient memory'"; + break; + case pmBufferTooSmall: + msg = "PortMidi: `Buffer too small'"; + break; + case pmBadPtr: + msg = "PortMidi: `Bad pointer'"; + break; + case pmInternalError: + msg = "PortMidi: `Internal PortMidi Error'"; + break; + case pmBufferOverflow: + msg = "PortMidi: `Buffer overflow'"; + break; + case pmBadData: + msg = "PortMidi: `Invalid MIDI message Data'"; + default: + msg = "PortMidi: `Illegal error number'"; + break; + } + return msg; +} + + +/* This can be called whenever you get a pmHostError return value. + * The error will always be in the global pm_hosterror_text. + */ +void Pm_GetHostErrorText(char * msg, unsigned int len) { + assert(msg); + assert(len > 0); + if (pm_hosterror) { /* we have the string already from open or close */ + strncpy(msg, (char *) pm_hosterror_text, len); + pm_hosterror = FALSE; + pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it + might help with debugging */ + msg[len - 1] = 0; /* make sure string is terminated */ + } else { + msg[0] = 0; /* no string to return */ + } +} + + +int Pm_HasHostError(PortMidiStream * stream) { + if (stream) { + PmInternal * midi = (PmInternal *) stream; + pm_hosterror = (*midi->dictionary->has_host_error)(midi); + if (pm_hosterror) { + midi->dictionary->host_error(midi, pm_hosterror_text, + PM_HOST_ERROR_MSG_LEN); + /* now error message is global */ + return TRUE; + } + } + return FALSE; +} + + +PmError Pm_Initialize( void ) { + pm_hosterror_text[0] = 0; /* the null string */ + if (!pm_initialized) { + pm_init(); + pm_initialized = TRUE; + } + return pmNoError; +} + + +PmError Pm_Terminate( void ) { + if (pm_initialized) { + pm_term(); + pm_initialized = FALSE; + } + return pmNoError; +} + + +/* Pm_Read -- read up to length longs from source into buffer */ +/* + returns number of longs actually read, or error code + + When the reader wants data: + if overflow_flag: + do not get anything + empty the buffer (read_ptr = write_ptr) + clear overflow_flag + return pmBufferOverflow + get data + return number of messages +*/ +PmError Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { + PmInternal *midi = (PmInternal *) stream; + int n = 0; + long head; + PmError err = pmNoError; + + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(Pm_HasHostError(midi)) + err = pmHostError; + else if(!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.input) + err = pmBadPtr; + + /* First poll for data in the buffer... + * This either simply checks for data, or attempts first to fill the buffer + * with data from the MIDI hardware; this depends on the implementation. + * We could call Pm_Poll here, but that would redo a lot of redundant + * parameter checking, so I copied some code from Pm_Poll to here: */ + else err = (*(midi->dictionary->poll))(midi); + + if (err != pmNoError) { + return pm_errmsg(err); + } + + head = midi->head; + while (head != midi->tail && n < length) { + PmEvent event = midi->buffer[head++]; + *buffer++ = event; + if (head == midi->buffer_len) head = 0; + n++; + } + midi->head = head; + if (midi->overflow) { + midi->head = midi->tail; + midi->overflow = FALSE; + return pm_errmsg(pmBufferOverflow); + } + return n; +} + +PmError Pm_Poll( PortMidiStream *stream ) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(Pm_HasHostError(midi)) + err = pmHostError; + else if(!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.input) + err = pmBadPtr; + else + err = (*(midi->dictionary->poll))(midi); + + if (err != pmNoError) + return pm_errmsg(err); + else + return midi->head != midi->tail; +} + +/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and + Pm_WriteSysEx all operate a state machine that "outputs" calls to + write_short, begin_sysex, write_byte, end_sysex, and write_realtime */ + +PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + int i; + int bits; + + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(Pm_HasHostError(midi)) + err = pmHostError; + else if(!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if(!descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else + err = pmNoError; + + if (err != pmNoError) goto pm_write_error; + + if (midi->latency == 0) { + midi->now = 0; + } else { + midi->now = (*(midi->time_proc))(midi->time_info); + if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) { + /* time to resync */ + midi->now = (*midi->dictionary->synchronize)(midi); + midi->first_message = FALSE; + } + } + + for (i = 0; i < length; i++) { + unsigned long msg = buffer[i].message; + bits = 0; + /* is this a sysex message? */ + if (Pm_MessageStatus(msg) == MIDI_SYSEX) { + if (midi->sysex_in_progress) { + /* error: previous sysex was not terminated by EOX */ + midi->sysex_in_progress = FALSE; + err = pmBadData; + goto pm_write_error; + } + midi->sysex_in_progress = TRUE; + if ((err = (*midi->dictionary->begin_sysex)(midi, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + bits = 8; + /* fall through to continue sysex processing */ + } else if ((msg & MIDI_STATUS_MASK) && + (Pm_MessageStatus(msg) != MIDI_EOX)) { + /* a non-sysex message */ + if (midi->sysex_in_progress) { + /* this should be a non-realtime message */ + if (is_real_time(msg)) { + if ((err = (*midi->dictionary->write_realtime)(midi, + &(buffer[i]))) != pmNoError) + goto pm_write_error; + } else { + midi->sysex_in_progress = FALSE; + err = pmBadData; + /* ignore any error from this, because we already have one */ + /* pass 0 as timestamp -- it's ignored */ + (*midi->dictionary->end_sysex)(midi, 0); + goto pm_write_error; + } + } else { /* regular short midi message */ + if ((err = (*midi->dictionary->write_short)(midi, + &(buffer[i]))) != pmNoError) + goto pm_write_error; + continue; + } + } + if (midi->sysex_in_progress) { /* send sysex bytes until EOX */ + while (bits < 32) { + unsigned char midi_byte = (unsigned char) (msg >> bits); + if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + if (midi_byte == MIDI_EOX) { + midi->sysex_in_progress = FALSE; + if ((err = (*midi->dictionary->end_sysex)(midi, + buffer[i].timestamp)) != pmNoError) + goto pm_write_error; + break; /* from while loop */ + } + bits += 8; + } + } else { + /* not in sysex mode, but message did not start with status */ + err = pmBadData; + goto pm_write_error; + } + } + /* after all messages are processed, send the data */ + err = (*midi->dictionary->write_flush)(midi); +pm_write_error: + return pm_errmsg(err); +} + + +PmError Pm_WriteShort( PortMidiStream *stream, long when, long msg) +{ + PmEvent event; + + event.timestamp = when; + event.message = msg; + return Pm_Write(stream, &event, 1); +} + + +PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, + unsigned char *msg) +{ + /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */ + /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */ +#define BUFLEN (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage)) + PmEvent buffer[BUFLEN]; + /* the next byte in the buffer is represented by an index, bufx, and + a shift in bits */ + int shift = 0; + int bufx = 0; + buffer[0].message = 0; + buffer[0].timestamp = when; + + while (1) { + /* insert next byte into buffer */ + buffer[bufx].message |= ((*msg) << shift); + shift += 8; + if (shift == 32) { + shift = 0; + bufx++; + if (bufx == BUFLEN) { + PmError err = Pm_Write(stream, buffer, BUFLEN); + if (err) return err; + /* prepare to fill another buffer */ + bufx = 0; + } + buffer[bufx].message = 0; + buffer[bufx].timestamp = when; + } + /* keep inserting bytes until you find MIDI_EOX */ + if (*msg++ == MIDI_EOX) break; + } + + /* we're finished sending full buffers, but there may + * be a partial one left. + */ + if (shift != 0) bufx++; /* add partial message to buffer len */ + if (bufx) { /* bufx is number of PmEvents to send from buffer */ + return Pm_Write(stream, buffer, bufx); + } + return pmNoError; +} + + + +PmError Pm_OpenInput(PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputDriverInfo, + long bufferSize, + PmTimeProcPtr time_proc, + void *time_info) +{ + PmInternal *midi; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; + + /* arg checking */ + if (inputDevice < 0 || inputDevice >= pm_descriptor_index) + err = pmInvalidDeviceId; + else if (!descriptors[inputDevice].pub.input) + err = pmBadPtr; + else if(descriptors[inputDevice].pub.opened) + err = pmBadPtr; + + if (err != pmNoError) + goto error_return; + + /* create portMidi internal data */ + midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); + *stream = midi; + if (!midi) { + err = pmInsufficientMemory; + goto error_return; + } + midi->device_id = inputDevice; + midi->write_flag = FALSE; + midi->time_proc = time_proc; + midi->time_info = time_info; + /* windows adds timestamps in the driver and these are more accurate than + using a time_proc, so do not automatically provide a time proc. Non-win + implementations may want to provide a default time_proc in their + system-specific midi_out_open() method. + */ + if (bufferSize <= 0) bufferSize = 256; /* default buffer size */ + else bufferSize++; /* buffer holds N-1 msgs, so increase request by 1 */ + midi->buffer_len = bufferSize; /* portMidi input storage */ + midi->buffer = (PmEvent *) pm_alloc(sizeof(PmEvent) * midi->buffer_len); + if (!midi->buffer) { + /* free portMidi data */ + *stream = NULL; + pm_free(midi); + err = pmInsufficientMemory; + goto error_return; + } + midi->head = 0; + midi->tail = 0; + midi->latency = 0; /* not used */ + midi->overflow = FALSE; + midi->flush = FALSE; + midi->sysex_in_progress = FALSE; + midi->sysex_message = 0; + midi->sysex_message_count = 0; + midi->filters = PM_FILT_ACTIVE; + midi->channel_mask = 0xFFFF; + midi->sync_time = 0; + midi->first_message = TRUE; + midi->dictionary = descriptors[inputDevice].dictionary; + descriptors[inputDevice].internalDescriptor = midi; + /* open system dependent input device */ + err = (*midi->dictionary->open)(midi, inputDriverInfo); + if (err) { + *stream = NULL; + descriptors[inputDevice].internalDescriptor = NULL; + /* free portMidi data */ + pm_free(midi->buffer); + pm_free(midi); + } else { + /* portMidi input open successful */ + descriptors[inputDevice].pub.opened = TRUE; + } +error_return: + return pm_errmsg(err); +} + + +PmError Pm_OpenOutput(PortMidiStream** stream, + PmDeviceID outputDevice, + void *outputDriverInfo, + long bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + long latency) +{ + PmInternal *midi; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; + + /* arg checking */ + if (outputDevice < 0 || outputDevice >= pm_descriptor_index) + err = pmInvalidDeviceId; + else if (!descriptors[outputDevice].pub.output) + err = pmBadPtr; + else if (descriptors[outputDevice].pub.opened) + err = pmBadPtr; + if (err != pmNoError) + goto error_return; + + /* create portMidi internal data */ + midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); + *stream = midi; + if (!midi) { + err = pmInsufficientMemory; + goto error_return; + } + midi->device_id = outputDevice; + midi->write_flag = TRUE; + midi->time_proc = time_proc; + /* if latency > 0, we need a time reference. If none is provided, + use PortTime library */ + if (time_proc == NULL && latency != 0) { + if (!Pt_Started()) + Pt_Start(1, 0, 0); + /* time_get does not take a parameter, so coerce */ + midi->time_proc = (PmTimeProcPtr) Pt_Time; + } + midi->time_info = time_info; + /* when stream used, this buffer allocated and used by + winmm_out_open; deleted by winmm_out_close */ + midi->buffer_len = bufferSize; + midi->buffer = NULL; + midi->head = 0; /* unused by output */ + midi->tail = 0; /* unused by output */ + /* if latency zero, output immediate (timestamps ignored) */ + /* if latency < 0, use 0 but don't return an error */ + if (latency < 0) latency = 0; + midi->latency = latency; + midi->overflow = FALSE; /* not used */ + midi->flush = FALSE; /* not used */ + midi->sysex_in_progress = FALSE; + midi->sysex_message = 0; /* unused by output */ + midi->sysex_message_count = 0; /* unused by output */ + midi->filters = 0; /* not used for output */ + midi->channel_mask = 0xFFFF; /* not used for output */ + midi->sync_time = 0; + midi->first_message = TRUE; + midi->dictionary = descriptors[outputDevice].dictionary; + descriptors[outputDevice].internalDescriptor = midi; + /* open system dependent output device */ + err = (*midi->dictionary->open)(midi, outputDriverInfo); + if (err) { + *stream = NULL; + descriptors[outputDevice].internalDescriptor = NULL; + /* free portMidi data */ + pm_free(midi); + } else { + /* portMidi input open successful */ + descriptors[outputDevice].pub.opened = TRUE; + } +error_return: + return pm_errmsg(err); +} + +PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + if (midi == NULL) + err = pmBadPtr; + else + midi->channel_mask = mask; + + return pm_errmsg(err); +} + +PmError Pm_SetFilter(PortMidiStream *stream, long filters) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + /* arg checking */ + if (midi == NULL) + err = pmBadPtr; + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + midi->filters = filters; + return pm_errmsg(err); +} + + +PmError Pm_Close( PortMidiStream *stream ) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + /* arg checking */ + if (midi == NULL) /* midi must point to something */ + err = pmBadPtr; + /* if it is an open device, the device_id will be valid */ + else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index) + err = pmBadPtr; + /* and the device should be in the opened state */ + else if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + + if (err != pmNoError) + goto error_return; + + /* close the device */ + err = (*midi->dictionary->close)(midi); + /* even if an error occurred, continue with cleanup */ + descriptors[midi->device_id].internalDescriptor = NULL; + descriptors[midi->device_id].pub.opened = FALSE; + pm_free(midi->buffer); + pm_free(midi); +error_return: + return pm_errmsg(err); +} + + +PmError Pm_Abort( PortMidiStream* stream ) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + /* arg checking */ + if (midi == NULL) + err = pmBadPtr; + if (!descriptors[midi->device_id].pub.output) + err = pmBadPtr; + if (!descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + err = (*midi->dictionary->abort)(midi); + return pm_errmsg(err); +} + +/* in win32 multimedia API (via callbacks) some of these functions used; assume never fail */ +long pm_next_time(PmInternal *midi) { + + /* arg checking */ + assert(midi != NULL); + assert(!Pm_HasHostError(midi)); + + return midi->buffer[midi->head].timestamp; +} +/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */ +static int pm_channel_filtered(int status, int mask) +{ + if ((status & 0xF0) == 0xF0) /* 0xF? messages don't have a channel */ + return 0; + return !(Pm_Channel(status & 0x0F) & mask); + /* it'd be easier to return 0 for filtered, 1 for allowed, + but it would different from the other filtering functions + */ + +} +/* The following two functions will checks to see if a MIDI message matches + the filtering criteria. Since the sysex routines only want to filter realtime messages, + we need to have separate routines. + */ + +/* pm_realtime_filtered returns non-zero if the filter will kill the current message. + Note that only realtime messages are checked here. + */ +static int pm_realtime_filtered(int status, long filters) +{ + return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE)) + || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK)) + || ((status == MIDI_START) && (filters & PM_FILT_PLAY)) + || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY)) + || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY)) + || ((status == MIDI_F9) && (filters & PM_FILT_F9)) + || ((status == MIDI_FD) && (filters & PM_FILT_FD)) + || ((status == MIDI_RESET) && (filters & PM_FILT_RESET)) + || ((status == MIDI_MTC) && (filters & PM_FILT_MTC)) + || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION)) + || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT)) + || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE)); +} +/* pm_status_filtered returns non-zero if a filter will kill the current message, based on status. + Note that sysex and real time are not checked. It is up to the subsystem (winmm, core midi, alsa) + to filter sysex, as it is handled more easily and efficiently at that level. + Realtime message are filtered in pm_realtime_filtered. + */ + +static int pm_status_filtered(int status, long filters) +{ + status &= 0xF0; /* remove channel information */ + return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE)) + || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE)) + || ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH)) + || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH)) + || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM)) + || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL)) + || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND)); + +} + +/* pm_read_short and pm_read_byte + are the interface between system-dependent MIDI input handlers + and the system-independent PortMIDI code. + The input handler MUST obey these rules: + 1) all short input messages must be sent to pm_read_short, which + enqueues them to a FIFO for the application. + 2) eash sysex byte should be reported by calling pm_read_byte + (which sets midi->sysex_in_progress). After the eox byte, + pm_read_byte will clear sysex_in_progress and midi->flush + (Note that the overflow flag is managed by pm_read_short + and Pm_Read, so the supplier should not read or write it.) + */ + +/* pm_read_short is the place where all input messages arrive from + system-dependent code such as pmwinmm.c. Here, the messages + are entered into the PortMidi input buffer. + */ + + /* Algorithnm: + if overflow or flush, return + ATOMIC: + enqueue data + if buffer overflow, set overflow + if buffer overflow and sysex_in_progress, set flush + */ +void pm_read_short(PmInternal *midi, PmEvent *event) +{ + long tail; + int status; + /* arg checking */ + assert(midi != NULL); + assert(!Pm_HasHostError(midi)); + /* midi filtering is applied here */ + status = Pm_MessageStatus(event->message); + if (!pm_status_filtered(status, midi->filters) + && !pm_realtime_filtered(status, midi->filters) + && !pm_channel_filtered(status, midi->channel_mask)) { + /* if sysex is in progress and we get a status byte, it had + better be a realtime message or the starting SYSEX byte; + otherwise, we exit the sysex_in_progress state + */ + if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK) && + !is_real_time(status) && status != MIDI_SYSEX ) { + midi->sysex_in_progress = FALSE; + midi->flush = FALSE; + } + + /* don't try to do anything more in an overflow state */ + if (midi->overflow || midi->flush) return; + + /* insert the message */ + tail = midi->tail; + midi->buffer[tail++] = *event; + if (tail == midi->buffer_len) tail = 0; + if (tail == midi->head || midi->overflow) { + midi->overflow = TRUE; + if (midi->sysex_in_progress) midi->flush = TRUE; + /* drop the rest of the message, this must be cleared + by caller when EOX is received */ + return; + } + midi->tail = tail; /* complete the write */ + } +} + + +void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + PmEvent event; + + /* there may be nothing in the buffer */ + if (midi->sysex_message_count == 0) return; /* nothing to flush */ + + event.message = midi->sysex_message; + event.timestamp = timestamp; + pm_read_short(midi, &event); + midi->sysex_message_count = 0; + midi->sysex_message = 0; +} + + +void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp) +{ + assert(midi); + assert(!Pm_HasHostError(midi)); + /* here is the logic for controlling sysex_in_progress */ + if (midi->sysex_in_progress) { + if (byte == MIDI_EOX) midi->sysex_in_progress = FALSE; + else if (byte == MIDI_SYSEX) { + /* problem: need to terminate the current sysex and start + a new one + */ + pm_flush_sysex(midi, timestamp); + } + } else if (byte == MIDI_SYSEX) { + midi->sysex_in_progress = TRUE; + } else { + /* error: we're getting data bytes or EOX but we're no sysex is + in progress. Drop the data. (Would it be better to report an + error? Is this a host error or a pmBadData error? Is this + ever possible? + */ +#ifdef DEBUG + printf("PortMidi debug msg: unexpected sysex data or EOX\n"); +#endif + return; + } + + if (pm_realtime_filtered(byte, midi->filters)) + return; + /* this awkward expression places the bytes in increasingly higher- + order bytes of the long message */ + midi->sysex_message |= (byte << (8 * midi->sysex_message_count++)); + if (midi->sysex_message_count == 4 || !midi->sysex_in_progress) { + pm_flush_sysex(midi, timestamp); + if (!midi->sysex_in_progress) midi->flush = FALSE; + } +} + + +int pm_queue_full(PmInternal *midi) +{ + long tail; + + /* arg checking */ + assert(midi != NULL); + assert(!Pm_HasHostError(midi)); + + tail = midi->tail + 1; + if (tail == midi->buffer_len) tail = 0; + return tail == midi->head; +} + diff --git a/pd/portmidi/pm_common/portmidi.h b/pd/portmidi/pm_common/portmidi.h new file mode 100644 index 00000000..8c9c04c2 --- /dev/null +++ b/pd/portmidi/pm_common/portmidi.h @@ -0,0 +1,692 @@ +#ifndef PORT_MIDI_H +#define PORT_MIDI_H +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * PortMidi Portable Real-Time MIDI Library + * PortMidi API Header File + * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/ + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * Copyright (c) 2001 Roger B. Dannenberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +/* CHANGELOG FOR PORTMIDI + * + * 15Nov04 Ben Allison + * - sysex output now uses one buffer/message and reallocates buffer + * - if needed + * - filters expanded for many message types and channels + * - detailed changes are as follows: + * ------------- in pmwinmm.c -------------- + * - new #define symbol: OUTPUT_BYTES_PER_BUFFER + * - change SYSEX_BYTES_PER_BUFFER to 1024 + * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length + * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) + * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro + * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH + * - similar to above, but counts appropriately for sysex messages + * - added the following members to midiwinmm_struct for sysex data: + * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data ** + * - int num_sysex_buffers; ** how many sysex buffers ** + * - int next_sysex_buffer; ** index of next sysexbuffer to send ** + * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer ** + * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer + * - into equivalent sysex_buffer form + * - changed winmm_in_open to initialize new midiwinmm_struct members and + * - to use the new allocate_sysex_buffer() function instead of + * - allocate_buffer() + * - changed winmm_out_open to initialize new members, create sysex buffer + * - signal, and allocate 2 sysex buffers + * - changed winmm_out_delete to free sysex buffers and shut down the sysex + * - buffer signal + * - create new function resize_sysex_buffer which resizes m->hdr to the + * - passed size, and corrects the midiwinmm_struct accordingly. + * - changed winmm_write_byte to use new resize_sysex_buffer function, + * - if resize fails, write current buffer to output and continue + * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal + * - depending on which buffer was finished + * ------------- in portmidi.h -------------- + * - added pmBufferMaxSize to PmError to indicate that the buffer would be + * - too large for the underlying API + * - added additional filters + * - added prototype, documentation, and helper macro for Pm_SetChannelMask + * ------------- in portmidi.c -------------- + * - added pm_status_filtered() and pm_realtime_filtered() functions to + * separate filtering logic from buffer logic in pm_read_short + * - added Pm_SetChannelMask function + * - added pm_channel_filtered() function + * ------------- in pminternal.h -------------- + * - added member to PortMidiStream for channel mask + * + * 25May04 RBD + * - removed support for MIDI THRU + * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl + * - extensive work on Mac OS X port, especially sysex and error handling + * + * 18May04 RBD + * - removed side-effects from assert() calls. Now you can disable assert(). + * - no longer check pm_hosterror everywhere, fixing a bug where an open + * failure could cause a write not to work on a previously opened port + * until you call Pm_GetHostErrorText(). + * 16May04 RBD and Chris Roberts + * - Some documentation wordsmithing in portmidi.h + * - Dynamically allocate port descriptor structures + * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer. + * + * 09Oct03 RBD + * - Changed Thru handling. Now the client does all the work and the client + * must poll or read to keep thru messages flowing. + * + * 31May03 RBD + * - Fixed various bugs. + * - Added linux ALSA support with help from Clemens Ladisch + * - Added Mac OS X support, implemented by Jon Parise, updated and + * integrated by Andrew Zeldis and Zico Kolter + * - Added latency program to build histogram of system latency using PortTime. + * + * 30Jun02 RBD Extensive rewrite of sysex handling. It works now. + * Extensive reworking of error reporting and error text -- no + * longer use dictionary call to delete data; instead, Pm_Open + * and Pm_Close clean up before returning an error code, and + * error text is saved in a system-independent location. + * Wrote sysex.c to test sysex message handling. + * + * 15Jun02 BCT changes: + * - Added pmHostError text handling. + * - For robustness, check PortMidi stream args not NULL. + * - Re-C-ANSI-fied code (changed many C++ comments to C style) + * - Reorganized code in pmwinmm according to input/output functionality (made + * cleanup handling easier to reason about) + * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error) + * - Cleaned up memory handling (now system specific data deleted via dictionary + * call in PortMidi, allows client to query host errors). + * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as + * logic implies it should. Specifically: verified callback routines not reentrant and + * all verified status for all unchecked Win32 MMedia API calls perform successfully + * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API + * bug (i.e. if devices not explicitly closed, must reboot to debug application further). + * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or + * explicitly Pm_Close open devices when using WinMM version of PortMidi. + * + * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling + * + * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent + * opening an input as output and vice versa. + * Added comments and documentation. + * Implemented Pm_Terminate(). + * + * + * IMPORTANT INFORMATION ABOUT A WIN32 BUG: + * + * Windows apparently has a serious midi bug -- if you do not close ports, Windows + * may crash. PortMidi tries to protect against this by using a DLL to clean up. + * + * If client exits for example with: + * i) assert + * ii) Ctrl^c, + * then DLL clean-up routine called. However, when client does something + * really bad (e.g. assigns value to NULL pointer) then DLL CLEANUP ROUTINE + * NEVER RUNS! In this state, if you wait around long enough, you will + * probably get the blue screen of death. Can also go into Pview and there will + * exist zombie process that you can't kill. + * + * NOTES ON HOST ERROR REPORTING: + * + * PortMidi errors (of type PmError) are generic, system-independent errors. + * When an error does not map to one of the more specific PmErrors, the + * catch-all code pmHostError is returned. This means that PortMidi has + * retained a more specific system-dependent error code. The caller can + * get more information by calling Pm_HasHostError() to test if there is + * a pending host error, and Pm_GetHostErrorText() to get a text string + * describing the error. Host errors are reported on a per-device basis + * because only after you open a device does PortMidi have a place to + * record the host error code. I.e. only + * those routines that receive a (PortMidiStream *) argument check and + * report errors. One exception to this is that Pm_OpenInput() and + * Pm_OpenOutput() can report errors even though when an error occurs, + * there is no PortMidiStream* to hold the error. Fortunately, both + * of these functions return any error immediately, so we do not really + * need per-device error memory. Instead, any host error code is stored + * in a global, pmHostError is returned, and the user can call + * Pm_GetHostErrorText() to get the error message (and the invalid stream + * parameter will be ignored.) The functions + * pm_init and pm_term do not fail or raise + * errors. The job of pm_init is to locate all available devices so that + * the caller can get information via PmDeviceInfo(). If an error occurs, + * the device is simply not listed as available. + * + * Host errors come in two flavors: + * a) host error + * b) host error during callback + * These can occur w/midi input or output devices. (b) can only happen + * asynchronously (during callback routines), whereas (a) only occurs while + * synchronously running PortMidi and any resulting system dependent calls + * + * Host-error reporting relies on following assumptions: + * 1) PortMidi routines won't allow system dependent routines to be + * called when args are bogus. + * Thus, in pmwinmm.c it is safe to assume: + * - stream ptr valid + * - currently not operating in "has host error" state + * 2) Host-error reporting relies on a staged delivery of error messages. + * When a host error occurs, the error code is saved with the stream. + * The error is reported as a return code from the next operation on + * the stream. This could be immediately if the error is synchronous, + * or delayed if the error is an asynchronous callback problem. In + * any case, when pmHostError is returned, the error is copied to + * a global, pm_hosterror and the error code stored with the stream + * is cleared. If the user chooses to inquire about the error using + * Pm_GetHostErrorText(), the error will be reported as text. If the + * user ignores the error and makes another call on the stream, the + * call will proceed because the error code associated with the stream + * has been cleared. + * + */ + +#ifndef FALSE + #define FALSE 0 +#endif +#ifndef TRUE + #define TRUE 1 +#endif + +/* default size of buffers for sysex transmission: */ +#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024 + + +typedef enum { + pmNoError = 0, + pmHostError = -10000, + pmInvalidDeviceId, /* out of range or output device when input is requested or vice versa */ + pmInsufficientMemory, + pmBufferTooSmall, + pmBufferOverflow, + pmBadPtr, + pmBadData, /* illegal midi data, e.g. missing EOX */ + pmInternalError, + pmBufferMaxSize, /* buffer is already as large as it can be */ +} PmError; + +/* + Pm_Initialize() is the library initialisation function - call this before + using the library. +*/ + +PmError Pm_Initialize( void ); + +/* + Pm_Terminate() is the library termination function - call this after + using the library. +*/ + +PmError Pm_Terminate( void ); + +/* A single PortMidiStream is a descriptor for an open MIDI device. +*/ +typedef void PortMidiStream; +#define PmStream PortMidiStream + +/* + Test whether stream has a pending host error. Normally, the client finds + out about errors through returned error codes, but some errors can occur + asynchronously where the client does not + explicitly call a function, and therefore cannot receive an error code. + The client can test for a pending error using Pm_HasHostError(). If true, + the error can be accessed and cleared by calling Pm_GetErrorText(). The + client does not need to call Pm_HasHostError(). Any pending error will be + reported the next time the client performs an explicit function call on + the stream, e.g. an input or output operation. +*/ +int Pm_HasHostError( PortMidiStream * stream ); + + +/* Translate portmidi error number into human readable message. + These strings are constants (set at compile time) so client has + no need to allocate storage +*/ +const char *Pm_GetErrorText( PmError errnum ); + +/* Translate portmidi host error into human readable message. + These strings are computed at run time, so client has to allocate storage. + After this routine executes, the host error is cleared. +*/ +void Pm_GetHostErrorText(char * msg, unsigned int len); + +#define HDRLENGTH 50 +#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less + than this number of characters */ + +/* + Device enumeration mechanism. + + Device ids range from 0 to Pm_CountDevices()-1. + +*/ +typedef int PmDeviceID; +#define pmNoDevice -1 +typedef struct { + int structVersion; + const char *interf; /* underlying MIDI API, e.g. MMSystem or DirectX */ + const char *name; /* device name, e.g. USB MidiSport 1x1 */ + int input; /* true iff input is available */ + int output; /* true iff output is available */ + int opened; /* used by generic PortMidi code to do error checking on arguments */ + +} PmDeviceInfo; + + +int Pm_CountDevices( void ); +/* + Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() + + Return the default device ID or pmNoDevice if there are no devices. + The result can be passed to Pm_OpenMidi(). + + On the PC, the user can specify a default device by + setting an environment variable. For example, to use device #1. + + set PM_RECOMMENDED_OUTPUT_DEVICE=1 + + The user should first determine the available device ID by using + the supplied application "testin" or "testout". + + In general, the registry is a better place for this kind of info, + and with USB devices that can come and go, using integers is not + very reliable for device identification. Under Windows, if + PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is + *NOT* found in the environment, then the default device is obtained + by looking for a string in the registry under: + HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device + and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device + for a string. The number of the first device with a substring that + matches the string exactly is returned. For example, if the string + in the registry is "USB", and device 1 is named + "In USB MidiSport 1x1", then that will be the default + input because it contains the string "USB". + + In addition to the name, PmDeviceInfo has the member "interf", which + is the interface name. (The "interface" is the underlying software + system or API used by PortMidi to access devices. Examples are + MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.) + At present, the only Win32 interface is "MMSystem", the only Linux + interface is "ALSA", and the only Max OS X interface is "CoreMIDI". + To specify both the interface and the device name in the registry, + separate the two with a comma and a space, e.g.: + MMSystem, In USB MidiSport 1x1 + In this case, the string before the comma must be a substring of + the "interf" string, and the string after the space must be a + substring of the "name" name string in order to match the device. + + Note: in the current release, the default is simply the first device + (the input or output device with the lowest PmDeviceID). +*/ +PmDeviceID Pm_GetDefaultInputDeviceID( void ); +PmDeviceID Pm_GetDefaultOutputDeviceID( void ); + +/* + PmTimestamp is used to represent a millisecond clock with arbitrary + start time. The type is used for all MIDI timestampes and clocks. +*/ +typedef long PmTimestamp; +typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); + +/* TRUE if t1 before t2 */ +#define PmBefore(t1,t2) ((t1-t2) < 0) + +/* + Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure + referring to the device specified by id. + If id is out of range the function returns NULL. + + The returned structure is owned by the PortMidi implementation and must + not be manipulated or freed. The pointer is guaranteed to be valid + between calls to Pm_Initialize() and Pm_Terminate(). +*/ +const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); + +/* + Pm_OpenInput() and Pm_OpenOutput() open devices. + + stream is the address of a PortMidiStream pointer which will receive + a pointer to the newly opened stream. + + inputDevice is the id of the device used for input (see PmDeviceID above). + + inputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or handle processing. + inputDriverInfo is never required for correct operation. If not used + inputDriverInfo should be NULL. + + outputDevice is the id of the device used for output (see PmDeviceID above.) + + outputDriverInfo is a pointer to an optional driver specific data structure + containing additional information for device setup or handle processing. + outputDriverInfo is never required for correct operation. If not used + outputDriverInfo should be NULL. + + For input, the buffersize specifies the number of input events to be + buffered waiting to be read using Pm_Read(). For output, buffersize + specifies the number of output events to be buffered waiting for output. + (In some cases -- see below -- PortMidi does not buffer output at all + and merely passes data to a lower-level API, in which case buffersize + is ignored.) + + latency is the delay in milliseconds applied to timestamps to determine + when the output should actually occur. (If latency is < 0, 0 is assumed.) + If latency is zero, timestamps are ignored and all output is delivered + immediately. If latency is greater than zero, output is delayed until + the message timestamp plus the latency. (NOTE: time is measured relative + to the time source indicated by time_proc. Timestamps are absolute, not + relative delays or offsets.) In some cases, PortMidi can obtain + better timing than your application by passing timestamps along to the + device driver or hardware. Latency may also help you to synchronize midi + data to audio data by matching midi latency to the audio buffer latency. + + time_proc is a pointer to a procedure that returns time in milliseconds. It + may be NULL, in which case a default millisecond timebase (PortTime) is + used. If the application wants to use PortTime, it should start the timer + (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the + application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, + it may get a ptAlreadyStarted error from Pt_Start, and the application's + preferred time resolution and callback function will be ignored. + time_proc result values are appended to incoming MIDI data, and time_proc + times are used to schedule outgoing MIDI data (when latency is non-zero). + + time_info is a pointer passed to time_proc. + + return value: + Upon success Pm_Open() returns PmNoError and places a pointer to a + valid PortMidiStream in the stream argument. + If a call to Pm_Open() fails a nonzero error code is returned (see + PMError above) and the value of port is invalid. + + Any stream that is successfully opened should eventually be closed + by calling Pm_Close(). + +*/ +PmError Pm_OpenInput( PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputDriverInfo, + long bufferSize, + PmTimeProcPtr time_proc, + void *time_info ); + +PmError Pm_OpenOutput( PortMidiStream** stream, + PmDeviceID outputDevice, + void *outputDriverInfo, + long bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + long latency ); + +/* + Pm_SetFilter() sets filters on an open input stream to drop selected + input types. By default, only active sensing messages are filtered. + To prohibit, say, active sensing and sysex messages, call + Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX); + + Filtering is useful when midi routing or midi thru functionality is being + provided by the user application. + For example, you may want to exclude timing messages (clock, MTC, start/stop/continue), + while allowing note-related messages to pass. + Or you may be using a sequencer or drum-machine for MIDI clock information but want to + exclude any notes it may play. + */ + +/* filter active sensing messages (0xFE): */ +#define PM_FILT_ACTIVE 0x1 +/* filter system exclusive messages (0xF0): */ +#define PM_FILT_SYSEX 0x2 +/* filter clock messages (0xF8 only, does not filter clock start, etc.): */ +#define PM_FILT_CLOCK 0x4 +/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ +#define PM_FILT_PLAY 0x8 +/* filter undefined F9 messages (some equipment uses this as a 10ms 'tick') */ +#define PM_FILT_F9 0x10 +#define PM_FILT_TICK PM_FILT_F9 +/* filter undefined FD messages */ +#define PM_FILT_FD 0x20 +/* filter undefined real-time messages */ +#define PM_FILT_UNDEFINED (PM_FILT_F9 | PM_FILT_FD) +/* filter reset messages (0xFF) */ +#define PM_FILT_RESET 0x40 +/* filter all real-time messages */ +#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET) +/* filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ +#define PM_FILT_NOTE 0x80 +/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ +#define PM_FILT_CHANNEL_AFTERTOUCH 0x100 +/* per-note aftertouch (Ensoniq holds a patent on generating this on keyboards until June 2006) (0xA0-0xAF) */ +#define PM_FILT_POLY_AFTERTOUCH 0x200 +/* filter both channel and poly aftertouch */ +#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH) +/* Program changes (0xC0-0xCF) */ +#define PM_FILT_PROGRAM 0x400 +/* Control Changes (CC's) (0xB0-0xBF)*/ +#define PM_FILT_CONTROL 0x800 +/* Pitch Bender (0xE0-0xEF*/ +#define PM_FILT_PITCHBEND 0x1000 +/* MIDI Time Code (0xF1)*/ +#define PM_FILT_MTC 0x2000 +/* Song Position (0xF2) */ +#define PM_FILT_SONG_POSITION 0x4000 +/* Song Select (0xF3)*/ +#define PM_FILT_SONG_SELECT 0x8000 +/* Tuning request (0xF6)*/ +#define PM_FILT_TUNE 0x10000 +/* All System Common messages (mtc, song position, song select, tune request) */ +#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) + + +PmError Pm_SetFilter( PortMidiStream* stream, long filters ); + + +/* + Pm_SetChannelMask() filters incoming messages based on channel. + The mask is a 16-bit bitfield corresponding to appropriate channels + The Pm_Channel macro can assist in calling this function. + i.e. to set receive only input on channel 1, call with + Pm_SetChannelMask(Pm_Channel(1)); + Multiple channels should be OR'd together, like + Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11)) + + All channels are allowed by default +*/ +#define Pm_Channel(channel) (1<<(channel)) + +PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); + +/* + Pm_Abort() terminates outgoing messages immediately + The caller should immediately close the output port; + this call may result in transmission of a partial midi message. + There is no abort for Midi input because the user can simply + ignore messages in the buffer and close an input device at + any time. + */ +PmError Pm_Abort( PortMidiStream* stream ); + +/* + Pm_Close() closes a midi stream, flushing any pending buffers. + (PortMidi attempts to close open streams when the application + exits -- this is particularly difficult under Windows.) +*/ +PmError Pm_Close( PortMidiStream* stream ); + +/* + Pm_Message() encodes a short Midi message into a long word. If data1 + and/or data2 are not present, use zero. + + Pm_MessageStatus(), Pm_MessageData1(), and + Pm_MessageData2() extract fields from a long-encoded midi message. +*/ +#define Pm_Message(status, data1, data2) \ + ((((data2) << 16) & 0xFF0000) | \ + (((data1) << 8) & 0xFF00) | \ + ((status) & 0xFF)) +#define Pm_MessageStatus(msg) ((msg) & 0xFF) +#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) +#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) + +/* All midi data comes in the form of PmEvent structures. A sysex + message is encoded as a sequence of PmEvent structures, with each + structure carrying 4 bytes of the message, i.e. only the first + PmEvent carries the status byte. + + Note that MIDI allows nested messages: the so-called "real-time" MIDI + messages can be inserted into the MIDI byte stream at any location, + including within a sysex message. MIDI real-time messages are one-byte + messages used mainly for timing (see the MIDI spec). PortMidi retains + the order of non-real-time MIDI messages on both input and output, but + it does not specify exactly how real-time messages are processed. This + is particulary problematic for MIDI input, because the input parser + must either prepare to buffer an unlimited number of sysex message + bytes or to buffer an unlimited number of real-time messages that + arrive embedded in a long sysex message. To simplify things, the input + parser is allowed to pass real-time MIDI messages embedded within a + sysex message, and it is up to the client to detect, process, and + remove these messages as they arrive. + + When receiving sysex messages, the sysex message is terminated + by either an EOX status byte (anywhere in the 4 byte messages) or + by a non-real-time status byte in the low order byte of the message. + If you get a non-real-time status byte but there was no EOX byte, it + means the sysex message was somehow truncated. This is not + considered an error; e.g., a missing EOX can result from the user + disconnecting a MIDI cable during sysex transmission. + + A real-time message can occur within a sysex message. A real-time + message will always occupy a full PmEvent with the status byte in + the low-order byte of the PmEvent message field. (This implies that + the byte-order of sysex bytes and real-time message bytes may not + be preserved -- for example, if a real-time message arrives after + 3 bytes of a sysex message, the real-time message will be delivered + first. The first word of the sysex message will be delivered only + after the 4th byte arrives, filling the 4-byte PmEvent message field. + + The timestamp field is observed when the output port is opened with + a non-zero latency. A timestamp of zero means "use the current time", + which in turn means to deliver the message with a delay of + latency (the latency parameter used when opening the output port.) + Do not expect PortMidi to sort data according to timestamps -- + messages should be sent in the correct order, and timestamps MUST + be non-decreasing. + + A sysex message will generally fill many PmEvent structures. On + output to a PortMidiStream with non-zero latency, the first timestamp + on sysex message data will determine the time to begin sending the + message. PortMidi implementations may ignore timestamps for the + remainder of the sysex message. + + On input, the timestamp ideally denotes the arrival time of the + status byte of the message. The first timestamp on sysex message + data will be valid. Subsequent timestamps may denote + when message bytes were actually received, or they may be simply + copies of the first timestamp. + + Timestamps for nested messages: If a real-time message arrives in + the middle of some other message, it is enqueued immediately with + the timestamp corresponding to its arrival time. The interrupted + non-real-time message or 4-byte packet of sysex data will be enqueued + later. The timestamp of interrupted data will be equal to that of + the interrupting real-time message to insure that timestamps are + non-decreasing. + */ +typedef long PmMessage; +typedef struct { + PmMessage message; + PmTimestamp timestamp; +} PmEvent; + +/* + Pm_Read() retrieves midi data into a buffer, and returns the number + of events read. Result is a non-negative number unless an error occurs, + in which case a PmError value will be returned. + + Buffer Overflow + + The problem: if an input overflow occurs, data will be lost, ultimately + because there is no flow control all the way back to the data source. + When data is lost, the receiver should be notified and some sort of + graceful recovery should take place, e.g. you shouldn't resume receiving + in the middle of a long sysex message. + + With a lock-free fifo, which is pretty much what we're stuck with to + enable portability to the Mac, it's tricky for the producer and consumer + to synchronously reset the buffer and resume normal operation. + + Solution: the buffer managed by PortMidi will be flushed when an overflow + occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow) + and ordinary processing resumes as soon as a new message arrives. The + remainder of a partial sysex message is not considered to be a "new + message" and will be flushed as well. + +*/ +PmError Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length ); + +/* + Pm_Poll() tests whether input is available, + returning TRUE, FALSE, or an error value. +*/ +PmError Pm_Poll( PortMidiStream *stream); + +/* + Pm_Write() writes midi data from a buffer. This may contain: + - short messages + or + - sysex messages that are converted into a sequence of PmEvent + structures, e.g. sending data from a file or forwarding them + from midi input. + + Use Pm_WriteSysEx() to write a sysex message stored as a contiguous + array of bytes. + + Sysex data may contain embedded real-time messages. +*/ +PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length ); + +/* + Pm_WriteShort() writes a timestamped non-system-exclusive midi message. + Messages are delivered in order as received, and timestamps must be + non-decreasing. (But timestamps are ignored if the stream was opened + with latency = 0.) +*/ +PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, long msg); + +/* + Pm_WriteSysEx() writes a timestamped system-exclusive midi message. +*/ +PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PORT_MIDI_H */ -- cgit v1.2.1