aboutsummaryrefslogtreecommitdiff
path: root/pd/portmidi
diff options
context:
space:
mode:
authorHans-Christoph Steiner <eighthave@users.sourceforge.net>2005-12-15 00:57:02 +0000
committerHans-Christoph Steiner <eighthave@users.sourceforge.net>2005-12-15 00:57:02 +0000
commit90d5b8b4a064420d74678654e94ea4755b377f21 (patch)
tree5d49b9f6ae17b75c98ad1a8302a0d84f1d99f75b /pd/portmidi
parent59ad9e9cf0a72b31f8bfd371cb97536ed4d4fe61 (diff)
checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip
svn path=/trunk/; revision=4216
Diffstat (limited to 'pd/portmidi')
-rw-r--r--pd/portmidi/pm_common/pminternal.h173
-rw-r--r--pd/portmidi/pm_common/pmutil.c132
-rw-r--r--pd/portmidi/pm_common/pmutil.h56
-rw-r--r--pd/portmidi/pm_common/portmidi.c980
-rw-r--r--pd/portmidi/pm_common/portmidi.h692
-rw-r--r--pd/portmidi/pm_linux/README_LINUX.txt32
-rw-r--r--pd/portmidi/pm_linux/pmlinux.c51
-rw-r--r--pd/portmidi/pm_linux/pmlinux.h5
-rw-r--r--pd/portmidi/pm_linux/pmlinuxalsa.c724
-rw-r--r--pd/portmidi/pm_linux/pmlinuxalsa.h6
-rw-r--r--pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj1324
-rw-r--r--pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser1508
-rw-r--r--pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj337
-rw-r--r--pd/portmidi/pm_mac/pmmac.c42
-rw-r--r--pd/portmidi/pm_mac/pmmac.h4
-rw-r--r--pd/portmidi/pm_mac/pmmacosxcm.c701
-rw-r--r--pd/portmidi/pm_mac/pmmacosxcm.h4
-rw-r--r--pd/portmidi/pm_test/latency.c278
-rw-r--r--pd/portmidi/pm_test/latency.dsp102
-rw-r--r--pd/portmidi/pm_test/midithread.c327
-rw-r--r--pd/portmidi/pm_test/midithread.dsp102
-rw-r--r--pd/portmidi/pm_test/midithru.c364
-rw-r--r--pd/portmidi/pm_test/midithru.dsp102
-rw-r--r--pd/portmidi/pm_test/midithru.dsw29
-rw-r--r--pd/portmidi/pm_test/sysex.c319
-rw-r--r--pd/portmidi/pm_test/sysex.dsp102
-rw-r--r--pd/portmidi/pm_test/test.c469
-rw-r--r--pd/portmidi/pm_test/test.dsp102
-rw-r--r--pd/portmidi/pm_test/txdata.syx257
-rw-r--r--pd/portmidi/pm_win/README_WIN.txt183
-rw-r--r--pd/portmidi/pm_win/copy-dll.bat13
-rw-r--r--pd/portmidi/pm_win/debugging_dlls.txt145
-rw-r--r--pd/portmidi/pm_win/pm_dll.dsp107
-rw-r--r--pd/portmidi/pm_win/pmdll.c49
-rw-r--r--pd/portmidi/pm_win/pmdll.h5
-rw-r--r--pd/portmidi/pm_win/pmwin.c113
-rw-r--r--pd/portmidi/pm_win/pmwinmm.c1547
-rw-r--r--pd/portmidi/pm_win/pmwinmm.h5
-rw-r--r--pd/portmidi/porttime/porttime.c3
-rw-r--r--pd/portmidi/porttime/porttime.dsp104
-rw-r--r--pd/portmidi/porttime/porttime.h36
-rw-r--r--pd/portmidi/porttime/ptlinux.c120
-rw-r--r--pd/portmidi/porttime/ptmacosx_cf.c135
-rw-r--r--pd/portmidi/porttime/ptmacosx_mach.c123
-rw-r--r--pd/portmidi/porttime/ptwinmm.c65
45 files changed, 12077 insertions, 0 deletions
diff --git a/pd/portmidi/pm_common/pminternal.h b/pd/portmidi/pm_common/pminternal.h
new file mode 100644
index 00000000..20677308
--- /dev/null
+++ b/pd/portmidi/pm_common/pminternal.h
@@ -0,0 +1,173 @@
+/* pminternal.h -- header for interface implementations */
+
+/* this file is included by files that implement library internals */
+/* Here is a guide to implementers:
+ provide an initialization function similar to pm_winmm_init()
+ add your initialization function to pm_init()
+ Note that your init function should never require not-standard
+ libraries or fail in any way. If the interface is not available,
+ simply do not call pm_add_device. This means that non-standard
+ libraries should try to do dynamic linking at runtime using a DLL
+ and return without error if the DLL cannot be found or if there
+ is any other failure.
+ implement functions as indicated in pm_fns_type to open, read, write,
+ close, etc.
+ call pm_add_device() for each input and output device, passing it a
+ pm_fns_type structure.
+ assumptions about pm_fns_type functions are given below.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* these are defined in system-specific file */
+void *pm_alloc(size_t s);
+void pm_free(void *ptr);
+
+/* if an error occurs while opening or closing a midi stream, set these: */
+extern int pm_hosterror;
+extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
+
+struct pm_internal_struct;
+
+/* these do not use PmInternal because it is not defined yet... */
+typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi,
+ PmEvent *buffer);
+typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
+ PmTimestamp timestamp);
+typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
+ unsigned char byte, PmTimestamp timestamp);
+typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
+ PmEvent *buffer);
+typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi);
+typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
+/* pm_open_fn should clean up all memory and close the device if any part
+ of the open fails */
+typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
+ void *driverInfo);
+typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);
+/* pm_close_fn should clean up all memory and close the device if any
+ part of the close fails. */
+typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);
+typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);
+typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg,
+ unsigned int len);
+typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi);
+
+typedef struct {
+ pm_write_short_fn write_short; /* output short MIDI msg */
+ pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
+ pm_end_sysex_fn end_sysex; /* marks end of sysex message */
+ pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
+ pm_write_realtime_fn write_realtime; /* send real-time message within sysex */
+ pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
+ pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */
+ pm_open_fn open; /* open MIDI device */
+ pm_abort_fn abort; /* abort */
+ pm_close_fn close; /* close device */
+ pm_poll_fn poll; /* read pending midi events into portmidi buffer */
+ pm_has_host_error_fn has_host_error; /* true when device has had host
+ error message */
+ pm_host_error_fn host_error; /* provide text readable host error message
+ for device (clears and resets) */
+} pm_fns_node, *pm_fns_type;
+
+
+/* when open fails, the dictionary gets this set of functions: */
+extern pm_fns_node pm_none_dictionary;
+
+typedef struct {
+ PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic
+ device closing (see PmDeviceInfo struct) */
+ void *descriptor; /* ID number passed to win32 multimedia API open */
+ void *internalDescriptor; /* points to PmInternal device, allows automatic
+ device closing */
+ pm_fns_type dictionary;
+} descriptor_node, *descriptor_type;
+
+extern int pm_descriptor_max;
+extern descriptor_type descriptors;
+extern int pm_descriptor_index;
+
+typedef unsigned long (*time_get_proc_type)(void *time_info);
+
+typedef struct pm_internal_struct {
+ int device_id; /* which device is open (index to descriptors) */
+ short write_flag; /* MIDI_IN, or MIDI_OUT */
+
+ PmTimeProcPtr time_proc; /* where to get the time */
+ void *time_info; /* pass this to get_time() */
+
+ long buffer_len; /* how big is the buffer */
+ PmEvent *buffer; /* storage for:
+ - midi input
+ - midi output w/latency != 0 */
+ long head;
+ long tail;
+
+ long latency; /* time delay in ms between timestamps and actual output */
+ /* set to zero to get immediate, simple blocking output */
+ /* if latency is zero, timestamps will be ignored; */
+ /* if midi input device, this field ignored */
+
+ int overflow; /* set to non-zero if input is dropped */
+ int flush; /* flag to drop incoming sysex data because of overflow */
+ int sysex_in_progress; /* use for overflow management */
+ PmMessage sysex_message; /* buffer for 4 bytes of sysex data */
+ int sysex_message_count; /* how many bytes in sysex_message so far */
+
+ long filters; /* flags that filter incoming message classes */
+ int channel_mask; /* filter incoming messages based on channel */
+ PmTimestamp last_msg_time; /* timestamp of last message */
+ PmTimestamp sync_time; /* time of last synchronization */
+ PmTimestamp now; /* set by PmWrite to current time */
+ int first_message; /* initially true, used to run first synchronization */
+ pm_fns_type dictionary; /* implementation functions */
+ void *descriptor; /* system-dependent state */
+
+} PmInternal;
+
+typedef struct {
+ long head;
+ long tail;
+ long len;
+ long msg_size;
+ long overflow;
+ char *buffer;
+} PmQueueRep;
+
+/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
+void pm_init(void);
+void pm_term(void);
+
+/* defined by portMidi, used by pmwinmm */
+PmError none_write_short(PmInternal *midi, PmEvent *buffer);
+PmError none_sysex(PmInternal *midi, PmTimestamp timestamp);
+PmError none_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp);
+PmTimestamp none_synchronize(PmInternal *midi);
+
+PmError pm_fail_fn(PmInternal *midi);
+PmError pm_success_fn(PmInternal *midi);
+PmError pm_add_device(char *interf, char *name, int input, void *descriptor,
+ pm_fns_type dictionary);
+void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp);
+void pm_begin_sysex(PmInternal *midi);
+void pm_end_sysex(PmInternal *midi);
+void pm_read_short(PmInternal *midi, PmEvent *event);
+
+#define none_write_flush pm_fail_fn
+#define none_poll pm_fail_fn
+#define success_poll pm_success_fn
+
+#define MIDI_REALTIME_MASK 0xf8
+#define is_real_time(msg) \
+ ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
+
+#ifdef __cplusplus
+}
+#endif
+
diff --git a/pd/portmidi/pm_common/pmutil.c b/pd/portmidi/pm_common/pmutil.c
new file mode 100644
index 00000000..1178b80b
--- /dev/null
+++ b/pd/portmidi/pm_common/pmutil.c
@@ -0,0 +1,132 @@
+/* pmutil.c -- some helpful utilities for building midi
+ applications that use PortMidi
+ */
+#include "stdlib.h"
+#include "memory.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+
+
+PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg)
+{
+ PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
+
+ /* arg checking */
+ if (!queue)
+ return NULL;
+
+ queue->len = num_msgs * bytes_per_msg;
+ queue->buffer = pm_alloc(queue->len);
+ if (!queue->buffer) {
+ pm_free(queue);
+ return NULL;
+ }
+ queue->head = 0;
+ queue->tail = 0;
+ queue->msg_size = bytes_per_msg;
+ queue->overflow = FALSE;
+ return queue;
+}
+
+
+PmError Pm_QueueDestroy(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+
+ /* arg checking */
+ if (!queue || !queue->buffer)
+ return pmBadPtr;
+
+ pm_free(queue->buffer);
+ pm_free(queue);
+ return pmNoError;
+}
+
+
+PmError Pm_Dequeue(PmQueue *q, void *msg)
+{
+ long head;
+ PmQueueRep *queue = (PmQueueRep *) q;
+
+ /* arg checking */
+ if(!queue)
+ return pmBadPtr;
+
+ if (queue->overflow) {
+ queue->overflow = FALSE;
+ return pmBufferOverflow;
+ }
+
+ head = queue->head; /* make sure this is written after access */
+ if (head == queue->tail) return 0;
+ memcpy(msg, queue->buffer + head, queue->msg_size);
+ head += queue->msg_size;
+ if (head == queue->len) head = 0;
+ queue->head = head;
+ return 1; /* success */
+}
+
+
+/* source should not enqueue data if overflow is set */
+/**/
+PmError Pm_Enqueue(PmQueue *q, void *msg)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ long tail;
+
+ /* arg checking */
+ if (!queue)
+ return pmBadPtr;
+
+ tail = queue->tail;
+ memcpy(queue->buffer + tail, msg, queue->msg_size);
+ tail += queue->msg_size;
+ if (tail == queue->len) tail = 0;
+ if (tail == queue->head) {
+ queue->overflow = TRUE;
+ /* do not update tail, so message is lost */
+ return pmBufferOverflow;
+ }
+ queue->tail = tail;
+ return pmNoError;
+}
+
+int Pm_QueueEmpty(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ if (!queue) return TRUE;
+ return (queue->head == queue->tail);
+}
+
+int Pm_QueueFull(PmQueue *q)
+{
+ PmQueueRep *queue = (PmQueueRep *) q;
+ long tail;
+
+ /* arg checking */
+ if(!queue)
+ return pmBadPtr;
+
+ tail = queue->tail;
+ tail += queue->msg_size;
+ if (tail == queue->len) {
+ tail = 0;
+ }
+ return (tail == queue->head);
+}
+
+void *Pm_QueuePeek(PmQueue *q)
+{
+ long head;
+ PmQueueRep *queue = (PmQueueRep *) q;
+
+ /* arg checking */
+ if(!queue)
+ return NULL;
+
+ head = queue->head; /* make sure this is written after access */
+ if (head == queue->tail) return NULL;
+ return queue->buffer + head;
+}
+
diff --git a/pd/portmidi/pm_common/pmutil.h b/pd/portmidi/pm_common/pmutil.h
new file mode 100644
index 00000000..a7fb3ebf
--- /dev/null
+++ b/pd/portmidi/pm_common/pmutil.h
@@ -0,0 +1,56 @@
+/* pmutil.h -- some helpful utilities for building midi
+ applications that use PortMidi
+ */
+
+typedef void PmQueue;
+
+/*
+ A single-reader, single-writer queue is created by
+ Pm_QueueCreate(), which takes the number of messages and
+ the message size as parameters. The queue only accepts
+ fixed sized messages. Returns NULL if memory cannot be allocated.
+
+ Pm_QueueDestroy() destroys the queue and frees its storage.
+ */
+
+PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg);
+PmError Pm_QueueDestroy(PmQueue *queue);
+
+/*
+ Pm_Dequeue() removes one item from the queue, copying it into msg.
+ Returns 1 if successful, and 0 if the queue is empty.
+ Returns pmBufferOverflow, clears the overflow flag, and does not
+ return a data item if the overflow flag is set. (This protocol
+ ensures that the reader will be notified when data is lost due
+ to overflow.)
+ */
+PmError Pm_Dequeue(PmQueue *queue, void *msg);
+
+
+/*
+ Pm_Enqueue() inserts one item into the queue, copying it from msg.
+ Returns pmNoError if successful and pmBufferOverflow if the queue was
+ already full. If pmBufferOverflow is returned, the overflow flag is set.
+ */
+PmError Pm_Enqueue(PmQueue *queue, void *msg);
+
+
+/*
+ Pm_QueueFull() returns non-zero if the queue is full
+ Pm_QueueEmpty() returns non-zero if the queue is empty
+
+ Either condition may change immediately because a parallel
+ enqueue or dequeue operation could be in progress.
+ */
+int Pm_QueueFull(PmQueue *queue);
+int Pm_QueueEmpty(PmQueue *queue);
+
+
+/*
+ Pm_QueuePeek() returns a pointer to the item at the head of the queue,
+ or NULL if the queue is empty. The item is not removed from the queue.
+ If queue is in an overflow state, a valid pointer is returned and the
+ queue remains in the overflow state.
+ */
+void *Pm_QueuePeek(PmQueue *queue);
+
diff --git a/pd/portmidi/pm_common/portmidi.c b/pd/portmidi/pm_common/portmidi.c
new file mode 100644
index 00000000..e1b962d7
--- /dev/null
+++ b/pd/portmidi/pm_common/portmidi.c
@@ -0,0 +1,980 @@
+#include "stdlib.h"
+#include "string.h"
+#include "portmidi.h"
+#include "porttime.h"
+#include "pminternal.h"
+#include <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;
+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 pm_descriptor_max = 0;
+int pm_descriptor_index = 0;
+descriptor_type descriptors = NULL;
+
+/* pm_add_device -- describe interface/device pair to library
+ *
+ * This is called at intialization time, once for each
+ * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1)
+ * The strings are retained but NOT COPIED, so do not destroy them!
+ *
+ * returns pmInvalidDeviceId if device memory is exceeded
+ * otherwise returns pmNoError
+ */
+PmError pm_add_device(char *interf, char *name, int input,
+ void *descriptor, pm_fns_type dictionary) {
+ if (pm_descriptor_index >= pm_descriptor_max) {
+ // expand descriptors
+ descriptor_type new_descriptors =
+ pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
+ if (!new_descriptors) return pmInsufficientMemory;
+ if (descriptors) {
+ memcpy(new_descriptors, descriptors,
+ sizeof(descriptor_node) * pm_descriptor_max);
+ free(descriptors);
+ }
+ pm_descriptor_max += 32;
+ descriptors = new_descriptors;
+ }
+ descriptors[pm_descriptor_index].pub.interf = interf;
+ descriptors[pm_descriptor_index].pub.name = name;
+ descriptors[pm_descriptor_index].pub.input = input;
+ descriptors[pm_descriptor_index].pub.output = !input;
+
+ /* default state: nothing to close (for automatic device closing) */
+ descriptors[pm_descriptor_index].pub.opened = FALSE;
+
+ /* ID number passed to win32 multimedia API open */
+ descriptors[pm_descriptor_index].descriptor = descriptor;
+
+ /* points to PmInternal, allows automatic device closing */
+ descriptors[pm_descriptor_index].internalDescriptor = NULL;
+
+ descriptors[pm_descriptor_index].dictionary = dictionary;
+
+ pm_descriptor_index++;
+
+ return pmNoError;
+}
+
+
+/*
+====================================================================
+portmidi implementation
+====================================================================
+*/
+
+int Pm_CountDevices( void )
+{
+ PmError err = Pm_Initialize();
+ if (err)
+ return pm_errmsg(err);
+ return pm_descriptor_index;
+}
+
+
+const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id )
+{
+ PmError err = Pm_Initialize();
+ if (err)
+ return NULL;
+ if (id >= 0 && id < pm_descriptor_index) {
+ return &descriptors[id].pub;
+ }
+ return NULL;
+}
+
+/* pm_success_fn -- "noop" function pointer */
+PmError pm_success_fn(PmInternal *midi) {
+ return pmNoError;
+}
+
+/* none_write -- returns an error if called */
+PmError none_write_short(PmInternal *midi, PmEvent *buffer)
+{
+ return pmBadPtr;
+}
+
+/* none_sysex -- placeholder for begin_sysex and end_sysex */
+PmError none_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ return pmBadPtr;
+}
+
+PmError none_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ return pmBadPtr;
+}
+
+/* pm_fail_fn -- generic function, returns error if called */
+PmError pm_fail_fn(PmInternal *midi)
+{
+ return pmBadPtr;
+}
+
+static PmError none_open(PmInternal *midi, void *driverInfo)
+{
+ return pmBadPtr;
+}
+static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) {
+ strcpy(msg,"");
+}
+static unsigned int none_has_host_error(PmInternal * midi) {
+ return FALSE;
+}
+PmTimestamp none_synchronize(PmInternal *midi) {
+ return 0;
+}
+
+#define none_abort pm_fail_fn
+#define none_close pm_fail_fn
+
+pm_fns_node pm_none_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ none_synchronize,
+ none_open,
+ none_abort,
+ none_close,
+ none_poll,
+ none_has_host_error,
+ none_get_host_error
+};
+
+
+const char *Pm_GetErrorText( PmError errnum ) {
+ const char *msg;
+
+ switch(errnum)
+ {
+ case pmNoError:
+ msg = "";
+ break;
+ case pmHostError:
+ msg = "PortMidi: `Host error'";
+ break;
+ case pmInvalidDeviceId:
+ msg = "PortMidi: `Invalid device ID'";
+ break;
+ case pmInsufficientMemory:
+ msg = "PortMidi: `Insufficient memory'";
+ break;
+ case pmBufferTooSmall:
+ msg = "PortMidi: `Buffer too small'";
+ break;
+ case pmBadPtr:
+ msg = "PortMidi: `Bad pointer'";
+ break;
+ case pmInternalError:
+ msg = "PortMidi: `Internal PortMidi Error'";
+ break;
+ case pmBufferOverflow:
+ msg = "PortMidi: `Buffer overflow'";
+ break;
+ case pmBadData:
+ msg = "PortMidi: `Invalid MIDI message Data'";
+ default:
+ msg = "PortMidi: `Illegal error number'";
+ break;
+ }
+ return msg;
+}
+
+
+/* This can be called whenever you get a pmHostError return value.
+ * The error will always be in the global pm_hosterror_text.
+ */
+void Pm_GetHostErrorText(char * msg, unsigned int len) {
+ assert(msg);
+ assert(len > 0);
+ if (pm_hosterror) { /* we have the string already from open or close */
+ strncpy(msg, (char *) pm_hosterror_text, len);
+ pm_hosterror = FALSE;
+ pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
+ might help with debugging */
+ msg[len - 1] = 0; /* make sure string is terminated */
+ } else {
+ msg[0] = 0; /* no string to return */
+ }
+}
+
+
+int Pm_HasHostError(PortMidiStream * stream) {
+ if (stream) {
+ PmInternal * midi = (PmInternal *) stream;
+ pm_hosterror = (*midi->dictionary->has_host_error)(midi);
+ if (pm_hosterror) {
+ midi->dictionary->host_error(midi, pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ /* now error message is global */
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+PmError Pm_Initialize( void ) {
+ pm_hosterror_text[0] = 0; /* the null string */
+ if (!pm_initialized) {
+ pm_init();
+ pm_initialized = TRUE;
+ }
+ return pmNoError;
+}
+
+
+PmError Pm_Terminate( void ) {
+ if (pm_initialized) {
+ pm_term();
+ pm_initialized = FALSE;
+ }
+ return pmNoError;
+}
+
+
+/* Pm_Read -- read up to length longs from source into buffer */
+/*
+ returns number of longs actually read, or error code
+
+ When the reader wants data:
+ if overflow_flag:
+ do not get anything
+ empty the buffer (read_ptr = write_ptr)
+ clear overflow_flag
+ return pmBufferOverflow
+ get data
+ return number of messages
+*/
+PmError Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) {
+ PmInternal *midi = (PmInternal *) stream;
+ int n = 0;
+ long head;
+ PmError err = pmNoError;
+
+ /* arg checking */
+ if(midi == NULL)
+ err = pmBadPtr;
+ else if(Pm_HasHostError(midi))
+ err = pmHostError;
+ else if(!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.input)
+ err = pmBadPtr;
+
+ /* First poll for data in the buffer...
+ * This either simply checks for data, or attempts first to fill the buffer
+ * with data from the MIDI hardware; this depends on the implementation.
+ * We could call Pm_Poll here, but that would redo a lot of redundant
+ * parameter checking, so I copied some code from Pm_Poll to here: */
+ else err = (*(midi->dictionary->poll))(midi);
+
+ if (err != pmNoError) {
+ return pm_errmsg(err);
+ }
+
+ head = midi->head;
+ while (head != midi->tail && n < length) {
+ PmEvent event = midi->buffer[head++];
+ *buffer++ = event;
+ if (head == midi->buffer_len) head = 0;
+ n++;
+ }
+ midi->head = head;
+ if (midi->overflow) {
+ midi->head = midi->tail;
+ midi->overflow = FALSE;
+ return pm_errmsg(pmBufferOverflow);
+ }
+ return n;
+}
+
+PmError Pm_Poll( PortMidiStream *stream )
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err;
+
+ /* arg checking */
+ if(midi == NULL)
+ err = pmBadPtr;
+ else if(Pm_HasHostError(midi))
+ err = pmHostError;
+ else if(!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.input)
+ err = pmBadPtr;
+ else
+ err = (*(midi->dictionary->poll))(midi);
+
+ if (err != pmNoError)
+ return pm_errmsg(err);
+ else
+ return midi->head != midi->tail;
+}
+
+/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
+ Pm_WriteSysEx all operate a state machine that "outputs" calls to
+ write_short, begin_sysex, write_byte, end_sysex, and write_realtime */
+
+PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err;
+ int i;
+ int bits;
+
+ /* arg checking */
+ if(midi == NULL)
+ err = pmBadPtr;
+ else if(Pm_HasHostError(midi))
+ err = pmHostError;
+ else if(!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else if(!descriptors[midi->device_id].pub.output)
+ err = pmBadPtr;
+ else
+ err = pmNoError;
+
+ if (err != pmNoError) goto pm_write_error;
+
+ if (midi->latency == 0) {
+ midi->now = 0;
+ } else {
+ midi->now = (*(midi->time_proc))(midi->time_info);
+ if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
+ /* time to resync */
+ midi->now = (*midi->dictionary->synchronize)(midi);
+ midi->first_message = FALSE;
+ }
+ }
+
+ for (i = 0; i < length; i++) {
+ unsigned long msg = buffer[i].message;
+ bits = 0;
+ /* is this a sysex message? */
+ if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
+ if (midi->sysex_in_progress) {
+ /* error: previous sysex was not terminated by EOX */
+ midi->sysex_in_progress = FALSE;
+ err = pmBadData;
+ goto pm_write_error;
+ }
+ midi->sysex_in_progress = TRUE;
+ if ((err = (*midi->dictionary->begin_sysex)(midi,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ bits = 8;
+ /* fall through to continue sysex processing */
+ } else if ((msg & MIDI_STATUS_MASK) &&
+ (Pm_MessageStatus(msg) != MIDI_EOX)) {
+ /* a non-sysex message */
+ if (midi->sysex_in_progress) {
+ /* this should be a non-realtime message */
+ if (is_real_time(msg)) {
+ if ((err = (*midi->dictionary->write_realtime)(midi,
+ &(buffer[i]))) != pmNoError)
+ goto pm_write_error;
+ } else {
+ midi->sysex_in_progress = FALSE;
+ err = pmBadData;
+ /* ignore any error from this, because we already have one */
+ /* pass 0 as timestamp -- it's ignored */
+ (*midi->dictionary->end_sysex)(midi, 0);
+ goto pm_write_error;
+ }
+ } else { /* regular short midi message */
+ if ((err = (*midi->dictionary->write_short)(midi,
+ &(buffer[i]))) != pmNoError)
+ goto pm_write_error;
+ continue;
+ }
+ }
+ if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
+ while (bits < 32) {
+ unsigned char midi_byte = (unsigned char) (msg >> bits);
+ if ((err = (*midi->dictionary->write_byte)(midi, midi_byte,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ if (midi_byte == MIDI_EOX) {
+ midi->sysex_in_progress = FALSE;
+ if ((err = (*midi->dictionary->end_sysex)(midi,
+ buffer[i].timestamp)) != pmNoError)
+ goto pm_write_error;
+ break; /* from while loop */
+ }
+ bits += 8;
+ }
+ } else {
+ /* not in sysex mode, but message did not start with status */
+ err = pmBadData;
+ goto pm_write_error;
+ }
+ }
+ /* after all messages are processed, send the data */
+ err = (*midi->dictionary->write_flush)(midi);
+pm_write_error:
+ return pm_errmsg(err);
+}
+
+
+PmError Pm_WriteShort( PortMidiStream *stream, long when, long msg)
+{
+ PmEvent event;
+
+ event.timestamp = when;
+ event.message = msg;
+ return Pm_Write(stream, &event, 1);
+}
+
+
+PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when,
+ unsigned char *msg)
+{
+ /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */
+ /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */
+#define BUFLEN (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))
+ PmEvent buffer[BUFLEN];
+ /* the next byte in the buffer is represented by an index, bufx, and
+ a shift in bits */
+ int shift = 0;
+ int bufx = 0;
+ buffer[0].message = 0;
+ buffer[0].timestamp = when;
+
+ while (1) {
+ /* insert next byte into buffer */
+ buffer[bufx].message |= ((*msg) << shift);
+ shift += 8;
+ if (shift == 32) {
+ shift = 0;
+ bufx++;
+ if (bufx == BUFLEN) {
+ PmError err = Pm_Write(stream, buffer, BUFLEN);
+ if (err) return err;
+ /* prepare to fill another buffer */
+ bufx = 0;
+ }
+ buffer[bufx].message = 0;
+ buffer[bufx].timestamp = when;
+ }
+ /* keep inserting bytes until you find MIDI_EOX */
+ if (*msg++ == MIDI_EOX) break;
+ }
+
+ /* we're finished sending full buffers, but there may
+ * be a partial one left.
+ */
+ if (shift != 0) bufx++; /* add partial message to buffer len */
+ if (bufx) { /* bufx is number of PmEvents to send from buffer */
+ return Pm_Write(stream, buffer, bufx);
+ }
+ return pmNoError;
+}
+
+
+
+PmError Pm_OpenInput(PortMidiStream** stream,
+ PmDeviceID inputDevice,
+ void *inputDriverInfo,
+ long bufferSize,
+ PmTimeProcPtr time_proc,
+ void *time_info)
+{
+ PmInternal *midi;
+ PmError err = pmNoError;
+ pm_hosterror = FALSE;
+ *stream = NULL;
+
+ /* arg checking */
+ if (inputDevice < 0 || inputDevice >= pm_descriptor_index)
+ err = pmInvalidDeviceId;
+ else if (!descriptors[inputDevice].pub.input)
+ err = pmBadPtr;
+ else if(descriptors[inputDevice].pub.opened)
+ err = pmBadPtr;
+
+ if (err != pmNoError)
+ goto error_return;
+
+ /* create portMidi internal data */
+ midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
+ *stream = midi;
+ if (!midi) {
+ err = pmInsufficientMemory;
+ goto error_return;
+ }
+ midi->device_id = inputDevice;
+ midi->write_flag = FALSE;
+ midi->time_proc = time_proc;
+ midi->time_info = time_info;
+ /* windows adds timestamps in the driver and these are more accurate than
+ using a time_proc, so do not automatically provide a time proc. Non-win
+ implementations may want to provide a default time_proc in their
+ system-specific midi_out_open() method.
+ */
+ if (bufferSize <= 0) bufferSize = 256; /* default buffer size */
+ else bufferSize++; /* buffer holds N-1 msgs, so increase request by 1 */
+ midi->buffer_len = bufferSize; /* portMidi input storage */
+ midi->buffer = (PmEvent *) pm_alloc(sizeof(PmEvent) * midi->buffer_len);
+ if (!midi->buffer) {
+ /* free portMidi data */
+ *stream = NULL;
+ pm_free(midi);
+ err = pmInsufficientMemory;
+ goto error_return;
+ }
+ midi->head = 0;
+ midi->tail = 0;
+ midi->latency = 0; /* not used */
+ midi->overflow = FALSE;
+ midi->flush = FALSE;
+ midi->sysex_in_progress = FALSE;
+ midi->sysex_message = 0;
+ midi->sysex_message_count = 0;
+ midi->filters = PM_FILT_ACTIVE;
+ midi->channel_mask = 0xFFFF;
+ midi->sync_time = 0;
+ midi->first_message = TRUE;
+ midi->dictionary = descriptors[inputDevice].dictionary;
+ descriptors[inputDevice].internalDescriptor = midi;
+ /* open system dependent input device */
+ err = (*midi->dictionary->open)(midi, inputDriverInfo);
+ if (err) {
+ *stream = NULL;
+ descriptors[inputDevice].internalDescriptor = NULL;
+ /* free portMidi data */
+ pm_free(midi->buffer);
+ pm_free(midi);
+ } else {
+ /* portMidi input open successful */
+ descriptors[inputDevice].pub.opened = TRUE;
+ }
+error_return:
+ return pm_errmsg(err);
+}
+
+
+PmError Pm_OpenOutput(PortMidiStream** stream,
+ PmDeviceID outputDevice,
+ void *outputDriverInfo,
+ long bufferSize,
+ PmTimeProcPtr time_proc,
+ void *time_info,
+ long latency)
+{
+ PmInternal *midi;
+ PmError err = pmNoError;
+ pm_hosterror = FALSE;
+ *stream = NULL;
+
+ /* arg checking */
+ if (outputDevice < 0 || outputDevice >= pm_descriptor_index)
+ err = pmInvalidDeviceId;
+ else if (!descriptors[outputDevice].pub.output)
+ err = pmBadPtr;
+ else if (descriptors[outputDevice].pub.opened)
+ err = pmBadPtr;
+ if (err != pmNoError)
+ goto error_return;
+
+ /* create portMidi internal data */
+ midi = (PmInternal *) pm_alloc(sizeof(PmInternal));
+ *stream = midi;
+ if (!midi) {
+ err = pmInsufficientMemory;
+ goto error_return;
+ }
+ midi->device_id = outputDevice;
+ midi->write_flag = TRUE;
+ midi->time_proc = time_proc;
+ /* if latency > 0, we need a time reference. If none is provided,
+ use PortTime library */
+ if (time_proc == NULL && latency != 0) {
+ if (!Pt_Started())
+ Pt_Start(1, 0, 0);
+ /* time_get does not take a parameter, so coerce */
+ midi->time_proc = (PmTimeProcPtr) Pt_Time;
+ }
+ midi->time_info = time_info;
+ /* when stream used, this buffer allocated and used by
+ winmm_out_open; deleted by winmm_out_close */
+ midi->buffer_len = bufferSize;
+ midi->buffer = NULL;
+ midi->head = 0; /* unused by output */
+ midi->tail = 0; /* unused by output */
+ /* if latency zero, output immediate (timestamps ignored) */
+ /* if latency < 0, use 0 but don't return an error */
+ if (latency < 0) latency = 0;
+ midi->latency = latency;
+ midi->overflow = FALSE; /* not used */
+ midi->flush = FALSE; /* not used */
+ midi->sysex_in_progress = FALSE;
+ midi->sysex_message = 0; /* unused by output */
+ midi->sysex_message_count = 0; /* unused by output */
+ midi->filters = 0; /* not used for output */
+ midi->channel_mask = 0xFFFF; /* not used for output */
+ midi->sync_time = 0;
+ midi->first_message = TRUE;
+ midi->dictionary = descriptors[outputDevice].dictionary;
+ descriptors[outputDevice].internalDescriptor = midi;
+ /* open system dependent output device */
+ err = (*midi->dictionary->open)(midi, outputDriverInfo);
+ if (err) {
+ *stream = NULL;
+ descriptors[outputDevice].internalDescriptor = NULL;
+ /* free portMidi data */
+ pm_free(midi);
+ } else {
+ /* portMidi input open successful */
+ descriptors[outputDevice].pub.opened = TRUE;
+ }
+error_return:
+ return pm_errmsg(err);
+}
+
+PmError Pm_SetChannelMask(PortMidiStream *stream, int mask)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+
+ if (midi == NULL)
+ err = pmBadPtr;
+ else
+ midi->channel_mask = mask;
+
+ return pm_errmsg(err);
+}
+
+PmError Pm_SetFilter(PortMidiStream *stream, long filters)
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+
+ /* arg checking */
+ if (midi == NULL)
+ err = pmBadPtr;
+ else if (!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else
+ midi->filters = filters;
+ return pm_errmsg(err);
+}
+
+
+PmError Pm_Close( PortMidiStream *stream )
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err = pmNoError;
+
+ /* arg checking */
+ if (midi == NULL) /* midi must point to something */
+ err = pmBadPtr;
+ /* if it is an open device, the device_id will be valid */
+ else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index)
+ err = pmBadPtr;
+ /* and the device should be in the opened state */
+ else if (!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+
+ if (err != pmNoError)
+ goto error_return;
+
+ /* close the device */
+ err = (*midi->dictionary->close)(midi);
+ /* even if an error occurred, continue with cleanup */
+ descriptors[midi->device_id].internalDescriptor = NULL;
+ descriptors[midi->device_id].pub.opened = FALSE;
+ pm_free(midi->buffer);
+ pm_free(midi);
+error_return:
+ return pm_errmsg(err);
+}
+
+
+PmError Pm_Abort( PortMidiStream* stream )
+{
+ PmInternal *midi = (PmInternal *) stream;
+ PmError err;
+ /* arg checking */
+ if (midi == NULL)
+ err = pmBadPtr;
+ if (!descriptors[midi->device_id].pub.output)
+ err = pmBadPtr;
+ if (!descriptors[midi->device_id].pub.opened)
+ err = pmBadPtr;
+ else
+ err = (*midi->dictionary->abort)(midi);
+ return pm_errmsg(err);
+}
+
+/* in win32 multimedia API (via callbacks) some of these functions used; assume never fail */
+long pm_next_time(PmInternal *midi) {
+
+ /* arg checking */
+ assert(midi != NULL);
+ assert(!Pm_HasHostError(midi));
+
+ return midi->buffer[midi->head].timestamp;
+}
+/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */
+static int pm_channel_filtered(int status, int mask)
+{
+ if ((status & 0xF0) == 0xF0) /* 0xF? messages don't have a channel */
+ return 0;
+ return !(Pm_Channel(status & 0x0F) & mask);
+ /* it'd be easier to return 0 for filtered, 1 for allowed,
+ but it would different from the other filtering functions
+ */
+
+}
+/* The following two functions will checks to see if a MIDI message matches
+ the filtering criteria. Since the sysex routines only want to filter realtime messages,
+ we need to have separate routines.
+ */
+
+/* pm_realtime_filtered returns non-zero if the filter will kill the current message.
+ Note that only realtime messages are checked here.
+ */
+static int pm_realtime_filtered(int status, long filters)
+{
+ return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))
+ || ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))
+ || ((status == MIDI_START) && (filters & PM_FILT_PLAY))
+ || ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))
+ || ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))
+ || ((status == MIDI_F9) && (filters & PM_FILT_F9))
+ || ((status == MIDI_FD) && (filters & PM_FILT_FD))
+ || ((status == MIDI_RESET) && (filters & PM_FILT_RESET))
+ || ((status == MIDI_MTC) && (filters & PM_FILT_MTC))
+ || ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))
+ || ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))
+ || ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));
+}
+/* pm_status_filtered returns non-zero if a filter will kill the current message, based on status.
+ Note that sysex and real time are not checked. It is up to the subsystem (winmm, core midi, alsa)
+ to filter sysex, as it is handled more easily and efficiently at that level.
+ Realtime message are filtered in pm_realtime_filtered.
+ */
+
+static int pm_status_filtered(int status, long filters)
+{
+ status &= 0xF0; /* remove channel information */
+ return ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))
+ || ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))
+ || ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH))
+ || ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))
+ || ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))
+ || ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))
+ || ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
+
+}
+
+/* pm_read_short and pm_read_byte
+ are the interface between system-dependent MIDI input handlers
+ and the system-independent PortMIDI code.
+ The input handler MUST obey these rules:
+ 1) all short input messages must be sent to pm_read_short, which
+ enqueues them to a FIFO for the application.
+ 2) eash sysex byte should be reported by calling pm_read_byte
+ (which sets midi->sysex_in_progress). After the eox byte,
+ pm_read_byte will clear sysex_in_progress and midi->flush
+ (Note that the overflow flag is managed by pm_read_short
+ and Pm_Read, so the supplier should not read or write it.)
+ */
+
+/* pm_read_short is the place where all input messages arrive from
+ system-dependent code such as pmwinmm.c. Here, the messages
+ are entered into the PortMidi input buffer.
+ */
+
+ /* Algorithnm:
+ if overflow or flush, return
+ ATOMIC:
+ enqueue data
+ if buffer overflow, set overflow
+ if buffer overflow and sysex_in_progress, set flush
+ */
+void pm_read_short(PmInternal *midi, PmEvent *event)
+{
+ long tail;
+ int status;
+ /* arg checking */
+ assert(midi != NULL);
+ assert(!Pm_HasHostError(midi));
+ /* midi filtering is applied here */
+ status = Pm_MessageStatus(event->message);
+ if (!pm_status_filtered(status, midi->filters)
+ && !pm_realtime_filtered(status, midi->filters)
+ && !pm_channel_filtered(status, midi->channel_mask)) {
+ /* if sysex is in progress and we get a status byte, it had
+ better be a realtime message or the starting SYSEX byte;
+ otherwise, we exit the sysex_in_progress state
+ */
+ if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK) &&
+ !is_real_time(status) && status != MIDI_SYSEX ) {
+ midi->sysex_in_progress = FALSE;
+ midi->flush = FALSE;
+ }
+
+ /* don't try to do anything more in an overflow state */
+ if (midi->overflow || midi->flush) return;
+
+ /* insert the message */
+ tail = midi->tail;
+ midi->buffer[tail++] = *event;
+ if (tail == midi->buffer_len) tail = 0;
+ if (tail == midi->head || midi->overflow) {
+ midi->overflow = TRUE;
+ if (midi->sysex_in_progress) midi->flush = TRUE;
+ /* drop the rest of the message, this must be cleared
+ by caller when EOX is received */
+ return;
+ }
+ midi->tail = tail; /* complete the write */
+ }
+}
+
+
+void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ PmEvent event;
+
+ /* there may be nothing in the buffer */
+ if (midi->sysex_message_count == 0) return; /* nothing to flush */
+
+ event.message = midi->sysex_message;
+ event.timestamp = timestamp;
+ pm_read_short(midi, &event);
+ midi->sysex_message_count = 0;
+ midi->sysex_message = 0;
+}
+
+
+void pm_read_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
+{
+ assert(midi);
+ assert(!Pm_HasHostError(midi));
+ /* here is the logic for controlling sysex_in_progress */
+ if (midi->sysex_in_progress) {
+ if (byte == MIDI_EOX) midi->sysex_in_progress = FALSE;
+ else if (byte == MIDI_SYSEX) {
+ /* problem: need to terminate the current sysex and start
+ a new one
+ */
+ pm_flush_sysex(midi, timestamp);
+ }
+ } else if (byte == MIDI_SYSEX) {
+ midi->sysex_in_progress = TRUE;
+ } else {
+ /* error: we're getting data bytes or EOX but we're no sysex is
+ in progress. Drop the data. (Would it be better to report an
+ error? Is this a host error or a pmBadData error? Is this
+ ever possible?
+ */
+#ifdef DEBUG
+ printf("PortMidi debug msg: unexpected sysex data or EOX\n");
+#endif
+ return;
+ }
+
+ if (pm_realtime_filtered(byte, midi->filters))
+ return;
+ /* this awkward expression places the bytes in increasingly higher-
+ order bytes of the long message */
+ midi->sysex_message |= (byte << (8 * midi->sysex_message_count++));
+ if (midi->sysex_message_count == 4 || !midi->sysex_in_progress) {
+ pm_flush_sysex(midi, timestamp);
+ if (!midi->sysex_in_progress) midi->flush = FALSE;
+ }
+}
+
+
+int pm_queue_full(PmInternal *midi)
+{
+ long tail;
+
+ /* arg checking */
+ assert(midi != NULL);
+ assert(!Pm_HasHostError(midi));
+
+ tail = midi->tail + 1;
+ if (tail == midi->buffer_len) tail = 0;
+ return tail == midi->head;
+}
+
diff --git a/pd/portmidi/pm_common/portmidi.h b/pd/portmidi/pm_common/portmidi.h
new file mode 100644
index 00000000..8c9c04c2
--- /dev/null
+++ b/pd/portmidi/pm_common/portmidi.h
@@ -0,0 +1,692 @@
+#ifndef PORT_MIDI_H
+#define PORT_MIDI_H
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * PortMidi Portable Real-Time MIDI Library
+ * PortMidi API Header File
+ * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/
+ *
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ * Copyright (c) 2001 Roger B. Dannenberg
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/* CHANGELOG FOR PORTMIDI
+ *
+ * 15Nov04 Ben Allison
+ * - sysex output now uses one buffer/message and reallocates buffer
+ * - if needed
+ * - filters expanded for many message types and channels
+ * - detailed changes are as follows:
+ * ------------- in pmwinmm.c --------------
+ * - new #define symbol: OUTPUT_BYTES_PER_BUFFER
+ * - change SYSEX_BYTES_PER_BUFFER to 1024
+ * - added MIDIHDR_BUFFER_LENGTH(x) to correctly count midihdr buffer length
+ * - change MIDIHDR_SIZE(x) to (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
+ * - change allocate_buffer to use new MIDIHDR_BUFFER_LENGTH macro
+ * - new macros for MIDIHDR_SYSEX_SIZE and MIDIHDR_SYSEX_BUFFER_LENGTH
+ * - similar to above, but counts appropriately for sysex messages
+ * - added the following members to midiwinmm_struct for sysex data:
+ * - LPMIDIHDR *sysex_buffers; ** pool of buffers for sysex data **
+ * - int num_sysex_buffers; ** how many sysex buffers **
+ * - int next_sysex_buffer; ** index of next sysexbuffer to send **
+ * - HANDLE sysex_buffer_signal; ** to wait for free sysex buffer **
+ * - duplicated allocate_buffer, alocate_buffers and get_free_output_buffer
+ * - into equivalent sysex_buffer form
+ * - changed winmm_in_open to initialize new midiwinmm_struct members and
+ * - to use the new allocate_sysex_buffer() function instead of
+ * - allocate_buffer()
+ * - changed winmm_out_open to initialize new members, create sysex buffer
+ * - signal, and allocate 2 sysex buffers
+ * - changed winmm_out_delete to free sysex buffers and shut down the sysex
+ * - buffer signal
+ * - create new function resize_sysex_buffer which resizes m->hdr to the
+ * - passed size, and corrects the midiwinmm_struct accordingly.
+ * - changed winmm_write_byte to use new resize_sysex_buffer function,
+ * - if resize fails, write current buffer to output and continue
+ * - changed winmm_out_callback to use buffer_signal or sysex_buffer_signal
+ * - depending on which buffer was finished
+ * ------------- in portmidi.h --------------
+ * - added pmBufferMaxSize to PmError to indicate that the buffer would be
+ * - too large for the underlying API
+ * - added additional filters
+ * - added prototype, documentation, and helper macro for Pm_SetChannelMask
+ * ------------- in portmidi.c --------------
+ * - added pm_status_filtered() and pm_realtime_filtered() functions to
+ * separate filtering logic from buffer logic in pm_read_short
+ * - added Pm_SetChannelMask function
+ * - added pm_channel_filtered() function
+ * ------------- in pminternal.h --------------
+ * - added member to PortMidiStream for channel mask
+ *
+ * 25May04 RBD
+ * - removed support for MIDI THRU
+ * - moved filtering from Pm_Read to pm_enqueue to avoid buffer ovfl
+ * - extensive work on Mac OS X port, especially sysex and error handling
+ *
+ * 18May04 RBD
+ * - removed side-effects from assert() calls. Now you can disable assert().
+ * - no longer check pm_hosterror everywhere, fixing a bug where an open
+ * failure could cause a write not to work on a previously opened port
+ * until you call Pm_GetHostErrorText().
+ * 16May04 RBD and Chris Roberts
+ * - Some documentation wordsmithing in portmidi.h
+ * - Dynamically allocate port descriptor structures
+ * - Fixed parameter error in midiInPrepareBuffer and midiInAddBuffer.
+ *
+ * 09Oct03 RBD
+ * - Changed Thru handling. Now the client does all the work and the client
+ * must poll or read to keep thru messages flowing.
+ *
+ * 31May03 RBD
+ * - Fixed various bugs.
+ * - Added linux ALSA support with help from Clemens Ladisch
+ * - Added Mac OS X support, implemented by Jon Parise, updated and
+ * integrated by Andrew Zeldis and Zico Kolter
+ * - Added latency program to build histogram of system latency using PortTime.
+ *
+ * 30Jun02 RBD Extensive rewrite of sysex handling. It works now.
+ * Extensive reworking of error reporting and error text -- no
+ * longer use dictionary call to delete data; instead, Pm_Open
+ * and Pm_Close clean up before returning an error code, and
+ * error text is saved in a system-independent location.
+ * Wrote sysex.c to test sysex message handling.
+ *
+ * 15Jun02 BCT changes:
+ * - Added pmHostError text handling.
+ * - For robustness, check PortMidi stream args not NULL.
+ * - Re-C-ANSI-fied code (changed many C++ comments to C style)
+ * - Reorganized code in pmwinmm according to input/output functionality (made
+ * cleanup handling easier to reason about)
+ * - Fixed Pm_Write calls (portmidi.h says these should not return length but Pm_Error)
+ * - Cleaned up memory handling (now system specific data deleted via dictionary
+ * call in PortMidi, allows client to query host errors).
+ * - Added explicit asserts to verify various aspects of pmwinmm implementation behaves as
+ * logic implies it should. Specifically: verified callback routines not reentrant and
+ * all verified status for all unchecked Win32 MMedia API calls perform successfully
+ * - Moved portmidi initialization and clean-up routines into DLL to fix Win32 MMedia API
+ * bug (i.e. if devices not explicitly closed, must reboot to debug application further).
+ * With this change, clients no longer need explicitly call Pm_Initialize, Pm_Terminate, or
+ * explicitly Pm_Close open devices when using WinMM version of PortMidi.
+ *
+ * 23Jan02 RBD Fixed bug in pmwinmm.c thru handling
+ *
+ * 21Jan02 RBD Added tests in Pm_OpenInput() and Pm_OpenOutput() to prevent
+ * opening an input as output and vice versa.
+ * Added comments and documentation.
+ * Implemented Pm_Terminate().
+ *
+ *
+ * IMPORTANT INFORMATION ABOUT A WIN32 BUG:
+ *
+ * Windows apparently has a serious midi bug -- if you do not close ports, Windows
+ * may crash. PortMidi tries to protect against this by using a DLL to clean up.
+ *
+ * If client exits for example with:
+ * i) assert
+ * ii) Ctrl^c,
+ * then DLL clean-up routine called. However, when client does something
+ * really bad (e.g. assigns value to NULL pointer) then DLL CLEANUP ROUTINE
+ * NEVER RUNS! In this state, if you wait around long enough, you will
+ * probably get the blue screen of death. Can also go into Pview and there will
+ * exist zombie process that you can't kill.
+ *
+ * NOTES ON HOST ERROR REPORTING:
+ *
+ * PortMidi errors (of type PmError) are generic, system-independent errors.
+ * When an error does not map to one of the more specific PmErrors, the
+ * catch-all code pmHostError is returned. This means that PortMidi has
+ * retained a more specific system-dependent error code. The caller can
+ * get more information by calling Pm_HasHostError() to test if there is
+ * a pending host error, and Pm_GetHostErrorText() to get a text string
+ * describing the error. Host errors are reported on a per-device basis
+ * because only after you open a device does PortMidi have a place to
+ * record the host error code. I.e. only
+ * those routines that receive a (PortMidiStream *) argument check and
+ * report errors. One exception to this is that Pm_OpenInput() and
+ * Pm_OpenOutput() can report errors even though when an error occurs,
+ * there is no PortMidiStream* to hold the error. Fortunately, both
+ * of these functions return any error immediately, so we do not really
+ * need per-device error memory. Instead, any host error code is stored
+ * in a global, pmHostError is returned, and the user can call
+ * Pm_GetHostErrorText() to get the error message (and the invalid stream
+ * parameter will be ignored.) The functions
+ * pm_init and pm_term do not fail or raise
+ * errors. The job of pm_init is to locate all available devices so that
+ * the caller can get information via PmDeviceInfo(). If an error occurs,
+ * the device is simply not listed as available.
+ *
+ * Host errors come in two flavors:
+ * a) host error
+ * b) host error during callback
+ * These can occur w/midi input or output devices. (b) can only happen
+ * asynchronously (during callback routines), whereas (a) only occurs while
+ * synchronously running PortMidi and any resulting system dependent calls
+ *
+ * Host-error reporting relies on following assumptions:
+ * 1) PortMidi routines won't allow system dependent routines to be
+ * called when args are bogus.
+ * Thus, in pmwinmm.c it is safe to assume:
+ * - stream ptr valid
+ * - currently not operating in "has host error" state
+ * 2) Host-error reporting relies on a staged delivery of error messages.
+ * When a host error occurs, the error code is saved with the stream.
+ * The error is reported as a return code from the next operation on
+ * the stream. This could be immediately if the error is synchronous,
+ * or delayed if the error is an asynchronous callback problem. In
+ * any case, when pmHostError is returned, the error is copied to
+ * a global, pm_hosterror and the error code stored with the stream
+ * is cleared. If the user chooses to inquire about the error using
+ * Pm_GetHostErrorText(), the error will be reported as text. If the
+ * user ignores the error and makes another call on the stream, the
+ * call will proceed because the error code associated with the stream
+ * has been cleared.
+ *
+ */
+
+#ifndef FALSE
+ #define FALSE 0
+#endif
+#ifndef TRUE
+ #define TRUE 1
+#endif
+
+/* default size of buffers for sysex transmission: */
+#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
+
+
+typedef enum {
+ pmNoError = 0,
+ pmHostError = -10000,
+ pmInvalidDeviceId, /* out of range or output device when input is requested or vice versa */
+ pmInsufficientMemory,
+ pmBufferTooSmall,
+ pmBufferOverflow,
+ pmBadPtr,
+ pmBadData, /* illegal midi data, e.g. missing EOX */
+ pmInternalError,
+ pmBufferMaxSize, /* buffer is already as large as it can be */
+} PmError;
+
+/*
+ Pm_Initialize() is the library initialisation function - call this before
+ using the library.
+*/
+
+PmError Pm_Initialize( void );
+
+/*
+ Pm_Terminate() is the library termination function - call this after
+ using the library.
+*/
+
+PmError Pm_Terminate( void );
+
+/* A single PortMidiStream is a descriptor for an open MIDI device.
+*/
+typedef void PortMidiStream;
+#define PmStream PortMidiStream
+
+/*
+ Test whether stream has a pending host error. Normally, the client finds
+ out about errors through returned error codes, but some errors can occur
+ asynchronously where the client does not
+ explicitly call a function, and therefore cannot receive an error code.
+ The client can test for a pending error using Pm_HasHostError(). If true,
+ the error can be accessed and cleared by calling Pm_GetErrorText(). The
+ client does not need to call Pm_HasHostError(). Any pending error will be
+ reported the next time the client performs an explicit function call on
+ the stream, e.g. an input or output operation.
+*/
+int Pm_HasHostError( PortMidiStream * stream );
+
+
+/* Translate portmidi error number into human readable message.
+ These strings are constants (set at compile time) so client has
+ no need to allocate storage
+*/
+const char *Pm_GetErrorText( PmError errnum );
+
+/* Translate portmidi host error into human readable message.
+ These strings are computed at run time, so client has to allocate storage.
+ After this routine executes, the host error is cleared.
+*/
+void Pm_GetHostErrorText(char * msg, unsigned int len);
+
+#define HDRLENGTH 50
+#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less
+ than this number of characters */
+
+/*
+ Device enumeration mechanism.
+
+ Device ids range from 0 to Pm_CountDevices()-1.
+
+*/
+typedef int PmDeviceID;
+#define pmNoDevice -1
+typedef struct {
+ int structVersion;
+ const char *interf; /* underlying MIDI API, e.g. MMSystem or DirectX */
+ const char *name; /* device name, e.g. USB MidiSport 1x1 */
+ int input; /* true iff input is available */
+ int output; /* true iff output is available */
+ int opened; /* used by generic PortMidi code to do error checking on arguments */
+
+} PmDeviceInfo;
+
+
+int Pm_CountDevices( void );
+/*
+ Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID()
+
+ Return the default device ID or pmNoDevice if there are no devices.
+ The result can be passed to Pm_OpenMidi().
+
+ On the PC, the user can specify a default device by
+ setting an environment variable. For example, to use device #1.
+
+ set PM_RECOMMENDED_OUTPUT_DEVICE=1
+
+ The user should first determine the available device ID by using
+ the supplied application "testin" or "testout".
+
+ In general, the registry is a better place for this kind of info,
+ and with USB devices that can come and go, using integers is not
+ very reliable for device identification. Under Windows, if
+ PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
+ *NOT* found in the environment, then the default device is obtained
+ by looking for a string in the registry under:
+ HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
+ and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
+ for a string. The number of the first device with a substring that
+ matches the string exactly is returned. For example, if the string
+ in the registry is "USB", and device 1 is named
+ "In USB MidiSport 1x1", then that will be the default
+ input because it contains the string "USB".
+
+ In addition to the name, PmDeviceInfo has the member "interf", which
+ is the interface name. (The "interface" is the underlying software
+ system or API used by PortMidi to access devices. Examples are
+ MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
+ At present, the only Win32 interface is "MMSystem", the only Linux
+ interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
+ To specify both the interface and the device name in the registry,
+ separate the two with a comma and a space, e.g.:
+ MMSystem, In USB MidiSport 1x1
+ In this case, the string before the comma must be a substring of
+ the "interf" string, and the string after the space must be a
+ substring of the "name" name string in order to match the device.
+
+ Note: in the current release, the default is simply the first device
+ (the input or output device with the lowest PmDeviceID).
+*/
+PmDeviceID Pm_GetDefaultInputDeviceID( void );
+PmDeviceID Pm_GetDefaultOutputDeviceID( void );
+
+/*
+ PmTimestamp is used to represent a millisecond clock with arbitrary
+ start time. The type is used for all MIDI timestampes and clocks.
+*/
+typedef long PmTimestamp;
+typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
+
+/* TRUE if t1 before t2 */
+#define PmBefore(t1,t2) ((t1-t2) < 0)
+
+/*
+ Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure
+ referring to the device specified by id.
+ If id is out of range the function returns NULL.
+
+ The returned structure is owned by the PortMidi implementation and must
+ not be manipulated or freed. The pointer is guaranteed to be valid
+ between calls to Pm_Initialize() and Pm_Terminate().
+*/
+const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id );
+
+/*
+ Pm_OpenInput() and Pm_OpenOutput() open devices.
+
+ stream is the address of a PortMidiStream pointer which will receive
+ a pointer to the newly opened stream.
+
+ inputDevice is the id of the device used for input (see PmDeviceID above).
+
+ inputDriverInfo is a pointer to an optional driver specific data structure
+ containing additional information for device setup or handle processing.
+ inputDriverInfo is never required for correct operation. If not used
+ inputDriverInfo should be NULL.
+
+ outputDevice is the id of the device used for output (see PmDeviceID above.)
+
+ outputDriverInfo is a pointer to an optional driver specific data structure
+ containing additional information for device setup or handle processing.
+ outputDriverInfo is never required for correct operation. If not used
+ outputDriverInfo should be NULL.
+
+ For input, the buffersize specifies the number of input events to be
+ buffered waiting to be read using Pm_Read(). For output, buffersize
+ specifies the number of output events to be buffered waiting for output.
+ (In some cases -- see below -- PortMidi does not buffer output at all
+ and merely passes data to a lower-level API, in which case buffersize
+ is ignored.)
+
+ latency is the delay in milliseconds applied to timestamps to determine
+ when the output should actually occur. (If latency is < 0, 0 is assumed.)
+ If latency is zero, timestamps are ignored and all output is delivered
+ immediately. If latency is greater than zero, output is delayed until
+ the message timestamp plus the latency. (NOTE: time is measured relative
+ to the time source indicated by time_proc. Timestamps are absolute, not
+ relative delays or offsets.) In some cases, PortMidi can obtain
+ better timing than your application by passing timestamps along to the
+ device driver or hardware. Latency may also help you to synchronize midi
+ data to audio data by matching midi latency to the audio buffer latency.
+
+ time_proc is a pointer to a procedure that returns time in milliseconds. It
+ may be NULL, in which case a default millisecond timebase (PortTime) is
+ used. If the application wants to use PortTime, it should start the timer
+ (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the
+ application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput,
+ it may get a ptAlreadyStarted error from Pt_Start, and the application's
+ preferred time resolution and callback function will be ignored.
+ time_proc result values are appended to incoming MIDI data, and time_proc
+ times are used to schedule outgoing MIDI data (when latency is non-zero).
+
+ time_info is a pointer passed to time_proc.
+
+ return value:
+ Upon success Pm_Open() returns PmNoError and places a pointer to a
+ valid PortMidiStream in the stream argument.
+ If a call to Pm_Open() fails a nonzero error code is returned (see
+ PMError above) and the value of port is invalid.
+
+ Any stream that is successfully opened should eventually be closed
+ by calling Pm_Close().
+
+*/
+PmError Pm_OpenInput( PortMidiStream** stream,
+ PmDeviceID inputDevice,
+ void *inputDriverInfo,
+ long bufferSize,
+ PmTimeProcPtr time_proc,
+ void *time_info );
+
+PmError Pm_OpenOutput( PortMidiStream** stream,
+ PmDeviceID outputDevice,
+ void *outputDriverInfo,
+ long bufferSize,
+ PmTimeProcPtr time_proc,
+ void *time_info,
+ long latency );
+
+/*
+ Pm_SetFilter() sets filters on an open input stream to drop selected
+ input types. By default, only active sensing messages are filtered.
+ To prohibit, say, active sensing and sysex messages, call
+ Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
+
+ Filtering is useful when midi routing or midi thru functionality is being
+ provided by the user application.
+ For example, you may want to exclude timing messages (clock, MTC, start/stop/continue),
+ while allowing note-related messages to pass.
+ Or you may be using a sequencer or drum-machine for MIDI clock information but want to
+ exclude any notes it may play.
+ */
+
+/* filter active sensing messages (0xFE): */
+#define PM_FILT_ACTIVE 0x1
+/* filter system exclusive messages (0xF0): */
+#define PM_FILT_SYSEX 0x2
+/* filter clock messages (0xF8 only, does not filter clock start, etc.): */
+#define PM_FILT_CLOCK 0x4
+/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
+#define PM_FILT_PLAY 0x8
+/* filter undefined F9 messages (some equipment uses this as a 10ms 'tick') */
+#define PM_FILT_F9 0x10
+#define PM_FILT_TICK PM_FILT_F9
+/* filter undefined FD messages */
+#define PM_FILT_FD 0x20
+/* filter undefined real-time messages */
+#define PM_FILT_UNDEFINED (PM_FILT_F9 | PM_FILT_FD)
+/* filter reset messages (0xFF) */
+#define PM_FILT_RESET 0x40
+/* filter all real-time messages */
+#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET)
+/* filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
+#define PM_FILT_NOTE 0x80
+/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
+#define PM_FILT_CHANNEL_AFTERTOUCH 0x100
+/* per-note aftertouch (Ensoniq holds a patent on generating this on keyboards until June 2006) (0xA0-0xAF) */
+#define PM_FILT_POLY_AFTERTOUCH 0x200
+/* filter both channel and poly aftertouch */
+#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH)
+/* Program changes (0xC0-0xCF) */
+#define PM_FILT_PROGRAM 0x400
+/* Control Changes (CC's) (0xB0-0xBF)*/
+#define PM_FILT_CONTROL 0x800
+/* Pitch Bender (0xE0-0xEF*/
+#define PM_FILT_PITCHBEND 0x1000
+/* MIDI Time Code (0xF1)*/
+#define PM_FILT_MTC 0x2000
+/* Song Position (0xF2) */
+#define PM_FILT_SONG_POSITION 0x4000
+/* Song Select (0xF3)*/
+#define PM_FILT_SONG_SELECT 0x8000
+/* Tuning request (0xF6)*/
+#define PM_FILT_TUNE 0x10000
+/* All System Common messages (mtc, song position, song select, tune request) */
+#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE)
+
+
+PmError Pm_SetFilter( PortMidiStream* stream, long filters );
+
+
+/*
+ Pm_SetChannelMask() filters incoming messages based on channel.
+ The mask is a 16-bit bitfield corresponding to appropriate channels
+ The Pm_Channel macro can assist in calling this function.
+ i.e. to set receive only input on channel 1, call with
+ Pm_SetChannelMask(Pm_Channel(1));
+ Multiple channels should be OR'd together, like
+ Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
+
+ All channels are allowed by default
+*/
+#define Pm_Channel(channel) (1<<(channel))
+
+PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
+
+/*
+ Pm_Abort() terminates outgoing messages immediately
+ The caller should immediately close the output port;
+ this call may result in transmission of a partial midi message.
+ There is no abort for Midi input because the user can simply
+ ignore messages in the buffer and close an input device at
+ any time.
+ */
+PmError Pm_Abort( PortMidiStream* stream );
+
+/*
+ Pm_Close() closes a midi stream, flushing any pending buffers.
+ (PortMidi attempts to close open streams when the application
+ exits -- this is particularly difficult under Windows.)
+*/
+PmError Pm_Close( PortMidiStream* stream );
+
+/*
+ Pm_Message() encodes a short Midi message into a long word. If data1
+ and/or data2 are not present, use zero.
+
+ Pm_MessageStatus(), Pm_MessageData1(), and
+ Pm_MessageData2() extract fields from a long-encoded midi message.
+*/
+#define Pm_Message(status, data1, data2) \
+ ((((data2) << 16) & 0xFF0000) | \
+ (((data1) << 8) & 0xFF00) | \
+ ((status) & 0xFF))
+#define Pm_MessageStatus(msg) ((msg) & 0xFF)
+#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
+#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
+
+/* All midi data comes in the form of PmEvent structures. A sysex
+ message is encoded as a sequence of PmEvent structures, with each
+ structure carrying 4 bytes of the message, i.e. only the first
+ PmEvent carries the status byte.
+
+ Note that MIDI allows nested messages: the so-called "real-time" MIDI
+ messages can be inserted into the MIDI byte stream at any location,
+ including within a sysex message. MIDI real-time messages are one-byte
+ messages used mainly for timing (see the MIDI spec). PortMidi retains
+ the order of non-real-time MIDI messages on both input and output, but
+ it does not specify exactly how real-time messages are processed. This
+ is particulary problematic for MIDI input, because the input parser
+ must either prepare to buffer an unlimited number of sysex message
+ bytes or to buffer an unlimited number of real-time messages that
+ arrive embedded in a long sysex message. To simplify things, the input
+ parser is allowed to pass real-time MIDI messages embedded within a
+ sysex message, and it is up to the client to detect, process, and
+ remove these messages as they arrive.
+
+ When receiving sysex messages, the sysex message is terminated
+ by either an EOX status byte (anywhere in the 4 byte messages) or
+ by a non-real-time status byte in the low order byte of the message.
+ If you get a non-real-time status byte but there was no EOX byte, it
+ means the sysex message was somehow truncated. This is not
+ considered an error; e.g., a missing EOX can result from the user
+ disconnecting a MIDI cable during sysex transmission.
+
+ A real-time message can occur within a sysex message. A real-time
+ message will always occupy a full PmEvent with the status byte in
+ the low-order byte of the PmEvent message field. (This implies that
+ the byte-order of sysex bytes and real-time message bytes may not
+ be preserved -- for example, if a real-time message arrives after
+ 3 bytes of a sysex message, the real-time message will be delivered
+ first. The first word of the sysex message will be delivered only
+ after the 4th byte arrives, filling the 4-byte PmEvent message field.
+
+ The timestamp field is observed when the output port is opened with
+ a non-zero latency. A timestamp of zero means "use the current time",
+ which in turn means to deliver the message with a delay of
+ latency (the latency parameter used when opening the output port.)
+ Do not expect PortMidi to sort data according to timestamps --
+ messages should be sent in the correct order, and timestamps MUST
+ be non-decreasing.
+
+ A sysex message will generally fill many PmEvent structures. On
+ output to a PortMidiStream with non-zero latency, the first timestamp
+ on sysex message data will determine the time to begin sending the
+ message. PortMidi implementations may ignore timestamps for the
+ remainder of the sysex message.
+
+ On input, the timestamp ideally denotes the arrival time of the
+ status byte of the message. The first timestamp on sysex message
+ data will be valid. Subsequent timestamps may denote
+ when message bytes were actually received, or they may be simply
+ copies of the first timestamp.
+
+ Timestamps for nested messages: If a real-time message arrives in
+ the middle of some other message, it is enqueued immediately with
+ the timestamp corresponding to its arrival time. The interrupted
+ non-real-time message or 4-byte packet of sysex data will be enqueued
+ later. The timestamp of interrupted data will be equal to that of
+ the interrupting real-time message to insure that timestamps are
+ non-decreasing.
+ */
+typedef long PmMessage;
+typedef struct {
+ PmMessage message;
+ PmTimestamp timestamp;
+} PmEvent;
+
+/*
+ Pm_Read() retrieves midi data into a buffer, and returns the number
+ of events read. Result is a non-negative number unless an error occurs,
+ in which case a PmError value will be returned.
+
+ Buffer Overflow
+
+ The problem: if an input overflow occurs, data will be lost, ultimately
+ because there is no flow control all the way back to the data source.
+ When data is lost, the receiver should be notified and some sort of
+ graceful recovery should take place, e.g. you shouldn't resume receiving
+ in the middle of a long sysex message.
+
+ With a lock-free fifo, which is pretty much what we're stuck with to
+ enable portability to the Mac, it's tricky for the producer and consumer
+ to synchronously reset the buffer and resume normal operation.
+
+ Solution: the buffer managed by PortMidi will be flushed when an overflow
+ occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow)
+ and ordinary processing resumes as soon as a new message arrives. The
+ remainder of a partial sysex message is not considered to be a "new
+ message" and will be flushed as well.
+
+*/
+PmError Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length );
+
+/*
+ Pm_Poll() tests whether input is available,
+ returning TRUE, FALSE, or an error value.
+*/
+PmError Pm_Poll( PortMidiStream *stream);
+
+/*
+ Pm_Write() writes midi data from a buffer. This may contain:
+ - short messages
+ or
+ - sysex messages that are converted into a sequence of PmEvent
+ structures, e.g. sending data from a file or forwarding them
+ from midi input.
+
+ Use Pm_WriteSysEx() to write a sysex message stored as a contiguous
+ array of bytes.
+
+ Sysex data may contain embedded real-time messages.
+*/
+PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length );
+
+/*
+ Pm_WriteShort() writes a timestamped non-system-exclusive midi message.
+ Messages are delivered in order as received, and timestamps must be
+ non-decreasing. (But timestamps are ignored if the stream was opened
+ with latency = 0.)
+*/
+PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, long msg);
+
+/*
+ Pm_WriteSysEx() writes a timestamped system-exclusive midi message.
+*/
+PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif /* PORT_MIDI_H */
diff --git a/pd/portmidi/pm_linux/README_LINUX.txt b/pd/portmidi/pm_linux/README_LINUX.txt
new file mode 100644
index 00000000..e8a4332f
--- /dev/null
+++ b/pd/portmidi/pm_linux/README_LINUX.txt
@@ -0,0 +1,32 @@
+README_LINUX.txt for PortMidi
+Roger Dannenberg
+8 June 2004
+
+To make PortMidi and PortTime, go back up to the portmidi
+directory and type make.
+
+The Makefile will build all test programs and the portmidi
+library. You may want to modify the Makefile to remove the
+PM_CHECK_ERRORS definition. For experimental software,
+especially programs running from the command line, we
+recommend using PM_CHECK_ERRORS -- it will terminate your
+program and print a helpful message if any PortMidi
+function returns an error code.
+
+If you do not compile with PM_CHECK_ERRORS, you should
+check for errors yourself.
+
+This code has not been carefully tested; however,
+all test programs in pm_test seem to run properly.
+
+CHANGELOG
+
+08-Jun-2004 Roger B. Dannenberg
+ Updated code to use new system abstraction.
+
+12-Apr-2003 Roger B. Dannenberg
+ Fixed pm_test/test.c to filter clocks and active messages.
+ Integrated changes from Clemens Ladisch:
+ cleaned up pmlinuxalsa.c
+ record timestamp on sysex input
+ deallocate some resources previously left open
diff --git a/pd/portmidi/pm_linux/pmlinux.c b/pd/portmidi/pm_linux/pmlinux.c
new file mode 100644
index 00000000..8c70319f
--- /dev/null
+++ b/pd/portmidi/pm_linux/pmlinux.c
@@ -0,0 +1,51 @@
+/* pmlinux.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement pm_init(), which calls various
+ routines to register the available midi devices. This file must
+ be separate from the main portmidi.c file because it is system
+ dependent, and it is separate from, pmlinuxalsa.c, because it
+ might need to register non-alsa devices as well.
+ */
+
+#include "stdlib.h"
+#include "portmidi.h"
+#ifdef PMALSA
+ #include "pmlinuxalsa.h"
+#endif
+
+#ifdef PMNULL
+ #include "pmlinuxnull.h"
+#endif
+
+PmError pm_init()
+{
+ #ifdef PMALSA
+ pm_linuxalsa_init();
+ #endif
+ #ifdef PMNULL
+ pm_linuxnull_init();
+ #endif
+}
+
+void pm_term(void)
+{
+ #ifdef PMALSA
+ pm_linuxalsa_term();
+ #endif
+}
+
+PmDeviceID pm_default_input_device_id = -1;
+PmDeviceID pm_default_output_device_id = -1;
+
+PmDeviceID Pm_GetDefaultInputDeviceID() {
+ return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+ return pm_default_output_device_id;
+}
+
+void *pm_alloc(size_t s) { return malloc(s); }
+
+void pm_free(void *ptr) { free(ptr); }
+
diff --git a/pd/portmidi/pm_linux/pmlinux.h b/pd/portmidi/pm_linux/pmlinux.h
new file mode 100644
index 00000000..75c91f14
--- /dev/null
+++ b/pd/portmidi/pm_linux/pmlinux.h
@@ -0,0 +1,5 @@
+/* pmlinux.h */
+
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id;
+
diff --git a/pd/portmidi/pm_linux/pmlinuxalsa.c b/pd/portmidi/pm_linux/pmlinuxalsa.c
new file mode 100644
index 00000000..9b0eee75
--- /dev/null
+++ b/pd/portmidi/pm_linux/pmlinuxalsa.c
@@ -0,0 +1,724 @@
+/*
+ * pmlinuxalsa.c -- system specific definitions
+ *
+ * written by:
+ * Roger Dannenberg (port to Alsa 0.9.x)
+ * Clemens Ladisch (provided code examples and invaluable consulting)
+ * Jason Cohen, Rico Colon, Matt Filippone (Alsa 0.5.x implementation)
+ */
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pminternal.h"
+#include "pmlinuxalsa.h"
+#include "string.h"
+#include "porttime.h"
+#include "pmlinux.h"
+
+#include <alsa/asoundlib.h>
+
+/* I used many print statements to debug this code. I left them in the
+ * source, and you can turn them on by changing false to true below:
+ */
+#define VERBOSE_ON 0
+#define VERBOSE if (VERBOSE_ON)
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+#if SND_LIB_MAJOR == 0 && SND_LIB_MINOR < 9
+#error needs ALSA 0.9.0 or later
+#endif
+
+/* to store client/port in the device descriptor */
+#define MAKE_DESCRIPTOR(client, port) ((void*)(((client) << 8) | (port)))
+#define GET_DESCRIPTOR_CLIENT(info) ((((int)(info)) >> 8) & 0xff)
+#define GET_DESCRIPTOR_PORT(info) (((int)(info)) & 0xff)
+
+#define BYTE unsigned char
+#define UINT unsigned long
+
+extern pm_fns_node pm_linuxalsa_in_dictionary;
+extern pm_fns_node pm_linuxalsa_out_dictionary;
+
+static snd_seq_t *seq; // all input comes here, output queue allocated on seq
+static int queue, queue_used; /* one for all ports, reference counted */
+
+typedef struct alsa_descriptor_struct {
+ int client;
+ int port;
+ int this_port;
+ int in_sysex;
+ snd_midi_event_t *parser;
+ int error; /* host error code */
+} alsa_descriptor_node, *alsa_descriptor_type;
+
+
+/* get_alsa_error_text -- copy error text to potentially short string */
+/**/
+static void get_alsa_error_text(char *msg, int len, int err)
+{
+ int errlen = strlen(snd_strerror(err));
+ if (errlen < len) {
+ strcpy(msg, snd_strerror(err));
+ } else if (len > 20) {
+ sprintf(msg, "Alsa error %d", err);
+ } else if (len > 4) {
+ strcpy(msg, "Alsa");
+ } else {
+ msg[0] = 0;
+ }
+}
+
+
+/* queue is shared by both input and output, reference counted */
+static PmError alsa_use_queue(void)
+{
+ if (queue_used == 0) {
+ snd_seq_queue_tempo_t *tempo;
+
+ queue = snd_seq_alloc_queue(seq);
+ if (queue < 0) {
+ pm_hosterror = queue;
+ return pmHostError;
+ }
+ snd_seq_queue_tempo_alloca(&tempo);
+ snd_seq_queue_tempo_set_tempo(tempo, 480000);
+ snd_seq_queue_tempo_set_ppq(tempo, 480);
+ pm_hosterror = snd_seq_set_queue_tempo(seq, queue, tempo);
+ if (pm_hosterror < 0)
+ return pmHostError;
+
+ snd_seq_start_queue(seq, queue, NULL);
+ snd_seq_drain_output(seq);
+ }
+ ++queue_used;
+ return pmNoError;
+}
+
+
+static void alsa_unuse_queue(void)
+{
+ if (--queue_used == 0) {
+ snd_seq_stop_queue(seq, queue, NULL);
+ snd_seq_drain_output(seq);
+ snd_seq_free_queue(seq, queue);
+ VERBOSE printf("queue freed\n");
+ }
+}
+
+
+/* midi_message_length -- how many bytes in a message? */
+static int midi_message_length(PmMessage message)
+{
+ message &= 0xff;
+ if (message < 0x80) {
+ return 0;
+ } else if (message < 0xf0) {
+ static const int length[] = {3, 3, 3, 3, 2, 2, 3};
+ return length[(message - 0x80) >> 4];
+ } else {
+ static const int length[] = {
+ -1, 2, 3, 2, 0, 0, 1, -1, 1, 0, 1, 1, 1, 0, 1, 1};
+ return length[message - 0xf0];
+ }
+}
+
+
+static PmError alsa_out_open(PmInternal *midi, void *driverInfo)
+{
+ void *client_port = descriptors[midi->device_id].descriptor;
+ alsa_descriptor_type desc = (alsa_descriptor_type)
+ pm_alloc(sizeof(alsa_descriptor_node));
+ snd_seq_port_info_t *info;
+ int err;
+
+ if (!desc) return pmInsufficientMemory;
+
+ snd_seq_port_info_alloca(&info);
+ snd_seq_port_info_set_port(info, midi->device_id);
+ snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_READ);
+ snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_port_specified(info, 1);
+ err = snd_seq_create_port(seq, info);
+ if (err < 0) goto free_desc;
+
+ /* fill in fields of desc, which is passed to pm_write routines */
+ midi->descriptor = desc;
+ desc->client = GET_DESCRIPTOR_CLIENT(client_port);
+ desc->port = GET_DESCRIPTOR_PORT(client_port);
+ desc->this_port = midi->device_id;
+ desc->in_sysex = 0;
+
+ desc->error = 0;
+
+ err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &desc->parser);
+ if (err < 0) goto free_this_port;
+
+ if (midi->latency > 0) { /* must delay output using a queue */
+ err = alsa_use_queue();
+ if (err < 0) goto free_parser;
+
+ err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port);
+ if (err < 0) goto unuse_queue; /* clean up and return on error */
+ } else {
+ err = snd_seq_connect_to(seq, desc->this_port, desc->client, desc->port);
+ if (err < 0) goto free_parser; /* clean up and return on error */
+ }
+ return pmNoError;
+
+ unuse_queue:
+ alsa_unuse_queue();
+ free_parser:
+ snd_midi_event_free(desc->parser);
+ free_this_port:
+ snd_seq_delete_port(seq, desc->this_port);
+ free_desc:
+ pm_free(desc);
+ pm_hosterror = err;
+ if (err < 0) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
+ }
+ return pmHostError;
+}
+
+
+static PmError alsa_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ snd_seq_event_t ev;
+ int err;
+
+ snd_seq_ev_clear(&ev);
+ if (snd_midi_event_encode_byte(desc->parser, byte, &ev) == 1) {
+ snd_seq_ev_set_dest(&ev, desc->client, desc->port);
+ snd_seq_ev_set_source(&ev, desc->this_port);
+ if (midi->latency > 0) {
+ /* compute relative time of event = timestamp - now + latency */
+ PmTimestamp now = (midi->time_proc ?
+ midi->time_proc(midi->time_info) :
+ Pt_Time(NULL));
+ int when = timestamp;
+ /* if timestamp is zero, send immediately */
+ /* otherwise compute time delay and use delay if positive */
+ if (when == 0) when = now;
+ when = (when - now) + midi->latency;
+ if (when < 0) when = 0;
+ VERBOSE printf("timestamp %d now %d latency %d, ",
+ timestamp, now, midi->latency);
+ VERBOSE printf("scheduling event after %d\n", when);
+ /* message is sent in relative ticks, where 1 tick = 1 ms */
+ snd_seq_ev_schedule_tick(&ev, queue, 1, when);
+ /* NOTE: for cases where the user does not supply a time function,
+ we could optimize the code by not starting Pt_Time and using
+ the alsa tick time instead. I didn't do this because it would
+ entail changing the queue management to start the queue tick
+ count when PortMidi is initialized and keep it running until
+ PortMidi is terminated. (This should be simple, but it's not
+ how the code works now.) -RBD */
+ } else { /* send event out without queueing */
+ VERBOSE printf("direct\n");
+ /* ev.queue = SND_SEQ_QUEUE_DIRECT;
+ ev.dest.client = SND_SEQ_ADDRESS_SUBSCRIBERS; */
+ snd_seq_ev_set_direct(&ev);
+ }
+ VERBOSE printf("sending event\n");
+ err = snd_seq_event_output(seq, &ev);
+ if (err < 0) {
+ desc->error = err;
+ return pmHostError;
+ }
+ }
+ return pmNoError;
+}
+
+
+static PmError alsa_out_close(PmInternal *midi)
+{
+ int err;
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ if (!desc) return pmBadPtr;
+
+ if (pm_hosterror = snd_seq_disconnect_to(seq, desc->this_port,
+ desc->client, desc->port)) {
+ // if there's an error, try to delete the port anyway, but don't
+ // change the pm_hosterror value so we retain the first error
+ snd_seq_delete_port(seq, desc->this_port);
+ } else { // if there's no error, delete the port and retain any error
+ pm_hosterror = snd_seq_delete_port(seq, desc->this_port);
+ }
+ if (midi->latency > 0) alsa_unuse_queue();
+ snd_midi_event_free(desc->parser);
+ pm_free(desc);
+ if (pm_hosterror) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
+ pm_hosterror);
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
+{
+ void *client_port = descriptors[midi->device_id].descriptor;
+ alsa_descriptor_type desc = (alsa_descriptor_type)
+ pm_alloc(sizeof(alsa_descriptor_node));
+ snd_seq_port_info_t *info;
+ snd_seq_port_subscribe_t *sub;
+ snd_seq_addr_t addr;
+ int err;
+
+ if (!desc) return pmInsufficientMemory;
+
+ err = alsa_use_queue();
+ if (err < 0) goto free_desc;
+
+ snd_seq_port_info_alloca(&info);
+ snd_seq_port_info_set_port(info, midi->device_id);
+ snd_seq_port_info_set_capability(info, SND_SEQ_PORT_CAP_WRITE |
+ SND_SEQ_PORT_CAP_READ);
+ snd_seq_port_info_set_type(info, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+ SND_SEQ_PORT_TYPE_APPLICATION);
+ snd_seq_port_info_set_port_specified(info, 1);
+ err = snd_seq_create_port(seq, info);
+ if (err < 0) goto free_queue;
+
+ /* fill in fields of desc, which is passed to pm_write routines */
+ midi->descriptor = desc;
+ desc->client = GET_DESCRIPTOR_CLIENT(client_port);
+ desc->port = GET_DESCRIPTOR_PORT(client_port);
+ desc->this_port = midi->device_id;
+ desc->in_sysex = 0;
+
+ desc->error = 0;
+
+ VERBOSE printf("snd_seq_connect_from: %d %d %d\n",
+ desc->this_port, desc->client, desc->port);
+ snd_seq_port_subscribe_alloca(&sub);
+ addr.client = snd_seq_client_id(seq);
+ addr.port = desc->this_port;
+ snd_seq_port_subscribe_set_dest(sub, &addr);
+ addr.client = desc->client;
+ addr.port = desc->port;
+ snd_seq_port_subscribe_set_sender(sub, &addr);
+ snd_seq_port_subscribe_set_time_update(sub, 1);
+ /* this doesn't seem to work: messages come in with real timestamps */
+ snd_seq_port_subscribe_set_time_real(sub, 0);
+ err = snd_seq_subscribe_port(seq, sub);
+ /* err =
+ snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */
+ if (err < 0) goto free_this_port; /* clean up and return on error */
+ return pmNoError;
+
+ free_this_port:
+ snd_seq_delete_port(seq, desc->this_port);
+ free_queue:
+ alsa_unuse_queue();
+ free_desc:
+ pm_free(desc);
+ pm_hosterror = err;
+ if (err < 0) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
+ }
+ return pmHostError;
+}
+
+static PmError alsa_in_close(PmInternal *midi)
+{
+ int err;
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ if (!desc) return pmBadPtr;
+ if (pm_hosterror = snd_seq_disconnect_from(seq, desc->this_port,
+ desc->client, desc->port)) {
+ snd_seq_delete_port(seq, desc->this_port); /* try to close port */
+ } else {
+ pm_hosterror = snd_seq_delete_port(seq, desc->this_port);
+ }
+ alsa_unuse_queue();
+ pm_free(desc);
+ if (pm_hosterror) {
+ get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
+ pm_hosterror);
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+static PmError alsa_abort(PmInternal *midi)
+{
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ /* This is supposed to flush any pending output. */
+ printf("WARNING: alsa_abort not implemented\n");
+ return pmNoError;
+}
+
+
+#ifdef GARBAGE
+This is old code here temporarily for reference
+static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length)
+{
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ int i, bytes;
+ unsigned char byte;
+ long msg;
+
+ desc->error = 0;
+ for (; length > 0; length--, buffer++) {
+ VERBOSE printf("message 0x%x\n", buffer->message);
+ if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX)
+ desc->in_sysex = TRUE;
+ if (desc->in_sysex) {
+ msg = buffer->message;
+ for (i = 0; i < 4; i++) {
+ byte = msg; /* extract next byte to send */
+ alsa_write_byte(midi, byte, buffer->timestamp);
+ if (byte == MIDI_EOX) {
+ desc->in_sysex = FALSE;
+ break;
+ }
+ if (desc->error < 0) break;
+ msg >>= 8; /* shift next byte into position */
+ }
+ } else {
+ bytes = midi_message_length(buffer->message);
+ msg = buffer->message;
+ for (i = 0; i < bytes; i++) {
+ byte = msg; /* extract next byte to send */
+ VERBOSE printf("sending 0x%x\n", byte);
+ alsa_write_byte(midi, byte, buffer->timestamp);
+ if (desc->error < 0) break;
+ msg >>= 8; /* shift next byte into position */
+ }
+ }
+ }
+ if (desc->error < 0) return pmHostError;
+
+ VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq);
+ desc->error = snd_seq_drain_output(seq);
+ if (desc->error < 0) return pmHostError;
+
+ desc->error = pmNoError;
+ return pmNoError;
+}
+#endif
+
+
+static PmError alsa_write_flush(PmInternal *midi)
+{
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq);
+ desc->error = snd_seq_drain_output(seq);
+ if (desc->error < 0) return pmHostError;
+
+ desc->error = pmNoError;
+ return pmNoError;
+}
+
+
+static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
+{
+ int bytes = midi_message_length(event->message);
+ long msg = event->message;
+ int i;
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ for (i = 0; i < bytes; i++) {
+ unsigned char byte = msg;
+ VERBOSE printf("sending 0x%x\n", byte);
+ alsa_write_byte(midi, byte, event->timestamp);
+ if (desc->error < 0) break;
+ msg >>= 8; /* shift next byte into position */
+ }
+ if (desc->error < 0) return pmHostError;
+ desc->error = pmNoError;
+ return pmNoError;
+}
+
+
+/* alsa_sysex -- implements begin_sysex and end_sysex */
+PmError alsa_sysex(PmInternal *midi, PmTimestamp timestamp) {
+ return pmNoError;
+}
+
+
+static PmTimestamp alsa_synchronize(PmInternal *midi)
+{
+ return 0; /* linux implementation does not use this synchronize function */
+ /* Apparently, Alsa data is relative to the time you send it, and there
+ is no reference. If this is true, this is a serious shortcoming of
+ Alsa. If not true, then PortMidi has a serious shortcoming -- it
+ should be scheduling relative to Alsa's time reference. */
+}
+
+
+static void handle_event(snd_seq_event_t *ev)
+{
+ int device_id = ev->dest.port;
+ PmInternal *midi = descriptors[device_id].internalDescriptor;
+ PmEvent pm_ev;
+ PmTimeProcPtr time_proc = midi->time_proc;
+ PmTimestamp timestamp;
+
+ /* time stamp should be in ticks, using our queue where 1 tick = 1ms */
+ assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
+
+ /* if no time_proc, just return "native" ticks (ms) */
+ if (time_proc == NULL) {
+ timestamp = ev->time.tick;
+ } else { /* translate time to time_proc basis */
+ snd_seq_queue_status_t *queue_status;
+ snd_seq_queue_status_alloca(&queue_status);
+ snd_seq_get_queue_status(seq, queue, queue_status);
+ /* return (now - alsa_now) + alsa_timestamp */
+ timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
+ snd_seq_queue_status_get_tick_time(queue_status);
+ }
+ pm_ev.timestamp = timestamp;
+ switch (ev->type) {
+ case SND_SEQ_EVENT_NOTEON:
+ pm_ev.message = Pm_Message(0x90 | ev->data.note.channel,
+ ev->data.note.note & 0x7f,
+ ev->data.note.velocity & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_NOTEOFF:
+ pm_ev.message = Pm_Message(0x80 | ev->data.note.channel,
+ ev->data.note.note & 0x7f,
+ ev->data.note.velocity & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_KEYPRESS:
+ pm_ev.message = Pm_Message(0xa0 | ev->data.note.channel,
+ ev->data.note.note & 0x7f,
+ ev->data.note.velocity & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CONTROLLER:
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param & 0x7f,
+ ev->data.control.value & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_PGMCHANGE:
+ pm_ev.message = Pm_Message(0xc0 | ev->data.note.channel,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CHANPRESS:
+ pm_ev.message = Pm_Message(0xd0 | ev->data.note.channel,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_PITCHBEND:
+ pm_ev.message = Pm_Message(0xe0 | ev->data.note.channel,
+ (ev->data.control.value + 0x2000) & 0x7f,
+ ((ev->data.control.value + 0x2000) >> 7) & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CONTROL14:
+ if (ev->data.control.param < 0x20) {
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param,
+ (ev->data.control.value >> 7) & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param + 0x20,
+ ev->data.control.value & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ } else {
+ pm_ev.message = Pm_Message(0xb0 | ev->data.note.channel,
+ ev->data.control.param & 0x7f,
+ ev->data.control.value & 0x7f);
+
+ pm_read_short(midi, &pm_ev);
+ }
+ break;
+ case SND_SEQ_EVENT_SONGPOS:
+ pm_ev.message = Pm_Message(0xf2,
+ ev->data.control.value & 0x7f,
+ (ev->data.control.value >> 7) & 0x7f);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_SONGSEL:
+ pm_ev.message = Pm_Message(0xf3,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_QFRAME:
+ pm_ev.message = Pm_Message(0xf1,
+ ev->data.control.value & 0x7f, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_START:
+ pm_ev.message = Pm_Message(0xfa, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CONTINUE:
+ pm_ev.message = Pm_Message(0xfb, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_STOP:
+ pm_ev.message = Pm_Message(0xfc, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_CLOCK:
+ pm_ev.message = Pm_Message(0xf8, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_TUNE_REQUEST:
+ pm_ev.message = Pm_Message(0xf6, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_RESET:
+ pm_ev.message = Pm_Message(0xff, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_SENSING:
+ pm_ev.message = Pm_Message(0xfe, 0, 0);
+ pm_read_short(midi, &pm_ev);
+ break;
+ case SND_SEQ_EVENT_SYSEX: {
+ const BYTE *ptr = (const BYTE *) ev->data.ext.ptr;
+ int i;
+ long msg = 0;
+ int shift = 0;
+ if (!(midi->filters & PM_FILT_SYSEX)) {
+ for (i = 0; i < ev->data.ext.len; i++) {
+ pm_read_byte(midi, *ptr++, timestamp);
+ }
+ }
+ break;
+ }
+ }
+}
+
+static PmError alsa_poll(PmInternal *midi)
+{
+ snd_seq_event_t *ev;
+ while (snd_seq_event_input(seq, &ev) >= 0) {
+ if (ev) {
+ handle_event(ev);
+ }
+ }
+ return pmNoError;
+}
+
+
+static unsigned int alsa_has_host_error(PmInternal *midi)
+{
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ return desc->error;
+}
+
+
+static void alsa_get_host_error(PmInternal *midi, char *msg, unsigned int len)
+{
+ alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
+ int err = (pm_hosterror || desc->error);
+ get_alsa_error_text(msg, len, err);
+}
+
+
+pm_fns_node pm_linuxalsa_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ alsa_synchronize,
+ alsa_in_open,
+ alsa_abort,
+ alsa_in_close,
+ alsa_poll,
+ alsa_has_host_error,
+ alsa_get_host_error
+};
+
+pm_fns_node pm_linuxalsa_out_dictionary = {
+ alsa_write_short,
+ alsa_sysex,
+ alsa_sysex,
+ alsa_write_byte,
+ alsa_write_short, /* short realtime message */
+ alsa_write_flush,
+ alsa_synchronize,
+ alsa_out_open,
+ alsa_abort,
+ alsa_out_close,
+ none_poll,
+ alsa_has_host_error,
+ alsa_get_host_error
+};
+
+
+/* pm_strdup -- copy a string to the heap. Use this rather than strdup so
+ * that we call pm_alloc, not malloc. This allows portmidi to avoid
+ * malloc which might cause priority inversion. Probably ALSA is going
+ * to call malloc anyway, so this extra work here may be pointless.
+ */
+char *pm_strdup(const char *s)
+{
+ int len = strlen(s);
+ char *dup = (char *) pm_alloc(len + 1);
+ strcpy(dup, s);
+ return dup;
+}
+
+
+PmError pm_linuxalsa_init( void )
+{
+ int err;
+ snd_seq_client_info_t *cinfo;
+ snd_seq_port_info_t *pinfo;
+ unsigned int caps;
+
+ err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
+ if (err < 0) return;
+
+ snd_seq_client_info_alloca(&cinfo);
+ snd_seq_port_info_alloca(&pinfo);
+
+ snd_seq_client_info_set_client(cinfo, -1);
+ while (snd_seq_query_next_client(seq, cinfo) == 0) {
+ snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
+ snd_seq_port_info_set_port(pinfo, -1);
+ while (snd_seq_query_next_port(seq, pinfo) == 0) {
+ if (snd_seq_port_info_get_client(pinfo) == SND_SEQ_CLIENT_SYSTEM)
+ continue; /* ignore Timer and Announce ports on client 0 */
+ caps = snd_seq_port_info_get_capability(pinfo);
+ if (!(caps & (SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE)))
+ continue; /* ignore if you cannot read or write port */
+ if (caps & SND_SEQ_PORT_CAP_SUBS_WRITE) {
+ if (pm_default_output_device_id == -1)
+ pm_default_output_device_id = pm_descriptor_index;
+ pm_add_device("ALSA",
+ pm_strdup(snd_seq_port_info_get_name(pinfo)),
+ FALSE,
+ MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
+ snd_seq_port_info_get_port(pinfo)),
+ &pm_linuxalsa_out_dictionary);
+ }
+ if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
+ if (pm_default_input_device_id == -1)
+ pm_default_input_device_id = pm_descriptor_index;
+ pm_add_device("ALSA",
+ pm_strdup(snd_seq_port_info_get_name(pinfo)),
+ TRUE,
+ MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
+ snd_seq_port_info_get_port(pinfo)),
+ &pm_linuxalsa_in_dictionary);
+ }
+ }
+ }
+}
+
+
+void pm_linuxalsa_term(void)
+{
+ snd_seq_close(seq);
+}
diff --git a/pd/portmidi/pm_linux/pmlinuxalsa.h b/pd/portmidi/pm_linux/pmlinuxalsa.h
new file mode 100644
index 00000000..d4bff16c
--- /dev/null
+++ b/pd/portmidi/pm_linux/pmlinuxalsa.h
@@ -0,0 +1,6 @@
+/* pmlinuxalsa.h -- system-specific definitions */
+
+PmError pm_linuxalsa_init(void);
+void pm_linuxalsa_term(void);
+
+
diff --git a/pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj b/pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj
new file mode 100644
index 00000000..f35008ed
--- /dev/null
+++ b/pd/portmidi/pm_mac/pm_mac.pbproj/project.pbxproj
@@ -0,0 +1,1324 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 38;
+ objects = {
+ 3D9CFCAA067657D50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3D9CFCAB067657D50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXResourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3D9CFCAC067657D50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3D9CFCB8067657FA0002CE69,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3D9CFCAD067657D50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3D9CFCBA067658A90002CE69,
+ 3D9CFCBB067658AA0002CE69,
+ 3D9CFCBC067658AA0002CE69,
+ 3D9CFCBD067659FE0002CE69,
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3D9CFCAE067657D50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3D9CFCAF067657D50002CE69 = {
+ buildPhases = (
+ 3D9CFCAA067657D50002CE69,
+ 3D9CFCAB067657D50002CE69,
+ 3D9CFCAC067657D50002CE69,
+ 3D9CFCAD067657D50002CE69,
+ 3D9CFCAE067657D50002CE69,
+ );
+ buildSettings = {
+ OPTIMIZATION_CFLAGS = "";
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = latency;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ WRAPPER_EXTENSION = app;
+ };
+ comments = "a test program to measure OS latency";
+ dependencies = (
+ );
+ isa = PBXApplicationTarget;
+ name = latency;
+ productInstallPath = "$(USER_APPS_DIR)";
+ productName = latency;
+ productReference = 3D9CFCB0067657D50002CE69;
+ productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string></string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string></string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.1d1</string>
+</dict>
+</plist>
+";
+ };
+ 3D9CFCB0067657D50002CE69 = {
+ isa = PBXApplicationReference;
+ path = latency.app;
+ refType = 3;
+ };
+ 3D9CFCB8067657FA0002CE69 = {
+ fileRef = 3DA774110663E9DA0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3D9CFCBA067658A90002CE69 = {
+ fileRef = 3DA774480663EAE60002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3D9CFCBB067658AA0002CE69 = {
+ fileRef = 3DA7744E0663EBB20002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3D9CFCBC067658AA0002CE69 = {
+ fileRef = 3DA774500663EBBF0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3D9CFCBD067659FE0002CE69 = {
+ fileRef = 5851D5380479562300EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712AE0664C7C50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712AF0664C7C50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXResourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712B00664C7C50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA712B90664C8490002CE69,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712B10664C7C50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA712B60664C8400002CE69,
+ 3DA712B70664C8400002CE69,
+ 3DA712B80664C8410002CE69,
+ 3DA712BA0664C8A40002CE69,
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712B20664C7C50002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712B30664C7C50002CE69 = {
+ buildPhases = (
+ 3DA712AE0664C7C50002CE69,
+ 3DA712AF0664C7C50002CE69,
+ 3DA712B00664C7C50002CE69,
+ 3DA712B10664C7C50002CE69,
+ 3DA712B20664C7C50002CE69,
+ );
+ buildSettings = {
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = midithread;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ WRAPPER_EXTENSION = app;
+ };
+ comments = "Demonstrates the use of a low-latency thread for midi processing";
+ dependencies = (
+ );
+ isa = PBXApplicationTarget;
+ name = midithread;
+ productInstallPath = "$(USER_APPS_DIR)";
+ productName = midithread;
+ productReference = 3DA712B40664C7C50002CE69;
+ productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string></string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string></string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.1d1</string>
+</dict>
+</plist>
+";
+ };
+ 3DA712B40664C7C50002CE69 = {
+ isa = PBXApplicationReference;
+ path = midithread.app;
+ refType = 3;
+ };
+ 3DA712B60664C8400002CE69 = {
+ fileRef = 3DA774480663EAE60002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712B70664C8400002CE69 = {
+ fileRef = 3DA7744E0663EBB20002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712B80664C8410002CE69 = {
+ fileRef = 3DA774500663EBBF0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712B90664C8490002CE69 = {
+ fileRef = 3DA774160663E9DA0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712BA0664C8A40002CE69 = {
+ fileRef = 5851D5380479562300EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712BB0664C9B40002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712BC0664C9B40002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXResourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712BD0664C9B40002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA712C40664C9C40002CE69,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712BE0664C9B40002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA712C30664C9C20002CE69,
+ 3DA712C50664C9C70002CE69,
+ 3DA712C60664C9C70002CE69,
+ 3DA712C70664C9C80002CE69,
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712BF0664C9B40002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712C00664C9B40002CE69 = {
+ buildPhases = (
+ 3DA712BB0664C9B40002CE69,
+ 3DA712BC0664C9B40002CE69,
+ 3DA712BD0664C9B40002CE69,
+ 3DA712BE0664C9B40002CE69,
+ 3DA712BF0664C9B40002CE69,
+ );
+ buildSettings = {
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = midithru;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ WRAPPER_EXTENSION = app;
+ };
+ comments = "Demonstrates midi thru processing in a low-priority thread";
+ dependencies = (
+ );
+ isa = PBXApplicationTarget;
+ name = midithru;
+ productInstallPath = "$(USER_APPS_DIR)";
+ productName = midithru;
+ productReference = 3DA712C10664C9B40002CE69;
+ productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string></string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string></string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.1d1</string>
+</dict>
+</plist>
+";
+ };
+ 3DA712C10664C9B40002CE69 = {
+ isa = PBXApplicationReference;
+ path = midithru.app;
+ refType = 3;
+ };
+ 3DA712C30664C9C20002CE69 = {
+ fileRef = 5851D5380479562300EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712C40664C9C40002CE69 = {
+ fileRef = 3DA7741B0663E9DA0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712C50664C9C70002CE69 = {
+ fileRef = 3DA774480663EAE60002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712C60664C9C70002CE69 = {
+ fileRef = 3DA7744E0663EBB20002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712C70664C9C80002CE69 = {
+ fileRef = 3DA774500663EBBF0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712C80664CB1B0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712C90664CB1B0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXResourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712CA0664CB1B0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA712D10664CB430002CE69,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712CB0664CB1B0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA712D00664CB3F0002CE69,
+ 3DA712D20664CB470002CE69,
+ 3DA712D30664CB480002CE69,
+ 3DA712D40664CB480002CE69,
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712CC0664CB1B0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA712CD0664CB1B0002CE69 = {
+ buildPhases = (
+ 3DA712C80664CB1B0002CE69,
+ 3DA712C90664CB1B0002CE69,
+ 3DA712CA0664CB1B0002CE69,
+ 3DA712CB0664CB1B0002CE69,
+ 3DA712CC0664CB1B0002CE69,
+ );
+ buildSettings = {
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = sysex;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ WRAPPER_EXTENSION = app;
+ };
+ comments = "System exclusive input and output test";
+ dependencies = (
+ );
+ isa = PBXApplicationTarget;
+ name = sysex;
+ productInstallPath = "$(USER_APPS_DIR)";
+ productName = sysex;
+ productReference = 3DA712CE0664CB1B0002CE69;
+ productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string></string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string></string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.1d1</string>
+</dict>
+</plist>
+";
+ };
+ 3DA712CE0664CB1B0002CE69 = {
+ isa = PBXApplicationReference;
+ path = sysex.app;
+ refType = 3;
+ };
+ 3DA712D00664CB3F0002CE69 = {
+ fileRef = 5851D5380479562300EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712D10664CB430002CE69 = {
+ fileRef = 3DA774210663E9DA0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712D20664CB470002CE69 = {
+ fileRef = 3DA774480663EAE60002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712D30664CB480002CE69 = {
+ fileRef = 3DA7744E0663EBB20002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA712D40664CB480002CE69 = {
+ fileRef = 3DA774500663EBBF0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA774070663E8EB0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA774080663E8EB0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXResourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA774090663E8EB0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA774390663E9DA0002CE69,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA7740A0663E8EB0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ 3DA774470663EA6A0002CE69,
+ 3DA774490663EAE60002CE69,
+ 3DA7744F0663EBB20002CE69,
+ 3DA774510663EBBF0002CE69,
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA7740B0663E8EB0002CE69 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3DA7740C0663E8EB0002CE69 = {
+ buildPhases = (
+ 3DA774070663E8EB0002CE69,
+ 3DA774080663E8EB0002CE69,
+ 3DA774090663E8EB0002CE69,
+ 3DA7740A0663E8EB0002CE69,
+ 3DA7740B0663E8EB0002CE69,
+ );
+ buildSettings = {
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = test;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ WRAPPER_EXTENSION = app;
+ };
+ comments = "A PortMidi test program";
+ dependencies = (
+ );
+ isa = PBXApplicationTarget;
+ name = test;
+ productInstallPath = "$(USER_APPS_DIR)";
+ productName = test;
+ productReference = 3DA7740D0663E8EB0002CE69;
+ productSettingsXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
+<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
+<plist version=\"1.0\">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string></string>
+ <key>CFBundleGetInfoString</key>
+ <string></string>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundleIdentifier</key>
+ <string></string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string></string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>0.0.1d1</string>
+</dict>
+</plist>
+";
+ };
+ 3DA7740D0663E8EB0002CE69 = {
+ isa = PBXApplicationReference;
+ path = test.app;
+ refType = 3;
+ };
+ 3DA774100663E9DA0002CE69 = {
+ children = (
+ 3DA774110663E9DA0002CE69,
+ 3DA774120663E9DA0002CE69,
+ 3DA774130663E9DA0002CE69,
+ 3DA774140663E9DA0002CE69,
+ 3DA774150663E9DA0002CE69,
+ 3DA774160663E9DA0002CE69,
+ 3DA774170663E9DA0002CE69,
+ 3DA774180663E9DA0002CE69,
+ 3DA774190663E9DA0002CE69,
+ 3DA7741A0663E9DA0002CE69,
+ 3DA7741B0663E9DA0002CE69,
+ 3DA7741C0663E9DA0002CE69,
+ 3DA7741D0663E9DA0002CE69,
+ 3DA7741E0663E9DA0002CE69,
+ 3DA7741F0663E9DA0002CE69,
+ 3DA774200663E9DA0002CE69,
+ 3DA774210663E9DA0002CE69,
+ 3DA774220663E9DA0002CE69,
+ 3DA774230663E9DA0002CE69,
+ 3DA774240663E9DA0002CE69,
+ 3DA774250663E9DA0002CE69,
+ 3DA774260663E9DA0002CE69,
+ 3DA774270663E9DA0002CE69,
+ 3DA774280663E9DA0002CE69,
+ 3DA774290663E9DA0002CE69,
+ 3DA7742A0663E9DA0002CE69,
+ 3DA7742B0663E9DA0002CE69,
+ );
+ isa = PBXGroup;
+ name = pm_test;
+ path = /Users/rbd/portmidi/pm_test;
+ refType = 0;
+ };
+ 3DA774110663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = latency.c;
+ refType = 4;
+ };
+ 3DA774120663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = latency.dsp;
+ refType = 4;
+ };
+ 3DA774130663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = latency.plg;
+ refType = 4;
+ };
+ 3DA774140663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = latencyDebug;
+ refType = 4;
+ };
+ 3DA774150663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = latencyRelease;
+ refType = 4;
+ };
+ 3DA774160663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithread.c;
+ refType = 4;
+ };
+ 3DA774170663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithread.dsp;
+ refType = 4;
+ };
+ 3DA774180663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithread.plg;
+ refType = 4;
+ };
+ 3DA774190663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = midithreadDebug;
+ refType = 4;
+ };
+ 3DA7741A0663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = midithreadRelease;
+ refType = 4;
+ };
+ 3DA7741B0663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithru.c;
+ refType = 4;
+ };
+ 3DA7741C0663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithru.dsp;
+ refType = 4;
+ };
+ 3DA7741D0663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithru.dsw;
+ refType = 4;
+ };
+ 3DA7741E0663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = midithru.plg;
+ refType = 4;
+ };
+ 3DA7741F0663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = midithruDebug;
+ refType = 4;
+ };
+ 3DA774200663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = midithruRelease;
+ refType = 4;
+ };
+ 3DA774210663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = sysex.c;
+ refType = 4;
+ };
+ 3DA774220663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = sysex.dsp;
+ refType = 4;
+ };
+ 3DA774230663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = sysex.plg;
+ refType = 4;
+ };
+ 3DA774240663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = sysexDebug;
+ refType = 4;
+ };
+ 3DA774250663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = sysexRelease;
+ refType = 4;
+ };
+ 3DA774260663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = test.c;
+ refType = 4;
+ };
+ 3DA774270663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = test.dsp;
+ refType = 4;
+ };
+ 3DA774280663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = test.plg;
+ refType = 4;
+ };
+ 3DA774290663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = testDebug;
+ refType = 4;
+ };
+ 3DA7742A0663E9DA0002CE69 = {
+ children = (
+ );
+ isa = PBXGroup;
+ path = testRelease;
+ refType = 4;
+ };
+ 3DA7742B0663E9DA0002CE69 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = txdata.syx;
+ refType = 4;
+ };
+ 3DA774390663E9DA0002CE69 = {
+ fileRef = 3DA774260663E9DA0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA774470663EA6A0002CE69 = {
+ fileRef = 5851D5380479562300EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA774480663EAE60002CE69 = {
+ isa = PBXFrameworkReference;
+ name = CoreMIDI.framework;
+ path = /System/Library/Frameworks/CoreMIDI.framework;
+ refType = 0;
+ };
+ 3DA774490663EAE60002CE69 = {
+ fileRef = 3DA774480663EAE60002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA7744E0663EBB20002CE69 = {
+ isa = PBXFrameworkReference;
+ name = CoreFoundation.framework;
+ path = /System/Library/Frameworks/CoreFoundation.framework;
+ refType = 0;
+ };
+ 3DA7744F0663EBB20002CE69 = {
+ fileRef = 3DA7744E0663EBB20002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 3DA774500663EBBF0002CE69 = {
+ isa = PBXFrameworkReference;
+ name = CoreAudio.framework;
+ path = /System/Library/Frameworks/CoreAudio.framework;
+ refType = 0;
+ };
+ 3DA774510663EBBF0002CE69 = {
+ fileRef = 3DA774500663EBBF0002CE69;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+//3D0
+//3D1
+//3D2
+//3D3
+//3D4
+//580
+//581
+//582
+//583
+//584
+ 5851D5140479552300EE98CD = {
+ children = (
+ 5851D51D047955A100EE98CD,
+ 5851D51C0479553600EE98CD,
+ 5851D51B0479553500EE98CD,
+ 5851D51A0479553300EE98CD,
+ 5851D5190479552E00EE98CD,
+ 5851D5390479562300EE98CD,
+ 3DA774100663E9DA0002CE69,
+ 3DA774480663EAE60002CE69,
+ 3DA7744E0663EBB20002CE69,
+ 3DA774500663EBBF0002CE69,
+ );
+ isa = PBXGroup;
+ refType = 4;
+ };
+ 5851D5160479552300EE98CD = {
+ buildRules = (
+ );
+ buildSettings = {
+ COPY_PHASE_STRIP = NO;
+ };
+ isa = PBXBuildStyle;
+ name = Development;
+ };
+ 5851D5170479552300EE98CD = {
+ buildRules = (
+ );
+ buildSettings = {
+ COPY_PHASE_STRIP = YES;
+ };
+ isa = PBXBuildStyle;
+ name = Deployment;
+ };
+ 5851D5180479552300EE98CD = {
+ buildStyles = (
+ 5851D5160479552300EE98CD,
+ 5851D5170479552300EE98CD,
+ );
+ hasScannedForEncodings = 1;
+ isa = PBXProject;
+ mainGroup = 5851D5140479552300EE98CD;
+ productRefGroup = 5851D5390479562300EE98CD;
+ projectDirPath = "";
+ targets = (
+ 5851D5370479562300EE98CD,
+ 3DA7740C0663E8EB0002CE69,
+ 3DA712B30664C7C50002CE69,
+ 3DA712C00664C9B40002CE69,
+ 3DA712CD0664CB1B0002CE69,
+ 3D9CFCAF067657D50002CE69,
+ );
+ };
+ 5851D5190479552E00EE98CD = {
+ children = (
+ 5851D5310479560B00EE98CD,
+ 5851D5320479560B00EE98CD,
+ 5851D5730479613900EE98CD,
+ 5851D5740479613900EE98CD,
+ );
+ isa = PBXGroup;
+ name = pm_mac;
+ refType = 4;
+ };
+ 5851D51A0479553300EE98CD = {
+ children = (
+ 5851D52C047955FD00EE98CD,
+ 5851D52D047955FD00EE98CD,
+ 5851D52E047955FD00EE98CD,
+ 5851D52F047955FD00EE98CD,
+ 5851D530047955FD00EE98CD,
+ );
+ isa = PBXGroup;
+ name = pm_win;
+ refType = 4;
+ };
+ 5851D51B0479553500EE98CD = {
+ children = (
+ 5851D528047955D700EE98CD,
+ 5851D529047955D700EE98CD,
+ 5851D52A047955D700EE98CD,
+ 5851D52B047955D700EE98CD,
+ );
+ isa = PBXGroup;
+ name = pm_linux;
+ refType = 4;
+ };
+ 5851D51C0479553600EE98CD = {
+ children = (
+ 5851D523047955C800EE98CD,
+ 5851D524047955C800EE98CD,
+ 5851D525047955C800EE98CD,
+ 5851D526047955C800EE98CD,
+ 5851D527047955C800EE98CD,
+ );
+ isa = PBXGroup;
+ name = pm_common;
+ path = "";
+ refType = 2;
+ };
+ 5851D51D047955A100EE98CD = {
+ children = (
+ 5851D51E047955B800EE98CD,
+ 5851D51F047955B800EE98CD,
+ 5851D520047955B800EE98CD,
+ 5851D521047955B800EE98CD,
+ 58ED5B81047D41DB00B92E62,
+ 5851D522047955B800EE98CD,
+ );
+ isa = PBXGroup;
+ name = porttime;
+ refType = 4;
+ };
+ 5851D51E047955B800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = porttime.c;
+ path = ../porttime/porttime.c;
+ refType = 2;
+ };
+ 5851D51F047955B800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = porttime.h;
+ path = ../porttime/porttime.h;
+ refType = 2;
+ };
+ 5851D520047955B800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = ptlinux.c;
+ path = ../porttime/ptlinux.c;
+ refType = 2;
+ };
+ 5851D521047955B800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = ptmacosx_cf.c;
+ path = ../porttime/ptmacosx_cf.c;
+ refType = 2;
+ };
+ 5851D522047955B800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = ptwinmm.c;
+ path = ../porttime/ptwinmm.c;
+ refType = 2;
+ };
+ 5851D523047955C800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pminternal.h;
+ path = ../pm_common/pminternal.h;
+ refType = 2;
+ };
+ 5851D524047955C800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmutil.c;
+ path = ../pm_common/pmutil.c;
+ refType = 2;
+ };
+ 5851D525047955C800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmutil.h;
+ path = ../pm_common/pmutil.h;
+ refType = 2;
+ };
+ 5851D526047955C800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = portmidi.c;
+ path = ../pm_common/portmidi.c;
+ refType = 2;
+ };
+ 5851D527047955C800EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = portmidi.h;
+ path = ../pm_common/portmidi.h;
+ refType = 2;
+ };
+ 5851D528047955D700EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmlinux.c;
+ path = ../pm_linux/pmlinux.c;
+ refType = 2;
+ };
+ 5851D529047955D700EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmlinux.h;
+ path = ../pm_linux/pmlinux.h;
+ refType = 2;
+ };
+ 5851D52A047955D700EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmlinuxalsa.c;
+ path = ../pm_linux/pmlinuxalsa.c;
+ refType = 2;
+ };
+ 5851D52B047955D700EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmlinuxalsa.h;
+ path = ../pm_linux/pmlinuxalsa.h;
+ refType = 2;
+ };
+ 5851D52C047955FD00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmdll.c;
+ path = ../pm_win/pmdll.c;
+ refType = 2;
+ };
+ 5851D52D047955FD00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmdll.h;
+ path = ../pm_win/pmdll.h;
+ refType = 2;
+ };
+ 5851D52E047955FD00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmwin.c;
+ path = ../pm_win/pmwin.c;
+ refType = 2;
+ };
+ 5851D52F047955FD00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmwinmm.c;
+ path = ../pm_win/pmwinmm.c;
+ refType = 2;
+ };
+ 5851D530047955FD00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmwinmm.h;
+ path = ../pm_win/pmwinmm.h;
+ refType = 2;
+ };
+ 5851D5310479560B00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = pmmacosxcm.c;
+ refType = 2;
+ };
+ 5851D5320479560B00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ path = pmmacosxcm.h;
+ refType = 2;
+ };
+ 5851D5330479562300EE98CD = {
+ buildActionMask = 2147483647;
+ files = (
+ 5851D53B0479564000EE98CD,
+ 5851D53D0479564100EE98CD,
+ 5851D53F0479564100EE98CD,
+ 5851D5420479564600EE98CD,
+ 5851D5430479564F00EE98CD,
+ 5851D5750479613900EE98CD,
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 5851D5340479562300EE98CD = {
+ buildActionMask = 2147483647;
+ files = (
+ 5851D53C0479564000EE98CD,
+ 5851D53E0479564100EE98CD,
+ 5851D5400479564400EE98CD,
+ 5851D5760479613900EE98CD,
+ 58ED5C7A047D694900B92E62,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 5851D5350479562300EE98CD = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 5851D5360479562300EE98CD = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 5851D5370479562300EE98CD = {
+ buildPhases = (
+ 5851D5330479562300EE98CD,
+ 5851D5340479562300EE98CD,
+ 5851D5350479562300EE98CD,
+ 5851D5360479562300EE98CD,
+ );
+ buildSettings = {
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ LIBRARY_STYLE = STATIC;
+ OTHER_CFLAGS = "-DPM_CHECK_ERRORS -DDEBUG";
+ OTHER_LDFLAGS = "";
+ OTHER_LIBTOOL_FLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = libportmidi.a;
+ REZ_EXECUTABLE = YES;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ };
+ dependencies = (
+ );
+ isa = PBXLibraryTarget;
+ name = libportmidi.a;
+ productInstallPath = /usr/local/lib;
+ productName = libportmidi.a;
+ productReference = 5851D5380479562300EE98CD;
+ };
+ 5851D5380479562300EE98CD = {
+ isa = PBXLibraryReference;
+ path = libportmidi.a;
+ refType = 3;
+ };
+ 5851D5390479562300EE98CD = {
+ children = (
+ 5851D5380479562300EE98CD,
+ 3DA7740D0663E8EB0002CE69,
+ 3DA712B40664C7C50002CE69,
+ 3DA712C10664C9B40002CE69,
+ 3DA712CE0664CB1B0002CE69,
+ 3D9CFCB0067657D50002CE69,
+ );
+ isa = PBXGroup;
+ name = Products;
+ refType = 4;
+ };
+ 5851D53B0479564000EE98CD = {
+ fileRef = 5851D523047955C800EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D53C0479564000EE98CD = {
+ fileRef = 5851D524047955C800EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D53D0479564100EE98CD = {
+ fileRef = 5851D525047955C800EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D53E0479564100EE98CD = {
+ fileRef = 5851D526047955C800EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D53F0479564100EE98CD = {
+ fileRef = 5851D527047955C800EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5400479564400EE98CD = {
+ fileRef = 5851D5310479560B00EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5420479564600EE98CD = {
+ fileRef = 5851D5320479560B00EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5430479564F00EE98CD = {
+ fileRef = 5851D51F047955B800EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5730479613900EE98CD = {
+ fileEncoding = 4;
+ isa = PBXFileReference;
+ path = pmmac.h;
+ refType = 2;
+ };
+ 5851D5740479613900EE98CD = {
+ fileEncoding = 4;
+ isa = PBXFileReference;
+ path = pmmac.c;
+ refType = 2;
+ };
+ 5851D5750479613900EE98CD = {
+ fileRef = 5851D5730479613900EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5760479613900EE98CD = {
+ fileRef = 5851D5740479613900EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 58ED5B81047D41DB00B92E62 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = ptmacosx_mach.c;
+ path = ../porttime/ptmacosx_mach.c;
+ refType = 2;
+ };
+ 58ED5C7A047D694900B92E62 = {
+ fileRef = 58ED5B81047D41DB00B92E62;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ };
+ rootObject = 5851D5180479552300EE98CD;
+}
diff --git a/pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser b/pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser
new file mode 100644
index 00000000..6f36b563
--- /dev/null
+++ b/pd/portmidi/pm_mac/pm_mac.pbproj/rbd.pbxuser
@@ -0,0 +1,1508 @@
+// !$*UTF8*$!
+{
+ 3D7903E9066690E00002CE69 = {
+ fRef = 3DA774260663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "test.c: TIME_START";
+ rLen = 17;
+ rLoc = 309;
+ rType = 0;
+ vrLen = 955;
+ vrLoc = 0;
+ };
+ 3D7903EC066690E00002CE69 = {
+ fRef = 5851D526047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "portmidi.c: pm_read_short";
+ rLen = 0;
+ rLoc = 24290;
+ rType = 0;
+ vrLen = 1795;
+ vrLoc = 23823;
+ };
+ 3D7903ED066690E00002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_in_open";
+ rLen = 6;
+ rLoc = 10561;
+ rType = 0;
+ vrLen = 1218;
+ vrLoc = 9520;
+ };
+ 3D7903EE066690E00002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 3992;
+ rType = 0;
+ vrLen = 1768;
+ vrLoc = 2466;
+ };
+ 3D7903EF066690E00002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: err";
+ rLen = 0;
+ rLoc = 17896;
+ rType = 0;
+ vrLen = 1449;
+ vrLoc = 16660;
+ };
+ 3D7903F0066690E00002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 3992;
+ rType = 0;
+ vrLen = 903;
+ vrLoc = 0;
+ };
+ 3D7903F1066690E00002CE69 = {
+ fRef = 3DA774260663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "test.c: TIME_START";
+ rLen = 17;
+ rLoc = 309;
+ rType = 0;
+ vrLen = 955;
+ vrLoc = 0;
+ };
+ 3D7903F2066690E00002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 4871;
+ rType = 0;
+ vrLen = 804;
+ vrLoc = 254;
+ };
+ 3D7903F3066690E00002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: SYSEX_BUFFER_SIZE";
+ rLen = 0;
+ rLoc = 1197;
+ rType = 0;
+ vrLen = 1450;
+ vrLoc = 884;
+ };
+ 3D7903F4066690E00002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 3483;
+ rType = 0;
+ vrLen = 1752;
+ vrLoc = 3284;
+ };
+ 3D9CFCA7067651E80002CE69 = {
+ fileReference = 5851D526047955C800EE98CD;
+ isa = PBXFileBreakpoint;
+ lineNumber = 396;
+ state = 1;
+ };
+ 3D9CFCA90676535F0002CE69 = {
+ fileReference = 5851D526047955C800EE98CD;
+ isa = PBXFileBreakpoint;
+ lineNumber = 390;
+ state = 1;
+ };
+ 3D9CFCAF067657D50002CE69 = {
+ activeExec = 0;
+ executables = (
+ 3D9CFCB1067657D50002CE69,
+ );
+ };
+ 3D9CFCB1067657D50002CE69 = {
+ activeArgIndex = 2147483647;
+ activeArgIndices = (
+ );
+ argumentStrings = (
+ );
+ configStateDict = {
+ };
+ debuggerPlugin = GDBDebugging;
+ enableDebugStr = 1;
+ environmentEntries = (
+ );
+ isa = PBXExecutable;
+ name = latency;
+ shlibInfoDictList = (
+ );
+ sourceDirectories = (
+ );
+ };
+ 3D9CFCBE06765A7F0002CE69 = {
+ fRef = 3D9CFCDB06765A7F0002CE69;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: 1";
+ rLen = 0;
+ rLoc = 0;
+ rType = 0;
+ vrLen = 1521;
+ vrLoc = 0;
+ };
+ 3D9CFCBF06765A7F0002CE69 = {
+ fRef = 5851D526047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "portmidi.c: 759";
+ rLen = 0;
+ rLoc = 24290;
+ rType = 0;
+ vrLen = 1709;
+ vrLoc = 25249;
+ };
+ 3D9CFCC006765A7F0002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_begin_sysex";
+ rLen = 0;
+ rLoc = 14525;
+ rType = 0;
+ vrLen = 1466;
+ vrLoc = 13829;
+ };
+ 3D9CFCC106765A7F0002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 3483;
+ rType = 0;
+ vrLen = 903;
+ vrLoc = 0;
+ };
+ 3D9CFCC206765A7F0002CE69 = {
+ exec = 3DA712CF0664CB1B0002CE69;
+ isa = PBXExecutableBookmark;
+ uiCtxt = {
+ buildSettingsExpandedSubviews = (
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ );
+ buildSettingsVisRect = "{{0, 0}, {746, 603}}";
+ };
+ };
+ 3D9CFCC306765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3DA712C00664C9B40002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}";
+ TOCViewSelectedItems = (
+ 3DA712BE0664C9B40002CE69,
+ );
+ };
+ };
+ 3D9CFCC406765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3DA712CD0664CB1B0002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {518, 119}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}";
+ TOCViewSelectedItems = (
+ 3DA712CB0664CB1B0002CE69,
+ );
+ };
+ };
+ 3D9CFCC506765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3D9CFCAF067657D50002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {518, 102}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}";
+ TOCViewSelectedItems = (
+ 3D9CFCAD067657D50002CE69,
+ );
+ };
+ };
+ 3D9CFCC606765A7F0002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_write_short";
+ rLen = 0;
+ rLoc = 13963;
+ rType = 0;
+ vrLen = 1621;
+ vrLoc = 13417;
+ };
+ 3D9CFCC706765A7F0002CE69 = {
+ fRef = 3D9CFCD906765A7F0002CE69;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: 1";
+ rLen = 0;
+ rLoc = 0;
+ rType = 0;
+ vrLen = 1521;
+ vrLoc = 0;
+ };
+ 3D9CFCC806765A7F0002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: readProc";
+ rLen = 0;
+ rLoc = 5664;
+ rType = 0;
+ vrLen = 1840;
+ vrLoc = 4759;
+ };
+ 3D9CFCC906765A7F0002CE69 = {
+ fRef = 3D9CFCDA06765A7F0002CE69;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: 1";
+ rLen = 0;
+ rLoc = 0;
+ rType = 0;
+ vrLen = 1521;
+ vrLoc = 0;
+ };
+ 3D9CFCCA06765A7F0002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 3483;
+ rType = 0;
+ vrLen = 1712;
+ vrLoc = 3270;
+ };
+ 3D9CFCCB06765A7F0002CE69 = {
+ fRef = 5851D526047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "portmidi.c: 759";
+ rLen = 0;
+ rLoc = 24290;
+ rType = 0;
+ vrLen = 1709;
+ vrLoc = 25249;
+ };
+ 3D9CFCCC06765A7F0002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_begin_sysex";
+ rLen = 0;
+ rLoc = 14525;
+ rType = 0;
+ vrLen = 1466;
+ vrLoc = 13829;
+ };
+ 3D9CFCCD06765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3D9CFCAF067657D50002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {491, 287}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {210, 603}}";
+ TOCViewSelectedItems = (
+ PBXTargetLdLinkerSettingsModule,
+ );
+ };
+ };
+ 3D9CFCCE06765A7F0002CE69 = {
+ fRef = 3DA774210663E9DA0002CE69;
+ isa = PBXTextBookmark;
+ name = "sysex.c: loopback_test";
+ rLen = 0;
+ rLoc = 3483;
+ rType = 0;
+ vrLen = 903;
+ vrLoc = 0;
+ };
+ 3D9CFCCF06765A7F0002CE69 = {
+ exec = 3DA712CF0664CB1B0002CE69;
+ isa = PBXExecutableBookmark;
+ uiCtxt = {
+ buildSettingsExpandedSubviews = (
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ );
+ buildSettingsVisRect = "{{0, 0}, {746, 603}}";
+ };
+ };
+ 3D9CFCD006765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3DA712C00664C9B40002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}";
+ TOCViewSelectedItems = (
+ 3DA712BE0664C9B40002CE69,
+ );
+ };
+ };
+ 3D9CFCD106765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3DA712CD0664CB1B0002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {517, 233}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}";
+ TOCViewSelectedItems = (
+ PBXTargetSummarySettingsModule,
+ );
+ };
+ };
+ 3D9CFCD206765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3D9CFCAF067657D50002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {517, 102}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}";
+ TOCViewSelectedItems = (
+ 3D9CFCAD067657D50002CE69,
+ );
+ };
+ };
+ 3D9CFCD306765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3DA712CD0664CB1B0002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}";
+ TOCViewSelectedItems = (
+ 3DA712CB0664CB1B0002CE69,
+ );
+ };
+ };
+ 3D9CFCD406765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3D9CFCAF067657D50002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {518, 102}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}";
+ TOCViewSelectedItems = (
+ 3D9CFCAD067657D50002CE69,
+ );
+ };
+ };
+ 3D9CFCD506765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3DA712CD0664CB1B0002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {518, 119}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {204, 603}}";
+ TOCViewSelectedItems = (
+ 3DA712CB0664CB1B0002CE69,
+ );
+ };
+ };
+ 3D9CFCD606765A7F0002CE69 = {
+ isa = PBXTargetBookmark;
+ trg = 3D9CFCAF067657D50002CE69;
+ uiCtxt = {
+ TOCViewDetailVisibleRect = "{{0, 0}, {517, 119}}";
+ TOCViewExpandedItems = (
+ "com.apple.target-editor-pane.settings",
+ "com.apple.target-editor-pane.settings.simple",
+ "com.apple.target-editor-pane.info-plist",
+ "com.apple.target-editor-pane.info-plist.simple",
+ "com.apple.target-editor-pane.buildphases",
+ );
+ TOCViewMasterVisibleRect = "{{0, 0}, {205, 603}}";
+ TOCViewSelectedItems = (
+ 3D9CFCAD067657D50002CE69,
+ );
+ };
+ };
+ 3D9CFCD706765A7F0002CE69 = {
+ fRef = 5851D526047955C800EE98CD;
+ isa = PBXTextBookmark;
+ rLen = 0;
+ rLoc = 396;
+ rType = 1;
+ };
+ 3D9CFCD806765A7F0002CE69 = {
+ fRef = 5851D526047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "portmidi.c: Pm_Write";
+ rLen = 0;
+ rLoc = 11148;
+ rType = 0;
+ vrLen = 876;
+ vrLoc = 11059;
+ };
+ 3D9CFCD906765A7F0002CE69 = {
+ isa = PBXFileReference;
+ name = pmmacosxcm.c;
+ path = "/Users/rbd/portmidi-27may04/pm_mac/pmmacosxcm.c";
+ refType = 0;
+ };
+ 3D9CFCDA06765A7F0002CE69 = {
+ isa = PBXFileReference;
+ name = pmmacosxcm.c;
+ path = "/Users/rbd/portmidi-27may04/pm_mac/pmmacosxcm.c";
+ refType = 0;
+ };
+ 3D9CFCDB06765A7F0002CE69 = {
+ isa = PBXFileReference;
+ name = pmmacosxcm.c;
+ path = "/Users/rbd/portmidi-27may04/pm_mac/pmmacosxcm.c";
+ refType = 0;
+ };
+ 3DA712B30664C7C50002CE69 = {
+ activeExec = 0;
+ executables = (
+ 3DA712B50664C7C50002CE69,
+ );
+ };
+ 3DA712B50664C7C50002CE69 = {
+ activeArgIndex = 2147483647;
+ activeArgIndices = (
+ );
+ argumentStrings = (
+ );
+ configStateDict = {
+ };
+ debuggerPlugin = GDBDebugging;
+ enableDebugStr = 1;
+ environmentEntries = (
+ );
+ isa = PBXExecutable;
+ name = midithread;
+ shlibInfoDictList = (
+ );
+ sourceDirectories = (
+ );
+ };
+ 3DA712C00664C9B40002CE69 = {
+ activeExec = 0;
+ executables = (
+ 3DA712C20664C9B40002CE69,
+ );
+ };
+ 3DA712C20664C9B40002CE69 = {
+ activeArgIndex = 2147483647;
+ activeArgIndices = (
+ );
+ argumentStrings = (
+ );
+ configStateDict = {
+ };
+ debuggerPlugin = GDBDebugging;
+ dylibVariantSuffix = "";
+ enableDebugStr = 1;
+ environmentEntries = (
+ );
+ isa = PBXExecutable;
+ name = midithru;
+ shlibInfoDictList = (
+ );
+ sourceDirectories = (
+ );
+ };
+ 3DA712CD0664CB1B0002CE69 = {
+ activeExec = 0;
+ executables = (
+ 3DA712CF0664CB1B0002CE69,
+ );
+ };
+ 3DA712CF0664CB1B0002CE69 = {
+ activeArgIndex = 2147483647;
+ activeArgIndices = (
+ );
+ argumentStrings = (
+ );
+ configStateDict = {
+ };
+ debuggerPlugin = GDBDebugging;
+ dylibVariantSuffix = "";
+ enableDebugStr = 1;
+ environmentEntries = (
+ );
+ isa = PBXExecutable;
+ name = sysex;
+ shlibInfoDictList = (
+ );
+ sourceDirectories = (
+ );
+ };
+ 3DA715FB066511460002CE69 = {
+ isa = PBXSymbolicBreakpoint;
+ state = 2;
+ symbolName = "";
+ };
+ 3DA7160D066548C80002CE69 = {
+ fileReference = 3DA774210663E9DA0002CE69;
+ isa = PBXFileBreakpoint;
+ lineNumber = 128;
+ state = 2;
+ };
+ 3DA7161606664C500002CE69 = {
+ fRef = 5851D527047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "portmidi.h: 174";
+ rLen = 7;
+ rLoc = 8744;
+ rType = 0;
+ vrLen = 466;
+ vrLoc = 8611;
+ };
+ 3DA7161A06664C500002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_write";
+ rLen = 0;
+ rLoc = 18396;
+ rType = 0;
+ vrLen = 605;
+ vrLoc = 11721;
+ };
+ 3DA7161B06664C500002CE69 = {
+ fRef = 5851D523047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "pminternal.h: 124";
+ rLen = 0;
+ rLoc = 6172;
+ rType = 0;
+ vrLen = 1926;
+ vrLoc = 4173;
+ };
+ 3DA7161C06664C500002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_write";
+ rLen = 0;
+ rLoc = 18396;
+ rType = 0;
+ vrLen = 1782;
+ vrLoc = 11284;
+ };
+ 3DA7161D06664C500002CE69 = {
+ fRef = 5851D523047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "pminternal.h: 124";
+ rLen = 0;
+ rLoc = 6093;
+ rType = 0;
+ vrLen = 1762;
+ vrLoc = 4480;
+ };
+ 3DA7161E06664C500002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_write";
+ rLen = 0;
+ rLoc = 18396;
+ rType = 0;
+ vrLen = 583;
+ vrLoc = 12640;
+ };
+ 3DA7161F06664C500002CE69 = {
+ fRef = 5851D523047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "pminternal.h: 124";
+ rLen = 0;
+ rLoc = 6093;
+ rType = 0;
+ vrLen = 606;
+ vrLoc = 6296;
+ };
+ 3DA7162006664C500002CE69 = {
+ fRef = 5851D527047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "portmidi.h: 174";
+ rLen = 7;
+ rLoc = 8744;
+ rType = 0;
+ vrLen = 466;
+ vrLoc = 8611;
+ };
+ 3DA7162106664C500002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_write";
+ rLen = 0;
+ rLoc = 14327;
+ rType = 0;
+ vrLen = 1769;
+ vrLoc = 10723;
+ };
+ 3DA7162206664C500002CE69 = {
+ fRef = 5851D523047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "pminternal.h: 133";
+ rLen = 17;
+ rLoc = 6353;
+ rType = 0;
+ vrLen = 2777;
+ vrLoc = 1303;
+ };
+ 3DA7162306664C500002CE69 = {
+ fRef = 5851D5310479560B00EE98CD;
+ isa = PBXTextBookmark;
+ name = "pmmacosxcm.c: midi_end_sysex";
+ rLen = 0;
+ rLoc = 17151;
+ rType = 0;
+ vrLen = 1470;
+ vrLoc = 14541;
+ };
+ 3DA7162406664C500002CE69 = {
+ fRef = 5851D523047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "pminternal.h: 133";
+ rLen = 17;
+ rLoc = 6353;
+ rType = 0;
+ vrLen = 2610;
+ vrLoc = 1569;
+ };
+ 3DA716320666705B0002CE69 = {
+ fileReference = 5851D526047955C800EE98CD;
+ isa = PBXFileBreakpoint;
+ lineNumber = 839;
+ state = 1;
+ };
+ 3DA7740C0663E8EB0002CE69 = {
+ activeExec = 0;
+ executables = (
+ 3DA7740E0663E8EB0002CE69,
+ );
+ };
+ 3DA7740E0663E8EB0002CE69 = {
+ activeArgIndex = 2147483647;
+ activeArgIndices = (
+ );
+ argumentStrings = (
+ );
+ configStateDict = {
+ };
+ debuggerPlugin = GDBDebugging;
+ dylibVariantSuffix = "";
+ enableDebugStr = 1;
+ environmentEntries = (
+ );
+ isa = PBXExecutable;
+ name = test;
+ shlibInfoDictList = (
+ );
+ sourceDirectories = (
+ );
+ };
+ 3DDE89D906667BAD0002CE69 = {
+ fRef = 5851D523047955C800EE98CD;
+ isa = PBXTextBookmark;
+ name = "pminternal.h: none_synchronize";
+ rLen = 0;
+ rLoc = 6900;
+ rType = 0;
+ vrLen = 2193;
+ vrLoc = 0;
+ };
+ 5851D5180479552300EE98CD = {
+ activeBuildStyle = 5851D5160479552300EE98CD;
+ activeExecutable = 3D9CFCB1067657D50002CE69;
+ activeTarget = 3D9CFCAF067657D50002CE69;
+ addToTargets = (
+ 3DA7740C0663E8EB0002CE69,
+ );
+ breakpoints = (
+ 3DA715FB066511460002CE69,
+ 3DA7160D066548C80002CE69,
+ 3DA716320666705B0002CE69,
+ 3D9CFCA7067651E80002CE69,
+ 3D9CFCA90676535F0002CE69,
+ );
+ executables = (
+ 3DA7740E0663E8EB0002CE69,
+ 3DA712B50664C7C50002CE69,
+ 3DA712C20664C9B40002CE69,
+ 3DA712CF0664CB1B0002CE69,
+ 3D9CFCB1067657D50002CE69,
+ );
+ perUserDictionary = {
+ PBXPerProjectTemplateStateSaveDate = 108416809;
+ "PBXTemplateGeometry-F5314676015831810DCA290F" = {
+ ContentSize = "{685, 434}";
+ LeftSlideOut = {
+ Collapsed = NO;
+ Frame = "{{0, 23}, {685, 411}}";
+ Split0 = {
+ ActiveTab = 2;
+ ActiveTabName = PBXBuildResultsModule;
+ Collapsed = NO;
+ Frame = "{{0, 0}, {685, 411}}";
+ Split0 = {
+ Frame = "{{0, 212}, {685, 199}}";
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {952, 321}}";
+ };
+ Tab1 = {
+ Debugger = {
+ Collapsed = NO;
+ Frame = "{{0, 0}, {781, 452}}";
+ Split0 = {
+ Frame = "{{0, 24}, {781, 428}}";
+ Split0 = {
+ Frame = "{{0, 0}, {383, 428}}";
+ };
+ Split1 = {
+ DebugVariablesTableConfiguration = (
+ Name,
+ 123,
+ Value,
+ 85,
+ Summary,
+ 155.123,
+ );
+ Frame = "{{392, 0}, {389, 428}}";
+ };
+ SplitCount = 2;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ Tab1 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ TabCount = 2;
+ TabsVisible = YES;
+ };
+ Frame = "{{0, 0}, {781, 452}}";
+ LauncherConfigVersion = 7;
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {685, 215}}";
+ LauncherConfigVersion = 3;
+ Runner = {
+ Frame = "{{0, 0}, {685, 215}}";
+ };
+ };
+ Tab3 = {
+ BuildMessageFrame = "{{0, 0}, {687, 117}}";
+ BuildTranscriptFrame = "{{0, 126}, {687, 58}}";
+ BuildTranscriptFrameExpanded = YES;
+ Frame = "{{0, 0}, {685, 206}}";
+ };
+ Tab4 = {
+ Frame = "{{0, 0}, {612, 295}}";
+ };
+ TabCount = 5;
+ TabsVisible = NO;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {300, 533}}";
+ GroupTreeTableConfiguration = (
+ TargetStatusColumn,
+ 18,
+ MainColumn,
+ 267,
+ );
+ };
+ Tab1 = {
+ ClassesFrame = "{{0, 0}, {280, 398}}";
+ ClassesTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXClassColumnIdentifier,
+ 237,
+ );
+ Frame = "{{0, 0}, {278, 659}}";
+ MembersFrame = "{{0, 407}, {280, 252}}";
+ MembersTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXMethodColumnIdentifier,
+ 236,
+ );
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {200, 100}}";
+ };
+ Tab3 = {
+ Frame = "{{0, 0}, {200, 100}}";
+ TargetTableConfiguration = (
+ ActiveObject,
+ 16,
+ ObjectNames,
+ 202.296,
+ );
+ };
+ Tab4 = {
+ BreakpointsTreeTableConfiguration = (
+ breakpointColumn,
+ 197,
+ enabledColumn,
+ 31,
+ );
+ Frame = "{{0, 0}, {250, 100}}";
+ };
+ TabCount = 5;
+ TabsVisible = NO;
+ };
+ NavBarShownByDefault = YES;
+ StatusViewVisible = YES;
+ Template = F5314676015831810DCA290F;
+ ToolbarVisible = YES;
+ WindowLocation = "{325, 250}";
+ };
+ "PBXTemplateGeometry-F5CA7EC9015C08ED0DCA290F" = {
+ ContentSize = "{665, 594}";
+ LeftSlideOut = {
+ Collapsed = NO;
+ Frame = "{{0, 0}, {665, 594}}";
+ Split0 = {
+ ActiveTab = 3;
+ ActiveTabName = PBXProjectFindModule;
+ Collapsed = NO;
+ Frame = "{{0, 0}, {665, 594}}";
+ Split0 = {
+ Frame = "{{0, 301}, {665, 293}}";
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {484, 208}}";
+ };
+ Tab1 = {
+ Debugger = {
+ Collapsed = NO;
+ Frame = "{{0, 0}, {664, 208}}";
+ Split0 = {
+ Frame = "{{0, 24}, {664, 184}}";
+ Split0 = {
+ Frame = "{{0, 0}, {325, 184}}";
+ };
+ Split1 = {
+ DebugVariablesTableConfiguration = (
+ Name,
+ 123,
+ Value,
+ 85,
+ Summary,
+ 96.123,
+ );
+ Frame = "{{334, 0}, {330, 184}}";
+ };
+ SplitCount = 2;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ Tab1 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ TabCount = 2;
+ TabsVisible = YES;
+ };
+ Frame = "{{0, 0}, {664, 208}}";
+ LauncherConfigVersion = 7;
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {664, 50}}";
+ LauncherConfigVersion = 3;
+ Runner = {
+ Frame = "{{0, 0}, {664, 50}}";
+ };
+ };
+ Tab3 = {
+ BuildMessageFrame = "{{0, 0}, {667, 206}}";
+ BuildTranscriptFrame = "{{0, 215}, {667, 85}}";
+ BuildTranscriptFrameExpanded = YES;
+ Frame = "{{0, 0}, {665, 298}}";
+ };
+ Tab4 = {
+ Frame = "{{0, 0}, {665, 295}}";
+ };
+ TabCount = 5;
+ TabsVisible = NO;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {313, 531}}";
+ GroupTreeTableConfiguration = (
+ TargetStatusColumn,
+ 18,
+ MainColumn,
+ 280,
+ );
+ };
+ Tab1 = {
+ ClassesFrame = "{{0, 0}, {280, 398}}";
+ ClassesTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXClassColumnIdentifier,
+ 237,
+ );
+ Frame = "{{0, 0}, {278, 659}}";
+ MembersFrame = "{{0, 407}, {280, 252}}";
+ MembersTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXMethodColumnIdentifier,
+ 236,
+ );
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {200, 100}}";
+ };
+ Tab3 = {
+ Frame = "{{0, 0}, {200, 557}}";
+ TargetTableConfiguration = (
+ ActiveObject,
+ 16,
+ ObjectNames,
+ 202.296,
+ );
+ };
+ Tab4 = {
+ BreakpointsTreeTableConfiguration = (
+ breakpointColumn,
+ 197,
+ enabledColumn,
+ 31,
+ );
+ Frame = "{{0, 0}, {250, 100}}";
+ };
+ TabCount = 5;
+ TabsVisible = NO;
+ };
+ NavBarShownByDefault = YES;
+ StatusViewVisible = NO;
+ Template = F5CA7EC9015C08ED0DCA290F;
+ ToolbarVisible = NO;
+ WindowLocation = "{335, 130}";
+ };
+ PBXWorkspaceContents = (
+ {
+ LeftSlideOut = {
+ Split0 = {
+ Split0 = {
+ NavContent0 = {
+ bookmark = 3D9CFCD606765A7F0002CE69;
+ history = (
+ 3DA7161606664C500002CE69,
+ 3DDE89D906667BAD0002CE69,
+ 3D7903E9066690E00002CE69,
+ 3D9CFCBE06765A7F0002CE69,
+ 3D9CFCBF06765A7F0002CE69,
+ 3D9CFCC006765A7F0002CE69,
+ 3D9CFCC106765A7F0002CE69,
+ 3D9CFCC206765A7F0002CE69,
+ 3D9CFCC306765A7F0002CE69,
+ 3D9CFCC406765A7F0002CE69,
+ 3D9CFCC506765A7F0002CE69,
+ );
+ prevStack = (
+ 3DA7161A06664C500002CE69,
+ 3DA7161B06664C500002CE69,
+ 3DA7161C06664C500002CE69,
+ 3DA7161D06664C500002CE69,
+ 3DA7161E06664C500002CE69,
+ 3DA7161F06664C500002CE69,
+ 3DA7162006664C500002CE69,
+ 3DA7162106664C500002CE69,
+ 3DA7162206664C500002CE69,
+ 3DA7162306664C500002CE69,
+ 3DA7162406664C500002CE69,
+ 3D7903EC066690E00002CE69,
+ 3D7903ED066690E00002CE69,
+ 3D7903EE066690E00002CE69,
+ 3D7903EF066690E00002CE69,
+ 3D7903F0066690E00002CE69,
+ 3D7903F1066690E00002CE69,
+ 3D7903F2066690E00002CE69,
+ 3D7903F3066690E00002CE69,
+ 3D7903F4066690E00002CE69,
+ 3D9CFCC606765A7F0002CE69,
+ 3D9CFCC706765A7F0002CE69,
+ 3D9CFCC806765A7F0002CE69,
+ 3D9CFCC906765A7F0002CE69,
+ 3D9CFCCA06765A7F0002CE69,
+ 3D9CFCCB06765A7F0002CE69,
+ 3D9CFCCC06765A7F0002CE69,
+ 3D9CFCCD06765A7F0002CE69,
+ 3D9CFCCE06765A7F0002CE69,
+ 3D9CFCCF06765A7F0002CE69,
+ 3D9CFCD006765A7F0002CE69,
+ 3D9CFCD106765A7F0002CE69,
+ 3D9CFCD206765A7F0002CE69,
+ 3D9CFCD306765A7F0002CE69,
+ 3D9CFCD406765A7F0002CE69,
+ 3D9CFCD506765A7F0002CE69,
+ );
+ };
+ NavCount = 1;
+ NavGeometry0 = {
+ Frame = "{{0, 0}, {761, 657}}";
+ NavBarVisible = YES;
+ };
+ NavSplitVertical = NO;
+ };
+ SplitCount = 1;
+ Tab1 = {
+ Debugger = {
+ Split0 = {
+ SplitCount = 2;
+ };
+ SplitCount = 1;
+ TabCount = 2;
+ };
+ LauncherConfigVersion = 7;
+ };
+ Tab2 = {
+ LauncherConfigVersion = 3;
+ Runner = {
+ };
+ };
+ TabCount = 5;
+ };
+ SplitCount = 1;
+ Tab1 = {
+ OptionsSetName = "Hierarchy, all classes";
+ };
+ TabCount = 5;
+ };
+ },
+ {
+ LeftSlideOut = {
+ Split0 = {
+ Split0 = {
+ NavContent0 = {
+ bookmark = 3D9CFCD806765A7F0002CE69;
+ history = (
+ 3D9CFCD706765A7F0002CE69,
+ );
+ };
+ NavCount = 1;
+ NavGeometry0 = {
+ Frame = "{{0, 0}, {620, 353}}";
+ NavBarVisible = YES;
+ };
+ NavSplitVertical = NO;
+ };
+ SplitCount = 1;
+ Tab1 = {
+ Debugger = {
+ Split0 = {
+ SplitCount = 2;
+ };
+ SplitCount = 1;
+ TabCount = 2;
+ };
+ LauncherConfigVersion = 7;
+ };
+ Tab2 = {
+ LauncherConfigVersion = 3;
+ Runner = {
+ };
+ };
+ TabCount = 5;
+ };
+ SplitCount = 1;
+ Tab1 = {
+ OptionsSetName = "Hierarchy, all classes";
+ };
+ TabCount = 5;
+ };
+ },
+ );
+ PBXWorkspaceGeometries = (
+ {
+ ContentSize = "{1024, 680}";
+ LeftSlideOut = {
+ ActiveTab = 3;
+ ActiveTabName = PBXTargetTreeModule;
+ Collapsed = NO;
+ Frame = "{{0, 23}, {1024, 657}}";
+ Split0 = {
+ Collapsed = NO;
+ Frame = "{{263, 0}, {761, 657}}";
+ Split0 = {
+ Frame = "{{0, 0}, {761, 657}}";
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {681, 289}}";
+ };
+ Tab1 = {
+ Debugger = {
+ Collapsed = NO;
+ Frame = "{{0, 0}, {681, 150}}";
+ Split0 = {
+ Frame = "{{0, 24}, {681, 126}}";
+ Split0 = {
+ Frame = "{{0, 0}, {333, 126}}";
+ };
+ Split1 = {
+ DebugVariablesTableConfiguration = (
+ Name,
+ 123,
+ Value,
+ 85,
+ Summary,
+ 105.123,
+ );
+ Frame = "{{342, 0}, {339, 126}}";
+ };
+ SplitCount = 2;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ Tab1 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ TabCount = 2;
+ TabsVisible = YES;
+ };
+ Frame = "{{0, 0}, {681, 120}}";
+ LauncherConfigVersion = 7;
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {681, 234}}";
+ LauncherConfigVersion = 3;
+ Runner = {
+ Frame = "{{0, 0}, {681, 234}}";
+ };
+ };
+ Tab3 = {
+ BuildMessageFrame = "{{0, 0}, {683, 163}}";
+ BuildTranscriptFrame = "{{0, 172}, {683, 68}}";
+ BuildTranscriptFrameExpanded = YES;
+ Frame = "{{0, 0}, {681, 238}}";
+ };
+ Tab4 = {
+ Frame = "{{0, 0}, {612, 295}}";
+ };
+ TabCount = 5;
+ TabsVisible = NO;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {260, 657}}";
+ GroupTreeTableConfiguration = (
+ TargetStatusColumn,
+ 18,
+ MainColumn,
+ 227,
+ );
+ };
+ Tab1 = {
+ ClassesFrame = "{{0, 0}, {250, 395}}";
+ ClassesTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXClassColumnIdentifier,
+ 207,
+ );
+ Frame = "{{0, 0}, {248, 657}}";
+ MembersFrame = "{{0, 404}, {250, 253}}";
+ MembersTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXMethodColumnIdentifier,
+ 206,
+ );
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {217, 657}}";
+ };
+ Tab3 = {
+ Frame = "{{0, 0}, {239, 657}}";
+ TargetTableConfiguration = (
+ ActiveObject,
+ 16,
+ ObjectNames,
+ 206.296,
+ );
+ };
+ Tab4 = {
+ BreakpointsTreeTableConfiguration = (
+ breakpointColumn,
+ 197,
+ enabledColumn,
+ 31,
+ );
+ Frame = "{{0, 0}, {250, 657}}";
+ };
+ TabCount = 5;
+ TabsVisible = YES;
+ };
+ NavBarShownByDefault = YES;
+ StatusViewVisible = YES;
+ Template = 64ABBB4501FA494900185B06;
+ ToolbarVisible = YES;
+ WindowLocation = "{0, 4}";
+ },
+ {
+ ContentSize = "{644, 662}";
+ LeftSlideOut = {
+ Collapsed = NO;
+ Frame = "{{0, 23}, {644, 639}}";
+ Split0 = {
+ ActiveTab = 0;
+ ActiveTabName = PBXDebugSessionModule;
+ Collapsed = NO;
+ Frame = "{{24, 0}, {620, 639}}";
+ Split0 = {
+ Frame = "{{0, 286}, {620, 353}}";
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {749, 267}}";
+ };
+ Tab1 = {
+ Debugger = {
+ ActiveTab = 0;
+ ActiveTabName = PBXDebugCLIModule;
+ Collapsed = NO;
+ Frame = "{{0, 0}, {620, 280}}";
+ Split0 = {
+ Frame = "{{0, 115}, {620, 165}}";
+ Split0 = {
+ Frame = "{{0, 0}, {297, 165}}";
+ };
+ Split1 = {
+ DebugVariablesTableConfiguration = (
+ Name,
+ 123,
+ Value,
+ 85,
+ Summary,
+ 80.123,
+ );
+ Frame = "{{306, 0}, {314, 165}}";
+ };
+ SplitCount = 2;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {620, 91}}";
+ };
+ Tab1 = {
+ Frame = "{{0, 0}, {100, 50}}";
+ };
+ TabCount = 2;
+ TabsVisible = YES;
+ };
+ Frame = "{{0, 0}, {620, 280}}";
+ LauncherConfigVersion = 7;
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {664, 50}}";
+ LauncherConfigVersion = 3;
+ Runner = {
+ Frame = "{{0, 0}, {664, 50}}";
+ };
+ };
+ Tab3 = {
+ BuildMessageFrame = "{{0, 0}, {614, 203}}";
+ BuildTranscriptFrame = "{{0, 212}, {614, 85}}";
+ BuildTranscriptFrameExpanded = YES;
+ Frame = "{{0, 0}, {612, 295}}";
+ };
+ Tab4 = {
+ Frame = "{{0, 0}, {612, 295}}";
+ };
+ TabCount = 5;
+ TabsVisible = NO;
+ };
+ SplitCount = 1;
+ Tab0 = {
+ Frame = "{{0, 0}, {313, 531}}";
+ GroupTreeTableConfiguration = (
+ TargetStatusColumn,
+ 18,
+ MainColumn,
+ 280,
+ );
+ };
+ Tab1 = {
+ ClassesFrame = "{{0, 0}, {280, 398}}";
+ ClassesTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXClassColumnIdentifier,
+ 237,
+ );
+ Frame = "{{0, 0}, {278, 659}}";
+ MembersFrame = "{{0, 407}, {280, 252}}";
+ MembersTreeTableConfiguration = (
+ PBXBookColumnIdentifier,
+ 20,
+ PBXMethodColumnIdentifier,
+ 236,
+ );
+ };
+ Tab2 = {
+ Frame = "{{0, 0}, {200, 100}}";
+ };
+ Tab3 = {
+ Frame = "{{0, 0}, {200, 557}}";
+ TargetTableConfiguration = (
+ ActiveObject,
+ 16,
+ ObjectNames,
+ 202.296,
+ );
+ };
+ Tab4 = {
+ BreakpointsTreeTableConfiguration = (
+ breakpointColumn,
+ 197,
+ enabledColumn,
+ 31,
+ );
+ Frame = "{{0, 0}, {250, 100}}";
+ };
+ TabCount = 5;
+ TabsVisible = YES;
+ };
+ NavBarShownByDefault = YES;
+ StatusViewVisible = YES;
+ Template = F5CA7ECC015C09990DCA290F;
+ ToolbarVisible = YES;
+ WindowLocation = "{48, 17}";
+ },
+ );
+ PBXWorkspaceStateSaveDate = 108416809;
+ };
+ perUserProjectItems = {
+ 3D7903E9066690E00002CE69 = 3D7903E9066690E00002CE69;
+ 3D7903EC066690E00002CE69 = 3D7903EC066690E00002CE69;
+ 3D7903ED066690E00002CE69 = 3D7903ED066690E00002CE69;
+ 3D7903EE066690E00002CE69 = 3D7903EE066690E00002CE69;
+ 3D7903EF066690E00002CE69 = 3D7903EF066690E00002CE69;
+ 3D7903F0066690E00002CE69 = 3D7903F0066690E00002CE69;
+ 3D7903F1066690E00002CE69 = 3D7903F1066690E00002CE69;
+ 3D7903F2066690E00002CE69 = 3D7903F2066690E00002CE69;
+ 3D7903F3066690E00002CE69 = 3D7903F3066690E00002CE69;
+ 3D7903F4066690E00002CE69 = 3D7903F4066690E00002CE69;
+ 3D9CFCBE06765A7F0002CE69 = 3D9CFCBE06765A7F0002CE69;
+ 3D9CFCBF06765A7F0002CE69 = 3D9CFCBF06765A7F0002CE69;
+ 3D9CFCC006765A7F0002CE69 = 3D9CFCC006765A7F0002CE69;
+ 3D9CFCC106765A7F0002CE69 = 3D9CFCC106765A7F0002CE69;
+ 3D9CFCC206765A7F0002CE69 = 3D9CFCC206765A7F0002CE69;
+ 3D9CFCC306765A7F0002CE69 = 3D9CFCC306765A7F0002CE69;
+ 3D9CFCC406765A7F0002CE69 = 3D9CFCC406765A7F0002CE69;
+ 3D9CFCC506765A7F0002CE69 = 3D9CFCC506765A7F0002CE69;
+ 3D9CFCC606765A7F0002CE69 = 3D9CFCC606765A7F0002CE69;
+ 3D9CFCC706765A7F0002CE69 = 3D9CFCC706765A7F0002CE69;
+ 3D9CFCC806765A7F0002CE69 = 3D9CFCC806765A7F0002CE69;
+ 3D9CFCC906765A7F0002CE69 = 3D9CFCC906765A7F0002CE69;
+ 3D9CFCCA06765A7F0002CE69 = 3D9CFCCA06765A7F0002CE69;
+ 3D9CFCCB06765A7F0002CE69 = 3D9CFCCB06765A7F0002CE69;
+ 3D9CFCCC06765A7F0002CE69 = 3D9CFCCC06765A7F0002CE69;
+ 3D9CFCCD06765A7F0002CE69 = 3D9CFCCD06765A7F0002CE69;
+ 3D9CFCCE06765A7F0002CE69 = 3D9CFCCE06765A7F0002CE69;
+ 3D9CFCCF06765A7F0002CE69 = 3D9CFCCF06765A7F0002CE69;
+ 3D9CFCD006765A7F0002CE69 = 3D9CFCD006765A7F0002CE69;
+ 3D9CFCD106765A7F0002CE69 = 3D9CFCD106765A7F0002CE69;
+ 3D9CFCD206765A7F0002CE69 = 3D9CFCD206765A7F0002CE69;
+ 3D9CFCD306765A7F0002CE69 = 3D9CFCD306765A7F0002CE69;
+ 3D9CFCD406765A7F0002CE69 = 3D9CFCD406765A7F0002CE69;
+ 3D9CFCD506765A7F0002CE69 = 3D9CFCD506765A7F0002CE69;
+ 3D9CFCD606765A7F0002CE69 = 3D9CFCD606765A7F0002CE69;
+ 3D9CFCD706765A7F0002CE69 = 3D9CFCD706765A7F0002CE69;
+ 3D9CFCD806765A7F0002CE69 = 3D9CFCD806765A7F0002CE69;
+ 3DA7161606664C500002CE69 = 3DA7161606664C500002CE69;
+ 3DA7161A06664C500002CE69 = 3DA7161A06664C500002CE69;
+ 3DA7161B06664C500002CE69 = 3DA7161B06664C500002CE69;
+ 3DA7161C06664C500002CE69 = 3DA7161C06664C500002CE69;
+ 3DA7161D06664C500002CE69 = 3DA7161D06664C500002CE69;
+ 3DA7161E06664C500002CE69 = 3DA7161E06664C500002CE69;
+ 3DA7161F06664C500002CE69 = 3DA7161F06664C500002CE69;
+ 3DA7162006664C500002CE69 = 3DA7162006664C500002CE69;
+ 3DA7162106664C500002CE69 = 3DA7162106664C500002CE69;
+ 3DA7162206664C500002CE69 = 3DA7162206664C500002CE69;
+ 3DA7162306664C500002CE69 = 3DA7162306664C500002CE69;
+ 3DA7162406664C500002CE69 = 3DA7162406664C500002CE69;
+ 3DDE89D906667BAD0002CE69 = 3DDE89D906667BAD0002CE69;
+ };
+ projectwideBuildSettings = {
+ };
+ wantsIndex = 1;
+ wantsSCM = -1;
+ };
+ 5851D5370479562300EE98CD = {
+ activeExec = 0;
+ };
+}
diff --git a/pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj b/pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj
new file mode 100644
index 00000000..8e25d1f3
--- /dev/null
+++ b/pd/portmidi/pm_mac/pm_test.pbproj/project.pbxproj
@@ -0,0 +1,337 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 38;
+ objects = {
+ 014CEA490018CE3211CA2923 = {
+ buildRules = (
+ );
+ buildSettings = {
+ COPY_PHASE_STRIP = NO;
+ OPTIMIZATION_CFLAGS = "-O0";
+ };
+ isa = PBXBuildStyle;
+ name = Development;
+ };
+ 014CEA4A0018CE3211CA2923 = {
+ buildRules = (
+ );
+ buildSettings = {
+ COPY_PHASE_STRIP = YES;
+ };
+ isa = PBXBuildStyle;
+ name = Deployment;
+ };
+//010
+//011
+//012
+//013
+//014
+//030
+//031
+//032
+//033
+//034
+ 034768E2FF38A6DC11DB9C8B = {
+ isa = PBXExecutableFileReference;
+ path = pm_test;
+ refType = 3;
+ };
+//030
+//031
+//032
+//033
+//034
+//080
+//081
+//082
+//083
+//084
+ 08FB7793FE84155DC02AAC07 = {
+ buildStyles = (
+ 014CEA490018CE3211CA2923,
+ 014CEA4A0018CE3211CA2923,
+ );
+ hasScannedForEncodings = 1;
+ isa = PBXProject;
+ mainGroup = 08FB7794FE84155DC02AAC07;
+ projectDirPath = "";
+ targets = (
+ 08FB779FFE84155DC02AAC07,
+ );
+ };
+ 08FB7794FE84155DC02AAC07 = {
+ children = (
+ 08FB7795FE84155DC02AAC07,
+ 08FB779DFE84155DC02AAC07,
+ 19C28FBDFE9D53C911CA2CBB,
+ );
+ isa = PBXGroup;
+ name = pm_test;
+ refType = 4;
+ };
+ 08FB7795FE84155DC02AAC07 = {
+ children = (
+ 5851D5650479591100EE98CD,
+ 58ED5B7E047D3A1200B92E62,
+ 5851D56F04795A1A00EE98CD,
+ 5851D5690479593D00EE98CD,
+ 5851D57104795A3C00EE98CD,
+ 5851D56B0479594B00EE98CD,
+ );
+ isa = PBXGroup;
+ name = Source;
+ refType = 4;
+ };
+ 08FB779DFE84155DC02AAC07 = {
+ children = (
+ 09AB6884FE841BABC02AAC07,
+ 58058934047C96F0009FBA67,
+ 5851D5670479592300EE98CD,
+ 5851D56D0479596A00EE98CD,
+ );
+ isa = PBXGroup;
+ name = "External Frameworks and Libraries";
+ refType = 4;
+ };
+ 08FB779FFE84155DC02AAC07 = {
+ buildPhases = (
+ 08FB77A0FE84155DC02AAC07,
+ 08FB77A1FE84155DC02AAC07,
+ 08FB77A3FE84155DC02AAC07,
+ 08FB77A5FE84155DC02AAC07,
+ C6859E980290922304C91782,
+ );
+ buildSettings = {
+ FRAMEWORK_SEARCH_PATHS = "";
+ HEADER_SEARCH_PATHS = "";
+ INSTALL_PATH = "$(HOME)/bin";
+ LIBRARY_SEARCH_PATHS = /Users/zk/Downloads/portmidi.1/pm_mac/build;
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ OTHER_REZFLAGS = "";
+ PRODUCT_NAME = pm_test;
+ REZ_EXECUTABLE = YES;
+ SECTORDER_FLAGS = "";
+ WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas";
+ };
+ dependencies = (
+ );
+ isa = PBXToolTarget;
+ name = pm_test;
+ productInstallPath = "$(HOME)/bin";
+ productName = pm_test;
+ productReference = 034768E2FF38A6DC11DB9C8B;
+ };
+ 08FB77A0FE84155DC02AAC07 = {
+ buildActionMask = 2147483647;
+ files = (
+ 5851D56A0479593D00EE98CD,
+ 5851D56C0479594B00EE98CD,
+ 5851D57204795A3C00EE98CD,
+ );
+ isa = PBXHeadersBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 08FB77A1FE84155DC02AAC07 = {
+ buildActionMask = 2147483647;
+ files = (
+ 58ED5C7B047D697A00B92E62,
+ );
+ isa = PBXSourcesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 08FB77A3FE84155DC02AAC07 = {
+ buildActionMask = 2147483647;
+ files = (
+ 09AB6885FE841BABC02AAC07,
+ 5851D5680479592300EE98CD,
+ 5851D56E0479596A00EE98CD,
+ 58058935047C96F0009FBA67,
+ );
+ isa = PBXFrameworksBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 08FB77A5FE84155DC02AAC07 = {
+ buildActionMask = 2147483647;
+ files = (
+ );
+ isa = PBXRezBuildPhase;
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+//080
+//081
+//082
+//083
+//084
+//090
+//091
+//092
+//093
+//094
+ 09AB6884FE841BABC02AAC07 = {
+ isa = PBXFrameworkReference;
+ name = CoreFoundation.framework;
+ path = /System/Library/Frameworks/CoreFoundation.framework;
+ refType = 0;
+ };
+ 09AB6885FE841BABC02AAC07 = {
+ fileRef = 09AB6884FE841BABC02AAC07;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+//090
+//091
+//092
+//093
+//094
+//190
+//191
+//192
+//193
+//194
+ 19C28FBDFE9D53C911CA2CBB = {
+ children = (
+ 034768E2FF38A6DC11DB9C8B,
+ );
+ isa = PBXGroup;
+ name = Products;
+ refType = 4;
+ };
+//190
+//191
+//192
+//193
+//194
+//580
+//581
+//582
+//583
+//584
+ 58058934047C96F0009FBA67 = {
+ isa = PBXFrameworkReference;
+ name = CoreAudio.framework;
+ path = /System/Library/Frameworks/CoreAudio.framework;
+ refType = 0;
+ };
+ 58058935047C96F0009FBA67 = {
+ fileRef = 58058934047C96F0009FBA67;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5650479591100EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = test.c;
+ path = ../pm_test/test.c;
+ refType = 2;
+ };
+ 5851D5670479592300EE98CD = {
+ isa = PBXFrameworkReference;
+ name = CoreMIDI.framework;
+ path = /System/Library/Frameworks/CoreMIDI.framework;
+ refType = 0;
+ };
+ 5851D5680479592300EE98CD = {
+ fileRef = 5851D5670479592300EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D5690479593D00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = portmidi.h;
+ path = ../pm_common/portmidi.h;
+ refType = 2;
+ };
+ 5851D56A0479593D00EE98CD = {
+ fileRef = 5851D5690479593D00EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D56B0479594B00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = porttime.h;
+ path = ../porttime/porttime.h;
+ refType = 2;
+ };
+ 5851D56C0479594B00EE98CD = {
+ fileRef = 5851D56B0479594B00EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D56D0479596A00EE98CD = {
+ isa = PBXFileReference;
+ name = libportmidi.a;
+ path = build/libportmidi.a;
+ refType = 4;
+ };
+ 5851D56E0479596A00EE98CD = {
+ fileRef = 5851D56D0479596A00EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 5851D56F04795A1A00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = midithread.c;
+ path = ../pm_test/midithread.c;
+ refType = 2;
+ };
+ 5851D57104795A3C00EE98CD = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = pmutil.h;
+ path = ../pm_common/pmutil.h;
+ refType = 2;
+ };
+ 5851D57204795A3C00EE98CD = {
+ fileRef = 5851D57104795A3C00EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 58ED5B7E047D3A1200B92E62 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ name = latency.c;
+ path = ../pm_test/latency.c;
+ refType = 2;
+ };
+ 58ED5C7B047D697A00B92E62 = {
+ fileRef = 5851D5650479591100EE98CD;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+//580
+//581
+//582
+//583
+//584
+//C60
+//C61
+//C62
+//C63
+//C64
+ C6859E980290922304C91782 = {
+ buildActionMask = 8;
+ dstPath = /usr/share/man/man1/;
+ dstSubfolderSpec = 0;
+ files = (
+ );
+ isa = PBXCopyFilesBuildPhase;
+ runOnlyForDeploymentPostprocessing = 1;
+ };
+ };
+ rootObject = 08FB7793FE84155DC02AAC07;
+}
diff --git a/pd/portmidi/pm_mac/pmmac.c b/pd/portmidi/pm_mac/pmmac.c
new file mode 100644
index 00000000..fbf31c83
--- /dev/null
+++ b/pd/portmidi/pm_mac/pmmac.c
@@ -0,0 +1,42 @@
+/* pmmac.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement:
+pm_init(), which calls various routines to register the
+available midi devices,
+Pm_GetDefaultInputDeviceID(), and
+Pm_GetDefaultOutputDeviceID().
+It is seperate from pmmacosxcm because we might want to register
+non-CoreMIDI devices.
+*/
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmmacosxcm.h"
+
+PmError pm_init()
+{
+ return pm_macosxcm_init();
+}
+
+void pm_term(void)
+{
+ pm_macosxcm_term();
+}
+
+PmDeviceID pm_default_input_device_id = -1;
+PmDeviceID pm_default_output_device_id = -1;
+
+PmDeviceID Pm_GetDefaultInputDeviceID()
+{
+ return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+ return pm_default_output_device_id;
+}
+
+void *pm_alloc(size_t s) { return malloc(s); }
+
+void pm_free(void *ptr) { free(ptr); }
+
+
diff --git a/pd/portmidi/pm_mac/pmmac.h b/pd/portmidi/pm_mac/pmmac.h
new file mode 100644
index 00000000..2d714254
--- /dev/null
+++ b/pd/portmidi/pm_mac/pmmac.h
@@ -0,0 +1,4 @@
+/* pmmac.h */
+
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id; \ No newline at end of file
diff --git a/pd/portmidi/pm_mac/pmmacosxcm.c b/pd/portmidi/pm_mac/pmmacosxcm.c
new file mode 100644
index 00000000..55b9b084
--- /dev/null
+++ b/pd/portmidi/pm_mac/pmmacosxcm.c
@@ -0,0 +1,701 @@
+/*
+ * Platform interface to the MacOS X CoreMIDI framework
+ *
+ * Jon Parise <jparise@cmu.edu>
+ * and subsequent work by Andrew Zeldis and Zico Kolter
+ * and Roger B. Dannenberg
+ *
+ * $Id: pmmacosxcm.c,v 1.1 2005-12-15 00:56:57 eighthave Exp $
+ */
+
+/* Notes:
+ since the input and output streams are represented by MIDIEndpointRef
+ values and almost no other state, we store the MIDIEndpointRef on
+ descriptors[midi->device_id].descriptor. The only other state we need
+ is for errors: we need to know if there is an error and if so, what is
+ the error text. As in pmwinmm.c, we use a structure with two kinds of
+ host error: "error" and "callback_error". That way, asynchronous callbacks
+ do not interfere with other error information.
+
+ OS X does not seem to have an error-code-to-text function, so we will
+ just use text messages instead of error codes.
+ */
+
+#include <stdlib.h>
+
+#include "portmidi.h"
+#include "pminternal.h"
+#include "porttime.h"
+#include "pmmac.h"
+#include "pmmacosxcm.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <CoreServices/CoreServices.h>
+#include <CoreMIDI/MIDIServices.h>
+#include <CoreAudio/HostTime.h>
+
+#define PACKET_BUFFER_SIZE 1024
+
+/* this is very strange: if I put in a reasonable
+ number here, e.g. 128, which would allow sysex data
+ to be sent 128 bytes at a time, then I lose sysex
+ data in my loopback test. With a buffer size of 4,
+ we put at most 4 bytes in a packet (but maybe many
+ packets in a packetList), and everything works fine.
+ */
+#define SYSEX_BUFFER_SIZE 4
+
+#define VERBOSE_ON 1
+#define VERBOSE if (VERBOSE_ON)
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+#define MIDI_STATUS_MASK 0x80
+
+static MIDIClientRef client = NULL; /* Client handle to the MIDI server */
+static MIDIPortRef portIn = NULL; /* Input port handle */
+static MIDIPortRef portOut = NULL; /* Output port handle */
+
+extern pm_fns_node pm_macosx_in_dictionary;
+extern pm_fns_node pm_macosx_out_dictionary;
+
+typedef struct midi_macosxcm_struct {
+ unsigned long sync_time; /* when did we last determine delta? */
+ UInt64 delta; /* difference between stream time and real time in ns */
+ UInt64 last_time; /* last output time */
+ int first_message; /* tells midi_write to sychronize timestamps */
+ int sysex_mode; /* middle of sending sysex */
+ unsigned long sysex_word; /* accumulate data when receiving sysex */
+ unsigned int sysex_byte_count; /* count how many received */
+ char error[PM_HOST_ERROR_MSG_LEN];
+ char callback_error[PM_HOST_ERROR_MSG_LEN];
+ Byte packetBuffer[PACKET_BUFFER_SIZE];
+ MIDIPacketList *packetList; /* a pointer to packetBuffer */
+ MIDIPacket *packet;
+ Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
+ MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */
+} midi_macosxcm_node, *midi_macosxcm_type;
+
+/* private function declarations */
+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
+
+char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
+
+
+static int
+midi_length(long msg)
+{
+ int status, high, low;
+ static int high_lengths[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
+ 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
+ };
+ static int low_lengths[] = {
+ 1, 1, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
+ 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
+ };
+
+ status = msg & 0xFF;
+ high = status >> 4;
+ low = status & 15;
+
+ return (high != 0xF0) ? high_lengths[high] : low_lengths[low];
+}
+
+static PmTimestamp midi_synchronize(PmInternal *midi)
+{
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ UInt64 pm_stream_time_2 =
+ AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+ PmTimestamp real_time;
+ UInt64 pm_stream_time;
+ /* if latency is zero and this is an output, there is no
+ time reference and midi_synchronize should never be called */
+ assert(midi->time_proc);
+ assert(!(midi->write_flag && midi->latency == 0));
+ do {
+ /* read real_time between two reads of stream time */
+ pm_stream_time = pm_stream_time_2;
+ real_time = (*midi->time_proc)(midi->time_info);
+ pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+ /* repeat if more than 0.5 ms has elapsed */
+ } while (pm_stream_time_2 > pm_stream_time + 500000);
+ m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
+ m->sync_time = real_time;
+ return real_time;
+}
+
+
+/* called when MIDI packets are received */
+static void
+readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
+{
+ PmInternal *midi;
+ midi_macosxcm_type m;
+ PmEvent event;
+ MIDIPacket *packet;
+ unsigned int packetIndex;
+ unsigned long now;
+ unsigned int status;
+
+ /* Retrieve the context for this connection */
+ midi = (PmInternal *) connRefCon;
+ m = (midi_macosxcm_type) midi->descriptor;
+ assert(m);
+
+ /* synchronize time references every 100ms */
+ now = (*midi->time_proc)(midi->time_info);
+ if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
+ /* time to resync */
+ now = midi_synchronize(midi);
+ m->first_message = FALSE;
+ }
+
+ packet = (MIDIPacket *) &newPackets->packet[0];
+ /* printf("readproc packet status %x length %d\n", packet->data[0], packet->length); */
+ for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
+ /* Set the timestamp and dispatch this message */
+ event.timestamp =
+ (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) /
+ (UInt64) 1000000;
+ status = packet->data[0];
+ /* process packet as sysex data if it begins with MIDI_SYSEX, or
+ MIDI_EOX or non-status byte */
+ if (status == MIDI_SYSEX || status == MIDI_EOX ||
+ !(status & MIDI_STATUS_MASK)) {
+ int i = 0;
+ while (i < packet->length) {
+ pm_read_byte(midi, packet->data[i], event.timestamp);
+ i++;
+ }
+ } else {
+ /* Build the PmMessage for the PmEvent structure */
+ switch (packet->length) {
+ case 1:
+ event.message = Pm_Message(packet->data[0], 0, 0);
+ break;
+ case 2:
+ event.message = Pm_Message(packet->data[0],
+ packet->data[1], 0);
+ break;
+ case 3:
+ event.message = Pm_Message(packet->data[0],
+ packet->data[1],
+ packet->data[2]);
+ break;
+ default:
+ /* Skip packets that are too large to fit in a PmMessage */
+#ifdef DEBUG
+ printf("PortMidi debug msg: large packet skipped\n");
+#endif
+ continue;
+ }
+ pm_read_short(midi, &event);
+ }
+ packet = MIDIPacketNext(packet);
+ }
+}
+
+static PmError
+midi_in_open(PmInternal *midi, void *driverInfo)
+{
+ MIDIEndpointRef endpoint;
+ midi_macosxcm_type m;
+ OSStatus macHostError;
+
+ /* insure that we have a time_proc for timing */
+ if (midi->time_proc == NULL) {
+ if (!Pt_Started())
+ Pt_Start(1, 0, 0);
+ /* time_get does not take a parameter, so coerce */
+ midi->time_proc = (PmTimeProcPtr) Pt_Time;
+ }
+
+ endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
+ if (endpoint == NULL) {
+ return pmInvalidDeviceId;
+ }
+
+ m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
+ midi->descriptor = m;
+ if (!m) {
+ return pmInsufficientMemory;
+ }
+ m->error[0] = 0;
+ m->callback_error[0] = 0;
+ m->sync_time = 0;
+ m->delta = 0;
+ m->last_time = 0;
+ m->first_message = TRUE;
+ m->sysex_mode = FALSE;
+ m->sysex_word = 0;
+ m->sysex_byte_count = 0;
+ m->packetList = NULL;
+ m->packet = NULL;
+
+ macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
+ if (macHostError != noErr) {
+ pm_hosterror = macHostError;
+ sprintf(pm_hosterror_text,
+ "Host error %ld: MIDIPortConnectSource() in midi_in_open()",
+ macHostError);
+ midi->descriptor = NULL;
+ pm_free(m);
+ return pmHostError;
+ }
+
+ return pmNoError;
+}
+
+static PmError
+midi_in_close(PmInternal *midi)
+{
+ MIDIEndpointRef endpoint;
+ OSStatus macHostError;
+ PmError err = pmNoError;
+
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+
+ if (!m) return pmBadPtr;
+
+ endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
+ if (endpoint == NULL) {
+ pm_hosterror = pmBadPtr;
+ }
+
+ /* shut off the incoming messages before freeing data structures */
+ macHostError = MIDIPortDisconnectSource(portIn, endpoint);
+ if (macHostError != noErr) {
+ pm_hosterror = macHostError;
+ sprintf(pm_hosterror_text,
+ "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()",
+ macHostError);
+ err = pmHostError;
+ }
+
+ midi->descriptor = NULL;
+ pm_free(midi->descriptor);
+
+ return err;
+}
+
+
+static PmError
+midi_out_open(PmInternal *midi, void *driverInfo)
+{
+ midi_macosxcm_type m;
+
+ m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
+ midi->descriptor = m;
+ if (!m) {
+ return pmInsufficientMemory;
+ }
+ m->error[0] = 0;
+ m->callback_error[0] = 0;
+ m->sync_time = 0;
+ m->delta = 0;
+ m->last_time = 0;
+ m->first_message = TRUE;
+ m->sysex_mode = FALSE;
+ m->sysex_word = 0;
+ m->sysex_byte_count = 0;
+ m->packetList = (MIDIPacketList *) m->packetBuffer;
+ m->packet = NULL;
+
+ return pmNoError;
+}
+
+static PmError
+midi_out_close(PmInternal *midi)
+{
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ if (!m) return pmBadPtr;
+
+ midi->descriptor = NULL;
+ pm_free(midi->descriptor);
+
+ return pmNoError;
+}
+
+static PmError
+midi_abort(PmInternal *midi)
+{
+ return pmNoError;
+}
+
+
+static PmError
+midi_write_flush(PmInternal *midi)
+{
+ OSStatus macHostError;
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ MIDIEndpointRef endpoint =
+ (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
+ assert(m);
+ assert(endpoint);
+ if (m->packet != NULL) {
+ /* out of space, send the buffer and start refilling it */
+ macHostError = MIDISend(portOut, endpoint, m->packetList);
+ m->packet = NULL; /* indicate no data in packetList now */
+ if (macHostError != noErr) goto send_packet_error;
+ }
+ return pmNoError;
+
+send_packet_error:
+ pm_hosterror = macHostError;
+ sprintf(pm_hosterror_text,
+ "Host error %ld: MIDISend() in midi_write()",
+ macHostError);
+ return pmHostError;
+
+}
+
+
+static PmError
+send_packet(PmInternal *midi, Byte *message, unsigned int messageLength,
+ MIDITimeStamp timestamp)
+{
+ PmError err;
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ assert(m);
+
+ /* printf("add %d to packet %lx len %d\n", message[0], m->packet, messageLength); */
+ m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
+ m->packet, timestamp, messageLength,
+ message);
+ if (m->packet == NULL) {
+ /* out of space, send the buffer and start refilling it */
+ /* make midi->packet non-null to fool midi_write_flush into sending */
+ m->packet = (MIDIPacket *) 4;
+ if ((err = midi_write_flush(midi)) != pmNoError) return err;
+ m->packet = MIDIPacketListInit(m->packetList);
+ assert(m->packet); /* if this fails, it's a programming error */
+ m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
+ m->packet, timestamp, messageLength,
+ message);
+ assert(m->packet); /* can't run out of space on first message */
+ }
+ return pmNoError;
+}
+
+
+static PmError
+midi_write_short(PmInternal *midi, PmEvent *event)
+{
+ long when = event->timestamp;
+ long what = event->message;
+ MIDITimeStamp timestamp;
+ UInt64 when_ns;
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ Byte message[4];
+ unsigned int messageLength;
+
+ if (m->packet == NULL) {
+ m->packet = MIDIPacketListInit(m->packetList);
+ /* this can never fail, right? failure would indicate something
+ unrecoverable */
+ assert(m->packet);
+ }
+
+ /* compute timestamp */
+ if (when == 0) when = midi->now;
+ /* if latency == 0, midi->now is not valid. We will just set it to zero */
+ if (midi->latency == 0) when = 0;
+ when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
+ /* make sure we don't go backward in time */
+ if (when_ns < m->last_time) when_ns = m->last_time;
+ m->last_time = when_ns;
+ timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
+
+ message[0] = Pm_MessageStatus(what);
+ message[1] = Pm_MessageData1(what);
+ message[2] = Pm_MessageData2(what);
+ messageLength = midi_length(what);
+
+ /* Add this message to the packet list */
+ return send_packet(midi, message, messageLength, timestamp);
+}
+
+
+static PmError
+midi_begin_sysex(PmInternal *midi, PmTimestamp when)
+{
+ UInt64 when_ns;
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ assert(m);
+ m->sysex_byte_count = 0;
+
+ /* compute timestamp */
+ if (when == 0) when = midi->now;
+ /* if latency == 0, midi->now is not valid. We will just set it to zero */
+ if (midi->latency == 0) when = 0;
+ when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
+ m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
+
+ if (m->packet == NULL) {
+ m->packet = MIDIPacketListInit(m->packetList);
+ /* this can never fail, right? failure would indicate something
+ unrecoverable */
+ assert(m->packet);
+ }
+ return pmNoError;
+}
+
+
+static PmError
+midi_end_sysex(PmInternal *midi, PmTimestamp when)
+{
+ PmError err;
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ assert(m);
+
+ /* make sure we don't go backward in time */
+ if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time;
+
+ /* now send what's in the buffer */
+ err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
+ m->sysex_timestamp);
+ m->sysex_byte_count = 0;
+ if (err != pmNoError) {
+ m->packet = NULL; /* flush everything in the packet list */
+ return err;
+ }
+ return pmNoError;
+}
+
+
+static PmError
+midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
+{
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ assert(m);
+ if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
+ PmError err = midi_end_sysex(midi, timestamp);
+ if (err != pmNoError) return err;
+ }
+ m->sysex_buffer[m->sysex_byte_count++] = byte;
+ return pmNoError;
+}
+
+
+static PmError
+midi_write_realtime(PmInternal *midi, PmEvent *event)
+{
+ /* to send a realtime message during a sysex message, first
+ flush all pending sysex bytes into packet list */
+ PmError err = midi_end_sysex(midi, 0);
+ if (err != pmNoError) return err;
+ /* then we can just do a normal midi_write_short */
+ return midi_write_short(midi, event);
+}
+
+static unsigned int midi_has_host_error(PmInternal *midi)
+{
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ return (m->callback_error[0] != 0) || (m->error[0] != 0);
+}
+
+
+static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)
+{
+ midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
+ msg[0] = 0; /* initialize to empty string */
+ if (m) { /* make sure there is an open device to examine */
+ if (m->error[0]) {
+ strncpy(msg, m->error, len);
+ m->error[0] = 0; /* clear the error */
+ } else if (m->callback_error[0]) {
+ strncpy(msg, m->callback_error, len);
+ m->callback_error[0] = 0; /* clear the error */
+ }
+ msg[len - 1] = 0; /* make sure string is terminated */
+ }
+}
+
+
+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
+{
+ UInt64 nanos;
+ if (timestamp <= 0) {
+ return (MIDITimeStamp)0;
+ } else {
+ nanos = (UInt64)timestamp * (UInt64)1000000;
+ return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
+ }
+}
+
+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
+{
+ UInt64 nanos;
+ nanos = AudioConvertHostTimeToNanos(timestamp);
+ return (PmTimestamp)(nanos / (UInt64)1000000);
+}
+
+
+char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
+{
+ MIDIEntityRef entity;
+ MIDIDeviceRef device;
+ CFStringRef endpointName = NULL, deviceName = NULL, fullName = NULL;
+ CFStringEncoding defaultEncoding;
+ char* newName;
+
+ /* get the default string encoding */
+ defaultEncoding = CFStringGetSystemEncoding();
+
+ /* get the entity and device info */
+ MIDIEndpointGetEntity(endpoint, &entity);
+ MIDIEntityGetDevice(entity, &device);
+
+ /* create the nicely formated name */
+ MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);
+ MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);
+ if (deviceName != NULL) {
+ fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),
+ deviceName, endpointName);
+ } else {
+ fullName = endpointName;
+ }
+
+ /* copy the string into our buffer */
+ newName = (char*)malloc(CFStringGetLength(fullName) + 1);
+ CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
+ defaultEncoding);
+
+ /* clean up */
+ if (endpointName) CFRelease(endpointName);
+ if (deviceName) CFRelease(deviceName);
+ if (fullName) CFRelease(fullName);
+
+ return newName;
+}
+
+
+
+pm_fns_node pm_macosx_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ none_synchronize,
+ midi_in_open,
+ midi_abort,
+ midi_in_close,
+ success_poll,
+ midi_has_host_error,
+ midi_get_host_error,
+};
+
+pm_fns_node pm_macosx_out_dictionary = {
+ midi_write_short,
+ midi_begin_sysex,
+ midi_end_sysex,
+ midi_write_byte,
+ midi_write_realtime,
+ midi_write_flush,
+ midi_synchronize,
+ midi_out_open,
+ midi_abort,
+ midi_out_close,
+ success_poll,
+ midi_has_host_error,
+ midi_get_host_error,
+};
+
+
+PmError pm_macosxcm_init(void)
+{
+ ItemCount numInputs, numOutputs, numDevices;
+ MIDIEndpointRef endpoint;
+ int i;
+ OSStatus macHostError;
+ char *error_text;
+
+ /* Determine the number of MIDI devices on the system */
+ numDevices = MIDIGetNumberOfDevices();
+ numInputs = MIDIGetNumberOfSources();
+ numOutputs = MIDIGetNumberOfDestinations();
+
+ /* Return prematurely if no devices exist on the system
+ Note that this is not an error. There may be no devices.
+ Pm_CountDevices() will return zero, which is correct and
+ useful information
+ */
+ if (numDevices <= 0) {
+ return pmNoError;
+ }
+
+
+ /* Initialize the client handle */
+ macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);
+ if (macHostError != noErr) {
+ error_text = "MIDIClientCreate() in pm_macosxcm_init()";
+ goto error_return;
+ }
+
+ /* Create the input port */
+ macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
+ NULL, &portIn);
+ if (macHostError != noErr) {
+ error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
+ goto error_return;
+ }
+
+ /* Create the output port */
+ macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
+ if (macHostError != noErr) {
+ error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
+ goto error_return;
+ }
+
+ /* Iterate over the MIDI input devices */
+ for (i = 0; i < numInputs; i++) {
+ endpoint = MIDIGetSource(i);
+ if (endpoint == NULL) {
+ continue;
+ }
+
+ /* set the first input we see to the default */
+ if (pm_default_input_device_id == -1)
+ pm_default_input_device_id = pm_descriptor_index;
+
+ /* Register this device with PortMidi */
+ pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
+ TRUE, (void*)endpoint, &pm_macosx_in_dictionary);
+ }
+
+ /* Iterate over the MIDI output devices */
+ for (i = 0; i < numOutputs; i++) {
+ endpoint = MIDIGetDestination(i);
+ if (endpoint == NULL) {
+ continue;
+ }
+
+ /* set the first output we see to the default */
+ if (pm_default_output_device_id == -1)
+ pm_default_output_device_id = pm_descriptor_index;
+
+ /* Register this device with PortMidi */
+ pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
+ FALSE, (void*)endpoint, &pm_macosx_out_dictionary);
+ }
+ return pmNoError;
+
+error_return:
+ pm_hosterror = macHostError;
+ sprintf(pm_hosterror_text, "Host error %ld: %s\n", macHostError, error_text);
+ pm_macosxcm_term(); /* clear out any opened ports */
+ return pmHostError;
+}
+
+void pm_macosxcm_term(void)
+{
+ if (client != NULL) MIDIClientDispose(client);
+ if (portIn != NULL) MIDIPortDispose(portIn);
+ if (portOut != NULL) MIDIPortDispose(portOut);
+}
diff --git a/pd/portmidi/pm_mac/pmmacosxcm.h b/pd/portmidi/pm_mac/pmmacosxcm.h
new file mode 100644
index 00000000..17259359
--- /dev/null
+++ b/pd/portmidi/pm_mac/pmmacosxcm.h
@@ -0,0 +1,4 @@
+/* system-specific definitions */
+
+PmError pm_macosxcm_init(void);
+void pm_macosxcm_term(void); \ No newline at end of file
diff --git a/pd/portmidi/pm_test/latency.c b/pd/portmidi/pm_test/latency.c
new file mode 100644
index 00000000..87b1965b
--- /dev/null
+++ b/pd/portmidi/pm_test/latency.c
@@ -0,0 +1,278 @@
+/* latency.c -- measure latency of OS */
+
+#include "porttime.h"
+#include "portmidi.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+/* Latency is defined here to mean the time starting when a
+ process becomes ready to run, and ending when the process
+ actually runs. Latency is due to contention for the
+ processor, usually due to other processes, OS activity
+ including device drivers handling interrupts, and
+ waiting for the scheduler to suspend the currently running
+ process and activate the one that is waiting.
+
+ Latency can affect PortMidi applications: if a process fails
+ to wake up promptly, MIDI input may sit in the input buffer
+ waiting to be handled, and MIDI output may not be generated
+ with accurate timing. Using the latency parameter when
+ opening a MIDI output port allows the caller to defer timing
+ to PortMidi, which in most implementations will pass the
+ data on to the OS. By passing timestamps and data to the
+ OS kernel, device driver, or even hardware, there are fewer
+ sources of latency that can affect the ultimate timing of
+ the data. On the other hand, the application must generate
+ and deliver the data ahead of the timestamp. The amount by
+ which data is computed early must be at least as large as
+ the worst-case latency to avoid timing problems.
+
+ Latency is even more important in audio applications. If an
+ application lets an audio output buffer underflow, an audible
+ pop or click is produced. Audio input buffers can overflow,
+ causing data to be lost. In general the audio buffers must
+ be large enough to buffer the worst-case latency that the
+ application will encounter.
+
+ This program measures latency by recording the difference
+ between the scheduled callback time and the current real time.
+ We do not really know the scheduled callback time, so we will
+ record the differences between the real time of each callback
+ and the real time of the previous callback. Differences that
+ are larger than the scheduled difference are recorded. Smaller
+ differences indicate the system is recovering from an earlier
+ latency, so these are ignored.
+ Since printing by the callback process can cause all sorts of
+ delays, this program records latency observations in a
+ histogram. When the program is stopped, the histogram is
+ printed to the console.
+
+ Optionally the system can be tested under a load of MIDI input,
+ MIDI output, or both. If MIDI input is selected, the callback
+ thread will read any waiting MIDI events each iteration. You
+ must generate events on this interface for the test to actually
+ put any appreciable load on PortMidi. If MIDI output is
+ selected, alternating note on and note off events are sent each
+ X iterations, where you specify X. For example, with a timer
+ callback period of 2ms and X=1, a MIDI event is sent every 2ms.
+
+
+ INTERPRETING RESULTS: Time is quantized to 1ms, so there is
+ some uncertainty due to rounding. A microsecond latency that
+ spans the time when the clock is incremented will be reported
+ as a latency of 1. On the other hand, a latency of almost
+ 1ms that falls between two clock ticks will be reported as
+ zero. In general, if the highest nonzero bin is numbered N,
+ then the maximum latency is N+1.
+
+CHANGE LOG
+
+18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive
+ MIDI during test, and made period user-settable.
+ */
+
+#define HIST_LEN 21 /* how many 1ms bins in the histogram */
+
+#define STRING_MAX 80 /* used for console input */
+
+#define INPUT_BUFFER_SIZE 100
+#define OUTPUT_BUFFER_SIZE 0
+
+#ifndef max
+#define max(a, b) ((a) > (b) ? (a) : (b))
+#endif
+#ifndef min
+#define min(a, b) ((a) <= (b) ? (a) : (b))
+#endif
+
+int get_number(char *prompt);
+
+PtTimestamp previous_callback_time = 0;
+
+int period; /* milliseconds per callback */
+
+long histogram[HIST_LEN];
+long max_latency = 0; /* worst latency observed */
+long out_of_range = 0; /* how many points outside of HIST_LEN? */
+
+int test_in, test_out; /* test MIDI in and/or out? */
+int output_period; /* output MIDI every __ iterations if test_out true */
+int iteration = 0;
+PmStream *in, *out;
+int note_on = 0; /* is the note currently on? */
+
+/* callback function for PortTime -- computes histogram */
+void pt_callback(PtTimestamp timestamp, void *userData)
+{
+ PtTimestamp difference = timestamp - previous_callback_time - period;
+ previous_callback_time = timestamp;
+
+ /* allow 5 seconds for the system to settle down */
+ if (timestamp < 5000) return;
+
+ iteration++;
+ /* send a note on/off if user requested it */
+ if (test_out && (iteration % output_period == 0)) {
+ PmEvent buffer[1];
+ buffer[0].timestamp = Pt_Time(NULL);
+ if (note_on) {
+ /* note off */
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ note_on = 0;
+ } else {
+ /* note on */
+ buffer[0].message = Pm_Message(0x90, 60, 100);
+ note_on = 1;
+ }
+ Pm_Write(out, buffer, 1);
+ iteration = 0;
+ }
+
+ /* read all waiting events (if user requested) */
+ if (test_in) {
+ PmError status;
+ PmEvent buffer[1];
+ do {
+ status = Pm_Poll(in);
+ if (status == TRUE) {
+ Pm_Read(in,buffer,1);
+ }
+ } while (status == TRUE);
+ }
+
+ if (difference < 0) return; /* ignore when system is "catching up" */
+
+ /* update the histogram */
+ if (difference < HIST_LEN) {
+ histogram[difference]++;
+ } else {
+ out_of_range++;
+ }
+
+ if (max_latency < difference) max_latency = difference;
+}
+
+
+int main()
+{
+ char line[STRING_MAX];
+ int i;
+ int len;
+ int choice;
+ PtTimestamp stop;
+ printf("Latency histogram.\n");
+ period = get_number("Choose timer period (in ms): ");
+ assert(period >= 1);
+ printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n",
+ "1. No MIDI traffic",
+ "2. MIDI input",
+ "3. MIDI output",
+ "4. MIDI input and output");
+ choice = get_number("? ");
+ switch (choice) {
+ case 1: test_in = 0; test_out = 0; break;
+ case 2: test_in = 1; test_out = 0; break;
+ case 3: test_in = 0; test_out = 1; break;
+ case 4: test_in = 1; test_out = 1; break;
+ default: assert(0);
+ }
+ if (test_in || test_out) {
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if ((test_in && info->input) ||
+ (test_out && info->output)) {
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) printf(" (input)");
+ if (info->output) printf(" (output)");
+ printf("\n");
+ }
+ }
+ /* open stream(s) */
+ if (test_in) {
+ int i = get_number("MIDI input device number: ");
+ Pm_OpenInput(&in,
+ i,
+ NULL,
+ INPUT_BUFFER_SIZE,
+ (long (*)(void *)) Pt_Time,
+ NULL);
+ /* turn on filtering; otherwise, input might overflow in the
+ 5-second period before timer callback starts reading midi */
+ Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ }
+ if (test_out) {
+ int i = get_number("MIDI output device number: ");
+ PmEvent buffer[1];
+ Pm_OpenOutput(&out,
+ i,
+ NULL,
+ OUTPUT_BUFFER_SIZE,
+ (long (*)(void *)) Pt_Time,
+ NULL,
+ 0); /* no latency scheduling */
+
+ /* send a program change to force a status byte -- this fixes
+ a problem with a buggy linux MidiSport driver, and shouldn't
+ hurt anything else
+ */
+ buffer[0].timestamp = 0;
+ buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */
+ Pm_Write(out, buffer, 1);
+
+ output_period = get_number(
+ "MIDI out should be sent every __ callback iterations: ");
+
+ assert(output_period >= 1);
+ }
+ }
+
+ printf("%s%s", "Latency measurements will start in 5 seconds. ",
+ "Type return to stop: ");
+ Pt_Start(period, &pt_callback, 0);
+ fgets(line, STRING_MAX, stdin);
+ stop = Pt_Time();
+ Pt_Stop();
+
+ /* courteously turn off the last note, if necessary */
+ if (note_on) {
+ PmEvent buffer[1];
+ buffer[0].timestamp = Pt_Time(NULL);
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ Pm_Write(out, buffer, 1);
+ }
+
+ /* print the histogram */
+ printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001);
+ printf("Latency(ms) Number of occurrences\n");
+ /* avoid printing beyond last non-zero histogram entry */
+ len = min(HIST_LEN, max_latency + 1);
+ for (i = 0; i < len; i++) {
+ printf("%2d %10ld\n", i, histogram[i]);
+ }
+ printf("Number of points greater than %dms: %ld\n",
+ HIST_LEN - 1, out_of_range);
+ printf("Maximum latency: %ld milliseconds\n", max_latency);
+ printf("\nNote that due to rounding, actual latency can be 1ms higher\n");
+ printf("than the numbers reported here.\n");
+ printf("Type return to exit...");
+ fgets(line, STRING_MAX, stdin);
+ return 0;
+}
+
+
+/* read a number from console */
+int get_number(char *prompt)
+{
+ char line[STRING_MAX];
+ int n = 0, i;
+ printf(prompt);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+
+ }
+ return i;
+}
diff --git a/pd/portmidi/pm_test/latency.dsp b/pd/portmidi/pm_test/latency.dsp
new file mode 100644
index 00000000..ff6bbbd7
--- /dev/null
+++ b/pd/portmidi/pm_test/latency.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="latency" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=latency - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "latency.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "latency.mak" CFG="latency - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "latency - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "latency - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "latency - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "latency___Win32_Release"
+# PROP BASE Intermediate_Dir "latency___Win32_Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "latencyRelease"
+# PROP Intermediate_Dir "latencyRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../porttime" /I "../pm_common" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "latency - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "latency___Win32_Debug"
+# PROP BASE Intermediate_Dir "latency___Win32_Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "latencyDebug"
+# PROP Intermediate_Dir "latencyDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../porttime" /I "../pm_common" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "latency - Win32 Release"
+# Name "latency - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\latency.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/midithread.c b/pd/portmidi/pm_test/midithread.c
new file mode 100644
index 00000000..861b347c
--- /dev/null
+++ b/pd/portmidi/pm_test/midithread.c
@@ -0,0 +1,327 @@
+/* midithread.c -- example program showing how to do midi processing
+ in a preemptive thread
+
+ Notes: if you handle midi I/O from your main program, there will be
+ some delay before handling midi messages whenever the program is
+ doing something like file I/O, graphical interface updates, etc.
+
+ To handle midi with minimal delay, you should do all midi processing
+ in a separate, high priority thread. A convenient way to get a high
+ priority thread in windows is to use the timer callback provided by
+ the PortTime library. That is what we show here.
+
+ If the high priority thread writes to a file, prints to the console,
+ or does just about anything other than midi processing, this may
+ create delays, so all this processing should be off-loaded to the
+ "main" process or thread. Communication between threads can be tricky.
+ If one thread is writing at the same time the other is reading, very
+ tricky race conditions can arise, causing programs to behave
+ incorrectly, but only under certain timing conditions -- a terrible
+ thing to debug. Advanced programmers know this as a synchronization
+ problem. See any operating systems textbook for the complete story.
+
+ To avoid synchronization problems, a simple, reliable approach is
+ to communicate via messages. PortMidi offers a message queue as a
+ datatype, and operations to insert and remove messages. Use two
+ queues as follows: midi_to_main transfers messages from the midi
+ thread to the main thread, and main_to_midi transfers messages from
+ the main thread to the midi thread. Queues are safe for use between
+ threads as long as ONE thread writes and ONE thread reads. You must
+ NEVER allow two threads to write to the same queue.
+
+ This program transposes incoming midi data by an amount controlled
+ by the main program. To change the transposition, type an integer
+ followed by return. The main program sends this via a message queue
+ to the midi thread. To quit, type 'q' followed by return.
+
+ The midi thread can also send a pitch to the main program on request.
+ Type 'm' followed by return to wait for the next midi message and
+ print the pitch.
+
+ This program illustrates:
+ Midi processing in a high-priority thread.
+ Communication with a main process via message queues.
+
+ */
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "string.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "porttime.h"
+
+/* if INPUT_BUFFER_SIZE is 0, PortMidi uses a default value */
+#define INPUT_BUFFER_SIZE 0
+
+#define OUTPUT_BUFFER_SIZE 100
+#define DRIVER_INFO NULL
+#define TIME_PROC NULL
+#define TIME_INFO NULL
+/* use zero latency because we want output to be immediate */
+#define LATENCY 0
+
+#define STRING_MAX 80
+
+/**********************************/
+/* DATA USED ONLY BY process_midi */
+/* (except during initialization) */
+/**********************************/
+
+int active = FALSE;
+int monitor = FALSE;
+int midi_thru = TRUE;
+
+long transpose;
+PmStream *midi_in;
+PmStream *midi_out;
+
+/****************************/
+/* END OF process_midi DATA */
+/****************************/
+
+/* shared queues */
+PmQueue *midi_to_main;
+PmQueue *main_to_midi;
+
+#define QUIT_MSG 1000
+#define MONITOR_MSG 1001
+#define THRU_MSG 1002
+
+/* timer interrupt for processing midi data */
+void process_midi(PtTimestamp timestamp, void *userData)
+{
+ PmError result;
+ PmEvent buffer; /* just one message at a time */
+ long msg;
+
+ /* do nothing until initialization completes */
+ if (!active)
+ return;
+
+ /* check for messages */
+ do {
+ result = Pm_Dequeue(main_to_midi, &msg);
+ if (result) {
+ if (msg >= -127 && msg <= 127)
+ transpose = msg;
+ else if (msg == QUIT_MSG) {
+ /* acknowledge receipt of quit message */
+ Pm_Enqueue(midi_to_main, &msg);
+ active = FALSE;
+ return;
+ } else if (msg == MONITOR_MSG) {
+ /* main has requested a pitch. monitor is a flag that
+ * records the request:
+ */
+ monitor = TRUE;
+ } else if (msg == THRU_MSG) {
+ /* toggle Thru on or off */
+ midi_thru = !midi_thru;
+ }
+ }
+ } while (result);
+
+ /* see if there is any midi input to process */
+ do {
+ result = Pm_Poll(midi_in);
+ if (result) {
+ long status, data1, data2;
+ if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
+ continue;
+ if (midi_thru)
+ Pm_Write(midi_out, &buffer, 1);
+ /* unless there was overflow, we should have a message now */
+ status = Pm_MessageStatus(buffer.message);
+ data1 = Pm_MessageData1(buffer.message);
+ data2 = Pm_MessageData2(buffer.message);
+ if ((status & 0xF0) == 0x90 ||
+ (status & 0xF0) == 0x80) {
+
+ /* this is a note-on or note-off, so transpose and send */
+ data1 += transpose;
+
+ /* keep within midi pitch range, keep proper pitch class */
+ while (data1 > 127)
+ data1 -= 12;
+ while (data1 < 0)
+ data1 += 12;
+
+ /* send the message */
+ buffer.message = Pm_Message(status, data1, data2);
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* if monitor is set, send the pitch to the main thread */
+ if (monitor) {
+ Pm_Enqueue(midi_to_main, &data1);
+ monitor = FALSE; /* only send one pitch per request */
+ }
+ }
+ }
+ } while (result);
+}
+
+void exit_with_message(char *msg)
+{
+ char line[STRING_MAX];
+ printf("%s\n", msg);
+ fgets(line, STRING_MAX, stdin);
+ exit(1);
+}
+
+int main()
+{
+ int id;
+ long n;
+ const PmDeviceInfo *info;
+ char line[STRING_MAX];
+ int spin;
+ int done = FALSE;
+
+ /* determine what type of test to run */
+ printf("begin PortMidi multithread test...\n");
+
+ /* note that it is safe to call PortMidi from the main thread for
+ initialization and opening devices. You should not make any
+ calls to PortMidi from this thread once the midi thread begins.
+ to make PortMidi calls.
+ */
+
+ /* make the message queues */
+ /* messages can be of any size and any type, but all messages in
+ * a given queue must have the same size. We'll just use long's
+ * for our messages in this simple example
+ */
+ midi_to_main = Pm_QueueCreate(32, sizeof(long));
+ assert(midi_to_main != NULL);
+ main_to_midi = Pm_QueueCreate(32, sizeof(long));
+ assert(main_to_midi != NULL);
+
+ /* a little test of enqueue and dequeue operations. Ordinarily,
+ * you would call Pm_Enqueue from one thread and Pm_Dequeue from
+ * the other. Since the midi thread is not running, this is safe.
+ */
+ n = 1234567890;
+ Pm_Enqueue(midi_to_main, &n);
+ n = 987654321;
+ Pm_Enqueue(midi_to_main, &n);
+ Pm_Dequeue(midi_to_main, &n);
+ if (n != 1234567890) {
+ exit_with_message("Pm_Dequeue produced unexpected result.");
+ }
+ Pm_Dequeue(midi_to_main, &n);
+ if(n != 987654321) {
+ exit_with_message("Pm_Dequeue produced unexpected result.");
+ }
+
+ /* always start the timer before you start midi */
+ Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
+ /* the timer will call our function, process_midi() every millisecond */
+
+ Pm_Initialize();
+
+ id = Pm_GetDefaultOutputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default output device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening output device %s %s\n", info->interf, info->name);
+
+ /* use zero latency because we want output to be immediate */
+ Pm_OpenOutput(&midi_out,
+ id,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ LATENCY);
+
+ id = Pm_GetDefaultInputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default input device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening input device %s %s\n", info->interf, info->name);
+ Pm_OpenInput(&midi_in,
+ id,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+
+ active = TRUE; /* enable processing in the midi thread -- yes, this
+ is a shared variable without synchronization, but
+ this simple assignment is safe */
+
+ printf("Enter midi input; it will be transformed as specified by...\n");
+ printf("%s\n%s\n%s\n",
+ "Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or",
+ "type a number to specify transposition.",
+ "Must terminate with [ENTER]");
+
+ while (!done) {
+ long msg;
+ int len;
+ fgets(line, STRING_MAX, stdin);
+ /* remove the newline: */
+ len = strlen(line);
+ if (len > 0) line[len - 1] = 0; /* overwrite the newline char */
+ if (strcmp(line, "q") == 0) {
+ msg = QUIT_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ /* wait for acknowlegement */
+ do {
+ spin = Pm_Dequeue(midi_to_main, &msg);
+ } while (spin == 0); /* spin */ ;
+ done = TRUE; /* leave the command loop and wrap up */
+ } else if (strcmp(line, "m") == 0) {
+ msg = MONITOR_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ printf("Waiting for note...\n");
+ do {
+ spin = Pm_Dequeue(midi_to_main, &msg);
+ } while (spin == 0); /* spin */ ;
+ printf("... pitch is %ld\n", msg);
+ } else if (strcmp(line, "t") == 0) {
+ /* reading midi_thru asynchronously could give incorrect results,
+ e.g. if you type "t" twice before the midi thread responds to
+ the first one, but we'll do it this way anyway. Perhaps a more
+ correct way would be to wait for an acknowledgement message
+ containing the new state. */
+ printf("Setting THRU %s\n", (midi_thru ? "off" : "on"));
+ msg = THRU_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ } else if (sscanf(line, "%ld", &msg) == 1) {
+ if (msg >= -127 && msg <= 127) {
+ /* send transposition value */
+ printf("Transposing by %ld\n", msg);
+ Pm_Enqueue(main_to_midi, &msg);
+ } else {
+ printf("Transposition must be within -127...127\n");
+ }
+ } else {
+ printf("%s\n%s\n%s\n",
+ "Type 'q' to quit, 'm' to monitor next pitch, or",
+ "type a number to specify transposition.",
+ "Must terminate with [ENTER]");
+ }
+ }
+
+ /* at this point, midi thread is inactive and we need to shut down
+ * the midi input and output
+ */
+ Pt_Stop(); /* stop the timer */
+ Pm_QueueDestroy(midi_to_main);
+ Pm_QueueDestroy(main_to_midi);
+
+ /* Belinda! if close fails here, some memory is deleted, right??? */
+ Pm_Close(midi_in);
+ Pm_Close(midi_out);
+
+ printf("finished portMidi multithread test...enter any character to quit [RETURN]...");
+ fgets(line, STRING_MAX, stdin);
+ return 0;
+}
diff --git a/pd/portmidi/pm_test/midithread.dsp b/pd/portmidi/pm_test/midithread.dsp
new file mode 100644
index 00000000..7dc0a16f
--- /dev/null
+++ b/pd/portmidi/pm_test/midithread.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="midithread" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=midithread - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "midithread.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "midithread.mak" CFG="midithread - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "midithread - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "midithread - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "midithread - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "midithreadRelease"
+# PROP Intermediate_Dir "midithreadRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "midithread - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "midithreadDebug"
+# PROP BASE Intermediate_Dir "midithreadDebug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "midithreadDebug"
+# PROP Intermediate_Dir "midithreadDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "midithread - Win32 Release"
+# Name "midithread - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\midithread.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/midithru.c b/pd/portmidi/pm_test/midithru.c
new file mode 100644
index 00000000..270246fb
--- /dev/null
+++ b/pd/portmidi/pm_test/midithru.c
@@ -0,0 +1,364 @@
+/* midithru.c -- example program implementing background thru processing */
+
+/* suppose you want low-latency midi-thru processing, but your application
+ wants to take advantage of the input buffer and timestamped data so that
+ it does not have to operate with very low latency.
+
+ This program illustrates how to use a timer callback from PortTime to
+ implement a low-latency process that handles midi thru, including correctly
+ merging midi data from the application with midi data from the input port.
+
+ The main application, which runs in the main program thread, will use an
+ interface similar to that of PortMidi, but since PortMidi does not allow
+ concurrent threads to share access to a stream, the application will
+ call private methods that transfer MIDI messages to and from the timer
+ thread. All PortMidi API calls are made from the timer thread.
+ */
+
+/* DESIGN
+
+All setup will be done by the main thread. Then, all direct access to
+PortMidi will be handed off to the timer callback thread.
+
+After this hand-off, the main thread will get/send messages via a queue.
+
+The goal is to send incoming messages to the midi output while merging
+any midi data generated by the application. Sysex is a problem here
+because you cannot insert (merge) a midi message while a sysex is in
+progress. There are at least three ways to implement midi thru with
+sysex messages:
+
+1) Turn them off. If your application does not need them, turn them off
+ with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will
+ not receive sysex (or active sensing messages), so you will not have
+ to handle them.
+
+2) Make them atomic. As you receive sysex messages, copy the data into
+ a (big) buffer. Ideally, expand the buffer as needed -- sysex messages
+ do not have any maximum length. Even more ideally, use a list structure
+ and real-time memory allocation to avoid latency in the timer thread.
+ When a full sysex message is received, send it to the midi output all
+ at once.
+
+3) Process sysex incrementally. Send sysex data to midi output as it
+ arrives. Block any non-real-time messages from the application until
+ the sysex message completes. There is the risk that an incomplete
+ sysex message will block messages forever, so implement a 5-second
+ timeout: if no sysex data is seen for 5 seconds, release the block,
+ possibly losing the rest of the sysex message.
+
+ Application messages must be processed similarly: once started, a
+ sysex message will block MIDI THRU processing. We will assume that
+ the application will not abort a sysex message, so timeouts are not
+ necessary here.
+
+This code implements (3).
+
+Latency is also an issue. PortMidi requires timestamps to be in
+non-decreasing order. Since we'll be operating with a low-latency
+timer thread, we can just set the latency to zero meaning timestamps
+are ignored by PortMidi. This will allow thru to go through with
+minimal latency. The application, however, needs to use timestamps
+because we assume it is high latency (the whole purpose of this
+example is to illustrate how to get low-latency thru with a high-latency
+application.) So the callback thread will implement midi timing by
+observing timestamps. The current timestamp will be available in the
+global variable current_timestamp.
+
+*/
+
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "string.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "porttime.h"
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+/* active is set true when midi processing should start */
+int active = FALSE;
+/* process_midi_exit_flag is set when the timer thread shuts down */
+int process_midi_exit_flag;
+
+PmStream *midi_in;
+PmStream *midi_out;
+
+/* shared queues */
+#define IN_QUEUE_SIZE 1024
+#define OUT_QUEUE_SIZE 1024
+PmQueue *in_queue;
+PmQueue *out_queue;
+PmTimestamp current_timestamp = 0;
+int thru_sysex_in_progress = FALSE;
+int app_sysex_in_progress = FALSE;
+PmTimestamp last_timestamp = 0;
+
+
+/* time proc parameter for Pm_MidiOpen */
+long midithru_time_proc(void *info)
+{
+ return current_timestamp;
+}
+
+
+/* timer interrupt for processing midi data.
+ Incoming data is delivered to main program via in_queue.
+ Outgoing data from main program is delivered via out_queue.
+ Incoming data from midi_in is copied with low latency to midi_out.
+ Sysex messages from either source block messages from the other.
+ */
+void process_midi(PtTimestamp timestamp, void *userData)
+{
+ PmError result;
+ PmEvent buffer; /* just one message at a time */
+
+ current_timestamp++; /* update every millisecond */
+ /* if (current_timestamp % 1000 == 0)
+ printf("time %d\n", current_timestamp); */
+
+ /* do nothing until initialization completes */
+ if (!active) {
+ /* this flag signals that no more midi processing will be done */
+ process_midi_exit_flag = TRUE;
+ return;
+ }
+
+ /* see if there is any midi input to process */
+ if (!app_sysex_in_progress) {
+ do {
+ result = Pm_Poll(midi_in);
+ if (result) {
+ long status;
+ if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
+ continue;
+
+ /* record timestamp of most recent data */
+ last_timestamp = current_timestamp;
+
+ /* the data might be the end of a sysex message that
+ has timed out, in which case we must ignore it.
+ It's a continuation of a sysex message if status
+ is actually a data byte (high-order bit is zero). */
+ status = Pm_MessageStatus(buffer.message);
+ if (((status & 0x80) == 0) && !thru_sysex_in_progress) {
+ continue; /* ignore this data */
+ }
+
+ /* implement midi thru */
+ /* note that you could output to multiple ports or do other
+ processing here if you wanted
+ */
+ /* printf("thru: %x\n", buffer.message); */
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* send the message to the application */
+ /* you might want to filter clock or active sense messages here
+ to avoid sending a bunch of junk to the application even if
+ you want to send it to MIDI THRU
+ */
+ Pm_Enqueue(in_queue, &buffer);
+
+ /* sysex processing */
+ if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE;
+ else if ((status & 0xF8) != 0xF8) {
+ /* not MIDI_SYSEX and not real-time, so */
+ thru_sysex_in_progress = FALSE;
+ }
+ if (thru_sysex_in_progress && /* look for EOX */
+ (((buffer.message & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 8) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 16) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) {
+ thru_sysex_in_progress = FALSE;
+ }
+ }
+ } while (result);
+ }
+
+
+ /* see if there is application midi data to process */
+ while (!Pm_QueueEmpty(out_queue)) {
+ /* see if it is time to output the next message */
+ PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue);
+ assert(next); /* must be non-null because queue is not empty */
+ if (next->timestamp <= current_timestamp) {
+ /* time to send a message, first make sure it's not blocked */
+ long status = Pm_MessageStatus(buffer.message);
+ if ((status & 0xF8) == 0xF8) {
+ ; /* real-time messages are not blocked */
+ } else if (thru_sysex_in_progress) {
+ /* maybe sysex has timed out (output becomes unblocked) */
+ if (last_timestamp + 5000 < current_timestamp) {
+ thru_sysex_in_progress = FALSE;
+ } else break; /* output is blocked, so exit loop */
+ }
+ Pm_Dequeue(out_queue, &buffer);
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* inspect message to update app_sysex_in_progress */
+ if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE;
+ else if ((status & 0xF8) != 0xF8) {
+ /* not MIDI_SYSEX and not real-time, so */
+ app_sysex_in_progress = FALSE;
+ }
+ if (app_sysex_in_progress && /* look for EOX */
+ (((buffer.message & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 8) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 16) & 0xFF) == MIDI_EOX) ||
+ (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) {
+ app_sysex_in_progress = FALSE;
+ }
+ } else break; /* wait until indicated timestamp */
+ }
+}
+
+
+void exit_with_message(char *msg)
+{
+#define STRING_MAX 80
+ char line[STRING_MAX];
+ printf("%s\nType ENTER...", msg);
+ fgets(line, STRING_MAX, stdin);
+ exit(1);
+}
+
+
+void initialize()
+/* set up midi processing thread and open midi streams */
+{
+ /* note that it is safe to call PortMidi from the main thread for
+ initialization and opening devices. You should not make any
+ calls to PortMidi from this thread once the midi thread begins.
+ to make PortMidi calls.
+ */
+
+ /* note that this routine provides minimal error checking. If
+ you use the PortMidi library compiled with PM_CHECK_ERRORS,
+ then error messages will be printed and the program will exit
+ if an error is encountered. Otherwise, you should add some
+ error checking to this code.
+ */
+
+ const PmDeviceInfo *info;
+ int id;
+
+ /* make the message queues */
+ in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent));
+ assert(in_queue != NULL);
+ out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent));
+ assert(out_queue != NULL);
+
+ /* always start the timer before you start midi */
+ Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
+ /* the timer will call our function, process_midi() every millisecond */
+
+ Pm_Initialize();
+
+ id = Pm_GetDefaultOutputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default output device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening output device %s %s\n", info->interf, info->name);
+
+ /* use zero latency because we want output to be immediate */
+ Pm_OpenOutput(&midi_out,
+ id,
+ NULL /* driver info */,
+ OUT_QUEUE_SIZE,
+ &midithru_time_proc,
+ NULL /* time info */,
+ 0 /* Latency */);
+
+ id = Pm_GetDefaultInputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default input device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening input device %s %s\n", info->interf, info->name);
+ Pm_OpenInput(&midi_in,
+ id,
+ NULL /* driver info */,
+ 0 /* use default input size */,
+ &midithru_time_proc,
+ NULL /* time info */);
+ /* Note: if you set a filter here, then this will filter what goes
+ to the MIDI THRU port. You may not want to do this.
+ */
+ Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+
+ active = TRUE; /* enable processing in the midi thread -- yes, this
+ is a shared variable without synchronization, but
+ this simple assignment is safe */
+
+}
+
+
+void finalize()
+{
+ /* the timer thread could be in the middle of accessing PortMidi stuff */
+ /* to detect that it is done, we first clear process_midi_exit_flag and
+ then wait for the timer thread to set it
+ */
+ process_midi_exit_flag = FALSE;
+ active = FALSE;
+ /* busy wait for flag from timer thread that it is done */
+ while (!process_midi_exit_flag) ;
+ /* at this point, midi thread is inactive and we need to shut down
+ * the midi input and output
+ */
+ Pt_Stop(); /* stop the timer */
+ Pm_QueueDestroy(in_queue);
+ Pm_QueueDestroy(out_queue);
+
+ Pm_Close(midi_in);
+ Pm_Close(midi_out);
+
+ Pm_Terminate();
+}
+
+
+int main(int argc, char *argv[])
+{
+ PmTimestamp last_time = 0;
+ PmEvent buffer;
+
+ /* determine what type of test to run */
+ printf("begin PortMidi midithru program...\n");
+
+ initialize(); /* set up and start midi processing */
+
+ printf("%s\n%s\n",
+ "This program will run for 60 seconds, or until you play middle C,",
+ "echoing all input with a 2 second delay.");
+
+ while (current_timestamp < 60000) {
+ /* just to make the point that this is not a low-latency process,
+ spin until half a second has elapsed */
+ last_time = last_time + 500;
+ while (last_time > current_timestamp) ;
+
+ /* now read data and send it after changing timestamps */
+ while (Pm_Dequeue(in_queue, &buffer) == 1) {
+ /* printf("timestamp %d\n", buffer.timestamp); */
+ /* printf("message %x\n", buffer.message); */
+ buffer.timestamp = buffer.timestamp + 2000; /* delay */
+ Pm_Enqueue(out_queue, &buffer);
+ /* play middle C to break out of loop */
+ if (Pm_MessageStatus(buffer.message) == 0x90 &&
+ Pm_MessageData1(buffer.message) == 60) {
+ goto quit_now;
+ }
+ }
+ }
+quit_now:
+ finalize();
+ exit_with_message("finished PortMidi midithru program.");
+ return 0; /* never executed, but keeps the compiler happy */
+}
diff --git a/pd/portmidi/pm_test/midithru.dsp b/pd/portmidi/pm_test/midithru.dsp
new file mode 100644
index 00000000..83f28cfc
--- /dev/null
+++ b/pd/portmidi/pm_test/midithru.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="midithru" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=midithru - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "midithru.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "midithru.mak" CFG="midithru - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "midithru - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "midithru - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "midithru - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "midithruRelease"
+# PROP Intermediate_Dir "midithruRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "midithru - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "midithruDebug"
+# PROP BASE Intermediate_Dir "midithruDebug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "midithruDebug"
+# PROP Intermediate_Dir "midithruDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "midithru - Win32 Release"
+# Name "midithru - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\midithru.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/midithru.dsw b/pd/portmidi/pm_test/midithru.dsw
new file mode 100644
index 00000000..b244a104
--- /dev/null
+++ b/pd/portmidi/pm_test/midithru.dsw
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "midithread"=.\midithru.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/pd/portmidi/pm_test/sysex.c b/pd/portmidi/pm_test/sysex.c
new file mode 100644
index 00000000..f49bf962
--- /dev/null
+++ b/pd/portmidi/pm_test/sysex.c
@@ -0,0 +1,319 @@
+/* sysex.c -- example program showing how to send and receive sysex
+ messages
+
+ Messages are stored in a file using 2-digit hexadecimal numbers,
+ one per byte, separated by blanks, with up to 32 numbers per line:
+ F0 14 A7 4B ...
+
+ */
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "porttime.h"
+#include "string.h"
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+#define STRING_MAX 80
+
+int latency = 0;
+
+/* read a number from console */
+/**/
+int get_number(char *prompt)
+{
+ char line[STRING_MAX];
+ int n = 0, i;
+ printf(prompt);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+
+ }
+ return i;
+}
+
+
+/* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */
+/**/
+void loopback_test()
+{
+ int outp;
+ int inp;
+ PmStream *midi_in;
+ PmStream *midi_out;
+ unsigned char msg[1024];
+ char line[80];
+ long len;
+ int i;
+ int data;
+ PmEvent event;
+ int shift;
+
+ Pt_Start(1, 0, 0);
+
+ printf("Connect a midi cable from an output port to an input port.\n");
+ printf("This test will send random data via sysex message from output\n");
+ printf("to input and check that the correct data was received.\n");
+ outp = get_number("Type output device number: ");
+ /* Open output with 1ms latency -- when latency is non-zero, the Win32
+ implementation supports sending sysex messages incrementally in a
+ series of buffers. This is nicer than allocating a big buffer for the
+ message, and it also seems to work better. Either way works.
+ */
+ Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
+ inp = get_number("Type input device number: ");
+ /* since we are going to send and then receive, make sure the input buffer
+ is large enough for the entire message */
+ Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
+
+ srand((unsigned int) Pt_Time()); /* seed for random numbers */
+
+ while (1) {
+ PmError count;
+ long start_time;
+ long error_position;
+ long expected = 0;
+ long actual = 0;
+ printf("Type return to send message, q to quit: ");
+ fgets(line, STRING_MAX, stdin);
+ if (line[0] == 'q') goto cleanup;
+
+ /* compose the message */
+ len = rand() % 998 + 2; /* len only counts data bytes */
+ msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */
+ /* data bytes go from 1 to len */
+ for (i = 0; i < len; i++) {
+ msg[i + 1] = rand() & 0x7f; /* MIDI data */
+ }
+ /* final EOX goes in len+1, total of len+2 bytes in msg */
+ msg[len + 1] = (char) MIDI_EOX;
+
+ /* sanity check: before we send, there should be no queued data */
+ count = Pm_Read(midi_in, &event, 1);
+
+ if (count != 0) {
+ printf("Before sending anything, a MIDI message was found in\n");
+ printf("the input buffer. Please try again.\n");
+ break;
+ }
+
+ /* send the message */
+ printf("Sending %ld byte sysex message.\n", len + 2);
+ Pm_WriteSysEx(midi_out, 0, msg);
+
+ /* receive the message and compare to msg[] */
+ data = 0;
+ shift = 0;
+ i = 0;
+ start_time = Pt_Time();
+ error_position = -1;
+ /* allow up to 2 seconds for transmission */
+ while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) {
+ count = Pm_Read(midi_in, &event, 1);
+ /* CAUTION: this causes busy waiting. It would be better to
+ be in a polling loop to avoid being compute bound. PortMidi
+ does not support a blocking read since this is so seldom
+ useful. There is no timeout, so if we don't receive a sysex
+ message, or at least an EOX, the program will hang here.
+ */
+ if (count == 0) continue;
+
+ /* printf("read %lx ", event.message);
+ fflush(stdout); */
+
+ /* compare 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ data = (event.message >> shift) & 0xFF;
+ if (data != msg[i] && error_position < 0) {
+ error_position = i;
+ expected = msg[i];
+ actual = data;
+ }
+ i++;
+ }
+ }
+ if (error_position >= 0) {
+ printf("Error at byte %ld: sent %lx recd %lx\n", error_position, expected, actual);
+ } else if (i != len + 2) {
+ printf("Error: byte %d not received\n", i);
+ } else {
+ printf("Correctly ");
+ }
+ printf("received %d byte sysex message.\n", i);
+ }
+cleanup:
+ Pm_Close(midi_out);
+ Pm_Close(midi_in);
+ return;
+}
+
+
+#define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8)
+
+
+void receive_sysex()
+{
+ char line[80];
+ FILE *f;
+ PmStream *midi;
+ int shift = 0;
+ int data = 0;
+ int bytes_on_line = 0;
+ PmEvent msg;
+
+ /* determine which output device to use */
+ int i = get_number("Type input device number: ");
+
+ /* open input device */
+ Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL);
+ printf("Midi Input opened, type file for sysex data: ");
+
+ /* open file */
+ fgets(line, STRING_MAX, stdin);
+ /* remove the newline character */
+ if (strlen(line) > 0) line[strlen(line) - 1] = 0;
+ f = fopen(line, "w");
+ if (!f) {
+ printf("Could not open %s\n", line);
+ Pm_Close(midi);
+ return;
+ }
+
+ printf("Ready to receive a sysex message\n");
+
+ /* read data and write to file */
+ while (data != MIDI_EOX) {
+ PmError count;
+ count = Pm_Read(midi, &msg, 1);
+ /* CAUTION: this causes busy waiting. It would be better to
+ be in a polling loop to avoid being compute bound. PortMidi
+ does not support a blocking read since this is so seldom
+ useful.
+ */
+ if (count == 0) continue;
+ /* ignore real-time messages */
+ if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue;
+
+ /* write 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ data = (msg.message >> shift) & 0xFF;
+ /* if this is a status byte that's not MIDI_EOX, the sysex
+ message is incomplete and there is no more sysex data */
+ if (data & 0x80 && data != MIDI_EOX) break;
+ fprintf(f, "%2x ", data);
+ if (++bytes_on_line >= 16) {
+ fprintf(f, "\n");
+ bytes_on_line = 0;
+ }
+ }
+ }
+ fclose(f);
+ Pm_Close(midi);
+}
+
+
+void send_sysex()
+{
+ char line[80];
+ FILE *f;
+ PmStream *midi;
+ int data;
+ int shift = 0;
+ PmEvent msg;
+
+ /* determine which output device to use */
+ int i = get_number("Type output device number: ");
+
+ msg.timestamp = 0; /* no need for timestamp */
+
+ /* open output device */
+ Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency);
+ printf("Midi Output opened, type file with sysex data: ");
+
+ /* open file */
+ fgets(line, STRING_MAX, stdin);
+ /* remove the newline character */
+ if (strlen(line) > 0) line[strlen(line) - 1] = 0;
+ f = fopen(line, "r");
+ if (!f) {
+ printf("Could not open %s\n", line);
+ Pm_Close(midi);
+ return;
+ }
+
+ /* read file and send data */
+ msg.message = 0;
+ while (1) {
+ /* get next byte from file */
+
+ if (fscanf(f, "%x", &data) == 1) {
+ /* printf("read %x, ", data); */
+ /* OR byte into message at proper offset */
+ msg.message |= (data << shift);
+ shift += 8;
+ }
+ /* send the message if it's full (shift == 32) or if we are at end */
+ if (shift == 32 || data == MIDI_EOX) {
+ /* this will send sysex data 4 bytes at a time -- it would
+ be much more efficient to send multiple PmEvents at once
+ but this method is simpler. See Pm_WriteSysex for a more
+ efficient code example.
+ */
+ Pm_Write(midi, &msg, 1);
+ msg.message = 0;
+ shift = 0;
+ }
+ if (data == MIDI_EOX) { /* end of message */
+ fclose(f);
+ Pm_Close(midi);
+ return;
+ }
+ }
+}
+
+
+int main()
+{
+ int i;
+ char line[80];
+
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) printf(" (input)");
+ if (info->output) printf(" (output)");
+ printf("\n");
+ }
+ latency = get_number("Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ");
+ while (1) {
+ printf("Type r to receive sysex, s to send,"
+ " l for loopback test, q to quit: ");
+ fgets(line, STRING_MAX, stdin);
+ switch (line[0]) {
+ case 'r':
+ receive_sysex();
+ break;
+ case 's':
+ send_sysex();
+ break;
+ case 'l':
+ loopback_test();
+ case 'q':
+ exit(0);
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+
+
+
+
diff --git a/pd/portmidi/pm_test/sysex.dsp b/pd/portmidi/pm_test/sysex.dsp
new file mode 100644
index 00000000..329d3ef9
--- /dev/null
+++ b/pd/portmidi/pm_test/sysex.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="sysex" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=sysex - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "sysex.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "sysex.mak" CFG="sysex - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "sysex - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "sysex - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "sysex - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "sysexRelease"
+# PROP Intermediate_Dir "sysexRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\pm_common" /I "..\porttime" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\Release\portmidi.lib ..\porttime\Release\porttime.lib ..\pm_win\Release\pm_dll.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "sysex - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "sysexDebug"
+# PROP Intermediate_Dir "sysexDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\pm_common" /I "..\porttime" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "sysex - Win32 Release"
+# Name "sysex - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\sysex.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/test.c b/pd/portmidi/pm_test/test.c
new file mode 100644
index 00000000..ade8564d
--- /dev/null
+++ b/pd/portmidi/pm_test/test.c
@@ -0,0 +1,469 @@
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define INPUT_BUFFER_SIZE 100
+#define OUTPUT_BUFFER_SIZE 0
+#define DRIVER_INFO NULL
+#define TIME_PROC ((long (*)(void *)) Pt_Time)
+#define TIME_INFO NULL
+#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
+
+#define STRING_MAX 80 /* used for console input */
+
+long latency = 0;
+
+/* crash the program to test whether midi ports are closed */
+/**/
+void doSomethingReallyStupid() {
+ int * tmp = NULL;
+ *tmp = 5;
+}
+
+
+/* exit the program without any explicit cleanup */
+/**/
+void doSomethingStupid() {
+ assert(0);
+}
+
+
+/* read a number from console */
+/**/
+int get_number(char *prompt)
+{
+ char line[STRING_MAX];
+ int n = 0, i;
+ printf(prompt);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+
+ }
+ return i;
+}
+
+
+/*
+ * the somethingStupid parameter can be set to simulate a program crash.
+ * We want PortMidi to close Midi ports automatically in the event of a
+ * crash because Windows does not (and this may cause an OS crash)
+ */
+void main_test_input(unsigned int somethingStupid) {
+ PmStream * midi;
+ PmError status, length;
+ PmEvent buffer[1];
+ int num = 10;
+ int i = get_number("Type input number: ");
+ /* It is recommended to start timer before Midi; otherwise, PortMidi may
+ start the timer with its (default) parameters
+ */
+ TIME_START;
+
+ /* open input device */
+ Pm_OpenInput(&midi,
+ i,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+
+ printf("Midi Input opened. Reading %d Midi messages...\n",num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Poll(midi)) {
+ Pm_Read(midi, buffer, 1);
+ }
+ /* now start paying attention to messages */
+ i = 0; /* count messages as they arrive */
+ while (i < num) {
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi,buffer, 1);
+ if (length > 0) {
+ printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
+ i,
+ buffer[0].timestamp,
+ Pm_MessageStatus(buffer[0].message),
+ Pm_MessageData1(buffer[0].message),
+ Pm_MessageData2(buffer[0].message));
+ i++;
+ } else {
+ assert(0);
+ }
+ }
+ /* simulate crash if somethingStupid is 1 or 2 */
+ if ((i > (num/2)) && (somethingStupid == 1)) {
+ doSomethingStupid();
+ } else if ((i > (num/2)) && (somethingStupid == 2)) {
+ doSomethingReallyStupid();
+ }
+ }
+
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close...");
+
+ Pm_Close(midi);
+ printf("done closing...");
+}
+
+
+
+void main_test_output() {
+ PmStream * midi;
+ char line[80];
+ long off_time;
+ int chord[] = { 60, 67, 76, 83, 90 };
+ #define chord_size 5
+ PmEvent buffer[chord_size];
+ PmTimestamp timestamp;
+
+ /* determine which output device to use */
+ int i = get_number("Type output number: ");
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device -- since PortMidi avoids opening a timer
+ when latency is zero, we will pass in a NULL timer pointer
+ for that case. If PortMidi tries to access the time_proc,
+ we will crash, so this test will tell us something. */
+ Pm_OpenOutput(&midi,
+ i,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ (latency == 0 ? NULL : TIME_PROC),
+ (latency == 0 ? NULL : TIME_INFO),
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", latency);
+
+ /* output note on/off w/latency offset; hold until user prompts */
+ printf("ready to send program 1 change... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ /* if we were writing midi for immediate output, we could always use
+ timestamps of zero, but since we may be writing with latency, we
+ will explicitly set the timestamp to "now" by getting the time.
+ The source of timestamps should always correspond to the TIME_PROC
+ and TIME_INFO parameters used in Pm_OpenOutput(). */
+ buffer[0].timestamp = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0xC0, 0, 0);
+ Pm_Write(midi, buffer, 1);
+
+ printf("ready to note-on... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ buffer[0].timestamp = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0x90, 60, 100);
+ Pm_Write(midi, buffer, 1);
+ printf("ready to note-off... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ buffer[0].timestamp = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ Pm_Write(midi, buffer, 1);
+
+ /* output short note on/off w/latency offset; hold until user prompts */
+ printf("ready to note-on (short form)... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ Pm_WriteShort(midi, TIME_PROC(TIME_INFO),
+ Pm_Message(0x90, 60, 100));
+ printf("ready to note-off (short form)... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ Pm_WriteShort(midi, TIME_PROC(TIME_INFO),
+ Pm_Message(0x90, 60, 0));
+
+ /* output several note on/offs to test timing.
+ Should be 1s between notes */
+ printf("chord will arpeggiate if latency > 0\n");
+ printf("ready to chord-on/chord-off... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ timestamp = TIME_PROC(TIME_INFO);
+ for (i = 0; i < chord_size; i++) {
+ buffer[i].timestamp = timestamp + 1000 * i;
+ buffer[i].message = Pm_Message(0x90, chord[i], 100);
+ }
+ Pm_Write(midi, buffer, chord_size);
+
+ off_time = timestamp + 1000 + chord_size * 1000;
+ while (TIME_PROC(TIME_INFO) < off_time)
+ /* busy wait */;
+ for (i = 0; i < chord_size; i++) {
+ buffer[i].timestamp = timestamp + 1000 * i;
+ buffer[i].message = Pm_Message(0x90, chord[i], 0);
+ }
+ Pm_Write(midi, buffer, chord_size);
+
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close and terminate... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+
+ Pm_Close(midi);
+ Pm_Terminate();
+ printf("done closing and terminating...\n");
+}
+
+
+void main_test_both()
+{
+ int i = 0;
+ int in, out;
+ PmStream * midi, * midiOut;
+ PmEvent buffer[1];
+ PmError status, length;
+ int num = 10;
+
+ in = get_number("Type input number: ");
+ out = get_number("Type output number: ");
+
+ /* In is recommended to start timer before PortMidi */
+ TIME_START;
+
+ Pm_OpenOutput(&midiOut,
+ out,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", latency);
+ /* open input device */
+ Pm_OpenInput(&midi,
+ in,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+ printf("Midi Input opened. Reading %d Midi messages...\n",num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Poll(midi)) {
+ Pm_Read(midi, buffer, 1);
+ }
+ i = 0;
+ while (i < num) {
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi,buffer,1);
+ if (length > 0) {
+ Pm_Write(midiOut, buffer, 1);
+ printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
+ i,
+ buffer[0].timestamp,
+ Pm_MessageStatus(buffer[0].message),
+ Pm_MessageData1(buffer[0].message),
+ Pm_MessageData2(buffer[0].message));
+ i++;
+ } else {
+ assert(0);
+ }
+ }
+ }
+
+ /* since close device should not needed, lets get
+ rid of it just to make sure program exit closes MIDI devices */
+ /* Pm_Close(midi);
+ Pm_Close(midiOut);
+ Pm_Terminate(); */
+}
+
+
+/* main_test_stream exercises windows winmm API's stream mode */
+/* The winmm stream mode is used for latency>0, and sends
+ timestamped messages. The timestamps are relative (delta)
+ times, whereas PortMidi times are absolute. Since peculiar
+ things happen when messages are not always sent in advance,
+ this function allows us to exercise the system and test it.
+ */
+void main_test_stream() {
+ PmStream * midi;
+ char line[80];
+ PmEvent buffer[16];
+
+ /* determine which output device to use */
+ int i = get_number("Type output number: ");
+
+ latency = 500; /* ignore LATENCY for this test and
+ fix the latency at 500ms */
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device */
+ Pm_OpenOutput(&midi,
+ i,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", latency);
+
+ /* output note on/off w/latency offset; hold until user prompts */
+ printf("ready to send output... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+
+ /* if we were writing midi for immediate output, we could always use
+ timestamps of zero, but since we may be writing with latency, we
+ will explicitly set the timestamp to "now" by getting the time.
+ The source of timestamps should always correspond to the TIME_PROC
+ and TIME_INFO parameters used in Pm_OpenOutput(). */
+ buffer[0].timestamp = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0xC0, 0, 0);
+ buffer[1].timestamp = buffer[0].timestamp;
+ buffer[1].message = Pm_Message(0x90, 60, 100);
+ buffer[2].timestamp = buffer[0].timestamp + 1000;
+ buffer[2].message = Pm_Message(0x90, 62, 100);
+ buffer[3].timestamp = buffer[0].timestamp + 2000;
+ buffer[3].message = Pm_Message(0x90, 64, 100);
+ buffer[4].timestamp = buffer[0].timestamp + 3000;
+ buffer[4].message = Pm_Message(0x90, 66, 100);
+ buffer[5].timestamp = buffer[0].timestamp + 4000;
+ buffer[5].message = Pm_Message(0x90, 60, 0);
+ buffer[6].timestamp = buffer[0].timestamp + 4000;
+ buffer[6].message = Pm_Message(0x90, 62, 0);
+ buffer[7].timestamp = buffer[0].timestamp + 4000;
+ buffer[7].message = Pm_Message(0x90, 64, 0);
+ buffer[8].timestamp = buffer[0].timestamp + 4000;
+ buffer[8].message = Pm_Message(0x90, 66, 0);
+
+ Pm_Write(midi, buffer, 9);
+#ifdef SEND8
+ /* Now, we're ready for the real test.
+ Play 4 notes at now, now+500, now+1000, and now+1500
+ Then wait until now+2000.
+ Play 4 more notes as before.
+ We should hear 8 evenly spaced notes. */
+ now = TIME_PROC(TIME_INFO);
+ for (i = 0; i < 4; i++) {
+ buffer[i * 2].timestamp = now + (i * 500);
+ buffer[i * 2].message = Pm_Message(0x90, 60, 100);
+ buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
+ buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
+ }
+ Pm_Write(midi, buffer, 8);
+
+ while (Pt_Time() < now + 2500)
+ /* busy wait */;
+ /* now we are 500 ms behind schedule, but since the latency
+ is 500, the delay should not be audible */
+ now += 2000;
+ for (i = 0; i < 4; i++) {
+ buffer[i * 2].timestamp = now + (i * 500);
+ buffer[i * 2].message = Pm_Message(0x90, 60, 100);
+ buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
+ buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
+ }
+ Pm_Write(midi, buffer, 8);
+#endif
+ /* close device (this not explicitly needed in most implementations) */
+ printf("ready to close and terminate... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+
+ Pm_Close(midi);
+ Pm_Terminate();
+ printf("done closing and terminating...\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: test [-h] [-l latency-in-ms]\n");
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ int i = 0, n = 0;
+ char line[STRING_MAX];
+ int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
+ int stream_test = 0;
+ int latency_valid = FALSE;
+
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-h") == 0) {
+ show_usage();
+ } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
+ i = i + 1;
+ latency = atoi(argv[i]);
+ printf("Latency will be %ld\n", latency);
+ latency_valid = TRUE;
+ } else {
+ show_usage();
+ }
+ }
+
+ while (!latency_valid) {
+ printf("Latency in ms: ");
+ if (scanf("%ld", &latency) == 1) {
+ latency_valid = TRUE;
+ }
+ }
+
+ /* determine what type of test to run */
+ printf("begin portMidi test...\n");
+ printf("%s%s%s%s%s",
+ "enter your choice...\n 1: test input\n",
+ " 2: test input (fail w/assert)\n",
+ " 3: test input (fail w/NULL assign)\n",
+ " 4: test output\n 5: test both\n",
+ " 6: stream test\n");
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+ switch(i) {
+ case 1:
+ test_input = 1;
+ break;
+ case 2:
+ test_input = 1;
+ somethingStupid = 1;
+ break;
+ case 3:
+ test_input = 1;
+ somethingStupid = 2;
+ break;
+ case 4:
+ test_output = 1;
+ break;
+ case 5:
+ test_both = 1;
+ break;
+ case 6:
+ stream_test = 1;
+ break;
+ default:
+ printf("got %d (invalid input)\n", n);
+ break;
+ }
+ }
+
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ if (((test_input | test_both) & info->input) |
+ ((test_output | test_both | stream_test) & info->output)) {
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) printf(" (input)");
+ if (info->output) printf(" (output)");
+ printf("\n");
+ }
+ }
+
+ /* run test */
+ if (stream_test) {
+ main_test_stream();
+ } else if (test_input) {
+ main_test_input(somethingStupid);
+ } else if (test_output) {
+ main_test_output();
+ } else if (test_both) {
+ main_test_both();
+ }
+
+ printf("finished portMidi test...type ENTER to quit...");
+ fgets(line, STRING_MAX, stdin);
+ return 0;
+}
diff --git a/pd/portmidi/pm_test/test.dsp b/pd/portmidi/pm_test/test.dsp
new file mode 100644
index 00000000..66ba3e91
--- /dev/null
+++ b/pd/portmidi/pm_test/test.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="test" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=test - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "test.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "test.mak" CFG="test - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "test - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "test - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "test - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "testRelease"
+# PROP Intermediate_Dir "testRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\Release\portmidi.lib ..\porttime\Release\porttime.lib ..\pm_win\Release\pm_dll.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "test - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "testDebug"
+# PROP Intermediate_Dir "testDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "test - Win32 Release"
+# Name "test - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\test.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/txdata.syx b/pd/portmidi/pm_test/txdata.syx
new file mode 100644
index 00000000..1e06e5a6
--- /dev/null
+++ b/pd/portmidi/pm_test/txdata.syx
@@ -0,0 +1,257 @@
+20 0 1d 4 c 6 0 34 1 4d 4 d 1f 7 3 6
+ c 5e 4 4d d b 18 5 3 6 0 3d 1 4a 16 18
+1f 8 3 6 d 0 1 63 4 13 3a 23 0 0 0 2
+ c 2 4 0 63 32 0 0 0 32 0 47 72 61 6e 64
+50 69 61 6e 6f 63 63 63 32 32 32 0 0 0 0 0
+10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 9 9 f c 27 2 35 37 10 1f 4 3 4
+ d 19 4 56 5 16 1f f 8 d c 0 43 60 4 e
+1f c 3 7 e 0 43 63 5 10 3c 14 8 2 1b 56
+ 5 2 4 0 63 32 0 0 0 32 0 4c 6f 54 69 6e
+65 38 31 5a 20 63 63 63 32 32 32 0 7f 0 1 0
+18 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f e f e 9 0 3 43 2d e 1f f 5 7
+ f 16 43 5a 0 0 1f 12 6 8 d 0 3 63 4 0
+1f 12 6 8 f 0 2 63 4 6 34 14 0 1 2 4e
+18 2 4 0 63 32 0 32 0 32 0 44 79 6e 6f 6d
+69 74 65 45 50 63 63 63 32 32 32 0 70 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f b 1 b 8 18 40 5f a e 1f 1f 0 a
+ f 0 40 5f 4 0 1f 1f 0 a f 0 40 63 5 6
+1f 1f 0 a f 0 40 5f 0 8 1f 20 0 3 0 5a
+18 4 4 0 63 32 32 0 0 32 0 50 65 72 63 4f
+72 67 61 6e 20 63 63 63 32 32 32 0 0 0 0 0
+ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f b 7 f 9 0 4 49 13 13 1f 8 7 5
+ e 0 2 58 0 c 1f 6 4 6 f 23 3 46 10 a
+1f 7 8 c d 0 2 63 8 b 2 1c 0 0 0 52
+18 4 4 0 63 32 0 32 0 32 0 54 68 69 6e 20
+43 6c 61 76 20 63 63 63 32 32 32 0 70 0 20 0
+10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f c 0 6 1 a 4 50 20 e 1f c 0 6
+ 1 a 4 50 1f 8 1f b 9 5 e 0 2 63 5 e
+1f b 9 5 e 0 3 63 4 8 4 1a 0 0 0 52
+1d 2 4 0 63 32 0 32 0 32 0 42 72 69 74 65
+43 65 6c 73 74 63 63 63 32 32 32 0 20 0 26 0
+ 1 0 8 4 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 f 1f 4 8 f 0 3a 51 4 b e 1f 0 8
+ f 0 22 4b 4 3 f 1a b 8 d 0 3b 36 9 3
+12 1f 0 8 f 0 22 5d 4 b 3a 1e 19 5 0 52
+18 4 4 0 63 32 0 0 0 32 0 54 72 75 6d 70
+65 74 38 31 5a 63 63 63 32 32 32 0 0 0 50 0
+51 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 c 5 0 8 0 0 2 4a 4 b f 1f 0 8
+ f 0 2 3f 4 3 1f f 0 8 0 23 3 44 b 3
+10 1f 0 9 f 0 2 5e 4 c 3a 1f 19 7 0 52
+18 4 4 0 63 32 0 0 0 32 0 46 6c 75 67 65
+6c 68 6f 72 6e 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 10 1f 0 8 f 0 42 4a 0 3 11 1f 0 8
+ f a 43 51 0 3 11 9 0 8 d 0 42 2b 16 6
+10 1f 0 9 f 0 42 63 4 b 3a 1e 9 9 0 5a
+24 4 4 0 63 32 31 0 0 32 0 52 61 73 70 41
+6c 74 6f 20 20 63 63 63 32 32 32 0 10 0 20 0
+54 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 10 9 2 6 d 0 41 3e 4 15 c b 2 3
+ e 0 41 4f 4 12 c e 2 8 d 0 42 4b a 1c
+ d b 1 9 e 0 3 63 a 14 0 23 f 2 1b 5e
+18 4 5 0 63 28 50 32 0 32 0 48 61 72 6d 6f
+6e 69 63 61 20 63 63 63 32 32 32 0 50 10 50 0
+50 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1c 2 0 4 e 63 0 4e 4 3 d 5 0 6
+ e 63 1 56 a 8 12 7 0 6 9 63 2 47 1b e
+ a a 0 5 f 0 1 63 4 b 32 1a 8 d 0 52
+ c 4 4 0 63 32 0 0 0 32 0 44 6f 75 62 6c
+65 42 61 73 73 63 63 63 32 32 32 0 10 0 0 0
+ 3 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 b 4 0 4 f 14 2 49 9 6 a 7 0 4
+ f 14 2 51 a 0 8 1f 0 5 f 0 1 63 9 6
+ a 1f 0 5 f 0 1 63 a 0 3c 1f 6 9 0 52
+ 5 4 4 0 63 32 0 0 0 32 0 48 69 53 74 72
+69 6e 67 20 31 63 63 63 32 32 32 0 2 0 30 0
+32 0 10 5 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 10 13 f 4 a 0 3 3b 14 14 1f e 8 7
+ 9 0 2 42 5 e 18 13 d 9 c 0 2 3c 13 8
+1f 11 7 4 f 0 42 63 4 10 3a 1b 0 0 0 52
+1d 4 4 0 63 32 0 0 0 32 0 48 61 72 70 20
+20 20 20 20 20 63 63 63 32 32 32 8 0 0 21 0
+ 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 6 6 4 f 0 40 48 5 0 c 8 7 5
+ f 5 0 52 4 0 f 7 3 7 e 8 3 63 4 6
+ f 8 4 5 f 0 3 63 4 6 7c 1f 0 6 0 4a
+11 2 4 0 63 32 0 0 0 32 0 46 61 6e 66 61
+72 54 70 74 73 63 63 63 32 32 32 6 1 0 38 0
+ 8 0 48 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 d b 0 1 c 0 2 2c 3d 3 d 7 0 1
+ c 0 2 1f 3c 3 d 1f 0 5 f 0 2 63 5 6
+ d 1f 0 5 f 0 2 63 4 0 3c 63 0 2f 0 53
+11 4 4 0 63 32 0 0 0 32 0 42 72 65 61 74
+68 4f 72 67 6e 63 63 63 32 32 32 4 30 5 50 0
+11 0 18 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 9 0 6 0 27 2 51 19 b 1c 6 0 8
+ 0 37 2 47 a 3 1f a 0 9 0 3d 2 4d a e
+1f 12 8 8 f 0 3 61 4 b 28 1f 0 3 0 52
+ c 3 4 0 63 32 1 32 0 32 0 4e 79 6c 6f 6e
+47 75 69 74 20 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f e e f f 0 3 48 2d 6 1f f 4 f
+ f 25 3 5b 0 0 1f 12 6 c e 1c 3 55 0 10
+1f 13 7 8 e 6 4 62 4 e 3b 14 0 0 0 42
+18 2 4 0 63 32 0 32 0 32 0 47 75 69 74 61
+72 20 23 31 20 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 19 8 a 3 0 3 63 10 18 1f c 5 b
+ 5 0 3 52 0 b 1f 19 6 b 5 0 3 63 a 16
+1f f 11 9 7 0 4 63 4 3 3a 14 0 0 0 42
+18 2 4 0 63 32 0 32 0 32 0 46 75 6e 6b 79
+20 50 69 63 6b 63 63 63 32 32 32 0 30 0 0 0
+ 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 1 0 8 4 0 3 3d a 1e 1f 1 0 8
+ 0 0 0 43 0 10 1f 9 6 8 c 1b 7 46 1c 1e
+1f 9 0 9 9 0 1 63 4 3 3a 1c 0 0 0 52
+ c 4 5 0 63 4b 0 0 0 32 0 45 6c 65 63 42
+61 73 73 20 31 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f f f e 9 0 3 46 1d 16 1f f 5 e
+ e d 3 63 0 b 1f 13 6 5 d 1c 3 63 0 0
+1f 13 6 8 f 0 4 63 4 6 3b 1f 0 0 0 42
+ c 4 4 0 63 32 0 32 0 32 0 53 79 6e 46 75
+6e 6b 42 61 73 63 63 63 32 32 32 d 6c 0 0 0
+70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 10 7 8 3 0 3 4f 4 3 1f 9 0 8
+ 0 0 1 4a 0 b 1f 11 0 8 0 0 1 47 4 8
+1f 9 0 8 0 0 0 63 0 b 39 19 0 7 0 52
+ c 2 4 0 63 32 0 32 0 32 0 4c 61 74 65 6c
+79 42 61 73 73 63 63 63 32 32 32 2 0 0 0 0
+40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 13 12 0 9 d 22 0 51 0 b 1f 14 0 5
+ 8 24 40 5c 0 3 1f 11 0 6 c 2c 0 53 9 0
+10 1f 0 b f 0 0 5c a e 3a 22 11 e 1e 5e
+18 7 4 0 63 32 0 32 0 32 0 53 79 6e 63 20
+4c 65 61 64 20 63 63 63 32 32 32 0 70 0 40 0
+ 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 13 1e 0 9 e 0 0 63 3f b 1f 14 0 5
+ e 24 1 51 4 3 1f 14 0 f 1 0 41 4d 8 3
+ f 1f 0 b f 0 2 63 4 b 3b 20 11 12 33 56
+18 4 4 0 63 37 e 0 0 32 0 4a 61 7a 7a 20
+46 6c 75 74 65 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 15 13 d 3 d 1e 2 50 18 e 15 14 9 4
+ c 1e 2 56 11 8 1b 1f f 7 f 0 1 63 4 6
+1a 1f e 6 f 0 2 63 4 0 7c b 0 8 0 62
+18 4 4 0 63 32 0 0 0 32 0 4a 61 76 61 20
+4a 69 76 65 20 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 0 0 4 f 0 40 63 3c 0 b 8 7 7
+ f 5 0 63 4 6 f 5 3 7 f 8 0 3b 5 6
+ e 8 4 5 f 0 3 63 3 0 7e 1d 6 f 0 4a
+11 0 4 0 63 32 0 0 0 32 0 42 61 61 64 42
+72 65 61 74 68 63 63 63 32 32 32 6 30 0 38 0
+ 1 0 46 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 0 0 4 f 0 40 47 2f 0 e 8 7 7
+ f 5 0 4c 0 6 13 1c d c 6 8 0 63 5 6
+14 11 d b 0 0 3 63 4 0 7a 10 0 51 0 68
+17 0 4 0 63 32 0 0 0 32 0 56 6f 63 61 6c
+4e 75 74 73 20 63 63 63 32 32 32 6 30 0 30 0
+ 1 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 1f 0 5 f 0 0 41 32 3 1f 14 10 5
+ 5 1 2 63 7 3 1f b 12 8 f 0 1 63 c 3
+1f 1f f 8 f 0 1 63 4 3 39 23 0 0 0 62
+18 7 4 0 63 32 0 0 0 32 0 57 61 74 65 72
+47 6c 61 73 73 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 16 2 0 4 6 9 1 4f 8 0 19 e 1 4
+ 0 20 1 43 19 0 1f 12 10 6 7 0 0 54 3d 3
+16 d 6 6 2 1e 3 61 8 e 3a 20 1 14 0 42
+ c 2 4 2 63 63 63 0 0 32 0 46 75 7a 7a 79
+20 4b 6f 74 6f 63 63 63 32 32 32 0 0 0 0 b
+50 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1c 8 0 3 e 0 1 55 12 3 1c 7 0 1
+ e 2e 1 58 27 b e 4 0 2 a 0 2 63 4 a
+ d 9 0 2 c 1 2 63 10 b 4 54 0 47 0 53
+18 7 4 0 63 32 0 0 0 32 0 42 72 74 68 62
+65 6c 6c 73 20 63 63 63 32 32 32 0 4 0 40 0
+40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1a 4 1 1 b 16 0 47 5 3 15 e 0 1
+ d 0 0 4c 5 16 1c 6 4 2 7 0 0 63 4 16
+18 18 3 1 e 0 0 5e 4 10 24 7 0 4 0 62
+24 4 4 0 63 32 0 0 0 32 0 54 75 62 65 20
+42 65 6c 6c 73 63 63 63 32 32 32 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 1f 13 3 0 0 0 5f 3d 6 1f 12 13 2
+ 0 0 1 52 5 2 1f 14 13 3 0 0 1 56 28 5
+1e b 13 f 9 0 0 63 6 3 3b 63 0 63 0 73
+23 7 4 0 63 32 0 0 0 32 0 4e 6f 69 73 65
+20 53 68 6f 74 63 63 63 32 32 32 8 0 0 0 8
+ 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 1f 16 0 3 7 0 1 50 0 3 1f 18 3 3
+ 3 22 0 63 0 14 1d 7 6 3 6 0 1 3c 8 3
+1f 5 7 3 0 0 1 63 4 1b 39 23 0 8 0 42
+18 4 4 0 63 32 0 0 0 32 0 48 61 6e 64 20
+44 72 75 6d 20 63 63 63 32 32 32 0 1 0 3 0
+ 1 0 1 3 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 7d f7 \ No newline at end of file
diff --git a/pd/portmidi/pm_win/README_WIN.txt b/pd/portmidi/pm_win/README_WIN.txt
new file mode 100644
index 00000000..df120e96
--- /dev/null
+++ b/pd/portmidi/pm_win/README_WIN.txt
@@ -0,0 +1,183 @@
+File: PortMidi Win32 Readme
+Author: Belinda Thom, June 16 2002
+Revised by: Roger Dannenberg, June 2002, May 2004
+
+=============================================================================
+USING PORTMIDI:
+=============================================================================
+
+PortMidi has been created using a DLL because the Win32 MMedia API doesn't
+handle midiInput properly in the debugger. Specifically, it doesn't clean up
+after itself if the user (i.e. you, a PortMidi application) hasn't explicitly
+closed all open midi input devices. This lack of cleanup can lead to much
+pain and agony, including the blue-screen-of-death. This situation becomes
+increasingly unacceptable when you are debugging your code, so a portMidi DLL
+seemed to be the most elegant solution.
+
+Using Microsoft Visual C++ project files (provided with PortMidi), there
+are two configurations of the PortMidi library. The Debug version is
+intended for debugging, especially in a console application. The Debug
+version enables some extra error checking and outputs some text as well
+as a prompt to type ENTER so that you don't lose any debugging text when
+the program exits. You can turn off this extra debugging info by taking
+out the compile-time definition for DEBUG. This debugging version also
+defines PM_CHECK_ERRORS, which forces a check for error return codes from
+every call to PortMidi. You can disable this checking (especially if you
+want to handle error codes in your own way) by removing PM_CHECK_ERRORS
+from the predefined symbols list in the Settings dialog box.
+
+PortMidi is designed to run without a console and should work perfectly
+well within a graphical user interface application. The Release version
+is both optimized and lacking the debugging printout code of the Debug
+version.
+
+Read the portmidi.h file for PortMidi API details on using the PortMidi API.
+See <...>\pm_dll_test\test.c or <...>\multithread\test.c for usage examples.
+
+=============================================================================
+TO INSTALL PORTMIDI:
+=============================================================================
+1) download portmidi.zip
+
+2) unzip portmidi.zip into directory: <...>\portmidi
+
+=============================================================================
+TO COMPILE PORTMIDI:
+=============================================================================
+
+3) go to this directory
+
+4) click on the portmidi.dsw workspace
+
+5) the following projects exist within this workspace:
+ - portmidi (the PortMidi library)
+ - pm_dll (the dll library used to close midi ports on program exit)
+ - porttime (a small portable library implementing timer facilities)
+ - test (simple midi I/O testing)
+ - multithread (an example illustrating low-latency MIDI processing
+ using a dedicated low-latency thread)
+ - sysex (simple sysex message I/O testing)
+ - latency (uses porttime to measure system latency)
+
+6) verify that all project settings are for Win32 Debug release:
+ - hit Alt-F7
+ - highlight all three projects in left part of Project Settings window;
+ - "Settings For" should say "Win32 Debug"
+
+7) set pm_dll as the active project (e.g. Project->Select Active Project)
+
+8) use Build->Batch Build ... to build everything in the project
+
+9) The settings for these projects were distributed in the zip file, so
+ compile should just work.
+
+10) IMPORTANT! PortMidi uses a DLL, pm_dll.dll, but there is no simple way
+ to set up projects to use pm_dll. THEREFORE, you need to copy DLLs
+ as follows (you can do this with <...>\portmidi\pm_win\copy-dll.bat):
+ copy <...>\portmidi\pm_win\Debug\pm_dll.dll to:
+ <...>\portmidi\pm_test\latencyDebug\pm_dll.dll
+ <...>\portmidi\pm_test\midithreadDebug\pm_dll.dll
+ <...>\portmidi\pm_test\sysexDebug\pm_dll.dll
+ <...>\portmidi\pm_test\testDebug\pm_dll.dll
+ <...>\portmidi\pm_test\midithruDebug\pm_dll.dll
+ and copy <...>\portmidi\pm_win\Release\pm_dll.dll to:
+ <...>\portmidi\pm_test\latencyRelease\pm_dll.dll
+ <...>\portmidi\pm_test\midithreadRelease\pm_dll.dll
+ <...>\portmidi\pm_test\sysexRelease\pm_dll.dll
+ <...>\portmidi\pm_test\testRelease\pm_dll.dll
+ <...>\portmidi\pm_test\midithruRelease\pm_dll.dll
+ each time you rebuild the pm_dll project, these copies must be redone!
+
+ Since Windows will look in the executable directory for DLLs, we
+ recommend that you always install a copy of pm_dll.dll (either the
+ debug version or the release version) in the same directory as the
+ application using PortMidi. The release DLL is about 40KB. This will
+ ensure that the application uses the correct DLL.
+
+11) run test project; use the menu that shows up from the command prompt to
+ test that portMidi works on your system. tests include:
+ - verify midi output works
+ - verify midi input works
+ - verify midi input w/midi thru works
+
+12) run other projects if you wish: sysex, latency, and midithread
+
+============================================================================
+TO CREATE YOUR OWN PORTMIDI CLIENT APPLICATION:
+============================================================================
+
+NOTE: this section needs to be reviewed and tested. My suggestion would
+be to copy the test project file (test.dsp) and modify it. -RBD
+
+The easiest way is to start a new project w/in the portMidi workspace:
+
+1) To open new project:
+ - File->New->Projects
+ - Location: <...>\portmidi\<yourProjectName>
+ - check Add to current workspace
+ - select Win32 Console Application (recommended for now)
+ - do *NOT* select the "make dependency" box (you will explicitly do this
+ in the next step)
+ - Click OK
+ - Select "An Empty Project" and click Finish
+
+2) Now this project will be the active project. Make it explicitly depend
+ on PortMidi dll:
+ - Project->Dependencies
+ - Click pm_dll
+
+3) Important! in order to be able to use portMidi DLL from your new project
+ and set breakpoints, copy following files from <...>\pm_dll\Debug into
+ <...>\<yourProjectName>\Debug directory:
+ pm_dll.lib
+ pm_dll.dll
+ each time you rebuild pm_dll, these copies must be redone!
+
+4) add whatever files you wish to add to your new project, using portMidi
+ calls as desired (see USING PORTMIDI at top of this readme)
+
+5) when you include portMidi files, do so like this:
+ - #include "..\pm_dll\portmidi.h"
+ - etc.
+
+6) build and run your project
+
+============================================================================
+DESIGN NOTES
+============================================================================
+
+The DLL is used so that PortMidi can (usually) close open devices when the
+program terminates. Failure to close input devices under WinNT, Win2K, and
+probably later systems causes the OS to crash.
+
+This is accomplished with a .LIB/.DLL pair, linking to the .LIB
+in order to access functions in the .DLL.
+
+PortMidi for Win32 exists as a simple library,
+with Win32-specific code in pmwin.c and MM-specific code in pmwinmm.c.
+pmwin.c uses a DLL in pmdll.c to call Pm_Terminate() when the program
+exits to make sure that all MIDI ports are closed.
+
+Orderly cleanup after errors are encountered is based on a fixed order of
+steps and state changes to reflect each step. Here's the order:
+
+To open input:
+ initialize return value to NULL
+ - allocate the PmInternal strucure (representation of PortMidiStream)
+ return value is (non-null) PmInternal structure
+ - allocate midi buffer
+ set buffer field of PmInternal structure
+ - call system-dependent open code
+ - allocate midiwinmm_type for winmm dependent data
+ set descriptor field of PmInternal structure
+ - open device
+ set handle field of midiwinmm_type structure
+ - allocate buffer 1 for sysex
+ buffer is added to input port
+ - allocate buffer 2 for sysex
+ buffer is added to input port
+ - return
+ - return
+
+
+
diff --git a/pd/portmidi/pm_win/copy-dll.bat b/pd/portmidi/pm_win/copy-dll.bat
new file mode 100644
index 00000000..34ccbedd
--- /dev/null
+++ b/pd/portmidi/pm_win/copy-dll.bat
@@ -0,0 +1,13 @@
+copy Debug\pm_dll.dll ..\pm_test\testDebug\pm_dll.dll
+copy Debug\pm_dll.dll ..\pm_test\sysexDebug\pm_dll.dll
+copy Debug\pm_dll.dll ..\pm_test\midithreadDebug\pm_dll.dll
+copy Debug\pm_dll.dll ..\pm_test\latencyDebug\pm_dll.dll
+copy Debug\pm_dll.dll ..\pm_test\midithruDebug\pm_dll.dll
+
+copy Release\pm_dll.dll ..\pm_test\testRelease\pm_dll.dll
+copy Release\pm_dll.dll ..\pm_test\sysexRelease\pm_dll.dll
+copy Release\pm_dll.dll ..\pm_test\midithreadRelease\pm_dll.dll
+copy Release\pm_dll.dll ..\pm_test\latencyRelease\pm_dll.dll
+copy Release\pm_dll.dll ..\pm_test\midithruRelease\pm_dll.dll
+
+
diff --git a/pd/portmidi/pm_win/debugging_dlls.txt b/pd/portmidi/pm_win/debugging_dlls.txt
new file mode 100644
index 00000000..82b81a5b
--- /dev/null
+++ b/pd/portmidi/pm_win/debugging_dlls.txt
@@ -0,0 +1,145 @@
+========================================================================================================================
+Methods for Debugging DLLs
+========================================================================================================================
+If you have the source for both the DLL and the calling program, open the project for the calling executable file and
+debug the DLL from there. If you load a DLL dynamically, you must specify it in the Additional DLLs category of the
+Debug tab in the Project Settings dialog box.
+
+If you have the source for the DLL only, open the project that builds the DLL. Use the Debug tab in the Project
+Settings dialog box to specify the executable file that calls the DLL.
+
+You can also debug a DLL without a project. For example, maybe you just picked up a DLL and source code but you
+don’t have an associated project or workspace. You can use the Open command on the File menu to select the .DLL
+file you want to debug. The debug information should be in either the .DLL or the related .PDB file. After
+Visual C++ opens the file, on the Build menu click Start Debug and Go to begin debugging.
+
+To debug a DLL using the project for the executable file
+
+From the Project menu, click Settings.
+The Project Settings dialog box appears.
+
+Choose the Debug tab.
+
+
+In the Category drop-down list box, select General.
+
+
+In the Program Arguments text box, type any command-line arguments required by the executable file.
+
+
+In the Category drop-down list box, select Additional DLLs.
+
+
+In the Local Name column, type the names of DLLs to debug.
+If you are debugging remotely, the Remote Name column appears. In this column, type the complete path for the
+remote module to map to the local module name.
+
+In the Preload column, select the check box if you want to load the module before debugging begins.
+
+
+Click OK to store the information in your project.
+
+
+From the Build menu, click Start Debug and Go to start the debugger.
+You can set breakpoints in the DLL or the calling program. You can open a source file for the DLL and set breakpoints
+in that file, even though it is not a part of the executable file’s project.
+
+To debug a DLL using the project for the DLL
+
+From the Project menu, click Settings.
+The Project Settings dialog box appears.
+
+Choose the Debug tab.
+
+
+In the Category drop-down list box, select General.
+
+
+In the Executable For Debug Session text box, type the name of the executable file that calls the DLL.
+
+
+In the Category list box, select Additional DLLs.
+
+
+In the Local Module Name column, type the name of the DLLs you want to debug.
+
+
+Click OK to store the information in your project.
+
+
+Set breakpoints as required in your DLL source files or on function symbols in the DLL.
+
+
+From the Build menu, click Start Debug and Go to start the debugger.
+To debug a DLL created with an external project
+
+From the Project menu, click Settings.
+The Project Settings dialog box appears.
+
+Choose the Debug tab.
+
+
+In the Category drop-down list box, select General.
+
+
+In the Executable For Debug Session text box, type the name of the DLL that your external makefile builds.
+
+
+Click OK to store the information in your project.
+
+
+Build a debug version of the DLL with symbolic debugging information, if you don’t already have one.
+
+
+Follow one of the two procedures immediately preceding this one to debug the DLL.
+
+========================================================================================================================
+Why Don’t My DLL Breakpoints Work?
+========================================================================================================================
+Some reasons why your breakpoints don’t work as expected are listed here, along with solutions or work-arounds for each.
+If you follow the instructions in one topic and are still having breakpoint problems, look at some of the other topics.
+Often breakpoint problems result from a combination of conditions.
+
+You can't set a breakpoint in a source file when the corresponding symbolic information isn't loaded into memory by
+the debugger.
+You cannot set a breakpoint in any source file when the corresponding symbolic information will not be loaded into memory
+by the debugger.
+Symptoms include messages such as "the breakpoint cannot be set" or a simple, noninformational beep.
+
+When setting breakpoints before the code to be debugged has been started, the debugger uses a breakpoint list to keep
+track of how and where to set breakpoints. When you actually begin the debugging session, the debugger loads the symbolic
+information for all the code to be debugged and then walks through its breakpoint list, attempting to set the
+breakpoints.
+
+However, if one or more of the code modules have not been designated to the debugger, there will be no symbolic
+information for the debugger to use when walking through its breakpoint list. Situations where this is likely to
+occur include:
+
+Attempts to set breakpoints in a DLL before the call to LoadLibrary.
+
+Setting a breakpoint in an ActiveX server before the container has started the server.
+
+Other similar cases.
+
+To prevent this behavior in Visual C++, specify all additional DLLs and COM servers in the Additional DLLs field
+in the Debug/Options dialog box to notify the debugger that you want it to load symbolic debug information for
+additional .DLL files. When this has been done, breakpoints set in code that has not yet been loaded into memory
+will be "virtual" breakpoints. When the code is actually loaded into memory by the loader, these become physical
+breakpoints. Make sure that these additional debugging processes are not already running when you start your
+debugging session. The debugging process and these additional processes must be sychronized at the same beginning
+point to work correctly, hitting all breakpoints.
+
+Breakpoints are missed when more than one copy of a DLL is on your hard disk.
+Having more than one copy of a DLL on your hard drive, especially if it is in your Windows directory, can cause
+debugger confusion. The debugger will load the symbolic information for the DLL specified to it at run time (with the
+Additional DLLs field in the Debug/Options dialog box), while Windows has actually loaded a different copy of the
+DLL itself into memory. Because there is no way to force the debugger to load a specific DLL, it is a good idea to
+keep only one version of a DLL at a time in your path, current directory, and Windows directory.
+
+You can’t set "Break When Expression Has Changed" breakpoints on a variable local to a DLL.
+Setting a "Break When Expression Has Changed" breakpoint on a variable local to a DLL function before the call
+to LoadLibrary causes the breakpoint to be virtual (there are no physical addresses for the DLL in memory yet).
+Virtual breakpoints involving expressions pose a special problem. The DLL must be specified to the debugger at
+startup (causing its symbolic information to be loaded). In addition, the DLL's executable code must also be loaded
+into memory before this kind of breakpoint can be set. This means that the calling application's code must be
+executed to the point after its call to LoadLibrary before the debugger will allow this type of breakpoint to be set.
diff --git a/pd/portmidi/pm_win/pm_dll.dsp b/pd/portmidi/pm_win/pm_dll.dsp
new file mode 100644
index 00000000..d08e2de7
--- /dev/null
+++ b/pd/portmidi/pm_win/pm_dll.dsp
@@ -0,0 +1,107 @@
+# Microsoft Developer Studio Project File - Name="pm_dll" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=pm_dll - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "pm_dll.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "pm_dll.mak" CFG="pm_dll - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "pm_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "pm_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "pm_dll - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "pm_win\Release"
+# PROP Intermediate_Dir "pm_win\Release"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "pm_dll - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "pm_win\Debug"
+# PROP Intermediate_Dir "pm_win\Debug"
+# PROP Ignore_Export_Lib 1
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "pm_common" /D "_WINDOWS" /D "_USRDLL" /D "PM_DLL_EXPORTS" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "pm_dll - Win32 Release"
+# Name "pm_dll - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\pmdll.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\pmdll.h
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_win/pmdll.c b/pd/portmidi/pm_win/pmdll.c
new file mode 100644
index 00000000..c3acba33
--- /dev/null
+++ b/pd/portmidi/pm_win/pmdll.c
@@ -0,0 +1,49 @@
+/*
+====================================================================
+DLL to perform action when program shuts down
+====================================================================
+*/
+
+#include "windows.h"
+#include "pmdll.h"
+
+static close_fn_ptr_type close_function = NULL;
+
+
+DLL_EXPORT pm_set_close_function(close_fn_ptr_type close_fn_ptr)
+{
+ close_function = close_fn_ptr;
+}
+
+
+static void Initialize( void ) {
+ return;
+}
+
+static void Terminate( void ) {
+ if (close_function) {
+ (*close_function)();
+ }
+}
+
+
+BOOL WINAPI DllMain(HINSTANCE hinstDLL, //DLL module handle
+ DWORD fdwReason, //for calling function
+ LPVOID lbpvReserved)//reserved
+{
+ switch(fdwReason) {
+ case DLL_PROCESS_ATTACH:
+ /* when DLL starts, run this */
+ Initialize();
+ break;
+ case DLL_PROCESS_DETACH:
+ /* when DLL ends, this run (note: verified this run */
+ Terminate();
+ break;
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+
diff --git a/pd/portmidi/pm_win/pmdll.h b/pd/portmidi/pm_win/pmdll.h
new file mode 100644
index 00000000..e5ad1c5b
--- /dev/null
+++ b/pd/portmidi/pm_win/pmdll.h
@@ -0,0 +1,5 @@
+#define DLL_EXPORT __declspec( dllexport )
+
+typedef void (*close_fn_ptr_type)();
+
+DLL_EXPORT pm_set_close_function(close_fn_ptr_type close_fn_ptr);
diff --git a/pd/portmidi/pm_win/pmwin.c b/pd/portmidi/pm_win/pmwin.c
new file mode 100644
index 00000000..b289194b
--- /dev/null
+++ b/pd/portmidi/pm_win/pmwin.c
@@ -0,0 +1,113 @@
+/* pmwin.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement:
+ pm_init(), which calls various routines to register the
+ available midi devices,
+ Pm_GetDefaultInputDeviceID(), and
+ Pm_GetDefaultOutputDeviceID().
+ This file must
+ be separate from the main portmidi.c file because it is system
+ dependent, and it is separate from, say, pmwinmm.c, because it
+ might need to register devices for winmm, directx, and others.
+ */
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pminternal.h"
+#include "pmwinmm.h"
+#ifdef USE_DLL_FOR_CLEANUP
+#include "pmdll.h" /* used to close ports on exit */
+#endif
+#ifdef DEBUG
+#include "stdio.h"
+#endif
+
+/* pm_exit is called when the program exits.
+ It calls pm_term to make sure PortMidi is properly closed.
+ If DEBUG is on, we prompt for input to avoid losing error messages.
+ */
+static void pm_exit(void) {
+ pm_term();
+#ifdef DEBUG
+#define STRING_MAX 80
+ {
+ char line[STRING_MAX];
+ printf("Type ENTER...\n");
+ /* note, w/o this prompting, client console application can not see one
+ of its errors before closing. */
+ fgets(line, STRING_MAX, stdin);
+ }
+#endif
+}
+
+
+/* pm_init is the windows-dependent initialization.*/
+void pm_init(void)
+{
+#ifdef USE_DLL_FOR_CLEANUP
+ /* we were hoping a DLL could offer more robust cleanup after errors,
+ but the DLL does not seem to run after crashes. Thus, the atexit()
+ mechanism is just as powerful, and simpler to implement.
+ */
+ pm_set_close_function(pm_exit);
+#ifdef DEBUG
+ printf("registered pm_term with cleanup DLL\n");
+#endif
+#else
+ atexit(pm_exit);
+#ifdef DEBUG
+ printf("registered pm_exit with atexit()\n");
+#endif
+#endif
+ pm_winmm_init();
+ /* initialize other APIs (DirectX?) here */
+}
+
+
+void pm_term(void) {
+ pm_winmm_term();
+}
+
+
+PmDeviceID Pm_GetDefaultInputDeviceID() {
+ /* This routine should check the environment and the registry
+ as specified in portmidi.h, but for now, it just returns
+ the first device of the proper input/output flavor.
+ */
+ int i;
+ Pm_Initialize(); /* make sure descriptors exist! */
+ for (i = 0; i < pm_descriptor_index; i++) {
+ if (descriptors[i].pub.input) {
+ return i;
+ }
+ }
+ return pmNoDevice;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+ /* This routine should check the environment and the registry
+ as specified in portmidi.h, but for now, it just returns
+ the first device of the proper input/output flavor.
+ */
+ int i;
+ Pm_Initialize(); /* make sure descriptors exist! */
+ for (i = 0; i < pm_descriptor_index; i++) {
+ if (descriptors[i].pub.output) {
+ return i;
+ }
+ }
+ return pmNoDevice;
+ return 0;
+}
+
+#include "stdio.h"
+
+void *pm_alloc(size_t s) {
+ return malloc(s);
+}
+
+
+void pm_free(void *ptr) {
+ free(ptr);
+}
+
diff --git a/pd/portmidi/pm_win/pmwinmm.c b/pd/portmidi/pm_win/pmwinmm.c
new file mode 100644
index 00000000..5bfb0cff
--- /dev/null
+++ b/pd/portmidi/pm_win/pmwinmm.c
@@ -0,0 +1,1547 @@
+/* pmwinmm.c -- system specific definitions */
+
+#include "windows.h"
+#include "mmsystem.h"
+#include "portmidi.h"
+#include "pminternal.h"
+#include "pmwinmm.h"
+#include "string.h"
+#include "porttime.h"
+
+/* asserts used to verify portMidi code logic is sound; later may want
+ something more graceful */
+#include <assert.h>
+
+#ifdef DEBUG
+/* this printf stuff really important for debugging client app w/host errors.
+ probably want to do something else besides read/write from/to console
+ for portability, however */
+#define STRING_MAX 80
+#include "stdio.h"
+#endif
+
+#define streql(x, y) (strcmp(x, y) == 0)
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+/* callback routines */
+static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,
+ WORD wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+ DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
+static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
+ DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
+
+extern pm_fns_node pm_winmm_in_dictionary;
+extern pm_fns_node pm_winmm_out_dictionary;
+
+static void winmm_out_delete(PmInternal *midi); /* forward reference */
+
+#define SYSEX_BYTES_PER_BUFFER 1024
+/* 3 midi messages per buffer */
+#define OUTPUT_BYTES_PER_BUFFER 36
+
+#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
+#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
+#define MIDIHDR_BUFFER_LENGTH(x) (x)
+#define MIDIHDR_SIZE(x) (MIDIHDR_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
+
+/*
+==============================================================================
+win32 mmedia system specific structure passed to midi callbacks
+==============================================================================
+*/
+
+/* global winmm device info */
+MIDIINCAPS *midi_in_caps = NULL;
+MIDIINCAPS midi_in_mapper_caps;
+UINT midi_num_inputs = 0;
+MIDIOUTCAPS *midi_out_caps = NULL;
+MIDIOUTCAPS midi_out_mapper_caps;
+UINT midi_num_outputs = 0;
+
+/* per device info */
+typedef struct midiwinmm_struct
+{
+
+ union {
+ HMIDISTRM stream; /* windows handle for stream */
+ HMIDIOUT out; /* windows handle for out calls */
+ HMIDIIN in; /* windows handle for in calls */
+ } handle;
+
+ /* midi output messages are sent in these buffers, which are allocated
+ * in a round-robin fashion, using next_buffer as an index
+ */
+ LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */
+ int num_buffers; /* how many buffers */
+ int next_buffer; /* index of next buffer to send */
+ HANDLE buffer_signal; /* used to wait for buffer to become free */
+
+ LPMIDIHDR *sysex_buffers; /* pool of buffers for sysex data */
+ int num_sysex_buffers; /* how many sysex buffers */
+ int next_sysex_buffer; /* index of next sysexbuffer to send */
+ HANDLE sysex_buffer_signal; /* used to wait for sysex buffer to become free */
+
+ unsigned long last_time; /* last output time */
+ int first_message; /* flag: treat first message differently */
+ int sysex_mode; /* middle of sending sysex */
+ unsigned long sysex_word; /* accumulate data when receiving sysex */
+ unsigned int sysex_byte_count; /* count how many received or to send */
+ LPMIDIHDR hdr; /* the message accumulating sysex to send */
+ unsigned long sync_time; /* when did we last determine delta? */
+ long delta; /* difference between stream time and
+ real time */
+ int error; /* host error from doing port midi call */
+ int callback_error; /* host error from midi in or out callback */
+}
+midiwinmm_node, *midiwinmm_type;
+
+
+/*
+=============================================================================
+general MIDI device queries
+=============================================================================
+*/
+static void pm_winmm_general_inputs()
+{
+ UINT i;
+ WORD wRtn;
+ midi_num_inputs = midiInGetNumDevs();
+ midi_in_caps = pm_alloc(sizeof(MIDIINCAPS) * midi_num_inputs);
+
+ if (midi_in_caps == NULL) {
+ // if you can't open a particular system-level midi interface
+ // (such as winmm), we just consider that system or API to be
+ // unavailable and move on without reporting an error. This
+ // may be the wrong thing to do, especially in this case.
+ return ;
+ }
+
+ for (i = 0; i < midi_num_inputs; i++) {
+ wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],
+ sizeof(MIDIINCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ /* ignore errors here -- if pm_descriptor_max is exceeded, some
+ devices will not be accessible. */
+ pm_add_device("MMSystem", midi_in_caps[i].szPname, TRUE,
+ (void *) i, &pm_winmm_in_dictionary);
+ }
+ }
+}
+
+
+static void pm_winmm_mapper_input()
+{
+ WORD wRtn;
+ /* Note: if MIDIMAPPER opened as input (documentation implies you
+ can, but current system fails to retrieve input mapper
+ capabilities) then you still should retrieve some formof
+ setup info. */
+ wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
+ (LPMIDIINCAPS) & midi_in_mapper_caps, sizeof(MIDIINCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device("MMSystem", midi_in_mapper_caps.szPname, TRUE,
+ (void *) MIDIMAPPER, &pm_winmm_in_dictionary);
+ }
+}
+
+
+static void pm_winmm_general_outputs()
+{
+ UINT i;
+ DWORD wRtn;
+ midi_num_outputs = midiOutGetNumDevs();
+ midi_out_caps = pm_alloc( sizeof(MIDIOUTCAPS) * midi_num_outputs );
+
+ if (midi_out_caps == NULL) {
+ // no error is reported -- see pm_winmm_general_inputs
+ return ;
+ }
+
+ for (i = 0; i < midi_num_outputs; i++) {
+ wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],
+ sizeof(MIDIOUTCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device("MMSystem", midi_out_caps[i].szPname, FALSE,
+ (void *) i, &pm_winmm_out_dictionary);
+ }
+ }
+}
+
+
+static void pm_winmm_mapper_output()
+{
+ WORD wRtn;
+ /* Note: if MIDIMAPPER opened as output (pseudo MIDI device
+ maps device independent messages into device dependant ones,
+ via NT midimapper program) you still should get some setup info */
+ wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)
+ & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));
+ if (wRtn == MMSYSERR_NOERROR) {
+ pm_add_device("MMSystem", midi_out_mapper_caps.szPname, FALSE,
+ (void *) MIDIMAPPER, &pm_winmm_out_dictionary);
+ }
+}
+
+
+/*
+=========================================================================================
+host error handling
+=========================================================================================
+*/
+static unsigned int winmm_has_host_error(PmInternal * midi)
+{
+ midiwinmm_type m = (midiwinmm_type)midi->descriptor;
+ return m->callback_error || m->error;
+}
+
+
+/* str_copy_len -- like strcat, but won't overrun the destination string */
+/*
+ * returns length of resulting string
+ */
+static int str_copy_len(char *dst, char *src, int len)
+{
+ strncpy(dst, src, len);
+ /* just in case suffex is greater then len, terminate with zero */
+ dst[len - 1] = 0;
+ return strlen(dst);
+}
+
+
+static void winmm_get_host_error(PmInternal * midi, char * msg, UINT len)
+{
+ /* precondition: midi != NULL */
+ midiwinmm_node * m = (midiwinmm_node *) midi->descriptor;
+ char *hdr1 = "Host error: ";
+ char *hdr2 = "Host callback error: ";
+
+ msg[0] = 0; /* initialize result string to empty */
+
+ if (descriptors[midi->device_id].pub.input) {
+ /* input and output use different winmm API calls */
+ if (m) { /* make sure there is an open device to examine */
+ if (m->error != MMSYSERR_NOERROR) {
+ int n = str_copy_len(msg, hdr1, len);
+ /* read and record host error */
+ int err = midiInGetErrorText(m->error, msg + n, len - n);
+ assert(err == MMSYSERR_NOERROR);
+ m->error = MMSYSERR_NOERROR;
+ } else if (m->callback_error != MMSYSERR_NOERROR) {
+ int n = str_copy_len(msg, hdr2, len);
+ int err = midiInGetErrorText(m->callback_error, msg + n,
+ len - n);
+ assert(err == MMSYSERR_NOERROR);
+ m->callback_error = MMSYSERR_NOERROR;
+ }
+ }
+ } else { /* output port */
+ if (m) {
+ if (m->error != MMSYSERR_NOERROR) {
+ int n = str_copy_len(msg, hdr1, len);
+ int err = midiOutGetErrorText(m->error, msg + n, len - n);
+ assert(err == MMSYSERR_NOERROR);
+ m->error = MMSYSERR_NOERROR;
+ } else if (m->callback_error != MMSYSERR_NOERROR) {
+ int n = str_copy_len(msg, hdr2, len);
+ int err = midiOutGetErrorText(m->callback_error, msg + n,
+ len = n);
+ assert(err == MMSYSERR_NOERROR);
+ m->callback_error = MMSYSERR_NOERROR;
+ }
+ }
+ }
+}
+
+
+/*
+=============================================================================
+buffer handling
+=============================================================================
+*/
+static MIDIHDR *allocate_buffer(long data_size)
+{
+ /*
+ * with short messages, the MIDIEVENT structure contains the midi message,
+ * so there is no need for additional data
+ */
+
+ LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SIZE(data_size));
+ MIDIEVENT *evt;
+ if (!hdr) return NULL;
+ evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
+ hdr->lpData = (LPSTR) evt;
+ hdr->dwBufferLength = MIDIHDR_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */
+ hdr->dwFlags = 0;
+ hdr->dwUser = 0;
+ return hdr;
+}
+
+static MIDIHDR *allocate_sysex_buffer(long data_size)
+{
+ /* we're actually allocating slightly more than data_size because one more word of
+ * data is contained in MIDIEVENT. We include the size of MIDIEVENT because we need
+ * the MIDIEVENT header in addition to the data
+ */
+ LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
+ MIDIEVENT *evt;
+ if (!hdr) return NULL;
+ evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
+ hdr->lpData = (LPSTR) evt;
+ hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size); /* was: sizeof(MIDIEVENT) + data_size; */
+ hdr->dwFlags = 0;
+ hdr->dwUser = 0;
+ return hdr;
+}
+
+static PmError allocate_buffers(midiwinmm_type m, long data_size, long count)
+{
+ PmError rslt = pmNoError;
+ /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
+ m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
+ if (!m->buffers) return pmInsufficientMemory;
+ m->num_buffers = count;
+ while (count > 0) {
+ LPMIDIHDR hdr = allocate_buffer(data_size);
+ if (!hdr) rslt = pmInsufficientMemory;
+ count--;
+ m->buffers[count] = hdr; /* this may be NULL if allocation fails */
+ }
+ return rslt;
+}
+
+static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size, long count)
+{
+ PmError rslt = pmNoError;
+ /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
+ m->sysex_buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
+ if (!m->sysex_buffers) return pmInsufficientMemory;
+ m->num_sysex_buffers = count;
+ while (count > 0) {
+ LPMIDIHDR hdr = allocate_sysex_buffer(data_size);
+ if (!hdr) rslt = pmInsufficientMemory;
+ count--;
+ m->sysex_buffers[count] = hdr; /* this may be NULL if allocation fails */
+ }
+ return rslt;
+}
+
+static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi)
+{
+ LPMIDIHDR r = NULL;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ if (!m->sysex_buffers) {
+ if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) {
+ return NULL;
+ }
+ }
+ /* busy wait until we find a free buffer */
+ while (TRUE) {
+ int i;
+ for (i = 0; i < m->num_sysex_buffers; i++) {
+ m->next_sysex_buffer++;
+ if (m->next_sysex_buffer >= m->num_sysex_buffers) m->next_sysex_buffer = 0;
+ r = m->sysex_buffers[m->next_sysex_buffer];
+ if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer;
+ }
+ /* after scanning every buffer and not finding anything, block */
+ WaitForSingleObject(m->sysex_buffer_signal, INFINITE);
+ }
+found_sysex_buffer:
+ r->dwBytesRecorded = 0;
+ m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR));
+ return r;
+}
+
+
+static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
+{
+ LPMIDIHDR r = NULL;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ if (!m->buffers) {
+ if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, 2)) {
+ return NULL;
+ }
+ }
+ /* busy wait until we find a free buffer */
+ while (TRUE) {
+ int i;
+ for (i = 0; i < m->num_buffers; i++) {
+ m->next_buffer++;
+ if (m->next_buffer >= m->num_buffers) m->next_buffer = 0;
+ r = m->buffers[m->next_buffer];
+ if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
+ }
+ /* after scanning every buffer and not finding anything, block */
+ WaitForSingleObject(m->buffer_signal, INFINITE);
+ }
+found_buffer:
+ r->dwBytesRecorded = 0;
+ m->error = midiOutPrepareHeader(m->handle.out, r, sizeof(MIDIHDR));
+ return r;
+}
+
+static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size)
+{
+ LPMIDIHDR big;
+ int i;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ /* buffer must be smaller than 64k, but be also be a multiple of 4 */
+ if (new_size > 65520) {
+ if (old_size >= 65520)
+ return pmBufferMaxSize;
+ else
+ new_size = 65520;
+ }
+ /* allocate a bigger message */
+ big = allocate_sysex_buffer(new_size);
+ /* printf("expand to %d bytes\n", new_size);*/
+ if (!big) return pmInsufficientMemory;
+ m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR));
+ if (m->error) {
+ pm_free(big);
+ return pmHostError;
+ }
+ /* make sure we're not going to overwrite any memory */
+ assert(old_size <= new_size);
+ memcpy(big->lpData, m->hdr->lpData, old_size);
+
+ /* find which buffer this was, and replace it */
+
+ for (i = 0;i < m->num_sysex_buffers;i++) {
+ if (m->sysex_buffers[i] == m->hdr) {
+ m->sysex_buffers[i] = big;
+ pm_free(m->hdr);
+ m->hdr = big;
+ break;
+ }
+ }
+ assert(i != m->num_sysex_buffers);
+
+ return pmNoError;
+
+}
+/*
+=========================================================================================
+begin midi input implementation
+=========================================================================================
+*/
+
+static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
+{
+ DWORD dwDevice;
+ int i = midi->device_id;
+ midiwinmm_type m;
+ LPMIDIHDR hdr;
+ long buffer_len;
+ dwDevice = (DWORD) descriptors[i].descriptor;
+
+ /* create system dependent device data */
+ m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */
+ midi->descriptor = m;
+ if (!m) goto no_memory;
+ m->handle.in = NULL;
+ m->buffers = NULL;
+ m->num_buffers = 0;
+ m->next_buffer = 0;
+ m->sysex_buffers = NULL;
+ m->num_sysex_buffers = 0;
+ m->next_sysex_buffer = 0;
+ m->last_time = 0;
+ m->first_message = TRUE; /* not used for input */
+ m->sysex_mode = FALSE;
+ m->sysex_word = 0;
+ m->sysex_byte_count = 0;
+ m->sync_time = 0;
+ m->delta = 0;
+ m->error = MMSYSERR_NOERROR;
+ m->callback_error = MMSYSERR_NOERROR;
+
+ /* open device */
+ pm_hosterror = midiInOpen(&(m->handle.in), /* input device handle */
+ dwDevice, /* device ID */
+ (DWORD) winmm_in_callback, /* callback address */
+ (DWORD) midi, /* callback instance data */
+ CALLBACK_FUNCTION); /* callback is a procedure */
+ if (pm_hosterror) goto free_descriptor;
+
+ /* allocate first buffer for sysex data */
+ buffer_len = midi->buffer_len - 1;
+ if (midi->buffer_len < 32)
+ buffer_len = PM_DEFAULT_SYSEX_BUFFER_SIZE;
+
+ hdr = allocate_sysex_buffer(buffer_len);
+ if (!hdr) goto close_device;
+ pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR));
+ if (pm_hosterror) {
+ pm_free(hdr);
+ goto close_device;
+ }
+ pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR));
+ if (pm_hosterror) goto close_device;
+
+ /* allocate second buffer */
+ hdr = allocate_sysex_buffer(buffer_len);
+ if (!hdr) goto close_device;
+ pm_hosterror = midiInPrepareHeader(m->handle.in, hdr, sizeof(MIDIHDR));
+ if (pm_hosterror) {
+ pm_free(hdr);
+ goto reset_device; /* because first buffer was added */
+ }
+ pm_hosterror = midiInAddBuffer(m->handle.in, hdr, sizeof(MIDIHDR));
+ if (pm_hosterror) goto reset_device;
+
+ /* start device */
+ pm_hosterror = midiInStart(m->handle.in);
+ if (pm_hosterror) goto reset_device;
+ return pmNoError;
+
+ /* undo steps leading up to the detected error */
+reset_device:
+ /* ignore return code (we already have an error to report) */
+ midiInReset(m->handle.in);
+close_device:
+ midiInClose(m->handle.in); /* ignore return code */
+free_descriptor:
+ midi->descriptor = NULL;
+ pm_free(m);
+no_memory:
+ if (pm_hosterror) {
+ int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ assert(err == MMSYSERR_NOERROR);
+ return pmHostError;
+ }
+ /* if !pm_hosterror, then the error must be pmInsufficientMemory */
+ return pmInsufficientMemory;
+ /* note: if we return an error code, the device will be
+ closed and memory will be freed. It's up to the caller
+ to free the parameter midi */
+}
+
+
+/* winmm_in_close -- close an open midi input device */
+/*
+ * assume midi is non-null (checked by caller)
+ */
+static PmError winmm_in_close(PmInternal *midi)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ if (!m) return pmBadPtr;
+ /* device to close */
+ if (pm_hosterror = midiInStop(m->handle.in)) {
+ midiInReset(m->handle.in); /* try to reset and close port */
+ midiInClose(m->handle.in);
+ } else if (pm_hosterror = midiInReset(m->handle.in)) {
+ midiInClose(m->handle.in); /* best effort to close midi port */
+ } else {
+ pm_hosterror = midiInClose(m->handle.in);
+ }
+ midi->descriptor = NULL;
+ pm_free(m); /* delete */
+ if (pm_hosterror) {
+ int err = midiInGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ assert(err == MMSYSERR_NOERROR);
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+/* Callback function executed via midiInput SW interrupt (via midiInOpen). */
+static void FAR PASCAL winmm_in_callback(
+ HMIDIIN hMidiIn, /* midiInput device Handle */
+ WORD wMsg, /* midi msg */
+ DWORD dwInstance, /* application data */
+ DWORD dwParam1, /* MIDI data */
+ DWORD dwParam2) /* device timestamp (wrt most recent midiInStart) */
+{
+ static int entry = 0;
+ PmInternal *midi = (PmInternal *) dwInstance;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+
+ if (++entry > 1) {
+ assert(FALSE);
+ }
+
+ /* for simplicity, this logic perhaps overly conservative */
+ /* note also that this might leak memory if buffers are being
+ returned as a result of midiInReset */
+ if (m->callback_error) {
+ entry--;
+ return ;
+ }
+
+ switch (wMsg) {
+ case MIM_DATA: {
+ /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
+ message LOB;
+ dwParam2 is time message received by input device driver, specified
+ in [ms] from when midiInStart called.
+ each message is expanded to include the status byte */
+
+ long new_driver_time = dwParam2;
+
+ if ((dwParam1 & 0x80) == 0) {
+ /* not a status byte -- ignore it. This happens running the
+ sysex.c test under Win2K with MidiMan USB 1x1 interface.
+ Is it a driver bug or a Win32 bug? If not, there's a bug
+ here somewhere. -RBD
+ */
+ } else { /* data to process */
+ PmEvent event;
+ if (midi->time_proc)
+ dwParam2 = (*midi->time_proc)(midi->time_info);
+ event.timestamp = dwParam2;
+ event.message = dwParam1;
+ pm_read_short(midi, &event);
+ }
+ break;
+ }
+ case MIM_LONGDATA: {
+ MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
+ unsigned char *data = lpMidiHdr->lpData;
+ unsigned int i = 0;
+ long size = sizeof(MIDIHDR) + lpMidiHdr->dwBufferLength;
+ /* ignore sysex data, but free returned buffers */
+ if (lpMidiHdr->dwBytesRecorded > 0 &&
+ midi->filters & PM_FILT_SYSEX) {
+ m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr,
+ sizeof(MIDIHDR));
+ break;
+ }
+ if (midi->time_proc)
+ dwParam2 = (*midi->time_proc)(midi->time_info);
+
+ while (i < lpMidiHdr->dwBytesRecorded) {
+ /* collect bytes from *data into a word */
+ pm_read_byte(midi, *data, dwParam2);
+ data++;
+ i++;
+ }
+ /* when a device is closed, the pending MIM_LONGDATA buffers are
+ returned to this callback with dwBytesRecorded == 0. In this
+ case, we do not want to send them back to the interface (if
+ we do, the interface will not close, and Windows OS may hang). */
+ if (lpMidiHdr->dwBytesRecorded > 0) {
+ m->callback_error = midiInAddBuffer(hMidiIn, lpMidiHdr,
+ sizeof(MIDIHDR));
+ } else {
+ pm_free(lpMidiHdr);
+ }
+ break;
+ }
+ case MIM_OPEN: /* fall thru */
+ case MIM_CLOSE:
+ case MIM_ERROR:
+ case MIM_LONGERROR:
+ default:
+ break;
+ }
+ entry--;
+}
+
+/*
+=========================================================================================
+begin midi output implementation
+=========================================================================================
+*/
+
+/* begin helper routines used by midiOutStream interface */
+
+/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */
+static int add_to_buffer(midiwinmm_type m, LPMIDIHDR hdr,
+ unsigned long delta, unsigned long msg)
+{
+ unsigned long *ptr = (unsigned long *)
+ (hdr->lpData + hdr->dwBytesRecorded);
+ *ptr++ = delta; /* dwDeltaTime */
+ *ptr++ = 0; /* dwStream */
+ *ptr++ = msg; /* dwEvent */
+ hdr->dwBytesRecorded += 3 * sizeof(long);
+ /* if the addition of three more words (a message) would extend beyond
+ the buffer length, then return TRUE (full)
+ */
+ return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;
+}
+
+#ifdef GARBAGE
+static void start_sysex_buffer(LPMIDIHDR hdr, unsigned long delta)
+{
+ unsigned long *ptr = (unsigned long *) hdr->lpData;
+ *ptr++ = delta;
+ *ptr++ = 0;
+ *ptr = MEVT_F_LONG;
+ hdr->dwBytesRecorded = 3 * sizeof(long);
+}
+
+static int add_byte_to_buffer(midiwinmm_type m, LPMIDIHDR hdr,
+ unsigned char midi_byte)
+{
+ allocate message if hdr is null
+ send message if it is full
+ add byte to non - full message
+ unsigned char *ptr = (unsigned char *) (hdr->lpData + hdr->dwBytesRecorded);
+ *ptr = midi_byte;
+ return ++hdr->dwBytesRecorded >= hdr->dwBufferLength;
+}
+#endif
+
+
+static PmTimestamp pm_time_get(midiwinmm_type m)
+{
+ MMTIME mmtime;
+ MMRESULT wRtn;
+ mmtime.wType = TIME_TICKS;
+ mmtime.u.ticks = 0;
+ wRtn = midiStreamPosition(m->handle.stream, &mmtime, sizeof(mmtime));
+ assert(wRtn == MMSYSERR_NOERROR);
+ return mmtime.u.ticks;
+}
+
+#ifdef GARBAGE
+static unsigned long synchronize(PmInternal *midi, midiwinmm_type m)
+{
+ unsigned long pm_stream_time_2 = pm_time_get(m);
+ unsigned long real_time;
+ unsigned long pm_stream_time;
+ /* figure out the time */
+ do {
+ /* read real_time between two reads of stream time */
+ pm_stream_time = pm_stream_time_2;
+ real_time = (*midi->time_proc)(midi->time_info);
+ pm_stream_time_2 = pm_time_get(m);
+ /* repeat if more than 1ms elapsed */
+ } while (pm_stream_time_2 > pm_stream_time + 1);
+ m->delta = pm_stream_time - real_time;
+ m->sync_time = real_time;
+ return real_time;
+}
+#endif
+
+
+/* end helper routines used by midiOutStream interface */
+
+
+static PmError winmm_out_open(PmInternal *midi, void *driverInfo)
+{
+ DWORD dwDevice;
+ int i = midi->device_id;
+ midiwinmm_type m;
+ MIDIPROPTEMPO propdata;
+ MIDIPROPTIMEDIV divdata;
+ dwDevice = (DWORD) descriptors[i].descriptor;
+
+ /* create system dependent device data */
+ m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */
+ midi->descriptor = m;
+ if (!m) goto no_memory;
+ m->handle.out = NULL;
+ m->buffers = NULL;
+ m->num_buffers = 0;
+ m->next_buffer = 0;
+ m->sysex_buffers = NULL;
+ m->num_sysex_buffers = 0;
+ m->next_sysex_buffer = 0;
+ m->last_time = 0;
+ m->first_message = TRUE; /* we treat first message as special case */
+ m->sysex_mode = FALSE;
+ m->sysex_word = 0;
+ m->sysex_byte_count = 0;
+ m->hdr = NULL;
+ m->sync_time = 0;
+ m->delta = 0;
+ m->error = MMSYSERR_NOERROR;
+ m->callback_error = MMSYSERR_NOERROR;
+
+ /* create a signal */
+ m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+ m->sysex_buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ /* this should only fail when there are very serious problems */
+ assert(m->buffer_signal);
+ assert(m->sysex_buffer_signal);
+
+ /* open device */
+ if (midi->latency == 0) {
+ /* use simple midi out calls */
+ pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out, /* device Handle */
+ dwDevice, /* device ID */
+ (DWORD) winmm_out_callback,
+ (DWORD) midi, /* callback instance data */
+ CALLBACK_FUNCTION); /* callback type */
+ } else {
+ /* use stream-based midi output (schedulable in future) */
+ pm_hosterror = midiStreamOpen(&m->handle.stream, /* device Handle */
+ (LPUINT) & dwDevice, /* device ID pointer */
+ 1, /* reserved, must be 1 */
+ (DWORD) winmm_streamout_callback,
+ (DWORD) midi, /* callback instance data */
+ CALLBACK_FUNCTION);
+ }
+ if (pm_hosterror != MMSYSERR_NOERROR) {
+ goto free_descriptor;
+ }
+
+ if (midi->latency != 0) {
+ long dur = 0;
+ /* with stream output, specified number of buffers allocated here */
+ int count = midi->buffer_len;
+ if (count == 0)
+ count = midi->latency / 2; /* how many buffers to get */
+
+ propdata.cbStruct = sizeof(MIDIPROPTEMPO);
+ propdata.dwTempo = 480000; /* microseconds per quarter */
+ pm_hosterror = midiStreamProperty(m->handle.stream,
+ (LPBYTE) & propdata,
+ MIDIPROP_SET | MIDIPROP_TEMPO);
+ if (pm_hosterror) goto close_device;
+
+ divdata.cbStruct = sizeof(MIDIPROPTEMPO);
+ divdata.dwTimeDiv = 480; /* divisions per quarter */
+ pm_hosterror = midiStreamProperty(m->handle.stream,
+ (LPBYTE) & divdata,
+ MIDIPROP_SET | MIDIPROP_TIMEDIV);
+ if (pm_hosterror) goto close_device;
+
+ /* allocate at least 3 buffers */
+ if (count < 3) count = 3;
+ if (allocate_buffers(m, OUTPUT_BYTES_PER_BUFFER, count)) goto free_buffers;
+ if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER, 2)) goto free_buffers;
+ /* start device */
+ pm_hosterror = midiStreamRestart(m->handle.stream);
+ if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
+ }
+ return pmNoError;
+
+free_buffers:
+ /* buffers are freed below by winmm_out_delete */
+close_device:
+ midiOutClose(m->handle.out);
+free_descriptor:
+ midi->descriptor = NULL;
+ winmm_out_delete(midi); /* frees buffers and m */
+no_memory:
+ if (pm_hosterror) {
+ int err = midiOutGetErrorText(pm_hosterror, (char *) pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ assert(err == MMSYSERR_NOERROR);
+ return pmHostError;
+ }
+ return pmInsufficientMemory;
+}
+
+
+/* winmm_out_delete -- carefully free data associated with midi */
+/**/
+static void winmm_out_delete(PmInternal *midi)
+{
+ /* delete system dependent device data */
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ if (m) {
+ if (m->buffer_signal) {
+ /* don't report errors -- better not to stop cleanup */
+ CloseHandle(m->buffer_signal);
+ }
+ if (m->sysex_buffer_signal) {
+ /* don't report errors -- better not to stop cleanup */
+ CloseHandle(m->sysex_buffer_signal);
+ }
+ if (m->buffers) {
+ /* if using stream output, free buffers */
+ int i;
+ for (i = 0; i < m->num_buffers; i++) {
+ if (m->buffers[i]) pm_free(m->buffers[i]);
+ }
+ pm_free(m->buffers);
+ }
+
+ if (m->sysex_buffers) {
+ /* free sysex buffers */
+ int i;
+ for (i = 0; i < m->num_sysex_buffers; i++) {
+ if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]);
+ }
+ pm_free(m->sysex_buffers);
+ }
+ }
+ midi->descriptor = NULL;
+ pm_free(m); /* delete */
+}
+
+
+/* see comments for winmm_in_close */
+static PmError winmm_out_close(PmInternal *midi)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ if (m->handle.out) {
+ /* device to close */
+ if (midi->latency == 0) {
+ pm_hosterror = midiOutClose(m->handle.out);
+ } else {
+ pm_hosterror = midiStreamClose(m->handle.stream);
+ }
+ /* regardless of outcome, free memory */
+ winmm_out_delete(midi);
+ }
+ if (pm_hosterror) {
+ int err = midiOutGetErrorText(pm_hosterror,
+ (char *) pm_hosterror_text,
+ PM_HOST_ERROR_MSG_LEN);
+ assert(err == MMSYSERR_NOERROR);
+ return pmHostError;
+ }
+ return pmNoError;
+}
+
+
+static PmError winmm_out_abort(PmInternal *midi)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ m->error = MMSYSERR_NOERROR;
+
+ /* only stop output streams */
+ if (midi->latency > 0) {
+ m->error = midiStreamStop(m->handle.stream);
+ }
+ return m->error ? pmHostError : pmNoError;
+}
+
+#ifdef GARBAGE
+static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ unsigned char *msg_buffer;
+
+ /* at the beginning of sysex, m->hdr is NULL */
+ if (!m->hdr) { /* allocate a buffer if none allocated yet */
+ m->hdr = get_free_output_buffer(midi);
+ if (!m->hdr) return pmInsufficientMemory;
+ m->sysex_byte_count = 0;
+ }
+ /* figure out where to write byte */
+ msg_buffer = (unsigned char *) (m->hdr->lpData);
+ assert(m->hdr->lpData == (char *) (m->hdr + 1));
+
+ /* check for overflow */
+ if (m->sysex_byte_count >= m->hdr->dwBufferLength) {
+ /* allocate a bigger message -- double it every time */
+ LPMIDIHDR big = allocate_buffer(m->sysex_byte_count * 2);
+ /* printf("expand to %d bytes\n", m->sysex_byte_count * 2); */
+ if (!big) return pmInsufficientMemory;
+ m->error = midiOutPrepareHeader(m->handle.out, big,
+ sizeof(MIDIHDR));
+ if (m->error) {
+ m->hdr = NULL;
+ return pmHostError;
+ }
+ memcpy(big->lpData, msg_buffer, m->sysex_byte_count);
+ msg_buffer = (unsigned char *) (big->lpData);
+ if (m->buffers[0] == m->hdr) {
+ m->buffers[0] = big;
+ pm_free(m->hdr);
+ /* printf("freed m->hdr\n"); */
+ } else if (m->buffers[1] == m->hdr) {
+ m->buffers[1] = big;
+ pm_free(m->hdr);
+ /* printf("freed m->hdr\n"); */
+ }
+ m->hdr = big;
+ }
+
+ /* append byte to message */
+ msg_buffer[m->sysex_byte_count++] = byte;
+
+ /* see if we have a complete message */
+ if (byte == MIDI_EOX) {
+ m->hdr->dwBytesRecorded = m->sysex_byte_count;
+ /*
+ { int i; int len = m->hdr->dwBytesRecorded;
+ printf("OutLongMsg %d ", len);
+ for (i = 0; i < len; i++) {
+ printf("%2x ", msg_buffer[i]);
+ }
+ }
+ */
+ m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR));
+ m->hdr = NULL; /* stop using this message buffer */
+ if (m->error) return pmHostError;
+ }
+ return pmNoError;
+}
+#endif
+
+
+static PmError winmm_write_short(PmInternal *midi, PmEvent *event)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ PmError rslt = pmNoError;
+ assert(m);
+ if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
+ m->error = midiOutShortMsg(m->handle.out, event->message);
+ if (m->error) rslt = pmHostError;
+ } else { /* use midiStream interface -- pass data through buffers */
+ unsigned long when = event->timestamp;
+ unsigned long delta;
+ int full;
+ if (when == 0) when = midi->now;
+ /* when is in real_time; translate to intended stream time */
+ when = when + m->delta + midi->latency;
+ /* make sure we don't go backward in time */
+ if (when < m->last_time) when = m->last_time;
+ delta = when - m->last_time;
+ m->last_time = when;
+ /* before we insert any data, we must have a buffer */
+ if (m->hdr == NULL) {
+ /* stream interface: buffers allocated when stream is opened */
+ m->hdr = get_free_output_buffer(midi);
+ }
+ full = add_to_buffer(m, m->hdr, delta, event->message);
+ if (full) {
+ m->error = midiStreamOut(m->handle.stream, m->hdr,
+ sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ m->hdr = NULL;
+ }
+ }
+ return rslt;
+}
+
+
+static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ PmError rslt = pmNoError;
+ if (midi->latency == 0) {
+ /* do nothing -- it's handled in winmm_write_byte */
+ } else {
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ /* sysex expects an empty buffer */
+ if (m->hdr) {
+ m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ }
+ m->hdr = NULL;
+ }
+ return rslt;
+}
+
+
+static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ PmError rslt = pmNoError;
+ assert(m);
+
+ if (midi->latency == 0) {
+ /* Not using the stream interface. The entire sysex message is
+ in m->hdr, and we send it using midiOutLongMsg.
+ */
+ m->hdr->dwBytesRecorded = m->sysex_byte_count;
+ /*
+ { int i; int len = m->hdr->dwBytesRecorded;
+ printf("OutLongMsg %d ", len);
+ for (i = 0; i < len; i++) {
+ printf("%2x ", msg_buffer[i]);
+ }
+ }
+ */
+
+ m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ } else if (m->hdr) {
+ /* Using stream interface. There are accumulated bytes in m->hdr
+ to send using midiStreamOut
+ */
+ /* add bytes recorded to MIDIEVENT length, but don't
+ count the MIDIEVENT data (3 longs) */
+ MIDIEVENT *evt = (MIDIEVENT *) m->hdr->lpData;
+ evt->dwEvent += m->hdr->dwBytesRecorded - 3 * sizeof(long);
+ /* round up BytesRecorded to multiple of 4 */
+ m->hdr->dwBytesRecorded = (m->hdr->dwBytesRecorded + 3) & ~3;
+
+ m->error = midiStreamOut(m->handle.stream, m->hdr,
+ sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ }
+ m->hdr = NULL; /* make sure we don't send it again */
+ return rslt;
+}
+
+
+static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,
+ PmTimestamp timestamp)
+{
+ /* write a sysex byte */
+ PmError rslt = pmNoError;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ assert(m);
+ if (midi->latency == 0) {
+ /* Not using stream interface. Accumulate the entire message into
+ m->hdr */
+ unsigned char *msg_buffer;
+ /* at the beginning of sysex, m->hdr is NULL */
+ if (!m->hdr) { /* allocate a buffer if none allocated yet */
+ m->hdr = get_free_sysex_buffer(midi);
+ if (!m->hdr) return pmInsufficientMemory;
+ m->sysex_byte_count = 0;
+ }
+ /* figure out where to write byte */
+ msg_buffer = (unsigned char *) (m->hdr->lpData);
+ assert(m->hdr->lpData == (char *) (m->hdr + 1));
+
+ /* append byte to message */
+ msg_buffer[m->sysex_byte_count++] = byte;
+
+ /* check for overflow */
+ if (m->sysex_byte_count >= m->hdr->dwBufferLength) {
+ rslt = resize_sysex_buffer(midi, m->sysex_byte_count, m->sysex_byte_count * 2);
+
+ if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */
+ rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */
+
+ }
+
+ } else { /* latency is not zero, use stream interface: accumulate
+ sysex data in m->hdr and send whenever the buffer fills */
+ int full;
+ unsigned char *ptr;
+
+ /* if m->hdr does not exist, allocate it */
+ if (m->hdr == NULL) {
+ unsigned long when = (unsigned long) timestamp;
+ unsigned long delta;
+ unsigned long *ptr;
+ if (when == 0) when = midi->now;
+ /* when is in real_time; translate to intended stream time */
+ when = when + m->delta + midi->latency;
+ /* make sure we don't go backward in time */
+ if (when < m->last_time) when = m->last_time;
+ delta = when - m->last_time;
+ m->last_time = when;
+
+ m->hdr = get_free_sysex_buffer(midi);
+ assert(m->hdr);
+ ptr = (unsigned long *) m->hdr->lpData;
+ *ptr++ = delta;
+ *ptr++ = 0;
+ *ptr = MEVT_F_LONG;
+ m->hdr->dwBytesRecorded = 3 * sizeof(long);
+ }
+
+ /* add the data byte */
+ ptr = (unsigned char *) (m->hdr->lpData + m->hdr->dwBytesRecorded);
+ *ptr = byte;
+ full = ++m->hdr->dwBytesRecorded >= m->hdr->dwBufferLength;
+
+ /* see if we need to resize */
+ if (full) {
+ int bytesRecorded = m->hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */
+ rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded);
+ m->hdr->dwBytesRecorded = bytesRecorded;
+
+ if (rslt == pmBufferMaxSize) /* if buffer can't be resized */
+ rslt = winmm_end_sysex(midi, timestamp); /* write what we've got and continue */
+ }
+ }
+ return rslt;
+}
+
+
+
+static PmError winmm_write_flush(PmInternal *midi)
+{
+ PmError rslt = pmNoError;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ assert(m);
+ if (midi->latency == 0) {
+ /* all messages are sent immediately */
+ } else if ((m->hdr) && (!midi->sysex_in_progress)) {
+ /* sysex messages are sent upon completion, but ordinary messages
+ may be sitting in a buffer
+ */
+ m->error = midiStreamOut(m->handle.stream, m->hdr, sizeof(MIDIHDR));
+ m->hdr = NULL;
+ if (m->error) rslt = pmHostError;
+ }
+ return rslt;
+}
+
+static PmTimestamp winmm_synchronize(PmInternal *midi)
+{
+ midiwinmm_type m;
+ unsigned long pm_stream_time_2;
+ unsigned long real_time;
+ unsigned long pm_stream_time;
+
+ /* only synchronize if we are using stream interface */
+ if (midi->latency == 0) return 0;
+
+ /* figure out the time */
+ m = (midiwinmm_type) midi->descriptor;
+ pm_stream_time_2 = pm_time_get(m);
+
+ do {
+ /* read real_time between two reads of stream time */
+ pm_stream_time = pm_stream_time_2;
+ real_time = (*midi->time_proc)(midi->time_info);
+ pm_stream_time_2 = pm_time_get(m);
+ /* repeat if more than 1ms elapsed */
+ } while (pm_stream_time_2 > pm_stream_time + 1);
+ m->delta = pm_stream_time - real_time;
+ m->sync_time = real_time;
+ return real_time;
+}
+
+
+#ifdef GARBAGE
+static PmError winmm_write(PmInternal *midi,
+ PmEvent *buffer,
+ long length)
+{
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ unsigned long now;
+ int i;
+ long msg;
+ PmError rslt = pmNoError;
+
+ m->error = MMSYSERR_NOERROR;
+ if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
+ for (i = 0; (i < length) && (rslt == pmNoError); i++) {
+ int b = 0; /* count sysex bytes as they are handled */
+ msg = buffer[i].message;
+ if ((msg & 0xFF) == MIDI_SYSEX) {
+ /* start a sysex message */
+ m->sysex_mode = TRUE;
+ unsigned char midi_byte = (unsigned char) msg;
+ rslt = winmm_write_sysex_byte(midi, midi_byte);
+ b = 8;
+ } else if ((msg & 0x80) && ((msg & 0xFF) != MIDI_EOX)) {
+ /* a non-sysex message */
+ m->error = midiOutShortMsg(m->handle.out, msg);
+ if (m->error) rslt = pmHostError;
+ /* any non-real-time message will terminate sysex message */
+ if (!is_real_time(msg)) m->sysex_mode = FALSE;
+ }
+ /* transmit sysex bytes until we find EOX */
+ if (m->sysex_mode) {
+ while (b < 32 /*bits*/ && (rslt == pmNoError)) {
+ unsigned char midi_byte = (unsigned char) (msg >> b);
+ rslt = winmm_write_sysex_byte(midi, midi_byte);
+ if (midi_byte == MIDI_EOX) {
+ b = 24; /* end of message */
+ m->sysex_mode = FALSE;
+ }
+ b += 8;
+ }
+ }
+ }
+ } else { /* use midiStream interface -- pass data through buffers */
+ LPMIDIHDR hdr = NULL;
+ now = (*midi->time_proc)(midi->time_info);
+ if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
+ /* time to resync */
+ now = synchronize(midi, m);
+ m->first_message = FALSE;
+ }
+ for (i = 0; i < length && rslt == pmNoError; i++) {
+ unsigned long when = buffer[i].timestamp;
+ unsigned long delta;
+ if (when == 0) when = now;
+ /* when is in real_time; translate to intended stream time */
+ when = when + m->delta + midi->latency;
+ /* make sure we don't go backward in time */
+ if (when < m->last_time) when = m->last_time;
+ delta = when - m->last_time;
+ m->last_time = when;
+ /* before we insert any data, we must have a buffer */
+ if (hdr == NULL) {
+ /* stream interface: buffers allocated when stream is opened */
+ hdr = get_free_output_buffer(midi);
+ assert(hdr);
+ if (m->sysex_mode) {
+ /* we are in the middle of a sysex message */
+ start_sysex_buffer(hdr, delta);
+ }
+ }
+ msg = buffer[i].message;
+ if ((msg & 0xFF) == MIDI_SYSEX) {
+ /* sysex expects an empty buffer */
+ if (hdr->dwBytesRecorded != 0) {
+ m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ hdr = get_free_output_buffer(midi);
+ assert(hdr);
+ }
+ /* when we see a MIDI_SYSEX, we always enter sysex mode and call
+ start_sysex_buffer() */
+ start_sysex_buffer(hdr, delta);
+ m->sysex_mode = TRUE;
+ }
+ /* allow a non-real-time status byte to terminate sysex message */
+ if (m->sysex_mode && (msg & 0x80) && (msg & 0xFF) != MIDI_SYSEX &&
+ !is_real_time(msg)) {
+ /* I'm not sure what WinMM does if you send an incomplete sysex
+ message, but the best way out of this mess seems to be to
+ recreate the code used when you encounter an EOX, so ...
+ */
+ MIDIEVENT *evt = (MIDIEVENT) hdr->lpData;
+ evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
+ /* round up BytesRecorded to multiple of 4 */
+ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
+ m->error = midiStreamOut(m->handle.stream, hdr,
+ sizeof(MIDIHDR));
+ if (m->error) {
+ rslt = pmHostError;
+ }
+ hdr = NULL; /* make sure we don't send it again */
+ m->sysex_mode = FALSE; /* skip to normal message send code */
+ }
+ if (m->sysex_mode) {
+ int b = 0; /* count bytes as they are handled */
+ while (b < 32 /* bits per word */ && (rslt == pmNoError)) {
+ int full;
+ unsigned char midi_byte = (unsigned char) (msg >> b);
+ if (!hdr) {
+ hdr = get_free_output_buffer(midi);
+ assert(hdr);
+ /* get ready to put sysex bytes in buffer */
+ start_sysex_buffer(hdr, delta);
+ }
+ full = add_byte_to_buffer(m, hdr, midi_byte);
+ if (midi_byte == MIDI_EOX) {
+ b = 24; /* pretend this is last byte to exit loop */
+ m->sysex_mode = FALSE;
+ }
+ /* see if it's time to send buffer, note that by always
+ sending complete sysex message right away, we can use
+ this code to set up the MIDIEVENT properly
+ */
+ if (full || midi_byte == MIDI_EOX) {
+ /* add bytes recorded to MIDIEVENT length, but don't
+ count the MIDIEVENT data (3 longs) */
+ MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData;
+ evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
+ /* round up BytesRecorded to multiple of 4 */
+ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
+ m->error = midiStreamOut(m->handle.stream, hdr,
+ sizeof(MIDIHDR));
+ if (m->error) {
+ rslt = pmHostError;
+ }
+ hdr = NULL; /* make sure we don't send it again */
+ }
+ b += 8; /* shift to next byte */
+ }
+ /* test rslt here in case it was set when we terminated a sysex early
+ (see above) */
+ } else if (rslt == pmNoError) {
+ int full = add_to_buffer(m, hdr, delta, msg);
+ if (full) {
+ m->error = midiStreamOut(m->handle.stream, hdr,
+ sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ hdr = NULL;
+ }
+ }
+ }
+ if (hdr && rslt == pmNoError) {
+ if (m->sysex_mode) {
+ MIDIEVENT *evt = (MIDIEVENT *) hdr->lpData;
+ evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
+ /* round up BytesRecorded to multiple of 4 */
+ hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
+ }
+ m->error = midiStreamOut(m->handle.stream, hdr, sizeof(MIDIHDR));
+ if (m->error) rslt = pmHostError;
+ }
+ }
+ return rslt;
+}
+#endif
+
+
+/* winmm_out_callback -- recycle sysex buffers */
+static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
+ DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
+{
+ int i;
+ PmInternal *midi = (PmInternal *) dwInstance;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
+ int err = 0; /* set to 0 so that no buffer match will also be an error */
+ static int entry = 0;
+ if (++entry > 1) {
+ assert(FALSE);
+ }
+ if (m->callback_error || wMsg != MOM_DONE) {
+ entry--;
+ return ;
+ }
+ /* Future optimization: eliminate UnprepareHeader calls -- they aren't
+ necessary; however, this code uses the prepared-flag to indicate which
+ buffers are free, so we need to do something to flag empty buffers if
+ we leave them prepared
+ */
+ m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr,
+ sizeof(MIDIHDR));
+ /* notify waiting sender that a buffer is available */
+ /* any errors could be reported via callback_error, but this is always
+ treated as a Midi error, so we'd have to write a lot more code to
+ detect that a non-Midi error occurred and do the right thing to find
+ the corresponding error message text. Therefore, just use assert()
+ */
+
+ /* determine if this is an output buffer or a sysex buffer */
+
+ for (i = 0 ;i < m->num_buffers;i++) {
+ if (hdr == m->buffers[i]) {
+ err = SetEvent(m->buffer_signal);
+ break;
+ }
+ }
+ for (i = 0 ;i < m->num_sysex_buffers;i++) {
+ if (hdr == m->sysex_buffers[i]) {
+ err = SetEvent(m->sysex_buffer_signal);
+ break;
+ }
+ }
+ assert(err); /* false -> error */
+ entry--;
+}
+
+
+/* winmm_streamout_callback -- unprepare (free) buffer header */
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+ DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
+{
+ PmInternal *midi = (PmInternal *) dwInstance;
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
+ int err;
+ static int entry = 0;
+ if (++entry > 1) {
+ /* We've reentered this routine. I assume this never happens, but
+ check to make sure. Apparently, it is possible that this callback
+ can be called reentrantly because it happened once while debugging.
+ It looks like this routine is actually reentrant so we can remove
+ the assertion if necessary. */
+ assert(FALSE);
+ }
+ if (m->callback_error || wMsg != MOM_DONE) {
+ entry--;
+ return ;
+ }
+ m->callback_error = midiOutUnprepareHeader(m->handle.out, hdr,
+ sizeof(MIDIHDR));
+ err = SetEvent(m->buffer_signal);
+ assert(err); /* false -> error */
+ entry--;
+}
+
+
+/*
+=========================================================================================
+begin exported functions
+=========================================================================================
+*/
+
+#define winmm_in_abort pm_fail_fn
+pm_fns_node pm_winmm_in_dictionary = {
+ none_write_short,
+ none_sysex,
+ none_sysex,
+ none_write_byte,
+ none_write_short,
+ none_write_flush,
+ winmm_synchronize,
+ winmm_in_open,
+ winmm_in_abort,
+ winmm_in_close,
+ success_poll,
+ winmm_has_host_error,
+ winmm_get_host_error
+ };
+
+pm_fns_node pm_winmm_out_dictionary = {
+ winmm_write_short,
+ winmm_begin_sysex,
+ winmm_end_sysex,
+ winmm_write_byte,
+ winmm_write_short, /* short realtime message */
+ winmm_write_flush,
+ winmm_synchronize,
+ winmm_out_open,
+ winmm_out_abort,
+ winmm_out_close,
+ none_poll,
+ winmm_has_host_error,
+ winmm_get_host_error
+ };
+
+
+/* initialize winmm interface. Note that if there is something wrong
+ with winmm (e.g. it is not supported or installed), it is not an
+ error. We should simply return without having added any devices to
+ the table. Hence, no error code is returned. Furthermore, this init
+ code is called along with every other supported interface, so the
+ user would have a very hard time figuring out what hardware and API
+ generated the error. Finally, it would add complexity to pmwin.c to
+ remember where the error code came from in order to convert to text.
+ */
+void pm_winmm_init( void )
+{
+ pm_winmm_mapper_input();
+ pm_winmm_mapper_output();
+ pm_winmm_general_inputs();
+ pm_winmm_general_outputs();
+}
+
+
+/* no error codes are returned, even if errors are encountered, because
+ there is probably nothing the user could do (e.g. it would be an error
+ to retry.
+ */
+void pm_winmm_term( void )
+{
+ int i;
+#ifdef DEBUG
+ char msg[PM_HOST_ERROR_MSG_LEN];
+#endif
+ int doneAny = 0;
+#ifdef DEBUG
+ printf("pm_winmm_term called\n");
+#endif
+ for (i = 0; i < pm_descriptor_index; i++) {
+ PmInternal * midi = descriptors[i].internalDescriptor;
+ if (midi) {
+ midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+ if (m->handle.out) {
+ /* close next open device*/
+#ifdef DEBUG
+ if (doneAny == 0) {
+ printf("begin closing open devices...\n");
+ doneAny = 1;
+ }
+ /* report any host errors; this EXTEREMELY useful when
+ trying to debug client app */
+ if (winmm_has_host_error(midi)) {
+ winmm_get_host_error(midi, msg, PM_HOST_ERROR_MSG_LEN);
+ printf(msg);
+ }
+#endif
+ /* close all open ports */
+ (*midi->dictionary->close)(midi);
+ }
+ }
+ }
+#ifdef DEBUG
+ if (doneAny) {
+ printf("warning: devices were left open. They have been closed.\n");
+ }
+ printf("pm_winmm_term exiting\n");
+#endif
+ pm_descriptor_index = 0;
+}
diff --git a/pd/portmidi/pm_win/pmwinmm.h b/pd/portmidi/pm_win/pmwinmm.h
new file mode 100644
index 00000000..53c5fe28
--- /dev/null
+++ b/pd/portmidi/pm_win/pmwinmm.h
@@ -0,0 +1,5 @@
+/* midiwin32.h -- system-specific definitions */
+
+void pm_winmm_init( void );
+void pm_winmm_term( void );
+
diff --git a/pd/portmidi/porttime/porttime.c b/pd/portmidi/porttime/porttime.c
new file mode 100644
index 00000000..71b06f4a
--- /dev/null
+++ b/pd/portmidi/porttime/porttime.c
@@ -0,0 +1,3 @@
+/* porttime.c -- portable API for millisecond timer */
+
+/* There is no machine-independent implementation code to put here */
diff --git a/pd/portmidi/porttime/porttime.dsp b/pd/portmidi/porttime/porttime.dsp
new file mode 100644
index 00000000..364373c9
--- /dev/null
+++ b/pd/portmidi/porttime/porttime.dsp
@@ -0,0 +1,104 @@
+# Microsoft Developer Studio Project File - Name="porttime" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Static Library" 0x0104
+
+CFG=porttime - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "porttime.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "porttime.mak" CFG="porttime - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "porttime - Win32 Release" (based on "Win32 (x86) Static Library")
+!MESSAGE "porttime - Win32 Debug" (based on "Win32 (x86) Static Library")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "porttime - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo
+
+!ELSEIF "$(CFG)" == "porttime - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "_LIB" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo
+
+!ENDIF
+
+# Begin Target
+
+# Name "porttime - Win32 Release"
+# Name "porttime - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\porttime.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ptwinmm.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE=.\porttime.h
+# End Source File
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/porttime/porttime.h b/pd/portmidi/porttime/porttime.h
new file mode 100644
index 00000000..029ea777
--- /dev/null
+++ b/pd/portmidi/porttime/porttime.h
@@ -0,0 +1,36 @@
+/* porttime.h -- portable interface to millisecond timer */
+
+/* CHANGE LOG FOR PORTTIME
+ 10-Jun-03 Mark Nelson & RBD
+ boost priority of timer thread in ptlinux.c implementation
+ */
+
+/* Should there be a way to choose the source of time here? */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+typedef enum {
+ ptNoError = 0,
+ ptHostError = -10000,
+ ptAlreadyStarted,
+ ptAlreadyStopped,
+ ptInsufficientMemory
+} PtError;
+
+
+typedef long PtTimestamp;
+
+typedef void (PtCallback)( PtTimestamp timestamp, void *userData );
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
+PtError Pt_Stop();
+int Pt_Started();
+PtTimestamp Pt_Time();
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/pd/portmidi/porttime/ptlinux.c b/pd/portmidi/porttime/ptlinux.c
new file mode 100644
index 00000000..c99abf04
--- /dev/null
+++ b/pd/portmidi/porttime/ptlinux.c
@@ -0,0 +1,120 @@
+/* ptlinux.c -- portable timer implementation for linux */
+
+
+/* IMPLEMENTATION NOTES (by Mark Nelson):
+
+Unlike Windows, Linux has no system call to request a periodic callback,
+so if Pt_Start() receives a callback parameter, it must create a thread
+that wakes up periodically and calls the provided callback function.
+If running as superuser, use setpriority() to renice thread to -20.
+One could also set the timer thread to a real-time priority (SCHED_FIFO
+and SCHED_RR), but this is dangerous for This is necessary because
+if the callback hangs it'll never return. A more serious reason
+is that the current scheduler implementation busy-waits instead
+of sleeping when realtime threads request a sleep of <=2ms (as a way
+to get around the 10ms granularity), which means the thread would never
+let anyone else on the CPU.
+
+CHANGE LOG
+
+18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer
+ thread. Simplified implementation notes.
+
+*/
+
+#include "porttime.h"
+#include "sys/time.h"
+#include "sys/resource.h"
+#include "sys/timeb.h"
+#include "pthread.h"
+
+#define TRUE 1
+#define FALSE 0
+
+static int time_started_flag = FALSE;
+static struct timeb time_offset = {0, 0, 0, 0};
+static pthread_t pt_thread_pid;
+
+/* note that this is static data -- we only need one copy */
+typedef struct {
+ int id;
+ int resolution;
+ PtCallback *callback;
+ void *userData;
+} pt_callback_parameters;
+
+static int pt_callback_proc_id = 0;
+
+static void *Pt_CallbackProc(void *p)
+{
+ pt_callback_parameters *parameters = (pt_callback_parameters *) p;
+ int mytime = 1;
+ /* to kill a process, just increment the pt_callback_proc_id */
+ /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
+ parameters->id); */
+ if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
+ while (pt_callback_proc_id == parameters->id) {
+ /* wait for a multiple of resolution ms */
+ struct timeval timeout;
+ int delay = mytime++ * parameters->resolution - Pt_Time();
+ if (delay < 0) delay = 0;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = delay * 1000;
+ select(0, NULL, NULL, NULL, &timeout);
+ (*(parameters->callback))(Pt_Time(), parameters->userData);
+ }
+ printf("Pt_CallbackProc exiting\n");
+// free(parameters);
+ return NULL;
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ if (time_started_flag) return ptNoError;
+ ftime(&time_offset); /* need this set before process runs */
+ if (callback) {
+ int res;
+ pt_callback_parameters *parms = (pt_callback_parameters *)
+ malloc(sizeof(pt_callback_parameters));
+ if (!parms) return ptInsufficientMemory;
+ parms->id = pt_callback_proc_id;
+ parms->resolution = resolution;
+ parms->callback = callback;
+ parms->userData = userData;
+ res = pthread_create(&pt_thread_pid, NULL,
+ Pt_CallbackProc, parms);
+ if (res != 0) return ptHostError;
+ }
+ time_started_flag = TRUE;
+ return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+ printf("Pt_Stop called\n");
+ pt_callback_proc_id++;
+ time_started_flag = FALSE;
+ return ptNoError;
+}
+
+
+int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+ long seconds, milliseconds;
+ struct timeb now;
+ ftime(&now);
+ seconds = now.time - time_offset.time;
+ milliseconds = now.millitm - time_offset.millitm;
+ return seconds * 1000 + milliseconds;
+}
+
+
+
diff --git a/pd/portmidi/porttime/ptmacosx_cf.c b/pd/portmidi/porttime/ptmacosx_cf.c
new file mode 100644
index 00000000..c0c22a32
--- /dev/null
+++ b/pd/portmidi/porttime/ptmacosx_cf.c
@@ -0,0 +1,135 @@
+/* ptmacosx.c -- portable timer implementation for mac os x */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <pthread.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+#import <mach/mach.h>
+#import <mach/mach_error.h>
+#import <mach/mach_time.h>
+#import <mach/clock.h>
+
+#include "porttime.h"
+
+#define THREAD_IMPORTANCE 30
+#define LONG_TIME 1000000000.0
+
+static int time_started_flag = FALSE;
+static CFAbsoluteTime startTime = 0.0;
+static CFRunLoopRef timerRunLoop;
+
+typedef struct {
+ int resolution;
+ PtCallback *callback;
+ void *userData;
+} PtThreadParams;
+
+
+void Pt_CFTimerCallback(CFRunLoopTimerRef timer, void *info)
+{
+ PtThreadParams *params = (PtThreadParams*)info;
+ (*params->callback)(Pt_Time(), params->userData);
+}
+
+static void* Pt_Thread(void *p)
+{
+ CFTimeInterval timerInterval;
+ CFRunLoopTimerContext timerContext;
+ CFRunLoopTimerRef timer;
+ PtThreadParams *params = (PtThreadParams*)p;
+ //CFTimeInterval timeout;
+
+ /* raise the thread's priority */
+ kern_return_t error;
+ thread_extended_policy_data_t extendedPolicy;
+ thread_precedence_policy_data_t precedencePolicy;
+
+ extendedPolicy.timeshare = 0;
+ error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
+ (thread_policy_t)&extendedPolicy,
+ THREAD_EXTENDED_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread timeshare policy", error);
+ }
+
+ precedencePolicy.importance = THREAD_IMPORTANCE;
+ error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
+ (thread_policy_t)&precedencePolicy,
+ THREAD_PRECEDENCE_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread precedence policy", error);
+ }
+
+ /* set up the timer context */
+ timerContext.version = 0;
+ timerContext.info = params;
+ timerContext.retain = NULL;
+ timerContext.release = NULL;
+ timerContext.copyDescription = NULL;
+
+ /* create a new timer */
+ timerInterval = (double)params->resolution / 1000.0;
+ timer = CFRunLoopTimerCreate(NULL, startTime+timerInterval, timerInterval,
+ 0, 0, Pt_CFTimerCallback, &timerContext);
+
+ timerRunLoop = CFRunLoopGetCurrent();
+ CFRunLoopAddTimer(timerRunLoop, timer, CFSTR("PtTimeMode"));
+
+ /* run until we're told to stop by Pt_Stop() */
+ CFRunLoopRunInMode(CFSTR("PtTimeMode"), LONG_TIME, false);
+
+ CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, CFSTR("PtTimeMode"));
+ CFRelease(timer);
+ free(params);
+
+ return NULL;
+}
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ PtThreadParams *params = (PtThreadParams*)malloc(sizeof(PtThreadParams));
+ pthread_t pthread_id;
+
+ printf("Pt_Start() called\n");
+
+ // /* make sure we're not already playing */
+ if (time_started_flag) return ptAlreadyStarted;
+ startTime = CFAbsoluteTimeGetCurrent();
+
+ if (callback) {
+
+ params->resolution = resolution;
+ params->callback = callback;
+ params->userData = userData;
+
+ pthread_create(&pthread_id, NULL, Pt_Thread, params);
+ }
+
+ time_started_flag = TRUE;
+ return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+ printf("Pt_Stop called\n");
+
+ CFRunLoopStop(timerRunLoop);
+ time_started_flag = FALSE;
+ return ptNoError;
+}
+
+
+int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+ CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
+ return (PtTimestamp) ((now - startTime) * 1000.0);
+}
+
diff --git a/pd/portmidi/porttime/ptmacosx_mach.c b/pd/portmidi/porttime/ptmacosx_mach.c
new file mode 100644
index 00000000..935d99bb
--- /dev/null
+++ b/pd/portmidi/porttime/ptmacosx_mach.c
@@ -0,0 +1,123 @@
+/* ptmacosx.c -- portable timer implementation for mac os x */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <CoreAudio/HostTime.h>
+
+#import <mach/mach.h>
+#import <mach/mach_error.h>
+#import <mach/mach_time.h>
+#import <mach/clock.h>
+
+#include "porttime.h"
+#include "sys/time.h"
+#include "pthread.h"
+
+#define NSEC_PER_MSEC 1000000
+#define THREAD_IMPORTANCE 30
+
+static int time_started_flag = FALSE;
+static UInt64 start_time;
+static pthread_t pt_thread_pid;
+
+/* note that this is static data -- we only need one copy */
+typedef struct {
+ int id;
+ int resolution;
+ PtCallback *callback;
+ void *userData;
+} pt_callback_parameters;
+
+static int pt_callback_proc_id = 0;
+
+static void *Pt_CallbackProc(void *p)
+{
+ pt_callback_parameters *parameters = (pt_callback_parameters *) p;
+ int mytime = 1;
+
+ kern_return_t error;
+ thread_extended_policy_data_t extendedPolicy;
+ thread_precedence_policy_data_t precedencePolicy;
+
+ extendedPolicy.timeshare = 0;
+ error = thread_policy_set(mach_thread_self(), THREAD_EXTENDED_POLICY,
+ (thread_policy_t)&extendedPolicy,
+ THREAD_EXTENDED_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread timeshare policy", error);
+ }
+
+ precedencePolicy.importance = THREAD_IMPORTANCE;
+ error = thread_policy_set(mach_thread_self(), THREAD_PRECEDENCE_POLICY,
+ (thread_policy_t)&precedencePolicy,
+ THREAD_PRECEDENCE_POLICY_COUNT);
+ if (error != KERN_SUCCESS) {
+ mach_error("Couldn't set thread precedence policy", error);
+ }
+
+
+ /* to kill a process, just increment the pt_callback_proc_id */
+ printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id, parameters->id);
+ while (pt_callback_proc_id == parameters->id) {
+ /* wait for a multiple of resolution ms */
+ UInt64 wait_time;
+ int delay = mytime++ * parameters->resolution - Pt_Time();
+ if (delay < 0) delay = 0;
+ wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
+ wait_time += AudioGetCurrentHostTime();
+ error = mach_wait_until(wait_time);
+ (*(parameters->callback))(Pt_Time(), parameters->userData);
+ }
+ printf("Pt_CallbackProc exiting\n");
+ free(parameters);
+ return NULL;
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ if (time_started_flag) return ptAlreadyStarted;
+ start_time = AudioGetCurrentHostTime();
+
+ if (callback) {
+ int res;
+ pt_callback_parameters *parms;
+
+ parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
+ if (!parms) return ptInsufficientMemory;
+ parms->id = pt_callback_proc_id;
+ parms->resolution = resolution;
+ parms->callback = callback;
+ parms->userData = userData;
+ res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
+ if (res != 0) return ptHostError;
+ }
+
+ time_started_flag = TRUE;
+ return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+ printf("Pt_Stop called\n");
+ pt_callback_proc_id++;
+ time_started_flag = FALSE;
+ return ptNoError;
+}
+
+
+int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+ UInt64 clock_time, nsec_time;
+ clock_time = AudioGetCurrentHostTime() - start_time;
+ nsec_time = AudioConvertHostTimeToNanos(clock_time);
+ return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
+}
+
diff --git a/pd/portmidi/porttime/ptwinmm.c b/pd/portmidi/porttime/ptwinmm.c
new file mode 100644
index 00000000..bbfebb0e
--- /dev/null
+++ b/pd/portmidi/porttime/ptwinmm.c
@@ -0,0 +1,65 @@
+/* ptwinmm.c -- portable timer implementation for win32 */
+
+
+#include "porttime.h"
+#include "windows.h"
+#include "time.h"
+
+
+TIMECAPS caps;
+
+static long time_offset = 0;
+static int time_started_flag = FALSE;
+static long time_resolution;
+static MMRESULT timer_id;
+static PtCallback *time_callback;
+
+void CALLBACK winmm_time_callback(UINT uID, UINT uMsg, DWORD dwUser,
+ DWORD dw1, DWORD dw2)
+{
+ (*time_callback)(Pt_Time(), (void *) dwUser);
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+ if (time_started_flag) return ptAlreadyStarted;
+ timeBeginPeriod(resolution);
+ time_resolution = resolution;
+ time_offset = timeGetTime();
+ time_started_flag = TRUE;
+ time_callback = callback;
+ if (callback) {
+ timer_id = timeSetEvent(resolution, 1, winmm_time_callback,
+ (DWORD) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
+ if (!timer_id) return ptHostError;
+ }
+ return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+ if (!time_started_flag) return ptAlreadyStopped;
+ if (time_callback && timer_id) {
+ timeKillEvent(timer_id);
+ time_callback = NULL;
+ timer_id = 0;
+ }
+ time_started_flag = FALSE;
+ timeEndPeriod(time_resolution);
+ return ptNoError;
+}
+
+
+int Pt_Started()
+{
+ return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+ return timeGetTime() - time_offset;
+}
+