diff options
Diffstat (limited to 'pd/portmidi')
-rw-r--r-- | pd/portmidi/pm_common/pminternal.h | 75 | ||||
-rw-r--r-- | pd/portmidi/pm_common/pmutil.c | 281 | ||||
-rw-r--r-- | pd/portmidi/pm_common/pmutil.h | 82 | ||||
-rw-r--r-- | pd/portmidi/pm_common/portmidi.c | 702 | ||||
-rw-r--r-- | pd/portmidi/pm_common/portmidi.h | 418 | ||||
-rw-r--r-- | pd/portmidi/pm_linux/README_LINUX.txt | 15 | ||||
-rw-r--r-- | pd/portmidi/pm_linux/pmlinux.c | 9 | ||||
-rw-r--r-- | pd/portmidi/pm_linux/pmlinuxalsa.c | 100 | ||||
-rw-r--r-- | pd/portmidi/pm_mac/pmmacosxcm.c | 1643 | ||||
-rw-r--r-- | pd/portmidi/pm_win/README_WIN.txt | 134 | ||||
-rw-r--r-- | pd/portmidi/pm_win/copy-dll.bat | 4 | ||||
-rw-r--r-- | pd/portmidi/pm_win/pm_dll.dsp | 10 | ||||
-rw-r--r-- | pd/portmidi/pm_win/pmwin.c | 1 | ||||
-rw-r--r-- | pd/portmidi/pm_win/pmwinmm.c | 1100 | ||||
-rw-r--r-- | pd/portmidi/porttime/porttime.h | 6 | ||||
-rw-r--r-- | pd/portmidi/porttime/ptlinux.c | 15 | ||||
-rw-r--r-- | pd/portmidi/porttime/ptmacosx_cf.c | 3 | ||||
-rw-r--r-- | pd/portmidi/porttime/ptmacosx_mach.c | 9 |
18 files changed, 2664 insertions, 1943 deletions
diff --git a/pd/portmidi/pm_common/pminternal.h b/pd/portmidi/pm_common/pminternal.h index 20677308..b8c03232 100644 --- a/pd/portmidi/pm_common/pminternal.h +++ b/pd/portmidi/pm_common/pminternal.h @@ -28,11 +28,11 @@ 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,
+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);
@@ -42,7 +42,8 @@ typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi, unsigned char byte, PmTimestamp timestamp);
typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
PmEvent *buffer);
-typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi);
+typedef 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 */
@@ -69,7 +70,7 @@ typedef struct { 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
+ 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) */
@@ -83,7 +84,7 @@ 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
+ void *internalDescriptor; /* points to PmInternal device, allows automatic
device closing */
pm_fns_type dictionary;
} descriptor_node, *descriptor_type;
@@ -97,69 +98,77 @@ typedef unsigned long (*time_get_proc_type)(void *time_info); typedef struct pm_internal_struct {
int device_id; /* which device is open (index to descriptors) */
short write_flag; /* MIDI_IN, or MIDI_OUT */
-
+
PmTimeProcPtr time_proc; /* where to get the time */
void *time_info; /* pass this to get_time() */
-
- long buffer_len; /* how big is the buffer */
- PmEvent *buffer; /* storage for:
- - midi input
+ long buffer_len; /* how big is the buffer or queue? */
+#ifdef NEWBUFFER
+ PmQueue *queue;
+#else
+ PmEvent *buffer; /* storage for:
+ - midi input
- midi output w/latency != 0 */
long head;
long tail;
-
+ int overflow; /* set to non-zero if input is dropped */
+#endif
long latency; /* time delay in ms between timestamps and actual output */
/* set to zero to get immediate, simple blocking output */
/* if latency is zero, timestamps will be ignored; */
/* if midi input device, this field ignored */
-
- int overflow; /* set to non-zero if input is dropped */
- int flush; /* flag to drop incoming sysex data because of overflow */
- int sysex_in_progress; /* use for overflow management */
+
+ 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; /* filter incoming messages based on channel */
+ 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 */
+ int *fill_offset_ptr; /* offset of next sysex byte */
+ int fill_length; /* how many sysex bytes to write */
} PmInternal;
-typedef struct {
- long head;
- long tail;
- long len;
- long msg_size;
- long overflow;
- char *buffer;
-} PmQueueRep;
/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
-void pm_init(void);
-void pm_term(void);
+void pm_init(void);
+void pm_term(void);
/* defined by portMidi, used by pmwinmm */
PmError none_write_short(PmInternal *midi, PmEvent *buffer);
-PmError none_sysex(PmInternal *midi, PmTimestamp timestamp);
-PmError none_write_byte(PmInternal *midi, unsigned char byte,
+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);
-void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp);
-void pm_begin_sysex(PmInternal *midi);
-void pm_end_sysex(PmInternal *midi);
+unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data, int len,
+ PmTimestamp timestamp);
void pm_read_short(PmInternal *midi, PmEvent *event);
-#define none_write_flush pm_fail_fn
+#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
diff --git a/pd/portmidi/pm_common/pmutil.c b/pd/portmidi/pm_common/pmutil.c index 1178b80b..42f386c1 100644 --- a/pd/portmidi/pm_common/pmutil.c +++ b/pd/portmidi/pm_common/pmutil.c @@ -2,30 +2,73 @@ applications that use PortMidi
*/
#include "stdlib.h"
+#include "assert.h"
#include "memory.h"
#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)
{
PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
-
- /* arg checking */
+ int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) &
+ ~(sizeof(int32) - 1)) / sizeof(int32);
+ /* arg checking */
if (!queue)
- return NULL;
-
- queue->len = num_msgs * bytes_per_msg;
- queue->buffer = pm_alloc(queue->len);
+ 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;
- queue->msg_size = bytes_per_msg;
+ /* 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;
}
@@ -33,12 +76,13 @@ PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg) PmError Pm_QueueDestroy(PmQueue *q)
{
PmQueueRep *queue = (PmQueueRep *) q;
-
- /* arg checking */
- if (!queue || !queue->buffer)
- return pmBadPtr;
+
+ /* arg checking */
+ if (!queue || !queue->buffer || !queue->peek)
+ return pmBadPtr;
- pm_free(queue->buffer);
+ pm_free(queue->peek);
+ pm_free(queue->buffer);
pm_free(queue);
return pmNoError;
}
@@ -48,19 +92,87 @@ 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;
+ /* 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) {
+#ifdef QUEUE_DEBUG
+ printf("Pm_Dequeue returns peek msg:");
+ for (i = 0; i < queue->msg_size - 1; i++) {
+ printf(" %d", queue->peek[i]);
+ }
+ printf("\n");
+#endif
+ memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32));
+ queue->peek_flag = FALSE;
+ return 1;
+ }
- if (queue->overflow) {
- queue->overflow = FALSE;
+ 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;
}
- head = queue->head; /* make sure this is written after access */
- if (head == queue->tail) return 0;
- memcpy(msg, queue->buffer + head, queue->msg_size);
+ /* 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 0;
+ }
+ }
+#ifdef QUEUE_DEBUG
+ printf("Pm_Dequeue:");
+ for (i = 0; i < queue->msg_size; i++) {
+ printf(" %d", queue->buffer[head + i]);
+ }
+ printf("\n");
+#endif
+ 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;
@@ -68,65 +180,132 @@ PmError Pm_Dequeue(PmQueue *q, void *msg) }
-/* source should not enqueue data if overflow is set */
-/**/
+
+PmError Pm_SetOverflow(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ long tail;
+ /* no more enqueue until receiver acknowledges overflow */
+ if (queue->overflow) return pmBufferOverflow;
+ if (!queue)
+ return pmBadPtr;
+ 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;
- /* arg checking */
- if (!queue)
- return pmBadPtr;
+ int32 *dest;
- tail = queue->tail;
- memcpy(queue->buffer + tail, msg, queue->msg_size);
- tail += queue->msg_size;
- if (tail == queue->len) tail = 0;
- if (tail == queue->head) {
- queue->overflow = TRUE;
- /* do not update tail, so message is lost */
+ int rslt;
+ /* no more enqueue until receiver acknowledges overflow */
+ if (!queue) return pmBadPtr;
+ 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;
+#ifdef QUEUE_DEBUG
+ printf("Pm_Enqueue:");
+ for (i = 0; i < queue->msg_size; i++) {
+ printf(" %d", queue->buffer[tail + i]);
+ }
+ printf("\n");
+#endif
+ tail += queue->msg_size;
+ if (tail == queue->len) tail = 0;
queue->tail = tail;
return pmNoError;
}
+
int Pm_QueueEmpty(PmQueue *q)
{
PmQueueRep *queue = (PmQueueRep *) q;
if (!queue) return TRUE;
- return (queue->head == queue->tail);
+ return (queue->buffer[queue->head] == 0);
}
+
int Pm_QueueFull(PmQueue *q)
{
PmQueueRep *queue = (PmQueueRep *) q;
- long tail;
-
- /* arg checking */
- if(!queue)
- return pmBadPtr;
-
- tail = queue->tail;
- tail += queue->msg_size;
- if (tail == queue->len) {
- tail = 0;
+ int tail;
+ int i;
+ /* 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 (tail == queue->head);
+ return FALSE;
}
void *Pm_QueuePeek(PmQueue *q)
{
- long head;
PmQueueRep *queue = (PmQueueRep *) q;
+ PmError rslt;
+ long temp;
- /* arg checking */
- if(!queue)
- return NULL;
+ /* arg checking */
+ if (!queue)
+ return NULL;
- head = queue->head; /* make sure this is written after access */
- if (head == queue->tail) return NULL;
- return queue->buffer + head;
+ 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/pd/portmidi/pm_common/pmutil.h b/pd/portmidi/pm_common/pmutil.h index a7fb3ebf..8e429467 100644 --- a/pd/portmidi/pm_common/pmutil.h +++ b/pd/portmidi/pm_common/pmutil.h @@ -2,6 +2,10 @@ applications that use PortMidi
*/
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
typedef void PmQueue;
/*
@@ -10,6 +14,33 @@ typedef void PmQueue; the message size as parameters. The queue only accepts
fixed sized messages. Returns NULL if memory cannot be allocated.
+ 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.
+
+ This implementation does not try to keep reader/writer data in
+ separate cache lines or prevent thrashing on cache lines.
+ However, this algorithm differs by doing inserts/removals in
+ units of messages rather than units of machine words. Some
+ performance improvement might be obtained by not clearing data
+ immediately after a read, but instead by waiting for the end
+ 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.
+
Pm_QueueDestroy() destroys the queue and frees its storage.
*/
@@ -19,10 +50,11 @@ PmError Pm_QueueDestroy(PmQueue *queue); /*
Pm_Dequeue() removes one item from the queue, copying it into msg.
Returns 1 if successful, and 0 if the queue is empty.
- Returns pmBufferOverflow, clears the overflow flag, and does not
- return a data item if the overflow flag is set. (This protocol
- ensures that the reader will be notified when data is lost due
- to overflow.)
+ 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);
@@ -40,7 +72,12 @@ PmError Pm_Enqueue(PmQueue *queue, void *msg); Pm_QueueEmpty() returns non-zero if the queue is empty
Either condition may change immediately because a parallel
- enqueue or dequeue operation could be in progress.
+ 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.
*/
int Pm_QueueFull(PmQueue *queue);
int Pm_QueueEmpty(PmQueue *queue);
@@ -49,8 +86,39 @@ int Pm_QueueEmpty(PmQueue *queue); /*
Pm_QueuePeek() returns a pointer to the item at the head of the queue,
or NULL if the queue is empty. The item is not removed from the queue.
- If queue is in an overflow state, a valid pointer is returned and the
- queue remains in the overflow state.
+ 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.
*/
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.
+ */
+PmError Pm_SetOverflow(PmQueue *queue);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
diff --git a/pd/portmidi/pm_common/portmidi.c b/pd/portmidi/pm_common/portmidi.c index e1b962d7..71988e47 100644 --- a/pd/portmidi/pm_common/portmidi.c +++ b/pd/portmidi/pm_common/portmidi.c @@ -2,6 +2,9 @@ #include "string.h"
#include "portmidi.h"
#include "porttime.h"
+#ifdef NEWBUFFER
+#include "pmutil.h"
+#endif
#include "pminternal.h"
#include <assert.h>
@@ -31,7 +34,7 @@ #define is_empty(midi) ((midi)->tail == (midi)->head)
static int pm_initialized = FALSE;
-int pm_hosterror = FALSE;
+int pm_hosterror;
char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
#ifdef PM_CHECK_ERRORS
@@ -80,24 +83,24 @@ int pm_descriptor_max = 0; int pm_descriptor_index = 0;
descriptor_type descriptors = NULL;
-/* pm_add_device -- describe interface/device pair to library
+/* pm_add_device -- describe interface/device pair to library
*
- * This is called at intialization time, once for each
+ * 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,
+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 new_descriptors =
pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
if (!new_descriptors) return pmInsufficientMemory;
if (descriptors) {
- memcpy(new_descriptors, descriptors,
+ memcpy(new_descriptors, descriptors,
sizeof(descriptor_node) * pm_descriptor_max);
free(descriptors);
}
@@ -114,14 +117,14 @@ PmError pm_add_device(char *interf, char *name, int input, /* 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;
}
@@ -132,20 +135,15 @@ portmidi implementation ====================================================================
*/
-int Pm_CountDevices( void )
-{
- PmError err = Pm_Initialize();
- if (err)
- return pm_errmsg(err);
+int Pm_CountDevices( void ) {
+ Pm_Initialize();
+ /* no error checking -- Pm_Initialize() does not fail */
return pm_descriptor_index;
}
-const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id )
-{
- PmError err = Pm_Initialize();
- if (err)
- return NULL;
+const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) {
+ Pm_Initialize(); /* no error check needed */
if (id >= 0 && id < pm_descriptor_index) {
return &descriptors[id].pub;
}
@@ -158,35 +156,30 @@ PmError pm_success_fn(PmInternal *midi) { }
/* none_write -- returns an error if called */
-PmError none_write_short(PmInternal *midi, PmEvent *buffer)
-{
+PmError none_write_short(PmInternal *midi, PmEvent *buffer) {
return pmBadPtr;
}
-/* none_sysex -- placeholder for begin_sysex and end_sysex */
-PmError none_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
+/* 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)
-{
+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)
-{
+PmError pm_fail_fn(PmInternal *midi) {
return pmBadPtr;
}
-static PmError none_open(PmInternal *midi, void *driverInfo)
-{
+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,"");
+ strcpy(msg, "");
}
static unsigned int none_has_host_error(PmInternal * midi) {
return FALSE;
@@ -207,11 +200,11 @@ pm_fns_node pm_none_dictionary = { none_write_flush,
none_synchronize,
none_open,
- none_abort,
+ none_abort,
none_close,
none_poll,
none_has_host_error,
- none_get_host_error
+ none_get_host_error
};
@@ -220,34 +213,38 @@ const char *Pm_GetErrorText( PmError errnum ) { switch(errnum)
{
- case pmNoError:
- msg = "";
+ case pmNoError:
+ msg = "";
break;
- case pmHostError:
- msg = "PortMidi: `Host error'";
+ case pmHostError:
+ msg = "PortMidi: `Host error'";
break;
- case pmInvalidDeviceId:
- msg = "PortMidi: `Invalid device ID'";
+ case pmInvalidDeviceId:
+ msg = "PortMidi: `Invalid device ID'";
break;
- case pmInsufficientMemory:
- msg = "PortMidi: `Insufficient memory'";
+ case pmInsufficientMemory:
+ msg = "PortMidi: `Insufficient memory'";
break;
- case pmBufferTooSmall:
- msg = "PortMidi: `Buffer too small'";
+ case pmBufferTooSmall:
+ msg = "PortMidi: `Buffer too small'";
break;
- case pmBadPtr:
- msg = "PortMidi: `Bad pointer'";
+ case pmBadPtr:
+ msg = "PortMidi: `Bad pointer'";
break;
- case pmInternalError:
- msg = "PortMidi: `Internal PortMidi Error'";
+ case pmInternalError:
+ msg = "PortMidi: `Internal PortMidi Error'";
break;
case pmBufferOverflow:
msg = "PortMidi: `Buffer overflow'";
break;
case pmBadData:
msg = "PortMidi: `Invalid MIDI message Data'";
- default:
- msg = "PortMidi: `Illegal error number'";
+ break;
+ case pmBufferMaxSize:
+ msg = "PortMidi: `Buffer cannot be made larger'";
+ break;
+ default:
+ msg = "PortMidi: `Illegal error number'";
break;
}
return msg;
@@ -260,11 +257,11 @@ const char *Pm_GetErrorText( PmError errnum ) { void Pm_GetHostErrorText(char * msg, unsigned int len) {
assert(msg);
assert(len > 0);
- if (pm_hosterror) { /* we have the string already from open or close */
+ 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 */
+ might help with debugging */
msg[len - 1] = 0; /* make sure string is terminated */
} else {
msg[0] = 0; /* no string to return */
@@ -273,11 +270,12 @@ void Pm_GetHostErrorText(char * msg, unsigned int len) { 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,
+ midi->dictionary->host_error(midi, pm_hosterror_text,
PM_HOST_ERROR_MSG_LEN);
/* now error message is global */
return TRUE;
@@ -288,8 +286,9 @@ int Pm_HasHostError(PortMidiStream * stream) { PmError Pm_Initialize( void ) {
- pm_hosterror_text[0] = 0; /* the null string */
if (!pm_initialized) {
+ pm_hosterror = FALSE;
+ pm_hosterror_text[0] = 0; /* the null string */
pm_init();
pm_initialized = TRUE;
}
@@ -300,6 +299,13 @@ PmError Pm_Initialize( void ) { 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;
@@ -308,33 +314,23 @@ PmError Pm_Terminate( void ) { /* Pm_Read -- read up to length longs from source into buffer */
/*
- returns number of longs actually read, or error code
-
- When the reader wants data:
- if overflow_flag:
- do not get anything
- empty the buffer (read_ptr = write_ptr)
- clear overflow_flag
- return pmBufferOverflow
- get data
- return number of messages
-*/
+ * returns number of longs actually read, or error code
+ */
PmError Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) {
PmInternal *midi = (PmInternal *) stream;
int n = 0;
+#ifndef NEWBUFFER
long head;
+#endif
PmError err = pmNoError;
-
+ pm_hosterror = FALSE;
/* arg checking */
if(midi == NULL)
err = pmBadPtr;
- else if(Pm_HasHostError(midi))
- err = pmHostError;
else if(!descriptors[midi->device_id].pub.opened)
err = pmBadPtr;
else if(!descriptors[midi->device_id].pub.input)
- err = pmBadPtr;
-
+ 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.
@@ -343,9 +339,26 @@ PmError Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { 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);
}
+#ifdef NEWBUFFER
+ 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++;
+ }
+#else
head = midi->head;
while (head != midi->tail && n < length) {
PmEvent event = midi->buffer[head++];
@@ -359,6 +372,7 @@ PmError Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) { midi->overflow = FALSE;
return pm_errmsg(pmBufferOverflow);
}
+#endif
return n;
}
@@ -366,12 +380,14 @@ PmError Pm_Poll( PortMidiStream *stream ) {
PmInternal *midi = (PmInternal *) stream;
PmError err;
+#ifdef NEWBUFFER
+ PmEvent *event;
+#endif
+ pm_hosterror = FALSE;
/* arg checking */
if(midi == NULL)
err = pmBadPtr;
- else if(Pm_HasHostError(midi))
- err = pmHostError;
else if(!descriptors[midi->device_id].pub.opened)
err = pmBadPtr;
else if(!descriptors[midi->device_id].pub.input)
@@ -379,12 +395,41 @@ PmError Pm_Poll( PortMidiStream *stream ) else
err = (*(midi->dictionary->poll))(midi);
- if (err != pmNoError)
+ 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);
- else
- return midi->head != midi->tail;
+ }
+
+#ifdef NEWBUFFER
+ event = (PmEvent *) Pm_QueuePeek(midi->queue);
+ return event != NULL;
+#else
+ return midi->head != midi->tail;
+#endif
+}
+
+
+/* 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 */
@@ -395,21 +440,20 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) PmError err;
int i;
int bits;
-
+
+ pm_hosterror = FALSE;
/* arg checking */
if(midi == NULL)
err = pmBadPtr;
- else if(Pm_HasHostError(midi))
- err = pmHostError;
else if(!descriptors[midi->device_id].pub.opened)
err = pmBadPtr;
else if(!descriptors[midi->device_id].pub.output)
err = pmBadPtr;
else
err = pmNoError;
-
+
if (err != pmNoError) goto pm_write_error;
-
+
if (midi->latency == 0) {
midi->now = 0;
} else {
@@ -420,7 +464,19 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) 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;
@@ -433,21 +489,21 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) goto pm_write_error;
}
midi->sysex_in_progress = TRUE;
- if ((err = (*midi->dictionary->begin_sysex)(midi,
+ 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)
+ buffer[i].timestamp)) != pmNoError)
goto pm_write_error;
bits = 8;
/* fall through to continue sysex processing */
- } else if ((msg & MIDI_STATUS_MASK) &&
+ } else if ((msg & MIDI_STATUS_MASK) &&
(Pm_MessageStatus(msg) != MIDI_EOX)) {
/* a non-sysex message */
if (midi->sysex_in_progress) {
- /* this should be a non-realtime message */
+ /* this should be a realtime message */
if (is_real_time(msg)) {
- if ((err = (*midi->dictionary->write_realtime)(midi,
+ if ((err = (*midi->dictionary->write_realtime)(midi,
&(buffer[i]))) != pmNoError)
goto pm_write_error;
} else {
@@ -459,23 +515,34 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) goto pm_write_error;
}
} else { /* regular short midi message */
- if ((err = (*midi->dictionary->write_short)(midi,
+ 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,
+ if ((err = (*midi->dictionary->write_byte)(midi, midi_byte,
buffer[i].timestamp)) != pmNoError)
goto pm_write_error;
if (midi_byte == MIDI_EOX) {
- midi->sysex_in_progress = FALSE;
- if ((err = (*midi->dictionary->end_sysex)(midi,
- buffer[i].timestamp)) != pmNoError)
- goto pm_write_error;
+ err = pm_end_sysex(midi);
+ if (err != pmNoError) goto error_exit;
break; /* from while loop */
}
bits += 8;
@@ -487,8 +554,15 @@ PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length) }
}
/* after all messages are processed, send the data */
- err = (*midi->dictionary->write_flush)(midi);
+ 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);
}
@@ -496,20 +570,22 @@ pm_write_error: 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,
+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;
@@ -521,28 +597,58 @@ PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, /* 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 == BUFLEN) {
- PmError err = Pm_Write(stream, buffer, BUFLEN);
+ 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 */
- if (*msg++ == MIDI_EOX) break;
}
-
+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 */
- return Pm_Write(stream, buffer, bufx);
+ PmError err = Pm_Write(stream, buffer, bufx);
+ if (err) return err;
}
return pmNoError;
}
@@ -554,26 +660,25 @@ PmError Pm_OpenInput(PortMidiStream** stream, void *inputDriverInfo,
long bufferSize,
PmTimeProcPtr time_proc,
- void *time_info)
-{
+ void *time_info) {
PmInternal *midi;
PmError err = pmNoError;
pm_hosterror = FALSE;
*stream = NULL;
-
+
/* arg checking */
- if (inputDevice < 0 || inputDevice >= pm_descriptor_index)
+ if (inputDevice < 0 || inputDevice >= pm_descriptor_index)
err = pmInvalidDeviceId;
- else if (!descriptors[inputDevice].pub.input)
+ else if (!descriptors[inputDevice].pub.input)
err = pmBadPtr;
else if(descriptors[inputDevice].pub.opened)
err = pmBadPtr;
-
- if (err != pmNoError)
+
+ if (err != pmNoError)
goto error_return;
/* create portMidi internal data */
- midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
+ midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
*stream = midi;
if (!midi) {
err = pmInsufficientMemory;
@@ -589,29 +694,36 @@ PmError Pm_OpenInput(PortMidiStream** stream, system-specific midi_out_open() method.
*/
if (bufferSize <= 0) bufferSize = 256; /* default buffer size */
+#ifdef NEWBUFFER
+ midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent));
+ if (!midi->queue) {
+#else
else bufferSize++; /* buffer holds N-1 msgs, so increase request by 1 */
- midi->buffer_len = bufferSize; /* portMidi input storage */
- midi->buffer = (PmEvent *) pm_alloc(sizeof(PmEvent) * midi->buffer_len);
- if (!midi->buffer) {
+ midi->buffer = (PmEvent *) pm_alloc(sizeof(PmEvent) * midi->buffer_len);
+ midi->head = 0;
+ midi->tail = 0;
+ midi->overflow = FALSE;
+ if (!midi->buffer) {
+#endif
/* free portMidi data */
*stream = NULL;
- pm_free(midi);
+ pm_free(midi);
err = pmInsufficientMemory;
goto error_return;
}
- midi->head = 0;
- midi->tail = 0;
+ midi->buffer_len = bufferSize; /* portMidi input storage */
midi->latency = 0; /* not used */
- midi->overflow = FALSE;
- midi->flush = FALSE;
midi->sysex_in_progress = FALSE;
- midi->sysex_message = 0;
- midi->sysex_message_count = 0;
+ midi->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);
@@ -619,13 +731,21 @@ PmError Pm_OpenInput(PortMidiStream** stream, *stream = NULL;
descriptors[inputDevice].internalDescriptor = NULL;
/* free portMidi data */
- pm_free(midi->buffer);
+#ifdef NEWBUFFER
+ Pm_QueueDestroy(midi->queue);
+#else
+ pm_free(midi->buffer);
+#endif
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);
}
@@ -636,26 +756,25 @@ PmError Pm_OpenOutput(PortMidiStream** stream, long bufferSize,
PmTimeProcPtr time_proc,
void *time_info,
- long latency)
-{
+ long latency ) {
PmInternal *midi;
PmError err = pmNoError;
pm_hosterror = FALSE;
*stream = NULL;
-
+
/* arg checking */
if (outputDevice < 0 || outputDevice >= pm_descriptor_index)
err = pmInvalidDeviceId;
- else if (!descriptors[outputDevice].pub.output)
- err = pmBadPtr;
+ else if (!descriptors[outputDevice].pub.output)
+ err = pmInvalidDeviceId;
else if (descriptors[outputDevice].pub.opened)
- err = pmBadPtr;
- if (err != pmNoError)
+ err = pmInvalidDeviceId;
+ if (err != pmNoError)
goto error_return;
/* create portMidi internal data */
- midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
- *stream = midi;
+ midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
+ *stream = midi;
if (!midi) {
err = pmInsufficientMemory;
goto error_return;
@@ -666,32 +785,36 @@ PmError Pm_OpenOutput(PortMidiStream** stream, /* if latency > 0, we need a time reference. If none is provided,
use PortTime library */
if (time_proc == NULL && latency != 0) {
- if (!Pt_Started())
+ if (!Pt_Started())
Pt_Start(1, 0, 0);
/* time_get does not take a parameter, so coerce */
midi->time_proc = (PmTimeProcPtr) Pt_Time;
}
midi->time_info = time_info;
- /* when stream used, this buffer allocated and used by
- winmm_out_open; deleted by winmm_out_close */
midi->buffer_len = bufferSize;
+#ifdef NEWBUFFER
+ midi->queue = NULL; /* unused by output */
+#else
midi->buffer = NULL;
midi->head = 0; /* unused by output */
midi->tail = 0; /* unused by output */
+ midi->overflow = FALSE; /* not used */
+#endif
/* if latency zero, output immediate (timestamps ignored) */
/* if latency < 0, use 0 but don't return an error */
if (latency < 0) latency = 0;
midi->latency = latency;
- midi->overflow = FALSE; /* not used */
- midi->flush = FALSE; /* not used */
midi->sysex_in_progress = FALSE;
midi->sysex_message = 0; /* unused by output */
midi->sysex_message_count = 0; /* unused by output */
midi->filters = 0; /* not used for output */
- midi->channel_mask = 0xFFFF; /* not used for output */
+ midi->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);
@@ -699,15 +822,19 @@ PmError Pm_OpenOutput(PortMidiStream** stream, *stream = NULL;
descriptors[outputDevice].internalDescriptor = NULL;
/* free portMidi data */
- pm_free(midi);
+ 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;
@@ -721,8 +848,8 @@ PmError Pm_SetChannelMask(PortMidiStream *stream, int mask) return pm_errmsg(err);
}
-PmError Pm_SetFilter(PortMidiStream *stream, long filters)
-{
+
+PmError Pm_SetFilter(PortMidiStream *stream, long filters) {
PmInternal *midi = (PmInternal *) stream;
PmError err = pmNoError;
@@ -737,11 +864,11 @@ PmError Pm_SetFilter(PortMidiStream *stream, long filters) }
-PmError Pm_Close( PortMidiStream *stream )
-{
+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;
@@ -751,8 +878,8 @@ PmError Pm_Close( PortMidiStream *stream ) /* and the device should be in the opened state */
else if (!descriptors[midi->device_id].pub.opened)
err = pmBadPtr;
-
- if (err != pmNoError)
+
+ if (err != pmNoError)
goto error_return;
/* close the device */
@@ -760,15 +887,21 @@ PmError Pm_Close( PortMidiStream *stream ) /* even if an error occurred, continue with cleanup */
descriptors[midi->device_id].internalDescriptor = NULL;
descriptors[midi->device_id].pub.opened = FALSE;
- pm_free(midi->buffer);
- pm_free(midi);
+#ifdef NEWBUFFER
+ if (midi->queue) Pm_QueueDestroy(midi->queue);
+#else
+ if (midi->buffer) pm_free(midi->buffer);
+#endif
+ 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 )
-{
+PmError Pm_Abort( PortMidiStream* stream ) {
PmInternal *midi = (PmInternal *) stream;
PmError err;
/* arg checking */
@@ -780,39 +913,47 @@ PmError Pm_Abort( PortMidiStream* stream ) 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);
}
+#ifndef NEWBUFFER
+/* this is apparently an orphan routine -- I can find no reference to it now -RBD */
+
/* in win32 multimedia API (via callbacks) some of these functions used; assume never fail */
long pm_next_time(PmInternal *midi) {
/* arg checking */
assert(midi != NULL);
- assert(!Pm_HasHostError(midi));
-
+
return midi->buffer[midi->head].timestamp;
}
+#endif
+
+
/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */
-static int pm_channel_filtered(int status, int mask)
-{
- if ((status & 0xF0) == 0xF0) /* 0xF? messages don't have a channel */
- return 0;
- return !(Pm_Channel(status & 0x0F) & mask);
- /* it'd be easier to return 0 for filtered, 1 for allowed,
- but it would different from the other filtering functions
- */
+#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.
*/
-static int pm_realtime_filtered(int status, long filters)
-{
+#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))
@@ -825,16 +966,18 @@ static int pm_realtime_filtered(int status, long filters) || ((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))
-static int pm_status_filtered(int status, long filters)
-{
- status &= 0xF0; /* remove channel information */
+
+/*
return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))
|| ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))
|| ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH))
@@ -844,137 +987,198 @@ static int pm_status_filtered(int status, long filters) || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
}
+*/
+
+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_byte
+
+/* 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 sysex byte should be reported by calling pm_read_byte
- (which sets midi->sysex_in_progress). After the eox byte,
- pm_read_byte will clear sysex_in_progress and midi->flush
- (Note that the overflow flag is managed by pm_read_short
- and Pm_Read, so the supplier should not read or write it.)
+ 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
+/* 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.
+ are entered into the PortMidi input buffer.
*/
-
- /* Algorithnm:
- if overflow or flush, return
- ATOMIC:
- enqueue data
- if buffer overflow, set overflow
- if buffer overflow and sysex_in_progress, set flush
- */
void pm_read_short(PmInternal *midi, PmEvent *event)
-{
+{
+#ifndef NEWBUFFER
long tail;
+#endif
int status;
/* arg checking */
assert(midi != NULL);
- assert(!Pm_HasHostError(midi));
/* midi filtering is applied here */
status = Pm_MessageStatus(event->message);
if (!pm_status_filtered(status, midi->filters)
- && !pm_realtime_filtered(status, midi->filters)
+ && (!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) &&
- !is_real_time(status) && status != MIDI_SYSEX ) {
+ 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;
+ }
+#ifdef NEWBUFFER
+ } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {
midi->sysex_in_progress = FALSE;
- midi->flush = FALSE;
}
-
- /* don't try to do anything more in an overflow state */
- if (midi->overflow || midi->flush) return;
-
- /* insert the message */
- tail = midi->tail;
- midi->buffer[tail++] = *event;
- if (tail == midi->buffer_len) tail = 0;
- if (tail == midi->head || midi->overflow) {
- midi->overflow = TRUE;
- if (midi->sysex_in_progress) midi->flush = TRUE;
- /* drop the rest of the message, this must be cleared
- by caller when EOX is received */
- return;
+#else
+ } else {
+ /* don't try to do anything more in an overflow state */
+ if (midi->overflow) return;
+
+ /* insert the message */
+ tail = midi->tail;
+ midi->buffer[tail++] = *event;
+ if (tail == midi->buffer_len) tail = 0;
+ if (tail == midi->head || midi->overflow) {
+ midi->overflow = TRUE;
+ midi->sysex_in_progress = FALSE;
+ /* drop the rest of the message, this must be cleared
+ by caller when EOX is received */
+ return;
+ }
+ midi->tail = tail; /* complete the write */
}
- midi->tail = tail; /* complete the write */
+#endif
}
}
-
-void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
+/* pm_read_bytes -- read one (partial) sysex msg from MIDI data */
+/*
+ * returns how many bytes processed
+ */
+unsigned int pm_read_bytes(PmInternal *midi, unsigned char *data,
+ int len, PmTimestamp timestamp)
{
+ unsigned int i = 0; /* index into data */
PmEvent event;
-
- /* there may be nothing in the buffer */
- if (midi->sysex_message_count == 0) return; /* nothing to flush */
-
- event.message = midi->sysex_message;
event.timestamp = timestamp;
- pm_read_short(midi, &event);
- midi->sysex_message_count = 0;
- midi->sysex_message = 0;
-}
-
-
-void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
-{
assert(midi);
- assert(!Pm_HasHostError(midi));
- /* here is the logic for controlling sysex_in_progress */
- if (midi->sysex_in_progress) {
- if (byte == MIDI_EOX) midi->sysex_in_progress = FALSE;
- else if (byte == MIDI_SYSEX) {
- /* problem: need to terminate the current sysex and start
- a new one
- */
- pm_flush_sysex(midi, timestamp);
- }
- } else if (byte == MIDI_SYSEX) {
- midi->sysex_in_progress = TRUE;
- } else {
- /* error: we're getting data bytes or EOX but we're no sysex is
- in progress. Drop the data. (Would it be better to report an
- error? Is this a host error or a pmBadData error? Is this
- ever possible?
- */
-#ifdef DEBUG
- printf("PortMidi debug msg: unexpected sysex data or EOX\n");
-#endif
- return;
+ /* 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 */
}
-
- if (pm_realtime_filtered(byte, midi->filters))
- return;
- /* this awkward expression places the bytes in increasingly higher-
- order bytes of the long message */
- midi->sysex_message |= (byte << (8 * midi->sysex_message_count++));
- if (midi->sysex_message_count == 4 || !midi->sysex_in_progress) {
- pm_flush_sysex(midi, timestamp);
- if (!midi->sysex_in_progress) midi->flush = FALSE;
+ /* Now, i<len implies sysex_in_progress. If sysex_in_progress
+ * becomes false in the loop, there must have been an overflow
+ * and we can just drop all remaining bytes
+ */
+ while (i < len && midi->sysex_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;
}
+#ifndef NEWBUFFER
+/* this code is apparently never called */
int pm_queue_full(PmInternal *midi)
{
long tail;
/* arg checking */
assert(midi != NULL);
- assert(!Pm_HasHostError(midi));
-
+
tail = midi->tail + 1;
if (tail == midi->buffer_len) tail = 0;
return tail == midi->head;
}
-
+#endif
diff --git a/pd/portmidi/pm_common/portmidi.h b/pd/portmidi/pm_common/portmidi.h index 8c9c04c2..8f4879f3 100644 --- a/pd/portmidi/pm_common/portmidi.h +++ b/pd/portmidi/pm_common/portmidi.h @@ -10,7 +10,12 @@ extern "C" { * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/
*
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
- * Copyright (c) 2001 Roger B. Dannenberg
+ * 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
@@ -23,10 +28,6 @@ extern "C" { * The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
- * Any person wishing to distribute modifications to the Software is
- * requested to send the modifications to the original developer so that
- * they can be incorporated into the canonical version.
- *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
@@ -34,114 +35,21 @@ extern "C" { * 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
- *
- * 15Nov04 Ben Allison
- * - sysex output now uses one buffer/message and reallocates buffer
- * - if needed
- * - filters expanded for many message types and channels
- * - detailed changes are as follows:
- * ------------- in pmwinmm.c --------------
- * - new #define symbol: OUTPUT_BYTES_PER_BUFFER
- * - change SYSEX_BYTES_PER_BUFFER to 1024
- * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length
- * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
- * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro
- * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH
- * - similar to above, but counts appropriately for sysex messages
- * - added the following members to midiwinmm_struct for sysex data:
- * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data **
- * - int num_sysex_buffers; ** how many sysex buffers **
- * - int next_sysex_buffer; ** index of next sysexbuffer to send **
- * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer **
- * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer
- * - into equivalent sysex_buffer form
- * - changed winmm_in_open to initialize new midiwinmm_struct members and
- * - to use the new allocate_sysex_buffer() function instead of
- * - allocate_buffer()
- * - changed winmm_out_open to initialize new members, create sysex buffer
- * - signal, and allocate 2 sysex buffers
- * - changed winmm_out_delete to free sysex buffers and shut down the sysex
- * - buffer signal
- * - create new function resize_sysex_buffer which resizes m->hdr to the
- * - passed size, and corrects the midiwinmm_struct accordingly.
- * - changed winmm_write_byte to use new resize_sysex_buffer function,
- * - if resize fails, write current buffer to output and continue
- * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal
- * - depending on which buffer was finished
- * ------------- in portmidi.h --------------
- * - added pmBufferMaxSize to PmError to indicate that the buffer would be
- * - too large for the underlying API
- * - added additional filters
- * - added prototype, documentation, and helper macro for Pm_SetChannelMask
- * ------------- in portmidi.c --------------
- * - added pm_status_filtered() and pm_realtime_filtered() functions to
- * separate filtering logic from buffer logic in pm_read_short
- * - added Pm_SetChannelMask function
- * - added pm_channel_filtered() function
- * ------------- in pminternal.h --------------
- * - added member to PortMidiStream for channel mask
- *
- * 25May04 RBD
- * - removed support for MIDI THRU
- * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl
- * - extensive work on Mac OS X port, especially sysex and error handling
- *
- * 18May04 RBD
- * - removed side-effects from assert() calls. Now you can disable assert().
- * - no longer check pm_hosterror everywhere, fixing a bug where an open
- * failure could cause a write not to work on a previously opened port
- * until you call Pm_GetHostErrorText().
- * 16May04 RBD and Chris Roberts
- * - Some documentation wordsmithing in portmidi.h
- * - Dynamically allocate port descriptor structures
- * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer.
- *
- * 09Oct03 RBD
- * - Changed Thru handling. Now the client does all the work and the client
- * must poll or read to keep thru messages flowing.
- *
- * 31May03 RBD
- * - Fixed various bugs.
- * - Added linux ALSA support with help from Clemens Ladisch
- * - Added Mac OS X support, implemented by Jon Parise, updated and
- * integrated by Andrew Zeldis and Zico Kolter
- * - Added latency program to build histogram of system latency using PortTime.
- *
- * 30Jun02 RBD Extensive rewrite of sysex handling. It works now.
- * Extensive reworking of error reporting and error text -- no
- * longer use dictionary call to delete data; instead, Pm_Open
- * and Pm_Close clean up before returning an error code, and
- * error text is saved in a system-independent location.
- * Wrote sysex.c to test sysex message handling.
- *
- * 15Jun02 BCT changes:
- * - Added pmHostError text handling.
- * - For robustness, check PortMidi stream args not NULL.
- * - Re-C-ANSI-fied code (changed many C++ comments to C style)
- * - Reorganized code in pmwinmm according to input/output functionality (made
- * cleanup handling easier to reason about)
- * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error)
- * - Cleaned up memory handling (now system specific data deleted via dictionary
- * call in PortMidi, allows client to query host errors).
- * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as
- * logic implies it should. Specifically: verified callback routines not reentrant and
- * all verified status for all unchecked Win32 MMedia API calls perform successfully
- * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API
- * bug (i.e. if devices not explicitly closed, must reboot to debug application further).
- * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or
- * explicitly Pm_Close open devices when using WinMM version of PortMidi.
- *
- * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling
- *
- * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent
- * opening an input as output and vice versa.
- * Added comments and documentation.
- * Implemented Pm_Terminate().
- *
+ * (see ../CHANGELOG.txt)
*
* IMPORTANT INFORMATION ABOUT A WIN32 BUG:
*
@@ -149,15 +57,19 @@ extern "C" { * 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
+ * i) assert
+ * ii) Ctrl^c,
+ * then DLL clean-up routine called. However, when client does something
* really bad (e.g. assigns value to NULL pointer) then DLL CLEANUP ROUTINE
* NEVER RUNS! In this state, if you wait around long enough, you will
* probably get the blue screen of death. Can also go into Pview and there will
* exist zombie process that you can't kill.
*
- * NOTES ON HOST ERROR REPORTING:
+ * 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
@@ -165,50 +77,45 @@ extern "C" { * 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
+ * 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
+ * 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
+ * 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
+ * a) host error
* b) host error during callback
- * These can occur w/midi input or output devices. (b) can only happen
+ * 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
+ * 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().
*
- * Host-error reporting relies on following assumptions:
- * 1) PortMidi routines won't allow system dependent routines to be
- * called when args are bogus.
- * Thus, in pmwinmm.c it is safe to assume:
- * - stream ptr valid
- * - currently not operating in "has host error" state
- * 2) Host-error reporting relies on a staged delivery of error messages.
- * When a host error occurs, the error code is saved with the stream.
- * The error is reported as a return code from the next operation on
- * the stream. This could be immediately if the error is synchronous,
- * or delayed if the error is an asynchronous callback problem. In
- * any case, when pmHostError is returned, the error is copied to
- * a global, pm_hosterror and the error code stored with the stream
- * is cleared. If the user chooses to inquire about the error using
- * Pm_GetHostErrorText(), the error will be reported as text. If the
- * user ignores the error and makes another call on the stream, the
- * call will proceed because the error code associated with the stream
- * has been cleared.
+ * 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
@@ -224,14 +131,19 @@ extern "C" { typedef enum {
pmNoError = 0,
pmHostError = -10000,
- pmInvalidDeviceId, /* out of range or output device when input is requested or vice versa */
+ 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 */
+ 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;
/*
@@ -255,32 +167,35 @@ typedef void PortMidiStream; /*
Test whether stream has a pending host error. Normally, the client finds
- out about errors through returned error codes, but some errors can occur
- asynchronously where the client does not
- explicitly call a function, and therefore cannot receive an error code.
- The client can test for a pending error using Pm_HasHostError(). If true,
- the error can be accessed and cleared by calling Pm_GetErrorText(). The
- client does not need to call Pm_HasHostError(). Any pending error will be
- reported the next time the client performs an explicit function call on
- the stream, e.g. an input or output operation.
+ 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
+ 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.
+ 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
+#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less
than this number of characters */
/*
@@ -292,7 +207,7 @@ void Pm_GetHostErrorText(char * msg, unsigned int len); typedef int PmDeviceID;
#define pmNoDevice -1
typedef struct {
- int structVersion;
+ 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 */
@@ -308,12 +223,12 @@ int Pm_CountDevices( void ); 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".
@@ -327,25 +242,25 @@ int Pm_CountDevices( void ); 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 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".
+ 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
+ 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).
+ (the input or output device with the lowest PmDeviceID).
*/
PmDeviceID Pm_GetDefaultInputDeviceID( void );
PmDeviceID Pm_GetDefaultOutputDeviceID( void );
@@ -391,26 +306,26 @@ const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); 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.
+ 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.)
+ and merely passes data to a lower-level API, in which case buffersize
+ is ignored.)
+
+ latency is the delay in milliseconds applied to timestamps to determine
+ when the output should actually occur. (If latency is < 0, 0 is assumed.)
If latency is zero, timestamps are ignored and all output is delivered
immediately. If latency is greater than zero, output is delayed until
the message timestamp plus the latency. (NOTE: time is measured relative
to the time source indicated by time_proc. Timestamps are absolute, not
relative delays or offsets.) In some cases, PortMidi can obtain
- better timing than your application by passing timestamps along to the
- device driver or hardware. Latency may also help you to synchronize midi
- data to audio data by matching midi latency to the audio buffer latency.
+ 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
+ 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,
@@ -419,7 +334,7 @@ const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); 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.
+ time_info is a pointer passed to time_proc.
return value:
Upon success Pm_Open() returns PmNoError and places a pointer to a
@@ -428,7 +343,7 @@ const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); PMError above) and the value of port is invalid.
Any stream that is successfully opened should eventually be closed
- by calling Pm_Close().
+ by calling Pm_Close().
*/
PmError Pm_OpenInput( PortMidiStream** stream,
@@ -446,7 +361,7 @@ PmError Pm_OpenOutput( PortMidiStream** stream, 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
@@ -461,53 +376,52 @@ PmError Pm_OpenOutput( PortMidiStream** stream, */
/* filter active sensing messages (0xFE): */
-#define PM_FILT_ACTIVE 0x1
+#define PM_FILT_ACTIVE (1 << 0x0E)
/* filter system exclusive messages (0xF0): */
-#define PM_FILT_SYSEX 0x2
-/* filter clock messages (0xF8 only, does not filter clock start, etc.): */
-#define PM_FILT_CLOCK 0x4
+#define PM_FILT_SYSEX (1 << 0x00)
+/* filter clock messages (CLOCK 0xF8, START 0xFA, STOP 0xFC, and CONTINUE 0xFB) */
+#define PM_FILT_CLOCK ((1 << 0x08) | (1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
-#define PM_FILT_PLAY 0x8
-/* filter undefined F9 messages (some equipment uses this as a 10ms 'tick') */
-#define PM_FILT_F9 0x10
-#define PM_FILT_TICK PM_FILT_F9
+#define PM_FILT_PLAY (1 << 0x0A)
+/* filter tick messages (0xF9) */
+#define PM_FILT_TICK (1 << 0x09)
/* filter undefined FD messages */
-#define PM_FILT_FD 0x20
+#define PM_FILT_FD (1 << 0x0D)
/* filter undefined real-time messages */
-#define PM_FILT_UNDEFINED (PM_FILT_F9 | PM_FILT_FD)
+#define PM_FILT_UNDEFINED PM_FILT_FD
/* filter reset messages (0xFF) */
-#define PM_FILT_RESET 0x40
+#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)
+#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 0x80
+#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
-#define PM_FILT_CHANNEL_AFTERTOUCH 0x100
-/* per-note aftertouch (Ensoniq holds a patent on generating this on keyboards until June 2006) (0xA0-0xAF) */
-#define PM_FILT_POLY_AFTERTOUCH 0x200
+#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 0x400
+#define PM_FILT_PROGRAM (1 << 0x1C)
/* Control Changes (CC's) (0xB0-0xBF)*/
-#define PM_FILT_CONTROL 0x800
+#define PM_FILT_CONTROL (1 << 0x1B)
/* Pitch Bender (0xE0-0xEF*/
-#define PM_FILT_PITCHBEND 0x1000
+#define PM_FILT_PITCHBEND (1 << 0x1E)
/* MIDI Time Code (0xF1)*/
-#define PM_FILT_MTC 0x2000
+#define PM_FILT_MTC (1 << 0x01)
/* Song Position (0xF2) */
-#define PM_FILT_SONG_POSITION 0x4000
+#define PM_FILT_SONG_POSITION (1 << 0x02)
/* Song Select (0xF3)*/
-#define PM_FILT_SONG_SELECT 0x8000
+#define PM_FILT_SONG_SELECT (1 << 0x03)
/* Tuning request (0xF6)*/
-#define PM_FILT_TUNE 0x10000
+#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
@@ -532,11 +446,11 @@ PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); 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.)
+ (PortMidi attempts to close open streams when the application
+ exits -- this is particularly difficult under Windows.)
*/
PmError Pm_Close( PortMidiStream* stream );
@@ -544,7 +458,7 @@ 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_MessageStatus(), Pm_MessageData1(), and
Pm_MessageData2() extract fields from a long-encoded midi message.
*/
#define Pm_Message(status, data1, data2) \
@@ -560,61 +474,61 @@ PmError Pm_Close( PortMidiStream* stream ); 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,
+ 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
+ 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
+ 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
+ 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
+ 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
+ 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
+ Do not expect PortMidi to sort data according to timestamps --
+ messages should be sent in the correct order, and timestamps MUST
be non-decreasing.
- A sysex message will generally fill many PmEvent structures. On
+ 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
+ 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
+ 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.
@@ -627,19 +541,19 @@ typedef struct { /*
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,
+ 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
+ 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
+ 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
@@ -652,20 +566,20 @@ typedef struct { PmError Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length );
/*
- Pm_Poll() tests whether input is available,
+ 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
+ - 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
+ Use Pm_WriteSysEx() to write a sysex message stored as a contiguous
array of bytes.
Sysex data may contain embedded real-time messages.
@@ -674,9 +588,9 @@ 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.)
+ 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);
diff --git a/pd/portmidi/pm_linux/README_LINUX.txt b/pd/portmidi/pm_linux/README_LINUX.txt index e8a4332f..d3adf5a9 100644 --- a/pd/portmidi/pm_linux/README_LINUX.txt +++ b/pd/portmidi/pm_linux/README_LINUX.txt @@ -1,9 +1,14 @@ README_LINUX.txt for PortMidi
Roger Dannenberg
-8 June 2004
+29 Aug 2006
To make PortMidi and PortTime, go back up to the portmidi
-directory and type make.
+directory and type
+
+make -f pm_linux/Makefile
+
+(You can also copy pm_linux/Makefile to the portmidi
+directory and just type "make".)
The Makefile will build all test programs and the portmidi
library. You may want to modify the Makefile to remove the
@@ -21,6 +26,12 @@ all test programs in pm_test seem to run properly. CHANGELOG
+29-aug-2006 Roger B. Dannenberg
+ Fixed PortTime to join with time thread for clean exit.
+
+28-aug-2006 Roger B. Dannenberg
+ Updated this documentation.
+
08-Jun-2004 Roger B. Dannenberg
Updated code to use new system abstraction.
diff --git a/pd/portmidi/pm_linux/pmlinux.c b/pd/portmidi/pm_linux/pmlinux.c index 8c70319f..dcb2b8b2 100644 --- a/pd/portmidi/pm_linux/pmlinux.c +++ b/pd/portmidi/pm_linux/pmlinux.c @@ -5,6 +5,9 @@ be separate from the main portmidi.c file because it is system
dependent, and it is separate from, pmlinuxalsa.c, because it
might need to register non-alsa devices as well.
+
+ NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
+ in pmlinuxalsa.c, which assumes all input devices are ALSA.
*/
#include "stdlib.h"
@@ -19,12 +22,18 @@ PmError 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
pm_linuxnull_init();
#endif
+ return pmNoError;
}
void pm_term(void)
diff --git a/pd/portmidi/pm_linux/pmlinuxalsa.c b/pd/portmidi/pm_linux/pmlinuxalsa.c index 9b0eee75..6132e090 100644 --- a/pd/portmidi/pm_linux/pmlinuxalsa.c +++ b/pd/portmidi/pm_linux/pmlinuxalsa.c @@ -9,6 +9,9 @@ #include "stdlib.h"
#include "portmidi.h"
+#ifdef NEWBUFFER
+#include "pmutil.h"
+#endif
#include "pminternal.h"
#include "pmlinuxalsa.h"
#include "string.h"
@@ -41,7 +44,8 @@ extern pm_fns_node pm_linuxalsa_in_dictionary;
extern pm_fns_node pm_linuxalsa_out_dictionary;
-static snd_seq_t *seq; // all input comes here, output queue allocated on seq
+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 {
@@ -208,7 +212,7 @@ static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, when = (when - now) + midi->latency;
if (when < 0) when = 0;
VERBOSE printf("timestamp %d now %d latency %d, ",
- timestamp, now, midi->latency);
+ (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 */
snd_seq_ev_schedule_tick(&ev, queue, 1, when);
@@ -238,7 +242,6 @@ static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, static PmError alsa_out_close(PmInternal *midi)
{
- int err;
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
if (!desc) return pmBadPtr;
@@ -252,6 +255,7 @@ static PmError alsa_out_close(PmInternal *midi) }
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,
@@ -329,7 +333,6 @@ static PmError alsa_in_open(PmInternal *midi, void *driverInfo) static PmError alsa_in_close(PmInternal *midi)
{
- int err;
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
if (!desc) return pmBadPtr;
if (pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port,
@@ -351,8 +354,30 @@ static PmError alsa_in_close(PmInternal *midi) static PmError alsa_abort(PmInternal *midi)
{
+ /* NOTE: ALSA documentation is vague. This is supposed to
+ * remove any pending output messages. If you can test and
+ * confirm this code is correct, please update this comment. -RBD
+ */
+ /* Unfortunately, I can't even compile it -- my ALSA version
+ * does not implement snd_seq_remove_events_t, so this does
+ * not compile. I'll try again, but it looks like I'll need to
+ * upgrade my entire Linux OS -RBD
+ */
+ /*
alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
- /* This is supposed to flush any pending output. */
+ snd_seq_remove_events_t info;
+ snd_seq_addr_t addr;
+ addr.client = desc->client;
+ addr.port = desc->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);
+ if (pm_hosterror) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
+ pm_hosterror);
+ return pmHostError;
+ }
+ */
printf("WARNING: alsa_abort not implemented\n");
return pmNoError;
}
@@ -408,10 +433,10 @@ static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length) #endif
-static PmError alsa_write_flush(PmInternal *midi)
+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%x\n", seq);
+ VERBOSE printf("snd_seq_drain_output: 0x%x\n", (unsigned int) seq);
desc->error = snd_seq_drain_output(seq);
if (desc->error < 0) return pmHostError;
@@ -583,25 +608,42 @@ static void handle_event(snd_seq_event_t *ev) break;
case SND_SEQ_EVENT_SYSEX: {
const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
- int i;
- long msg = 0;
- int shift = 0;
- if (!(midi->filters & PM_FILT_SYSEX)) {
- for (i = 0; i < ev->data.ext.len; i++) {
- pm_read_byte(midi, *ptr++, timestamp);
- }
- }
+ /* assume there is one sysex byte to process */
+ pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
break;
}
}
}
+
static PmError alsa_poll(PmInternal *midi)
{
snd_seq_event_t *ev;
- while (snd_seq_event_input(seq, &ev) >= 0) {
- if (ev) {
- handle_event(ev);
+ /* expensive check for input data, gets data from device: */
+ while (snd_seq_event_input_pending(seq, TRUE) > 0) {
+ /* cheap check on local input buffer */
+ while (snd_seq_event_input_pending(seq, FALSE) > 0) {
+ /* check for and ignore errors, e.g. input overflow */
+ /* note: if there's overflow, this should be reported
+ * all the way through to client. Since input from all
+ * devices is merged, we need to find all input devices
+ * and set all to the overflow state.
+ * NOTE: this assumes every input is ALSA based.
+ */
+ int rslt = snd_seq_event_input(seq, &ev);
+ if (rslt >= 0) {
+ 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;
+ /* careful, device may not be open! */
+ if (midi) Pm_SetOverflow(midi->queue);
+ }
+ }
+ }
}
}
return pmNoError;
@@ -677,8 +719,17 @@ PmError pm_linuxalsa_init( void ) snd_seq_port_info_t *pinfo;
unsigned int caps;
- err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
- if (err < 0) return;
+ /* 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
+ * room to send all the data. The client should normally allocate
+ * a large enough buffer to avoid blocking on output.
+ * Now that blocking is enabled, the seq_event_input() will block
+ * if there is no input data. This is not what we want, so must
+ * call seq_event_input_pending() to avoid blocking.
+ */
+ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+ if (err < 0) return err;
snd_seq_client_info_alloca(&cinfo);
snd_seq_port_info_alloca(&pinfo);
@@ -715,10 +766,17 @@ PmError pm_linuxalsa_init( void ) }
}
}
+ return pmNoError;
}
void pm_linuxalsa_term(void)
{
- snd_seq_close(seq);
+ if (seq) {
+ snd_seq_close(seq);
+ pm_free(descriptors);
+ descriptors = NULL;
+ pm_descriptor_index = 0;
+ pm_descriptor_max = 0;
+ }
}
diff --git a/pd/portmidi/pm_mac/pmmacosxcm.c b/pd/portmidi/pm_mac/pmmacosxcm.c index 0a293aed..b990c86a 100644 --- a/pd/portmidi/pm_mac/pmmacosxcm.c +++ b/pd/portmidi/pm_mac/pmmacosxcm.c @@ -1,709 +1,934 @@ -/*
- * Platform interface to the MacOS X CoreMIDI framework
- *
- * Jon Parise <jparise@cmu.edu>
- * and subsequent work by Andrew Zeldis and Zico Kolter
- * and Roger B. Dannenberg
- *
- * $Id: pmmacosxcm.c,v 1.23 2007-08-06 16:39:54 millerpuckette Exp $
- */
-
-/* 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. As in pmwinmm.c, 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.
-
- OS X does not seem to have an error-code-to-text function, so we will
- just use text messages instead of error codes.
- */
-
-#include <stdlib.h>
-
-#include "portmidi.h"
-#include "pminternal.h"
-#include "porttime.h"
-#include "pmmac.h"
-#include "pmmacosxcm.h"
-
-#include <stdio.h>
-#include <string.h>
-
-#include <CoreServices/CoreServices.h>
-#include <CoreMIDI/MIDIServices.h>
-#include <CoreAudio/HostTime.h>
-
-#define PACKET_BUFFER_SIZE 1024
-
-/* 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 SYSEX_BUFFER_SIZE 4
-
-#define VERBOSE_ON 1
-#define VERBOSE if (VERBOSE_ON)
-
-#define MIDI_SYSEX 0xf0
-#define MIDI_EOX 0xf7
-#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 */
-
-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? */
- 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 */
- 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 */
-} midi_macosxcm_node, *midi_macosxcm_type;
-
-/* private function declarations */
-MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
-PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
-
-char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
-
-
-static int
-midi_length(long 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, 1, 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 != 0xF0) ? high_lengths[high] : low_lengths[low];
-}
-
-static PmTimestamp midi_synchronize(PmInternal *midi)
-{
- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
- UInt64 pm_stream_time_2 =
- AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
- PmTimestamp real_time;
- UInt64 pm_stream_time;
- /* 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));
- 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());
- /* 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;
- return real_time;
-}
-
-
-/* called when MIDI packets are received */
-static void
-readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
-{
- PmInternal *midi;
- midi_macosxcm_type m;
- PmEvent event;
- MIDIPacket *packet;
- unsigned int packetIndex;
- unsigned long now;
- unsigned int status;
-
- /* Retrieve the context for this connection */
- midi = (PmInternal *) connRefCon;
- m = (midi_macosxcm_type) midi->descriptor;
- assert(m);
-
- /* synchronize time references every 100ms */
- now = (*midi->time_proc)(midi->time_info);
- if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
- /* time to resync */
- now = midi_synchronize(midi);
- m->first_message = FALSE;
- }
-
- packet = (MIDIPacket *) &newPackets->packet[0];
- /* printf("readproc packet status %x length %d\n", 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 */
- if (status == MIDI_SYSEX || status == MIDI_EOX ||
- !(status & MIDI_STATUS_MASK)) {
- int i = 0;
- while (i < packet->length) {
- pm_read_byte(midi, packet->data[i], event.timestamp);
- i++;
- }
- } else {
- /* Build the PmMessage for the PmEvent structure */
- switch (packet->length) {
- case 1:
- event.message = Pm_Message(packet->data[0], 0, 0);
- break;
- case 2:
- event.message = Pm_Message(packet->data[0],
- packet->data[1], 0);
- break;
- case 3:
- event.message = Pm_Message(packet->data[0],
- packet->data[1],
- packet->data[2]);
- break;
- default:
- /* Skip packets that are too large to fit in a PmMessage */
-#ifdef DEBUG
- printf("PortMidi debug msg: large packet skipped\n");
-#endif
- continue;
- }
- pm_read_short(midi, &event);
- }
- packet = MIDIPacketNext(packet);
- }
-}
-
-static PmError
-midi_in_open(PmInternal *midi, void *driverInfo)
-{
- MIDIEndpointRef endpoint;
- midi_macosxcm_type m;
- OSStatus macHostError;
-
- /* 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;
- }
-
- m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
- midi->descriptor = m;
- if (!m) {
- 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;
-
- 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;
- }
-
- return pmNoError;
-}
-
-static PmError
-midi_in_close(PmInternal *midi)
-{
- MIDIEndpointRef endpoint;
- OSStatus macHostError;
- PmError err = pmNoError;
-
- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-
- if (!m) return pmBadPtr;
-
- endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
- if (endpoint == NULL) {
- pm_hosterror = 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;
- }
-
- midi->descriptor = NULL;
- pm_free(midi->descriptor);
-
- return err;
-}
-
-
-static PmError
-midi_out_open(PmInternal *midi, void *driverInfo)
-{
- midi_macosxcm_type m;
-
- m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
- midi->descriptor = m;
- if (!m) {
- 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;
-
- return pmNoError;
-}
-
-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);
-
- return pmNoError;
-}
-
-static PmError
-midi_abort(PmInternal *midi)
-{
- return pmNoError;
-}
-
-
-static PmError
-midi_write_flush(PmInternal *midi)
-{
- OSStatus macHostError;
- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
- MIDIEndpointRef endpoint =
- (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
- assert(m);
- assert(endpoint);
- if (m->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;
- }
- return pmNoError;
-
-send_packet_error:
- pm_hosterror = macHostError;
- sprintf(pm_hosterror_text,
- "Host error %ld: MIDISend() in midi_write()",
- macHostError);
- return pmHostError;
-
-}
-
-
-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);
-
- /* 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) {
- /* 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)) != 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 */
- }
- return pmNoError;
-}
-
-
-static PmError
-midi_write_short(PmInternal *midi, PmEvent *event)
-{
- long when = event->timestamp;
- long what = event->message;
- MIDITimeStamp timestamp;
- UInt64 when_ns;
- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
- Byte message[4];
- unsigned int messageLength;
-
- if (m->packet == NULL) {
- m->packet = MIDIPacketListInit(m->packetList);
- /* this can never fail, right? failure would indicate something
- unrecoverable */
- assert(m->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);
-
- message[0] = Pm_MessageStatus(what);
- message[1] = Pm_MessageData1(what);
- message[2] = Pm_MessageData2(what);
- messageLength = midi_length(what);
-
- /* Add this message to the packet list */
- return send_packet(midi, message, messageLength, timestamp);
-}
-
-
-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;
-
- /* 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);
-
- if (m->packet == NULL) {
- m->packet = MIDIPacketListInit(m->packetList);
- /* this can never fail, right? failure would indicate something
- unrecoverable */
- assert(m->packet);
- }
- return pmNoError;
-}
-
-
-static PmError
-midi_end_sysex(PmInternal *midi, PmTimestamp when)
-{
- PmError err;
- midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
- assert(m);
-
- /* make sure we don't go backward in time */
- if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time;
-
- /* now send what's in the buffer */
- if (m->packet == NULL) {
- /* if flush has been called in the meantime, packet list is NULL */
- m->packet = MIDIPacketListInit(m->packetList);
- /* this can never fail, right? failure would indicate something
- unrecoverable */
- assert(m->packet);
- }
-
- err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
- m->sysex_timestamp);
- m->sysex_byte_count = 0;
- if (err != pmNoError) {
- m->packet = NULL; /* flush everything in the packet list */
- return err;
- }
- return pmNoError;
-}
-
-
-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) {
- PmError err = midi_end_sysex(midi, timestamp);
- if (err != pmNoError) return err;
- }
- m->sysex_buffer[m->sysex_byte_count++] = byte;
- return pmNoError;
-}
-
-
-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 */
- PmError err = midi_end_sysex(midi, 0);
- if (err != pmNoError) return err;
- /* then we can just do a normal midi_write_short */
- 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)
-{
- 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 */
- }
-}
-
-
-MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
-{
- UInt64 nanos;
- if (timestamp <= 0) {
- return (MIDITimeStamp)0;
- } else {
- nanos = (UInt64)timestamp * (UInt64)1000000;
- return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
- }
-}
-
-PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
-{
- UInt64 nanos;
- nanos = AudioConvertHostTimeToNanos(timestamp);
- return (PmTimestamp)(nanos / (UInt64)1000000);
-}
-
-
-char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
-{
- MIDIEntityRef entity;
- MIDIDeviceRef device;
- CFStringRef endpointName = NULL, deviceName = NULL, fullName = NULL;
- CFStringEncoding defaultEncoding;
- char* newName;
-
- /* get the default string encoding */
- defaultEncoding = CFStringGetSystemEncoding();
-
- /* 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;
- }
-
- /* copy the string into our buffer */
- newName = (char*)malloc(CFStringGetLength(fullName) + 1);
- CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
- defaultEncoding);
-
- /* clean up */
- if (endpointName) CFRelease(endpointName);
- if (deviceName) CFRelease(deviceName);
- if (fullName) CFRelease(fullName);
-
- return newName;
-}
-
-
-
-pm_fns_node pm_macosx_in_dictionary = {
- none_write_short,
- none_sysex,
- none_sysex,
- none_write_byte,
- none_write_short,
- none_write_flush,
- none_synchronize,
- midi_in_open,
- midi_abort,
- midi_in_close,
- success_poll,
- midi_has_host_error,
- midi_get_host_error,
-};
-
-pm_fns_node pm_macosx_out_dictionary = {
- midi_write_short,
- midi_begin_sysex,
- midi_end_sysex,
- midi_write_byte,
- midi_write_realtime,
- midi_write_flush,
- midi_synchronize,
- midi_out_open,
- midi_abort,
- midi_out_close,
- success_poll,
- midi_has_host_error,
- midi_get_host_error,
-};
-
-
-PmError pm_macosxcm_init(void)
-{
- ItemCount numInputs, numOutputs, numDevices;
- MIDIEndpointRef endpoint;
- int i;
- OSStatus macHostError;
- char *error_text;
-
- /* 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.
- Pm_CountDevices() will return zero, which is correct and
- useful information
- */
- if (numDevices <= 0) {
- return pmNoError;
- }
-
-
- /* Initialize the client handle */
- macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);
- if (macHostError != noErr) {
- error_text = "MIDIClientCreate() in pm_macosxcm_init()";
- goto error_return;
- }
-
- /* Create the input port */
- macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
- 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) {
- error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
- goto error_return;
- }
-
- /* Iterate over the MIDI input devices */
- for (i = 0; i < numInputs; i++) {
- endpoint = MIDIGetSource(i);
- if (endpoint == NULL) {
- 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);
- }
-
- /* Iterate over the MIDI output devices */
- for (i = 0; i < numOutputs; i++) {
- endpoint = MIDIGetDestination(i);
- if (endpoint == NULL) {
- 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);
- }
- 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;
-}
-
-void pm_macosxcm_term(void)
-{
- if (client != NULL) MIDIClientDispose(client);
- if (portIn != NULL) MIDIPortDispose(portIn);
- if (portOut != NULL) MIDIPortDispose(portOut);
-}
+/* + * Platform interface to the MacOS X CoreMIDI framework + * + * Jon Parise <jparise at cmu.edu> + * and subsequent work by Andrew Zeldis and Zico Kolter + * and Roger B. Dannenberg + * + * $Id: pmmacosxcm.c,v 1.24 2008-01-16 21:54:10 millerpuckette Exp $ + */ + +/* 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. + + OS X does not seem to have an error-code-to-text function, so we will + just use text messages instead of error codes. + */ + +#include <stdlib.h> + +//#define CM_DEBUG 1 + +#include "portmidi.h" +#ifdef NEWBUFFER +#include "pmutil.h" +#endif +#include "pminternal.h" +#include "porttime.h" +#include "pmmac.h" +#include "pmmacosxcm.h" + +#include <stdio.h> +#include <string.h> + +#include <CoreServices/CoreServices.h> +#include <CoreMIDI/MIDIServices.h> +#include <CoreAudio/HostTime.h> + +#define PACKET_BUFFER_SIZE 1024 + +/* 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 SYSEX_BUFFER_SIZE 4 + +#define VERBOSE_ON 1 +#define VERBOSE if (VERBOSE_ON) + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#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 */ + +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? */ + 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 */ + 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 */ + /* 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; + +/* private function declarations */ +MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); +PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); + +char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint); + + +static int +midi_length(long 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, 1, 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 != 0xF0) ? high_lengths[high] : low_lengths[low]; +} + +static PmTimestamp midi_synchronize(PmInternal *midi) +{ + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + UInt64 pm_stream_time_2 = + AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + PmTimestamp real_time; + UInt64 pm_stream_time; + /* 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)); + 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()); + /* 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; + 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) +{ + PmInternal *midi; + midi_macosxcm_type m; + PmEvent event; + MIDIPacket *packet; + unsigned int packetIndex; + unsigned long now; + unsigned int status; + +#ifdef CM_DEBUG + printf("readProc: numPackets %d: ", newPackets->numPackets); +#endif + + /* Retrieve the context for this connection */ + midi = (PmInternal *) connRefCon; + m = (midi_macosxcm_type) midi->descriptor; + assert(m); + + /* synchronize time references every 100ms */ + now = (*midi->time_proc)(midi->time_info); + if (m->first_message || m->sync_time + 100 /*ms*/ < now) { + /* time to resync */ + now = midi_synchronize(midi); + m->first_message = FALSE; + } + + packet = (MIDIPacket *) &newPackets->packet[0]; + /* printf("readproc packet status %x length %d\n", 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); + } else { + process_packet(packet, &event, midi, m); + } + packet = MIDIPacketNext(packet); + } +#ifdef CM_DEBUG + printf("\n"); +#endif +} + +static PmError +midi_in_open(PmInternal *midi, void *driverInfo) +{ + MIDIEndpointRef endpoint; + midi_macosxcm_type m; + OSStatus macHostError; + + /* 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; + } + + m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ + midi->descriptor = m; + if (!m) { + 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; + } + + return pmNoError; +} + +static PmError +midi_in_close(PmInternal *midi) +{ + MIDIEndpointRef endpoint; + OSStatus macHostError; + PmError err = pmNoError; + + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + + if (!m) return pmBadPtr; + + endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor; + if (endpoint == NULL) { + pm_hosterror = 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; + } + + midi->descriptor = NULL; + pm_free(midi->descriptor); + + return err; +} + + +static PmError +midi_out_open(PmInternal *midi, void *driverInfo) +{ + midi_macosxcm_type m; + + m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */ + midi->descriptor = m; + if (!m) { + 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) +{ + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + if (!m) return pmBadPtr; + + midi->descriptor = NULL; + pm_free(midi->descriptor); + + return pmNoError; +} + +static PmError +midi_abort(PmInternal *midi) +{ + return pmNoError; +} + + +static PmError +midi_write_flush(PmInternal *midi, PmTimestamp timestamp) +{ + OSStatus macHostError; + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + MIDIEndpointRef endpoint = + (MIDIEndpointRef) descriptors[midi->device_id].descriptor; + assert(m); + assert(endpoint); + if (m->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; + } + return pmNoError; + +send_packet_error: + pm_hosterror = macHostError; + sprintf(pm_hosterror_text, + "Host error %ld: MIDISend() in midi_write()", + macHostError); + return pmHostError; + +} + + +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); + + /* 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) { + /* 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 */ + } + return pmNoError; +} + + +static PmError +midi_write_short(PmInternal *midi, PmEvent *event) +{ + long when = event->timestamp; + long what = event->message; + MIDITimeStamp timestamp; + UInt64 when_ns; + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + Byte message[4]; + unsigned int messageLength; + + if (m->packet == NULL) { + m->packet = MIDIPacketListInit(m->packetList); + /* this can never fail, right? failure would indicate something + unrecoverable */ + assert(m->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); + + message[0] = Pm_MessageStatus(what); + message[1] = Pm_MessageData1(what); + message[2] = Pm_MessageData2(what); + messageLength = midi_length(what); + + /* Add this message to the packet list */ + return send_packet(midi, message, messageLength, timestamp); +} + + +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; + + /* 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); + + if (m->packet == NULL) { + m->packet = MIDIPacketListInit(m->packetList); + /* this can never fail, right? failure would indicate something + unrecoverable */ + assert(m->packet); + } + return pmNoError; +} + + +static PmError +midi_end_sysex(PmInternal *midi, PmTimestamp when) +{ + PmError err; + midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor; + assert(m); + + /* make sure we don't go backward in time */ + if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time; + + /* if flush has been called in the meantime, packet list is NULL */ + if (m->packet == NULL) { + m->packet = MIDIPacketListInit(m->packetList); + assert(m->packet); + } + + /* 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; + if (err != pmNoError) { + m->packet = NULL; /* flush everything in the packet list */ + return err; + } + return pmNoError; +} + + +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) { + PmError err = midi_end_sysex(midi, timestamp); + if (err != pmNoError) return err; + } + m->sysex_buffer[m->sysex_byte_count++] = byte; + return pmNoError; +} + + +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 */ + PmError err = midi_end_sysex(midi, 0); + if (err != pmNoError) return err; + /* then we can just do a normal midi_write_short */ + 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) +{ + 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 */ + } +} + + +MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp) +{ + UInt64 nanos; + if (timestamp <= 0) { + return (MIDITimeStamp)0; + } else { + nanos = (UInt64)timestamp * (UInt64)1000000; + return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos); + } +} + +PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp) +{ + UInt64 nanos; + nanos = AudioConvertHostTimeToNanos(timestamp); + return (PmTimestamp)(nanos / (UInt64)1000000); +} + + +// +// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html +////////////////////////////////////// +// 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) +{ + 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); + } + + 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 + str = NULL; + MIDIObjectGetStringProperty(entity, 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 (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 { + // 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; +} + +// 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; + CFStringAppend(result, str); + CFRelease(str); + } + } + } + } + CFRelease(connections); + } + if (anyStrings) + return result; + + // Here, either the endpoint had no connections, or we failed to obtain names for any of them. + return EndpointName(endpoint, false); +} + + +char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint) +{ +#ifdef OLDCODE + MIDIEntityRef entity; + MIDIDeviceRef device; + + CFStringRef endpointName = NULL; + CFStringRef deviceName = NULL; +#endif + CFStringRef fullName = NULL; + CFStringEncoding defaultEncoding; + char* newName; + + /* get the default string encoding */ + defaultEncoding = CFStringGetSystemEncoding(); + + 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); + + /* clean up */ +#ifdef OLDCODE + if (endpointName) CFRelease(endpointName); + if (deviceName) CFRelease(deviceName); +#endif + if (fullName) CFRelease(fullName); + + return newName; +} + + + +pm_fns_node pm_macosx_in_dictionary = { + none_write_short, + none_sysex, + none_sysex, + none_write_byte, + none_write_short, + none_write_flush, + none_synchronize, + midi_in_open, + midi_abort, + midi_in_close, + success_poll, + midi_has_host_error, + midi_get_host_error, +}; + +pm_fns_node pm_macosx_out_dictionary = { + midi_write_short, + midi_begin_sysex, + midi_end_sysex, + midi_write_byte, + midi_write_realtime, + midi_write_flush, + midi_synchronize, + midi_out_open, + midi_abort, + midi_out_close, + success_poll, + midi_has_host_error, + midi_get_host_error, +}; + + +PmError pm_macosxcm_init(void) +{ + ItemCount numInputs, numOutputs, numDevices; + MIDIEndpointRef endpoint; + int i; + OSStatus macHostError; + char *error_text; + + /* 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. + Pm_CountDevices() will return zero, which is correct and + useful information + */ + if (numDevices <= 0) { + return pmNoError; + } + + + /* Initialize the client handle */ + macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client); + if (macHostError != noErr) { + error_text = "MIDIClientCreate() in pm_macosxcm_init()"; + goto error_return; + } + + /* Create the input port */ + macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc, + 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) { + error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()"; + goto error_return; + } + + /* Iterate over the MIDI input devices */ + for (i = 0; i < numInputs; i++) { + endpoint = MIDIGetSource(i); + if (endpoint == NULL) { + 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); + } + + /* Iterate over the MIDI output devices */ + for (i = 0; i < numOutputs; i++) { + endpoint = MIDIGetDestination(i); + if (endpoint == NULL) { + 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); + } + 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; +} + +void pm_macosxcm_term(void) +{ + if (client != NULL) MIDIClientDispose(client); + if (portIn != NULL) MIDIPortDispose(portIn); + if (portOut != NULL) MIDIPortDispose(portOut); +} diff --git a/pd/portmidi/pm_win/README_WIN.txt b/pd/portmidi/pm_win/README_WIN.txt index df120e96..3fe5ee41 100644 --- a/pd/portmidi/pm_win/README_WIN.txt +++ b/pd/portmidi/pm_win/README_WIN.txt @@ -20,7 +20,8 @@ intended for debugging, especially in a console application. The Debug version enables some extra error checking and outputs some text as well
as a prompt to type ENTER so that you don't lose any debugging text when
the program exits. You can turn off this extra debugging info by taking
-out the compile-time definition for DEBUG. This debugging version also
+out the compile-time definition for DEBUG. (But leave _DEBUG, which I
+think is important for compiling in Debug mode.) This debugging version also
defines PM_CHECK_ERRORS, which forces a check for error return codes from
every call to PortMidi. You can disable this checking (especially if you
want to handle error codes in your own way) by removing PM_CHECK_ERRORS
@@ -45,9 +46,9 @@ TO INSTALL PORTMIDI: TO COMPILE PORTMIDI:
=============================================================================
-3) go to this directory
+3) cd to or open the portmidi directory
-4) click on the portmidi.dsw workspace
+4) start or click on the portmidi.dsw workspace
5) the following projects exist within this workspace:
- portmidi (the PortMidi library)
@@ -60,18 +61,16 @@ TO COMPILE PORTMIDI: - latency (uses porttime to measure system latency)
6) verify that all project settings are for Win32 Debug release:
- - hit Alt-F7
+ - type Alt-F7
- highlight all three projects in left part of Project Settings window;
- "Settings For" should say "Win32 Debug"
-7) set pm_dll as the active project (e.g. Project->Select Active Project)
+7) use Build->Batch Build ... to build everything in the project
-8) use Build->Batch Build ... to build everything in the project
-
-9) The settings for these projects were distributed in the zip file, so
+8) The settings for these projects were distributed in the zip file, so
compile should just work.
-10) IMPORTANT! PortMidi uses a DLL, pm_dll.dll, but there is no simple way
+9) IMPORTANT! PortMidi uses a DLL, pm_dll.dll, but there is no simple way
to set up projects to use pm_dll. THEREFORE, you need to copy DLLs
as follows (you can do this with <...>\portmidi\pm_win\copy-dll.bat):
copy <...>\portmidi\pm_win\Debug\pm_dll.dll to:
@@ -94,13 +93,13 @@ TO COMPILE PORTMIDI: application using PortMidi. The release DLL is about 40KB. This will
ensure that the application uses the correct DLL.
-11) run test project; use the menu that shows up from the command prompt to
+10) run test project; use the menu that shows up from the command prompt to
test that portMidi works on your system. tests include:
- verify midi output works
- verify midi input works
- verify midi input w/midi thru works
-12) run other projects if you wish: sysex, latency, and midithread
+11) run other projects if you wish: sysex, latency, midithread, mm, qtest
============================================================================
TO CREATE YOUR OWN PORTMIDI CLIENT APPLICATION:
@@ -179,5 +178,114 @@ To open input: - return
- return
-
-
+SYSEX HANDLING -- the most complex, least exercised, and therefore most
+ buggy part of PortMidi (but maybe bugs are finally gone)
+
+There are three cases: simple output, stream output, input
+Each must deal with:
+ 1. Buffer Initialization (creating buffers)
+ 2. Buffer Allocation (finding a free buffer)
+ 3. Buffer Fill (putting bytes in the buffer)
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ 5. Buffer Send (to Midi device)
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ 8. Buffer Free (returning to the buffer pool)
+ 9. Buffer Finalization (returning to heap)
+
+Here's how simple output handles sysex:
+ 1. Buffer Initialization (creating buffers)
+ allocated when code tries to write first byte to a buffer
+ the test is "if (!m->sysex_buffers[0]) { ... }"
+ this field is initialized to NULL when device is opened
+ the size is SYSEX_BYTES_PER_BUFFER
+ allocate_sysex_buffers() does the initialization
+ note that the actual size of the allocation includes
+ additional space for a MIDIEVENT (3 longs) which are
+ not used in this case
+ 2. Buffer Allocation (finding a free buffer)
+ see get_free_sysex_buffer()
+ cycle through m->sysex_buffers[] using m->next_sysex_buffer
+ to determine where to look next
+ if nothing is found, wait by blocking on m->sysex_buffer_signal
+ this is signaled by the callback every time a message is
+ received
+ 3. Buffer Fill (putting bytes in the buffer)
+ essentially a state machine approach
+ hdr->dwBytesRecorded is a position in message pointed to by m->hdr
+ keep appending bytes until dwBytesRecorded >= SYSEX_BYTES_PER_BUFFER
+ then send the message, reseting the state to initial values
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ just before sending in winmm_end_sysex()
+ 5. Buffer Send (to Midi device)
+ message is padded with zero at end (since extra space was allocated
+ this is ok) -- the zero works around a bug in (an old version of)
+ MIDI YOKE drivers
+ dwBufferLength gets dwBytesRecorded, and dwBytesRecorded gets 0
+ uses midiOutLongMsg()
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ not applicable for output
+ 8. Buffer Free (returning to the buffer pool)
+ unprepare message to indicate that it is free
+ SetEvent on m->buffer_signal in case client is waiting
+ 9. Buffer Finalization (returning to heap)
+ when device is closed, winmm_out_delete frees all sysex buffers
+
+Here's how stream output handles sysex:
+ 1. Buffer Initialization (creating buffers)
+ same code as simple output (see above)
+ 2. Buffer Allocation (finding a free buffer)
+ same code as simple output (see above)
+ 3. Buffer Fill (putting bytes in the buffer)
+ essentially a state machine approach
+ m->dwBytesRecorded is a position in message
+ keep appending bytes until buffer is full (one byte to spare)
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ done before sending message
+ dwBytesRecorded and dwBufferLength are set in winmm_end_sysex
+ 5. Buffer Send (to Midi device)
+ uses midiStreamOutMsg()
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ not applicable for output
+ 8. Buffer Free (returning to the buffer pool)
+ unprepare message to indicate that it is free
+ SetEvent on m->buffer_signal in case client is waiting
+ 9. Buffer Finalization (returning to heap)
+ when device is closed, winmm_out_delete frees all sysex buffers
+
+
+Here's how input handles sysex:
+ 1. Buffer Initialization (creating buffers)
+ two buffers are allocated in winmm_in_open
+ 2. Buffer Allocation (finding a free buffer)
+ same code as simple output (see above)
+ 3. Buffer Fill (putting bytes in the buffer)
+ not applicable for input
+ 4. Buffer Preparation (midiOutPrepare, etc.)
+ done before sending message -- in winmm_in_open and in callback
+ 5. Buffer Send (to Midi device)
+ uses midiInAddbuffer in allocate_sysex_input_buffer (called from
+ winmm_in_open) and callback
+ 6. Buffer Receive (in callback)
+ 7. Buffer Empty (removing bytes from buffer)
+ done without pause in loop in callback
+ 8. Buffer Free (returning to the buffer pool)
+ done by midiInAddBuffer in callback, no pointer to buffers
+ is retained except by device
+ 9. Buffer Finalization (returning to heap)
+ when device is closed, empty buffers are delivered to callback,
+ which frees them
+
+IMPORTANT: In addition to the above, PortMidi now has
+"shortcuts" to optimize the transfer of sysex data. To enable
+the optimization for sysex output, the system-dependent code
+sets fields in the pmInternal structure: fill_base, fill_offset_ptr,
+and fill_length. When fill_base is non-null, the system-independent
+part of PortMidi is allowed to directly copy sysex bytes to
+"fill_base[*fill_offset_ptr++]" until *fill_offset_ptr reaches
+fill_length. See the code for details.
+
+
+
\ No newline at end of file diff --git a/pd/portmidi/pm_win/copy-dll.bat b/pd/portmidi/pm_win/copy-dll.bat index 34ccbedd..7c55fff3 100644 --- a/pd/portmidi/pm_win/copy-dll.bat +++ b/pd/portmidi/pm_win/copy-dll.bat @@ -3,11 +3,15 @@ copy Debug\pm_dll.dll ..\pm_test\sysexDebug\pm_dll.dll copy Debug\pm_dll.dll ..\pm_test\midithreadDebug\pm_dll.dll
copy Debug\pm_dll.dll ..\pm_test\latencyDebug\pm_dll.dll
copy Debug\pm_dll.dll ..\pm_test\midithruDebug\pm_dll.dll
+copy Debug\pm_dll.dll ..\pm_test\qtestDebug\pm_dll.dll
+copy Debug\pm_dll.dll ..\pm_test\mmDebug\pm_dll.dll
copy Release\pm_dll.dll ..\pm_test\testRelease\pm_dll.dll
copy Release\pm_dll.dll ..\pm_test\sysexRelease\pm_dll.dll
copy Release\pm_dll.dll ..\pm_test\midithreadRelease\pm_dll.dll
copy Release\pm_dll.dll ..\pm_test\latencyRelease\pm_dll.dll
copy Release\pm_dll.dll ..\pm_test\midithruRelease\pm_dll.dll
+copy Release\pm_dll.dll ..\pm_test\qtestRelease\pm_dll.dll
+copy Release\pm_dll.dll ..\pm_test\mmRelease\pm_dll.dll
diff --git a/pd/portmidi/pm_win/pm_dll.dsp b/pd/portmidi/pm_win/pm_dll.dsp index d08e2de7..77218ccb 100644 --- a/pd/portmidi/pm_win/pm_dll.dsp +++ b/pd/portmidi/pm_win/pm_dll.dsp @@ -38,8 +38,8 @@ RSC=rc.exe # PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
-# PROP Output_Dir "pm_win\Release"
-# PROP Intermediate_Dir "pm_win\Release"
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c
@@ -64,12 +64,12 @@ LINK32=link.exe # PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
-# PROP Output_Dir "pm_win\Debug"
-# PROP Intermediate_Dir "pm_win\Debug"
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 1
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "pm_common" /D "_WINDOWS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "pm_common" /D "_WINDOWS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c
# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x409 /d "_DEBUG"
diff --git a/pd/portmidi/pm_win/pmwin.c b/pd/portmidi/pm_win/pmwin.c index b289194b..716b68fa 100644 --- a/pd/portmidi/pm_win/pmwin.c +++ b/pd/portmidi/pm_win/pmwin.c @@ -13,6 +13,7 @@ #include "stdlib.h"
#include "portmidi.h"
+#include "pmutil.h"
#include "pminternal.h"
#include "pmwinmm.h"
#ifdef USE_DLL_FOR_CLEANUP
diff --git a/pd/portmidi/pm_win/pmwinmm.c b/pd/portmidi/pm_win/pmwinmm.c index 5bfb0cff..395b1134 100644 --- a/pd/portmidi/pm_win/pmwinmm.c +++ b/pd/portmidi/pm_win/pmwinmm.c @@ -1,8 +1,15 @@ /* pmwinmm.c -- system specific definitions */
+/* without this define, InitializeCriticalSectionAndSpinCount is undefined */
+/* this version level means "Windows 2000 and higher" */
+#define _WIN32_WINNT 0x0500
+
#include "windows.h"
#include "mmsystem.h"
#include "portmidi.h"
+#ifdef NEWBUFFER
+#include "pmutil.h"
+#endif
#include "pminternal.h"
#include "pmwinmm.h"
#include "string.h"
@@ -11,7 +18,7 @@ /* asserts used to verify portMidi code logic is sound; later may want
something more graceful */
#include <assert.h>
-
+#define DEBUG 1
#ifdef DEBUG
/* this printf stuff really important for debugging client app w/host errors.
probably want to do something else besides read/write from/to console
@@ -27,25 +34,67 @@ /* callback routines */
static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,
- WORD wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
+ WORD wMsg, DWORD dwInstance,
+ DWORD dwParam1, DWORD dwParam2);
static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
- DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
+ DWORD dwInstance, DWORD dwParam1,
+ DWORD dwParam2);
static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
- DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
+ DWORD dwInstance, DWORD dwParam1,
+ DWORD dwParam2);
extern pm_fns_node pm_winmm_in_dictionary;
extern pm_fns_node pm_winmm_out_dictionary;
static void winmm_out_delete(PmInternal *midi); /* forward reference */
-#define SYSEX_BYTES_PER_BUFFER 1024
-/* 3 midi messages per buffer */
-#define OUTPUT_BYTES_PER_BUFFER 36
+/*
+A note about buffers: WinMM seems to hold onto buffers longer than
+one would expect, e.g. when I tried using 2 small buffers to send
+long sysex messages, at some point WinMM held both buffers. This problem
+was fixed by making buffers bigger. Therefore, it seems that there should
+be enough buffer space to hold a whole sysex message.
+
+The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
+will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
+Call that the max_sysex_len = buffer_len * 4.
+
+For simple midi output (latency == 0), allocate 3 buffers, each with half
+the size of max_sysex_len, but each at least 256 bytes.
+
+For stream output, there will already be enough space in very short
+buffers, so use them, but make sure there are at least 16.
+
+For input, use many small buffers rather than 2 large ones so that when
+there are short sysex messages arriving frequently (as in control surfaces)
+there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
+but at least 16, of size 64 bytes each.
+
+The following constants help to represent these design parameters:
+*/
+#define NUM_SIMPLE_SYSEX_BUFFERS 3
+#define MIN_SIMPLE_SYSEX_LEN 256
+
+#define MIN_STREAM_BUFFERS 16
+#define STREAM_BUFFER_LEN 24
+
+#define INPUT_SYSEX_LEN 64
+#define MIN_INPUT_BUFFERS 16
+/* if we run out of space for output (assume this is due to a sysex msg,
+ expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
+ */
+#define NUM_EXPANSION_BUFFERS 128
+#define EXPANSION_BUFFER_LEN 1024
+
+/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
#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))
-#define MIDIHDR_BUFFER_LENGTH(x) (x)
-#define MIDIHDR_SIZE(x) (MIDIHDR_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
/*
==============================================================================
@@ -62,9 +111,7 @@ MIDIOUTCAPS midi_out_mapper_caps; UINT midi_num_outputs = 0;
/* per device info */
-typedef struct midiwinmm_struct
-{
-
+typedef struct midiwinmm_struct {
union {
HMIDISTRM stream; /* windows handle for stream */
HMIDIOUT out; /* windows handle for out calls */
@@ -75,28 +122,30 @@ typedef struct midiwinmm_struct * in a round-robin fashion, using next_buffer as an index
*/
LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */
- int num_buffers; /* how many buffers */
+ int max_buffers; /* length of buffers array */
+ int buffers_expanded; /* buffers array expanded for extra msgs? */
+ 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 */
-
- LPMIDIHDR *sysex_buffers; /* pool of buffers for sysex data */
- int num_sysex_buffers; /* how many sysex buffers */
+#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 */
- HANDLE sysex_buffer_signal; /* used to wait for sysex buffer to become free */
-
+#endif
unsigned long last_time; /* last output time */
int first_message; /* flag: treat first message differently */
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 or to send */
+ unsigned int sysex_byte_count; /* count how many received */
LPMIDIHDR hdr; /* the message accumulating sysex to send */
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 */
- int callback_error; /* host error from midi in or out callback */
-}
-midiwinmm_node, *midiwinmm_type;
+ CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */
+} midiwinmm_node, *midiwinmm_type;
/*
@@ -112,11 +161,11 @@ static void pm_winmm_general_inputs() midi_in_caps = pm_alloc(sizeof(MIDIINCAPS) * midi_num_inputs);
if (midi_in_caps == NULL) {
- // if you can't open a particular system-level midi interface
- // (such as winmm), we just consider that system or API to be
- // unavailable and move on without reporting an error. This
- // may be the wrong thing to do, especially in this case.
- return ;
+ /* if you can't open a particular system-level midi interface
+ * (such as winmm), we just consider that system or API to be
+ * unavailable and move on without reporting an error.
+ */
+ return;
}
for (i = 0; i < midi_num_inputs; i++) {
@@ -140,7 +189,8 @@ static void pm_winmm_mapper_input() capabilities) then you still should retrieve some formof
setup info. */
wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
- (LPMIDIINCAPS) & midi_in_mapper_caps, sizeof(MIDIINCAPS));
+ (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);
@@ -156,7 +206,7 @@ static void pm_winmm_general_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
+ /* no error is reported -- see pm_winmm_general_inputs */
return ;
}
@@ -194,7 +244,7 @@ host error handling static unsigned int winmm_has_host_error(PmInternal * midi)
{
midiwinmm_type m = (midiwinmm_type)midi->descriptor;
- return m->callback_error || m->error;
+ return m->error;
}
@@ -229,12 +279,6 @@ static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) int err = midiInGetErrorText(m->error, msg + n, len - n);
assert(err == MMSYSERR_NOERROR);
m->error = MMSYSERR_NOERROR;
- } else if (m->callback_error != MMSYSERR_NOERROR) {
- int n = str_copy_len(msg, hdr2, len);
- int err = midiInGetErrorText(m->callback_error, msg + n,
- len - n);
- assert(err == MMSYSERR_NOERROR);
- m->callback_error = MMSYSERR_NOERROR;
}
}
} else { /* output port */
@@ -244,12 +288,6 @@ static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len) int err = midiOutGetErrorText(m->error, msg + n, len - n);
assert(err == MMSYSERR_NOERROR);
m->error = MMSYSERR_NOERROR;
- } else if (m->callback_error != MMSYSERR_NOERROR) {
- int n = str_copy_len(msg, hdr2, len);
- int err = midiOutGetErrorText(m->callback_error, msg + n,
- len = n);
- assert(err == MMSYSERR_NOERROR);
- m->callback_error = MMSYSERR_NOERROR;
}
}
}
@@ -263,132 +301,181 @@ buffer handling */
static MIDIHDR *allocate_buffer(long data_size)
{
- /*
- * with short messages, the MIDIEVENT structure contains the midi message,
- * so there is no need for additional data
- */
-
- LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SIZE(data_size));
+ 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->dwBufferLength = MIDIHDR_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */
+ hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
+ hdr->dwBytesRecorded = 0;
hdr->dwFlags = 0;
- hdr->dwUser = 0;
+ hdr->dwUser = hdr->dwBufferLength;
return hdr;
}
+#ifdef USE_SYSEX_BUFFERS
static MIDIHDR *allocate_sysex_buffer(long data_size)
{
- /* we're actually allocating slightly more than data_size because one more word of
- * data is contained in MIDIEVENT. We include the size of MIDIEVENT because we need
- * the MIDIEVENT header in addition to the data
+ /* 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->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */
hdr->dwFlags = 0;
hdr->dwUser = 0;
return hdr;
}
+#endif
static PmError allocate_buffers(midiwinmm_type m, long data_size, long count)
{
- PmError rslt = pmNoError;
+ 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->num_buffers = count;
- while (count > 0) {
+ m->max_buffers = count;
+ for (i = 0; i < count; i++) {
LPMIDIHDR hdr = allocate_buffer(data_size);
- if (!hdr) rslt = pmInsufficientMemory;
- count--;
- m->buffers[count] = hdr; /* this may be NULL if allocation fails */
+ 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;
+ return pmInsufficientMemory;
+ }
+ m->buffers[i] = hdr; /* this may be NULL if allocation fails */
}
- return rslt;
+ m->num_buffers = count;
+ return pmNoError;
}
-static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size, long count)
+#ifdef USE_SYSEX_BUFFERS
+static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size)
{
PmError rslt = pmNoError;
- /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
- m->sysex_buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
- if (!m->sysex_buffers) return pmInsufficientMemory;
- m->num_sysex_buffers = count;
- while (count > 0) {
+ /* 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;
- count--;
- m->sysex_buffers[count] = hdr; /* this may be NULL if allocation fails */
+ 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) {
- if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) {
+ 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 < m->num_sysex_buffers; 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 >= m->num_sysex_buffers) m->next_sysex_buffer = 0;
+ 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 */
- WaitForSingleObject(m->sysex_buffer_signal, INFINITE);
+ 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;
- m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR));
+ 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;
- if (!m->buffers) {
- if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, 2)) {
- return NULL;
- }
- }
- /* busy wait until we find a free buffer */
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];
if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
}
/* after scanning every buffer and not finding anything, block */
- WaitForSingleObject(m->buffer_signal, INFINITE);
+ if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) {
+#ifdef DEBUG
+ 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) {
+ LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
+ (m->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.
+ * I don't know the right answer, but waiting is easier.
+ */
+ 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;
+ }
+ /* next, add one buffer and return it */
+ if (m->num_buffers < m->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;
+ goto found_buffer; /* break out of 2 loops */
+ }
+ /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
+ * and we have no free buffers to send. We'll just keep
+ * polling to see if any buffers show up.
+ */
+ }
}
found_buffer:
r->dwBytesRecorded = 0;
- m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR));
+ /* actual buffer length is saved in dwUser field */
+ r->dwBufferLength = (DWORD) r->dwUser;
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 be a multiple of 4 */
+ /* buffer must be smaller than 64k, but be also a multiple of 4 */
if (new_size > 65520) {
if (old_size >= 65520)
return pmBufferMaxSize;
@@ -407,35 +494,54 @@ static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_siz /* 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 < m->num_sysex_buffers;i++) {
+ 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 != m->num_sysex_buffers);
+ assert(i != NUM_SYSEX_BUFFERS);
return pmNoError;
-
}
+#endif
+
/*
=========================================================================================
begin midi input implementation
=========================================================================================
*/
+
+static PmError allocate_input_buffer(HMIDIIN h, long buffer_len)
+{
+ LPMIDIHDR hdr = allocate_buffer(buffer_len);
+ if (!hdr) return pmInsufficientMemory;
+ pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
+ if (pm_hosterror) {
+ pm_free(hdr);
+ return pm_hosterror;
+ }
+ pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
+ return pm_hosterror;
+}
+
+
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;
- LPMIDIHDR hdr;
- long buffer_len;
+
dwDevice = (DWORD) descriptors[i].descriptor;
/* create system dependent device data */
@@ -443,22 +549,31 @@ static PmError winmm_in_open(PmInternal *midi, void *driverInfo) midi->descriptor = m;
if (!m) goto no_memory;
m->handle.in = NULL;
- m->buffers = NULL;
- m->num_buffers = 0;
- m->next_buffer = 0;
- m->sysex_buffers = NULL;
- m->num_sysex_buffers = 0;
- m->next_sysex_buffer = 0;
+ 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;
- m->callback_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.
+ */
+ InitializeCriticalSectionAndSpinCount(&m->lock, 4000);
/* open device */
pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */
dwDevice, /* device ID */
@@ -467,32 +582,15 @@ static PmError winmm_in_open(PmInternal *midi, void *driverInfo) CALLBACK_FUNCTION); /* callback is a procedure */
if (pm_hosterror) goto free_descriptor;
- /* allocate first buffer for sysex data */
- buffer_len = midi->buffer_len - 1;
- if (midi->buffer_len < 32)
- buffer_len = PM_DEFAULT_SYSEX_BUFFER_SIZE;
-
- hdr = allocate_sysex_buffer(buffer_len);
- if (!hdr) goto close_device;
- pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR));
- if (pm_hosterror) {
- pm_free(hdr);
- goto close_device;
- }
- pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR));
- if (pm_hosterror) goto close_device;
-
- /* allocate second buffer */
- hdr = allocate_sysex_buffer(buffer_len);
- if (!hdr) goto close_device;
- pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR));
- if (pm_hosterror) {
- pm_free(hdr);
- goto reset_device; /* because first buffer was added */
+ 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)) {
+ /* either pm_hosterror was set, or the proper return code
+ is pmInsufficientMemory */
+ goto close_device;
+ }
}
- pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR));
- if (pm_hosterror) goto reset_device;
-
/* start device */
pm_hosterror = midiInStart(m->handle.in);
if (pm_hosterror) goto reset_device;
@@ -521,6 +619,12 @@ no_memory: 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 */
/*
@@ -540,6 +644,7 @@ static PmError winmm_in_close(PmInternal *midi) pm_hosterror = midiInClose(m->handle.in);
}
midi->descriptor = NULL;
+ DeleteCriticalSection(&m->lock);
pm_free(m); /* delete */
if (pm_hosterror) {
int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
@@ -563,85 +668,124 @@ static void FAR PASCAL winmm_in_callback( PmInternal *midi = (PmInternal *) dwInstance;
midiwinmm_type m = (midiwinmm_type) midi->descriptor;
- if (++entry > 1) {
- assert(FALSE);
- }
-
- /* for simplicity, this logic perhaps overly conservative */
- /* note also that this might leak memory if buffers are being
- returned as a result of midiInReset */
- if (m->callback_error) {
- entry--;
- return ;
- }
+ /* if this callback is reentered with data, we're in trouble. It's hard
+ * to imagine that Microsoft would allow callbacks to be reentrant --
+ * isn't the model that this is like a hardware interrupt? -- but I've
+ * seen reentrant behavior using a debugger, so it happens.
+ */
+ EnterCriticalSection(&m->lock);
switch (wMsg) {
case MIM_DATA: {
- /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
- message LOB;
- dwParam2 is time message received by input device driver, specified
- 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 happens running the
- sysex.c test under Win2K with MidiMan USB 1x1 interface.
- Is it a driver bug or a Win32 bug? If not, there's a bug
- here somewhere. -RBD
- */
- } else { /* data to process */
- PmEvent event;
- if (midi->time_proc)
- dwParam2 = (*midi->time_proc)(midi->time_info);
- event.timestamp = dwParam2;
- event.message = dwParam1;
- pm_read_short(midi, &event);
- }
- break;
- }
- case MIM_LONGDATA: {
- MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
- unsigned char *data = lpMidiHdr->lpData;
- unsigned int i = 0;
- long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength;
- /* ignore sysex data, but free returned buffers */
- if (lpMidiHdr->dwBytesRecorded > 0 &&
- midi->filters & PM_FILT_SYSEX) {
- m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr,
- sizeof(MIDIHDR));
- break;
- }
+ /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
+ message LOB;
+ dwParam2 is time message received by input device driver, specified
+ 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,
+ but I can't reproduce it. -RBD
+ */
+ /* printf("non-status byte found\n"); */
+ } else { /* data to process */
+ PmEvent event;
if (midi->time_proc)
dwParam2 = (*midi->time_proc)(midi->time_info);
-
- while (i < lpMidiHdr->dwBytesRecorded) {
- /* collect bytes from *data into a word */
+ event.timestamp = dwParam2;
+ event.message = dwParam1;
+ pm_read_short(midi, &event);
+ }
+ break;
+ }
+ case MIM_LONGDATA: {
+ MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
+ unsigned char *data = lpMidiHdr->lpData;
+ unsigned int processed = 0;
+ int remaining = lpMidiHdr->dwBytesRecorded;
+ /* 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;
+ }
+#ifdef DELETE_THIS
+ unsigned int i = 0;
+ long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength;
+
+ while (i < lpMidiHdr->dwBytesRecorded) {
+ /* optimization: if message_count == 0, we are on an (output)
+ * message boundary so we can transfer data directly to the
+ * queue
+ */
+ PmEvent event;
+ if (midi->sysex_message_count == 0 &&
+ !midi->flush &&
+ i <= lpMidiHdr->dwBytesRecorded - 4 &&
+ ((event.message = (((long) data[0]) |
+ (((long) data[1]) << 8) | (((long) data[2]) << 16) |
+ (((long) data[3]) << 24))) &
+ 0x80808080) == 0) { /* all data, no status */
+ event.timestamp = dwParam2;
+ if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
+ midi->flush = TRUE;
+ }
+ i += 4;
+ data += 4;
+ /* non-optimized: process one byte at a time. This is used to
+ * handle any embedded SYSEX or EOX bytes and to finish */
+ } else {
pm_read_byte(midi, *data, dwParam2);
data++;
i++;
}
- /* 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) {
- m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr,
- sizeof(MIDIHDR));
- } else {
- pm_free(lpMidiHdr);
- }
- break;
}
- case MIM_OPEN: /* fall thru */
+#endif
+ /* 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) {
+ lpMidiHdr->dwBytesRecorded = 0;
+ lpMidiHdr->dwFlags = 0;
+ /* note: no error checking -- can this actually fail? */
+ assert(midiInPrepareHeader(hMidiIn, lpMidiHdr,
+ sizeof(MIDIHDR)) == 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);
+ } else {
+ pm_free(lpMidiHdr);
+ }
+ break;
+ }
+ case MIM_OPEN:
+ break;
case MIM_CLOSE:
+ break;
case MIM_ERROR:
+ /* printf("MIM_ERROR\n"); */
+ break;
case MIM_LONGERROR:
+ /* printf("MIM_LONGERROR\n"); */
+ break;
default:
break;
}
- entry--;
+ LeaveCriticalSection(&m->lock);
}
/*
@@ -668,28 +812,6 @@ static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr, return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;
}
-#ifdef GARBAGE
-static void start_sysex_buffer(LPMIDIHDR hdr, unsigned long delta)
-{
- unsigned long *ptr = (unsigned long *) hdr->lpData;
- *ptr++ = delta;
- *ptr++ = 0;
- *ptr = MEVT_F_LONG;
- hdr->dwBytesRecorded = 3 * sizeof(long);
-}
-
-static int add_byte_to_buffer(midiwinmm_type m, LPMIDIHDR hdr,
- unsigned char midi_byte)
-{
- allocate message if hdr is null
- send message if it is full
- add byte to non - full message
- unsigned char *ptr = (unsigned char *) (hdr->lpData + hdr->dwBytesRecorded);
- *ptr = midi_byte;
- return ++hdr->dwBytesRecorded >= hdr->dwBufferLength;
-}
-#endif
-
static PmTimestamp pm_time_get(midiwinmm_type m)
{
@@ -702,26 +824,6 @@ static PmTimestamp pm_time_get(midiwinmm_type m) return mmtime.u.ticks;
}
-#ifdef GARBAGE
-static unsigned long synchronize(PmInternal *midi, midiwinmm_type m)
-{
- unsigned long pm_stream_time_2 = pm_time_get(m);
- unsigned long real_time;
- unsigned long pm_stream_time;
- /* figure out the time */
- 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);
- /* 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;
- return real_time;
-}
-#endif
-
/* end helper routines used by midiOutStream interface */
@@ -733,6 +835,9 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) midiwinmm_type m;
MIDIPROPTEMPO propdata;
MIDIPROPTIMEDIV divdata;
+ int max_sysex_len = midi->buffer_len * 4;
+ int output_buffer_len;
+ int num_buffers;
dwDevice = (DWORD) descriptors[i].descriptor;
/* create system dependent device data */
@@ -742,10 +847,14 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) m->handle.out = NULL;
m->buffers = NULL;
m->num_buffers = 0;
+ m->max_buffers = 0;
+ m->buffers_expanded = FALSE;
m->next_buffer = 0;
- m->sysex_buffers = NULL;
- m->num_sysex_buffers = 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;
@@ -755,22 +864,20 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) m->sync_time = 0;
m->delta = 0;
m->error = MMSYSERR_NOERROR;
- m->callback_error = MMSYSERR_NOERROR;
/* create a signal */
m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
- m->sysex_buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
/* this should only fail when there are very serious problems */
assert(m->buffer_signal);
- assert(m->sysex_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 */
- (DWORD) winmm_out_callback,
+ /* note: same callback fn as for StreamOpen: */
+ (DWORD) winmm_streamout_callback, /* callback fn */
(DWORD) midi, /* callback instance data */
CALLBACK_FUNCTION); /* callback type */
} else {
@@ -786,12 +893,17 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) goto free_descriptor;
}
- if (midi->latency != 0) {
+ if (midi->latency == 0) {
+ num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
+ output_buffer_len = max_sysex_len / num_buffers;
+ if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
+ output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
+ } else {
long dur = 0;
- /* with stream output, specified number of buffers allocated here */
- int count = midi->buffer_len;
- if (count == 0)
- count = midi->latency / 2; /* how many buffers to get */
+ num_buffers = max(midi->buffer_len, midi->latency / 2);
+ if (num_buffers < MIN_STREAM_BUFFERS)
+ num_buffers = MIN_STREAM_BUFFERS;
+ output_buffer_len = STREAM_BUFFER_LEN;
propdata.cbStruct = sizeof(MIDIPROPTEMPO);
propdata.dwTempo = 480000; /* microseconds per quarter */
@@ -806,12 +918,12 @@ static PmError winmm_out_open(PmInternal *midi, void *driverInfo) (LPBYTE) & divdata,
MIDIPROP_SET | MIDIPROP_TIMEDIV);
if (pm_hosterror) goto close_device;
-
- /* allocate at least 3 buffers */
- if (count < 3) count = 3;
- if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, count)) goto free_buffers;
- if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) goto free_buffers;
- /* start device */
+ }
+ /* allocate buffers */
+ if (allocate_buffers(m, output_buffer_len, num_buffers))
+ goto free_buffers;
+ /* start device */
+ if (midi->latency != 0) {
pm_hosterror = midiStreamRestart(m->handle.stream);
if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
}
@@ -839,6 +951,7 @@ no_memory: /**/
static void winmm_out_delete(PmInternal *midi)
{
+ int i;
/* delete system dependent device data */
midiwinmm_type m = (midiwinmm_type) midi->descriptor;
if (m) {
@@ -846,27 +959,19 @@ static void winmm_out_delete(PmInternal *midi) /* don't report errors -- better not to stop cleanup */
CloseHandle(m->buffer_signal);
}
- if (m->sysex_buffer_signal) {
- /* don't report errors -- better not to stop cleanup */
- CloseHandle(m->sysex_buffer_signal);
- }
- if (m->buffers) {
- /* if using stream output, free buffers */
- int i;
- for (i = 0; i < m->num_buffers; i++) {
- if (m->buffers[i]) pm_free(m->buffers[i]);
- }
- pm_free(m->buffers);
+ /* if using stream output, free buffers */
+ for (i = 0; i < m->num_buffers; i++) {
+ if (m->buffers[i]) pm_free(m->buffers[i]);
}
-
- if (m->sysex_buffers) {
- /* free sysex buffers */
- int i;
- for (i = 0; i < m->num_sysex_buffers; i++) {
- if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]);
- }
- pm_free(m->sysex_buffers);
+ 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]);
}
+#endif
}
midi->descriptor = NULL;
pm_free(m); /* delete */
@@ -910,6 +1015,40 @@ static PmError winmm_out_abort(PmInternal *midi) return m->error ? pmHostError : 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) {
+ /* 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));
+ } else {
+ m->error = midiStreamOut(m->handle.stream, m->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)
{
@@ -980,6 +1119,7 @@ static PmError winmm_write_short(PmInternal *midi, PmEvent *event) midiwinmm_type m = (midiwinmm_type) midi->descriptor;
PmError rslt = pmNoError;
assert(m);
+
if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
m->error = midiOutShortMsg(m->handle.out, event->message);
if (m->error) rslt = pmHostError;
@@ -1000,73 +1140,65 @@ static PmError winmm_write_short(PmInternal *midi, PmEvent *event) m->hdr = get_free_output_buffer(midi);
}
full = add_to_buffer(m, m->hdr, delta, event->message);
- if (full) {
- m->error = midiStreamOut(m->handle.stream, m->hdr,
- sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
- m->hdr = NULL;
- }
+ if (full) rslt = winmm_write_flush(midi, when);
}
return rslt;
}
-
+#define winmm_begin_sysex winmm_write_flush
+#ifndef winmm_begin_sysex
static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
PmError rslt = pmNoError;
+
if (midi->latency == 0) {
/* do nothing -- it's handled in winmm_write_byte */
} else {
- midiwinmm_type m = (midiwinmm_type) midi->descriptor;
- /* sysex expects an empty buffer */
- if (m->hdr) {
- m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
- }
- m->hdr = NULL;
+ /* sysex expects an empty sysex buffer, so send whatever is here */
+ rslt = winmm_write_flush(midi);
}
return rslt;
}
-
+#endif
static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
{
+ /* could check for callback_error here, but I haven't checked
+ * what happens if we exit early and don't finish the sysex msg
+ * and clean up
+ */
midiwinmm_type m = (midiwinmm_type) midi->descriptor;
PmError rslt = pmNoError;
- assert(m);
-
+ LPMIDIHDR hdr = m->hdr;
+ if (!hdr) return rslt; /* something bad happened earlier,
+ do not report an error because it would have been
+ reported (at least) once already */
+ /* a(n old) version of MIDI YOKE requires a zero byte after
+ * the sysex message, but do not increment dwBytesRecorded: */
+ hdr->lpData[hdr->dwBytesRecorded] = 0;
if (midi->latency == 0) {
- /* Not using the stream interface. The entire sysex message is
- in m->hdr, and we send it using midiOutLongMsg.
- */
- m->hdr->dwBytesRecorded = m->sysex_byte_count;
- /*
- { int i; int len = m->hdr->dwBytesRecorded;
+#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
+ /* DEBUG CODE: */
+ { int i; int len = m->hdr->dwBufferLength;
printf("OutLongMsg %d ", len);
for (i = 0; i < len; i++) {
- printf("%2x ", msg_buffer[i]);
+ printf("%2x ", (unsigned char) (m->hdr->lpData[i]));
}
}
- */
-
- m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
- } else if (m->hdr) {
+#endif
+ } else {
/* Using stream interface. There are accumulated bytes in m->hdr
to send using midiStreamOut
*/
/* add bytes recorded to MIDIEVENT length, but don't
count the MIDIEVENT data (3 longs) */
- MIDIEVENT *evt = (MIDIEVENT *) m->hdr->lpData;
- evt->dwEvent += m->hdr->dwBytesRecorded - 3 * sizeof(long);
+ MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);
+ evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
/* round up BytesRecorded to multiple of 4 */
- m->hdr->dwBytesRecorded = (m->hdr->dwBytesRecorded + 3) & ~3;
-
- m->error = midiStreamOut(m->handle.stream, m->hdr,
- sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
+ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
}
- m->hdr = NULL; /* make sure we don't send it again */
+ rslt = winmm_write_flush(midi, timestamp);
return rslt;
}
@@ -1077,40 +1209,21 @@ 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;
+ unsigned char *msg_buffer;
assert(m);
- if (midi->latency == 0) {
- /* Not using stream interface. Accumulate the entire message into
- m->hdr */
- 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_sysex_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));
-
- /* append byte to message */
- msg_buffer[m->sysex_byte_count++] = byte;
-
- /* check for overflow */
- if (m->sysex_byte_count >= m->hdr->dwBufferLength) {
- rslt = resize_sysex_buffer(midi, m->sysex_byte_count, m->sysex_byte_count * 2);
-
- if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */
- rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */
-
- }
-
- } else { /* latency is not zero, use stream interface: accumulate
- sysex data in m->hdr and send whenever the buffer fills */
- int full;
- unsigned char *ptr;
-
- /* if m->hdr does not exist, allocate it */
- if (m->hdr == NULL) {
+ if (!hdr) {
+ m->hdr = hdr = get_free_output_buffer(midi);
+ assert(hdr);
+ midi->fill_base = m->hdr->lpData;
+ midi->fill_offset_ptr = &(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
+ * of space for zero after message to make early version of
+ * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
+ midi->fill_length = hdr->dwBufferLength - 2;
+ if (midi->latency != 0) {
unsigned long when = (unsigned long) timestamp;
unsigned long delta;
unsigned long *ptr;
@@ -1122,52 +1235,47 @@ static PmError winmm_write_byte(PmInternal *midi, unsigned char byte, delta = when - m->last_time;
m->last_time = when;
- m->hdr = get_free_sysex_buffer(midi);
- assert(m->hdr);
- ptr = (unsigned long *) m->hdr->lpData;
+ ptr = (unsigned long *) hdr->lpData;
*ptr++ = delta;
*ptr++ = 0;
*ptr = MEVT_F_LONG;
- m->hdr->dwBytesRecorded = 3 * sizeof(long);
+ hdr->dwBytesRecorded = 3 * sizeof(long);
+ /* data will be added at an offset of dwBytesRecorded ... */
}
+ }
+ /* add the data byte */
+ msg_buffer = (unsigned char *) (hdr->lpData);
+ msg_buffer[hdr->dwBytesRecorded++] = byte;
+
+ /* see if buffer is full, leave one byte extra for pad */
+ if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
+ /* write what we've got and continue */
+ rslt = winmm_end_sysex(midi, timestamp);
+ }
+ 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.
- /* add the data byte */
- ptr = (unsigned char *) (m->hdr->lpData + m->hdr->dwBytesRecorded);
- *ptr = byte;
- full = ++m->hdr->dwBytesRecorded >= m->hdr->dwBufferLength;
+ rslt = resize_sysex_buffer(midi, m->sysex_byte_count,
+ m->sysex_byte_count * 2);
- /* see if we need to resize */
- if (full) {
- int bytesRecorded = m->hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */
+ 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);
- m->hdr->dwBytesRecorded = bytesRecorded;
+ hdr->dwBytesRecorded = bytesRecorded;
if (rslt == pmBufferMaxSize) /* if buffer can't be resized */
- rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */
- }
- }
- return rslt;
-}
-
+#endif
-static PmError winmm_write_flush(PmInternal *midi)
-{
- PmError rslt = pmNoError;
- midiwinmm_type m = (midiwinmm_type) midi->descriptor;
- assert(m);
- if (midi->latency == 0) {
- /* all messages are sent immediately */
- } else if ((m->hdr) && (!midi->sysex_in_progress)) {
- /* sysex messages are sent upon completion, but ordinary messages
- may be sitting in a buffer
- */
- m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR));
- m->hdr = NULL;
- if (m->error) rslt = pmHostError;
- }
- return rslt;
-}
static PmTimestamp winmm_synchronize(PmInternal *midi)
{
@@ -1195,223 +1303,32 @@ static PmTimestamp winmm_synchronize(PmInternal *midi) return real_time;
}
-
-#ifdef GARBAGE
-static PmError winmm_write(PmInternal *midi,
- PmEvent *buffer,
- long length)
-{
- midiwinmm_type m = (midiwinmm_type) midi->descriptor;
- unsigned long now;
- int i;
- long msg;
- PmError rslt = pmNoError;
-
- m->error = MMSYSERR_NOERROR;
- if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
- for (i = 0; (i < length) && (rslt == pmNoError); i++) {
- int b = 0; /* count sysex bytes as they are handled */
- msg = buffer[i].message;
- if ((msg & 0xFF) == MIDI_SYSEX) {
- /* start a sysex message */
- m->sysex_mode = TRUE;
- unsigned char midi_byte = (unsigned char) msg;
- rslt = winmm_write_sysex_byte(midi, midi_byte);
- b = 8;
- } else if ((msg & 0x80) && ((msg & 0xFF) != MIDI_EOX)) {
- /* a non-sysex message */
- m->error = midiOutShortMsg(m->handle.out, msg);
- if (m->error) rslt = pmHostError;
- /* any non-real-time message will terminate sysex message */
- if (!is_real_time(msg)) m->sysex_mode = FALSE;
- }
- /* transmit sysex bytes until we find EOX */
- if (m->sysex_mode) {
- while (b < 32 /*bits*/ && (rslt == pmNoError)) {
- unsigned char midi_byte = (unsigned char) (msg >> b);
- rslt = winmm_write_sysex_byte(midi, midi_byte);
- if (midi_byte == MIDI_EOX) {
- b = 24; /* end of message */
- m->sysex_mode = FALSE;
- }
- b += 8;
- }
- }
- }
- } else { /* use midiStream interface -- pass data through buffers */
- LPMIDIHDR hdr = NULL;
- now = (*midi->time_proc)(midi->time_info);
- if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
- /* time to resync */
- now = synchronize(midi, m);
- m->first_message = FALSE;
- }
- for (i = 0; i < length && rslt == pmNoError; i++) {
- unsigned long when = buffer[i].timestamp;
- unsigned long delta;
- if (when == 0) when = now;
- /* when is in real_time; translate to intended stream time */
- when = when + m->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;
- /* before we insert any data, we must have a buffer */
- if (hdr == NULL) {
- /* stream interface: buffers allocated when stream is opened */
- hdr = get_free_output_buffer(midi);
- assert(hdr);
- if (m->sysex_mode) {
- /* we are in the middle of a sysex message */
- start_sysex_buffer(hdr, delta);
- }
- }
- msg = buffer[i].message;
- if ((msg & 0xFF) == MIDI_SYSEX) {
- /* sysex expects an empty buffer */
- if (hdr->dwBytesRecorded != 0) {
- m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
- hdr = get_free_output_buffer(midi);
- assert(hdr);
- }
- /* when we see a MIDI_SYSEX, we always enter sysex mode and call
- start_sysex_buffer() */
- start_sysex_buffer(hdr, delta);
- m->sysex_mode = TRUE;
- }
- /* allow a non-real-time status byte to terminate sysex message */
- if (m->sysex_mode && (msg & 0x80) && (msg & 0xFF) != MIDI_SYSEX &&
- !is_real_time(msg)) {
- /* I'm not sure what WinMM does if you send an incomplete sysex
- message, but the best way out of this mess seems to be to
- recreate the code used when you encounter an EOX, so ...
- */
- MIDIEVENT *evt = (MIDIEVENT) hdr->lpData;
- evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
- /* round up BytesRecorded to multiple of 4 */
- hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
- m->error = midiStreamOut(m->handle.stream, hdr,
- sizeof(MIDIHDR));
- if (m->error) {
- rslt = pmHostError;
- }
- hdr = NULL; /* make sure we don't send it again */
- m->sysex_mode = FALSE; /* skip to normal message send code */
- }
- if (m->sysex_mode) {
- int b = 0; /* count bytes as they are handled */
- while (b < 32 /* bits per word */ && (rslt == pmNoError)) {
- int full;
- unsigned char midi_byte = (unsigned char) (msg >> b);
- if (!hdr) {
- hdr = get_free_output_buffer(midi);
- assert(hdr);
- /* get ready to put sysex bytes in buffer */
- start_sysex_buffer(hdr, delta);
- }
- full = add_byte_to_buffer(m, hdr, midi_byte);
- if (midi_byte == MIDI_EOX) {
- b = 24; /* pretend this is last byte to exit loop */
- m->sysex_mode = FALSE;
- }
- /* see if it's time to send buffer, note that by always
- sending complete sysex message right away, we can use
- this code to set up the MIDIEVENT properly
- */
- if (full || midi_byte == MIDI_EOX) {
- /* add bytes recorded to MIDIEVENT length, but don't
- count the MIDIEVENT data (3 longs) */
- MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData;
- evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
- /* round up BytesRecorded to multiple of 4 */
- hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
- m->error = midiStreamOut(m->handle.stream, hdr,
- sizeof(MIDIHDR));
- if (m->error) {
- rslt = pmHostError;
- }
- hdr = NULL; /* make sure we don't send it again */
- }
- b += 8; /* shift to next byte */
- }
- /* test rslt here in case it was set when we terminated a sysex early
- (see above) */
- } else if (rslt == pmNoError) {
- int full = add_to_buffer(m, hdr, delta, msg);
- if (full) {
- m->error = midiStreamOut(m->handle.stream, hdr,
- sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
- hdr = NULL;
- }
- }
- }
- if (hdr && rslt == pmNoError) {
- if (m->sysex_mode) {
- MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData;
- evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
- /* round up BytesRecorded to multiple of 4 */
- hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
- }
- m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR));
- if (m->error) rslt = pmHostError;
- }
- }
- return rslt;
-}
-#endif
-
-
+#ifdef USE_SYSEX_BUFFERS
/* winmm_out_callback -- recycle sysex buffers */
static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
- DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
+ DWORD dwInstance, DWORD dwParam1,
+ DWORD dwParam2)
{
- int i;
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 */
- static int entry = 0;
- if (++entry > 1) {
- assert(FALSE);
- }
- if (m->callback_error || wMsg != MOM_DONE) {
- entry--;
- return ;
- }
+
/* 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
*/
- m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr,
- sizeof(MIDIHDR));
+ 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 */
- /* any errors could be reported via callback_error, but this is always
- treated as a Midi error, so we'd have to write a lot more code to
- detect that a non-Midi error occurred and do the right thing to find
- the corresponding error message text. Therefore, just use assert()
- */
-
- /* determine if this is an output buffer or a sysex buffer */
-
- for (i = 0 ;i < m->num_buffers;i++) {
- if (hdr == m->buffers[i]) {
- err = SetEvent(m->buffer_signal);
- break;
- }
- }
- for (i = 0 ;i < m->num_sysex_buffers;i++) {
- if (hdr == m->sysex_buffers[i]) {
- err = SetEvent(m->sysex_buffer_signal);
- break;
- }
- }
+ err = SetEvent(m->buffer_signal);
assert(err); /* false -> error */
- entry--;
}
-
+#endif
/* winmm_streamout_callback -- unprepare (free) buffer header */
static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
@@ -1421,24 +1338,19 @@ static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg, midiwinmm_type m = (midiwinmm_type) midi->descriptor;
LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
int err;
- static int entry = 0;
- if (++entry > 1) {
- /* We've reentered this routine. I assume this never happens, but
- check to make sure. Apparently, it is possible that this callback
- can be called reentrantly because it happened once while debugging.
- It looks like this routine is actually reentrant so we can remove
- the assertion if necessary. */
- assert(FALSE);
- }
- if (m->callback_error || wMsg != MOM_DONE) {
- entry--;
- return ;
+
+ /* Even if an error is pending, I think we should unprepare msgs and
+ signal their arrival
+ */
+ /* 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);
}
- m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr,
- sizeof(MIDIHDR));
+ /* signal client in case it is blocked waiting for buffer */
err = SetEvent(m->buffer_signal);
assert(err); /* false -> error */
- entry--;
}
@@ -1460,7 +1372,7 @@ pm_fns_node pm_winmm_in_dictionary = { winmm_in_open,
winmm_in_abort,
winmm_in_close,
- success_poll,
+ winmm_in_poll,
winmm_has_host_error,
winmm_get_host_error
};
@@ -1529,7 +1441,7 @@ void pm_winmm_term( void ) trying to debug client app */
if (winmm_has_host_error(midi)) {
winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN);
- printf(msg);
+ printf("%s\n", msg);
}
#endif
/* close all open ports */
@@ -1537,6 +1449,14 @@ void pm_winmm_term( void ) }
}
}
+ if (midi_in_caps) {
+ pm_free(midi_in_caps);
+ midi_in_caps = NULL;
+ }
+ if (midi_out_caps) {
+ pm_free(midi_out_caps);
+ midi_out_caps = NULL;
+ }
#ifdef DEBUG
if (doneAny) {
printf("warning: devices were left open. They have been closed.\n");
diff --git a/pd/portmidi/porttime/porttime.h b/pd/portmidi/porttime/porttime.h index 762a71af..029ea777 100644 --- a/pd/portmidi/porttime/porttime.h +++ b/pd/portmidi/porttime/porttime.h @@ -27,9 +27,9 @@ typedef void (PtCallback)( PtTimestamp timestamp, void *userData ); PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
-PtError Pt_Stop( void);
-int Pt_Started( void);
-PtTimestamp Pt_Time( void);
+PtError Pt_Stop();
+int Pt_Started();
+PtTimestamp Pt_Time();
#ifdef __cplusplus
}
diff --git a/pd/portmidi/porttime/ptlinux.c b/pd/portmidi/porttime/ptlinux.c index c99abf04..468575aa 100644 --- a/pd/portmidi/porttime/ptlinux.c +++ b/pd/portmidi/porttime/ptlinux.c @@ -21,7 +21,13 @@ CHANGE LOG 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 <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
#include "porttime.h"
#include "sys/time.h"
#include "sys/resource.h"
@@ -63,8 +69,8 @@ static void *Pt_CallbackProc(void *p) select(0, NULL, NULL, NULL, &timeout);
(*(parameters->callback))(Pt_Time(), parameters->userData);
}
- printf("Pt_CallbackProc exiting\n");
-// free(parameters);
+ /* printf("Pt_CallbackProc exiting\n"); */
+ // free(parameters);
return NULL;
}
@@ -93,8 +99,9 @@ PtError Pt_Start(int resolution, PtCallback *callback, void *userData) PtError Pt_Stop()
{
- printf("Pt_Stop called\n");
+ /* printf("Pt_Stop called\n"); */
pt_callback_proc_id++;
+ pthread_join(pt_thread_pid, NULL);
time_started_flag = FALSE;
return ptNoError;
}
diff --git a/pd/portmidi/porttime/ptmacosx_cf.c b/pd/portmidi/porttime/ptmacosx_cf.c index 1837246b..c0c22a32 100644 --- a/pd/portmidi/porttime/ptmacosx_cf.c +++ b/pd/portmidi/porttime/ptmacosx_cf.c @@ -91,6 +91,8 @@ 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();
@@ -111,6 +113,7 @@ PtError Pt_Start(int resolution, PtCallback *callback, void *userData) PtError Pt_Stop()
{
+ printf("Pt_Stop called\n");
CFRunLoopStop(timerRunLoop);
time_started_flag = FALSE;
diff --git a/pd/portmidi/porttime/ptmacosx_mach.c b/pd/portmidi/porttime/ptmacosx_mach.c index 935d99bb..37d4318b 100644 --- a/pd/portmidi/porttime/ptmacosx_mach.c +++ b/pd/portmidi/porttime/ptmacosx_mach.c @@ -57,18 +57,19 @@ static void *Pt_CallbackProc(void *p) /* 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);
+ /* 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);
- (*(parameters->callback))(Pt_Time(), parameters->userData);
+ timestamp = Pt_Time();
+ (*(parameters->callback))(timestamp, parameters->userData);
}
- printf("Pt_CallbackProc exiting\n");
free(parameters);
return NULL;
}
@@ -100,7 +101,7 @@ PtError Pt_Start(int resolution, PtCallback *callback, void *userData) PtError Pt_Stop()
{
- printf("Pt_Stop called\n");
+ /* printf("Pt_Stop called\n"); */
pt_callback_proc_id++;
time_started_flag = FALSE;
return ptNoError;
|