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] |