diff --git a/PortMidi.cabal b/PortMidi.cabal index e960536..673c968 100644 --- a/PortMidi.cabal +++ b/PortMidi.cabal @@ -1,6 +1,6 @@ name: PortMidi version: 0.2.0.0 -Cabal-Version: >= 1.6 +Cabal-Version: >= 1.8 build-type: Simple license: BSD3 license-file: LICENSE @@ -14,20 +14,24 @@ description: A Haskell binding for PortMedia/PortMidi extra-source-files: README.txt CHANGELOG.md - portmidi/pm_common/portmidi.h - portmidi/pm_common/pmutil.h portmidi/pm_common/pminternal.h + portmidi/pm_common/pmutil.h + portmidi/pm_common/portmidi.h portmidi/pm_linux/pmlinux.h portmidi/pm_linux/pmlinuxalsa.h - portmidi/pm_mac/pmmac.h + portmidi/pm_linux/pmlinuxnull.h portmidi/pm_mac/pmmacosxcm.h portmidi/pm_win/pmwinmm.h portmidi/porttime/porttime.h + Library exposed-modules: Sound.PortMidi other-modules: Sound.PortMidi.DeviceInfo build-depends: base >= 4.8 && < 5 extensions: CPP, ForeignFunctionInterface + if arch(i386) || arch(x86_64) + cc-options: -msse2 + if os(linux) || os(freebsd) include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime cc-options: -DPMALSA @@ -36,20 +40,20 @@ Library portmidi/pm_common/portmidi.c portmidi/pm_linux/pmlinux.c portmidi/pm_linux/pmlinuxalsa.c + portmidi/pm_linux/pmlinuxnull.c portmidi/porttime/porttime.c portmidi/porttime/ptlinux.c extra-libraries: asound else if os(darwin) include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime - cc-options: -msse2 c-sources: portmidi/pm_common/pmutil.c portmidi/pm_common/portmidi.c portmidi/pm_mac/pmmac.c portmidi/pm_mac/pmmacosxcm.c portmidi/porttime/porttime.c - portmidi/porttime/ptmacosx_mach.c + portmidi/porttime/ptmacosx_cf.c frameworks: CoreMIDI CoreFoundation CoreAudio else if os(mingw32) diff --git a/portmidi/pm_common/pminternal.h b/portmidi/pm_common/pminternal.h index 84bc90a..8b3d8f5 100644 --- a/portmidi/pm_common/pminternal.h +++ b/portmidi/pm_common/pminternal.h @@ -1,174 +1,190 @@ -/* 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, - PmTimestamp timestamp); -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 or queue? */ - PmQueue *queue; - - 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 sysex_in_progress; /* when sysex status is seen, this flag becomes - * true until EOX is seen. When true, new data is appended to the - * stream of outgoing bytes. When overflow occurs, sysex data is - * dropped (until an EOX or non-real-timei status byte is seen) so - * that, if the overflow condition is cleared, we don't start - * sending data from the middle of a sysex message. If a sysex - * message is filtered, sysex_in_progress is false, causing the - * message to be dropped. */ - 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; /* flter 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 */ - /* the following are used to expedite sysex data */ - /* on windows, in debug mode, based on some profiling, these optimizations - * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, - * but this does not count time in the driver, so I don't know if it is - * important - */ - unsigned char *fill_base; /* addr of ptr to sysex data */ - unsigned long *fill_offset_ptr; /* offset of next sysex byte */ - int fill_length; /* how many sysex bytes to write */ -} PmInternal; - - -/* 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_write_byte(PmInternal *midi, unsigned char byte, - PmTimestamp timestamp); -PmTimestamp none_synchronize(PmInternal *midi); - -PmError pm_fail_fn(PmInternal *midi); -PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); -PmError pm_success_fn(PmInternal *midi); -PmError pm_add_device(char *interf, char *name, int input, void *descriptor, - pm_fns_type dictionary); -unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, - PmTimestamp timestamp); -void pm_read_short(PmInternal *midi, PmEvent *event); - -#define none_write_flush pm_fail_timestamp_fn -#define none_sysex pm_fail_timestamp_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 - +/** @file pminternal.h header for PortMidi 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. + */ + +/** @cond INTERNAL - add INTERNAL to Doxygen ENABLED_SECTIONS to include */ + +#ifdef __cplusplus +extern "C" { +#endif + +extern int pm_initialized; /* see note in portmidi.c */ +extern PmDeviceID pm_default_input_device_id; +extern PmDeviceID pm_default_output_device_id; + +/* these are defined in system-specific file */ +void *pm_alloc(size_t s); +void pm_free(void *ptr); + +/* if a host error (an error reported by the host MIDI API that is not + * mapped to a PortMidi error code) occurs in a synchronous operation + * (i.e., not in a callback from another thread) set these: */ +extern int pm_hosterror; /* boolean */ +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, + PmTimestamp timestamp); +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_create_fn)(int is_input, const char *name, + void *driverInfo); +typedef PmError (*pm_delete_fn)(PmDeviceID id); +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 unsigned int (*pm_check_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 msg within sysex */ + pm_write_flush_fn write_flush; /* send any accumulated but unsent data */ + pm_synchronize_fn synchronize; /* synchronize PM 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_check_host_error_fn check_host_error; /* true when device has had host */ + /* error; sets pm_hosterror and writes message to pm_hosterror_text */ +} 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 automatic + device closing -- see PmDeviceInfo struct) */ + int deleted; /* is this is a deleted virtual device? */ + void *descriptor; /* ID number passed to win32 multimedia API open, + * coreMIDI endpoint, etc., representing the device */ + struct pm_internal_struct *pm_internal; /* points to PmInternal device */ + /* when the device is open, allows automatic device closing */ + pm_fns_type dictionary; +} descriptor_node, *descriptor_type; + +extern int pm_descriptor_max; +extern descriptor_type pm_descriptors; +extern int pm_descriptor_len; + +typedef uint32_t (*time_get_proc_type)(void *time_info); + +typedef struct pm_internal_struct { + int device_id; /* which device is open (index to pm_descriptors) */ + short is_input; /* MIDI IN (true) or MIDI OUT (false) */ + short is_removed; /* MIDI device was removed */ + PmTimeProcPtr time_proc; /* where to get the time */ + void *time_info; /* pass this to get_time() */ + int32_t buffer_len; /* how big is the buffer or queue? */ + PmQueue *queue; + + int32_t 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 sysex_in_progress; /* when sysex status is seen, this flag becomes + * true until EOX is seen. When true, new data is appended to the + * stream of outgoing bytes. When overflow occurs, sysex data is + * dropped (until an EOX or non-real-timei status byte is seen) so + * that, if the overflow condition is cleared, we don't start + * sending data from the middle of a sysex message. If a sysex + * message is filtered, sysex_in_progress is false, causing the + * message to be dropped. */ + PmMessage message; /* buffer for 4 bytes of sysex data */ + int message_count; /* how many bytes in sysex_message so far */ + int short_message_count; /* how many bytes are expected in short message */ + unsigned char running_status; /* running status byte or zero if none */ + int32_t filters; /* flags that filter incoming message classes */ + int32_t 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 *api_info; /* system-dependent state */ + /* the following are used to expedite sysex data */ + /* on windows, in debug mode, based on some profiling, these optimizations + * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte, + * but this does not count time in the driver, so I don't know if it is + * important + */ + unsigned char *fill_base; /* addr of ptr to sysex data */ + uint32_t *fill_offset_ptr; /* offset of next sysex byte */ + uint32_t fill_length; /* how many sysex bytes to write */ +} PmInternal; + +/* what is the length of this short message? */ +int pm_midi_length(PmMessage msg); + +/* 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_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp); +PmTimestamp none_synchronize(PmInternal *midi); + +PmError pm_fail_fn(PmInternal *midi); +PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp); +PmError pm_success_fn(PmInternal *midi); +PmError pm_add_interf(char *interf, pm_create_fn create_fn, + pm_delete_fn delete_fn); +PmError pm_add_device(char *interf, const char *name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary); +void pm_undo_add_device(int id); +uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len, + PmTimestamp timestamp); +void pm_read_short(PmInternal *midi, PmEvent *event); + +#define none_write_flush pm_fail_timestamp_fn +#define none_sysex pm_fail_timestamp_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 + +/** @endcond */ diff --git a/portmidi/pm_common/pmutil.c b/portmidi/pm_common/pmutil.c index eb3d80a..a70fe2f 100644 --- a/portmidi/pm_common/pmutil.c +++ b/portmidi/pm_common/pmutil.c @@ -1,289 +1,284 @@ -/* pmutil.c -- some helpful utilities for building midi - applications that use PortMidi - */ -#include -#include -#include -#include "portmidi.h" -#include "pmutil.h" -#include "pminternal.h" - -#ifdef WIN32 -#define bzero(addr, siz) memset(addr, 0, siz) -#endif - -// #define QUEUE_DEBUG 1 -#ifdef QUEUE_DEBUG -#include "stdio.h" -#endif - -/* code is based on 4-byte words -- it should work on a 64-bit machine - as long as a "long" has 4 bytes. This code could be generalized to - be independent of the size of "long" */ - -typedef long int32; - -typedef struct { - long head; - long tail; - long len; - long msg_size; /* number of int32 in a message including extra word */ - long overflow; - long peek_overflow; - int32 *buffer; - int32 *peek; - int peek_flag; -} PmQueueRep; - - -PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) -{ - int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) & - ~(sizeof(int32) - 1)) / sizeof(int32); - PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); - if (!queue) /* memory allocation failed */ - return NULL; - - /* need extra word per message for non-zero encoding */ - queue->len = num_msgs * (int32s_per_msg + 1); - queue->buffer = (int32 *) pm_alloc(queue->len * sizeof(int32)); - bzero(queue->buffer, queue->len * sizeof(int32)); - if (!queue->buffer) { - pm_free(queue); - return NULL; - } else { /* allocate the "peek" buffer */ - queue->peek = (int32 *) pm_alloc(int32s_per_msg * sizeof(int32)); - if (!queue->peek) { - /* free everything allocated so far and return */ - pm_free(queue->buffer); - pm_free(queue); - return NULL; - } - } - bzero(queue->buffer, queue->len * sizeof(int32)); - queue->head = 0; - queue->tail = 0; - /* msg_size is in words */ - queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ - queue->overflow = FALSE; - queue->peek_overflow = FALSE; - queue->peek_flag = FALSE; - return queue; -} - - -PmError Pm_QueueDestroy(PmQueue *q) -{ - PmQueueRep *queue = (PmQueueRep *) q; - - /* arg checking */ - if (!queue || !queue->buffer || !queue->peek) - return pmBadPtr; - - pm_free(queue->peek); - pm_free(queue->buffer); - pm_free(queue); - return pmNoError; -} - - -PmError Pm_Dequeue(PmQueue *q, void *msg) -{ - long head; - PmQueueRep *queue = (PmQueueRep *) q; - int i; - int32 *msg_as_int32 = (int32 *) msg; - - /* arg checking */ - if (!queue) - return pmBadPtr; - /* a previous peek operation encountered an overflow, but the overflow - * has not yet been reported to client, so do it now. No message is - * returned, but on the next call, we will return the peek buffer. - */ - if (queue->peek_overflow) { - queue->peek_overflow = FALSE; - return pmBufferOverflow; - } - if (queue->peek_flag) { - memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32)); - queue->peek_flag = FALSE; - return pmGotData; - } - - head = queue->head; - /* if writer overflows, it writes queue->overflow = tail+1 so that - * when the reader gets to that position in the buffer, it can - * return the overflow condition to the reader. The problem is that - * at overflow, things have wrapped around, so tail == head, and the - * reader will detect overflow immediately instead of waiting until - * it reads everything in the buffer, wrapping around again to the - * point where tail == head. So the condition also checks that - * queue->buffer[head] is zero -- if so, then the buffer is now - * empty, and we're at the point in the msg stream where overflow - * occurred. It's time to signal overflow to the reader. If - * queue->buffer[head] is non-zero, there's a message there and we - * should read all the way around the buffer before signalling overflow. - * There is a write-order dependency here, but to fail, the overflow - * field would have to be written while an entire buffer full of - * writes are still pending. I'm assuming out-of-order writes are - * possible, but not that many. - */ - if (queue->overflow == head + 1 && !queue->buffer[head]) { - queue->overflow = 0; /* non-overflow condition */ - return pmBufferOverflow; - } - - /* test to see if there is data in the queue -- test from back - * to front so if writer is simultaneously writing, we don't - * waste time discovering the write is not finished - */ - for (i = queue->msg_size - 1; i >= 0; i--) { - if (!queue->buffer[head + i]) { - return pmNoData; - } - } - memcpy(msg, (char *) &queue->buffer[head + 1], - sizeof(int32) * (queue->msg_size - 1)); - /* fix up zeros */ - i = queue->buffer[head]; - while (i < queue->msg_size) { - int32 j; - i--; /* msg does not have extra word so shift down */ - j = msg_as_int32[i]; - msg_as_int32[i] = 0; - i = j; - } - /* signal that data has been removed by zeroing: */ - bzero((char *) &queue->buffer[head], sizeof(int32) * queue->msg_size); - - /* update head */ - head += queue->msg_size; - if (head == queue->len) head = 0; - queue->head = head; - return pmGotData; /* success */ -} - - - -PmError Pm_SetOverflow(PmQueue *q) -{ - PmQueueRep *queue = (PmQueueRep *) q; - long tail; - /* arg checking */ - if (!queue) - return pmBadPtr; - /* no more enqueue until receiver acknowledges overflow */ - if (queue->overflow) return pmBufferOverflow; - tail = queue->tail; - queue->overflow = tail + 1; - return pmBufferOverflow; -} - - -PmError Pm_Enqueue(PmQueue *q, void *msg) -{ - PmQueueRep *queue = (PmQueueRep *) q; - long tail; - int i; - int32 *src = (int32 *) msg; - int32 *ptr; - int32 *dest; - int rslt; - if (!queue) - return pmBadPtr; - /* no more enqueue until receiver acknowledges overflow */ - if (queue->overflow) return pmBufferOverflow; - rslt = Pm_QueueFull(q); - /* already checked above: if (rslt == pmBadPtr) return rslt; */ - tail = queue->tail; - if (rslt) { - queue->overflow = tail + 1; - return pmBufferOverflow; - } - - /* queue is has room for message, and overflow flag is cleared */ - ptr = &queue->buffer[tail]; - dest = ptr + 1; - for (i = 1; i < queue->msg_size; i++) { - int32 j = src[i - 1]; - if (!j) { - *ptr = i; - ptr = dest; - } else { - *dest = j; - } - dest++; - } - *ptr = i; - tail += queue->msg_size; - if (tail == queue->len) tail = 0; - queue->tail = tail; - return pmNoError; -} - - -int Pm_QueueEmpty(PmQueue *q) -{ - PmQueueRep *queue = (PmQueueRep *) q; - return (!queue) || /* null pointer -> return "empty" */ - (queue->buffer[queue->head] == 0 && !queue->peek_flag); -} - - -int Pm_QueueFull(PmQueue *q) -{ - int tail; - int i; - PmQueueRep *queue = (PmQueueRep *) q; - /* arg checking */ - if (!queue) - return pmBadPtr; - tail = queue->tail; - /* test to see if there is space in the queue */ - for (i = 0; i < queue->msg_size; i++) { - if (queue->buffer[tail + i]) { - return TRUE; - } - } - return FALSE; -} - - -void *Pm_QueuePeek(PmQueue *q) -{ - PmError rslt; - long temp; - PmQueueRep *queue = (PmQueueRep *) q; - /* arg checking */ - if (!queue) - return NULL; - - if (queue->peek_flag) { - return queue->peek; - } - /* this is ugly: if peek_overflow is set, then Pm_Dequeue() - * returns immediately with pmBufferOverflow, but here, we - * want Pm_Dequeue() to really check for data. If data is - * there, we can return it - */ - temp = queue->peek_overflow; - queue->peek_overflow = FALSE; - rslt = Pm_Dequeue(q, queue->peek); - queue->peek_overflow = temp; - - if (rslt == 1) { - queue->peek_flag = TRUE; - return queue->peek; - } else if (rslt == pmBufferOverflow) { - /* when overflow is indicated, the queue is empty and the - * first message that was dropped by Enqueue (signalling - * pmBufferOverflow to its caller) would have been the next - * message in the queue. Pm_QueuePeek will return NULL, but - * remember that an overflow occurred. (see Pm_Dequeue) - */ - queue->peek_overflow = TRUE; - } - return NULL; -} - +/* pmutil.c -- some helpful utilities for building midi + applications that use PortMidi + */ +#include +#include +#include +#include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + +#ifdef WIN32 +#define bzero(addr, siz) memset(addr, 0, siz) +#endif + +// #define QUEUE_DEBUG 1 +#ifdef QUEUE_DEBUG +#include "stdio.h" +#endif + +typedef struct { + long head; + long tail; + long len; + long overflow; + int32_t msg_size; /* number of int32_t in a message including extra word */ + int32_t peek_overflow; + int32_t *buffer; + int32_t *peek; + int32_t peek_flag; +} PmQueueRep; + + +PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg) +{ + int32_t int32s_per_msg = + (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) & + ~(sizeof(int32_t) - 1)) / sizeof(int32_t)); + PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep)); + if (!queue) /* memory allocation failed */ + return NULL; + + /* need extra word per message for non-zero encoding */ + queue->len = num_msgs * (int32s_per_msg + 1); + queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t)); + bzero(queue->buffer, queue->len * sizeof(int32_t)); + if (!queue->buffer) { + pm_free(queue); + return NULL; + } else { /* allocate the "peek" buffer */ + queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t)); + if (!queue->peek) { + /* free everything allocated so far and return */ + pm_free(queue->buffer); + pm_free(queue); + return NULL; + } + } + bzero(queue->buffer, queue->len * sizeof(int32_t)); + queue->head = 0; + queue->tail = 0; + /* msg_size is in words */ + queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */ + queue->overflow = FALSE; + queue->peek_overflow = FALSE; + queue->peek_flag = FALSE; + return queue; +} + + +PMEXPORT PmError Pm_QueueDestroy(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + + /* arg checking */ + if (!queue || !queue->buffer || !queue->peek) + return pmBadPtr; + + pm_free(queue->peek); + pm_free(queue->buffer); + pm_free(queue); + return pmNoError; +} + + +PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg) +{ + long head; + PmQueueRep *queue = (PmQueueRep *) q; + int i; + int32_t *msg_as_int32 = (int32_t *) msg; + + /* arg checking */ + if (!queue) + return pmBadPtr; + /* a previous peek operation encountered an overflow, but the overflow + * has not yet been reported to client, so do it now. No message is + * returned, but on the next call, we will return the peek buffer. + */ + if (queue->peek_overflow) { + queue->peek_overflow = FALSE; + return pmBufferOverflow; + } + if (queue->peek_flag) { + memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t)); + queue->peek_flag = FALSE; + return pmGotData; + } + + head = queue->head; + /* if writer overflows, it writes queue->overflow = tail+1 so that + * when the reader gets to that position in the buffer, it can + * return the overflow condition to the reader. The problem is that + * at overflow, things have wrapped around, so tail == head, and the + * reader will detect overflow immediately instead of waiting until + * it reads everything in the buffer, wrapping around again to the + * point where tail == head. So the condition also checks that + * queue->buffer[head] is zero -- if so, then the buffer is now + * empty, and we're at the point in the msg stream where overflow + * occurred. It's time to signal overflow to the reader. If + * queue->buffer[head] is non-zero, there's a message there and we + * should read all the way around the buffer before signalling overflow. + * There is a write-order dependency here, but to fail, the overflow + * field would have to be written while an entire buffer full of + * writes are still pending. I'm assuming out-of-order writes are + * possible, but not that many. + */ + if (queue->overflow == head + 1 && !queue->buffer[head]) { + queue->overflow = 0; /* non-overflow condition */ + return pmBufferOverflow; + } + + /* test to see if there is data in the queue -- test from back + * to front so if writer is simultaneously writing, we don't + * waste time discovering the write is not finished + */ + for (i = queue->msg_size - 1; i >= 0; i--) { + if (!queue->buffer[head + i]) { + return pmNoData; + } + } + memcpy(msg, (char *) &queue->buffer[head + 1], + sizeof(int32_t) * (queue->msg_size - 1)); + /* fix up zeros */ + i = queue->buffer[head]; + while (i < queue->msg_size) { + int32_t j; + i--; /* msg does not have extra word so shift down */ + j = msg_as_int32[i]; + msg_as_int32[i] = 0; + i = j; + } + /* signal that data has been removed by zeroing: */ + bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size); + + /* update head */ + head += queue->msg_size; + if (head == queue->len) head = 0; + queue->head = head; + return pmGotData; /* success */ +} + + + +PMEXPORT PmError Pm_SetOverflow(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + /* arg checking */ + if (!queue) + return pmBadPtr; + /* no more enqueue until receiver acknowledges overflow */ + if (queue->overflow) return pmBufferOverflow; + tail = queue->tail; + queue->overflow = tail + 1; + return pmBufferOverflow; +} + + +PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg) +{ + PmQueueRep *queue = (PmQueueRep *) q; + long tail; + int i; + int32_t *src = (int32_t *) msg; + int32_t *ptr; + int32_t *dest; + int rslt; + if (!queue) + return pmBadPtr; + /* no more enqueue until receiver acknowledges overflow */ + if (queue->overflow) return pmBufferOverflow; + rslt = Pm_QueueFull(q); + /* already checked above: if (rslt == pmBadPtr) return rslt; */ + tail = queue->tail; + if (rslt) { + queue->overflow = tail + 1; + return pmBufferOverflow; + } + + /* queue is has room for message, and overflow flag is cleared */ + ptr = &queue->buffer[tail]; + dest = ptr + 1; + for (i = 1; i < queue->msg_size; i++) { + int32_t j = src[i - 1]; + if (!j) { + *ptr = i; + ptr = dest; + } else { + *dest = j; + } + dest++; + } + *ptr = i; + tail += queue->msg_size; + if (tail == queue->len) tail = 0; + queue->tail = tail; + return pmNoError; +} + + +PMEXPORT int Pm_QueueEmpty(PmQueue *q) +{ + PmQueueRep *queue = (PmQueueRep *) q; + return (!queue) || /* null pointer -> return "empty" */ + (queue->buffer[queue->head] == 0 && !queue->peek_flag); +} + + +PMEXPORT int Pm_QueueFull(PmQueue *q) +{ + long tail; + int i; + PmQueueRep *queue = (PmQueueRep *) q; + /* arg checking */ + if (!queue) + return pmBadPtr; + tail = queue->tail; + /* test to see if there is space in the queue */ + for (i = 0; i < queue->msg_size; i++) { + if (queue->buffer[tail + i]) { + return TRUE; + } + } + return FALSE; +} + + +PMEXPORT void *Pm_QueuePeek(PmQueue *q) +{ + PmError rslt; + int32_t temp; + PmQueueRep *queue = (PmQueueRep *) q; + /* arg checking */ + if (!queue) + return NULL; + + if (queue->peek_flag) { + return queue->peek; + } + /* this is ugly: if peek_overflow is set, then Pm_Dequeue() + * returns immediately with pmBufferOverflow, but here, we + * want Pm_Dequeue() to really check for data. If data is + * there, we can return it + */ + temp = queue->peek_overflow; + queue->peek_overflow = FALSE; + rslt = Pm_Dequeue(q, queue->peek); + queue->peek_overflow = temp; + + if (rslt == 1) { + queue->peek_flag = TRUE; + return queue->peek; + } else if (rslt == pmBufferOverflow) { + /* when overflow is indicated, the queue is empty and the + * first message that was dropped by Enqueue (signalling + * pmBufferOverflow to its caller) would have been the next + * message in the queue. Pm_QueuePeek will return NULL, but + * remember that an overflow occurred. (see Pm_Dequeue) + */ + queue->peek_overflow = TRUE; + } + return NULL; +} + diff --git a/portmidi/pm_common/pmutil.h b/portmidi/pm_common/pmutil.h index 10a85ff..46c618e 100644 --- a/portmidi/pm_common/pmutil.h +++ b/portmidi/pm_common/pmutil.h @@ -1,31 +1,51 @@ -/* pmutil.h -- some helpful utilities for building midi - applications that use PortMidi +/** @file pmutil.h lock-free queue for building MIDI + applications with PortMidi. + + PortMidi is not reentrant, and locks can suffer from priority + inversion. To support coordination between system callbacks, a + high-priority thread created with PortTime, and the main + application thread, PortMidi uses a lock-free, non-blocking + queue. The queue implementation is not particular to MIDI and is + available for other uses. */ +#ifndef PORTMIDI_PMUTIL_H +#define PORTMIDI_PMUTIL_H + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +/** @defgroup grp_pmutil Lock-free Queue + @{ +*/ + +/** The queue representation is opaque. Declare a queue as PmQueue * */ 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. +/** create a single-reader, single-writer queue. + + @param num_msgs the number of messages the queue can hold + + @param the fixed message size + + @return the allocated and initialized queue, or NULL if memory + cannot be allocated. Allocation uses #pm_malloc(). + + The queue only accepts fixed sized messages. This queue implementation uses the "light pipe" algorithm which operates correctly even with multi-processors and out-of-order memory writes. (see Alexander Dokumentov, "Lock-free Interprocess - Communication," Dr. Dobbs Portal, http://www.ddj.com/, - articleID=189401457, June 15, 2006. This algorithm requires - that messages be translated to a form where no words contain - zeros. Each word becomes its own "data valid" tag. Because of - this translation, we cannot return a pointer to data still in - the queue when the "peek" method is called. Instead, a buffer - is preallocated so that data can be copied there. Pm_QueuePeek() - dequeues a message into this buffer and returns a pointer to - it. A subsequent Pm_Dequeue() will copy from this buffer. + Communication," Dr. Dobbs Portal, http://www.ddj.com/, + articleID=189401457, June 15, 2006. This algorithm requires that + messages be translated to a form where no words contain + zeros. Each word becomes its own "data valid" tag. Because of this + translation, we cannot return a pointer to data still in the queue + when the "peek" method is called. Instead, a buffer is + preallocated so that data can be copied there. Pm_QueuePeek() + dequeues a message into this buffer and returns a pointer to it. A + subsequent Pm_Dequeue() will copy from this buffer. This implementation does not try to keep reader/writer data in separate cache lines or prevent thrashing on cache lines. @@ -36,92 +56,129 @@ typedef void PmQueue; of the cache line, especially if messages are smaller than cache lines. See the Dokumentov article for explanation. - The algorithm is extended to handle "overflow" reporting. To report - an overflow, the sender writes the current tail position to a field. - The receiver must acknowlege receipt by zeroing the field. The sender - will not send more until the field is zeroed. + The algorithm is extended to handle "overflow" reporting. To + report an overflow, the sender writes the current tail position to + a field. The receiver must acknowlege receipt by zeroing the + field. The sender will not send more until the field is zeroed. + */ +PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); - Pm_QueueDestroy() destroys the queue and frees its storage. +/** destroy a queue and free its storage. + + @param queue a queue created by #Pm_QueueCreate(). + + @return pmNoError or an error code. + + Uses #pm_free(). + */ +PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); -PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg); -PmError Pm_QueueDestroy(PmQueue *queue); +/** remove one message from the queue, copying it into \p msg. -/* - 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 if what would have been the next thing - in the queue was dropped due to overflow. (So when overflow occurs, - the receiver can receive a queue full of messages before getting the - overflow report. This protocol ensures that the reader will be + @param queue a queue created by #Pm_QueueCreate(). + + @param msg address to which the message, if any, is copied. + + @return 1 if successful, and 0 if the queue is empty. Returns + #pmBufferOverflow if what would have been the next thing in the + queue was dropped due to overflow. (So when overflow occurs, the + receiver can receive a queue full of messages before getting the + overflow report. This protocol ensures that the reader will be notified when data is lost due to overflow. */ -PmError Pm_Dequeue(PmQueue *queue, void *msg); +PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); + +/** insert one message into the queue, copying it from \p msg. + @param queue a queue created by #Pm_QueueCreate(). -/* - 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. + @param msg address of the message to be enqueued. + + @return #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); +PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); +/** test if the queue is full. -/* - Pm_QueueFull() returns non-zero if the queue is full - Pm_QueueEmpty() returns non-zero if the queue is empty + @param queue a queue created by #Pm_QueueCreate(). - Either condition may change immediately because a parallel - enqueue or dequeue operation could be in progress. Furthermore, - Pm_QueueEmpty() is optimistic: it may say false, when due to - out-of-order writes, the full message has not arrived. Therefore, - Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns - false. On the other hand, Pm_QueueFull() is pessimistic: if it - returns false, then Pm_Enqueue() is guaranteed to succeed. + @return non-zero iff the queue is empty, and @pmBadPtr if \p queue + is NULL. - Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. - Pm_QueueEmpty() returns FALSE if queue is NULL. + The full condition may change immediately because a parallel + dequeue operation could be in progress. The result is + pessimistic: if it returns false (zero) to the single writer, then + #Pm_Enqueue() is guaranteed to succeed. */ -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. - Pm_QueuePeek() will not indicate when an overflow occurs. If you want - to get and check pmBufferOverflow messages, use the return value of - Pm_QueuePeek() *only* as an indication that you should call - Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would - return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally - clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume - enqueuing messages. A subsequent call to Pm_QueuePeek() - will return a pointer to the first message *after* the overflow. - Using this as an indication to call Pm_Dequeue(), the first call - to Pm_Dequeue() will return pmBufferOverflow. The second call will - return success, copying the same message pointed to by the previous - Pm_QueuePeek(). - - When to use Pm_QueuePeek(): (1) when you need to look at the message +PMEXPORT int Pm_QueueFull(PmQueue *queue); + +/** test if the queue is empty. + + @param queue a queue created by #Pm_QueueCreate(). + + @return zero iff the queue is either empty or NULL. + + The empty condition may change immediately because a parallel + enqueue operation could be in progress. Furthermore, the + result is optimistic: it may say false, when due to + out-of-order writes, the full message has not arrived. Therefore, + #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns + false. +*/ +PMEXPORT int Pm_QueueEmpty(PmQueue *queue); + +/** get a pointer to the item at the head of the queue. + + @param queue a queue created by #Pm_QueueCreate(). + + @result a pointer to the head message or NULL if the queue is empty. + + The message is not removed from the queue. #Pm_QueuePeek() will + not indicate when an overflow occurs. If you want to get and check + #pmBufferOverflow messages, use the return value of + #Pm_QueuePeek() *only* as an indication that you should call + #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue() + would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL, + but internally clear the #pmBufferOverflow flag, enabling + #Pm_Enqueue() to resume enqueuing messages. A subsequent call to + #Pm_QueuePeek() will return a pointer to the first message *after* + the overflow. Using this as an indication to call #Pm_Dequeue(), + the first call to #Pm_Dequeue() will return #pmBufferOverflow. The + second call will return success, copying the same message pointed + to by the previous #Pm_QueuePeek(). + + When to use #Pm_QueuePeek(): (1) when you need to look at the message data to decide who should be called to receive it. (2) when you need to know a message is ready but cannot accept the message. - Note that Pm_QueuePeek() is not a fast check, so if possible, you - might as well just call Pm_Dequeue() and accept the data if it is there. + Note that #Pm_QueuePeek() is not a fast check, so if possible, you + might as well just call #Pm_Dequeue() and accept the data if it is there. */ -void *Pm_QueuePeek(PmQueue *queue); - -/* - Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow - condition to the reader (dequeuer). E.g. when transfering data from - the OS to an application, if the OS indicates a buffer overrun, - Pm_SetOverflow() can be used to insure that the reader receives a - pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue - is NULL, returns pmBufferOverflow if buffer is already in an overflow - state, returns pmNoError if successfully set overflow state. +PMEXPORT void *Pm_QueuePeek(PmQueue *queue); + +/** allows the writer (enqueuer) to signal an overflow + condition to the reader (dequeuer). + + @param queue a queue created by #Pm_QueueCreate(). + + @return #pmNoError if overflow is set, or #pmBadPtr if queue is + NULL, or #pmBufferOverflow if buffer is already in an overflow + state. + + E.g., when transfering data from the OS to an application, if the + OS indicates a buffer overrun, #Pm_SetOverflow() can be used to + insure that the reader receives a #pmBufferOverflow result from + #Pm_Dequeue(). */ -PmError Pm_SetOverflow(PmQueue *queue); +PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); + +/** @} */ #ifdef __cplusplus } #endif /* __cplusplus */ + +#endif // PORTMIDI_PMUTIL_H diff --git a/portmidi/pm_common/portmidi.c b/portmidi/pm_common/portmidi.c index 70a64ba..e78ee73 100644 --- a/portmidi/pm_common/portmidi.c +++ b/portmidi/pm_common/portmidi.c @@ -1,1084 +1,1472 @@ -#include "stdlib.h" -#include "string.h" -#include "portmidi.h" -#include "porttime.h" -#include "pmutil.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; -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 = (descriptor_type) - 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 ) { - Pm_Initialize(); - /* no error checking -- Pm_Initialize() does not fail */ - return pm_descriptor_index; -} - - -const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) { - Pm_Initialize(); /* no error check needed */ - 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; -} - -/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */ -PmError pm_fail_timestamp_fn(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'"; - break; - case pmBufferMaxSize: - msg = "PortMidi: `Buffer cannot be made larger'"; - break; - 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) { - 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 (pm_hosterror) return TRUE; - 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 ) { - if (!pm_initialized) { - pm_hosterror = FALSE; - pm_hosterror_text[0] = 0; /* the null string */ - pm_init(); - pm_initialized = TRUE; - } - return pmNoError; -} - - -PmError Pm_Terminate( void ) { - if (pm_initialized) { - pm_term(); - // if there are no devices, descriptors might still be NULL - if (descriptors != NULL) { - free(descriptors); - descriptors = NULL; - } - pm_descriptor_index = 0; - pm_descriptor_max = 0; - 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 - */ -int Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { - PmInternal *midi = (PmInternal *) stream; - int n = 0; - PmError err = pmNoError; - pm_hosterror = FALSE; - /* arg checking */ - if(midi == NULL) - err = pmBadPtr; - 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) { - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - return pm_errmsg(err); - } - - while (n < length) { - PmError err = Pm_Dequeue(midi->queue, buffer++); - if (err == pmBufferOverflow) { - /* ignore the data we have retreived so far */ - return pm_errmsg(pmBufferOverflow); - } else if (err == 0) { /* empty queue */ - break; - } - n++; - } - return n; -} - -PmError Pm_Poll( PortMidiStream *stream ) -{ - PmInternal *midi = (PmInternal *) stream; - PmError err; - - pm_hosterror = FALSE; - /* arg checking */ - if(midi == NULL) - err = pmBadPtr; - 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) { - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - return pm_errmsg(err); - } - - return !Pm_QueueEmpty(midi->queue); -} - - -/* this is called from Pm_Write and Pm_WriteSysEx to issue a - * call to the system-dependent end_sysex function and handle - * the error return - */ -static PmError pm_end_sysex(PmInternal *midi) -{ - PmError err = (*midi->dictionary->end_sysex)(midi, 0); - midi->sysex_in_progress = FALSE; - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - return err; -} - - -/* 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 = pmNoError; - int i; - int bits; - - pm_hosterror = FALSE; - /* arg checking */ - if(midi == NULL) - err = pmBadPtr; - 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; - } - } - /* error recovery: when a sysex is detected, we call - * dictionary->begin_sysex() followed by calls to - * dictionary->write_byte() and dictionary->write_realtime() - * until an end-of-sysex is detected, when we call - * dictionary->end_sysex(). After an error occurs, - * Pm_Write() continues to call functions. For example, - * it will continue to call write_byte() even after - * an error sending a sysex message, and end_sysex() will be - * called when an EOX or non-real-time status is found. - * When errors are detected, Pm_Write() returns immediately, - * so it is possible that this will drop data and leave - * sysex messages in a partially transmitted state. - */ - 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 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 */ - /* see if we can accelerate data transfer */ - if (bits == 0 && midi->fill_base && /* 4 bytes to copy */ - (*midi->fill_offset_ptr) + 4 <= midi->fill_length && - (msg & 0x80808080) == 0) { /* all data */ - /* copy 4 bytes from msg to fill_base + fill_offset */ - unsigned char *ptr = midi->fill_base + - *(midi->fill_offset_ptr); - ptr[0] = msg; ptr[1] = msg >> 8; - ptr[2] = msg >> 18; ptr[3] = msg >> 24; - (*midi->fill_offset_ptr) += 4; - continue; - } - /* no acceleration, so do byte-by-byte copying */ - 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) { - err = pm_end_sysex(midi); - if (err != pmNoError) goto error_exit; - 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 */ - if (!midi->sysex_in_progress) - err = (*midi->dictionary->write_flush)(midi, 0); -pm_write_error: - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } -error_exit: - 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]; - int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */ - PmInternal *midi = (PmInternal *) stream; - /* 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 (*msg++ == MIDI_EOX) break; - if (shift == 32) { - shift = 0; - bufx++; - if (bufx == buffer_size) { - PmError err = Pm_Write(stream, buffer, buffer_size); - /* note: Pm_Write has already called errmsg() */ - if (err) return err; - /* prepare to fill another buffer */ - bufx = 0; - buffer_size = BUFLEN; - /* optimization: maybe we can just copy bytes */ - if (midi->fill_base) { - PmError err; - while (*(midi->fill_offset_ptr) < midi->fill_length) { - midi->fill_base[(*midi->fill_offset_ptr)++] = *msg; - if (*msg++ == MIDI_EOX) { - err = pm_end_sysex(midi); - if (err != pmNoError) return pm_errmsg(err); - goto end_of_sysex; - } - } - /* I thought that I could do a pm_Write here and - * change this if to a loop, avoiding calls in Pm_Write - * to the slower write_byte, but since - * sysex_in_progress is true, this will not flush - * the buffer, and we'll infinite loop: */ - /* err = Pm_Write(stream, buffer, 0); - if (err) return err; */ - /* instead, the way this works is that Pm_Write calls - * write_byte on 4 bytes. The first, since the buffer - * is full, will flush the buffer and allocate a new - * one. This primes the buffer so - * that we can return to the loop above and fill it - * efficiently without a lot of function calls. - */ - buffer_size = 1; /* get another message started */ - } - } - buffer[bufx].message = 0; - buffer[bufx].timestamp = when; - } - /* keep inserting bytes until you find MIDI_EOX */ - } -end_of_sysex: - /* 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 */ - PmError err = Pm_Write(stream, buffer, bufx); - if (err) return err; - } - 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 */ - midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent)); - if (!midi->queue) { - /* free portMidi data */ - *stream = NULL; - pm_free(midi); - err = pmInsufficientMemory; - goto error_return; - } - midi->buffer_len = bufferSize; /* portMidi input storage */ - midi->latency = 0; /* not used */ - 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; - midi->fill_base = NULL; - midi->fill_offset_ptr = NULL; - midi->fill_length = 0; - 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_QueueDestroy(midi->queue); - pm_free(midi); - } else { - /* portMidi input open successful */ - descriptors[inputDevice].pub.opened = TRUE; - } -error_return: - /* note: if there is a pmHostError, it is the responsibility - * of the system-dependent code (*midi->dictionary->open)() - * to set pm_hosterror and pm_hosterror_text - */ - 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 = pmInvalidDeviceId; - else if (descriptors[outputDevice].pub.opened) - err = pmInvalidDeviceId; - 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; - midi->buffer_len = bufferSize; - midi->queue = NULL; /* 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->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; - midi->sync_time = 0; - midi->first_message = TRUE; - midi->dictionary = descriptors[outputDevice].dictionary; - midi->fill_base = NULL; - midi->fill_offset_ptr = NULL; - midi->fill_length = 0; - 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: - /* note: system-dependent code must set pm_hosterror and - * pm_hosterror_text if a pmHostError occurs - */ - 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; - - pm_hosterror = FALSE; - /* 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; - if (midi->queue) Pm_QueueDestroy(midi->queue); - pm_free(midi); -error_return: - /* system dependent code must set pm_hosterror and - * pm_hosterror_text if a pmHostError occurs. - */ - 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); - - if (err == pmHostError) { - midi->dictionary->host_error(midi, pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - pm_hosterror = TRUE; - } - return pm_errmsg(err); -} - - - -/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */ -#define pm_channel_filtered(status, mask) \ - ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask)))) - - -/* 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. - */ -#define pm_realtime_filtered(status, filters) \ - ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (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. - */ -#define pm_status_filtered(status, filters) ((1 << (16 + ((status) >> 4))) & (filters)) - - -/* - 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)); - -} -*/ - -static 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; - /* copied from pm_read_short, avoids filtering */ - if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { - midi->sysex_in_progress = FALSE; - } - midi->sysex_message_count = 0; - midi->sysex_message = 0; -} - - -/* pm_read_short and pm_read_bytes - 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 buffer of sysex bytes should be reported by calling pm_read_bytes - (which sets midi->sysex_in_progress). After the eox byte, - pm_read_bytes will clear sysex_in_progress - */ - -/* 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. - */ -void pm_read_short(PmInternal *midi, PmEvent *event) -{ - int status; - /* arg checking */ - assert(midi != NULL); - /* midi filtering is applied here */ - status = Pm_MessageStatus(event->message); - if (!pm_status_filtered(status, midi->filters) - && (!is_real_time(status) || - !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)) { - /* two choices: real-time or not. If it's real-time, then - * this should be delivered as a sysex byte because it is - * embedded in a sysex message - */ - if (is_real_time(status)) { - midi->sysex_message |= - (status << (8 * midi->sysex_message_count++)); - if (midi->sysex_message_count == 4) { - pm_flush_sysex(midi, event->timestamp); - } - } else { /* otherwise, it's not real-time. This interrupts - * a sysex message in progress */ - midi->sysex_in_progress = FALSE; - } - } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) { - midi->sysex_in_progress = FALSE; - } - } -} - -/* pm_read_bytes -- read one (partial) sysex msg from MIDI data */ -/* - * returns how many bytes processed - */ -unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, - int len, PmTimestamp timestamp) -{ - unsigned int i = 0; /* index into data */ - PmEvent event; - event.timestamp = timestamp; - assert(midi); - /* note that since buffers may not have multiples of 4 bytes, - * pm_read_bytes may be called in the middle of an outgoing - * 4-byte PortMidi message. sysex_in_progress indicates that - * a sysex has been sent but no eox. - */ - if (len == 0) return 0; /* sanity check */ - if (!midi->sysex_in_progress) { - while (i < len) { /* process all data */ - unsigned char byte = data[i++]; - if (byte == MIDI_SYSEX && - !pm_realtime_filtered(byte, midi->filters)) { - midi->sysex_in_progress = TRUE; - i--; /* back up so code below will get SYSEX byte */ - break; /* continue looping below to process msg */ - } else if (byte == MIDI_EOX) { - midi->sysex_in_progress = FALSE; - return i; /* done with one message */ - } else if (byte & MIDI_STATUS_MASK) { - /* We're getting MIDI but no sysex in progress. - * Either the SYSEX status byte was dropped or - * the message was filtered. Drop the data, but - * send any embedded realtime bytes. - */ - /* assume that this is a real-time message: - * it is an error to pass non-real-time messages - * to pm_read_bytes - */ - event.message = byte; - pm_read_short(midi, &event); - } - } /* all bytes in the buffer are processed */ - } - /* Now, isysex_in_progress) { - if (midi->sysex_message_count == 0 && i <= len - 4 && - ((event.message = (((long) data[i]) | - (((long) data[i+1]) << 8) | - (((long) data[i+2]) << 16) | - (((long) data[i+3]) << 24))) & - 0x80808080) == 0) { /* all data, no status */ - if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { - midi->sysex_in_progress = FALSE; - } - i += 4; - } else { - while (i < len) { - /* send one byte at a time */ - unsigned char byte = data[i++]; - if (is_real_time(byte) && - pm_realtime_filtered(byte, midi->filters)) { - continue; /* real-time data is filtered, so omit */ - } - midi->sysex_message |= - (byte << (8 * midi->sysex_message_count++)); - if (byte == MIDI_EOX) { - midi->sysex_in_progress = FALSE; - pm_flush_sysex(midi, event.timestamp); - return i; - } else if (midi->sysex_message_count == 4) { - pm_flush_sysex(midi, event.timestamp); - /* after handling at least one non-data byte - * and reaching a 4-byte message boundary, - * resume trying to send 4 at a time in outer loop - */ - break; - } - } - } - } - return i; -} - - +/* portmidi.c -- cross-platform MIDI I/O library */ +/* see license.txt for license */ + +#include "stdlib.h" +#include "string.h" +#include "portmidi.h" +#include "porttime.h" +#include "pmutil.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) + +/* these are not static so that (possibly) some system-dependent code + * could override the portmidi.c default which is to use the first + * device added using pm_add_device() + */ +PmDeviceID pm_default_input_device_id = -1; +PmDeviceID pm_default_output_device_id = -1; + +/* this is not static so that pm_init can set it directly + * (see pmmac.c:pm_init()) + */ +int pm_initialized = FALSE; + +int pm_hosterror; /* boolean */ + +/* if PM_CHECK_ERRORS is enabled, but the caller wants to + * handle an error condition, declare this as extern and + * set to FALSE (this override is provided specifically + * for the test program virttest.c, where pmNameConflict + * is expected in a call to Pm_CreateVirtualInput()): + */ +int pm_check_errors = TRUE; + +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..."); + char *rslt = fgets(line, STRING_MAX, stdin); + /* this will clean up open ports: */ + exit(-1); +} + +static PmError pm_errmsg(PmError err) +{ + if (!pm_check_errors) { /* see pm_check_errors declaration above */ + ; + } else 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 + + +int pm_midi_length(PmMessage msg) +{ + int status, high, low; + static int high_lengths[] = { + 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */ + 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */ + }; + static int low_lengths[] = { + 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */ + 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */ + }; + + status = msg & 0xFF; + high = status >> 4; + low = status & 15; + + return (high != 0xF) ? high_lengths[high] : low_lengths[low]; +} + + +/* +==================================================================== +system implementation of portmidi interface +==================================================================== +*/ + +int pm_descriptor_max = 0; +int pm_descriptor_len = 0; +descriptor_type pm_descriptors = NULL; + +/* interface pm_descriptors are simple: an array of string/fnptr pairs: */ +#define MAX_INTERF 4 +static struct { + const char *interf; + pm_create_fn create_fn; + pm_delete_fn delete_fn; +} pm_interf_list[MAX_INTERF]; + +static int pm_interf_list_len = 0; + + +/* pm_add_interf -- describe an interface to library + * + * This is called at initialization time, once for each + * supported interface (e.g., CoreMIDI). The strings + * are retained but NOT COPIED, so do not destroy them! + * + * The purpose is to register functions that create/delete + * a virtual input or output device. + * + * returns pmInsufficientMemor if interface memory is + * exceeded, otherwise returns pmNoError. + */ +PmError pm_add_interf(char *interf, pm_create_fn create_fn, + pm_delete_fn delete_fn) +{ + if (pm_interf_list_len >= MAX_INTERF) { + return pmInsufficientMemory; + } + pm_interf_list[pm_interf_list_len].interf = interf; + pm_interf_list[pm_interf_list_len].create_fn = create_fn; + pm_interf_list[pm_interf_list_len].delete_fn = delete_fn; + pm_interf_list_len++; + return pmNoError; +} + + +PmError pm_create_virtual(PmInternal *midi, int is_input, const char *interf, + const char *name, void *device_info) +{ + int i; + if (pm_interf_list_len == 0) { + return pmNotImplemented; + } + if (!interf) { + /* default interface is the first one */ + interf = pm_interf_list[0].interf; + } + for (i = 0; i < pm_interf_list_len; i++) { + if (strcmp(pm_interf_list[i].interf, + interf) == 0) { + int id = (*pm_interf_list[i].create_fn)(is_input, name, + device_info); + pm_descriptors[id].pub.is_virtual = TRUE; + return id; + } + } + return pmInterfaceNotSupported; +} + + +/* 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). + * This is also called when user creates a virtual device. + * + * Normally, increasing integer indices are returned. If the device + * is virtual, a linear search is performed to ensure that the name + * is unique. If the name is already taken, the call will fail and + * no device is added. + * + * interf is assumed to be static memory, so it is NOT COPIED and + * NOT FREED. + * name is owned by caller, COPIED if needed, and FREED by PortMidi. + * Caller is resposible for freeing name when pm_add_device returns. + * + * returns pmInvalidDeviceId if device memory is exceeded or a virtual + * device would take the name of an existing device. + * otherwise returns index (portmidi device_id) of the added device + */ +PmError pm_add_device(char *interf, const char *name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary) { + /* printf("pm_add_device: %s %s %d %p %p\n", + interf, name, is_input, descriptor, dictionary); */ + int device_id; + PmDeviceInfo *d; + /* if virtual, search for duplicate name or unused ID; otherwise, + * just add a new device at the next integer available: + */ + for (device_id = (is_virtual ? 0 : pm_descriptor_len); + device_id < pm_descriptor_len; device_id++) { + d = &pm_descriptors[device_id].pub; + d->structVersion = PM_DEVICEINFO_VERS; + if (strcmp(d->interf, interf) == 0 && strcmp(d->name, name) == 0) { + /* only reuse a name if it is a deleted virtual device with + * a matching direction (input or output) */ + if (pm_descriptors[device_id].deleted && is_input == d->input) { + /* here, we know d->is_virtual because only virtual devices + * can be deleted, and we know is_virtual because we are + * in this loop. + */ + pm_free((void *) d->name); /* reuse this device entry */ + d->name = NULL; + break; + /* name conflict exists if the new device appears to others as + * the same direction (input or output) as the existing device. + * Note that virtual inputs appear to others as outputs and + * vice versa. + * The direction of the new virtual device to others is "output" + * if is_input, i.e., virtual inputs appear to others as outputs. + * The existing device appears to others as "output" if + * (d->is_virtual == d->input) by the same logic. + * The compare will detect if device directions are the same: + */ + } else if (is_input == (d->is_virtual == d->input)) { + return pmNameConflict; + } + } + } + if (device_id >= pm_descriptor_max) { + // expand pm_descriptors + descriptor_type new_descriptors = (descriptor_type) + pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32)); + if (!new_descriptors) return pmInsufficientMemory; + if (pm_descriptors) { + memcpy(new_descriptors, pm_descriptors, + sizeof(descriptor_node) * pm_descriptor_max); + pm_free(pm_descriptors); + } + pm_descriptor_max += 32; + pm_descriptors = new_descriptors; + } + if (device_id == pm_descriptor_len) { + pm_descriptor_len++; /* extending array of pm_descriptors */ + } + d = &pm_descriptors[device_id].pub; + d->interf = interf; + d->name = pm_alloc(strlen(name) + 1); + if (!d->name) { + return pmInsufficientMemory; + } +#if defined(WIN32) && !defined(_WIN32) +#pragma warning(suppress: 4996) // don't use suggested strncpy_s +#endif + strcpy(d->name, name); + d->input = is_input; + d->output = !is_input; + d->is_virtual = FALSE; /* caller should set to TRUE if this is virtual */ + + /* default state: nothing to close (for automatic device closing) */ + d->opened = FALSE; + + pm_descriptors[device_id].deleted = FALSE; + + /* ID number passed to win32 multimedia API open */ + pm_descriptors[device_id].descriptor = descriptor; + + /* points to PmInternal, allows automatic device closing */ + pm_descriptors[device_id].pm_internal = NULL; + + pm_descriptors[device_id].dictionary = dictionary; + + /* set the defaults to the first input and output we see */ + if (is_input && pm_default_input_device_id == -1) { + pm_default_input_device_id = device_id; + } else if (!is_input && pm_default_output_device_id == -1) { + pm_default_output_device_id = device_id; + } + + return device_id; +} + + +/* Undo a successful call to pm_add_device(). If a new device was + * allocated, it must be the last device in pm_descriptors, so it is + * easy to delete by decrementing the length of pm_descriptors, but + * first free the name (which was copied to the heap). Otherwise, + * the device must be a virtual device that was created previously + * and is in the interior of the array of pm_descriptors. Leave it, + * but mark it as deleted. + */ +void pm_undo_add_device(int id) +{ + /* Clear some fields (not all are strictly necessary) */ + pm_descriptors[id].deleted = TRUE; + pm_descriptors[id].descriptor = NULL; + pm_descriptors[id].pm_internal = NULL; + + if (id == pm_descriptor_len - 1) { + pm_free(pm_descriptors[id].pub.name); + pm_descriptor_len--; + } +} + + +/* utility to look up device, given a pattern, + note: pattern is modified + */ +int Pm_FindDevice(char *pattern, int is_input) +{ + int id = pmNoDevice; + int i; + /* first parse pattern into name, interf parts */ + char *interf_pref = ""; /* initially assume it is not there */ + char *name_pref = strstr(pattern, ", "); + + if (name_pref) { /* found separator, adjust the pointer */ + interf_pref = pattern; + name_pref[0] = 0; + name_pref += 2; + } else { + name_pref = pattern; /* whole string is the name pattern */ + } + for (i = 0; i < pm_descriptor_len; i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info->input == is_input && + strstr(info->name, name_pref) && + strstr(info->interf, interf_pref)) { + id = i; + break; + } + } + return id; +} + + +/* +==================================================================== +portmidi implementation +==================================================================== +*/ + +PMEXPORT int Pm_CountDevices(void) +{ + Pm_Initialize(); + /* no error checking -- Pm_Initialize() does not fail */ + return pm_descriptor_len; +} + + +PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id) +{ + Pm_Initialize(); /* no error check needed */ + if (id >= 0 && id < pm_descriptor_len && !pm_descriptors[id].deleted) { + return &pm_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; +} + +/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */ +PmError pm_fail_timestamp_fn(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 unsigned int none_check_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_check_host_error, +}; + + +PMEXPORT 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"; + break; + case pmBufferMaxSize: + msg = "PortMidi: Buffer cannot be made larger"; + break; + case pmNotImplemented: + msg = "PortMidi: Function is not implemented"; + break; + case pmInterfaceNotSupported: + msg = "PortMidi: Interface not supported"; + break; + case pmNameConflict: + msg = "PortMidi: Cannot create virtual device: name is taken"; + break; + case pmDeviceRemoved: + msg = "PortMidi: Output attempted after (USB) device removed"; + break; + default: + msg = "PortMidi: Illegal error number"; + break; + } + return msg; +} + + +/* This can be called whenever you get a pmHostError return value + * or TRUE from Pm_HasHostError(). + * The error will always be in the global pm_hosterror_text. + */ +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len) +{ + assert(msg); + assert(len > 0); + if (pm_hosterror) { +#if defined(WIN32) && !defined(_WIN32) +#pragma warning(suppress: 4996) // don't use suggested strncpy_s +#endif + 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 */ + } +} + + +PMEXPORT int Pm_HasHostError(PortMidiStream * stream) +{ + if (pm_hosterror) return TRUE; + if (stream) { + PmInternal * midi = (PmInternal *) stream; + return (*midi->dictionary->check_host_error)(midi); + } + return FALSE; +} + + +PMEXPORT PmError Pm_Initialize(void) +{ + if (!pm_initialized) { + pm_descriptor_len = 0; + pm_interf_list_len = 0; + pm_hosterror = FALSE; + pm_hosterror_text[0] = 0; /* the null string */ + pm_init(); + pm_initialized = TRUE; + } + return pmNoError; +} + + +PMEXPORT PmError Pm_Terminate(void) +{ + if (pm_initialized) { + pm_term(); + /* if there are no devices, pm_descriptors might still be NULL */ + if (pm_descriptors != NULL) { + int i; /* free names copied into pm_descriptors */ + for (i = 0; i < pm_descriptor_len; i++) { + if (pm_descriptors[i].pub.name) { + pm_free(pm_descriptors[i].pub.name); + } + } + pm_free(pm_descriptors); + pm_descriptors = NULL; + } + pm_descriptor_len = 0; + pm_descriptor_max = 0; + pm_interf_list_len = 0; + pm_initialized = FALSE; + } + return pmNoError; +} + + +/* Pm_Read -- read up to length messages from source into buffer */ +/* + * returns number of messages actually read, or error code + */ +PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length) +{ + PmInternal *midi = (PmInternal *) stream; + int n = 0; + PmError err = pmNoError; + pm_hosterror = FALSE; + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if(!pm_descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if(!pm_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) { + if (err == pmHostError) { + midi->dictionary->check_host_error(midi); + } + return pm_errmsg(err); + } + + while (n < length) { + err = Pm_Dequeue(midi->queue, buffer++); + if (err == pmBufferOverflow) { + /* ignore the data we have retreived so far */ + return pm_errmsg(pmBufferOverflow); + } else if (err == 0) { /* empty queue */ + break; + } + n++; + } + return n; +} + +PMEXPORT PmError Pm_Poll(PortMidiStream *stream) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + + pm_hosterror = FALSE; + /* arg checking */ + if(midi == NULL) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.input) + err = pmBadPtr; + else + err = (*(midi->dictionary->poll))(midi); + + if (err != pmNoError) { + return pm_errmsg(err); + } + + return (PmError) !Pm_QueueEmpty(midi->queue); +} + + +/* this is called from Pm_Write and Pm_WriteSysEx to issue a + * call to the system-dependent end_sysex function and handle + * the error return + */ +static PmError pm_end_sysex(PmInternal *midi) +{ + PmError err = (*midi->dictionary->end_sysex)(midi, 0); + midi->sysex_in_progress = FALSE; + return err; +} + + +/* 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 */ + +PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer, + int32_t length) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + int i; + int bits; + + pm_hosterror = FALSE; + /* arg checking */ + if (midi == NULL) { + err = pmBadPtr; + } else { + descriptor_type desc = &pm_descriptors[midi->device_id]; + if (!desc || !desc->pub.opened || + !desc->pub.output || !desc->pm_internal) { + err = pmBadPtr; + } else if (desc->pm_internal->is_removed) { + err = pmDeviceRemoved; + } + } + 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; + } + } + /* error recovery: when a sysex is detected, we call + * dictionary->begin_sysex() followed by calls to + * dictionary->write_byte() and dictionary->write_realtime() + * until an end-of-sysex is detected, when we call + * dictionary->end_sysex(). After an error occurs, + * Pm_Write() continues to call functions. For example, + * it will continue to call write_byte() even after + * an error sending a sysex message, and end_sysex() will be + * called when an EOX or non-real-time status is found. + * When errors are detected, Pm_Write() returns immediately, + * so it is possible that this will drop data and leave + * sysex messages in a partially transmitted state. + */ + for (i = 0; i < length; i++) { + uint32_t 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 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 */ + /* see if we can accelerate data transfer */ + if (bits == 0 && midi->fill_base && /* 4 bytes to copy */ + (*midi->fill_offset_ptr) + 4 <= midi->fill_length && + (msg & 0x80808080) == 0) { /* all data */ + /* copy 4 bytes from msg to fill_base + fill_offset */ + unsigned char *ptr = midi->fill_base + + *(midi->fill_offset_ptr); + ptr[0] = msg; ptr[1] = msg >> 8; + ptr[2] = msg >> 16; ptr[3] = msg >> 24; + (*midi->fill_offset_ptr) += 4; + continue; + } + /* no acceleration, so do byte-by-byte copying */ + 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) { + err = pm_end_sysex(midi); + if (err != pmNoError) goto error_exit; + 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 */ + if (!midi->sysex_in_progress) + err = (*midi->dictionary->write_flush)(midi, 0); +pm_write_error: + if (err == pmHostError) { + midi->dictionary->check_host_error(midi); + } +error_exit: + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, + PmMessage msg) +{ + PmEvent event; + + event.timestamp = when; + event.message = msg; + return Pm_Write(stream, &event, 1); +} + + +PMEXPORT 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 ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))) + PmEvent buffer[BUFLEN]; + int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + /* 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 (*msg++ == MIDI_EOX) break; + if (shift == 32) { + shift = 0; + bufx++; + if (bufx == buffer_size) { + err = Pm_Write(stream, buffer, buffer_size); + /* note: Pm_Write has already called errmsg() */ + if (err) return err; + /* prepare to fill another buffer */ + bufx = 0; + buffer_size = BUFLEN; + /* optimization: maybe we can just copy bytes */ + if (midi->fill_base) { + while (*(midi->fill_offset_ptr) < midi->fill_length) { + midi->fill_base[(*midi->fill_offset_ptr)++] = *msg; + if (*msg++ == MIDI_EOX) { + err = pm_end_sysex(midi); + if (err != pmNoError) return pm_errmsg(err); + goto end_of_sysex; + } + } + /* I thought that I could do a pm_Write here and + * change this if to a loop, avoiding calls in Pm_Write + * to the slower write_byte, but since + * sysex_in_progress is true, this will not flush + * the buffer, and we'll infinite loop: */ + /* err = Pm_Write(stream, buffer, 0); + if (err) return err; */ + /* instead, the way this works is that Pm_Write calls + * write_byte on 4 bytes. The first, since the buffer + * is full, will flush the buffer and allocate a new + * one. This primes the buffer so + * that we can return to the loop above and fill it + * efficiently without a lot of function calls. + */ + buffer_size = 1; /* get another message started */ + } + } + buffer[bufx].message = 0; + buffer[bufx].timestamp = when; + } + /* keep inserting bytes until you find MIDI_EOX */ + } +end_of_sysex: + /* 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 */ + err = Pm_Write(stream, buffer, bufx); + if (err) return err; + } + return pmNoError; +} + + + +PmError pm_create_internal(PmInternal **stream, PmDeviceID device_id, + int is_input, int latency, PmTimeProcPtr time_proc, + void *time_info, int buffer_size) +{ + PmInternal *midi; + if (device_id < 0 || device_id >= pm_descriptor_len) { + return pmInvalidDeviceId; + } + if (latency < 0) { /* force a legal value */ + latency = 0; + } + /* create portMidi internal data */ + midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); + *stream = midi; + if (!midi) { + return pmInsufficientMemory; + } + midi->device_id = device_id; + midi->is_input = is_input; + midi->is_removed = FALSE; + midi->time_proc = time_proc; + /* if latency != 0, we need a time reference for output. + we always need a time reference for input. + If none is provided, use PortTime library */ + if (time_proc == NULL && (latency != 0 || is_input)) { + 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; + if (is_input) { + midi->latency = 0; /* unused by input */ + if (buffer_size <= 0) buffer_size = 256; /* default buffer size */ + midi->queue = Pm_QueueCreate(buffer_size, (int32_t) sizeof(PmEvent)); + if (!midi->queue) { + /* free portMidi data */ + *stream = NULL; + pm_free(midi); + return pmInsufficientMemory; + } + } else { + /* 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->queue = NULL; /* unused by output; input needs to allocate: */ + } + midi->buffer_len = buffer_size; /* portMidi input storage */ + midi->sysex_in_progress = FALSE; + midi->message = 0; + midi->message_count = 0; + midi->filters = (is_input ? PM_FILT_ACTIVE : 0); + midi->channel_mask = 0xFFFF; + midi->sync_time = 0; + midi->first_message = TRUE; + midi->api_info = NULL; + midi->fill_base = NULL; + midi->fill_offset_ptr = NULL; + midi->fill_length = 0; + midi->dictionary = pm_descriptors[device_id].dictionary; + pm_descriptors[device_id].pm_internal = midi; + return pmNoError; +} + + +PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info) +{ + PmInternal *midi; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; /* invariant: *stream == midi */ + + /* arg checking */ + if (!pm_descriptors[inputDevice].pub.input) + err = pmInvalidDeviceId; + else if (pm_descriptors[inputDevice].pub.opened) + err = pmInvalidDeviceId; + if (err != pmNoError) + goto error_return; + + /* common initialization of PmInternal structure (midi): */ + err = pm_create_internal(&midi, inputDevice, TRUE, 0, time_proc, + time_info, bufferSize); + *stream = midi; + if (err) { + goto error_return; + } + + /* open system dependent input device */ + err = (*midi->dictionary->open)(midi, inputDriverInfo); + if (err) { + *stream = NULL; + pm_descriptors[inputDevice].pm_internal = NULL; + /* free portMidi data */ + Pm_QueueDestroy(midi->queue); + pm_free(midi); + } else { + /* portMidi input open successful */ + pm_descriptors[inputDevice].pub.opened = TRUE; + } +error_return: + /* note: if there is a pmHostError, it is the responsibility + * of the system-dependent code (*midi->dictionary->open)() + * to set pm_hosterror and pm_hosterror_text + */ + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, + PmDeviceID outputDevice, + void *outputDriverInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + int32_t latency) +{ + PmInternal *midi; + PmError err = pmNoError; + pm_hosterror = FALSE; + *stream = NULL; + + /* arg checking */ + if (outputDevice < 0 || outputDevice >= pm_descriptor_len) + err = pmInvalidDeviceId; + else if (!pm_descriptors[outputDevice].pub.output) + err = pmInvalidDeviceId; + else if (pm_descriptors[outputDevice].pub.opened) + err = pmInvalidDeviceId; + if (err != pmNoError) + goto error_return; + + /* common initialization of PmInternal structure (midi): */ + err = pm_create_internal(&midi, outputDevice, FALSE, latency, time_proc, + time_info, bufferSize); + *stream = midi; + if (err) { + goto error_return; + } + + /* open system dependent output device */ + err = (*midi->dictionary->open)(midi, outputDriverInfo); + if (err) { + *stream = NULL; + pm_descriptors[outputDevice].pm_internal = NULL; + /* free portMidi data */ + pm_free(midi); + } else { + /* portMidi input open successful */ + pm_descriptors[outputDevice].pub.opened = TRUE; + } +error_return: + /* note: system-dependent code must set pm_hosterror and + * pm_hosterror_text if a pmHostError occurs + */ + return pm_errmsg(err); +} + + +static PmError create_virtual_device(const char *name, const char *interf, + void *device_info, int is_input) +{ + PmError err = pmNoError; + int i; + pm_hosterror = FALSE; + + /* arg checking */ + if (!name) { + err = pmInvalidDeviceId; + goto error_return; + } + + Pm_Initialize(); /* just in case */ + + /* create the virtual device */ + if (pm_interf_list_len == 0) { + return pmNotImplemented; + } + if (!interf) { + /* default interface is the first one */ + interf = pm_interf_list[0].interf; + } + /* look up and call the create_fn for interf(ace), e.g. "CoreMIDI" */ + for (i = 0; i < pm_interf_list_len; i++) { + if (strcmp(pm_interf_list[i].interf, interf) == 0) { + int id = (*pm_interf_list[i].create_fn)(is_input, + name, device_info); + /* id could be pmNameConflict or an actual descriptor index */ + if (id >= 0) { + pm_descriptors[id].pub.is_virtual = TRUE; + } + err = id; + goto error_return; + } + } + err = pmInterfaceNotSupported; + +error_return: + /* note: if there is a pmHostError, it is the responsibility + * of the system-dependent code (*midi->dictionary->open)() + * to set pm_hosterror and pm_hosterror_text + */ + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_CreateVirtualInput(const char *name, + const char *interf, + void *deviceInfo) +{ + return create_virtual_device(name, interf, deviceInfo, TRUE); +} + +PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, const char *interf, + void *deviceInfo) +{ + return create_virtual_device(name, interf, deviceInfo, FALSE); +} + +PmError Pm_DeleteVirtualDevice(PmDeviceID id) +{ + int i; + const char *interf = pm_descriptors[id].pub.interf; + PmError err = pmBadData; /* returned if we cannot find the interface- + * specific delete function */ + /* arg checking */ + if (id < 0 || id >= pm_descriptor_len || + pm_descriptors[id].pub.opened || pm_descriptors[id].deleted) { + return pmInvalidDeviceId; + } + /* delete function pointer is in interfaces list */ + for (i = 0; i < pm_interf_list_len; i++) { + if (strcmp(pm_interf_list[i].interf, interf) == 0) { + err = (*pm_interf_list[i].delete_fn)(id); + break; + } + } + pm_descriptors[id].deleted = TRUE; + /* (pm_internal should already be NULL because !pub.opened) */ + pm_descriptors[id].pm_internal = NULL; + pm_descriptors[id].descriptor = NULL; + return err; +} + +PMEXPORT 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); +} + + +PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + /* arg checking */ + if (midi == NULL) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + midi->filters = filters; + return pm_errmsg(err); +} + + +PMEXPORT PmError Pm_Close(PortMidiStream *stream) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + + pm_hosterror = FALSE; + /* 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_len) + err = pmBadPtr; + /* and the device should be in the opened state */ + else if (!pm_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 */ + pm_descriptors[midi->device_id].pm_internal = NULL; + pm_descriptors[midi->device_id].pub.opened = FALSE; + if (midi->queue) Pm_QueueDestroy(midi->queue); + pm_free(midi); +error_return: + /* system dependent code must set pm_hosterror and + * pm_hosterror_text if a pmHostError occurs. + */ + return pm_errmsg(err); +} + +PmError Pm_Synchronize(PortMidiStream* stream) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err = pmNoError; + if (midi == NULL) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + midi->first_message = TRUE; + return err; +} + +PMEXPORT PmError Pm_Abort(PortMidiStream* stream) +{ + PmInternal *midi = (PmInternal *) stream; + PmError err; + /* arg checking */ + if (midi == NULL) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.output) + err = pmBadPtr; + else if (!pm_descriptors[midi->device_id].pub.opened) + err = pmBadPtr; + else + err = (*midi->dictionary->abort)(midi); + + if (err == pmHostError) { + midi->dictionary->check_host_error(midi); + } + return pm_errmsg(err); +} + + + +/* pm_channel_filtered returns non-zero if the channel mask is + blocking the current channel */ +#define pm_channel_filtered(status, mask) \ + ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask)))) + + +/* 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. + */ +#define pm_realtime_filtered(status, filters) \ + ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (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. + */ +#define pm_status_filtered(status, filters) \ + ((1 << (16 + ((status) >> 4))) & (filters)) + + +/* + 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)); + +} +*/ + +static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp) +{ + PmEvent event; + + /* there may be nothing in the buffer */ + if (midi->message_count == 0) return; /* nothing to flush */ + + event.message = midi->message; + event.timestamp = timestamp; + /* copied from pm_read_short, avoids filtering */ + if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { + midi->sysex_in_progress = FALSE; + } + midi->message_count = 0; + midi->message = 0; +} + + +/* pm_read_short and pm_read_bytes + 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) each buffer of sysex bytes should be reported by calling pm_read_bytes + (which sets midi->sysex_in_progress). After the eox byte, + pm_read_bytes will clear sysex_in_progress + */ + +/* 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. + */ +void pm_read_short(PmInternal *midi, PmEvent *event) +{ + int status; + /* arg checking */ + assert(midi != NULL); + /* midi filtering is applied here */ + status = Pm_MessageStatus(event->message); + if (!pm_status_filtered(status, midi->filters) + && (!is_real_time(status) || + !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)) { + /* two choices: real-time or not. If it's real-time, then + * this should be delivered as a sysex byte because it is + * embedded in a sysex message + */ + if (is_real_time(status)) { + midi->message |= (status << (8 * midi->message_count++)); + if (midi->message_count == 4) { + pm_flush_sysex(midi, event->timestamp); + } + } else { /* otherwise, it's not real-time. This interrupts + * a sysex message in progress */ + midi->sysex_in_progress = FALSE; + } + } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) { + midi->sysex_in_progress = FALSE; + } + } +} + + +/* pm_read_bytes -- a sequence of bytes has been read from a device. + * parse the bytes into PmEvents and put them in the queue. + * midi - the midi device + * data - the bytes + * len - the number of bytes + * timestamp - when were the bytes received? + * + * returns how many bytes processed, which is always the len parameter + */ +unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, + int len, PmTimestamp timestamp) +{ + int i = 0; /* index into data, must not be unsigned (!) */ + PmEvent event; + event.timestamp = timestamp; + assert(midi); + + /* Since sysex messages may have embedded real-time messages, we + * cannot simply send every consecutive group of 4 bytes as sysex + * data. Instead, we insert each data byte into midi->message and + * keep count using midi->message_count. If we encounter a + * real-time message, it is sent immediately as a 1-byte PmEvent, + * while sysex bytes are sent as PmEvents in groups of 4 bytes + * until the sysex is either terminated by EOX (F7) or a + * non-real-time message is encountered, indicating that the EOX + * was dropped. + */ + + /* This is a finite state machine so that we can accept any number + * of bytes, even if they contain partial messages. + * + * midi->sysex_in_progress says we are expecting sysex message bytes + * (otherwise, expect a short message or real-time message) + * midi->message accumulates bytes to enqueue for application + * midi->message_count is the number of bytes accumulated + * midi->short_message_count is how many bytes we need in midi->message, + * therefore midi->message_count, before we have a complete message + * midi->running_status is running status or 0 if there is none + * + * Set running status when: A status byte < F0 is received. + * Clear running status when: A status byte from F0 through F7 is + * received. + * Ignore (drop) data bytes when running status is 0. + * + * Our output buffer (the application input buffer) can overflow + * at any time. If that occurs, we have to clear sysex_in_progress + * (otherwise, the buffer could be flushed and we could resume + * inserting sysex bytes into the buffer, resulting in a continuation + * of a sysex message even though a buffer full of bytes was dropped.) + * + * Since we have to parse everything and form <=4-byte PmMessages, + * we send all messages via pm_read_short, which filters messages + * according to midi->filters and clears sysex_in_progress on + * buffer overflow. This also provides a "short cut" for short + * messages that are already parsed, allowing API-specific code + * to bypass this more expensive state machine. What if we are + * getting a sysex message, but it is interrupted by a short + * message (status 80-EF) and a direct call to pm_read_short? + * Without some care, the state machine would still be in + * sysex_in_progress mode, and subsequent data bytes would be + * accumulated as more sysex data, which is wrong since you + * cannot have a short message in the middle of a sysex message. + * To avoid this problem, pm_read_short clears sysex_in_progress + * when a non-real-time short message arrives. + */ + + while (i < len) { + unsigned char byte = data[i++]; + if (is_real_time(byte)) { + event.message = byte; + pm_read_short(midi, &event); + } else if (byte & MIDI_STATUS_MASK && byte != MIDI_EOX) { + midi->message = byte; + midi->message_count = 1; + if (byte == MIDI_SYSEX) { + midi->sysex_in_progress = TRUE; + } else { + midi->sysex_in_progress = FALSE; + midi->short_message_count = pm_midi_length(midi->message); + /* maybe we're done already with a 1-byte message: */ + if (midi->short_message_count == 1) { + pm_read_short(midi, &event); + midi->message_count = 0; + } + } + } else if (midi->sysex_in_progress) { /* sysex data byte */ + /* accumulate sysex message data or EOX */ + midi->message |= (byte << (8 * midi->message_count++)); + if (midi->message_count == 4 || byte == MIDI_EOX) { + event.message = midi->message; + /* enqueue if not filtered, and then if there is overflow, + stop sysex_in_progress */ + if (!(midi->filters & PM_FILT_SYSEX) && + Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) { + midi->sysex_in_progress = FALSE; + } else if (byte == MIDI_EOX) { /* continue unless EOX */ + midi->sysex_in_progress = FALSE; + } + midi->message_count = 0; + midi->message = 0; + } + } else { /* no sysex in progress, must be short message */ + if (midi->message_count == 0) { /* need a running status */ + if (midi->running_status) { + midi->message = midi->running_status; + midi->message_count = 1; + } else { /* drop data byte - not sysex and no status byte */ + continue; + } + } + midi->message |= (byte << (8 * midi->message_count++)); + if (midi->message_count == midi->short_message_count) { + event.message = midi->message; + pm_read_short(midi, &event); + } + } + } + return i; +} diff --git a/portmidi/pm_common/portmidi.h b/portmidi/pm_common/portmidi.h index f34fef6..8696a73 100644 --- a/portmidi/pm_common/portmidi.h +++ b/portmidi/pm_common/portmidi.h @@ -1,612 +1,974 @@ -#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-2006 Roger B. Dannenberg - * - * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/ - * - * Copyright (c) 1999-2000 Ross Bencina and Phil Burk - * Copyright (c) 2001-2006 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. - * - * 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. - */ - -/* - * The text above constitutes the entire PortMidi license; however, - * the PortMusic community also makes the following non-binding requests: - * - * 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. It is also - * requested that these non-binding requests be included along with the - * license above. - */ - -/* CHANGELOG FOR PORTMIDI - * (see ../CHANGELOG.txt) - * - * 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. - * - * You can enable the DLL cleanup routine by defining USE_DLL_FOR_CLEANUP. - * Do not define this preprocessor symbol if you do not want to use this - * feature. - * - * 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. - * Both (a) and (b) are reported by the next read or write call. You can - * also query for asynchronous errors (b) at any time by calling - * Pm_HasHostError(). - * - * NOTES ON COMPILE-TIME SWITCHES - * - * DEBUG assumes stdio and a console. Use this if you want automatic, simple - * error reporting, e.g. for prototyping. If you are using MFC or some - * other graphical interface with no console, DEBUG probably should be - * undefined. - * PM_CHECK_ERRORS more-or-less takes over error checking for return values, - * stopping your program and printing error messages when an error - * occurs. This also uses stdio for console text I/O. - * USE_DLL_FOR_CLEANUP is described above. (Windows only.) - * - */ - -#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, - pmNoData = 0, /* A "no error" return that also indicates no data avail. */ - pmGotData = 1, /* A "no error" return that also indicates data available */ - pmHostError = -10000, - pmInvalidDeviceId, /* out of range or - * output device when input is requested or - * input device when output is requested or - * device is already opened - */ - pmInsufficientMemory, - pmBufferTooSmall, - pmBufferOverflow, - pmBadPtr, - pmBadData, /* illegal midi data, e.g. missing EOX */ - pmInternalError, - pmBufferMaxSize /* buffer is already as large as it can be */ - /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */ -} 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(). - Errors are also cleared by calling other functions that can return - errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). 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. Until the error is cleared, - no new error codes will be obtained, even for a different stream. -*/ -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: the 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. - - Example: If I provide a timestamp of 5000, latency is 1, and time_proc - returns 4990, then the desired output time will be when time_proc returns - timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. - - 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 (1 << 0x0E) -/* filter system exclusive messages (0xF0): */ -#define PM_FILT_SYSEX (1 << 0x00) -/* filter MIDI clock message (0xF8) */ -#define PM_FILT_CLOCK (1 << 0x08) -/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ -#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) -/* filter tick messages (0xF9) */ -#define PM_FILT_TICK (1 << 0x09) -/* filter undefined FD messages */ -#define PM_FILT_FD (1 << 0x0D) -/* filter undefined real-time messages */ -#define PM_FILT_UNDEFINED PM_FILT_FD -/* filter reset messages (0xFF) */ -#define PM_FILT_RESET (1 << 0x0F) -/* 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 | PM_FILT_TICK) -/* filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ -#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18)) -/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ -#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D) -/* per-note aftertouch (0xA0-0xAF) */ -#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) -/* 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 (1 << 0x1C) -/* Control Changes (CC's) (0xB0-0xBF)*/ -#define PM_FILT_CONTROL (1 << 0x1B) -/* Pitch Bender (0xE0-0xEF*/ -#define PM_FILT_PITCHBEND (1 << 0x1E) -/* MIDI Time Code (0xF1)*/ -#define PM_FILT_MTC (1 << 0x01) -/* Song Position (0xF2) */ -#define PM_FILT_SONG_POSITION (1 << 0x02) -/* Song Select (0xF3)*/ -#define PM_FILT_SONG_SELECT (1 << 0x03) -/* Tuning request (0xF6)*/ -#define PM_FILT_TUNE (1 << 0x06) -/* 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. See also "Example" for Pm_OpenOutput() above. - - 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. - -*/ -int 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 */ +#ifndef PORTMIDI_PORTMIDI_H +#define PORTMIDI_PORTMIDI_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * PortMidi Portable Real-Time MIDI Library + * PortMidi API Header File + * Latest version available at: http://sourceforge.net/projects/portmedia + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * Copyright (c) 2001-2006 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. + * + * 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. + */ + +/* + * The text above constitutes the entire PortMidi license; however, + * the PortMusic community also makes the following non-binding requests: + * + * 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. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/* CHANGELOG FOR PORTMIDI + * (see ../CHANGELOG.txt) + * + * 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_GetHostErrorText() to get a text string describing + * the error. Host errors can arise asynchronously from callbacks, + * * so there is no specific return code. Asynchronous errors are + * checked and reported by Pm_Poll. You can also check by calling + * Pm_HasHostError(). If this returns TRUE, Pm_GetHostErrorText() + * will return a text description of the error. + * + * NOTES ON COMPILE-TIME SWITCHES + * + * DEBUG assumes stdio and a console. Use this if you want + * automatic, simple error reporting, e.g. for prototyping. If + * you are using MFC or some other graphical interface with no + * console, DEBUG probably should be undefined. + * PM_CHECK_ERRORS more-or-less takes over error checking for + * return values, stopping your program and printing error + * messages when an error occurs. This also uses stdio for + * console text I/O. You can selectively disable this error + * checking by declaring extern int pm_check_errors; and + * setting pm_check_errors = FALSE; You can also reenable. + */ +/** + \defgroup grp_basics Basic Definitions + @{ +*/ + +#include + +#ifdef _WINDLL +#define PMEXPORT __declspec(dllexport) +#else +#define PMEXPORT +#endif + +#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, /**< Normal return value indicating no error. */ + pmNoData = 0, /**< @brief No error, also indicates no data available. + * Use this constant where a value greater than zero would + * indicate data is available. + */ + pmGotData = 1, /**< A "no error" return also indicating data available. */ + pmHostError = -10000, + pmInvalidDeviceId, /**< Out of range or + * output device when input is requested or + * input device when output is requested or + * device is already opened. + */ + pmInsufficientMemory, + pmBufferTooSmall, + pmBufferOverflow, + pmBadPtr, /**< #PortMidiStream parameter is NULL or + * stream is not opened or + * stream is output when input is required or + * stream is input when output is required. */ + pmBadData, /**< Illegal midi data, e.g., missing EOX. */ + pmInternalError, + pmBufferMaxSize, /**< Buffer is already as large as it can be. */ + pmNotImplemented, /**< The function is not implemented, nothing was done. */ + pmInterfaceNotSupported, /**< The requested interface is not supported. */ + pmNameConflict, /**< Cannot create virtual device because name is taken. */ + pmDeviceRemoved /**< Output attempted after (USB) device was removed. */ + /* NOTE: If you add a new error type, you must update Pm_GetErrorText(). */ +} PmError; /**< @brief @enum PmError PortMidi error code; a common return type. + * No error is indicated by zero; errors are indicated by < 0. + */ + +/** + Pm_Initialize() is the library initialization function - call this before + using the library. + + *NOTE:* PortMidi scans for available devices when #Pm_Initialize + is called. To observe subsequent changes in the available + devices, you must shut down PortMidi by calling #Pm_Terminate and + then restart by calling #Pm_Initialize again. *IMPORTANT*: On + MacOS, #Pm_Initialize *must* always be called on the same + thread. Otherwise, changes in the available MIDI devices will + *not* be seen by PortMidi. As an example, if you start PortMidi in + a thread for processing MIDI, do not try to rescan devices by + calling #Pm_Initialize in a GUI thread. Instead, start PortMidi + the first time and every time in the GUI thread. Alternatively, + let the GUI request a restart in the MIDI thread. (These + restrictions only apply to macOS.) Speaking of threads, on all + platforms, you are allowed to call #Pm_Initialize in one thread, + yet send MIDI or poll for incoming MIDI in another + thread. However, PortMidi is not "thread safe," which means you + cannot allow threads to call PortMidi functions concurrently. + + @return pmNoError. + + PortMidi is designed to support multiple interfaces (such as ALSA, + CoreMIDI and WinMM). It is possible to return pmNoError because there + are no supported interfaces. In that case, zero devices will be + available. +*/ +PMEXPORT PmError Pm_Initialize(void); + +/** + Pm_Terminate() is the library termination function - call this after + using the library. +*/ +PMEXPORT PmError Pm_Terminate(void); + +/** Represents an open MIDI device. */ +typedef void PortMidiStream; + +/** A shorter form of #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 by calling + Pm_GetHostErrorText(). Pm_Poll() is similar to Pm_HasHostError(), + but if there is no error, it will return TRUE (1) if there is a + pending input message. +*/ +PMEXPORT 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. +*/ +PMEXPORT 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. +*/ +PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len); + +/** Any host error msg has at most this many characters, including EOS. */ +#define PM_HOST_ERROR_MSG_LEN 256u + +/** Devices are represented as small integers. Device ids range from 0 + to Pm_CountDevices()-1. Pm_GetDeviceInfo() is used to get information + about the device, and Pm_OpenInput() and PmOpenOutput() are used to + open the device. +*/ +typedef int PmDeviceID; + +/** This PmDeviceID (constant) value represents no device and may be + returned by Pm_GetDefaultInputDeviceID() or + Pm_GetDefaultOutputDeviceID() if no default exists. +*/ +#define pmNoDevice -1 + +/** MIDI device information is returned in this structure, which is + owned by PortMidi and read-only to applications. See Pm_GetDeviceInfo(). +*/ +#define PM_DEVICEINFO_VERS 200 +typedef struct { + int structVersion; /**< @brief this internal structure version */ + const char *interf; /**< @brief underlying MIDI API, e.g. + "MMSystem" or "DirectX" */ + char *name; /**< @brief device name, e.g. "USB MidiSport 1x1" */ + int input; /**< @brief true iff input is available */ + int output; /**< @brief true iff output is available */ + int opened; /**< @brief used by generic PortMidi for error checking */ + int is_virtual; /**< @brief true iff this is/was a virtual device */ +} PmDeviceInfo; + +/** MIDI system-dependent device or driver info is passed in this + structure, which is owned by the caller. +*/ +#define PM_SYSDEPINFO_VERS 210 + +enum PmSysDepPropertyKey { + pmKeyNone = 0, /**< a "noop" key value */ + /** CoreMIDI Manufacturer name, value is string */ + pmKeyCoreMidiManufacturer = 1, + /** Linux ALSA snd_seq_port_info_set_name, value is a string. Can be + passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput when opening + a device. The created port will be named accordingly and will be + visible for externally made connections (subscriptions). (Linux ALSA + ports are always enabled for this, but only get application-specific + names if you give it one.) This key/value is ignored when opening + virtual ports, which are named when they are created.) */ + pmKeyAlsaPortName = 2, + /** Linux ALSA snd_seq_set_client_name, value is a string. + Can be passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput. + Pm_CreateVirtualInput or Pm_CreateVirtualOutput. Will override + any previously set client name and applies to all ports. */ + pmKeyAlsaClientName = 3 + /* if system-dependent code introduces more options, register + the key here to avoid conflicts. */ +}; + +/** System-dependent information can be passed when creating and opening + ports using this data structure, which stores alternating keys and + values (addresses). See `pm_test/sendvirtual.c`, `pm_test/recvvirtual.c`, + and `pm_test/testio.c` for examples. + */ +typedef struct { + int structVersion; /**< @brief this structure version */ + int length; /**< @brief number of properties in this structure */ + struct { + enum PmSysDepPropertyKey key; + const void *value; + } properties[]; +} PmSysDepInfo; + + +/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ +PMEXPORT int Pm_CountDevices(void); + +/** + Return the default device ID or pmNoDevice if there are no devices. + The result (but not pmNoDevice) can be passed to Pm_OpenMidi(). + + The use of these functions is not recommended. There is no natural + "default device" on any system, so defaults must be set by users. + (Currently, PortMidi just returns the first device it finds as + "default", so if there *is* a default, implementors should use + pm_add_device to add system default input and output devices + first.) + + The recommended solution is pass the burden to applications. It is + easy to scan devices with PortMidi and build a device menu, and to + save menu selections in application preferences for next + time. This is my recommendation for any GUI program. For simple + command-line applications and utilities, see pm_test where all the + test programs now accept device numbers on the command line and/or + prompt for their entry. + + On linux, you can create virtual ports and use an external program + to set up inter-application and device connections. + + Some advice for preferences: MIDI devices used to be built-in or + plug-in cards, so the numbers rarely changed. Now MIDI devices are + often plug-in USB devices, so device numbers change, and you + probably need to design to reinitialize PortMidi to rescan + devices. MIDI is pretty stateless, so this isn't a big problem, + although it means you cannot find a new device while playing or + recording MIDI. + + Since device numbering can change whenever a USB device is plugged + in, preferences should record *names* of devices rather than + device numbers. It is simple enough to use string matching to find + a prefered device, so PortMidi does not provide any built-in + lookup function. +*/ +PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void); + +/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */ +PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void); + +/** Find a device that matches a pattern. + + @param pattern a substring of the device name, or if the pattern + contains the two-character separator ", ", then the first part of + the pattern represents a device interface substring and the second + part after the separator represents a device name substring. + + @param is_input restricts the search to an input when true, or an + output when false. + + @return the number of the first device whose device interface + contains the interface pattern (if any), whose device name + contains the name pattern, and whose direction (input or output) + matches the #is_input parameter. If no match is found, #pmNoDevice + (-1) is returned. +*/ +PMEXPORT PmDeviceID Pm_FindDevice(char *pattern, int is_input); + + +/** Represents a millisecond clock with arbitrary start time. + This type is used for all MIDI timestamps and clocks. +*/ +typedef int32_t PmTimestamp; +typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); + +/** TRUE if t1 before t2 */ +#define PmBefore(t1,t2) (((t1)-(t2)) < 0) +/** @} */ +/** + \defgroup grp_device Input/Output Devices Handling + @{ +*/ +/** Get a PmDeviceInfo structure describing a MIDI device. + + @param id the device to be queried. + + If \p id is out of range or if the device designates a deleted + virtual device, 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(). +*/ +PMEXPORT const PmDeviceInfo *Pm_GetDeviceInfo(PmDeviceID id); + +/** Open a MIDI device for input. + + @param stream the address of a #PortMidiStream pointer which will + receive a pointer to the newly opened stream. + + @param inputDevice the ID of the device to be opened (see #PmDeviceID). + + @param inputSysDepInfo a pointer to an optional system-dependent + data structure (a #PmSysDepInfo struct) containing additional + information for device setup or handle processing. This parameter + is never required for correct operation. If not used, specify + NULL. Declared `void *` here for backward compatibility. Note that + with Linux ALSA, you can use this parameter to specify a client name + and port name. + + @param bufferSize the number of input events to be buffered + waiting to be read using Pm_Read(). Messages will be lost if the + number of unread messages exceeds this value. + + @param time_proc (address of) 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. + \p time_proc result values are appended to incoming MIDI data, + normally by mapping system-provided timestamps to the \p time_proc + timestamps to maintain the precision of system-provided + timestamps. + + @param time_info is a pointer passed to time_proc. + + @return #pmNoError and places a pointer to a valid + #PortMidiStream in the stream argument. If the open operation + fails, a nonzero error code is returned (see #PMError) and + the value of stream is invalid. + + Any stream that is successfully opened should eventually be closed + by calling Pm_Close(). +*/ +PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream, + PmDeviceID inputDevice, + void *inputSysDepInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info); + +/** Open a MIDI device for output. + + @param stream the address of a #PortMidiStream pointer which will + receive a pointer to the newly opened stream. + + @param outputDevice the ID of the device to be opened (see #PmDeviceID). + + @param inputSysDepInfo a pointer to an optional system-specific + data structure (a #PmSysDepInfo struct) containing additional + information for device setup or handle processing. This parameter + is never required for correct operation. If not used, specify + NULL. Declared `void *` here for backward compatibility. Note that + with Linux ALSA, you can use this parameter to specify a client name + and port name. + + @param bufferSize 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 \p bufferSize is ignored. Since MIDI speeds now + vary from 1 to 50 or more messages per ms (over USB), put some + thought into this number. E.g. if latency is 20ms and you want to + burst 100 messages in that time (5000 messages per second), you + should set \p bufferSize to at least 100. The default on Windows + assumes an average rate of 500 messages per second and in this + example, output would be slowed waiting for free buffers. + + @param latency 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: the 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, so the best strategy to minimize jitter + is: wait until the real time to send the message, compute the + message, attach the *ideal* output time (not the current real + time, because some time may have elapsed), and send the + message. The \p latency will be added to the timestamp, and + provided the elapsed computation time has not exceeded \p latency, + the message will be delivered according to the timestamp. If the + real time is already past the timestamp, the message will be + delivered as soon as possible. Latency may also help you to + synchronize MIDI data to audio data by matching \p latency to the + audio buffer latency. + + @param time_proc (address of) 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. \p time_proc times are used to schedule + outgoing MIDI data (when latency is non-zero), usually by mapping + from time_proc timestamps to internal system timestamps to + maintain the precision of system-supported timing. + + @param time_info a pointer passed to time_proc. + + @return #pmNoError and places a pointer to a valid #PortMidiStream + in the stream argument. If the operation fails, a nonzero error + code is returned (see PMError) and the value of \p stream is + invalid. + + Note: ALSA appears to have a fixed-size priority queue for timed + output messages. Testing indicates the queue can hold a little + over 400 3-byte MIDI messages. Thus, you can send 10,000 + messages/second if the latency is 30ms (30ms * 10000 msgs/sec * + 0.001 sec/ms = 300 msgs), but not if the latency is 50ms + (resulting in about 500 pending messages, which is greater than + the 400 message limit). Since timestamps in ALSA are relative, + they are of less value than absolute timestamps in macOS and + Windows. This is a limitation of ALSA and apparently a design + flaw. + + Example 1: If I provide a timestamp of 5000, latency is 1, and + time_proc returns 4990, then the desired output time will be when + time_proc returns timestamp+latency = 5001. This will be 5001-4990 + = 11ms from now. + + Example 2: If I want to send at exactly 5010, and latency is 10, I + should wait until 5000, compute the messages and provide a + timestamp of 5000. As long as computation takes less than 10ms, + the message will be delivered at time 5010. + + Example 3 (recommended): It is often convenient to ignore latency. + E.g. if a sequence says to output at time 5010, just wait until + 5010, compute the message and use 5010 for the timestamp. Delivery + will then be at 5010+latency, but unless you are synchronizing to + something else, the absolute delay by latency will not matter. + + Any stream that is successfully opened should eventually be closed + by calling Pm_Close(). +*/ +PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream, + PmDeviceID outputDevice, + void *outputSysDepInfo, + int32_t bufferSize, + PmTimeProcPtr time_proc, + void *time_info, + int32_t latency); + +/** Create a virtual input device. + + @param name gives the virtual device name, which is visible to + other applications. + + @param interf is the interface (System API) used to create the + device Default interfaces are "MMSystem", "CoreMIDI" and + "ALSA". Currently, these are the only ones implemented, but future + implementations could support DirectMusic, Jack, sndio, or others. + + @param sysDepInfo contains interface-dependent additional + information (a #PmSysDepInfo struct), e.g., hints or options. This + parameter is never required for correct operation. If not used, + specify NULL. Declared `void *` here for backward compatibility. + + @return a device ID or #pmNameConflict (\p name is invalid or + already exists) or #pmInterfaceNotSupported (\p interf is does not + match a supported interface). + + The created virtual device appears to other applications as if it + is an output device. The device must be opened to obtain a stream + and read from it. + + Virtual devices are not supported by Windows (Multimedia API). Calls + on Windows do nothing except return #pmNotImplemented. +*/ +PMEXPORT PmError Pm_CreateVirtualInput(const char *name, + const char *interf, + void *sysDepInfo); + +/** Create a virtual output device. + + @param name gives the virtual device name, which is visible to + other applications. + + @param interf is the interface (System API) used to create the + device Default interfaces are "MMSystem", "CoreMIDI" and + "ALSA". Currently, these are the only ones implemented, but future + implementations could support DirectMusic, Jack, sndio, or others. + + @param sysDepInfo contains interface-dependent additional + information (a #PmSysDepInfo struct), e.g., hints or options. This + parameter is never required for correct operation. If not used, + specify NULL. Declared `void *` here for backward compatibility. + + @return a device ID or #pmInvalidDeviceId (\p name is invalid or + already exists) or #pmInterfaceNotSupported (\p interf is does not + match a supported interface). + + The created virtual device appears to other applications as if it + is an input device. The device must be opened to obtain a stream + and write to it. + + Virtual devices are not supported by Windows (Multimedia API). Calls + on Windows do nothing except return #pmNotImplemented. +*/ +PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, + const char *interf, + void *sysDepInfo); + +/** Remove a virtual device. + + @param device a device ID (small integer) designating the device. + + The device is removed; other applications can no longer see or open + this virtual device, which may be either for input or output. The + device must not be open. The device ID may be reused, but existing + devices are not renumbered. This means that the device ID could be + in the range from 0 to #Pm_CountDevices(), yet the device ID does + not designate a device. In that case, passing the ID to + #Pm_GetDeviceInfo() will return NULL. + + @return #pmNoError if the device was deleted or #pmInvalidDeviceId + if the device is open, already deleted, or \p device is out of + range. +*/ +PMEXPORT PmError Pm_DeleteVirtualDevice(PmDeviceID device); + /** @} */ + +/** + @defgroup grp_events_filters Events and Filters Handling + @{ +*/ + +/* Filter bit-mask definitions */ +/** filter active sensing messages (0xFE): */ +#define PM_FILT_ACTIVE (1 << 0x0E) +/** filter system exclusive messages (0xF0): */ +#define PM_FILT_SYSEX (1 << 0x00) +/** filter MIDI clock message (0xF8) */ +#define PM_FILT_CLOCK (1 << 0x08) +/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ +#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) +/** filter tick messages (0xF9) */ +#define PM_FILT_TICK (1 << 0x09) +/** filter undefined FD messages */ +#define PM_FILT_FD (1 << 0x0D) +/** filter undefined real-time messages */ +#define PM_FILT_UNDEFINED PM_FILT_FD +/** filter reset messages (0xFF) */ +#define PM_FILT_RESET (1 << 0x0F) +/** 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 | PM_FILT_TICK) +/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ +#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18)) +/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ +#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D) +/** per-note aftertouch (0xA0-0xAF) */ +#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) +/** 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 (1 << 0x1C) +/** Control Changes (CC's) (0xB0-0xBF)*/ +#define PM_FILT_CONTROL (1 << 0x1B) +/** Pitch Bender (0xE0-0xEF*/ +#define PM_FILT_PITCHBEND (1 << 0x1E) +/** MIDI Time Code (0xF1)*/ +#define PM_FILT_MTC (1 << 0x01) +/** Song Position (0xF2) */ +#define PM_FILT_SONG_POSITION (1 << 0x02) +/** Song Select (0xF3)*/ +#define PM_FILT_SONG_SELECT (1 << 0x03) +/** Tuning request (0xF6) */ +#define PM_FILT_TUNE (1 << 0x06) +/** 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) + + +/* Set filters on an open input stream to drop selected input types. + + @param stream an open MIDI input stream. + + @param filters indicate message types to filter (block). + + @return #pmNoError or an error code. + + 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. + */ +PMEXPORT PmError Pm_SetFilter(PortMidiStream* stream, int32_t filters); + +/** Create a mask that filters one channel. */ +#define Pm_Channel(channel) (1<<(channel)) + +/** Filter incoming messages based on channel. + + @param stream an open MIDI input stream. + + @param mask indicates channels to be received. + + @return #pmNoError or an error code. + + The \p mask is a 16-bit bitfield corresponding to appropriate channels. + The #Pm_Channel macro can assist in calling this function. + I.e. to 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)) + + Note that channels are numbered 0 to 15 (not 1 to 16). Most + synthesizer and interfaces number channels starting at 1, but + PortMidi numbers channels starting at 0. + + All channels are allowed by default +*/ +PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); + +/** Terminate outgoing messages immediately. + + @param stream an open MIDI output stream. + + @result #pmNoError or an error code. + + 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. If the + specified behavior cannot be achieved through the system-level + interface (ALSA, CoreMIDI, etc.), the behavior may be that of + Pm_Close(). + */ +PMEXPORT PmError Pm_Abort(PortMidiStream* stream); + +/** Close a midi stream, flush any pending buffers if possible. + + @param stream an open MIDI input or output stream. + + @result #pmNoError or an error code. + + If the system-level interface (ALSA, CoreMIDI, etc.) does not + support flushing remaining messages, the behavior may be one of + the following (most preferred first): block until all pending + timestamped messages are delivered; deliver messages to a server + or kernel process for later delivery but return immediately; drop + messages (as in Pm_Abort()). Therefore, to be safe, applications + should wait until the output queue is empty before calling + Pm_Close(). E.g. calling Pt_Sleep(100 + latency); will give a + 100ms "cushion" beyond latency (if any) before closing. +*/ +PMEXPORT PmError Pm_Close(PortMidiStream* stream); + +/** (re)synchronize to the time_proc passed when the stream was opened. + + @param stream an open MIDI input or output stream. + + @result #pmNoError or an error code. + + Typically, this is used when the stream must be opened before the + time_proc reference is actually advancing. In this case, message + timing may be erratic, but since timestamps of zero mean "send + immediately," initialization messages with zero timestamps can be + written without a functioning time reference and without + problems. Before the first MIDI message with a non-zero timestamp + is written to the stream, the time reference must begin to advance + (for example, if the time_proc computes time based on audio + samples, time might begin to advance when an audio stream becomes + active). After time_proc return values become valid, and BEFORE + writing the first non-zero timestamped MIDI message, call + Pm_Synchronize() so that PortMidi can observe the difference + between the current time_proc value and its MIDI stream time. + + In the more normal case where time_proc values advance + continuously, there is no need to call #Pm_Synchronize. PortMidi + will always synchronize at the first output message and + periodically thereafter. +*/ +PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream); + + +/** Encode a short Midi message into a 32-bit word. If data1 + and/or data2 are not present, use zero. +*/ +#define Pm_Message(status, data1, data2) \ + ((((data2) << 16) & 0xFF0000) | \ + (((data1) << 8) & 0xFF00) | \ + ((status) & 0xFF)) +/** Extract the status field from a 32-bit midi message. */ +#define Pm_MessageStatus(msg) ((msg) & 0xFF) +/** Extract the 1st data field (e.g., pitch) from a 32-bit midi message. */ +#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) +/** Extract the 2nd data field (e.g., velocity) from a 32-bit midi message. */ +#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) + +typedef uint32_t PmMessage; /**< @brief see #PmEvent */ +/** + 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. + + All other MIDI messages take 1 to 3 bytes and are encoded in a whole + PmMessage with status in the low-order byte and remaining bytes + unused, i.e., a 3-byte note-on message will occupy 3 low-order bytes + of PmMessage, leaving the high-order byte unused. + + 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. See also "Example" for Pm_OpenOutput() above. + + 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 struct { + PmMessage message; + PmTimestamp timestamp; +} PmEvent; + +/** @} */ + +/** \defgroup grp_io Reading and Writing Midi Messages + @{ +*/ +/** Retrieve midi data into a buffer. + + @param stream the open input stream. + + @return the number of events read, or, if the result is negative, + a #PmError value will be returned. + + The Buffer Overflow Problem + + 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 entire 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. +*/ +PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length); + +/** Test whether input is available. + + @param stream an open input stream. + + @return TRUE, FALSE, or an error value. + + If there was an asynchronous error, pmHostError is returned and you must + call again to determine if input is (also) available. + + You should probably *not* use this function. Call Pm_Read() + instead. If it returns 0, then there is no data available. It is + possible for Pm_Poll() to return TRUE before the complete message + is available, so Pm_Read() could return 0 even after Pm_Poll() + returns TRUE. Only call Pm_Poll() if you want to know that data is + probably available even though you are not ready to receive data. +*/ +PMEXPORT PmError Pm_Poll(PortMidiStream *stream); + +/** Write MIDI data from a buffer. + + @param stream an open output stream. + + @param buffer (address of) an array of MIDI event data. + + @param length the length of the \p buffer. + + @return TRUE, FALSE, or an error value. + + \b buffer may contain: + - short messages + - sysex messages that are converted into a sequence of PmEvent + structures, e.g. sending data from a file or forwarding them + from midi input, with 4 SysEx bytes per PmEvent message, + low-order byte first, until the last message, which may + contain from 1 to 4 bytes ending in MIDI EOX (0xF7). + - PortMidi allows 1-byte real-time messages to be embedded + within SysEx messages, but only on 4-byte boundaries so + that SysEx data always uses a full 4 bytes (except possibly + at the end). Each real-time message always occupies a full + PmEvent (3 of the 4 bytes in the PmEvent's message are + ignored) even when embedded in a SysEx message. + + Use Pm_WriteSysEx() to write a sysex message stored as a contiguous + array of bytes. + + Sysex data may contain embedded real-time messages. + + \p buffer is managed by the caller. The buffer may be destroyed + as soon as this call returns. +*/ +PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer, + int32_t length); + +/** Write a timestamped non-system-exclusive midi message. + + @param stream an open output stream. + + @param when timestamp for the event. + + @param msg the data for the event. + + @result #pmNoError or an error code. + + Messages are delivered in order, and timestamps must be + non-decreasing. (But timestamps are ignored if the stream was + opened with latency = 0, and otherwise, non-decreasing timestamps + are "corrected" to the lowest valid value.) +*/ +PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when, + PmMessage msg); + +/** Write a timestamped system-exclusive midi message. + + @param stream an open output stream. + + @param when timestamp for the event. + + @param msg the sysex message, terminated with an EOX status byte. + + @result #pmNoError or an error code. + + \p msg is managed by the caller and may be destroyed when this + call returns. +*/ +PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, + unsigned char *msg); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PORTMIDI_PORTMIDI_H */ diff --git a/portmidi/pm_linux/pmlinux.c b/portmidi/pm_linux/pmlinux.c index dcb2b8b..3766427 100644 --- a/portmidi/pm_linux/pmlinux.c +++ b/portmidi/pm_linux/pmlinux.c @@ -12,6 +12,9 @@ #include "stdlib.h" #include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" + #ifdef PMALSA #include "pmlinuxalsa.h" #endif @@ -20,20 +23,23 @@ #include "pmlinuxnull.h" #endif -PmError pm_init() +#if !(defined(PMALSA) || defined(PMNULL)) +#error One of PMALSA or PMNULL must be defined +#endif + +void pm_init() { /* Note: it is not an error for PMALSA to fail to initialize. * It may be a design error that the client cannot query what subsystems * are working properly other than by looking at the list of available * devices. */ - #ifdef PMALSA - pm_linuxalsa_init(); - #endif - #ifdef PMNULL +#ifdef PMALSA + pm_linuxalsa_init(); +#endif +#ifdef PMNULL pm_linuxnull_init(); - #endif - return pmNoError; +#endif } void pm_term(void) @@ -41,16 +47,18 @@ void pm_term(void) #ifdef PMALSA pm_linuxalsa_term(); #endif + #ifdef PMNULL + pm_linuxnull_term(); + #endif } -PmDeviceID pm_default_input_device_id = -1; -PmDeviceID pm_default_output_device_id = -1; - PmDeviceID Pm_GetDefaultInputDeviceID() { + Pm_Initialize(); return pm_default_input_device_id; } PmDeviceID Pm_GetDefaultOutputDeviceID() { + Pm_Initialize(); return pm_default_output_device_id; } diff --git a/portmidi/pm_linux/pmlinuxalsa.c b/portmidi/pm_linux/pmlinuxalsa.c index 53fd85f..b2e43e1 100644 --- a/portmidi/pm_linux/pmlinuxalsa.c +++ b/portmidi/pm_linux/pmlinuxalsa.c @@ -7,6 +7,11 @@ * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation) */ +/* omit this code if PMALSA is not defined -- this mechanism allows + * selection of different MIDI interfaces (at compile time). + */ +#ifdef PMALSA + #include "stdlib.h" #include "portmidi.h" #include "pmutil.h" @@ -14,7 +19,6 @@ #include "pmlinuxalsa.h" #include "string.h" #include "porttime.h" -#include "pmlinux.h" #include @@ -32,12 +36,11 @@ #endif /* to store client/port in the device descriptor */ -#define MAKE_DESCRIPTOR(client, port) ((void*)(size_t)(((client) << 8) | (port))) -#define GET_DESCRIPTOR_CLIENT(info) ((((size_t)(info)) >> 8) & 0xff) -#define GET_DESCRIPTOR_PORT(info) (((size_t)(info)) & 0xff) +#define MAKE_DESCRIPTOR(client, port) ((void*)(long)(((client) << 8) | (port))) +#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff) +#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff) #define BYTE unsigned char -#define UINT unsigned long extern pm_fns_node pm_linuxalsa_in_dictionary; extern pm_fns_node pm_linuxalsa_out_dictionary; @@ -46,14 +49,16 @@ static snd_seq_t *seq = NULL; // all input comes here, // output queue allocated on seq static int queue, queue_used; /* one for all ports, reference counted */ -typedef struct alsa_descriptor_struct { +#define PORT_IS_CLOSED -999999 + +typedef struct alsa_info_struct { + int is_virtual; int client; int port; int this_port; int in_sysex; snd_midi_event_t *parser; - int error; /* host error code */ -} alsa_descriptor_node, *alsa_descriptor_type; +} alsa_info_node, *alsa_info_type; /* get_alsa_error_text -- copy error text to potentially short string */ @@ -61,36 +66,45 @@ typedef struct alsa_descriptor_struct { static void get_alsa_error_text(char *msg, int len, int err) { int errlen = strlen(snd_strerror(err)); - if (errlen < len) { + if (errlen > 0 && errlen < len) { strcpy(msg, snd_strerror(err)); } else if (len > 20) { sprintf(msg, "Alsa error %d", err); - } else if (len > 4) { - strcpy(msg, "Alsa"); } else { msg[0] = 0; } } +static PmError check_hosterror(int err) +{ + if (err < 0) { + get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); + pm_hosterror = TRUE; + return pmHostError; + } + return pmNoError; +} + + /* queue is shared by both input and output, reference counted */ static PmError alsa_use_queue(void) { + int err = 0; if (queue_used == 0) { snd_seq_queue_tempo_t *tempo; queue = snd_seq_alloc_queue(seq); if (queue < 0) { - pm_hosterror = queue; - return pmHostError; + return check_hosterror(queue); } snd_seq_queue_tempo_alloca(&tempo); snd_seq_queue_tempo_set_tempo(tempo, 480000); snd_seq_queue_tempo_set_ppq(tempo, 480); - pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo); - if (pm_hosterror < 0) - return pmHostError; - + err = snd_seq_set_queue_tempo(seq, queue, tempo); + if (err < 0) { + return check_hosterror(err); + } snd_seq_start_queue(seq, queue, NULL); snd_seq_drain_output(seq); } @@ -127,89 +141,141 @@ static int midi_message_length(PmMessage message) } -static PmError alsa_out_open(PmInternal *midi, void *driverInfo) +static alsa_info_type alsa_info_create(int client_port, long id, int is_virtual) { - void *client_port = descriptors[midi->device_id].descriptor; - alsa_descriptor_type desc = (alsa_descriptor_type) - pm_alloc(sizeof(alsa_descriptor_node)); - snd_seq_port_info_t *info; - int err; + alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node)); + info->is_virtual = is_virtual; + info->this_port = id; + info->client = GET_DESCRIPTOR_CLIENT(client_port); + info->port = GET_DESCRIPTOR_PORT(client_port); + info->in_sysex = 0; + return info; +} + + +/* search system dependent extra parameters for string */ +static const char *get_sysdep_name(enum PmSysDepPropertyKey key, + PmSysDepInfo *info) +{ + /* the version where all current properties were introduced is 210 */ + if (info && info->structVersion >= 210) { + int i; + for (i = 0; i < info->length; i++) { /* search for key */ + if (info->properties[i].key == key) { + return info->properties[i].value; + } + } + } + return NULL; +} + - if (!desc) return pmInsufficientMemory; +static void maybe_set_client_name(PmSysDepInfo *driverInfo) +{ + if (!seq) { // make sure seq is created and we have info + return; + } - snd_seq_port_info_alloca(&info); - snd_seq_port_info_set_port(info, midi->device_id); - snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | - SND_SEQ_PORT_CAP_READ); - snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION); - snd_seq_port_info_set_port_specified(info, 1); - err = snd_seq_create_port(seq, info); - if (err < 0) goto free_desc; + const char *client_name = get_sysdep_name(pmKeyAlsaClientName, + (PmSysDepInfo *) driverInfo); + if (client_name) { + snd_seq_set_client_name(seq, client_name); + printf("maybe_set_client_name set client to %s\n", client_name); + } +} - /* fill in fields of desc, which is passed to pm_write routines */ - midi->descriptor = desc; - desc->client = GET_DESCRIPTOR_CLIENT(client_port); - desc->port = GET_DESCRIPTOR_PORT(client_port); - desc->this_port = midi->device_id; - desc->in_sysex = 0; - desc->error = 0; +static PmError alsa_out_open(PmInternal *midi, void *driverInfo) +{ + int id = midi->device_id; + void *client_port = pm_descriptors[id].descriptor; + alsa_info_type ainfo = alsa_info_create((long) client_port, id, + pm_descriptors[id].pub.is_virtual); + snd_seq_port_info_t *pinfo; + int err = 0; + int using_the_queue = 0; + + if (!ainfo) return pmInsufficientMemory; + midi->api_info = ainfo; - err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser); + snd_seq_port_info_alloca(&pinfo); + if (!ainfo->is_virtual) { + snd_seq_port_info_set_port(pinfo, id); + snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + const char *port_name = get_sysdep_name(pmKeyAlsaPortName, + (PmSysDepInfo *) driverInfo); + if (port_name) { + snd_seq_port_info_set_name(pinfo, port_name); + } + snd_seq_port_info_set_port_specified(pinfo, 1); + + err = snd_seq_create_port(seq, pinfo); + if (err < 0) goto free_ainfo; + + } + + err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser); if (err < 0) goto free_this_port; if (midi->latency > 0) { /* must delay output using a queue */ err = alsa_use_queue(); if (err < 0) goto free_parser; + using_the_queue++; + } - err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); + if (!ainfo->is_virtual) { + err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client, + ainfo->port); if (err < 0) goto unuse_queue; /* clean up and return on error */ - } else { - err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port); - if (err < 0) goto free_parser; /* clean up and return on error */ - } + } + + maybe_set_client_name(driverInfo); + return pmNoError; unuse_queue: - alsa_unuse_queue(); + if (using_the_queue > 0) /* only for latency>0 case */ + alsa_unuse_queue(); free_parser: - snd_midi_event_free(desc->parser); + snd_midi_event_free(ainfo->parser); free_this_port: - snd_seq_delete_port(seq, desc->this_port); - free_desc: - pm_free(desc); - pm_hosterror = err; - if (err < 0) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); - } - return pmHostError; + snd_seq_delete_port(seq, ainfo->this_port); + free_ainfo: + pm_free(ainfo); + return check_hosterror(err); } static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, - PmTimestamp timestamp) + PmTimestamp timestamp) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + alsa_info_type info = (alsa_info_type) midi->api_info; snd_seq_event_t ev; - int err; + int err = 0; snd_seq_ev_clear(&ev); - if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) { - snd_seq_ev_set_dest(&ev, desc->client, desc->port); - snd_seq_ev_set_source(&ev, desc->this_port); + if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) { + if (info->is_virtual) { + snd_seq_ev_set_subs(&ev); + } else { + snd_seq_ev_set_dest(&ev, info->client, info->port); + } + snd_seq_ev_set_source(&ev, info->this_port); if (midi->latency > 0) { /* compute relative time of event = timestamp - now + latency */ PmTimestamp now = (midi->time_proc ? midi->time_proc(midi->time_info) : - Pt_Time(NULL)); + Pt_Time()); int when = timestamp; /* if timestamp is zero, send immediately */ /* otherwise compute time delay and use delay if positive */ if (when == 0) when = now; when = (when - now) + midi->latency; if (when < 0) when = 0; - VERBOSE printf("timestamp %d now %d latency %ld, ", + VERBOSE printf("timestamp %d now %d latency %d, ", (int) timestamp, (int) now, midi->latency); VERBOSE printf("scheduling event after %d\n", when); /* message is sent in relative ticks, where 1 tick = 1 ms */ @@ -227,130 +293,182 @@ static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */ snd_seq_ev_set_direct(&ev); } - VERBOSE printf("sending event\n"); + VERBOSE printf("sending event, timestamp %d (%d+%dns) (%s, %s)\n", + ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec, + (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"), + (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs")); err = snd_seq_event_output(seq, &ev); - if (err < 0) { - desc->error = err; - return pmHostError; - } } - return pmNoError; + return check_hosterror(err); } static PmError alsa_out_close(PmInternal *midi) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - if (!desc) return pmBadPtr; - - if (pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port, - desc->client, desc->port)) { - // if there's an error, try to delete the port anyway, but don't - // change the pm_hosterror value so we retain the first error - snd_seq_delete_port(seq, desc->this_port); - } else { // if there's no error, delete the port and retain any error - pm_hosterror = snd_seq_delete_port(seq, desc->this_port); + alsa_info_type info = (alsa_info_type) midi->api_info; + int err = 0; + int error2 = 0; + if (!info) return pmBadPtr; + + if (info->this_port != PORT_IS_CLOSED) { + if (!info->is_virtual) { + err = snd_seq_disconnect_to(seq, info->this_port, + info->client, info->port); + /* even if there was an error, we still try to delete the port */ + error2 = snd_seq_delete_port(seq, info->this_port); + + if (!err) { /* retain original error if there was one */ + err = error2; /* otherwise, use port delete status */ + } + } } if (midi->latency > 0) alsa_unuse_queue(); - snd_midi_event_free(desc->parser); - midi->descriptor = NULL; /* destroy the pointer to signify "closed" */ - pm_free(desc); - if (pm_hosterror) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, - pm_hosterror); - return pmHostError; + snd_midi_event_free(info->parser); + midi->api_info = NULL; /* destroy the pointer to signify "closed" */ + pm_free(info); + return check_hosterror(err); +} + + +static PmError alsa_create_virtual(int is_input, const char *name, + void *device_info) +{ + snd_seq_port_info_t *pinfo; + int err; + int client, port; + + /* we need the id to set the port. */ + PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL, + (is_input ? &pm_linuxalsa_in_dictionary : + &pm_linuxalsa_out_dictionary)); + if (id < 0) { /* error -- out of memory? */ + return id; } - return pmNoError; + snd_seq_port_info_alloca(&pinfo); + snd_seq_port_info_set_capability(pinfo, + (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE : + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)); + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_name(pinfo, name); + snd_seq_port_info_set_port(pinfo, id); + snd_seq_port_info_set_port_specified(pinfo, 1); + /* next 3 lines needed to generate timestamp - PaulLiu */ + snd_seq_port_info_set_timestamping(pinfo, 1); + snd_seq_port_info_set_timestamp_real(pinfo, 0); + snd_seq_port_info_set_timestamp_queue(pinfo, queue); + + err = snd_seq_create_port(seq, pinfo); + if (err < 0) { + pm_undo_add_device(id); + return check_hosterror(err); + } + + client = snd_seq_port_info_get_client(pinfo); + port = snd_seq_port_info_get_port(pinfo); + pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port); + return id; } + static PmError alsa_delete_virtual(PmDeviceID id) + { + int err = snd_seq_delete_port(seq, id); + return check_hosterror(err); + } + + static PmError alsa_in_open(PmInternal *midi, void *driverInfo) { - void *client_port = descriptors[midi->device_id].descriptor; - alsa_descriptor_type desc = (alsa_descriptor_type) - pm_alloc(sizeof(alsa_descriptor_node)); - snd_seq_port_info_t *info; + int id = midi->device_id; + void *client_port = pm_descriptors[id].descriptor; + alsa_info_type ainfo = alsa_info_create((long) client_port, id, + pm_descriptors[id].pub.is_virtual); + snd_seq_port_info_t *pinfo; snd_seq_port_subscribe_t *sub; snd_seq_addr_t addr; - int err; - - if (!desc) return pmInsufficientMemory; + int err = 0; + int is_virtual = pm_descriptors[id].pub.is_virtual; + if (!ainfo) return pmInsufficientMemory; + midi->api_info = ainfo; + err = alsa_use_queue(); - if (err < 0) goto free_desc; + if (err < 0) goto free_ainfo; - snd_seq_port_info_alloca(&info); - snd_seq_port_info_set_port(info, midi->device_id); - snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE | - SND_SEQ_PORT_CAP_READ); - snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION); - snd_seq_port_info_set_port_specified(info, 1); - /* bugfix: without the following, it won't generate timestamp - PaulLiu */ - snd_seq_port_info_set_timestamping(info, 1); - snd_seq_port_info_set_timestamp_real(info, 0); - snd_seq_port_info_set_timestamp_queue(info, queue); - err = snd_seq_create_port(seq, info); - if (err < 0) goto free_queue; - - /* fill in fields of desc, which is passed to pm_write routines */ - midi->descriptor = desc; - desc->client = GET_DESCRIPTOR_CLIENT(client_port); - desc->port = GET_DESCRIPTOR_PORT(client_port); - desc->this_port = midi->device_id; - desc->in_sysex = 0; - - desc->error = 0; - - VERBOSE printf("snd_seq_connect_from: %d %d %d\n", - desc->this_port, desc->client, desc->port); - snd_seq_port_subscribe_alloca(&sub); - addr.client = snd_seq_client_id(seq); - addr.port = desc->this_port; - snd_seq_port_subscribe_set_dest(sub, &addr); - addr.client = desc->client; - addr.port = desc->port; - snd_seq_port_subscribe_set_sender(sub, &addr); - snd_seq_port_subscribe_set_time_update(sub, 1); - /* this doesn't seem to work: messages come in with real timestamps */ - snd_seq_port_subscribe_set_time_real(sub, 0); - err = snd_seq_subscribe_port(seq, sub); - /* err = - snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */ - if (err < 0) goto free_this_port; /* clean up and return on error */ - return pmNoError; + snd_seq_port_info_alloca(&pinfo); + if (is_virtual) { + ainfo->is_virtual = TRUE; + if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) { + pinfo = NULL; + goto free_ainfo; + } + } else { + /* create a port for this alsa client (seq) where the port + number matches the portmidi device ID of the input device */ + snd_seq_port_info_set_port(pinfo, id); + snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_WRITE); + + snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION); + snd_seq_port_info_set_port_specified(pinfo, 1); + + const char *port_name = get_sysdep_name(pmKeyAlsaPortName, + (PmSysDepInfo *) driverInfo); + if (port_name) { + snd_seq_port_info_set_name(pinfo, port_name); + } + + err = snd_seq_create_port(seq, pinfo); + if (err < 0) goto free_queue; + + /* forward messages from input to this alsa client, so this + * alsa client is the destination, and the destination port is the + * port we just created using the device ID as port number + */ + snd_seq_port_subscribe_alloca(&sub); + addr.client = snd_seq_client_id(seq); + addr.port = ainfo->this_port; + snd_seq_port_subscribe_set_dest(sub, &addr); + + /* forward from the sender which is the device named by + client and port */ + addr.client = ainfo->client; + addr.port = ainfo->port; + snd_seq_port_subscribe_set_sender(sub, &addr); + snd_seq_port_subscribe_set_time_update(sub, 1); + /* this doesn't seem to work: messages come in with real timestamps */ + snd_seq_port_subscribe_set_time_real(sub, 0); + err = snd_seq_subscribe_port(seq, sub); + if (err < 0) goto free_this_port; /* clean up and return on error */ + } + maybe_set_client_name(driverInfo); + + return pmNoError; free_this_port: - snd_seq_delete_port(seq, desc->this_port); + snd_seq_delete_port(seq, ainfo->this_port); free_queue: alsa_unuse_queue(); - free_desc: - pm_free(desc); - pm_hosterror = err; - if (err < 0) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err); - } - return pmHostError; + free_ainfo: + pm_free(ainfo); + return check_hosterror(err); } static PmError alsa_in_close(PmInternal *midi) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - if (!desc) return pmBadPtr; - if (pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port, - desc->client, desc->port)) { - snd_seq_delete_port(seq, desc->this_port); /* try to close port */ - } else { - pm_hosterror = snd_seq_delete_port(seq, desc->this_port); + int err = 0; + alsa_info_type info = (alsa_info_type) midi->api_info; + if (!info) return pmBadPtr; + /* virtual ports stay open because the represent devices */ + if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) { + err = snd_seq_delete_port(seq, info->this_port); } alsa_unuse_queue(); - pm_free(desc); - if (pm_hosterror) { - get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, - pm_hosterror); - return pmHostError; - } - return pmNoError; + midi->api_info = NULL; + pm_free(info); + return check_hosterror(err); } @@ -366,11 +484,11 @@ static PmError alsa_abort(PmInternal *midi) * upgrade my entire Linux OS -RBD */ /* - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + info_type info = (info_type) midi->api_info; snd_seq_remove_events_t info; snd_seq_addr_t addr; - addr.client = desc->client; - addr.port = desc->port; + addr.client = info->client; + addr.port = info->port; snd_seq_remove_events_set_dest(&info, &addr); snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST); pm_hosterror = snd_seq_remove_events(seq, &info); @@ -385,83 +503,32 @@ static PmError alsa_abort(PmInternal *midi) } -#ifdef GARBAGE -This is old code here temporarily for reference -static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - int i, bytes; - unsigned char byte; - long msg; - - desc->error = 0; - for (; length > 0; length--, buffer++) { - VERBOSE printf("message 0x%x\n", buffer->message); - if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX) - desc->in_sysex = TRUE; - if (desc->in_sysex) { - msg = buffer->message; - for (i = 0; i < 4; i++) { - byte = msg; /* extract next byte to send */ - alsa_write_byte(midi, byte, buffer->timestamp); - if (byte == MIDI_EOX) { - desc->in_sysex = FALSE; - break; - } - if (desc->error < 0) break; - msg >>= 8; /* shift next byte into position */ - } - } else { - bytes = midi_message_length(buffer->message); - msg = buffer->message; - for (i = 0; i < bytes; i++) { - byte = msg; /* extract next byte to send */ - VERBOSE printf("sending 0x%x\n", byte); - alsa_write_byte(midi, byte, buffer->timestamp); - if (desc->error < 0) break; - msg >>= 8; /* shift next byte into position */ - } - } - } - if (desc->error < 0) return pmHostError; - - VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq); - desc->error = snd_seq_drain_output(seq); - if (desc->error < 0) return pmHostError; - - desc->error = pmNoError; - return pmNoError; -} -#endif - - static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - VERBOSE printf("snd_seq_drain_output: 0x%p\n", seq); - desc->error = snd_seq_drain_output(seq); - if (desc->error < 0) return pmHostError; - - desc->error = pmNoError; - return pmNoError; + int err; + alsa_info_type info = (alsa_info_type) midi->api_info; + if (!info) return pmBadPtr; + VERBOSE printf("snd_seq_drain_output: %p\n", seq); + err = snd_seq_drain_output(seq); + return check_hosterror(err); } static PmError alsa_write_short(PmInternal *midi, PmEvent *event) { int bytes = midi_message_length(event->message); - long msg = event->message; + PmMessage msg = event->message; int i; - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; + alsa_info_type info = (alsa_info_type) midi->api_info; + if (!info) return pmBadPtr; for (i = 0; i < bytes; i++) { unsigned char byte = msg; VERBOSE printf("sending 0x%x\n", byte); alsa_write_byte(midi, byte, event->timestamp); - if (desc->error < 0) break; + if (pm_hosterror) break; msg >>= 8; /* shift next byte into position */ } - if (desc->error < 0) return pmHostError; - desc->error = pmNoError; + if (pm_hosterror) return pmHostError; return pmNoError; } @@ -485,25 +552,50 @@ static PmTimestamp alsa_synchronize(PmInternal *midi) static void handle_event(snd_seq_event_t *ev) { int device_id = ev->dest.port; - PmInternal *midi = descriptors[device_id].internalDescriptor; + PmInternal *midi = pm_descriptors[device_id].pm_internal; + // There is a race condition when closing a device and + // continuing to poll other open devices. The closed device may + // have outstanding events from before the close operation. + if (!midi) { + return; + } PmEvent pm_ev; - PmTimeProcPtr time_proc = midi->time_proc; - PmTimestamp timestamp; + PmTimestamp timestamp = midi->time_proc(midi->time_info); /* time stamp should be in ticks, using our queue where 1 tick = 1ms */ - assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); + /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK); + * Currently, event timestamp is ignored. See long note below. */ - /* if no time_proc, just return "native" ticks (ms) */ - if (time_proc == NULL) { - timestamp = ev->time.tick; - } else { /* translate time to time_proc basis */ + VERBOSE { + /* translate time to time_proc basis */ snd_seq_queue_status_t *queue_status; snd_seq_queue_status_alloca(&queue_status); snd_seq_get_queue_status(seq, queue, queue_status); - /* return (now - alsa_now) + alsa_timestamp */ - timestamp = (*time_proc)(midi->time_info) + ev->time.tick - - snd_seq_queue_status_get_tick_time(queue_status); + printf("handle_event: alsa_now %d, " + "event timestamp %d (%d+%dns) (%s, %s)\n", + snd_seq_queue_status_get_tick_time(queue_status), + ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec, + (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"), + (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs")); + /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */ + /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick - + snd_seq_queue_status_get_tick_time(queue_status); */ } + /* CURRENT: portmidi timestamp is "now". In a test, timestamps from + * hardware (MIDI over USB) were timestamped with the current ALSA + * time (snd_seq_queue_status_get_tick_time) and flags indicating + * absolute ticks, but timestamps from another application's virtual + * port, sent direct with 0 absolute ticks, were received with a + * large value that is apparently the time since the start time of + * the other application. Without any reference to our local time, + * this seems useless. PortMidi is supposed to return the local + * PortMidi time of the arrival of the message, so the best we can + * do is set the timestamp to our local clock. This seems to be a + * design flaw in ALSA -- I pointed this out a decade ago, but if + * there is a workaround, I'd still like to know. Maybe there is a + * way to use absolute real time and maybe that's sharable across + * applications by referencing the system time? + */ pm_ev.timestamp = timestamp; switch (ev->type) { case SND_SEQ_EVENT_NOTEON: @@ -614,17 +706,31 @@ static void handle_event(snd_seq_event_t *ev) pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp); break; } - /* TODO: must handle port close messsage - PaulLiu */ - //case SND_SEQ_EVENT_UNSUBSCRIBE: { - //} + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: { + /* this happens if you have an input port open and the + * device or application with virtual ports closes. We + * mark the port as closed to avoid closing a 2nd time + * when Pm_Close() is called. + */ + alsa_info_type info = (alsa_info_type) midi->api_info; + /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */ + info->this_port = PORT_IS_CLOSED; + break; + } + case SND_SEQ_EVENT_PORT_SUBSCRIBED: + break; /* someone connected to a virtual output port, not reported */ default: - printf("not handled type %x\n", ev->type); + printf("portmidi handle_event: not handled type %x\n", ev->type); + break; } } static PmError alsa_poll(PmInternal *midi) { + if (!midi) { + return pmBadPtr; + } snd_seq_event_t *ev; /* expensive check for input data, gets data from device: */ while (snd_seq_event_input_pending(seq, TRUE) > 0) { @@ -642,12 +748,11 @@ static PmError alsa_poll(PmInternal *midi) handle_event(ev); } else if (rslt == -ENOSPC) { int i; - for (i = 0; i < pm_descriptor_index; i++) { - if (descriptors[i].pub.input) { - PmInternal *midi = (PmInternal *) - descriptors[i].internalDescriptor; + for (i = 0; i < pm_descriptor_len; i++) { + if (pm_descriptors[i].pub.input) { + PmInternal *midi_i = pm_descriptors[i].pm_internal; /* careful, device may not be open! */ - if (midi) Pm_SetOverflow(midi->queue); + if (midi_i) Pm_SetOverflow(midi_i->queue); } } } @@ -657,18 +762,9 @@ static PmError alsa_poll(PmInternal *midi) } -static unsigned int alsa_has_host_error(PmInternal *midi) -{ - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - return desc->error; -} - - -static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len) +static unsigned int alsa_check_host_error(PmInternal *midi) { - alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor; - int err = (pm_hosterror || desc->error); - get_alsa_error_text(msg, len, err); + return FALSE; } @@ -684,8 +780,7 @@ pm_fns_node pm_linuxalsa_in_dictionary = { alsa_abort, alsa_in_close, alsa_poll, - alsa_has_host_error, - alsa_get_host_error + alsa_check_host_error }; pm_fns_node pm_linuxalsa_out_dictionary = { @@ -700,8 +795,7 @@ pm_fns_node pm_linuxalsa_out_dictionary = { alsa_abort, alsa_out_close, none_poll, - alsa_has_host_error, - alsa_get_host_error + alsa_check_host_error }; @@ -719,13 +813,16 @@ char *pm_strdup(const char *s) } -PmError pm_linuxalsa_init( void ) +PmError pm_linuxalsa_init(void) { int err; snd_seq_client_info_t *cinfo; snd_seq_port_info_t *pinfo; unsigned int caps; + /* Register interface ALSA with create_virtual fn */ + pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual); + /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this * would cause messages to be dropped if the ALSA buffer fills up. * The correct behavior is for writes to block until there is @@ -736,54 +833,61 @@ PmError pm_linuxalsa_init( void ) * call seq_event_input_pending() to avoid blocking. */ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0); - if (err < 0) return err; + if (err < 0) goto error_return; snd_seq_client_info_alloca(&cinfo); snd_seq_port_info_alloca(&pinfo); snd_seq_client_info_set_client(cinfo, -1); while (snd_seq_query_next_client(seq, cinfo) == 0) { - snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo)); + snd_seq_port_info_set_client(pinfo, + snd_seq_client_info_get_client(cinfo)); snd_seq_port_info_set_port(pinfo, -1); while (snd_seq_query_next_port(seq, pinfo) == 0) { if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM) continue; /* ignore Timer and Announce ports on client 0 */ caps = snd_seq_port_info_get_capability(pinfo); - if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE))) + if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | + SND_SEQ_PORT_CAP_SUBS_WRITE))) continue; /* ignore if you cannot read or write port */ if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) { if (pm_default_output_device_id == -1) - pm_default_output_device_id = pm_descriptor_index; + pm_default_output_device_id = pm_descriptor_len; pm_add_device("ALSA", - pm_strdup(snd_seq_port_info_get_name(pinfo)), - FALSE, - MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), - snd_seq_port_info_get_port(pinfo)), - &pm_linuxalsa_out_dictionary); + pm_strdup(snd_seq_port_info_get_name(pinfo)), + FALSE, FALSE, + MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), + snd_seq_port_info_get_port(pinfo)), + &pm_linuxalsa_out_dictionary); } if (caps & SND_SEQ_PORT_CAP_SUBS_READ) { if (pm_default_input_device_id == -1) - pm_default_input_device_id = pm_descriptor_index; + pm_default_input_device_id = pm_descriptor_len; pm_add_device("ALSA", - pm_strdup(snd_seq_port_info_get_name(pinfo)), - TRUE, - MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), - snd_seq_port_info_get_port(pinfo)), - &pm_linuxalsa_in_dictionary); + pm_strdup(snd_seq_port_info_get_name(pinfo)), + TRUE, FALSE, + MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo), + snd_seq_port_info_get_port(pinfo)), + &pm_linuxalsa_in_dictionary); } } } return pmNoError; + error_return: + pm_linuxalsa_term(); /* clean up */ + return check_hosterror(err); } - + void pm_linuxalsa_term(void) { if (seq) { snd_seq_close(seq); - pm_free(descriptors); - descriptors = NULL; - pm_descriptor_index = 0; + pm_free(pm_descriptors); + pm_descriptors = NULL; + pm_descriptor_len = 0; pm_descriptor_max = 0; } } + +#endif diff --git a/portmidi/pm_linux/pmlinuxnull.c b/portmidi/pm_linux/pmlinuxnull.c new file mode 100644 index 0000000..257f3d6 --- /dev/null +++ b/portmidi/pm_linux/pmlinuxnull.c @@ -0,0 +1,31 @@ +/* + * pmlinuxnull.c -- system specific definitions + * + * written by: + * Roger Dannenberg + * + * If there is no ALSA, you can define PMNULL and build PortMidi. It will + * not report any devices, so you will not be able to open any, but if + * you wanted to disable MIDI from some application, this could be used. + * Mainly, this code shows the possibility of supporting multiple + * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux. + * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA. + */ + +#ifdef PMNULL + +#include "portmidi.h" +#include "pmlinuxnull.h" + + +PmError pm_linuxnull_init(void) +{ + return pmNoError; +} + + +void pm_linuxnull_term(void) +{ +} + +#endif diff --git a/portmidi/pm_linux/pmlinuxnull.h b/portmidi/pm_linux/pmlinuxnull.h new file mode 100644 index 0000000..9835825 --- /dev/null +++ b/portmidi/pm_linux/pmlinuxnull.h @@ -0,0 +1,6 @@ +/* pmlinuxnull.h -- system-specific definitions */ + +PmError pm_linuxnull_init(void); +void pm_linuxnull_term(void); + + diff --git a/portmidi/pm_mac/pmmac.c b/portmidi/pm_mac/pmmac.c index fbf31c8..48ac17a 100644 --- a/portmidi/pm_mac/pmmac.c +++ b/portmidi/pm_mac/pmmac.c @@ -11,27 +11,29 @@ non-CoreMIDI devices. #include "stdlib.h" #include "portmidi.h" +#include "pmutil.h" +#include "pminternal.h" #include "pmmacosxcm.h" -PmError pm_init() +void pm_init(void) { - return pm_macosxcm_init(); + pm_macosxcm_init(); } + void pm_term(void) { pm_macosxcm_term(); } -PmDeviceID pm_default_input_device_id = -1; -PmDeviceID pm_default_output_device_id = -1; - -PmDeviceID Pm_GetDefaultInputDeviceID() +PmDeviceID Pm_GetDefaultInputDeviceID(void) { + Pm_Initialize(); return pm_default_input_device_id; } -PmDeviceID Pm_GetDefaultOutputDeviceID() { +PmDeviceID Pm_GetDefaultOutputDeviceID(void) { + Pm_Initialize(); return pm_default_output_device_id; } diff --git a/portmidi/pm_mac/pmmac.h b/portmidi/pm_mac/pmmac.h deleted file mode 100644 index 2d71425..0000000 --- a/portmidi/pm_mac/pmmac.h +++ /dev/null @@ -1,4 +0,0 @@ -/* pmmac.h */ - -extern PmDeviceID pm_default_input_device_id; -extern PmDeviceID pm_default_output_device_id; \ No newline at end of file diff --git a/portmidi/pm_mac/pmmacosxcm.c b/portmidi/pm_mac/pmmacosxcm.c index 999e41c..e8b196c 100644 --- a/portmidi/pm_mac/pmmacosxcm.c +++ b/portmidi/pm_mac/pmmacosxcm.c @@ -9,27 +9,72 @@ */ /* Notes: - since the input and output streams are represented by MIDIEndpointRef - values and almost no other state, we store the MIDIEndpointRef on - descriptors[midi->device_id].descriptor. The only other state we need - is for errors: we need to know if there is an error and if so, what is - the error text. We use a structure with two kinds of - host error: "error" and "callback_error". That way, asynchronous callbacks - do not interfere with other error information. + + Since the input and output streams are represented by + MIDIEndpointRef values and almost no other state, we store the + MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor. - OS X does not seem to have an error-code-to-text function, so we will - just use text messages instead of error codes. + OS X does not seem to have an error-code-to-text function, so we + will just use text messages instead of error codes. + + Virtual device input synchronization: Once we create a virtual + device, it is always "on" and receiving messages, but it must drop + messages unless the device has been opened with Pm_OpenInput. To + open, the main thread should create all the data structures, then + call OSMemoryBarrier so that writes are observed, then set + is_opened = TRUE. To close without locks, we need to get the + callback to set is_opened to FALSE before we free data structures; + otherwise, there's a race condition where closing could delete + structures in use by the virtual_read_callback function. We send + 8 MIDI resets (FF) in a single packet to our own port to signal + the virtual_read_callback to close it. Then, we wait for the + callback to recognize the "close" packet and reset is_opened. + + Device scanning is done when you first open an application. + PortMIDI does not actively update the devices. Instead, you must + Pm_Terminate() and Pm_Initialize(), basically starting over. But + CoreMIDI does not have a way to shut down(!), and even + MIDIClientDispose() somehow retains state (and docs say do not + call it even if it worked). The solution, apparently, is to + call CFRunLoopRunInMode(), which somehow updates CoreMIDI + state. + + But when do we call CFRunLoopRunInMode()? I tried calling it + in midi_in_poll() which is called when you call Pm_Read() since + that is called often. I observed that this caused the program + to block for as long as 50ms and fairly often for 2 or 3ms. + What was Apple thinking? Is it really OK to design systems that + can only function with a tricky multi-threaded, non-blocking + priority-based solution, and then not provide a proof of concept + or documentation? Or is Apple's design really flawed? If anyone + at Apple reads this, please let me know -- I'm curious. + + But I digress... Here's the PortMidi approach: Since + CFRunLoopRunInMode() is potentially a non-realtime operation, + we only call it in Pm_Initialize(), where other calls to look + up devices and device names are quite slow to begin with. Again, + PortMidi does not actively scan for new or deleted devices, so + if devices change, you won't see it until the next Pm_Terminate + and Pm_Initialize. + + Calling CFRunLoopRunInMode() once is probably not enough. There + might be better way, but it seems to work to just call it 100 + times and insert 20 1ms delays (in case some inter-process + communication or synchronization is going on). + This adds 20ms to the wall time of Pm_Initialize(), but it + typically runs 30ms to much more (~4s), so this has little impact. */ #include -//#define CM_DEBUG 1 +/* turn on lots of debugging print statements */ +#define CM_DEBUG if (0) +/* #define CM_DEBUG if (1) */ #include "portmidi.h" #include "pmutil.h" #include "pminternal.h" #include "porttime.h" -#include "pmmac.h" #include "pmmacosxcm.h" #include @@ -38,546 +83,735 @@ #include #include #include +#include +#include #define PACKET_BUFFER_SIZE 1024 +/* maximum overall data rate (OS X limits MIDI rate in case there + * is a cycle among IAC ports. + */ -/* this is very strange: if I put in a reasonable - number here, e.g. 128, which would allow sysex data - to be sent 128 bytes at a time, then I lose sysex - data in my loopback test. With a buffer size of 4, - we put at most 4 bytes in a packet (but maybe many - packets in a packetList), and everything works fine. +#define MAX_BYTES_PER_S 5400 + +/* Apple reports that packets are dropped when the MIDI bytes/sec + exceeds 15000. This is computed by "tracking the number of MIDI + bytes scheduled into 1-second buckets over the last six seconds and + averaging these counts." This was confirmed in measurements + (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with + macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec. + Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to + 5400 based on testing (which shows 5700 is too high) to fix the + packet loss problem that showed up with macOS 12. + + Experiments show this restriction applies to IAC bus MIDI, but not + to hardware interfaces. (I measured 0.5 Mbps each way over USB to a + Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe + it would get 1 Mbps one-way, which would make the CoreMIDI + restriction 18x slower than USB. Maybe other USB MIDI + implementations are faster -- USB top speed for other protocols is + certainly higher than 1 Mbps!) + + This is apparently based on timestamps, not on real time, so we + have to avoid constructing packets that schedule high speed output + regardless of when writes occur. The solution is to alter + timestamps to limit data rates. This adds a slight time + distortion, e.g. an 11 note chord with all notes on the same + timestamp will be altered so that the last message is delayed by + 11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms. + Note that this is about 2x MIDI speed, but at least 18x slower + than USB MIDI. + + Altering timestamps creates another problem, which is that a sender + that exceeds the maximum rate can queue up an unbounded number of + messages. With non-USB MIDI devices, you could be writing 5x faster + to CoreMIDI than the hardware interface can send, causing an + unbounded backlog, not to mention that the output stream will be a + steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms), + losing any original timing or rhythm. PortMidi does not guarantee + delivery if, over the long run, you write faster than the hardware + can send. + + The LIMIT_RATE symbol, if defined (which is the default), enables + code to modify timestamps for output to an IAC device as follows: + + Before a packet is formed, the message timestamp is set to the + maximum of the PortMidi timestamp (converted to CoreMIDI time) + and min_next_time. After each send, min_next_time is updated to + the packet time + packet length * delay_per_byte, which limits + the scheduled bytes-per-second. Also, after each packet list + flush, min_next_time is updated to the maximum of min_next_time + and the real time, which prevents many bytes to be scheduled in + the past. (We could more directly just say packets are never + scheduled in the past, but we prefer to get the current time -- a + system call -- only when we perform the more expensive operation + of flushing packets, so that's when we update min_next_time to + the current real time. If we are sending a lot, we have to flush + a lot, so the time will be updated frequently when it matters.) + + This possible adjustment to timestamps can distort accurate + timestamps by up to 0.556 us per 3-byte MIDI message. + + Nothing blocks the sender from queueing up an arbitrary number of + messages. Timestamps should be used for accurate timing by sending + timestamped messages a little ahead of real time, not for + scheduling an entire MIDI sequence at once! */ -#define SYSEX_BUFFER_SIZE 4 +#define LIMIT_RATE 1 + +#define SYSEX_BUFFER_SIZE 128 +/* What is the maximum PortMidi device number for an IAC device? A + * cleaner design would be to not use the endpoint as our device + * representation. Instead, we could have a private extensible struct + * to keep all device information, including whether the device is + * implemented with the AppleMIDIIACDriver, which we need because we + * have to limit the data rate to this particular driver to avoid + * dropping messages. Rather than rewrite a lot of code, I am just + * allocating 64 bytes to flag which devices are IAC ones. If an IAC + * device number is greater than 63, PortMidi will fail to limit + * writes to it, but will not complain and will not access memory + * outside the 64-element array of char. + */ +#define MAX_IAC_NUM 63 #define VERBOSE_ON 1 #define VERBOSE if (VERBOSE_ON) -#define MIDI_SYSEX 0xf0 -#define MIDI_EOX 0xf7 +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#define MIDI_CLOCK 0xf8 #define MIDI_STATUS_MASK 0x80 -static MIDIClientRef client = NULL; /* Client handle to the MIDI server */ -static MIDIPortRef portIn = NULL; /* Input port handle */ -static MIDIPortRef portOut = NULL; /* Output port handle */ +// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines +// NULL_REF is our representation of either 0 or NULL +#ifdef __LP64__ +#define NULL_REF 0 +#else +#define NULL_REF NULL +#endif + +static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */ +static MIDIPortRef portIn = NULL_REF; /* Input port handle */ +static MIDIPortRef portOut = NULL_REF; /* Output port handle */ +static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */ extern pm_fns_node pm_macosx_in_dictionary; extern pm_fns_node pm_macosx_out_dictionary; -typedef struct midi_macosxcm_struct { - unsigned long sync_time; /* when did we last determine delta? */ +typedef struct coremidi_info_struct { + int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */ UInt64 delta; /* difference between stream time and real time in ns */ - UInt64 last_time; /* last output time */ - int first_message; /* tells midi_write to sychronize timestamps */ int sysex_mode; /* middle of sending sysex */ - unsigned long sysex_word; /* accumulate data when receiving sysex */ - unsigned int sysex_byte_count; /* count how many received */ + uint32_t sysex_word; /* accumulate data when receiving sysex */ + uint32_t sysex_byte_count; /* count how many received */ char error[PM_HOST_ERROR_MSG_LEN]; char callback_error[PM_HOST_ERROR_MSG_LEN]; Byte packetBuffer[PACKET_BUFFER_SIZE]; MIDIPacketList *packetList; /* a pointer to packetBuffer */ MIDIPacket *packet; Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */ - MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */ + MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */ /* allow for running status (is running status possible here? -rbd): -cpr */ - unsigned char last_command; - long last_msg_length; -} midi_macosxcm_node, *midi_macosxcm_type; + UInt64 min_next_time; /* when can the next send take place? (host time) */ + int isIACdevice; + Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */ + UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */ +} coremidi_info_node, *coremidi_info_type; /* private function declarations */ -MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); -PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); +MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time +PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms -char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint); +char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag); - -static int -midi_length(long msg) +static PmError check_hosterror(OSStatus err, const char *msg) { - int status, high, low; - static int high_lengths[] = { - 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */ - 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */ - }; - static int low_lengths[] = { - 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */ - 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */ - }; - - status = msg & 0xFF; - high = status >> 4; - low = status & 15; - - return (high != 0xF) ? high_lengths[high] : low_lengths[low]; + if (err != noErr) { + snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg); + pm_hosterror = TRUE; + return pmHostError; + } + return pmNoError; } + static PmTimestamp midi_synchronize(PmInternal *midi) { - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - UInt64 pm_stream_time_2 = + coremidi_info_type info = (coremidi_info_type) midi->api_info; + UInt64 pm_stream_time_2 = // current time in ns AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); - PmTimestamp real_time; - UInt64 pm_stream_time; + PmTimestamp real_time; // in ms + UInt64 pm_stream_time; // in ns /* if latency is zero and this is an output, there is no time reference and midi_synchronize should never be called */ assert(midi->time_proc); - assert(!(midi->write_flag && midi->latency == 0)); + assert(midi->is_input || midi->latency != 0); do { /* read real_time between two reads of stream time */ pm_stream_time = pm_stream_time_2; real_time = (*midi->time_proc)(midi->time_info); - pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + pm_stream_time_2 = AudioConvertHostTimeToNanos( + AudioGetCurrentHostTime()); /* repeat if more than 0.5 ms has elapsed */ } while (pm_stream_time_2 > pm_stream_time + 500000); - m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); - m->sync_time = real_time; + info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000); + midi->sync_time = real_time; return real_time; } -static void -process_packet(MIDIPacket *packet, PmEvent *event, - PmInternal *midi, midi_macosxcm_type m) -{ - /* handle a packet of MIDI messages from CoreMIDI */ - /* there may be multiple short messages in one packet (!) */ - unsigned int remaining_length = packet->length; - unsigned char *cur_packet_data = packet->data; - while (remaining_length > 0) { - if (cur_packet_data[0] == MIDI_SYSEX || - /* are we in the middle of a sysex message? */ - (m->last_command == 0 && - !(cur_packet_data[0] & MIDI_STATUS_MASK))) { - m->last_command = 0; /* no running status */ - unsigned int amt = pm_read_bytes(midi, cur_packet_data, - remaining_length, - event->timestamp); - remaining_length -= amt; - cur_packet_data += amt; - } else if (cur_packet_data[0] == MIDI_EOX) { - /* this should never happen, because pm_read_bytes should - * get and read all EOX bytes*/ - midi->sysex_in_progress = FALSE; - m->last_command = 0; - } else if (cur_packet_data[0] & MIDI_STATUS_MASK) { - /* compute the length of the next (short) msg in packet */ - unsigned int cur_message_length = midi_length(cur_packet_data[0]); - if (cur_message_length > remaining_length) { -#ifdef DEBUG - printf("PortMidi debug msg: not enough data"); -#endif - /* since there's no more data, we're done */ - return; - } - m->last_msg_length = cur_message_length; - m->last_command = cur_packet_data[0]; - switch (cur_message_length) { - case 1: - event->message = Pm_Message(cur_packet_data[0], 0, 0); - break; - case 2: - event->message = Pm_Message(cur_packet_data[0], - cur_packet_data[1], 0); - break; - case 3: - event->message = Pm_Message(cur_packet_data[0], - cur_packet_data[1], - cur_packet_data[2]); - break; - default: - /* PortMIDI internal error; should never happen */ - assert(cur_message_length == 1); - return; /* give up on packet if continued after assert */ - } - pm_read_short(midi, event); - remaining_length -= m->last_msg_length; - cur_packet_data += m->last_msg_length; - } else if (m->last_msg_length > remaining_length + 1) { - /* we have running status, but not enough data */ -#ifdef DEBUG - printf("PortMidi debug msg: not enough data in CoreMIDI packet"); -#endif - /* since there's no more data, we're done */ - return; - } else { /* output message using running status */ - switch (m->last_msg_length) { - case 1: - event->message = Pm_Message(m->last_command, 0, 0); - break; - case 2: - event->message = Pm_Message(m->last_command, - cur_packet_data[0], 0); - break; - case 3: - event->message = Pm_Message(m->last_command, - cur_packet_data[0], - cur_packet_data[1]); - break; - default: - /* last_msg_length is invalid -- internal PortMIDI error */ - assert(m->last_msg_length == 1); - } - pm_read_short(midi, event); - remaining_length -= (m->last_msg_length - 1); - cur_packet_data += (m->last_msg_length - 1); - } - } -} - - - /* called when MIDI packets are received */ -static void -readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon) +static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi) { - PmInternal *midi; - midi_macosxcm_type m; - PmEvent event; + PmTimestamp timestamp; MIDIPacket *packet; unsigned int packetIndex; - unsigned long now; - unsigned int status; - -#ifdef CM_DEBUG - printf("readProc: numPackets %d: ", newPackets->numPackets); -#endif - + uint32_t now; /* Retrieve the context for this connection */ - midi = (PmInternal *) connRefCon; - m = (midi_macosxcm_type) midi->descriptor; - assert(m); + coremidi_info_type info = (coremidi_info_type) midi->api_info; + assert(info); + + CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets); /* synchronize time references every 100ms */ now = (*midi->time_proc)(midi->time_info); - if (m->first_message || m->sync_time + 100 /*ms*/ < now) { + if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) { /* time to resync */ now = midi_synchronize(midi); - m->first_message = FALSE; + midi->first_message = FALSE; } packet = (MIDIPacket *) &newPackets->packet[0]; - /* printf("readproc packet status %x length %d\n", packet->data[0], - packet->length); */ + /* hardware devices get untimed messages and apply timestamps. We + * want to preserve them because they should be more accurate than + * applying the current time here. virtual devices just pass on the + * packet->timeStamp, which could be anything. PortMidi says the + * PortMidi timestamp is the time the message is received. We do not + * know if we are receiving from a device driver or a virtual device. + * PortMidi sends to virtual devices get a current timestamp, so we + * can treat them as the receive time. If the timestamp is zero, + * suggested by CoreMIDI as the value to use for immediate delivery, + * then we plug in `now` which is obtained above. If another + * application sends bogus non-zero timestamps, we will convert them + * to this port's reference time and pass them as event.timestamp. + * Receiver beware. + */ + CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) " + "status %x length %d\n", + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()), + AudioGetCurrentHostTime(), + packet->data[0], packet->length); for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) { /* Set the timestamp and dispatch this message */ - event.timestamp = - (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / - (UInt64) 1000000; - status = packet->data[0]; - /* process packet as sysex data if it begins with MIDI_SYSEX, or - MIDI_EOX or non-status byte with no running status */ -#ifdef CM_DEBUG - printf(" %d", packet->length); -#endif - if (status == MIDI_SYSEX || status == MIDI_EOX || - ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) { - /* previously was: !(status & MIDI_STATUS_MASK)) { - * but this could mistake running status for sysex data - */ - /* reset running status data -cpr */ - m->last_command = 0; - m->last_msg_length = 0; - /* printf("sysex packet length: %d\n", packet->length); */ - pm_read_bytes(midi, packet->data, packet->length, event.timestamp); + CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n", + packet->timeStamp, + AudioConvertHostTimeToNanos(packet->timeStamp)); + if (packet->timeStamp == 0) { + timestamp = now; } else { - process_packet(packet, &event, midi, m); - } + timestamp = (PmTimestamp) /* explicit conversion */ ( + (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) / + (UInt64) 1000000); + } + pm_read_bytes(midi, packet->data, packet->length, timestamp); packet = MIDIPacketNext(packet); } -#ifdef CM_DEBUG - printf("\n"); -#endif } -static PmError -midi_in_open(PmInternal *midi, void *driverInfo) +/* callback for real devices - redirects to read_callback */ +static void device_read_callback(const MIDIPacketList *newPackets, + void *refCon, void *connRefCon) +{ + read_callback(newPackets, (PmInternal *) connRefCon); +} + + +/* callback for virtual devices - redirects to read_callback */ +static void virtual_read_callback(const MIDIPacketList *newPackets, + void *refCon, void *connRefCon) +{ + /* this refCon is the device ID -- if there is a valid ID and + the pm_descriptors table has a non-null pointer to a PmInternal, + then then device is open and should receive this data */ + PmDeviceID id = (PmDeviceID) (size_t) refCon; + if (id >= 0 && id < pm_descriptor_len) { + if (pm_descriptors[id].pub.opened) { + /* check for close request (7 reset status bytes): */ + if (newPackets->numPackets == 1 && + newPackets->packet[0].length == 8 && + /* CoreMIDI declares packets with 4-byte alignment, so we + * should be safe to test for 8 0xFF's as 2 32-bit values: */ + *(SInt32 *) &newPackets->packet[0].data[0] == -1 && + *(SInt32 *) &newPackets->packet[0].data[4] == -1) { + CM_DEBUG printf("got close request packet\n"); + pm_descriptors[id].pub.opened = FALSE; + return; + } else { + read_callback(newPackets, pm_descriptors[id].pm_internal); + } + } + } +} + + +/* allocate and initialize our internal coremidi connection info */ +static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input) +{ + coremidi_info_type info = (coremidi_info_type) + pm_alloc(sizeof(coremidi_info_node)); + if (!info) { + return NULL; + } + info->is_virtual = is_virtual; + info->delta = 0; + info->sysex_mode = FALSE; + info->sysex_word = 0; + info->sysex_byte_count = 0; + info->packet = NULL; + info->min_next_time = 0; + info->isIACdevice = FALSE; + info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency(); + info->host_ticks_per_byte = + (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S)); + info->packetList = (is_input ? NULL : + (MIDIPacketList *) info->packetBuffer); + return info; +} + + +static PmError midi_in_open(PmInternal *midi, void *driverInfo) { MIDIEndpointRef endpoint; - midi_macosxcm_type m; + coremidi_info_type info; OSStatus macHostError; + int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual; - /* insure that we have a time_proc for timing */ - if (midi->time_proc == NULL) { - if (!Pt_Started()) - Pt_Start(1, 0, 0); - /* time_get does not take a parameter, so coerce */ - midi->time_proc = (PmTimeProcPtr) Pt_Time; - } - - endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor; - if (endpoint == NULL) { - return pmInvalidDeviceId; + /* if this is an external device, descriptor is a MIDIEndpointRef. + * if this is a virtual device for this application, descriptor is NULL. + */ + if (!is_virtual) { + endpoint = (MIDIEndpointRef) (intptr_t) + pm_descriptors[midi->device_id].descriptor; + if (endpoint == NULL_REF) { + return pmInvalidDeviceId; + } } - m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ - midi->descriptor = m; - if (!m) { + info = create_macosxcm_info(is_virtual, TRUE); + midi->api_info = info; + if (!info) { return pmInsufficientMemory; } - m->error[0] = 0; - m->callback_error[0] = 0; - m->sync_time = 0; - m->delta = 0; - m->last_time = 0; - m->first_message = TRUE; - m->sysex_mode = FALSE; - m->sysex_word = 0; - m->sysex_byte_count = 0; - m->packetList = NULL; - m->packet = NULL; - m->last_command = 0; - m->last_msg_length = 0; - - macHostError = MIDIPortConnectSource(portIn, endpoint, midi); - if (macHostError != noErr) { - pm_hosterror = macHostError; - sprintf(pm_hosterror_text, - "Host error %ld: MIDIPortConnectSource() in midi_in_open()", - macHostError); - midi->descriptor = NULL; - pm_free(m); - return pmHostError; + if (!is_virtual) { + macHostError = MIDIPortConnectSource(portIn, endpoint, midi); + if (macHostError != noErr) { + midi->api_info = NULL; + pm_free(info); + return check_hosterror(macHostError, + "MIDIPortConnectSource() in midi_in_open()"); + } } - return pmNoError; } -static PmError -midi_in_close(PmInternal *midi) +static PmError midi_in_close(PmInternal *midi) { MIDIEndpointRef endpoint; OSStatus macHostError; PmError err = pmNoError; - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + coremidi_info_type info = (coremidi_info_type) midi->api_info; - if (!m) return pmBadPtr; + if (!info) return pmBadPtr; - endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor; - if (endpoint == NULL) { - pm_hosterror = pmBadPtr; + endpoint = (MIDIEndpointRef) (intptr_t) + pm_descriptors[midi->device_id].descriptor; + if (endpoint == NULL_REF) { + return pmBadPtr; } - /* shut off the incoming messages before freeing data structures */ - macHostError = MIDIPortDisconnectSource(portIn, endpoint); - if (macHostError != noErr) { - pm_hosterror = macHostError; - sprintf(pm_hosterror_text, - "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()", - macHostError); - err = pmHostError; + if (!info->is_virtual) { + /* shut off the incoming messages before freeing data structures */ + macHostError = MIDIPortDisconnectSource(portIn, endpoint); + /* If the source closes, you get paramErr == -50 here. It seems + * possible to monitor changes like sources closing by getting + * notifications ALL changes, but the CoreMIDI documentation is + * really terrible overall, and it seems easier to just ignore + * this host error. + */ + if (macHostError != noErr && macHostError != -50) { + pm_hosterror = TRUE; + err = check_hosterror(macHostError, + "MIDIPortDisconnectSource() in midi_in_close()"); + } + } else { + /* make "close virtual port" message */ + SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF; + /* memory requirements: packet count (4), timestamp (8), length (2), + * data (8). Total: 22, but we allocate plenty more: + */ + Byte packetBuffer[64]; + MIDIPacketList *plist = (MIDIPacketList *) packetBuffer; + MIDIPacket *packet = MIDIPacketListInit(plist); + MIDIPacketListAdd(plist, 64, packet, 0, 8, + (const Byte *) &close_port_bytes); + macHostError = MIDISend(portOut, endpoint, plist); + if (macHostError != noErr) { + err = check_hosterror(macHostError, "MIDISend() (PortMidi close " + "port packet) in midi_in_close()"); + } + /* when packet is delivered, callback thread will clear opened; + * we must wait for that before removing the input queues etc. + * Maybe this could use signals of some kind, but if signals use + * locks, locks can cause priority inversion problems, so we will + * just sleep as needed. On the MIDI timescale, inserting a 0.5ms + * latency should be OK, as the application has no business + * opening/closing devices during time-critical moments. + * + * We expect the MIDI thread to close the device quickly (<0.5ms), + * but we wait up to 50ms in case something terrible happens like + * getting paged out in the middle of deliving packets to this + * virtual device. If there is still no response, we time out and + * force the close without the MIDI thread (even this will probably + * succeed - the problem would be: this thread proceeds to delete + * the input queues, and the freed memory is reallocated and + * overwritten so that queues are no longer usable. Meanwhile, + * the MIDI thread has already begun to deliver packets, so the + * check for opened == TRUE passed, but MIDI thread does not insert + * into queue until queue is freed, reallocated and overwritten. + */ + for (int i = 0; i < 100; i++) { /* up to 50ms delay */ + if (!pm_descriptors[midi->device_id].pub.opened) { + break; + } + usleep(500); /* 0.5ms */ + } + pm_descriptors[midi->device_id].pub.opened = FALSE; /* force it */ } - - midi->descriptor = NULL; - pm_free(midi->descriptor); - + midi->api_info = NULL; + pm_free(info); return err; } -static PmError -midi_out_open(PmInternal *midi, void *driverInfo) +static PmError midi_out_open(PmInternal *midi, void *driverInfo) { - midi_macosxcm_type m; + coremidi_info_type info; + int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual; - m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ - midi->descriptor = m; - if (!m) { + info = create_macosxcm_info(is_virtual, FALSE); + if (midi->device_id <= MAX_IAC_NUM) { + info->isIACdevice = isIAC[midi->device_id]; + CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice); + } + midi->api_info = info; + if (!info) { return pmInsufficientMemory; } - m->error[0] = 0; - m->callback_error[0] = 0; - m->sync_time = 0; - m->delta = 0; - m->last_time = 0; - m->first_message = TRUE; - m->sysex_mode = FALSE; - m->sysex_word = 0; - m->sysex_byte_count = 0; - m->packetList = (MIDIPacketList *) m->packetBuffer; - m->packet = NULL; - m->last_command = 0; - m->last_msg_length = 0; - return pmNoError; } -static PmError -midi_out_close(PmInternal *midi) +static PmError midi_out_close(PmInternal *midi) { - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - if (!m) return pmBadPtr; - - midi->descriptor = NULL; - pm_free(midi->descriptor); - + coremidi_info_type info = (coremidi_info_type) midi->api_info; + if (!info) return pmBadPtr; + midi->api_info = NULL; + pm_free(info); return pmNoError; } -static PmError -midi_abort(PmInternal *midi) + +/* MIDIDestinationCreate apparently cannot create a virtual device + * without a callback and a "refCon" parameter, but when we create + * a virtual device, we do not want a PortMidi stream yet -- that + * should wait for the user to open the stream. So, for the refCon, + * use the PortMidi device ID. The callback will check if the + * device is opened within PortMidi, and if so, use the pm_descriptors + * table to locate the corresponding PmStream. + */ +static PmError midi_create_virtual(int is_input, const char *name, + void *device_info) { - return pmNoError; + OSStatus macHostError; + MIDIEndpointRef endpoint; + CFStringRef nameRef; + PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL, + (is_input ? &pm_macosx_in_dictionary : + &pm_macosx_out_dictionary)); + if (id < 0) { /* error -- out of memory or name conflict? */ + return id; + } + + nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8); + if (is_input) { + macHostError = MIDIDestinationCreate(client, nameRef, + virtual_read_callback, (void *) (intptr_t) id, &endpoint); + } else { + macHostError = MIDISourceCreate(client, nameRef, &endpoint); + } + CFRelease(nameRef); + + if (macHostError != noErr) { + /* undo the device we just allocated */ + pm_undo_add_device(id); + return check_hosterror(macHostError, (is_input ? + "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" : + "MIDISourceCreateWithProtocol() in midi_create_virtual()")); + } + + /* Do we have a manufacturer name? If not, set to "PortMidi" */ + const char *mfr_name = "PortMidi"; + PmSysDepInfo *info = (PmSysDepInfo *) device_info; + /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */ + if (info && info->structVersion >= 210) { + int i; + for (i = 0; i < info->length; i++) { /* search for key */ + if (info->properties[i].key == pmKeyCoreMidiManufacturer) { + mfr_name = info->properties[i].value; + break; + } /* no other keys are recognized; they are ignored */ + } + } + nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8); + MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef); + CFRelease(nameRef); + + pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint; + return id; } -static PmError -midi_write_flush(PmInternal *midi, PmTimestamp timestamp) +static PmError midi_delete_virtual(PmDeviceID id) { + MIDIEndpointRef endpoint; OSStatus macHostError; - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - MIDIEndpointRef endpoint = - (MIDIEndpointRef) descriptors[midi->device_id].descriptor; - assert(m); + + endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor; + if (endpoint == NULL_REF) { + return pmBadPtr; + } + macHostError = MIDIEndpointDispose(endpoint); + return check_hosterror(macHostError, + "MIDIEndpointDispose() in midi_in_close()"); +} + + +static PmError midi_abort(PmInternal *midi) +{ + OSStatus macHostError; + MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t) + pm_descriptors[midi->device_id].descriptor; + macHostError = MIDIFlushOutput(endpoint); + return check_hosterror(macHostError, + "MIDIFlushOutput() in midi_abort()"); +} + + +static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + OSStatus macHostError = 0; + coremidi_info_type info = (coremidi_info_type) midi->api_info; + MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t) + pm_descriptors[midi->device_id].descriptor; + assert(info); assert(endpoint); - if (m->packet != NULL) { + if (info->packet != NULL) { /* out of space, send the buffer and start refilling it */ - macHostError = MIDISend(portOut, endpoint, m->packetList); - m->packet = NULL; /* indicate no data in packetList now */ - if (macHostError != noErr) goto send_packet_error; + /* update min_next_time each flush to support rate limit */ + UInt64 host_now = AudioGetCurrentHostTime(); + if (host_now > info->min_next_time) + info->min_next_time = host_now; + if (info->is_virtual) { + macHostError = MIDIReceived(endpoint, info->packetList); + } else { + macHostError = MIDISend(portOut, endpoint, info->packetList); + } + info->packet = NULL; /* indicate no data in packetList now */ } - return pmNoError; - -send_packet_error: - pm_hosterror = macHostError; - sprintf(pm_hosterror_text, - "Host error %ld: MIDISend() in midi_write()", - macHostError); - return pmHostError; - + return check_hosterror(macHostError, (info->is_virtual ? + "MIDIReceived() in midi_write()" : + "MIDISend() in midi_write()")); } -static PmError -send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, - MIDITimeStamp timestamp) +static PmError send_packet(PmInternal *midi, Byte *message, + unsigned int messageLength, MIDITimeStamp timestamp) { PmError err; - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - assert(m); + coremidi_info_type info = (coremidi_info_type) midi->api_info; + assert(info); - /* printf("add %d to packet %lx len %d\n", message[0], m->packet, messageLength); */ - m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), - m->packet, timestamp, messageLength, - message); - if (m->packet == NULL) { + CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns " + "(host %lld)\n", + message[0], info->packet, messageLength, timestamp, + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()), + AudioGetCurrentHostTime()); + info->packet = MIDIPacketListAdd(info->packetList, + sizeof(info->packetBuffer), info->packet, + timestamp, messageLength, message); +#if LIMIT_SEND_RATE + info->byte_count += messageLength; +#endif + if (info->packet == NULL) { /* out of space, send the buffer and start refilling it */ /* make midi->packet non-null to fool midi_write_flush into sending */ - m->packet = (MIDIPacket *) 4; - if ((err = midi_write_flush(midi, timestamp)) != pmNoError) return err; - m->packet = MIDIPacketListInit(m->packetList); - assert(m->packet); /* if this fails, it's a programming error */ - m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), - m->packet, timestamp, messageLength, - message); - assert(m->packet); /* can't run out of space on first message */ + info->packet = (MIDIPacket *) 4; + /* timestamp is 0 because midi_write_flush ignores timestamp since + * timestamps are already in packets. The timestamp parameter is here + * because other API's need it. midi_write_flush can be called + * from system-independent code that must be cross-API. + */ + if ((err = midi_write_flush(midi, 0)) != pmNoError) return err; + info->packet = MIDIPacketListInit(info->packetList); + assert(info->packet); /* if this fails, it's a programming error */ + info->packet = MIDIPacketListAdd(info->packetList, + sizeof(info->packetBuffer), info->packet, + timestamp, messageLength, message); + assert(info->packet); /* can't run out of space on first message */ } return pmNoError; } -static PmError -midi_write_short(PmInternal *midi, PmEvent *event) +static PmError midi_write_short(PmInternal *midi, PmEvent *event) { - long when = event->timestamp; - long what = event->message; + PmTimestamp when = event->timestamp; + PmMessage what = event->message; MIDITimeStamp timestamp; - UInt64 when_ns; - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + coremidi_info_type info = (coremidi_info_type) midi->api_info; Byte message[4]; unsigned int messageLength; - if (m->packet == NULL) { - m->packet = MIDIPacketListInit(m->packetList); + if (info->packet == NULL) { + info->packet = MIDIPacketListInit(info->packetList); /* this can never fail, right? failure would indicate something unrecoverable */ - assert(m->packet); + assert(info->packet); } - /* compute timestamp */ - if (when == 0) when = midi->now; - /* if latency == 0, midi->now is not valid. We will just set it to zero */ - if (midi->latency == 0) when = 0; - when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta; - /* make sure we don't go backward in time */ - if (when_ns < m->last_time) when_ns = m->last_time; - m->last_time = when_ns; - timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); + /* PortMidi specifies that incoming timestamps are the receive + * time. Devices attach their receive times, but virtual devices + * do not. Instead, they pass along whatever timestamp was sent to + * them. We do not know if we are connected to real or virtual + * device. To avoid wild timestamps on the receiving end, we + * consider 2 cases: PortMidi timestamp is zero or latency is + * zero. Both mean send immediately, so we attach the current time + * which will go out immediately and arrive with a sensible + * timestamp (not zero and not zero mapped to the client's local + * time). Otherwise, we assume the timestamp is reasonable. It + * might be slighly in the past, but we pass it along after + * translation to MIDITimeStamp units. + * + * Compute timestamp: use current time if timestamp is zero or + * latency is zero. Both mean no timing and send immediately. + */ + if (when == 0 || midi->latency == 0) { + timestamp = AudioGetCurrentHostTime(); + } else { /* translate PortMidi time + latency to CoreMIDI time */ + timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + + info->delta; + timestamp = AudioConvertNanosToHostTime(timestamp); + } message[0] = Pm_MessageStatus(what); message[1] = Pm_MessageData1(what); message[2] = Pm_MessageData2(what); - messageLength = midi_length(what); - + messageLength = pm_midi_length((int32_t) what); + +#ifdef LIMIT_RATE + /* Make sure we go forward in time. */ + if (timestamp < info->min_next_time) { + timestamp = info->min_next_time; + } + /* Note that if application is way behind and slowly catching up, then + * timestamps could be increasing faster than real time, and since + * timestamps are used to estimate data rate, our estimate could be + * low, causing CoreMIDI to drop packets. This seems very unlikely. + */ + if (info->isIACdevice || info->is_virtual) { + info->min_next_time = timestamp + messageLength * + info->host_ticks_per_byte; + } +#endif /* Add this message to the packet list */ return send_packet(midi, message, messageLength, timestamp); } -static PmError -midi_begin_sysex(PmInternal *midi, PmTimestamp when) +static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when) { UInt64 when_ns; - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - assert(m); - m->sysex_byte_count = 0; + coremidi_info_type info = (coremidi_info_type) midi->api_info; + assert(info); + info->sysex_byte_count = 0; /* compute timestamp */ if (when == 0) when = midi->now; /* if latency == 0, midi->now is not valid. We will just set it to zero */ if (midi->latency == 0) when = 0; - when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta; - m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); + when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + + info->delta; + info->sysex_timestamp = + (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns); + UInt64 now; /* only make system time call when writing a virtual port */ + if (info->is_virtual && info->sysex_timestamp < + (now = AudioGetCurrentHostTime())) { + info->sysex_timestamp = now; + } - if (m->packet == NULL) { - m->packet = MIDIPacketListInit(m->packetList); + if (info->packet == NULL) { + info->packet = MIDIPacketListInit(info->packetList); /* this can never fail, right? failure would indicate something unrecoverable */ - assert(m->packet); + assert(info->packet); } return pmNoError; } -static PmError -midi_end_sysex(PmInternal *midi, PmTimestamp when) +static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when) { PmError err; - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - assert(m); + coremidi_info_type info = (coremidi_info_type) midi->api_info; + assert(info); - /* make sure we don't go backward in time */ - if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time; +#ifdef LIMIT_RATE + /* make sure we go foreward in time */ + if (info->sysex_timestamp < info->min_next_time) + info->sysex_timestamp = info->min_next_time; + + if (info->isIACdevice) { + info->min_next_time = info->sysex_timestamp + info->sysex_byte_count * + info->host_ticks_per_byte; + } +#endif /* now send what's in the buffer */ - err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count, - m->sysex_timestamp); - m->sysex_byte_count = 0; + err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count, + info->sysex_timestamp); + info->sysex_byte_count = 0; if (err != pmNoError) { - m->packet = NULL; /* flush everything in the packet list */ - return err; + info->packet = NULL; /* flush everything in the packet list */ } - return pmNoError; + return err; } -static PmError -midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp) +static PmError midi_write_byte(PmInternal *midi, unsigned char byte, + PmTimestamp timestamp) { - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - assert(m); - if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) { + coremidi_info_type info = (coremidi_info_type) midi->api_info; + assert(info); + if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) { PmError err = midi_end_sysex(midi, timestamp); if (err != pmNoError) return err; } - m->sysex_buffer[m->sysex_byte_count++] = byte; + info->sysex_buffer[info->sysex_byte_count++] = byte; return pmNoError; } -static PmError -midi_write_realtime(PmInternal *midi, PmEvent *event) +static PmError midi_write_realtime(PmInternal *midi, PmEvent *event) { /* to send a realtime message during a sysex message, first flush all pending sysex bytes into packet list */ @@ -587,27 +821,10 @@ midi_write_realtime(PmInternal *midi, PmEvent *event) return midi_write_short(midi, event); } -static unsigned int midi_has_host_error(PmInternal *midi) -{ - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - return (m->callback_error[0] != 0) || (m->error[0] != 0); -} - -static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len) +static unsigned int midi_check_host_error(PmInternal *midi) { - midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; - msg[0] = 0; /* initialize to empty string */ - if (m) { /* make sure there is an open device to examine */ - if (m->error[0]) { - strncpy(msg, m->error, len); - m->error[0] = 0; /* clear the error */ - } else if (m->callback_error[0]) { - strncpy(msg, m->callback_error, len); - m->callback_error[0] = 0; /* clear the error */ - } - msg[len - 1] = 0; /* make sure string is terminated */ - } + return FALSE; } @@ -622,6 +839,7 @@ MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) } } + PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) { UInt64 nanos; @@ -635,179 +853,172 @@ PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) ////////////////////////////////////// // Obtain the name of an endpoint without regard for whether it has connections. // The result should be released by the caller. -CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) +CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal, + int *iac_flag) { - CFMutableStringRef result = CFStringCreateMutable(NULL, 0); - CFStringRef str; - - // begin with the endpoint's name - str = NULL; - MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); - if (str != NULL) { - CFStringAppend(result, str); - CFRelease(str); - } + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + *iac_flag = FALSE; - MIDIEntityRef entity = NULL; - MIDIEndpointGetEntity(endpoint, &entity); - if (entity == NULL) - // probably virtual - return result; - - if (CFStringGetLength(result) == 0) { - // endpoint name has zero length -- try the entity + // begin with the endpoint's name str = NULL; - MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); + MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); if (str != NULL) { - CFStringAppend(result, str); - CFRelease(str); - } - } - // now consider the device's name - MIDIDeviceRef device = NULL; - MIDIEntityGetDevice(entity, &device); - if (device == NULL) - return result; - - str = NULL; - MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); - if (CFStringGetLength(result) == 0) { - CFRelease(result); - return str; - } - if (str != NULL) { - // if an external device has only one entity, throw away - // the endpoint name and just use the device name - if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { - CFRelease(result); - return str; - } else { - if (CFStringGetLength(str) == 0) { + CFStringAppend(result, str); CFRelease(str); + } + MIDIEntityRef entity = NULL_REF; + MIDIEndpointGetEntity(endpoint, &entity); + if (entity == NULL_REF) { + // probably virtual return result; - } - // does the entity name already start with the device name? - // (some drivers do this though they shouldn't) - // if so, do not prepend - if (CFStringCompareWithOptions( result, /* endpoint name */ - str /* device name */, - CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) { - // prepend the device name to the entity name - if (CFStringGetLength(result) > 0) - CFStringInsert(result, 0, CFSTR(" ")); - CFStringInsert(result, 0, str); - } - CFRelease(str); - } - } - return result; -} - + } + if (!isExternal) { /* detect IAC devices */ + //extern const CFStringRef kMIDIPropertyDriverOwner; + MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str); + if (str != NULL) { + char s[32]; /* driver name may truncate, but that's OK */ + CFStringGetCString(str, s, 31, kCFStringEncodingUTF8); + s[31] = 0; /* make sure it is terminated just to be safe */ + CM_DEBUG printf("driver %s\n", s); + *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0); + } + } -// Obtain the name of an endpoint, following connections. -// The result should be released by the caller. -static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint) -{ - CFMutableStringRef result = CFStringCreateMutable(NULL, 0); - CFStringRef str; - OSStatus err; - int i; - - // Does the endpoint have connections? - CFDataRef connections = NULL; - int nConnected = 0; - bool anyStrings = false; - err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); - if (connections != NULL) { - // It has connections, follow them - // Concatenate the names of all connected devices - nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID); - if (nConnected) { - const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); - for (i = 0; i < nConnected; ++i, ++pid) { - MIDIUniqueID id = EndianS32_BtoN(*pid); - MIDIObjectRef connObject; - MIDIObjectType connObjectType; - err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); - if (err == noErr) { - if (connObjectType == kMIDIObjectType_ExternalSource || - connObjectType == kMIDIObjectType_ExternalDestination) { - // Connected to an external device's endpoint (10.3 and later). - str = EndpointName((MIDIEndpointRef)(connObject), true); - } else { - // Connected to an external device (10.2) (or something else, catch-all) - str = NULL; - MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); - } - if (str != NULL) { - if (anyStrings) - CFStringAppend(result, CFSTR(", ")); - else anyStrings = true; + if (CFStringGetLength(result) == 0) { + // endpoint name has zero length -- try the entity + str = NULL; + MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); + if (str != NULL) { CFStringAppend(result, str); CFRelease(str); - } } - } } - CFRelease(connections); - } - if (anyStrings) - return result; + // now consider the device's name + MIDIDeviceRef device = NULL_REF; + MIDIEntityGetDevice(entity, &device); + if (device == NULL_REF) + return result; - // Here, either the endpoint had no connections, or we failed to obtain names for any of them. - return EndpointName(endpoint, false); + str = NULL; + MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); + if (CFStringGetLength(result) == 0) { + CFRelease(result); + return str; + } + if (str != NULL) { + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { + CFRelease(result); + return str; + } else { + if (CFStringGetLength(str) == 0) { + CFRelease(str); + return result; + } + // does the entity name already start with the device name? + // (some drivers do this though they shouldn't) + // if so, do not prepend + if (CFStringCompareWithOptions(result, /* endpoint name */ + str, /* device name */ + CFRangeMake(0, CFStringGetLength(str)), 0) != + kCFCompareEqualTo) { + // prepend the device name to the entity name + if (CFStringGetLength(result) > 0) + CFStringInsert(result, 0, CFSTR(" ")); + CFStringInsert(result, 0, str); + } + CFRelease(str); + } + } + return result; } -char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint) +// Obtain the name of an endpoint, following connections. +// The result should be released by the caller. +static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint, + int *iac_flag) { -#ifdef OLDCODE - MIDIEntityRef entity; - MIDIDeviceRef device; + CFMutableStringRef result = CFStringCreateMutable(NULL, 0); + CFStringRef str; + OSStatus err; + long i; + + // Does the endpoint have connections? + CFDataRef connections = NULL; + long nConnected = 0; + bool anyStrings = false; + err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, + &connections); + if (connections != NULL) { + // It has connections, follow them + // Concatenate the names of all connected devices + nConnected = CFDataGetLength(connections) / + (int32_t) sizeof(MIDIUniqueID); + if (nConnected) { + const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); + for (i = 0; i < nConnected; ++i, ++pid) { + MIDIUniqueID id = EndianS32_BtoN(*pid); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + err = MIDIObjectFindByUniqueID(id, &connObject, + &connObjectType); + if (err == noErr) { + if (connObjectType == kMIDIObjectType_ExternalSource || + connObjectType == kMIDIObjectType_ExternalDestination) { + // Connected to an external device's endpoint (>=10.3) + str = EndpointName((MIDIEndpointRef)(connObject), true, + iac_flag); + } else { + // Connected to an external device (10.2) + // (or something else, catch-all) + str = NULL; + MIDIObjectGetStringProperty(connObject, + kMIDIPropertyName, &str); + } + if (str != NULL) { + if (anyStrings) + CFStringAppend(result, CFSTR(", ")); + else anyStrings = true; + CFStringAppend(result, str); + CFRelease(str); + } + } + } + } + CFRelease(connections); + } + if (anyStrings) + return result; // caller should release result - CFStringRef endpointName = NULL; - CFStringRef deviceName = NULL; -#endif - CFStringRef fullName = NULL; - CFStringEncoding defaultEncoding; - char* newName; + CFRelease(result); - /* get the default string encoding */ - defaultEncoding = CFStringGetSystemEncoding(); + // Here, either the endpoint had no connections, or we failed to + // obtain names for any of them. + return EndpointName(endpoint, false, iac_flag); +} - fullName = ConnectedEndpointName(endpoint); - -#ifdef OLDCODE - /* get the entity and device info */ - MIDIEndpointGetEntity(endpoint, &entity); - MIDIEntityGetDevice(entity, &device); - /* create the nicely formated name */ - MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName); - MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName); - if (deviceName != NULL) { - fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"), - deviceName, endpointName); - } else { - fullName = endpointName; - } -#endif - /* copy the string into our buffer */ - newName = (char *) malloc(CFStringGetLength(fullName) + 1); - CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1, - defaultEncoding); +char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag) +{ + /* Thanks to Dan Wilcox for fixes for Unicode handling */ + CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag); + CFIndex utf16_len = CFStringGetLength(fullName) + 1; + CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding( + utf16_len, kCFStringEncodingUTF8) + 1; + char* pmname = (char *) pm_alloc(CFStringGetLength(fullName) + 1); + + /* copy the string into our buffer; note that there may be some wasted + space, but the total waste is not large */ + CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8); /* clean up */ -#ifdef OLDCODE - if (endpointName) CFRelease(endpointName); - if (deviceName) CFRelease(deviceName); -#endif if (fullName) CFRelease(fullName); - - return newName; + return pmname; } - pm_fns_node pm_macosx_in_dictionary = { none_write_short, @@ -821,8 +1032,7 @@ pm_fns_node pm_macosx_in_dictionary = { midi_abort, midi_in_close, success_poll, - midi_has_host_error, - midi_get_host_error, + midi_check_host_error }; pm_fns_node pm_macosx_out_dictionary = { @@ -837,23 +1047,49 @@ pm_fns_node pm_macosx_out_dictionary = { midi_abort, midi_out_close, success_poll, - midi_has_host_error, - midi_get_host_error, + midi_check_host_error }; +/* We do nothing with callbacks, but generating the callbacks also + * updates CoreMIDI state. Callback may not be essential, but calling + * the CFRunLoopRunInMode is necessary. + */ +void cm_notify(const MIDINotification *msg, void *refCon) +{ + /* for debugging, trace change notifications: + const char *descr[] = { + "undefined (0)", + "kMIDIMsgSetupChanged", + "kMIDIMsgObjectAdded", + "kMIDIMsgObjectRemoved", + "kMIDIMsgPropertyChanged", + "kMIDIMsgThruConnectionsChanged", + "kMIDIMsgSerialPortOwnerChanged", + "kMIDIMsgIOError"}; + + printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID, + descr[(int) msg->messageID]); + */ + return; +} + + PmError pm_macosxcm_init(void) { ItemCount numInputs, numOutputs, numDevices; MIDIEndpointRef endpoint; - int i; - OSStatus macHostError; + OSStatus macHostError = noErr; char *error_text; + memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */ + + /* Register interface CoreMIDI with create_virtual fn */ + pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual); + /* no check for error return because this always succeeds */ + /* Determine the number of MIDI devices on the system */ numDevices = MIDIGetNumberOfDevices(); - numInputs = MIDIGetNumberOfSources(); - numOutputs = MIDIGetNumberOfDestinations(); /* Return prematurely if no devices exist on the system Note that this is not an error. There may be no devices. @@ -864,22 +1100,32 @@ PmError pm_macosxcm_init(void) return pmNoError; } - /* Initialize the client handle */ - macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client); + if (client == NULL_REF) { + macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL, + &client); + } else { /* see notes above on device scanning */ + for (int i = 0; i < 100; i++) { + // look for any changes before scanning for devices + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); + if (i % 5 == 0) Pt_Sleep(1); /* insert 20 delays */ + } + } if (macHostError != noErr) { error_text = "MIDIClientCreate() in pm_macosxcm_init()"; goto error_return; } + numInputs = MIDIGetNumberOfSources(); + numOutputs = MIDIGetNumberOfDestinations(); /* Create the input port */ - macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc, - NULL, &portIn); + macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), + device_read_callback, NULL, &portIn); if (macHostError != noErr) { error_text = "MIDIInputPortCreate() in pm_macosxcm_init()"; goto error_return; } - + /* Create the output port */ macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut); if (macHostError != noErr) { @@ -888,48 +1134,46 @@ PmError pm_macosxcm_init(void) } /* Iterate over the MIDI input devices */ - for (i = 0; i < numInputs; i++) { + for (int i = 0; i < numInputs; i++) { + int iac_flag; endpoint = MIDIGetSource(i); - if (endpoint == NULL) { + if (endpoint == NULL_REF) { continue; } - - /* set the first input we see to the default */ - if (pm_default_input_device_id == -1) - pm_default_input_device_id = pm_descriptor_index; - /* Register this device with PortMidi */ - pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint), - TRUE, (void*)endpoint, &pm_macosx_in_dictionary); + pm_add_device("CoreMIDI", + cm_get_full_endpoint_name(endpoint, &iac_flag), TRUE, FALSE, + (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary); } /* Iterate over the MIDI output devices */ - for (i = 0; i < numOutputs; i++) { + for (int i = 0; i < numOutputs; i++) { + int iac_flag; + PmDeviceID id; endpoint = MIDIGetDestination(i); - if (endpoint == NULL) { + if (endpoint == NULL_REF) { continue; } - - /* set the first output we see to the default */ - if (pm_default_output_device_id == -1) - pm_default_output_device_id = pm_descriptor_index; - /* Register this device with PortMidi */ - pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint), - FALSE, (void*)endpoint, &pm_macosx_out_dictionary); + id = pm_add_device("CoreMIDI", + cm_get_full_endpoint_name(endpoint, &iac_flag), FALSE, FALSE, + (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary); + /* if this is an IAC device, tuck that info away for write functions */ + if (iac_flag && id <= MAX_IAC_NUM) { + isIAC[id] = TRUE; + } } return pmNoError; error_return: - pm_hosterror = macHostError; - sprintf(pm_hosterror_text, "Host error %ld: %s\n", macHostError, error_text); pm_macosxcm_term(); /* clear out any opened ports */ - return pmHostError; + return check_hosterror(macHostError, error_text); } void pm_macosxcm_term(void) { - if (client != NULL) MIDIClientDispose(client); - if (portIn != NULL) MIDIPortDispose(portIn); - if (portOut != NULL) MIDIPortDispose(portOut); + /* docs say do not explicitly dispose of client + if (client != NULL_REF) MIDIClientDispose(client); */ + if (portIn != NULL_REF) MIDIPortDispose(portIn); + if (portOut != NULL_REF) MIDIPortDispose(portOut); } diff --git a/portmidi/pm_mac/pmmacosxcm.h b/portmidi/pm_mac/pmmacosxcm.h index 1725935..166a0b7 100644 --- a/portmidi/pm_mac/pmmacosxcm.h +++ b/portmidi/pm_mac/pmmacosxcm.h @@ -1,4 +1,4 @@ /* system-specific definitions */ PmError pm_macosxcm_init(void); -void pm_macosxcm_term(void); \ No newline at end of file +void pm_macosxcm_term(void); diff --git a/portmidi/pm_win/pmwin.c b/portmidi/pm_win/pmwin.c index 716b68f..5cb73b0 100644 --- a/portmidi/pm_win/pmwin.c +++ b/portmidi/pm_win/pmwin.c @@ -9,6 +9,7 @@ be separate from the main portmidi.c file because it is system dependent, and it is separate from, say, pmwinmm.c, because it might need to register devices for winmm, directx, and others. + */ #include "stdlib.h" @@ -16,12 +17,10 @@ #include "pmutil.h" #include "pminternal.h" #include "pmwinmm.h" -#ifdef USE_DLL_FOR_CLEANUP -#include "pmdll.h" /* used to close ports on exit */ -#endif #ifdef DEBUG #include "stdio.h" #endif +#include /* pm_exit is called when the program exits. It calls pm_term to make sure PortMidi is properly closed. @@ -29,36 +28,23 @@ */ static void pm_exit(void) { pm_term(); -#ifdef DEBUG -#define STRING_MAX 80 - { - char line[STRING_MAX]; - printf("Type ENTER...\n"); - /* note, w/o this prompting, client console application can not see one - of its errors before closing. */ - fgets(line, STRING_MAX, stdin); - } -#endif } +static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType) +{ + exit(1); /* invokes pm_exit() */ + ExitProcess(1); /* probably never called */ + return TRUE; +} + /* pm_init is the windows-dependent initialization.*/ void pm_init(void) { -#ifdef USE_DLL_FOR_CLEANUP - /* we were hoping a DLL could offer more robust cleanup after errors, - but the DLL does not seem to run after crashes. Thus, the atexit() - mechanism is just as powerful, and simpler to implement. - */ - pm_set_close_function(pm_exit); -#ifdef DEBUG - printf("registered pm_term with cleanup DLL\n"); -#endif -#else atexit(pm_exit); + SetConsoleCtrlHandler(ctrl_c_handler, TRUE); #ifdef DEBUG printf("registered pm_exit with atexit()\n"); -#endif #endif pm_winmm_init(); /* initialize other APIs (DirectX?) here */ @@ -70,37 +56,34 @@ void pm_term(void) { } -PmDeviceID Pm_GetDefaultInputDeviceID() { - /* This routine should check the environment and the registry - as specified in portmidi.h, but for now, it just returns - the first device of the proper input/output flavor. - */ +static PmDeviceID pm_get_default_device_id(int is_input, char *key) { +#define PATTERN_MAX 256 + /* Find first input or device -- this is the default. */ + PmDeviceID id = pmNoDevice; int i; Pm_Initialize(); /* make sure descriptors exist! */ - for (i = 0; i < pm_descriptor_index; i++) { - if (descriptors[i].pub.input) { - return i; + for (i = 0; i < pm_descriptor_len; i++) { + if (pm_descriptors[i].pub.input == is_input) { + id = i; + break; } } - return pmNoDevice; + return id; } + +PmDeviceID Pm_GetDefaultInputDeviceID() { + return pm_get_default_device_id(TRUE, + "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E"); +} + + PmDeviceID Pm_GetDefaultOutputDeviceID() { - /* This routine should check the environment and the registry - as specified in portmidi.h, but for now, it just returns - the first device of the proper input/output flavor. - */ - int i; - Pm_Initialize(); /* make sure descriptors exist! */ - for (i = 0; i < pm_descriptor_index; i++) { - if (descriptors[i].pub.output) { - return i; - } - } - return pmNoDevice; - return 0; + return pm_get_default_device_id(FALSE, + "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E"); } + #include "stdio.h" void *pm_alloc(size_t s) { @@ -112,3 +95,4 @@ void pm_free(void *ptr) { free(ptr); } + diff --git a/portmidi/pm_win/pmwinmm.c b/portmidi/pm_win/pmwinmm.c index c91cbb3..6f4b6f3 100644 --- a/portmidi/pm_win/pmwinmm.c +++ b/portmidi/pm_win/pmwinmm.c @@ -7,6 +7,8 @@ #define _WIN32_WINNT 0x0500 #endif +#define UNICODE 1 +#include #include "windows.h" #include "mmsystem.h" #include "portmidi.h" @@ -15,12 +17,15 @@ #include "pmwinmm.h" #include #include "porttime.h" +#ifndef UNICODE +#error Expected UNICODE to be defined +#endif + /* asserts used to verify portMidi code logic is sound; later may want something more graceful */ #include -#define DEBUG 1 -#ifdef DEBUG +#ifdef MMDEBUG /* this printf stuff really important for debugging client app w/host errors. probably want to do something else besides read/write from/to console for portability, however */ @@ -35,14 +40,12 @@ /* callback routines */ static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn, - WORD wMsg, DWORD_PTR dwInstance, + UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2); static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, DWORD_PTR dwParam2); -static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, - DWORD_PTR dwParam2); extern pm_fns_node pm_winmm_in_dictionary; extern pm_fns_node pm_winmm_out_dictionary; @@ -92,10 +95,6 @@ The following constants help to represent these design parameters: #define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3) /* A MIDIHDR with a sysex message is the buffer length plus the header size */ #define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR)) -#ifdef USE_SYSEX_BUFFERS -/* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */ -#define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR)) -#endif /* ============================================================================== @@ -112,7 +111,7 @@ MIDIOUTCAPS midi_out_mapper_caps; UINT midi_num_outputs = 0; /* per device info */ -typedef struct midiwinmm_struct { +typedef struct winmm_info_struct { union { HMIDISTRM stream; /* windows handle for stream */ HMIDIOUT out; /* windows handle for out calls */ @@ -128,13 +127,6 @@ typedef struct midiwinmm_struct { int num_buffers; /* how many buffers allocated in buffers array */ int next_buffer; /* index of next buffer to send */ HANDLE buffer_signal; /* used to wait for buffer to become free */ -#ifdef USE_SYSEX_BUFFERS - /* sysex buffers will be allocated only when - * a sysex message is sent. The size of the buffer is fixed. - */ - LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */ - int next_sysex_buffer; /* index of next sysexbuffer to send */ -#endif unsigned long last_time; /* last output time */ int first_message; /* flag: treat first message differently */ int sysex_mode; /* middle of sending sysex */ @@ -144,9 +136,8 @@ typedef struct midiwinmm_struct { unsigned long sync_time; /* when did we last determine delta? */ long delta; /* difference between stream time and real time */ - int error; /* host error from doing port midi call */ CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */ -} midiwinmm_node, *midiwinmm_type; +} winmm_info_node, *winmm_info_type; /* @@ -154,6 +145,20 @@ typedef struct midiwinmm_struct { general MIDI device queries ============================================================================= */ + +/* add a device after converting device (product) name to UTF-8 */ +static void pm_add_device_w(char *api, WCHAR *device_name, int is_input, + int is_virtual, void *descriptor, pm_fns_type dictionary) +{ + char utf8name[4 * MAXPNAMELEN]; + WideCharToMultiByte(CP_UTF8, 0, device_name, -1, + utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL); + /* ignore errors here -- if pm_descriptor_max is exceeded, + some devices will not be accessible. */ + pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary); +} + + static void pm_winmm_general_inputs() { UINT i; @@ -173,10 +178,8 @@ static void pm_winmm_general_inputs() wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i], sizeof(MIDIINCAPS)); if (wRtn == MMSYSERR_NOERROR) { - /* ignore errors here -- if pm_descriptor_max is exceeded, some - devices will not be accessible. */ - pm_add_device("MMSystem", midi_in_caps[i].szPname, TRUE, - (void *) i, &pm_winmm_in_dictionary); + pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE, + (void *) (intptr_t) i, &pm_winmm_in_dictionary); } } } @@ -187,14 +190,15 @@ static void pm_winmm_mapper_input() WORD wRtn; /* Note: if MIDIMAPPER opened as input (documentation implies you can, but current system fails to retrieve input mapper - capabilities) then you still should retrieve some formof + capabilities) then you still should retrieve some form of setup info. */ wRtn = midiInGetDevCaps((UINT) MIDIMAPPER, (LPMIDIINCAPS) & midi_in_mapper_caps, sizeof(MIDIINCAPS)); if (wRtn == MMSYSERR_NOERROR) { - pm_add_device("MMSystem", midi_in_mapper_caps.szPname, TRUE, - (void *) MIDIMAPPER, &pm_winmm_in_dictionary); + pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE, + (void *) (intptr_t) MIDIMAPPER, + &pm_winmm_in_dictionary); } } @@ -204,7 +208,7 @@ static void pm_winmm_general_outputs() UINT i; DWORD wRtn; midi_num_outputs = midiOutGetNumDevs(); - midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs ); + midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs); if (midi_out_caps == NULL) { /* no error is reported -- see pm_winmm_general_inputs */ @@ -215,8 +219,8 @@ static void pm_winmm_general_outputs() wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i], sizeof(MIDIOUTCAPS)); if (wRtn == MMSYSERR_NOERROR) { - pm_add_device("MMSystem", midi_out_caps[i].szPname, FALSE, - (void *) i, &pm_winmm_out_dictionary); + pm_add_device_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE, + (void *) (intptr_t) i, &pm_winmm_out_dictionary); } } } @@ -231,67 +235,22 @@ static void pm_winmm_mapper_output() wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS) & midi_out_mapper_caps, sizeof(MIDIOUTCAPS)); if (wRtn == MMSYSERR_NOERROR) { - pm_add_device("MMSystem", midi_out_mapper_caps.szPname, FALSE, - (void *) MIDIMAPPER, &pm_winmm_out_dictionary); + pm_add_device_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE, + (void *) (intptr_t) MIDIMAPPER, + &pm_winmm_out_dictionary); } } /* -========================================================================================= +============================================================================ host error handling -========================================================================================= +============================================================================ */ -static unsigned int winmm_has_host_error(PmInternal * midi) -{ - midiwinmm_type m = (midiwinmm_type)midi->descriptor; - return m->error; -} - - -/* str_copy_len -- like strcat, but won't overrun the destination string */ -/* - * returns length of resulting string - */ -static int str_copy_len(char *dst, char *src, int len) -{ - strncpy(dst, src, len); - /* just in case suffex is greater then len, terminate with zero */ - dst[len - 1] = 0; - return strlen(dst); -} - -static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) +static unsigned int winmm_check_host_error(PmInternal *midi) { - /* precondition: midi != NULL */ - midiwinmm_node * m = (midiwinmm_node *) midi->descriptor; - char *hdr1 = "Host error: "; - char *hdr2 = "Host callback error: "; - - msg[0] = 0; /* initialize result string to empty */ - - if (descriptors[midi->device_id].pub.input) { - /* input and output use different winmm API calls */ - if (m) { /* make sure there is an open device to examine */ - if (m->error != MMSYSERR_NOERROR) { - int n = str_copy_len(msg, hdr1, len); - /* read and record host error */ - int err = midiInGetErrorText(m->error, msg + n, len - n); - assert(err == MMSYSERR_NOERROR); - m->error = MMSYSERR_NOERROR; - } - } - } else { /* output port */ - if (m) { - if (m->error != MMSYSERR_NOERROR) { - int n = str_copy_len(msg, hdr1, len); - int err = midiOutGetErrorText(m->error, msg + n, len - n); - assert(err == MMSYSERR_NOERROR); - m->error = MMSYSERR_NOERROR; - } - } - } + return FALSE; } @@ -314,123 +273,59 @@ static MIDIHDR *allocate_buffer(long data_size) return hdr; } -#ifdef USE_SYSEX_BUFFERS -static MIDIHDR *allocate_sysex_buffer(long data_size) -{ - /* we're actually allocating more than data_size because the buffer - * will include the MIDIEVENT header in addition to the data - */ - LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size)); - MIDIEVENT *evt; - if (!hdr) return NULL; - evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */ - hdr->lpData = (LPSTR) evt; - hdr->dwFlags = 0; - hdr->dwUser = 0; - return hdr; -} -#endif -static PmError allocate_buffers(midiwinmm_type m, long data_size, long count) +static PmError allocate_buffers(winmm_info_type info, long data_size, + long count) { int i; /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ - m->num_buffers = 0; /* in case no memory can be allocated */ - m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); - if (!m->buffers) return pmInsufficientMemory; - m->max_buffers = count; + info->num_buffers = 0; /* in case no memory can be allocated */ + info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count); + if (!info->buffers) return pmInsufficientMemory; + info->max_buffers = count; for (i = 0; i < count; i++) { LPMIDIHDR hdr = allocate_buffer(data_size); if (!hdr) { /* free everything allocated so far and return */ - for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]); - pm_free(m->buffers); - m->max_buffers = 0; + for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]); + pm_free(info->buffers); + info->max_buffers = 0; return pmInsufficientMemory; } - m->buffers[i] = hdr; /* this may be NULL if allocation fails */ + info->buffers[i] = hdr; /* this may be NULL if allocation fails */ } - m->num_buffers = count; + info->num_buffers = count; return pmNoError; } -#ifdef USE_SYSEX_BUFFERS -static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size) -{ - PmError rslt = pmNoError; - /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */ - int i; - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - LPMIDIHDR hdr = allocate_sysex_buffer(data_size); - - if (!hdr) rslt = pmInsufficientMemory; - m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */ - hdr->dwFlags = 0; /* mark as free */ - } - return rslt; -} -#endif - -#ifdef USE_SYSEX_BUFFERS -static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi) -{ - LPMIDIHDR r = NULL; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (!m->sysex_buffers[0]) { - if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) { - return NULL; - } - } - /* busy wait until we find a free buffer */ - while (TRUE) { - int i; - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */ - m->next_sysex_buffer++; - if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0; - r = m->sysex_buffers[m->next_sysex_buffer]; - if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer; - } - /* after scanning every buffer and not finding anything, block */ - if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { -#ifdef DEBUG - printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n"); -#endif - } - } -found_sysex_buffer: - r->dwBytesRecorded = 0; - r->dwBufferLength = 0; /* changed to correct value later */ - return r; -} -#endif static LPMIDIHDR get_free_output_buffer(PmInternal *midi) { LPMIDIHDR r = NULL; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; + winmm_info_type info = (winmm_info_type) midi->api_info; while (TRUE) { int i; - for (i = 0; i < m->num_buffers; i++) { - /* cycle through buffers, modulo m->num_buffers */ - m->next_buffer++; - if (m->next_buffer >= m->num_buffers) m->next_buffer = 0; - r = m->buffers[m->next_buffer]; + for (i = 0; i < info->num_buffers; i++) { + /* cycle through buffers, modulo info->num_buffers */ + info->next_buffer++; + if (info->next_buffer >= info->num_buffers) info->next_buffer = 0; + r = info->buffers[info->next_buffer]; if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer; } /* after scanning every buffer and not finding anything, block */ - if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) { -#ifdef DEBUG - printf("PortMidi warning: get_free_output_buffer() wait timed out after 1000ms\n"); + if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) { +#ifdef MMDEBUG + printf("PortMidi warning: get_free_output_buffer() " + "wait timed out after 1000ms\n"); #endif /* if we're trying to send a sysex message, maybe the * message is too big and we need more message buffers. * Expand the buffer pool by 128KB using 1024-byte buffers. */ /* first, expand the buffers array if necessary */ - if (!m->buffers_expanded) { + if (!info->buffers_expanded) { LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc( - (m->num_buffers + NUM_EXPANSION_BUFFERS) * - sizeof(LPMIDIHDR)); + (info->num_buffers + NUM_EXPANSION_BUFFERS) * + sizeof(LPMIDIHDR)); /* if no memory, we could return a no-memory error, but user * probably will be unprepared to deal with it. Maybe the * MIDI driver is temporarily hung so we should just wait. @@ -438,21 +333,21 @@ static LPMIDIHDR get_free_output_buffer(PmInternal *midi) */ if (!new_buffers) continue; /* copy buffers to new_buffers and replace buffers */ - memcpy(new_buffers, m->buffers, - m->num_buffers * sizeof(LPMIDIHDR)); - pm_free(m->buffers); - m->buffers = new_buffers; - m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS; - m->buffers_expanded = TRUE; + memcpy(new_buffers, info->buffers, + info->num_buffers * sizeof(LPMIDIHDR)); + pm_free(info->buffers); + info->buffers = new_buffers; + info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS; + info->buffers_expanded = TRUE; } /* next, add one buffer and return it */ - if (m->num_buffers < m->max_buffers) { + if (info->num_buffers < info->max_buffers) { r = allocate_buffer(EXPANSION_BUFFER_LEN); /* again, if there's no memory, we may not really be * dead -- maybe the system is temporarily hung and * we can just wait longer for a message buffer */ if (!r) continue; - m->buffers[m->num_buffers++] = r; + info->buffers[info->num_buffers++] = r; goto found_buffer; /* break out of 2 loops */ } /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers, @@ -468,63 +363,20 @@ static LPMIDIHDR get_free_output_buffer(PmInternal *midi) return r; } -#ifdef EXPANDING_SYSEX_BUFFERS -note: this is not working code, but might be useful if you want - to grow sysex buffers. -static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size) -{ - LPMIDIHDR big; - int i; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - /* buffer must be smaller than 64k, but be also a multiple of 4 */ - if (new_size > 65520) { - if (old_size >= 65520) - return pmBufferMaxSize; - else - new_size = 65520; - } - /* allocate a bigger message */ - big = allocate_sysex_buffer(new_size); - /* printf("expand to %d bytes\n", new_size);*/ - if (!big) return pmInsufficientMemory; - m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR)); - if (m->error) { - pm_free(big); - return pmHostError; - } - /* make sure we're not going to overwrite any memory */ - assert(old_size <= new_size); - memcpy(big->lpData, m->hdr->lpData, old_size); - /* keep track of how many sysex bytes are in message so far */ - big->dwBytesRecorded = m->hdr->dwBytesRecorded; - big->dwBufferLength = new_size; - /* find which buffer this was, and replace it */ - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - if (m->sysex_buffers[i] == m->hdr) { - m->sysex_buffers[i] = big; - m->sysex_buffer_size[i] = new_size; - pm_free(m->hdr); - m->hdr = big; - break; - } - } - assert(i != NUM_SYSEX_BUFFERS); - - return pmNoError; -} -#endif - /* -========================================================================================= +============================================================================ begin midi input implementation -========================================================================================= +============================================================================ */ -static PmError allocate_input_buffer(HMIDIIN h, long buffer_len) +static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len) { LPMIDIHDR hdr = allocate_buffer(buffer_len); if (!hdr) return pmInsufficientMemory; + /* note: pm_hosterror is normally a boolean, but here, we store Win + * error code. The caller must test the value for nonzero, set + * pm_hosterror_text, and then set pm_hosterror to TRUE */ pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR)); if (pm_hosterror) { pm_free(hdr); @@ -535,82 +387,121 @@ static PmError allocate_input_buffer(HMIDIIN h, long buffer_len) } +static winmm_info_type winmm_info_create() +{ + winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node)); + info->handle.in = NULL; + info->handle.out = NULL; + info->buffers = NULL; /* not used for input */ + info->num_buffers = 0; /* not used for input */ + info->max_buffers = 0; /* not used for input */ + info->buffers_expanded = FALSE; /* not used for input */ + info->next_buffer = 0; /* not used for input */ + info->buffer_signal = 0; /* not used for input */ + info->last_time = 0; + info->first_message = TRUE; /* not used for input */ + info->sysex_mode = FALSE; + info->sysex_word = 0; + info->sysex_byte_count = 0; + info->hdr = NULL; /* not used for input */ + info->sync_time = 0; + info->delta = 0; + return info; +} + + +static void report_hosterror(LPWCH error_msg) +{ + WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text, + sizeof(pm_hosterror_text), NULL, NULL); + if (pm_hosterror == MMSYSERR_NOMEM) { + /* add explanation to Window's confusing error message */ + /* if there's room: */ + if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) { + strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, + " Probably this MIDI device is open " + "in another application."); + } + } + pm_hosterror = TRUE; +} + + +static void report_hosterror_in() +{ + WCHAR error_msg[PM_HOST_ERROR_MSG_LEN]; + int err = midiInGetErrorText(pm_hosterror, error_msg, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + report_hosterror(error_msg); +} + + +static void report_hosterror_out() +{ + WCHAR error_msg[PM_HOST_ERROR_MSG_LEN]; + int err = midiOutGetErrorText(pm_hosterror, error_msg, + PM_HOST_ERROR_MSG_LEN); + assert(err == MMSYSERR_NOERROR); + report_hosterror(error_msg); +} + + static PmError winmm_in_open(PmInternal *midi, void *driverInfo) { DWORD dwDevice; int i = midi->device_id; int max_sysex_len = midi->buffer_len * 4; int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN; - midiwinmm_type m; + winmm_info_type info; - dwDevice = (DWORD) descriptors[i].descriptor; + dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor; /* create system dependent device data */ - m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ - midi->descriptor = m; - if (!m) goto no_memory; - m->handle.in = NULL; - m->buffers = NULL; /* not used for input */ - m->num_buffers = 0; /* not used for input */ - m->max_buffers = FALSE; /* not used for input */ - m->buffers_expanded = 0; /* not used for input */ - m->next_buffer = 0; /* not used for input */ - m->buffer_signal = 0; /* not used for input */ -#ifdef USE_SYSEX_BUFFERS - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) - m->sysex_buffers[i] = NULL; /* not used for input */ - m->next_sysex_buffer = 0; /* not used for input */ -#endif - m->last_time = 0; - m->first_message = TRUE; /* not used for input */ - m->sysex_mode = FALSE; - m->sysex_word = 0; - m->sysex_byte_count = 0; - m->hdr = NULL; /* not used for input */ - m->sync_time = 0; - m->delta = 0; - m->error = MMSYSERR_NOERROR; - /* 4000 is based on Windows documentation -- that's the value used in the - memory manager. It's small enough that it should not hurt performance even - if it's not optimal. + info = winmm_info_create(); + midi->api_info = info; + if (!info) goto no_memory; + /* 4000 is based on Windows documentation -- that's the value used + in the memory manager. It's small enough that it should not + hurt performance even if it's not optimal. */ - InitializeCriticalSectionAndSpinCount(&m->lock, 4000); + InitializeCriticalSectionAndSpinCount(&info->lock, 4000); /* open device */ - pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */ - dwDevice, /* device ID */ - (DWORD_PTR) winmm_in_callback, /* callback address */ - (DWORD_PTR) midi, /* callback instance data */ - CALLBACK_FUNCTION); /* callback is a procedure */ + pm_hosterror = midiInOpen( + &(info->handle.in), /* input device handle */ + dwDevice, /* device ID */ + (DWORD_PTR) winmm_in_callback, /* callback address */ + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback is a procedure */ if (pm_hosterror) goto free_descriptor; if (num_input_buffers < MIN_INPUT_BUFFERS) num_input_buffers = MIN_INPUT_BUFFERS; for (i = 0; i < num_input_buffers; i++) { - if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) { + if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) { /* either pm_hosterror was set, or the proper return code is pmInsufficientMemory */ goto close_device; } } /* start device */ - pm_hosterror = midiInStart(m->handle.in); - if (pm_hosterror) goto reset_device; - return pmNoError; + pm_hosterror = midiInStart(info->handle.in); + if (!pm_hosterror) { + return pmNoError; + } /* undo steps leading up to the detected error */ -reset_device: + /* ignore return code (we already have an error to report) */ - midiInReset(m->handle.in); + midiInReset(info->handle.in); close_device: - midiInClose(m->handle.in); /* ignore return code */ + midiInClose(info->handle.in); /* ignore return code */ free_descriptor: - midi->descriptor = NULL; - pm_free(m); + midi->api_info = NULL; + pm_free(info); no_memory: if (pm_hosterror) { - int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); + report_hosterror_in(); return pmHostError; } /* if !pm_hosterror, then the error must be pmInsufficientMemory */ @@ -620,12 +511,6 @@ static PmError winmm_in_open(PmInternal *midi, void *driverInfo) to free the parameter midi */ } -static PmError winmm_in_poll(PmInternal *midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - return m->error; -} - - /* winmm_in_close -- close an open midi input device */ /* @@ -633,24 +518,22 @@ static PmError winmm_in_poll(PmInternal *midi) { */ static PmError winmm_in_close(PmInternal *midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (!m) return pmBadPtr; + winmm_info_type info = (winmm_info_type) midi->api_info; + if (!info) return pmBadPtr; /* device to close */ - if (pm_hosterror = midiInStop(m->handle.in)) { - midiInReset(m->handle.in); /* try to reset and close port */ - midiInClose(m->handle.in); - } else if (pm_hosterror = midiInReset(m->handle.in)) { - midiInClose(m->handle.in); /* best effort to close midi port */ + if ((pm_hosterror = midiInStop(info->handle.in))) { + midiInReset(info->handle.in); /* try to reset and close port */ + midiInClose(info->handle.in); + } else if ((pm_hosterror = midiInReset(info->handle.in))) { + midiInClose(info->handle.in); /* best effort to close midi port */ } else { - pm_hosterror = midiInClose(m->handle.in); + pm_hosterror = midiInClose(info->handle.in); } - midi->descriptor = NULL; - DeleteCriticalSection(&m->lock); - pm_free(m); /* delete */ + midi->api_info = NULL; + DeleteCriticalSection(&info->lock); + pm_free(info); /* delete */ if (pm_hosterror) { - int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); + report_hosterror_in(); return pmHostError; } return pmNoError; @@ -659,19 +542,18 @@ static PmError winmm_in_close(PmInternal *midi) /* Callback function executed via midiInput SW interrupt (via midiInOpen). */ static void FAR PASCAL winmm_in_callback( - HMIDIIN hMidiIn, /* midiInput device Handle */ - WORD wMsg, /* midi msg */ - DWORD_PTR dwInstance, /* application data */ - DWORD_PTR dwParam1, /* MIDI data */ + HMIDIIN hMidiIn, /* midiInput device Handle */ + UINT wMsg, /* midi msg */ + DWORD_PTR dwInstance, /* application data */ + DWORD_PTR dwParam1, /* MIDI data */ DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */ { - static int entry = 0; PmInternal *midi = (PmInternal *) dwInstance; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; + winmm_info_type info = (winmm_info_type) midi->api_info; /* NOTE: we do not just EnterCriticalSection() here because an * MIM_CLOSE message arrives when the port is closed, but then - * the m->lock has been destroyed. + * the info->lock has been destroyed. */ switch (wMsg) { @@ -682,7 +564,7 @@ static void FAR PASCAL winmm_in_callback( * hardware interrupt? -- but I've seen reentrant behavior * using a debugger, so it happens. */ - EnterCriticalSection(&m->lock); + EnterCriticalSection(&info->lock); /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of message LOB; @@ -690,8 +572,6 @@ static void FAR PASCAL winmm_in_callback( in [ms] from when midiInStart called. each message is expanded to include the status byte */ - long new_driver_time = dwParam2; - if ((dwParam1 & 0x80) == 0) { /* not a status byte -- ignore it. This happened running the sysex.c test under Win2K with MidiMan USB 1x1 interface, @@ -702,11 +582,11 @@ static void FAR PASCAL winmm_in_callback( PmEvent event; if (midi->time_proc) dwParam2 = (*midi->time_proc)(midi->time_info); - event.timestamp = dwParam2; - event.message = dwParam1; + event.timestamp = (PmTimestamp)dwParam2; + event.message = (PmMessage)dwParam1; pm_read_short(midi, &event); } - LeaveCriticalSection(&m->lock); + LeaveCriticalSection(&info->lock); break; } case MIM_LONGDATA: { @@ -715,42 +595,38 @@ static void FAR PASCAL winmm_in_callback( unsigned int processed = 0; int remaining = lpMidiHdr->dwBytesRecorded; - EnterCriticalSection(&m->lock); + EnterCriticalSection(&info->lock); /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */ if (midi->time_proc) dwParam2 = (*midi->time_proc)(midi->time_info); /* can there be more than one message in one buffer? */ /* assume yes and iterate through them */ - while (remaining > 0) { - unsigned int amt = pm_read_bytes(midi, data + processed, - remaining, dwParam2); - remaining -= amt; - processed += amt; - } + pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2); /* when a device is closed, the pending MIM_LONGDATA buffers are returned to this callback with dwBytesRecorded == 0. In this case, we do not want to send them back to the interface (if we do, the interface will not close, and Windows OS may hang). */ if (lpMidiHdr->dwBytesRecorded > 0) { + MMRESULT rslt; lpMidiHdr->dwBytesRecorded = 0; lpMidiHdr->dwFlags = 0; /* note: no error checking -- can this actually fail? */ - assert(midiInPrepareHeader(hMidiIn, lpMidiHdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + assert(rslt == MMSYSERR_NOERROR); /* note: I don't think this can fail except possibly for * MMSYSERR_NOMEM, but the pain of reporting this * unlikely but probably catastrophic error does not seem * worth it. */ - assert(midiInAddBuffer(hMidiIn, lpMidiHdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); - LeaveCriticalSection(&m->lock); + rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR)); + assert(rslt == MMSYSERR_NOERROR); + LeaveCriticalSection(&info->lock); } else { midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR)); - LeaveCriticalSection(&m->lock); + LeaveCriticalSection(&info->lock); pm_free(lpMidiHdr); } break; @@ -771,15 +647,15 @@ static void FAR PASCAL winmm_in_callback( } /* -========================================================================================= +=========================================================================== begin midi output implementation -========================================================================================= +=========================================================================== */ /* begin helper routines used by midiOutStream interface */ /* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */ -static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, +static int add_to_buffer(winmm_info_type m, LPMIDIHDR hdr, unsigned long delta, unsigned long msg) { unsigned long *ptr = (unsigned long *) @@ -795,13 +671,13 @@ static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, } -static PmTimestamp pm_time_get(midiwinmm_type m) +static PmTimestamp pm_time_get(winmm_info_type info) { MMTIME mmtime; MMRESULT wRtn; mmtime.wType = TIME_TICKS; mmtime.u.ticks = 0; - wRtn = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime)); + wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime)); assert(wRtn == MMSYSERR_NOERROR); return mmtime.u.ticks; } @@ -814,62 +690,41 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) { DWORD dwDevice; int i = midi->device_id; - midiwinmm_type m; + winmm_info_type info; MIDIPROPTEMPO propdata; MIDIPROPTIMEDIV divdata; int max_sysex_len = midi->buffer_len * 4; int output_buffer_len; int num_buffers; - dwDevice = (DWORD) descriptors[i].descriptor; + dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor; /* create system dependent device data */ - m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */ - midi->descriptor = m; - if (!m) goto no_memory; - m->handle.out = NULL; - m->buffers = NULL; - m->num_buffers = 0; - m->max_buffers = 0; - m->buffers_expanded = FALSE; - m->next_buffer = 0; -#ifdef USE_SYSEX_BUFFERS - m->sysex_buffers[0] = NULL; - m->sysex_buffers[1] = NULL; - m->next_sysex_buffer = 0; -#endif - m->last_time = 0; - m->first_message = TRUE; /* we treat first message as special case */ - m->sysex_mode = FALSE; - m->sysex_word = 0; - m->sysex_byte_count = 0; - m->hdr = NULL; - m->sync_time = 0; - m->delta = 0; - m->error = MMSYSERR_NOERROR; - + info = winmm_info_create(); + midi->api_info = info; + if (!info) goto no_memory; /* create a signal */ - m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); - + info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL); /* this should only fail when there are very serious problems */ - assert(m->buffer_signal); - + assert(info->buffer_signal); /* open device */ if (midi->latency == 0) { /* use simple midi out calls */ - pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out, /* device Handle */ - dwDevice, /* device ID */ - /* note: same callback fn as for StreamOpen: */ - (DWORD_PTR) winmm_streamout_callback, /* callback fn */ - (DWORD_PTR) midi, /* callback instance data */ - CALLBACK_FUNCTION); /* callback type */ + pm_hosterror = midiOutOpen( + (LPHMIDIOUT) & info->handle.out, /* device Handle */ + dwDevice, /* device ID */ + /* note: same callback fn as for StreamOpen: */ + (DWORD_PTR) winmm_streamout_callback, /* callback fn */ + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); /* callback type */ } else { /* use stream-based midi output (schedulable in future) */ - pm_hosterror = midiStreamOpen(&m->handle.stream, /* device Handle */ - (LPUINT) & dwDevice, /* device ID pointer */ - 1, /* reserved, must be 1 */ - (DWORD_PTR) winmm_streamout_callback, - (DWORD_PTR) midi, /* callback instance data */ - CALLBACK_FUNCTION); + pm_hosterror = midiStreamOpen( + &info->handle.stream, /* device Handle */ + (LPUINT) & dwDevice, /* device ID pointer */ + 1, /* reserved, must be 1 */ + (DWORD_PTR) winmm_streamout_callback, + (DWORD_PTR) midi, /* callback instance data */ + CALLBACK_FUNCTION); } if (pm_hosterror != MMSYSERR_NOERROR) { goto free_descriptor; @@ -881,7 +736,6 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN) output_buffer_len = MIN_SIMPLE_SYSEX_LEN; } else { - long dur = 0; num_buffers = max(midi->buffer_len, midi->latency / 2); if (num_buffers < MIN_STREAM_BUFFERS) num_buffers = MIN_STREAM_BUFFERS; @@ -889,24 +743,24 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) propdata.cbStruct = sizeof(MIDIPROPTEMPO); propdata.dwTempo = 480000; /* microseconds per quarter */ - pm_hosterror = midiStreamProperty(m->handle.stream, + pm_hosterror = midiStreamProperty(info->handle.stream, (LPBYTE) & propdata, MIDIPROP_SET | MIDIPROP_TEMPO); if (pm_hosterror) goto close_device; divdata.cbStruct = sizeof(MIDIPROPTEMPO); divdata.dwTimeDiv = 480; /* divisions per quarter */ - pm_hosterror = midiStreamProperty(m->handle.stream, + pm_hosterror = midiStreamProperty(info->handle.stream, (LPBYTE) & divdata, MIDIPROP_SET | MIDIPROP_TIMEDIV); if (pm_hosterror) goto close_device; } /* allocate buffers */ - if (allocate_buffers(m, output_buffer_len, num_buffers)) + if (allocate_buffers(info, output_buffer_len, num_buffers)) goto free_buffers; /* start device */ if (midi->latency != 0) { - pm_hosterror = midiStreamRestart(m->handle.stream); + pm_hosterror = midiStreamRestart(info->handle.stream); if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers; } return pmNoError; @@ -914,15 +768,13 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) free_buffers: /* buffers are freed below by winmm_out_delete */ close_device: - midiOutClose(m->handle.out); + midiOutClose(info->handle.out); free_descriptor: - midi->descriptor = NULL; + midi->api_info = NULL; winmm_out_delete(midi); /* frees buffers and m */ no_memory: if (pm_hosterror) { - int err = midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); + report_hosterror_out(); return pmHostError; } return pmInsufficientMemory; @@ -935,50 +787,41 @@ static void winmm_out_delete(PmInternal *midi) { int i; /* delete system dependent device data */ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (m) { - if (m->buffer_signal) { + winmm_info_type info = (winmm_info_type) midi->api_info; + if (info) { + if (info->buffer_signal) { /* don't report errors -- better not to stop cleanup */ - CloseHandle(m->buffer_signal); + CloseHandle(info->buffer_signal); } /* if using stream output, free buffers */ - for (i = 0; i < m->num_buffers; i++) { - if (m->buffers[i]) pm_free(m->buffers[i]); - } - m->num_buffers = 0; - pm_free(m->buffers); - m->max_buffers = 0; -#ifdef USE_SYSEX_BUFFERS - /* free sysex buffers */ - for (i = 0; i < NUM_SYSEX_BUFFERS; i++) { - if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]); + for (i = 0; i < info->num_buffers; i++) { + if (info->buffers[i]) pm_free(info->buffers[i]); } -#endif + info->num_buffers = 0; + pm_free(info->buffers); + info->max_buffers = 0; } - midi->descriptor = NULL; - pm_free(m); /* delete */ + midi->api_info = NULL; + pm_free(info); /* delete */ } /* see comments for winmm_in_close */ static PmError winmm_out_close(PmInternal *midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (m->handle.out) { + winmm_info_type info = (winmm_info_type) midi->api_info; + if (info->handle.out) { /* device to close */ if (midi->latency == 0) { - pm_hosterror = midiOutClose(m->handle.out); + pm_hosterror = midiOutClose(info->handle.out); } else { - pm_hosterror = midiStreamClose(m->handle.stream); + pm_hosterror = midiStreamClose(info->handle.stream); } /* regardless of outcome, free memory */ winmm_out_delete(midi); } if (pm_hosterror) { - int err = midiOutGetErrorText(pm_hosterror, - (char *) pm_hosterror_text, - PM_HOST_ERROR_MSG_LEN); - assert(err == MMSYSERR_NOERROR); + report_hosterror_out(); return pmHostError; } return pmNoError; @@ -987,141 +830,86 @@ static PmError winmm_out_close(PmInternal *midi) static PmError winmm_out_abort(PmInternal *midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - m->error = MMSYSERR_NOERROR; + winmm_info_type info = (winmm_info_type) midi->api_info; /* only stop output streams */ if (midi->latency > 0) { - m->error = midiStreamStop(m->handle.stream); + pm_hosterror = midiStreamStop(info->handle.stream); + if (pm_hosterror) { + report_hosterror_out(); + return pmHostError; + } } - return m->error ? pmHostError : pmNoError; + return pmNoError; } static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - assert(m); - if (m->hdr) { - m->error = midiOutPrepareHeader(m->handle.out, m->hdr, - sizeof(MIDIHDR)); - if (m->error) { + winmm_info_type info = (winmm_info_type) midi->api_info; + assert(info); + if (info->hdr) { + pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr, + sizeof(MIDIHDR)); + if (pm_hosterror) { /* do not send message */ } else if (midi->latency == 0) { /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded * should be zero. This is set in get_free_sysex_buffer(). * The msg length goes in dwBufferLength in spite of what * Microsoft documentation says (or doesn't say). */ - m->hdr->dwBufferLength = m->hdr->dwBytesRecorded; - m->hdr->dwBytesRecorded = 0; - m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); + info->hdr->dwBufferLength = info->hdr->dwBytesRecorded; + info->hdr->dwBytesRecorded = 0; + pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr, + sizeof(MIDIHDR)); } else { - m->error = midiStreamOut(m->handle.stream, m->hdr, - sizeof(MIDIHDR)); + pm_hosterror = midiStreamOut(info->handle.stream, info->hdr, + sizeof(MIDIHDR)); } midi->fill_base = NULL; - m->hdr = NULL; - if (m->error) { - m->hdr->dwFlags = 0; /* release the buffer */ - return pmHostError; - } - } - return pmNoError; -} - - - -#ifdef GARBAGE -static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte) -{ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - unsigned char *msg_buffer; - - /* at the beginning of sysex, m->hdr is NULL */ - if (!m->hdr) { /* allocate a buffer if none allocated yet */ - m->hdr = get_free_output_buffer(midi); - if (!m->hdr) return pmInsufficientMemory; - m->sysex_byte_count = 0; - } - /* figure out where to write byte */ - msg_buffer = (unsigned char *) (m->hdr->lpData); - assert(m->hdr->lpData == (char *) (m->hdr + 1)); - - /* check for overflow */ - if (m->sysex_byte_count >= m->hdr->dwBufferLength) { - /* allocate a bigger message -- double it every time */ - LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2); - /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */ - if (!big) return pmInsufficientMemory; - m->error = midiOutPrepareHeader(m->handle.out, big, - sizeof(MIDIHDR)); - if (m->error) { - m->hdr = NULL; + info->hdr = NULL; + if (pm_hosterror) { + report_hosterror_out(); return pmHostError; } - memcpy(big->lpData, msg_buffer, m->sysex_byte_count); - msg_buffer = (unsigned char *) (big->lpData); - if (m->buffers[0] == m->hdr) { - m->buffers[0] = big; - pm_free(m->hdr); - /* printf("freed m->hdr\n"); */ - } else if (m->buffers[1] == m->hdr) { - m->buffers[1] = big; - pm_free(m->hdr); - /* printf("freed m->hdr\n"); */ - } - m->hdr = big; - } - - /* append byte to message */ - msg_buffer[m->sysex_byte_count++] = byte; - - /* see if we have a complete message */ - if (byte == MIDI_EOX) { - m->hdr->dwBytesRecorded = m->sysex_byte_count; - /* - { int i; int len = m->hdr->dwBytesRecorded; - printf("OutLongMsg %d ", len); - for (i = 0; i < len; i++) { - printf("%2x ", msg_buffer[i]); - } - } - */ - m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR)); - m->hdr = NULL; /* stop using this message buffer */ - if (m->error) return pmHostError; } return pmNoError; } -#endif static PmError winmm_write_short(PmInternal *midi, PmEvent *event) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; + winmm_info_type info = (winmm_info_type) midi->api_info; PmError rslt = pmNoError; - assert(m); + assert(info); if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */ - m->error = midiOutShortMsg(m->handle.out, event->message); - if (m->error) rslt = pmHostError; + pm_hosterror = midiOutShortMsg(info->handle.out, event->message); + if (pm_hosterror) { + if (info->hdr) { /* device disconnect may delete hdr */ + info->hdr->dwFlags = 0; /* release the buffer */ + } + report_hosterror_out(); + return pmHostError; + } } else { /* use midiStream interface -- pass data through buffers */ unsigned long when = event->timestamp; unsigned long delta; int full; if (when == 0) when = midi->now; /* when is in real_time; translate to intended stream time */ - when = when + m->delta + midi->latency; + when = when + info->delta + midi->latency; /* make sure we don't go backward in time */ - if (when < m->last_time) when = m->last_time; - delta = when - m->last_time; - m->last_time = when; + if (when < info->last_time) when = info->last_time; + delta = when - info->last_time; + info->last_time = when; /* before we insert any data, we must have a buffer */ - if (m->hdr == NULL) { + if (info->hdr == NULL) { /* stream interface: buffers allocated when stream is opened */ - m->hdr = get_free_output_buffer(midi); + info->hdr = get_free_output_buffer(midi); } - full = add_to_buffer(m, m->hdr, delta, event->message); + full = add_to_buffer(info, info->hdr, delta, event->message); + /* note: winmm_write_flush sets pm_hosterror etc. on host error */ if (full) rslt = winmm_write_flush(midi, when); } return rslt; @@ -1131,7 +919,7 @@ static PmError winmm_write_short(PmInternal *midi, PmEvent *event) #ifndef winmm_begin_sysex static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; + winmm_info_type m = (winmm_info_type) midi->api_info; PmError rslt = pmNoError; if (midi->latency == 0) { @@ -1150,9 +938,9 @@ static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) * what happens if we exit early and don't finish the sysex msg * and clean up */ - midiwinmm_type m = (midiwinmm_type) midi->descriptor; + winmm_info_type info = (winmm_info_type) midi->api_info; PmError rslt = pmNoError; - LPMIDIHDR hdr = m->hdr; + LPMIDIHDR hdr = info->hdr; if (!hdr) return rslt; /* something bad happened earlier, do not report an error because it would have been reported (at least) once already */ @@ -1162,15 +950,15 @@ static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp) if (midi->latency == 0) { #ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX /* DEBUG CODE: */ - { int i; int len = m->hdr->dwBufferLength; + { int i; int len = info->hdr->dwBufferLength; printf("OutLongMsg %d ", len); for (i = 0; i < len; i++) { - printf("%2x ", (unsigned char) (m->hdr->lpData[i])); + printf("%2x ", (unsigned char) (info->hdr->lpData[i])); } } #endif } else { - /* Using stream interface. There are accumulated bytes in m->hdr + /* Using stream interface. There are accumulated bytes in info->hdr to send using midiStreamOut */ /* add bytes recorded to MIDIEVENT length, but don't @@ -1190,15 +978,15 @@ static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, { /* write a sysex byte */ PmError rslt = pmNoError; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - LPMIDIHDR hdr = m->hdr; + winmm_info_type info = (winmm_info_type) midi->api_info; + LPMIDIHDR hdr = info->hdr; unsigned char *msg_buffer; - assert(m); + assert(info); if (!hdr) { - m->hdr = hdr = get_free_output_buffer(midi); + info->hdr = hdr = get_free_output_buffer(midi); assert(hdr); - midi->fill_base = (unsigned char *) m->hdr->lpData; - midi->fill_offset_ptr = &(hdr->dwBytesRecorded); + midi->fill_base = (unsigned char *) info->hdr->lpData; + midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded); /* when buffer fills, Pm_WriteSysEx will revert to calling * pmwin_write_byte, which expect to have space, so leave * one byte free for pmwin_write_byte. Leave another byte @@ -1211,11 +999,11 @@ static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, unsigned long *ptr; if (when == 0) when = midi->now; /* when is in real_time; translate to intended stream time */ - when = when + m->delta + midi->latency; + when = when + info->delta + midi->latency; /* make sure we don't go backward in time */ - if (when < m->last_time) when = m->last_time; - delta = when - m->last_time; - m->last_time = when; + if (when < info->last_time) when = info->last_time; + delta = when - info->last_time; + info->last_time = when; ptr = (unsigned long *) hdr->lpData; *ptr++ = delta; @@ -1237,31 +1025,10 @@ static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, return rslt; } -#ifdef EXPANDING_SYSEX_BUFFERS -note: this code is here as an aid in case you want sysex buffers - to expand to hold large messages completely. If so, you - will want to change SYSEX_BYTES_PER_BUFFER above to some - variable that remembers the buffer size. A good place to - put this value would be in the hdr->dwUser field. - - rslt = resize_sysex_buffer(midi, m->sysex_byte_count, - m->sysex_byte_count * 2); - - if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */ -#endif -#ifdef EXPANDING_SYSEX_BUFFERS - int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */ - rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded); - hdr->dwBytesRecorded = bytesRecorded; - - if (rslt == pmBufferMaxSize) /* if buffer can't be resized */ -#endif - - static PmTimestamp winmm_synchronize(PmInternal *midi) { - midiwinmm_type m; + winmm_info_type info; unsigned long pm_stream_time_2; unsigned long real_time; unsigned long pm_stream_time; @@ -1270,54 +1037,28 @@ static PmTimestamp winmm_synchronize(PmInternal *midi) if (midi->latency == 0) return 0; /* figure out the time */ - m = (midiwinmm_type) midi->descriptor; - pm_stream_time_2 = pm_time_get(m); + info = (winmm_info_type) midi->api_info; + pm_stream_time_2 = pm_time_get(info); do { /* read real_time between two reads of stream time */ pm_stream_time = pm_stream_time_2; real_time = (*midi->time_proc)(midi->time_info); - pm_stream_time_2 = pm_time_get(m); + pm_stream_time_2 = pm_time_get(info); /* repeat if more than 1ms elapsed */ } while (pm_stream_time_2 > pm_stream_time + 1); - m->delta = pm_stream_time - real_time; - m->sync_time = real_time; + info->delta = pm_stream_time - real_time; + info->sync_time = real_time; return real_time; } -#ifdef USE_SYSEX_BUFFERS -/* winmm_out_callback -- recycle sysex buffers */ -static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg, - DWORD_PTR dwInstance, DWORD_PTR dwParam1, - DWORD_PTR dwParam2) -{ - PmInternal *midi = (PmInternal *) dwInstance; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; - int err = 0; /* set to 0 so that no buffer match will also be an error */ - - /* Future optimization: eliminate UnprepareHeader calls -- they aren't - necessary; however, this code uses the prepared-flag to indicate which - buffers are free, so we need to do something to flag empty buffers if - we leave them prepared - */ - printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", - hdr, wMsg, MOM_DONE); - if (wMsg == MOM_DONE) - assert(midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); - /* notify waiting sender that a buffer is available */ - err = SetEvent(m->buffer_signal); - assert(err); /* false -> error */ -} -#endif /* winmm_streamout_callback -- unprepare (free) buffer header */ static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { PmInternal *midi = (PmInternal *) dwInstance; - midiwinmm_type m = (midiwinmm_type) midi->descriptor; + winmm_info_type info = (winmm_info_type) midi->api_info; LPMIDIHDR hdr = (LPMIDIHDR) dwParam1; int err; @@ -1327,19 +1068,36 @@ static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", hdr, wMsg, MOM_DONE); */ if (wMsg == MOM_DONE) { - assert(midiOutUnprepareHeader(m->handle.out, hdr, - sizeof(MIDIHDR)) == MMSYSERR_NOERROR); + MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr, + sizeof(MIDIHDR)); + assert(ret == MMSYSERR_NOERROR); + } else if (wMsg == MOM_CLOSE) { + /* The streaming API gets a callback when the device is closed. + * The non-streaming API gets a callback when the device is + * removed or closed. It is misleading to set is_removed when + * the device is closed normally, but in that case, midi itself + * will be freed immediately, so there should be no way to + * observe is_removed == TRUE. On the other hand, if the device + * is removed, setting is_removed will cause PortMidi to return + * the pmDeviceRemoved error on attempts to output to the device. + * In the case of normal closing, due to midiOutClose(), + * the call below is reentrant (!), but for some reason this does + * not cause an error or infinite recursion, so we are not taking + * any precautions to flag midi as "in the process of closing." + */ + midi->is_removed = TRUE; + midiOutClose(info->handle.out); } /* signal client in case it is blocked waiting for buffer */ - err = SetEvent(m->buffer_signal); + err = SetEvent(info->buffer_signal); assert(err); /* false -> error */ } /* -========================================================================================= +=========================================================================== begin exported functions -========================================================================================= +=========================================================================== */ #define winmm_in_abort pm_fail_fn @@ -1354,9 +1112,8 @@ pm_fns_node pm_winmm_in_dictionary = { winmm_in_open, winmm_in_abort, winmm_in_close, - winmm_in_poll, - winmm_has_host_error, - winmm_get_host_error + success_poll, + winmm_check_host_error }; pm_fns_node pm_winmm_out_dictionary = { @@ -1364,15 +1121,14 @@ pm_fns_node pm_winmm_out_dictionary = { winmm_begin_sysex, winmm_end_sysex, winmm_write_byte, - winmm_write_short, /* short realtime message */ + /* short realtime message: */ winmm_write_short, winmm_write_flush, winmm_synchronize, winmm_out_open, winmm_out_abort, winmm_out_close, none_poll, - winmm_has_host_error, - winmm_get_host_error + winmm_check_host_error }; @@ -1401,30 +1157,21 @@ void pm_winmm_init( void ) void pm_winmm_term( void ) { int i; -#ifdef DEBUG - char msg[PM_HOST_ERROR_MSG_LEN]; -#endif +#ifdef MMDEBUG int doneAny = 0; -#ifdef DEBUG printf("pm_winmm_term called\n"); #endif - for (i = 0; i < pm_descriptor_index; i++) { - PmInternal * midi = descriptors[i].internalDescriptor; + for (i = 0; i < pm_descriptor_len; i++) { + PmInternal *midi = pm_descriptors[i].pm_internal; if (midi) { - midiwinmm_type m = (midiwinmm_type) midi->descriptor; - if (m->handle.out) { + winmm_info_type info = (winmm_info_type) midi->api_info; + if (info->handle.out) { /* close next open device*/ -#ifdef DEBUG +#ifdef MMDEBUG if (doneAny == 0) { printf("begin closing open devices...\n"); doneAny = 1; } - /* report any host errors; this EXTEREMELY useful when - trying to debug client app */ - if (winmm_has_host_error(midi)) { - winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN); - printf("%s\n", msg); - } #endif /* close all open ports */ (*midi->dictionary->close)(midi); @@ -1439,11 +1186,11 @@ void pm_winmm_term( void ) pm_free(midi_out_caps); midi_out_caps = NULL; } -#ifdef DEBUG +#ifdef MMDEBUG if (doneAny) { printf("warning: devices were left open. They have been closed.\n"); } printf("pm_winmm_term exiting\n"); #endif - pm_descriptor_index = 0; + pm_descriptor_len = 0; } diff --git a/portmidi/pm_win/pmwinmm.h b/portmidi/pm_win/pmwinmm.h index 53c5fe2..4a353e2 100644 --- a/portmidi/pm_win/pmwinmm.h +++ b/portmidi/pm_win/pmwinmm.h @@ -1,4 +1,4 @@ -/* midiwin32.h -- system-specific definitions */ +/* pmwinmm.h -- system-specific definitions for windows multimedia API */ void pm_winmm_init( void ); void pm_winmm_term( void ); diff --git a/portmidi/porttime/porttime.h b/portmidi/porttime/porttime.h index eff0f78..0a61c5c 100644 --- a/portmidi/porttime/porttime.h +++ b/portmidi/porttime/porttime.h @@ -1,72 +1,103 @@ -/* porttime.h -- portable interface to millisecond timer */ - -/* CHANGE LOG FOR PORTTIME - 10-Jun-03 Mark Nelson & RBD - boost priority of timer thread in ptlinux.c implementation - */ - -/* Should there be a way to choose the source of time here? */ - -#ifdef __cplusplus -extern "C" { -#endif - - -typedef enum { - ptNoError = 0, /* success */ - ptHostError = -10000, /* a system-specific error occurred */ - ptAlreadyStarted, /* cannot start timer because it is already started */ - ptAlreadyStopped, /* cannot stop timer because it is already stopped */ - ptInsufficientMemory /* memory could not be allocated */ -} PtError; - - -typedef long PtTimestamp; - -typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); - -/* - Pt_Start() starts a real-time service. - - resolution is the timer resolution in ms. The time will advance every - resolution ms. - - callback is a function pointer to be called every resolution ms. - - userData is passed to callback as a parameter. - - return value: - Upon success, returns ptNoError. See PtError for other values. -*/ -PtError Pt_Start(int resolution, PtCallback *callback, void *userData); - -/* - Pt_Stop() stops the timer. - - return value: - Upon success, returns ptNoError. See PtError for other values. -*/ -PtError Pt_Stop(); - -/* - Pt_Started() returns true iff the timer is running. -*/ -int Pt_Started(); - -/* - Pt_Time() returns the current time in ms. -*/ -PtTimestamp Pt_Time(); - -/* - Pt_Sleep() pauses, allowing other threads to run. - - duration is the length of the pause in ms. The true duration - of the pause may be rounded to the nearest or next clock tick - as determined by resolution in Pt_Start(). -*/ -void Pt_Sleep(long duration); - -#ifdef __cplusplus -} -#endif +/** @file porttime.h portable interface to millisecond timer. */ + +/* CHANGE LOG FOR PORTTIME + 10-Jun-03 Mark Nelson & RBD + boost priority of timer thread in ptlinux.c implementation + */ + +#ifndef PORTMIDI_PORTTIME_H +#define PORTMIDI_PORTTIME_H + +/* Should there be a way to choose the source of time here? */ + +#ifdef WIN32 +#ifndef INT32_DEFINED +// rather than having users install a special .h file for windows, +// just put the required definitions inline here. portmidi.h uses +// these too, so the definitions are (unfortunately) duplicated there +typedef int int32_t; +typedef unsigned int uint32_t; +#define INT32_DEFINED +#endif +#else +#include // needed for int32_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PMEXPORT +#ifdef _WINDLL +#define PMEXPORT __declspec(dllexport) +#else +#define PMEXPORT +#endif +#endif + +/** @defgroup grp_porttime PortTime: Millisecond Timer + @{ +*/ + +typedef enum { + ptNoError = 0, /* success */ + ptHostError = -10000, /* a system-specific error occurred */ + ptAlreadyStarted, /* cannot start timer because it is already started */ + ptAlreadyStopped, /* cannot stop timer because it is already stopped */ + ptInsufficientMemory /* memory could not be allocated */ +} PtError; /**< @brief @enum PtError PortTime error code; a common return type. + * No error is indicated by zero; errors are indicated by < 0. + */ + +/** real time or time offset in milliseconds. */ +typedef int32_t PtTimestamp; + +/** a function that gets a current time */ +typedef void (PtCallback)(PtTimestamp timestamp, void *userData); + +/** start a real-time clock service. + + @param resolution the timer resolution in ms. The time will advance every + \p resolution ms. + + @param callback a function pointer to be called every resolution ms. + + @param userData is passed to \p callback as a parameter. + + @return #ptNoError on success. See #PtError for other values. +*/ +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData); + +/** stop the timer. + + @return #ptNoError on success. See #PtError for other values. +*/ +PMEXPORT PtError Pt_Stop(void); + +/** test if the timer is running. + + @return TRUE or FALSE +*/ +PMEXPORT int Pt_Started(void); + +/** get the current time in ms. + + @return the current time +*/ +PMEXPORT PtTimestamp Pt_Time(void); + +/** pauses the current thread, allowing other threads to run. + + @param duration the length of the pause in ms. The true duration + of the pause may be rounded to the nearest or next clock tick + as determined by resolution in #Pt_Start(). +*/ +PMEXPORT void Pt_Sleep(int32_t duration); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif // PORTMIDI_PORTTIME_H diff --git a/portmidi/porttime/ptlinux.c b/portmidi/porttime/ptlinux.c index 4bc6a7c..c4af5c1 100644 --- a/portmidi/porttime/ptlinux.c +++ b/portmidi/porttime/ptlinux.c @@ -1,133 +1,139 @@ -/* ptlinux.c -- portable timer implementation for linux */ - - -/* IMPLEMENTATION NOTES (by Mark Nelson): - -Unlike Windows, Linux has no system call to request a periodic callback, -so if Pt_Start() receives a callback parameter, it must create a thread -that wakes up periodically and calls the provided callback function. -If running as superuser, use setpriority() to renice thread to -20. -One could also set the timer thread to a real-time priority (SCHED_FIFO -and SCHED_RR), but this is dangerous for This is necessary because -if the callback hangs it'll never return. A more serious reason -is that the current scheduler implementation busy-waits instead -of sleeping when realtime threads request a sleep of <=2ms (as a way -to get around the 10ms granularity), which means the thread would never -let anyone else on the CPU. - -CHANGE LOG - -18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer - thread. Simplified implementation notes. - -*/ -/* stdlib, stdio, unistd, and sys/types were added because they appeared - * in a Gentoo patch, but I'm not sure why they are needed. -RBD - */ -#include -#include -#include -#include -#include "porttime.h" -#include "sys/time.h" -#include "sys/resource.h" -#include "sys/timeb.h" -#include "pthread.h" - -#define TRUE 1 -#define FALSE 0 - -static int time_started_flag = FALSE; -static struct timeb time_offset = {0, 0, 0, 0}; -static pthread_t pt_thread_pid; - -/* note that this is static data -- we only need one copy */ -typedef struct { - int id; - int resolution; - PtCallback *callback; - void *userData; -} pt_callback_parameters; - -static int pt_callback_proc_id = 0; - -static void *Pt_CallbackProc(void *p) -{ - pt_callback_parameters *parameters = (pt_callback_parameters *) p; - int mytime = 1; - /* to kill a process, just increment the pt_callback_proc_id */ - /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, - parameters->id); */ - if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); - while (pt_callback_proc_id == parameters->id) { - /* wait for a multiple of resolution ms */ - struct timeval timeout; - int delay = mytime++ * parameters->resolution - Pt_Time(); - if (delay < 0) delay = 0; - timeout.tv_sec = 0; - timeout.tv_usec = delay * 1000; - select(0, NULL, NULL, NULL, &timeout); - (*(parameters->callback))(Pt_Time(), parameters->userData); - } - /* printf("Pt_CallbackProc exiting\n"); */ - // free(parameters); - return NULL; -} - - -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) -{ - if (time_started_flag) return ptNoError; - ftime(&time_offset); /* need this set before process runs */ - if (callback) { - int res; - pt_callback_parameters *parms = (pt_callback_parameters *) - malloc(sizeof(pt_callback_parameters)); - if (!parms) return ptInsufficientMemory; - parms->id = pt_callback_proc_id; - parms->resolution = resolution; - parms->callback = callback; - parms->userData = userData; - res = pthread_create(&pt_thread_pid, NULL, - Pt_CallbackProc, parms); - if (res != 0) return ptHostError; - } - time_started_flag = TRUE; - return ptNoError; -} - - -PtError Pt_Stop() -{ - /* printf("Pt_Stop called\n"); */ - pt_callback_proc_id++; - pthread_join(pt_thread_pid, NULL); - time_started_flag = FALSE; - return ptNoError; -} - - -int Pt_Started() -{ - return time_started_flag; -} - - -PtTimestamp Pt_Time() -{ - long seconds, milliseconds; - struct timeb now; - ftime(&now); - seconds = now.time - time_offset.time; - milliseconds = now.millitm - time_offset.millitm; - return seconds * 1000 + milliseconds; -} - - -void Pt_Sleep(long duration) -{ - usleep(duration * 1000); -} - - - +/* ptlinux.c -- portable timer implementation for linux */ + + +/* IMPLEMENTATION NOTES (by Mark Nelson): + +Unlike Windows, Linux has no system call to request a periodic callback, +so if Pt_Start() receives a callback parameter, it must create a thread +that wakes up periodically and calls the provided callback function. +If running as superuser, use setpriority() to renice thread to -20. +One could also set the timer thread to a real-time priority (SCHED_FIFO +and SCHED_RR), but this is dangerous for This is necessary because +if the callback hangs it'll never return. A more serious reason +is that the current scheduler implementation busy-waits instead +of sleeping when realtime threads request a sleep of <=2ms (as a way +to get around the 10ms granularity), which means the thread would never +let anyone else on the CPU. + +CHANGE LOG + +18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer + thread. Simplified implementation notes. + +*/ +/* stdlib, stdio, unistd, and sys/types were added because they appeared + * in a Gentoo patch, but I'm not sure why they are needed. -RBD + */ +#include +#include +#include +#include +#include "porttime.h" +#include "time.h" +#include "sys/resource.h" +#include "pthread.h" + +#define TRUE 1 +#define FALSE 0 + +#ifndef CLOCK_MONOTONIC_RAW +#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +#endif + +static int time_started_flag = FALSE; +static struct timespec time_offset = {0, 0}; +static pthread_t pt_thread_pid; +static int pt_thread_created = FALSE; + +/* note that this is static data -- we only need one copy */ +typedef struct { + int id; + int resolution; + PtCallback *callback; + void *userData; +} pt_callback_parameters; + +static int pt_callback_proc_id = 0; + +static void *Pt_CallbackProc(void *p) +{ + pt_callback_parameters *parameters = (pt_callback_parameters *) p; + int mytime = 1; + /* to kill a process, just increment the pt_callback_proc_id */ + /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, + parameters->id); */ + if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20); + while (pt_callback_proc_id == parameters->id) { + /* wait for a multiple of resolution ms */ + struct timeval timeout; + int delay = mytime++ * parameters->resolution - Pt_Time(); + if (delay < 0) delay = 0; + timeout.tv_sec = 0; + timeout.tv_usec = delay * 1000; + select(0, NULL, NULL, NULL, &timeout); + (*(parameters->callback))(Pt_Time(), parameters->userData); + } + /* printf("Pt_CallbackProc exiting\n"); */ + // free(parameters); + return NULL; +} + + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + if (time_started_flag) return ptNoError; + /* need this set before process runs: */ + clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset); + if (callback) { + int res; + pt_callback_parameters *parms = (pt_callback_parameters *) + malloc(sizeof(pt_callback_parameters)); + if (!parms) return ptInsufficientMemory; + parms->id = pt_callback_proc_id; + parms->resolution = resolution; + parms->callback = callback; + parms->userData = userData; + res = pthread_create(&pt_thread_pid, NULL, + Pt_CallbackProc, parms); + if (res != 0) return ptHostError; + pt_thread_created = TRUE; + } + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + /* printf("Pt_Stop called\n"); */ + pt_callback_proc_id++; + if (pt_thread_created) { + pthread_join(pt_thread_pid, NULL); + pt_thread_created = FALSE; + } + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + long seconds, ms; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC_RAW, &now); + seconds = now.tv_sec - time_offset.tv_sec; + ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */ + return seconds * 1000 + ms; +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/portmidi/porttime/ptmacosx_cf.c b/portmidi/porttime/ptmacosx_cf.c new file mode 100644 index 0000000..a174c86 --- /dev/null +++ b/portmidi/porttime/ptmacosx_cf.c @@ -0,0 +1,140 @@ +/* ptmacosx.c -- portable timer implementation for mac os x */ + +#include +#include +#include +#include + +#import +#import +#import +#import + +#include "porttime.h" + +#define THREAD_IMPORTANCE 30 +#define LONG_TIME 1000000000.0 + +static int time_started_flag = FALSE; +static CFAbsoluteTime startTime = 0.0; +static CFRunLoopRef timerRunLoop; + +typedef struct { + int resolution; + PtCallback *callback; + void *userData; +} PtThreadParams; + + +void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info) +{ + PtThreadParams *params = (PtThreadParams*)info; + (*params->callback)(Pt_Time(), params->userData); +} + +static void* Pt_Thread(void *p) +{ + CFTimeInterval timerInterval; + CFRunLoopTimerContext timerContext; + CFRunLoopTimerRef timer; + PtThreadParams *params = (PtThreadParams*)p; + //CFTimeInterval timeout; + + /* raise the thread's priority */ + kern_return_t error; + thread_extended_policy_data_t extendedPolicy; + thread_precedence_policy_data_t precedencePolicy; + + extendedPolicy.timeshare = 0; + error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, + (thread_policy_t)&extendedPolicy, + THREAD_EXTENDED_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread timeshare policy", error); + } + + precedencePolicy.importance = THREAD_IMPORTANCE; + error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, + (thread_policy_t)&precedencePolicy, + THREAD_PRECEDENCE_POLICY_COUNT); + if (error != KERN_SUCCESS) { + mach_error("Couldn't set thread precedence policy", error); + } + + /* set up the timer context */ + timerContext.version = 0; + timerContext.info = params; + timerContext.retain = NULL; + timerContext.release = NULL; + timerContext.copyDescription = NULL; + + /* create a new timer */ + timerInterval = (double)params->resolution / 1000.0; + timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval, + 0, 0, Pt_CFTimerCallback, &timerContext); + + timerRunLoop = CFRunLoopGetCurrent(); + CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode")); + + /* run until we're told to stop by Pt_Stop() */ + CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false); + + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode")); + CFRelease(timer); + free(params); + + return NULL; +} + +PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +{ + PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams)); + pthread_t pthread_id; + + printf("Pt_Start() called\n"); + + // /* make sure we're not already playing */ + if (time_started_flag) return ptAlreadyStarted; + startTime = CFAbsoluteTimeGetCurrent(); + + if (callback) { + + params->resolution = resolution; + params->callback = callback; + params->userData = userData; + + pthread_create(&pthread_id, NULL, Pt_Thread, params); + } + + time_started_flag = TRUE; + return ptNoError; +} + + +PtError Pt_Stop() +{ + printf("Pt_Stop called\n"); + + CFRunLoopStop(timerRunLoop); + time_started_flag = FALSE; + return ptNoError; +} + + +int Pt_Started() +{ + return time_started_flag; +} + + +PtTimestamp Pt_Time() +{ + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + return (PtTimestamp) ((now - startTime) * 1000.0); +} + + +void Pt_Sleep(int32_t duration) +{ + usleep(duration * 1000); +} diff --git a/portmidi/porttime/ptmacosx_mach.c b/portmidi/porttime/ptmacosx_mach.c deleted file mode 100644 index f0fdcf9..0000000 --- a/portmidi/porttime/ptmacosx_mach.c +++ /dev/null @@ -1,130 +0,0 @@ -/* ptmacosx.c -- portable timer implementation for mac os x */ - -#include -#include -#include - -#import -#import -#import -#import -#include - -#include "porttime.h" -#include "sys/time.h" -#include "pthread.h" - -#define NSEC_PER_MSEC 1000000 -#define THREAD_IMPORTANCE 30 - -static int time_started_flag = FALSE; -static UInt64 start_time; -static pthread_t pt_thread_pid; - -/* note that this is static data -- we only need one copy */ -typedef struct { - int id; - int resolution; - PtCallback *callback; - void *userData; -} pt_callback_parameters; - -static int pt_callback_proc_id = 0; - -static void *Pt_CallbackProc(void *p) -{ - pt_callback_parameters *parameters = (pt_callback_parameters *) p; - int mytime = 1; - - kern_return_t error; - thread_extended_policy_data_t extendedPolicy; - thread_precedence_policy_data_t precedencePolicy; - - extendedPolicy.timeshare = 0; - error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY, - (thread_policy_t)&extendedPolicy, - THREAD_EXTENDED_POLICY_COUNT); - if (error != KERN_SUCCESS) { - mach_error("Couldn't set thread timeshare policy", error); - } - - precedencePolicy.importance = THREAD_IMPORTANCE; - error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY, - (thread_policy_t)&precedencePolicy, - THREAD_PRECEDENCE_POLICY_COUNT); - if (error != KERN_SUCCESS) { - mach_error("Couldn't set thread precedence policy", error); - } - - - /* to kill a process, just increment the pt_callback_proc_id */ - /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id); */ - while (pt_callback_proc_id == parameters->id) { - /* wait for a multiple of resolution ms */ - UInt64 wait_time; - int delay = mytime++ * parameters->resolution - Pt_Time(); - long timestamp; - if (delay < 0) delay = 0; - wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC); - wait_time += AudioGetCurrentHostTime(); - error = mach_wait_until(wait_time); - timestamp = Pt_Time(); - (*(parameters->callback))(timestamp, parameters->userData); - } - free(parameters); - return NULL; -} - - -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) -{ - if (time_started_flag) return ptAlreadyStarted; - start_time = AudioGetCurrentHostTime(); - - if (callback) { - int res; - pt_callback_parameters *parms; - - parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters)); - if (!parms) return ptInsufficientMemory; - parms->id = pt_callback_proc_id; - parms->resolution = resolution; - parms->callback = callback; - parms->userData = userData; - res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms); - if (res != 0) return ptHostError; - } - - time_started_flag = TRUE; - return ptNoError; -} - - -PtError Pt_Stop() -{ - /* printf("Pt_Stop called\n"); */ - pt_callback_proc_id++; - time_started_flag = FALSE; - return ptNoError; -} - - -int Pt_Started() -{ - return time_started_flag; -} - - -PtTimestamp Pt_Time() -{ - UInt64 clock_time, nsec_time; - clock_time = AudioGetCurrentHostTime() - start_time; - nsec_time = AudioConvertHostTimeToNanos(clock_time); - return (PtTimestamp)(nsec_time / NSEC_PER_MSEC); -} - - -void Pt_Sleep(long duration) -{ - usleep(duration * 1000); -} diff --git a/portmidi/porttime/ptwinmm.c b/portmidi/porttime/ptwinmm.c index 5814415..5204659 100644 --- a/portmidi/porttime/ptwinmm.c +++ b/portmidi/porttime/ptwinmm.c @@ -21,7 +21,7 @@ void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD_PTR dwUser, } -PtError Pt_Start(int resolution, PtCallback *callback, void *userData) +PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData) { if (time_started_flag) return ptAlreadyStarted; timeBeginPeriod(resolution); @@ -38,7 +38,7 @@ PtError Pt_Start(int resolution, PtCallback *callback, void *userData) } -PtError Pt_Stop() +PMEXPORT PtError Pt_Stop() { if (!time_started_flag) return ptAlreadyStopped; if (time_callback && timer_id) { @@ -52,19 +52,19 @@ PtError Pt_Stop() } -int Pt_Started() +PMEXPORT int Pt_Started() { return time_started_flag; } -PtTimestamp Pt_Time() +PMEXPORT PtTimestamp Pt_Time() { return timeGetTime() - time_offset; } -void Pt_Sleep(long duration) +PMEXPORT void Pt_Sleep(int32_t duration) { Sleep(duration); }