|
From: <pst...@us...> - 2008-05-26 20:50:21
|
Revision: 565
http://jazzplusplus.svn.sourceforge.net/jazzplusplus/?rev=565&view=rev
Author: pstieber
Date: 2008-05-26 13:50:13 -0700 (Mon, 26 May 2008)
Log Message:
-----------
Updated to match the current portmidi repository (revision 78)
Revision Links:
--------------
http://jazzplusplus.svn.sourceforge.net/jazzplusplus/?rev=78&view=rev
Modified Paths:
--------------
trunk/jazz/portmidi/README.txt
trunk/jazz/portmidi/pm_common/pminternal.h
trunk/jazz/portmidi/pm_common/pmutil.c
trunk/jazz/portmidi/pm_common/pmutil.h
trunk/jazz/portmidi/pm_common/portmidi.c
trunk/jazz/portmidi/pm_common/portmidi.h
trunk/jazz/portmidi/pm_linux/README_LINUX.txt
trunk/jazz/portmidi/pm_linux/pmlinux.c
trunk/jazz/portmidi/pm_linux/pmlinux.h
trunk/jazz/portmidi/pm_linux/pmlinuxalsa.c
trunk/jazz/portmidi/pm_linux/pmlinuxalsa.h
trunk/jazz/portmidi/pm_mac/pmmacosxcm.c
trunk/jazz/portmidi/pm_test/latency.c
trunk/jazz/portmidi/pm_test/midithread.c
trunk/jazz/portmidi/pm_test/sysex.c
trunk/jazz/portmidi/pm_test/test.c
trunk/jazz/portmidi/pm_win/README_WIN.txt
trunk/jazz/portmidi/pm_win/pmwin.c
trunk/jazz/portmidi/pm_win/pmwinmm.c
trunk/jazz/portmidi/porttime/porttime.h
trunk/jazz/portmidi/porttime/ptlinux.c
trunk/jazz/portmidi/porttime/ptmacosx_cf.c
trunk/jazz/portmidi/porttime/ptmacosx_mach.c
trunk/jazz/portmidi/porttime/ptwinmm.c
Added Paths:
-----------
trunk/jazz/portmidi/pm_mac/Makefile.osx
trunk/jazz/portmidi/pm_mac/README_MAC.txt
trunk/jazz/portmidi/pm_mac/pm_mac.xcodeproj/
trunk/jazz/portmidi/pm_mac/pm_mac.xcodeproj/project.pbxproj
trunk/jazz/portmidi/pm_test/latency.vcproj
trunk/jazz/portmidi/pm_test/midithread.vcproj
trunk/jazz/portmidi/pm_test/midithru.c
trunk/jazz/portmidi/pm_test/midithru.vcproj
trunk/jazz/portmidi/pm_test/mm.c
trunk/jazz/portmidi/pm_test/mm.vcproj
trunk/jazz/portmidi/pm_test/qtest.c
trunk/jazz/portmidi/pm_test/qtest.vcproj
trunk/jazz/portmidi/pm_test/sysex.vcproj
trunk/jazz/portmidi/pm_test/test.vcproj
trunk/jazz/portmidi/pm_test/txdata.syx
trunk/jazz/portmidi/pm_win/copy-dll.bat
trunk/jazz/portmidi/pm_win/pm_dll.vcproj
trunk/jazz/portmidi/porttime/porttime.vcproj
Removed Paths:
-------------
trunk/jazz/portmidi/Makefile
trunk/jazz/portmidi/pm_mac/.DS_Store
trunk/jazz/portmidi/pm_mac/pm_mac.pbproj/
trunk/jazz/portmidi/pm_test/latency.dsp
trunk/jazz/portmidi/pm_test/midithread.dsp
trunk/jazz/portmidi/pm_test/sysex.dsp
trunk/jazz/portmidi/pm_test/test.dsp
trunk/jazz/portmidi/pm_win/Debug/
trunk/jazz/portmidi/portmidi.dsp
trunk/jazz/portmidi/portmidi.dsw
Deleted: trunk/jazz/portmidi/Makefile
===================================================================
--- trunk/jazz/portmidi/Makefile 2008-05-24 21:59:59 UTC (rev 564)
+++ trunk/jazz/portmidi/Makefile 2008-05-26 20:50:13 UTC (rev 565)
@@ -1,61 +0,0 @@
-# MAKEFILE FOR PORTMIDI AND PORTTIME
-
-# Use this for linux alsa (0.9x) version
-versions = pm_linux/pmlinuxalsa.o
-ALSALIB = -lasound
-VFLAGS = -DPMALSA
-
-# Use this for null (a dummy implementation for no Midi I/O:
-# versions = pmlinuxnull.o
-# ALSALIB =
-# VFLAGS = -DPMNULL
-
-pmlib = pm_linux/libportmidi.a
-
-ptlib = porttime/libporttime.a
-
-CC = gcc $(VFLAGS) -g -Ipm_common -Iporttime
-
-pmobjects = pm_common/pmutil.o $(versions) pm_linux/pmlinux.o \
- pm_common/portmidi.o pm_linux/pmlinuxalsa.o
-
-ptobjects = porttime/porttime.o porttime/ptlinux.o
-
-current: all
-
-all: $(pmlib) $(ptlib) pm_test/test pm_test/sysex pm_test/midithread \
- pm_test/latency
-
-$(pmlib): Makefile $(pmobjects)
- ar -cr $(pmlib) $(pmobjects)
-
-$(ptlib): Makefile $(ptobjects)
- ar -cr $(ptlib) $(ptobjects)
-
-pm_linux/pmlinuxalsa.o: Makefile pm_linux/pmlinuxalsa.c pm_linux/pmlinuxalsa.h
- $(CC) -c pm_linux/pmlinuxalsa.c -o pm_linux/pmlinuxalsa.o
-
-pm_test/test: Makefile pm_test/test.o $(pmlib) $(ptlib)
- $(CC) pm_test/test.c -o pm_test/test $(pmlib) $(ptlib) $(ALSALIB)
-
-pm_test/sysex: Makefile pm_test/sysex.o $(pmlib) $(ptlib)
- $(CC) pm_test/sysex.c -o pm_test/sysex $(pmlib) $(ptlib) $(ALSALIB)
-
-pm_test/midithread: Makefile pm_test/midithread.o $(pmlib) $(ptlib)
- $(CC) pm_test/midithread.c -o pm_test/midithread $(pmlib) $(ptlib) $(ALSALIB)
-
-pm_test/latency: Makefile $(ptlib) $(pmlib) pm_test/latency.o
- $(CC) pm_test/latency.c -o pm_test/latency $(ptlib) $(pmlib) $(ALSALIB) -lpthread -lm
-
-porttime/ptlinux.o: Makefile porttime/ptlinux.c
- $(CC) -c porttime/ptlinux.c -o porttime/ptlinux.o
-
-clean:
- rm -f *.o *~ core* */*.o */*~ */core*
-
-cleaner: clean
-
-cleanest: cleaner
- rm -f $(pmlib) $(ptlib) pm_test/test pm_test/sysex pm_test/midithread
- rm -f pm_test/latency
-
Modified: trunk/jazz/portmidi/README.txt
===================================================================
--- trunk/jazz/portmidi/README.txt 2008-05-24 21:59:59 UTC (rev 564)
+++ trunk/jazz/portmidi/README.txt 2008-05-26 20:50:13 UTC (rev 565)
@@ -1,12 +1,19 @@
README for PortMidi
-Roger Dannenberg
-6 April 2003
-For Windows, please see also README_WIN.txt and debugging_dlls.txt
-in pm_win.
+Roger B. Dannenberg
-For Linux, please see also README_LINUX.txt in pm_linux.
+VERSION: please use "svn info" to get info.
+Documentation for PortMidi is found in pm_common/portmidi.h.
+
+Additional documentation:
+ - Windows: see pm_win/README_WIN.txt and pm_win/debugging_dlls.txt
+ - Linux: see pm_linux/README_LINUX.txt
+ - Mac OSX: see pm_mac/README_MAC.txt
+ - Common Lisp: see pm_cl/README_CL.txt
+
+---------- some notes on the design of PortMidi ----------
+
POINTERS VS DEVICE NUMBERS
When you open a MIDI port, PortMidi allocates a structure to
@@ -21,26 +28,53 @@
Error handling turned out to be much more complicated than expected.
PortMidi functions return error codes that the caller can check.
-In addition, errors may occur asynchronously due to MIDI input. In
-this case, the error code is transferred to the next call to
-Pm_Read. Furthermore, an error can arise during a MIDI THRU
-operation that is also invoked asynchronously when MIDI input
-arrives.
+In addition, errors may occur asynchronously due to MIDI input.
+However, for Windows, there are virtually no errors that can
+occur if the code is correct and not passing bogus values. One
+exception is an error that the system is out of memory, but my
+guess is that one is unlikely to recover gracefully from that.
+Therefore, all errors in callbacks are guarded by assert(), which
+means not guarded at all in release configurations.
Ordinarily, the caller checks for an error code. If the error is
system-dependent, pmHostError is returned and the caller can
call Pm_GetHostErrorText to get a text description of the error.
-Host errors are recorded in the system-specific data allocated for
-each open MIDI port. However, if an error occurs on open or close,
+Host error codes are system-specific and are recorded in the
+system-specific data allocated for each open MIDI port.
+However, if an error occurs on open or close,
we cannot store the error with the device because there will be
no device data (assuming PortMidi cleans up after devices that
-are not open). For open and close, we will store the host error
-in a global variable. The PortMidi is smart enough to look here
-first when the user asks for ErrorText.
+are not open). For open and close, we will convert the error
+to text, copy it to a global string, and set pm_hosterror, a
+global flag.
-Another problem is that when an error occurs in a MIDI THRU
-operation, the caller must go to the output port to retrieve
-the error, even though the initial indication of error will be
-on the input port.
+Similarly, whenever a Read or Write operation returns pmHostError,
+the corresponding error string is copied to a global string
+and pm_hosterror is set. This makes getting error strings
+simple and uniform, although it does cost a string copy and some
+overhead even if the user does not want to look at the error data.
+The system-specific Read, Write, Poll, etc. implementations should
+check for asynchronous errors and return immediately if one is
+found so that these get reported. This happens in the Mac OS X
+code, where lots of things are happening in callbacks, but again,
+in Windows, there are no error codes recorded in callbacks.
+
+DEBUGGING
+
+If you are building a console application for research, we suggest
+compiling with the option PM_CHECK_ERRORS. This will insert a
+check for error return values at the end of each PortMidi
+function. If an error is encountered, a text message is printed
+using printf(), the user is asked to type ENTER, and then exit(-1)
+is called to clean up and terminate the program.
+
+You should not use PM_CHECK_ERRORS if printf() does not work
+(e.g. this is not a console application under Windows, or there
+is no visible console on some other OS), and you should not use
+PM_CHECK_ERRORS if you intend to recover from errors rather than
+abruptly terminate the program.
+
+The Windows version (and perhaps others) also offers a DEBUG
+compile-time option. See README_WIN.txt.
Modified: trunk/jazz/portmidi/pm_common/pminternal.h
===================================================================
--- trunk/jazz/portmidi/pm_common/pminternal.h 2008-05-24 21:59:59 UTC (rev 564)
+++ trunk/jazz/portmidi/pm_common/pminternal.h 2008-05-26 20:50:13 UTC (rev 565)
@@ -3,7 +3,7 @@
/* this file is included by files that implement library internals */
/* Here is a guide to implementers:
provide an initialization function similar to pm_winmm_init()
- add your initizliation function to pm_init()
+ add your initialization function to pm_init()
Note that your init function should never require not-standard
libraries or fail in any way. If the interface is not available,
simply do not call pm_add_device. This means that non-standard
@@ -21,12 +21,6 @@
extern "C" {
#endif
-#ifdef NDEBUG
-Please do not disable assert -- PortMidi depends upon actions inside assert()
-calls. If you really want to turn off assertion checking, you must change
-the code inside assert() macros to be side-effect free.
-#endif
-
/* these are defined in system-specific file */
void *pm_alloc(size_t s);
void pm_free(void *ptr);
@@ -38,8 +32,19 @@
struct pm_internal_struct;
/* these do not use PmInternal because it is not defined yet... */
-typedef PmError (*pm_write_fn)(struct pm_internal_struct *midi,
- PmEvent *buffer, long length);
+typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi,
+ PmEvent *buffer);
+typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
+ unsigned char byte, PmTimestamp timestamp);
+typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
+ PmEvent *buffer);
+typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
/* pm_open_fn should clean up all memory and close the device if any part
of the open fails */
typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
@@ -54,7 +59,13 @@
typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi);
typedef struct {
- pm_write_fn write; /* output MIDI */
+ pm_write_short_fn write_short; /* output short MIDI msg */
+ pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
+ pm_end_sysex_fn end_sysex; /* marks end of sysex message */
+ pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
+ pm_write_realtime_fn write_realtime; /* send real-time message within sysex */
+ pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
+ pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */
pm_open_fn open; /* open MIDI device */
pm_abort_fn abort; /* abort */
pm_close_fn close; /* close device */
@@ -78,9 +89,9 @@
pm_fns_type dictionary;
} descriptor_node, *descriptor_type;
-#define pm_descriptor_max 32
-extern descriptor_node descriptors[pm_descriptor_max];
-extern int descriptor_index;
+extern int pm_descriptor_max;
+extern descriptor_type descriptors;
+extern int pm_descriptor_index;
typedef unsigned long (*time_get_proc_type)(void *time_info);
@@ -90,60 +101,73 @@
PmTimeProcPtr time_proc; /* where to get the time */
void *time_info; /* pass this to get_time() */
-
- long buffer_len; /* how big is the buffer */
- PmEvent *buffer; /* storage for:
- - midi input
- - midi output w/latency != 0 */
- long head;
- long tail;
-
+ long buffer_len; /* how big is the buffer or queue? */
+ PmQueue *queue;
+
long latency; /* time delay in ms between timestamps and actual output */
/* set to zero to get immediate, simple blocking output */
- /* if latency is zero, timestamps will be ignored;
+ /* if 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 */
-
- struct pm_internal_struct *thru; /* only used on midi input */
- PmError callback_thru_error; /* stores error code from the Pm_Write
- that implements midi thru */
+ 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);
/* defined by portMidi, used by pmwinmm */
-PmError none_write(PmInternal *midi, PmEvent *buffer, long length);
+PmError none_write_short(PmInternal *midi, PmEvent *buffer);
+PmError none_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp);
+PmTimestamp none_synchronize(PmInternal *midi);
+
PmError pm_fail_fn(PmInternal *midi);
+PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);
PmError pm_success_fn(PmInternal *midi);
PmError pm_add_device(char *interf, char *name, int input, void *descriptor,
pm_fns_type dictionary);
-extern int descriptor_index;
-void pm_enqueue(PmInternal *midi, PmEvent *event);
+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_timestamp_fn
+#define none_sysex pm_fail_timestamp_fn
#define none_poll pm_fail_fn
-
#define success_poll pm_success_fn
+#define MIDI_REALTIME_MASK 0xf8
+#define is_real_time(msg) \
+ ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
+
#ifdef __cplusplus
}
#endif
Modified: trunk/jazz/portmidi/pm_common/pmutil.c
===================================================================
--- trunk/jazz/portmidi/pm_common/pmutil.c 2008-05-24 21:59:59 UTC (rev 564)
+++ trunk/jazz/portmidi/pm_common/pmutil.c 2008-05-26 20:50:13 UTC (rev 565)
@@ -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));
+ int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) &
+ ~(sizeof(int32) - 1)) / sizeof(int32);
+ /* arg checking */
+ if (!queue)
+ return NULL;
- /* arg checking */
- if (!queue)
- return NULL;
-
- queue->len = num_msgs * bytes_per_msg;
- queue->buffer = pm_alloc(queue->len);
+ /* 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 @@
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 @@
{
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,44 +180,132 @@
}
-/* 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);
+ 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;
- if (tail == queue->head) {
- queue->overflow = TRUE;
- /* do not update tail, so message is lost */
- return pmBufferOverflow;
- }
queue->tail = tail;
return pmNoError;
}
+
+int Pm_QueueEmpty(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ if (!queue) return TRUE;
+ return (queue->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)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ PmError rslt;
+ long temp;
+
+ /* arg checking */
+ if (!queue)
+ return NULL;
+
+ if (queue->peek_flag) {
+ return queue->peek;
+ }
+ /* this is ugly: if peek_overflow is set, then Pm_Dequeue()
+ * returns immediately with pmBufferOverflow, but here, we
+ * want Pm_Dequeue() to really check for data. If data is
+ * there, we can return it
+ */
+ temp = queue->peek_overflow;
+ queue->peek_overflow = FALSE;
+ rslt = Pm_Dequeue(q, queue->peek);
+ queue->peek_overflow = temp;
+
+ if (rslt == 1) {
+ queue->peek_flag = TRUE;
+ return queue->peek;
+ } else if (rslt == pmBufferOverflow) {
+ /* when overflow is indicated, the queue is empty and the
+ * first message that was dropped by Enqueue (signalling
+ * pmBufferOverflow to its caller) would have been the next
+ * message in the queue. Pm_QueuePeek will return NULL, but
+ * remember that an overflow occurred. (see Pm_Dequeue)
+ */
+ queue->peek_overflow = TRUE;
+ }
+ return NULL;
+}
+
Modified: trunk/jazz/portmidi/pm_common/pmutil.h
===================================================================
--- trunk/jazz/portmidi/pm_common/pmutil.h 2008-05-24 21:59:59 UTC (rev 564)
+++ trunk/jazz/portmidi/pm_common/pmutil.h 2008-05-26 20:50:13 UTC (rev 565)
@@ -2,6 +2,10 @@
applications that use PortMidi
*/
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
typedef void PmQueue;
/*
@@ -10,6 +14,33 @@
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 @@
/*
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,53 @@
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);
-#define Pm_QueueEmpty(m) (m->head == m->tail)
+int Pm_QueueEmpty(PmQueue *queue);
+
+
+/*
+ Pm_QueuePeek() returns a pointer to the item at the head of the queue,
+ or NULL if the queue is empty. The item is not removed from the queue.
+ Pm_QueuePeek() will not indicate when an overflow occurs. If you want
+ to get and check pmBufferOverflow messages, use the return value of
+ Pm_QueuePeek() *only* as an indication that you should call
+ Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would
+ return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally
+ clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume
+ enqueuing messages. A subsequent call to Pm_QueuePeek()
+ will return a pointer to the first message *after* the overflow.
+ Using this as an indication to call Pm_Dequeue(), the first call
+ to Pm_Dequeue() will return pmBufferOverflow. The second call will
+ return success, copying the same message pointed to by the previous
+ Pm_QueuePeek().
+
+ When to use Pm_QueuePeek(): (1) when you need to look at the message
+ 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 */
Modified: trunk/jazz/portmidi/pm_common/portmidi.c
===================================================================
--- trunk/jazz/portmidi/pm_common/portmidi.c 2008-05-24 21:59:59 UTC (rev 564)
+++ trunk/jazz/portmidi/pm_common/portmidi.c 2008-05-26 20:50:13 UTC (rev 565)
@@ -1,25 +1,85 @@
#include "stdlib.h"
#include "string.h"
#include "portmidi.h"
+#include "porttime.h"
+#include "pmutil.h"
#include "pminternal.h"
+#include <assert.h>
#define MIDI_CLOCK 0xf8
#define MIDI_ACTIVE 0xfe
+#define MIDI_STATUS_MASK 0x80
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+#define MIDI_START 0xFA
+#define MIDI_STOP 0xFC
+#define MIDI_CONTINUE 0xFB
+#define MIDI_F9 0xF9
+#define MIDI_FD 0xFD
+#define MIDI_RESET 0xFF
+#define MIDI_NOTE_ON 0x90
+#define MIDI_NOTE_OFF 0x80
+#define MIDI_CHANNEL_AT 0xD0
+#define MIDI_POLY_AT 0xA0
+#define MIDI_PROGRAM 0xC0
+#define MIDI_CONTROL 0xB0
+#define MIDI_PITCHBEND 0xE0
+#define MIDI_MTC 0xF1
+#define MIDI_SONGPOS 0xF2
+#define MIDI_SONGSEL 0xF3
+#define MIDI_TUNE 0xF6
#define is_empty(midi) ((midi)->tail == (midi)->head)
static int pm_initialized = FALSE;
-int pm_hosterror = FALSE;
+int pm_hosterror;
char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
+#ifdef PM_CHECK_ERRORS
+
+#include <stdio.h>
+
+#define STRING_MAX 80
+
+static void prompt_and_exit(void)
+{
+ char line[STRING_MAX];
+ printf("type ENTER...");
+ fgets(line, STRING_MAX, stdin);
+ /* this will clean up open ports: */
+ exit(-1);
+}
+
+
+static PmError pm_errmsg(PmError err)
+{
+ if (err == pmHostError) {
+ /* it seems pointless to allocate memory and copy the string,
+ * so I will do the work of Pm_GetHostErrorText directly
+ */
+ printf("PortMidi found host error...\n %s\n", pm_hosterror_text);
+ pm_hosterror = FALSE;
+ pm_hosterror_text[0] = 0; /* clear the message */
+ prompt_and_exit();
+ } else if (err < 0) {
+ printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err));
+ prompt_and_exit();
+ }
+ return err;
+}
+#else
+#define pm_errmsg(err) err
+#endif
+
/*
====================================================================
system implementation of portmidi interface
====================================================================
*/
-int descriptor_index = 0;
-descriptor_node descriptors[pm_descriptor_max];
+int pm_descriptor_max = 0;
+int pm_descriptor_index = 0;
+descriptor_type descriptors = NULL;
/* pm_add_device -- describe interface/device pair to library
*
@@ -32,26 +92,36 @@
*/
PmError pm_add_device(char *interf, char *name, int input,
void *descriptor, pm_fns_type dictionary) {
- if (descriptor_index >= pm_descriptor_max) {
- return pmInvalidDeviceId;
+ if (pm_descriptor_index >= pm_descriptor_max) {
+ // expand descriptors
+ descriptor_type new_descriptors =
+ pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
+ if (!new_descriptors) return pmInsufficientMemory;
+ if (descriptors) {
+ memcpy(new_descriptors, descriptors,
+ sizeof(descriptor_node) * pm_descriptor_max);
+ free(descriptors);
+ }
+ pm_descriptor_max += 32;
+ descriptors = new_descriptors;
}
- descriptors[descriptor_index].pub.interf = interf;
- descriptors[descriptor_index].pub.name = name;
- descriptors[descriptor_index].pub.input = input;
- descriptors[descriptor_index].pub.output = !input;
+ descriptors[pm_descriptor_index].pub.interf = interf;
+ descriptors[pm_descriptor_index].pub.name = name;
+ descriptors[pm_descriptor_index].pub.input = input;
+ descriptors[pm_descriptor_index].pub.output = !input;
/* default state: nothing to close (for automatic device closing) */
- descriptors[descriptor_index].pub.opened = FALSE;
+ descriptors[pm_descriptor_index].pub.opened = FALSE;
/* ID number passed to win32 multimedia API open */
- descriptors[descriptor_index].descriptor = descriptor;
+ descriptors[pm_descriptor_index].descriptor = descriptor;
/* points to PmInternal, allows automatic device closing */
- descriptors[descriptor_index].internalDescriptor = NULL;
+ descriptors[pm_descriptor_index].internalDescriptor = NULL;
- descriptors[descriptor_index].dictionary = dictionary;
+ descriptors[pm_descriptor_index].dictionary = dictionary;
- descriptor_index++;
+ pm_descriptor_index++;
return pmNoError;
}
@@ -64,18 +134,15 @@
*/
int Pm_CountDevices( void ) {
- PmError err = Pm_Initialize();
- if (err)
- return err;
- return descriptor_index;
+ 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;
- if (id >= 0 && id < descriptor_index) {
+ Pm_Initialize(); /* no error check needed */
+ if (id >= 0 && id < pm_descriptor_index) {
return &descriptors[id].pub;
}
return NULL;
@@ -87,10 +154,20 @@
}
/* none_write -- returns an error if called */
-PmError none_write(PmInternal *midi, PmEvent *buffer, long length) {
+PmError none_write_short(PmInternal *midi, PmEvent *buffer) {
return pmBadPtr;
}
+/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */
+PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) {
+ return pmBadPtr;
+}
+
+PmError none_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp) {
+ return pmBadPtr;
+}
+
/* pm_fail_fn -- generic function, returns error if called */
PmError pm_fail_fn(PmInternal *midi) {
return pmBadPtr;
@@ -100,17 +177,26 @@
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;
}
+PmTimestamp none_synchronize(PmInternal *midi) {
+ return 0;
+}
#define none_abort pm_fail_fn
#define none_close pm_fail_fn
pm_fns_node pm_none_dictionary = {
- none_write,
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ none_synchronize,
none_open,
none_abort,
none_close,
@@ -149,6 +235,12 @@
case pmBufferOverflow:
msg = "PortMidi: `Buffer overflow'";
break;
+ case pmBadData:
+ msg = "PortMidi: `Invalid MIDI message Data'";
+ break;
+ case pmBufferMaxSize:
+ msg = "PortMidi: `Buffer cannot be made larger'";
+ break;
default:
msg = "PortMidi: `Illegal error number'";
break;
@@ -157,41 +249,44 @@
}
-void Pm_GetHostErrorText(PortMidiStream * stream, char * msg,
- unsigned int len) {
- PmInternal * midi = (PmInternal *) stream;
-
- if (pm_hosterror) { /* we have the string already from open or close */
+/* This can be called whenever you get a pmHostError return value.
+ * The error will always be in the global pm_hosterror_text.
+ */
+void Pm_GetHostErrorText(char * msg, unsigned int len) {
+ assert(msg);
+ assert(len > 0);
+ if (pm_hosterror) {
strncpy(msg, (char *) pm_hosterror_text, len);
pm_hosterror = FALSE;
pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
- might help with debugging */
- } else if (midi == NULL) {
- /* make this routine bullet-proof so we can always count on
- it running */
- strncpy(msg,"Can't print host error for bogus stream argument", len);
+ might help with debugging */
+ msg[len - 1] = 0; /* make sure string is terminated */
} else {
- (*midi->dictionary->host_error)(midi, msg, len);
+ msg[0] = 0; /* no string to return */
}
- msg[len - 1] = 0; /* make sure string is terminated */
}
int Pm_HasHostError(PortMidiStream * stream) {
- if (pm_hosterror) {
- return TRUE;
- } else if (stream) {
+ if (pm_hosterror) return TRUE;
+ if (stream) {
PmInternal * midi = (PmInternal *) stream;
- return (*midi->dictionary->has_host_error)(midi);
- } else {
- return FALSE;
+ pm_hosterror = (*midi->dictionary->has_host_error)(midi);
+ if (pm_hosterror) {
+ midi->dictionary->host_error(midi, pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ /* now error message is global */
+ return TRUE;
+ }
}
+ return FALSE;
}
PmError Pm_Initialize( void ) {
- pm_hosterror_text[0] = 0; /* the null string */
if (!pm_initialized) {
+ pm_hosterror = FALSE;
+ pm_hosterror_text[0] = 0; /* the null string */
pm_init();
pm_initialized = TRUE;
}
@@ -202,6 +297,13 @@
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;
@@ -210,91 +312,231 @@
/* 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;
- long head;
-
+ PmError err = pmNoError;
+ pm_hosterror = FALSE;
/* arg checking */
if(midi == NULL)
- return pmBadPtr;
- if(Pm_HasHostError(midi))
- return pmHostError;
- if(!descriptors[midi->device_id].pub.opened)
- return pmBadPtr;
- if(!descriptors[midi->device_id].pub.input)
- return pmBadPtr;
-
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.input)
+ err = pmBadPtr;
/* First poll for data in the buffer...
* This either simply checks for data, or attempts first to fill the buffer
- * with data from the MIDI hardware; this depends on the implementation. */
- if (Pm_Poll(midi) == 0)
- return 0;
+ * with data from the MIDI hardware; this depends on the implementation.
+ * We could call Pm_Poll here, but that would redo a lot of redundant
+ * parameter checking, so I copied some code from Pm_Poll to here: */
+ else err = (*(midi->dictionary->poll))(midi);
- head = midi->head;
- while (head != midi->tail && n < length) {
- *buffer++ = midi->buffer[head++];
- if (head == midi->buffer_len) head = 0;
+ if (err != pmNoError) {
+ if (err == pmHostError) {
+ midi->dictionary->host_error(midi, pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ pm_hosterror = TRUE;
+ }
+ return pm_errmsg(err);
+ }
+
+ while (n < length) {
+ PmError err = Pm_Dequeue(midi->queue, buffer++);
+ if (err == pmBufferOverflow) {
+ /* ignore the data we have retreived so far */
+ return pm_errmsg(pmBufferOverflow);
+ } else if (err == 0) { /* empty queue */
+ break;
+ }
n++;
}
- midi->head = head;
- if (midi->overflow) {
- midi->head = midi->tail;
- midi->overflow = FALSE;
- return pmBufferOverflow;
- }
return n;
}
PmError Pm_Poll( PortMidiStream *stream )
{
PmInternal *midi = (PmInternal *) stream;
- PmError result;
+ PmError err;
+ PmEvent *event;
+ pm_hosterror = FALSE;
/* arg checking */
if(midi == NULL)
- return pmBadPtr;
- if(Pm_HasHostError(midi))
- return pmHostError;
- if(!descriptors[midi->device_id].pub.opened)
- return pmBadPtr;
- if(!descriptors[midi->device_id].pub.input)
- return pmBadPtr;
-
- result = (*(midi->dictionary->poll))(midi);
- if (result != pmNoError)
- return result;
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.input)
+ err = pmBadPtr;
else
- return midi->head != midi->tail;
+ 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);
+ }
+
+ event = (PmEvent *) Pm_QueuePeek(midi->queue);
+ return event != NULL;
}
+/* this is called from Pm_Write and Pm_WriteSysEx to issue a
+ * call to the system-dependent end_sysex function and handle
+ * the error return
+ */
+static PmError pm_end_sysex(PmInternal *midi)
+{
+ PmError err = (*midi->dictionary->end_sysex)(midi, 0);
+ midi->sysex_in_progress = FALSE;
+ if (err == pmHostError) {
+ midi->dictionary->host_error(midi, pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ pm_hosterror = TRUE;
+ }
+ return err;
+}
+
+
+/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
+ Pm_WriteSysEx all operate a state machine that "outputs" calls to
+ write_short, begin_sysex, write_byte, end_sysex, and write_realtime */
+
PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length)
{
PmInternal *midi = (PmInternal *) stream;
+ PmError err;
+ int i;
+ int bits;
+ pm_hosterror = FALSE;
/* arg checking */
if(midi == NULL)
- return pmBadPtr;
- if(Pm_HasHostError(midi))
- return pmHostError;
- if(!descriptors[midi->device_id].pub.opened)
- return pmBadPtr;
- if(!descriptors[midi->device_id].pub.output)
- return pmBadPtr;
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.output)
+ err = pmBadPtr;
+ else
+ err = pmNoError;
- return (*midi->dictionary->write)(midi, buffer, length);
+ if (err != pmNoError) goto pm_write_error;
+
+ if (midi->latency == 0) {
+ midi->now = 0;
+ } else {
+ midi->now = (*(midi->time_proc))(midi->time_info);
+ if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
+ /* time to resync */
+ midi->now = (*midi->dictionary->synchronize)(midi);
+ midi->first_message = FALSE;
+ }
+ }
+ /* error recovery: when a sysex is detected, we call
+ * dictionary->begin_sysex() followed by calls to
+ * dictionary->write_byte() and dictionary->write_realtime()
+ * until an end-of-sysex is detected, when we call
+ * dictionary->end_sysex(). After an error occurs,
+ * Pm_Write() continues to call functions. For example,
+ * it will continue to call write_byte() even after
+ * an error sending a sysex message, and end_sysex() will be
+ * called when an EOX or non-real-time status is found.
+ * When errors are detected, Pm_Write() returns immediately,
+ * so it is possible that this will drop data and leave
+ * sysex messages in a partially transmitted state.
+ */
+ for (i = 0; i < length; i++) {
+ unsigned long msg = buffer[i].message;
+ bits = 0;
+ /* is this a sysex message? */
+ if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
+ if (midi->sysex_in_progress) {
+ /* error: previous sysex was not terminated by EOX */
+ midi->sysex_in_progress = FALSE;
+ err = pmBadData;
+ goto pm_write_error;
+ }
+ midi->sysex_in_progress = TRUE;
+ if ((err = (*midi->dictionary->begin_sysex)(midi,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ bits = 8;
+ /* fall through to continue sysex processing */
+ } else if ((msg & MIDI_STATUS_MASK) &&
+ (Pm_MessageStatus(msg) != MIDI_EOX)) {
+ /* a non-sysex message */
+ if (midi->sysex_in_progress) {
+ /* this should be a realtime message */
+ if (is_real_time(msg)) {
+ if ((err = (*midi->dictionary->write_realtime)(midi,
+ &(buffer[i]))) != pmNoError)
+ goto pm_write_error;
+ } else {
+ midi->sysex_in_progress = FALSE;
+ err = pmBadData;
+ /* ignore any error from this, because we already have one */
+ /* pass 0 as timestamp -- it's ignored */
+ (*midi->dictionary->end_sysex)(midi, 0);
+ goto pm_write_error;
+ }
+ } else { /* regular short midi message */
+ if ((err = (*midi->dictionary->write_short)(midi,
+ &(buffer[i]))) != pmNoError)
+ goto pm_write_error;
+ continue;
+ }
+ }
+ if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
+ /* see if we can accelerate data transfer */
+ if (bits == 0 && midi->fill_base && /* 4 bytes to copy */
+ (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&
+ (msg & 0x80808080) == 0) { /* all data */
+ /* copy 4 bytes from msg to fill_base + fill_offset */
+ unsigned char *ptr = midi->fill_base +
+ *(midi->fill_offset_ptr);
+ ptr[0] = msg; ptr[1] = msg >> 8;
+ ptr[2] = msg >> 18; ptr[3] = msg >> 24;
+ (*midi->fill_offset_ptr) += 4;
+ continue;
+ }
+ /* no acceleration, so do byte-by-byte copying */
+ while (bits < 32) {
+ unsigned char midi_byte = (unsigned char) (msg >> bits);
+ if ((err = (*midi->dictionary->write_byte)(midi, midi_byte,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ if (midi_byte == MIDI_EOX) {
+ err = pm_end_sysex(midi);
+ if (err != pmNoError) goto error_exit;
+ break; /* from while loop */
+ }
+ bits += 8;
+ }
+ } else {
+ /* not in sysex mode, but message did not start with status */
+ err = pmBadData;
+ goto pm_write_error;
+ }
+ }
+ /* after all messages are processed, send the data */
+ if (!midi->sysex_in_progress)
+ err = (*midi->dictionary->write_flush)(midi, 0);
+pm_write_error:
+ if (err == pmHostError) {
+ midi->dictionary->host_error(midi, pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ pm_hosterror = TRUE;
+ }
+error_exit:
+ return pm_errmsg(err);
}
@@ -308,13 +550,15 @@
}
-PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, unsigned char *msg)
+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))
-#define MIDI_EOX 0xf7
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;
@@ -326,28 +570,58 @@
/* 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;
}
@@ -359,61 +633,61 @@
void *inputDriverInfo,
long bufferSize,
PmTimeProcPtr time_proc,
- void *time_info,
- PmStream *thru) {
- PmInternal *midi, *midiThru;
- PmError err;
+ void *time_info) {
+ PmInternal *midi;
+ PmError err = pmNoError;
pm_hosterror = FALSE;
*stream = NULL;
/* arg checking */
- if (inputDevice < 0 || inputDevice >= descriptor_index)
- return pmInvalidDeviceId;
- if (!descriptors[inputDevice].pub.input)
- return pmBadPtr;
- if(descriptors[inputDevice].pub.opened)
- return pmBadPtr;
-
+ if (inputDevice < 0 || inputDevice >= pm_descriptor_index)
+ err = pmInvalidDeviceId;
+ else if (!descriptors[inputDevice].pub.input)
+ err = pmBadPtr;
+ else if(descriptors[inputDevice].pub.opened)
+ err = pmBadPtr;
+
+ if (err != pmNoError)
+ goto error_return;
+
/* create portMidi internal data */
midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
*stream = midi;
- if (!midi)
- return pmInsufficientMemory;
+ if (!midi) {
+ err = pmInsufficientMemory;
+ goto error_return;
+ }
midi->device_id = inputDevice;
midi->write_flag = FALSE;
midi->time_proc = time_proc;
midi->time_info = time_info;
+ /* windows adds timestamps in the driver and these are more accurate than
+ using a time_proc, so do not automatically provide a time proc. Non-win
+ implementations may want to provide a default time_proc in their
+ system-specific midi_out_open() method.
+ */
if (bufferSize <= 0) bufferSize = 256; /* default buffer size */
- else bufferSize++; /* buffer holds N-1 msgs, so increase request by 1 */
- midi->buffer_len = bufferSize; /* portMidi input storage */
- midi->buffer = (PmEvent *) pm_alloc(sizeof(PmEvent) * midi->buffer_len);
- if (!midi->buffer) {
+ midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent));
+ if (!midi->queue) {
/* free portMidi data */
*stream = NULL;
pm_free(midi);
- return pmInsufficientMemory;
+ 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->filters = PM_FILT_ACTIVE;
- midiThru = (PmInternal *) thru;
- /* verify midi thru is acceptable device */
- if(midiThru &&
- (!descriptors[midiThru->device_id].pub.opened ||
- !descriptors[midiThru->device_id].pub.output)) {
- /* failed, release portMidi internal data */
- *stream = NULL;
- pm_free(midi->buffer);
- pm_free(midi);
- return pmBadPtr;
- }
- midi->thru = midiThru;
- midi->callback_thru_error = FALSE;
- midi->dictionary = descriptors[inputDevice].dictionary;
+ midi->channel_mask = 0xFFFF;
+ midi->sync_time = 0;
+ mid...
[truncated message content] |