diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,11 @@
 [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 
 
+## 0.2.0.1
+### Changed
+- Link to the shared PortMidi library per default.
+  No user visible changes, but difference in how to build the package.
+
 ## 0.2.0.0
 ### Changed
 - Error reporting (Breaking change).
diff --git a/PortMidi.cabal b/PortMidi.cabal
--- a/PortMidi.cabal
+++ b/PortMidi.cabal
@@ -1,67 +1,93 @@
+Cabal-Version: 2.2
 name:          PortMidi
-version:       0.2.0.0
-Cabal-Version: >= 1.6
+version:       0.2.0.1
 build-type:    Simple
-license:       BSD3
+license:       BSD-3-Clause
 license-file:  LICENSE
 maintainer:    Paul H. Liu <paul@thev.net>
-             , Olivier Sohn <olivier.sohn@gmail.com>
-homepage:      http://github.com/ninegua/PortMidi
+             , Henning Thielemann <haskell@henning-thielemann.de>
+homepage:      http://github.com/PortMidi/PortMidi
 category:      Sound
 stability:     experimental
 synopsis:      A binding for PortMedia/PortMidi
 description:   A Haskell binding for PortMedia/PortMidi
-extra-source-files:
-        README.txt
+extra-doc-files:
+        README.md
         CHANGELOG.md
+extra-source-files:
         portmidi/pm_common/portmidi.h
         portmidi/pm_common/pmutil.h
         portmidi/pm_common/pminternal.h
-        portmidi/pm_linux/pmlinux.h
         portmidi/pm_linux/pmlinuxalsa.h
-        portmidi/pm_mac/pmmac.h
+        portmidi/pm_linux/pmlinuxnull.h
         portmidi/pm_mac/pmmacosxcm.h
         portmidi/pm_win/pmwinmm.h
         portmidi/porttime/porttime.h
+
+Flag vendoredC
+  Description: Use bundled C code instead of shared PortMidi library
+  Manual: True
+  Default: False
+
+Flag pkgConfig
+  Description: Use PkgConfig (portmidi.pc) instead of Extra-Libraries
+  Manual: True
+  Default: False
+
 Library
   exposed-modules: Sound.PortMidi
   other-modules:   Sound.PortMidi.DeviceInfo
-  build-depends:   base >= 3 && < 5
-  extensions:      CPP, ForeignFunctionInterface
-  if os(linux) || os(freebsd)
-    include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime
-    cc-options: -DPMALSA
-    c-sources:
+  build-depends:   base >= 4.8 && < 5
+  default-language:    Haskell98
+  default-extensions:  CPP, ForeignFunctionInterface
+  if flag(vendoredC)
+    if os(linux) || os(freebsd)
+      include-dirs: portmidi/pm_common portmidi/pm_linux portmidi/porttime
+      cc-options: -DPMALSA
+      c-sources:
           portmidi/pm_common/pmutil.c
           portmidi/pm_common/portmidi.c
           portmidi/pm_linux/pmlinux.c
           portmidi/pm_linux/pmlinuxalsa.c
+          portmidi/pm_linux/pmlinuxnull.c
           portmidi/porttime/porttime.c
           portmidi/porttime/ptlinux.c
-   extra-libraries: asound
-  else
-    if os(darwin)
-     include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime
-     cc-options:   -msse2
-     c-sources:
+      extra-libraries: asound
+    else
+      if os(darwin)
+        include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime
+        cc-options:   -msse2
+        c-sources:
           portmidi/pm_common/pmutil.c
           portmidi/pm_common/portmidi.c
           portmidi/pm_mac/pmmac.c
           portmidi/pm_mac/pmmacosxcm.c
           portmidi/porttime/porttime.c
+          portmidi/porttime/ptmacosx_cf.c
           portmidi/porttime/ptmacosx_mach.c
-     frameworks: CoreMIDI CoreFoundation CoreAudio
+        frameworks: CoreMIDI CoreFoundation CoreAudio
+      else
+        if os(mingw32)
+          include-dirs: portmidi/pm_common portmidi/pm_win portmidi/porttime
+          c-sources:
+            portmidi/pm_common/pmutil.c
+            portmidi/pm_common/portmidi.c
+            portmidi/pm_win/pmwin.c
+            portmidi/pm_win/pmwinmm.c
+            portmidi/porttime/porttime.c
+            portmidi/porttime/ptwinmm.c
+          extra-libraries: winmm
+  else
+    if flag(pkgConfig)
+      pkgconfig-depends: portmidi
     else
-      if os(mingw32)
-        include-dirs: portmidi/pm_common portmidi/pm_win portmidi/porttime
-        c-sources:
-          portmidi/pm_common/pmutil.c
-          portmidi/pm_common/portmidi.c
-          portmidi/pm_win/pmwin.c
-          portmidi/pm_win/pmwinmm.c
-          portmidi/porttime/porttime.c
-          portmidi/porttime/ptwinmm.c
-        extra-libraries: winmm
+      extra-libraries: portmidi
+
 source-repository head
   type:         git
-  location:     https://github.com/ninegua/PortMidi
+  location:     https://github.com/PortMidi/PortMidi-haskell
+
+source-repository this
+  tag:          0.2.0.1
+  type:         git
+  location:     https://github.com/PortMidi/PortMidi-haskell
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+This is a Haskell module for PortMidi audio library, which supports
+real-time MIDI input and output.
+
+
+ChangeLog
+=========
+
+Please see [CHANGELOG.md](CHANGELOG.md).
+
+
+Installation
+============
+
+The usual cabal installation steps apply, either:
+
+       runhaskell Setup.hs configure
+       runhaskell Setup.hs build
+       runhaskell Setup.hs install
+
+or simply:
+
+       cabal install
+
+This will install a PortMidi package that contains a `Sound.PortMidi` module.
+
+
+Bug reports / Feature requests
+==============================
+
+Bug reports and feature requests can be created at
+[GitHub](https://github.com/PortMidi/portmidi-haskell/issues).
+
+
+Examples
+========
+
+* https://hackage.haskell.org/package/Euterpea
+* https://hackage.haskell.org/package/portmidi-utility
+* https://hackage.haskell.org/package/PortMidi-simple
diff --git a/README.txt b/README.txt
deleted file mode 100644
--- a/README.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-This is a Haskell module for PortMidi audio library, which supports
-real-time MIDI input and output.
-
-=========
-ChangeLog
-=========
-
-Please see CHANGELOG.md.
-
-============
-Installation
-============
-
-The usual cabal installation steps apply, either:
-
-       runhaskell Setup.hs configure
-       runhaskell Setup.hs build
-       runhaskell Setup.hs install
-
-or simply:
-
-       cabal install
-
-This will install a PortMidi package that contains a Sound.PortMidi module.
-
-==============================
-Bug reports / Feature requests
-==============================
-
-Bug reports and feature requests can be created here:
-https://github.com/ninegua/PortMidi/issues.
diff --git a/portmidi/pm_common/pminternal.h b/portmidi/pm_common/pminternal.h
--- a/portmidi/pm_common/pminternal.h
+++ b/portmidi/pm_common/pminternal.h
@@ -1,174 +1,191 @@
-/* pminternal.h -- header for interface implementations */
-
-/* this file is included by files that implement library internals */
-/* Here is a guide to implementers:
-     provide an initialization function similar to pm_winmm_init()
-     add your initialization function to pm_init()
-     Note that your init function should never require not-standard
-         libraries or fail in any way. If the interface is not available,
-         simply do not call pm_add_device. This means that non-standard
-         libraries should try to do dynamic linking at runtime using a DLL
-         and return without error if the DLL cannot be found or if there
-         is any other failure.
-     implement functions as indicated in pm_fns_type to open, read, write,
-         close, etc.
-     call pm_add_device() for each input and output device, passing it a
-         pm_fns_type structure.
-     assumptions about pm_fns_type functions are given below.
- */
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* these are defined in system-specific file */
-void *pm_alloc(size_t s);
-void pm_free(void *ptr);
-
-/* if an error occurs while opening or closing a midi stream, set these: */
-extern int pm_hosterror;
-extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
- 
-struct pm_internal_struct;
-
-/* these do not use PmInternal because it is not defined yet... */
-typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, 
-                                     PmEvent *buffer);
-typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
-                                     PmTimestamp timestamp);
-typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
-                                   PmTimestamp timestamp);
-typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
-                                    unsigned char byte, PmTimestamp timestamp);
-typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
-                                        PmEvent *buffer);
-typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
-                                     PmTimestamp timestamp);
-typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
-/* pm_open_fn should clean up all memory and close the device if any part
-   of the open fails */
-typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
-                              void *driverInfo);
-typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);
-/* pm_close_fn should clean up all memory and close the device if any
-   part of the close fails. */
-typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);
-typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);
-typedef void (*pm_host_error_fn)(struct pm_internal_struct *midi, char * msg,
-                                 unsigned int len);
-typedef unsigned int (*pm_has_host_error_fn)(struct pm_internal_struct *midi);
-
-typedef struct {
-    pm_write_short_fn write_short; /* output short MIDI msg */
-    pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
-    pm_end_sysex_fn end_sysex; /* marks end of sysex message */
-    pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
-    pm_write_realtime_fn write_realtime; /* send real-time message within sysex */
-    pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
-    pm_synchronize_fn synchronize; /* synchronize portmidi time to stream time */
-    pm_open_fn open;   /* open MIDI device */
-    pm_abort_fn abort; /* abort */
-    pm_close_fn close; /* close device */
-    pm_poll_fn poll;   /* read pending midi events into portmidi buffer */
-    pm_has_host_error_fn has_host_error; /* true when device has had host 
-                                            error message */
-    pm_host_error_fn host_error; /* provide text readable host error message
-                                    for device (clears and resets) */
-} pm_fns_node, *pm_fns_type;
-
-
-/* when open fails, the dictionary gets this set of functions: */
-extern pm_fns_node pm_none_dictionary;
-
-typedef struct {
-    PmDeviceInfo pub; /* some portmidi state also saved in here (for autmatic
-                         device closing (see PmDeviceInfo struct) */
-    void *descriptor; /* ID number passed to win32 multimedia API open */
-    void *internalDescriptor; /* points to PmInternal device, allows automatic 
-                                 device closing */
-    pm_fns_type dictionary;
-} descriptor_node, *descriptor_type;
-
-extern int pm_descriptor_max;
-extern descriptor_type descriptors;
-extern int pm_descriptor_index;
-
-typedef unsigned long (*time_get_proc_type)(void *time_info);
-
-typedef struct pm_internal_struct {
-    int device_id; /* which device is open (index to descriptors) */
-    short write_flag; /* MIDI_IN, or MIDI_OUT */
-    
-    PmTimeProcPtr time_proc; /* where to get the time */
-    void *time_info; /* pass this to get_time() */
-    long buffer_len; /* how big is the buffer or queue? */
-    PmQueue *queue;
-
-    long latency; /* time delay in ms between timestamps and actual output */
-                  /* set to zero to get immediate, simple blocking output */
-                  /* if latency is zero, timestamps will be ignored; */
-                  /* if midi input device, this field ignored */
-    
-    int sysex_in_progress; /* when sysex status is seen, this flag becomes
-        * true until EOX is seen. When true, new data is appended to the
-        * stream of outgoing bytes. When overflow occurs, sysex data is 
-        * dropped (until an EOX or non-real-timei status byte is seen) so
-        * that, if the overflow condition is cleared, we don't start 
-        * sending data from the middle of a sysex message. If a sysex
-        * message is filtered, sysex_in_progress is false, causing the
-        * message to be dropped. */
-    PmMessage sysex_message; /* buffer for 4 bytes of sysex data */
-    int sysex_message_count; /* how many bytes in sysex_message so far */
-
-    long filters; /* flags that filter incoming message classes */
-    int channel_mask; /* flter incoming messages based on channel */
-    PmTimestamp last_msg_time; /* timestamp of last message */
-    PmTimestamp sync_time; /* time of last synchronization */
-    PmTimestamp now; /* set by PmWrite to current time */
-    int first_message; /* initially true, used to run first synchronization */
-    pm_fns_type dictionary; /* implementation functions */
-    void *descriptor; /* system-dependent state */
-    /* the following are used to expedite sysex data */
-    /* on windows, in debug mode, based on some profiling, these optimizations
-     * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte,
-     * but this does not count time in the driver, so I don't know if it is
-     * important
-     */
-    unsigned char *fill_base; /* addr of ptr to sysex data */
-    unsigned long *fill_offset_ptr; /* offset of next sysex byte */
-    int fill_length; /* how many sysex bytes to write */
-} PmInternal;
-
-
-/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
-void pm_init(void); 
-void pm_term(void); 
-
-/* defined by portMidi, used by pmwinmm */
-PmError none_write_short(PmInternal *midi, PmEvent *buffer);
-PmError none_write_byte(PmInternal *midi, unsigned char byte, 
-                        PmTimestamp timestamp);
-PmTimestamp none_synchronize(PmInternal *midi);
-
-PmError pm_fail_fn(PmInternal *midi);
-PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);
-PmError pm_success_fn(PmInternal *midi);
-PmError pm_add_device(char *interf, char *name, int input, void *descriptor,
-                      pm_fns_type dictionary);
-unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, int len,
-                           PmTimestamp timestamp);
-void pm_read_short(PmInternal *midi, PmEvent *event);
-
-#define none_write_flush pm_fail_timestamp_fn
-#define none_sysex pm_fail_timestamp_fn
-#define none_poll pm_fail_fn
-#define success_poll pm_success_fn
-
-#define MIDI_REALTIME_MASK 0xf8
-#define is_real_time(msg) \
-    ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
-
-#ifdef __cplusplus
-}
-#endif
-
+/** @file pminternal.h header for PortMidi implementations */
+
+/* this file is included by files that implement library internals */
+/* Here is a guide to implementers:
+     provide an initialization function similar to pm_winmm_init()
+     add your initialization function to pm_init()
+     Note that your init function should never require not-standard
+         libraries or fail in any way. If the interface is not available,
+         simply do not call pm_add_device. This means that non-standard
+         libraries should try to do dynamic linking at runtime using a DLL
+         and return without error if the DLL cannot be found or if there
+         is any other failure.
+     implement functions as indicated in pm_fns_type to open, read, write,
+         close, etc.
+     call pm_add_device() for each input and output device, passing it a
+         pm_fns_type structure.
+     assumptions about pm_fns_type functions are given below.
+ */
+
+/* add INTERNAL to Doxygen ENABLED_SECTIONS to include this: */
+/** @cond INTERNAL */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int pm_initialized; /* see note in portmidi.c */
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id;
+
+/* these are defined in system-specific file */
+void *pm_alloc(size_t s);
+void pm_free(void *ptr);
+
+/* if a host error (an error reported by the host MIDI API that is not
+ * mapped to a PortMidi error code) occurs in a synchronous operation 
+ * (i.e., not in a callback from another thread) set these: */
+extern int pm_hosterror;  /* boolean */
+extern char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
+ 
+struct pm_internal_struct;
+
+/* these do not use PmInternal because it is not defined yet... */
+typedef PmError (*pm_write_short_fn)(struct pm_internal_struct *midi, 
+                                     PmEvent *buffer);
+typedef PmError (*pm_begin_sysex_fn)(struct pm_internal_struct *midi,
+                                     PmTimestamp timestamp);
+typedef PmError (*pm_end_sysex_fn)(struct pm_internal_struct *midi,
+                                   PmTimestamp timestamp);
+typedef PmError (*pm_write_byte_fn)(struct pm_internal_struct *midi,
+                                    unsigned char byte, PmTimestamp timestamp);
+typedef PmError (*pm_write_realtime_fn)(struct pm_internal_struct *midi,
+                                        PmEvent *buffer);
+typedef PmError (*pm_write_flush_fn)(struct pm_internal_struct *midi,
+                                     PmTimestamp timestamp);
+typedef PmTimestamp (*pm_synchronize_fn)(struct pm_internal_struct *midi);
+/* pm_open_fn should clean up all memory and close the device if any part
+   of the open fails */
+typedef PmError (*pm_open_fn)(struct pm_internal_struct *midi,
+                              void *driverInfo);
+typedef PmError (*pm_create_fn)(int is_input, const char *name,
+                                void *driverInfo);
+typedef PmError (*pm_delete_fn)(PmDeviceID id);
+typedef PmError (*pm_abort_fn)(struct pm_internal_struct *midi);
+/* pm_close_fn should clean up all memory and close the device if any
+   part of the close fails. */
+typedef PmError (*pm_close_fn)(struct pm_internal_struct *midi);
+typedef PmError (*pm_poll_fn)(struct pm_internal_struct *midi);
+typedef unsigned int (*pm_check_host_error_fn)(struct pm_internal_struct *midi);
+
+typedef struct {
+    pm_write_short_fn write_short; /* output short MIDI msg */
+    pm_begin_sysex_fn begin_sysex; /* prepare to send a sysex message */
+    pm_end_sysex_fn end_sysex; /* marks end of sysex message */
+    pm_write_byte_fn write_byte; /* accumulate one more sysex byte */
+    pm_write_realtime_fn write_realtime; /* send real-time msg within sysex */
+    pm_write_flush_fn write_flush; /* send any accumulated but unsent data */
+    pm_synchronize_fn synchronize; /* synchronize PM time to stream time */
+    pm_open_fn open;   /* open MIDI device */
+    pm_abort_fn abort; /* abort */
+    pm_close_fn close; /* close device */
+    pm_poll_fn poll;   /* read pending midi events into portmidi buffer */
+    pm_check_host_error_fn check_host_error; /* true when device has had host */
+          /* error; sets pm_hosterror and writes message to pm_hosterror_text */
+} pm_fns_node, *pm_fns_type;
+
+
+/* when open fails, the dictionary gets this set of functions: */
+extern pm_fns_node pm_none_dictionary;
+
+typedef struct {
+    PmDeviceInfo pub; /* some portmidi state also saved in here (for automatic
+                         device closing -- see PmDeviceInfo struct) */
+    int deleted; /* is this is a deleted virtual device? */
+    void *descriptor; /* ID number passed to win32 multimedia API open, 
+                       * coreMIDI endpoint, etc., representing the device */
+    struct pm_internal_struct *pm_internal; /* points to PmInternal device */
+               /* when the device is open, allows automatic device closing */
+    pm_fns_type dictionary;
+} descriptor_node, *descriptor_type;
+
+extern int pm_descriptor_max;
+extern descriptor_type pm_descriptors;
+extern int pm_descriptor_len;
+
+typedef uint32_t (*time_get_proc_type)(void *time_info);
+
+typedef struct pm_internal_struct {
+    int device_id; /* which device is open (index to pm_descriptors) */
+    short is_input; /* MIDI IN (true) or MIDI OUT (false) */
+    short is_removed;  /* MIDI device was removed */
+    PmTimeProcPtr time_proc; /* where to get the time */
+    void *time_info; /* pass this to get_time() */
+    int32_t buffer_len; /* how big is the buffer or queue? */
+    PmQueue *queue;
+
+    int32_t latency; /* time delay in ms between timestamps and actual output */
+                  /* set to zero to get immediate, simple blocking output */
+                  /* if latency is zero, timestamps will be ignored; */
+                  /* if midi input device, this field ignored */
+    
+    int sysex_in_progress; /* when sysex status is seen, this flag becomes
+        * true until EOX is seen. When true, new data is appended to the
+        * stream of outgoing bytes. When overflow occurs, sysex data is 
+        * dropped (until an EOX or non-real-timei status byte is seen) so
+        * that, if the overflow condition is cleared, we don't start 
+        * sending data from the middle of a sysex message. If a sysex
+        * message is filtered, sysex_in_progress is false, causing the
+        * message to be dropped. */
+    PmMessage message; /* buffer for 4 bytes of sysex data */
+    int message_count; /* how many bytes in sysex_message so far */
+    int short_message_count; /* how many bytes are expected in short message */
+    unsigned char running_status; /* running status byte or zero if none */
+    int32_t filters; /* flags that filter incoming message classes */
+    int32_t channel_mask; /* filter incoming messages based on channel */
+    PmTimestamp last_msg_time; /* timestamp of last message */
+    PmTimestamp sync_time; /* time of last synchronization */
+    PmTimestamp now; /* set by PmWrite to current time */
+    int first_message; /* initially true, used to run first synchronization */
+    pm_fns_type dictionary; /* implementation functions */
+    void *api_info; /* system-dependent state */
+    /* the following are used to expedite sysex data */
+    /* on windows, in debug mode, based on some profiling, these optimizations
+     * cut the time to process sysex bytes from about 7.5 to 0.26 usec/byte,
+     * but this does not count time in the driver, so I don't know if it is
+     * important
+     */
+    unsigned char *fill_base; /* addr of ptr to sysex data */
+    uint32_t *fill_offset_ptr; /* offset of next sysex byte */
+    uint32_t fill_length; /* how many sysex bytes to write */
+} PmInternal;
+
+/* what is the length of this short message? */
+int pm_midi_length(PmMessage msg);
+
+/* defined by system specific implementation, e.g. pmwinmm, used by PortMidi */
+void pm_init(void); 
+void pm_term(void); 
+
+/* defined by portMidi, used by pmwinmm */
+PmError none_write_short(PmInternal *midi, PmEvent *buffer);
+PmError none_write_byte(PmInternal *midi, unsigned char byte, 
+                        PmTimestamp timestamp);
+PmTimestamp none_synchronize(PmInternal *midi);
+
+PmError pm_fail_fn(PmInternal *midi);
+PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp);
+PmError pm_success_fn(PmInternal *midi);
+PmError pm_add_interf(const char *interf, pm_create_fn create_fn,
+                      pm_delete_fn delete_fn);
+PmError pm_add_device(const char *interf, const char *name, int is_input,
+                      int is_virtual, void *descriptor, pm_fns_type dictionary);
+void pm_undo_add_device(int id);
+uint32_t pm_read_bytes(PmInternal *midi, const unsigned char *data, int len,
+                           PmTimestamp timestamp);
+void pm_read_short(PmInternal *midi, PmEvent *event);
+
+#define none_write_flush pm_fail_timestamp_fn
+#define none_sysex pm_fail_timestamp_fn
+#define none_poll pm_fail_fn
+#define success_poll pm_success_fn
+
+#define MIDI_REALTIME_MASK 0xf8
+#define is_real_time(msg) \
+    ((Pm_MessageStatus(msg) & MIDI_REALTIME_MASK) == MIDI_REALTIME_MASK)
+
+#ifdef __cplusplus
+}
+#endif
+
+/** @endcond */
diff --git a/portmidi/pm_common/pmutil.c b/portmidi/pm_common/pmutil.c
--- a/portmidi/pm_common/pmutil.c
+++ b/portmidi/pm_common/pmutil.c
@@ -1,289 +1,283 @@
-/* pmutil.c -- some helpful utilities for building midi
-               applications that use PortMidi
- */
-#include <stdlib.h>
-#include <assert.h>
-#include <string.h>
-#include "portmidi.h"
-#include "pmutil.h"
-#include "pminternal.h"
-
-#ifdef WIN32
-#define bzero(addr, siz) memset(addr, 0, siz)
-#endif
-
-// #define QUEUE_DEBUG 1
-#ifdef QUEUE_DEBUG
-#include "stdio.h"
-#endif
-
-/* code is based on 4-byte words -- it should work on a 64-bit machine
-   as long as a "long" has 4 bytes. This code could be generalized to
-   be independent of the size of "long" */
-
-typedef long int32;
-
-typedef struct {
-    long head;
-    long tail;
-    long len;
-    long msg_size; /* number of int32 in a message including extra word */
-    long overflow;
-    long peek_overflow;
-    int32 *buffer;
-    int32 *peek;
-    int peek_flag;
-} PmQueueRep;
-
-
-PmQueue *Pm_QueueCreate(long num_msgs, long bytes_per_msg)
-{
-    int int32s_per_msg = ((bytes_per_msg + sizeof(int32) - 1) &
-                          ~(sizeof(int32) - 1)) / sizeof(int32);
-    PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
-    if (!queue) /* memory allocation failed */
-        return NULL;
-
-    /* need extra word per message for non-zero encoding */
-    queue->len = num_msgs * (int32s_per_msg + 1);
-    queue->buffer = (int32 *) pm_alloc(queue->len * sizeof(int32));
-    bzero(queue->buffer, queue->len * sizeof(int32));
-    if (!queue->buffer) {
-        pm_free(queue);
-        return NULL;
-    } else { /* allocate the "peek" buffer */
-        queue->peek = (int32 *) pm_alloc(int32s_per_msg * sizeof(int32));
-        if (!queue->peek) {
-            /* free everything allocated so far and return */
-            pm_free(queue->buffer);
-            pm_free(queue);
-            return NULL;
-        }
-    }
-    bzero(queue->buffer, queue->len * sizeof(int32));
-    queue->head = 0;
-    queue->tail = 0;
-    /* msg_size is in words */
-    queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */
-    queue->overflow = FALSE;
-    queue->peek_overflow = FALSE;
-    queue->peek_flag = FALSE;
-    return queue;
-}
-
-
-PmError Pm_QueueDestroy(PmQueue *q)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-        
-    /* arg checking */
-    if (!queue || !queue->buffer || !queue->peek) 
-                return pmBadPtr;
-    
-    pm_free(queue->peek);
-    pm_free(queue->buffer);
-    pm_free(queue);
-    return pmNoError;
-}
-
-
-PmError Pm_Dequeue(PmQueue *q, void *msg)
-{
-    long head;
-    PmQueueRep *queue = (PmQueueRep *) q;
-    int i;
-    int32 *msg_as_int32 = (int32 *) msg;
-
-    /* arg checking */
-    if (!queue)
-        return pmBadPtr;
-    /* a previous peek operation encountered an overflow, but the overflow
-     * has not yet been reported to client, so do it now. No message is
-     * returned, but on the next call, we will return the peek buffer.
-     */
-    if (queue->peek_overflow) {
-        queue->peek_overflow = FALSE;
-        return pmBufferOverflow;
-    }
-    if (queue->peek_flag) {
-        memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32));
-        queue->peek_flag = FALSE;
-        return pmGotData;
-    }
-
-    head = queue->head;
-    /* if writer overflows, it writes queue->overflow = tail+1 so that
-     * when the reader gets to that position in the buffer, it can 
-     * return the overflow condition to the reader. The problem is that
-     * at overflow, things have wrapped around, so tail == head, and the
-     * reader will detect overflow immediately instead of waiting until
-     * it reads everything in the buffer, wrapping around again to the
-     * point where tail == head. So the condition also checks that
-     * queue->buffer[head] is zero -- if so, then the buffer is now
-     * empty, and we're at the point in the msg stream where overflow
-     * occurred. It's time to signal overflow to the reader. If 
-     * queue->buffer[head] is non-zero, there's a message there and we
-     * should read all the way around the buffer before signalling overflow.
-     * There is a write-order dependency here, but to fail, the overflow
-     * field would have to be written while an entire buffer full of 
-     * writes are still pending. I'm assuming out-of-order writes are
-     * possible, but not that many.
-     */
-    if (queue->overflow == head + 1 && !queue->buffer[head]) {
-        queue->overflow = 0; /* non-overflow condition */
-        return pmBufferOverflow;
-    }
-
-    /* test to see if there is data in the queue -- test from back
-     * to front so if writer is simultaneously writing, we don't
-     * waste time discovering the write is not finished 
-     */
-    for (i = queue->msg_size - 1; i >= 0; i--) {
-        if (!queue->buffer[head + i]) {
-            return pmNoData;
-        }
-    }
-    memcpy(msg, (char *) &queue->buffer[head + 1], 
-           sizeof(int32) * (queue->msg_size - 1));
-    /* fix up zeros */
-    i = queue->buffer[head];
-    while (i < queue->msg_size) {
-        int32 j;
-        i--; /* msg does not have extra word so shift down */
-        j = msg_as_int32[i];
-        msg_as_int32[i] = 0;
-        i = j;
-    }
-    /* signal that data has been removed by zeroing: */
-    bzero((char *) &queue->buffer[head], sizeof(int32) * queue->msg_size);
-
-    /* update head */
-    head += queue->msg_size;
-    if (head == queue->len) head = 0;
-    queue->head = head;
-    return pmGotData; /* success */
-}
-
-
-
-PmError Pm_SetOverflow(PmQueue *q)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-    long tail;
-    /* arg checking */
-    if (!queue)
-        return pmBadPtr;
-    /* no more enqueue until receiver acknowledges overflow */
-    if (queue->overflow) return pmBufferOverflow;
-    tail = queue->tail;
-    queue->overflow = tail + 1;
-    return pmBufferOverflow;
-}
-
-
-PmError Pm_Enqueue(PmQueue *q, void *msg)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-    long tail;
-    int i;
-    int32 *src = (int32 *) msg;
-    int32 *ptr;
-    int32 *dest;
-    int rslt;
-    if (!queue) 
-        return pmBadPtr;
-    /* no more enqueue until receiver acknowledges overflow */
-    if (queue->overflow) return pmBufferOverflow;
-    rslt = Pm_QueueFull(q);
-    /* already checked above: if (rslt == pmBadPtr) return rslt; */
-    tail = queue->tail;
-    if (rslt) {
-        queue->overflow = tail + 1;
-        return pmBufferOverflow;
-    }
-
-    /* queue is has room for message, and overflow flag is cleared */
-    ptr = &queue->buffer[tail];
-    dest = ptr + 1;
-    for (i = 1; i < queue->msg_size; i++) {
-        int32 j = src[i - 1];
-        if (!j) {
-            *ptr = i;
-            ptr = dest;
-        } else {
-            *dest = j;
-        }
-        dest++;
-    }
-    *ptr = i;
-    tail += queue->msg_size;
-    if (tail == queue->len) tail = 0;
-    queue->tail = tail;
-    return pmNoError;
-}
-
-
-int Pm_QueueEmpty(PmQueue *q)
-{
-    PmQueueRep *queue = (PmQueueRep *) q;
-    return (!queue) ||  /* null pointer -> return "empty" */
-           (queue->buffer[queue->head] == 0 && !queue->peek_flag);
-}
-
-
-int Pm_QueueFull(PmQueue *q)
-{
-    int tail;
-    int i; 
-    PmQueueRep *queue = (PmQueueRep *) q;
-    /* arg checking */
-    if (!queue)
-        return pmBadPtr;
-    tail = queue->tail;
-    /* test to see if there is space in the queue */
-    for (i = 0; i < queue->msg_size; i++) {
-        if (queue->buffer[tail + i]) {
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-
-void *Pm_QueuePeek(PmQueue *q)
-{
-    PmError rslt;
-    long temp;
-    PmQueueRep *queue = (PmQueueRep *) q;
-    /* arg checking */
-    if (!queue)
-        return NULL;
-
-    if (queue->peek_flag) {
-        return queue->peek;
-    }
-    /* this is ugly: if peek_overflow is set, then Pm_Dequeue() 
-     * returns immediately with pmBufferOverflow, but here, we
-     * want Pm_Dequeue() to really check for data. If data is
-     * there, we can return it
-     */
-    temp = queue->peek_overflow;
-    queue->peek_overflow = FALSE;
-    rslt = Pm_Dequeue(q, queue->peek);
-    queue->peek_overflow = temp;
-
-    if (rslt == 1) {
-        queue->peek_flag = TRUE;
-        return queue->peek;
-    } else if (rslt == pmBufferOverflow) {
-        /* when overflow is indicated, the queue is empty and the 
-         * first message that was dropped by Enqueue (signalling
-         * pmBufferOverflow to its caller) would have been the next
-         * message in the queue. Pm_QueuePeek will return NULL, but
-         * remember that an overflow occurred. (see Pm_Dequeue)
-         */
-        queue->peek_overflow = TRUE;
-    }
-    return NULL;
-}
-
+/* pmutil.c -- some helpful utilities for building midi
+               applications that use PortMidi
+ */
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+
+#ifdef WIN32
+#define bzero(addr, siz) memset(addr, 0, siz)
+#endif
+
+/* #define QUEUE_DEBUG 1 */
+#ifdef QUEUE_DEBUG
+#include "stdio.h"
+#endif
+
+typedef struct {
+    long head;
+    long tail;
+    long len;
+    long overflow;
+    int32_t msg_size; /* number of int32_t in a message including extra word */
+    int32_t peek_overflow;
+    int32_t *buffer;
+    int32_t *peek;
+    int32_t peek_flag;
+} PmQueueRep;
+
+
+PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg)
+{
+    int32_t int32s_per_msg = 
+            (int32_t) (((bytes_per_msg + sizeof(int32_t) - 1) &
+                       ~(sizeof(int32_t) - 1)) / sizeof(int32_t));
+    PmQueueRep *queue = (PmQueueRep *) pm_alloc(sizeof(PmQueueRep));
+    if (!queue) /* memory allocation failed */
+        return NULL;
+
+    /* need extra word per message for non-zero encoding */
+    queue->len = num_msgs * (int32s_per_msg + 1);
+    queue->buffer = (int32_t *) pm_alloc(queue->len * sizeof(int32_t));
+    if (!queue->buffer) {
+        pm_free(queue);
+        return NULL;
+    } else { /* allocate the "peek" buffer */
+        queue->peek = (int32_t *) pm_alloc(int32s_per_msg * sizeof(int32_t));
+        if (!queue->peek) {
+            /* free everything allocated so far and return */
+            pm_free(queue->buffer);
+            pm_free(queue);
+            return NULL;
+        }
+    }
+    bzero(queue->buffer, queue->len * sizeof(int32_t));
+    queue->head = 0;
+    queue->tail = 0;
+    /* msg_size is in words */
+    queue->msg_size = int32s_per_msg + 1; /* note extra word is counted */
+    queue->overflow = FALSE;
+    queue->peek_overflow = FALSE;
+    queue->peek_flag = FALSE;
+    return queue;
+}
+
+
+PMEXPORT PmError Pm_QueueDestroy(PmQueue *q)
+{
+    PmQueueRep *queue = (PmQueueRep *) q;
+        
+    /* arg checking */
+    if (!queue || !queue->buffer || !queue->peek) 
+                return pmBadPtr;
+    
+    pm_free(queue->peek);
+    pm_free(queue->buffer);
+    pm_free(queue);
+    return pmNoError;
+}
+
+
+PMEXPORT PmError Pm_Dequeue(PmQueue *q, void *msg)
+{
+    long head;
+    PmQueueRep *queue = (PmQueueRep *) q;
+    int i;
+    int32_t *msg_as_int32 = (int32_t *) msg;
+
+    /* arg checking */
+    if (!queue)
+        return pmBadPtr;
+    /* a previous peek operation encountered an overflow, but the overflow
+     * has not yet been reported to client, so do it now. No message is
+     * returned, but on the next call, we will return the peek buffer.
+     */
+    if (queue->peek_overflow) {
+        queue->peek_overflow = FALSE;
+        return pmBufferOverflow;
+    }
+    if (queue->peek_flag) {
+        memcpy(msg, queue->peek, (queue->msg_size - 1) * sizeof(int32_t));
+        queue->peek_flag = FALSE;
+        return pmGotData;
+    }
+
+    head = queue->head;
+    /* if writer overflows, it writes queue->overflow = tail+1 so that
+     * when the reader gets to that position in the buffer, it can 
+     * return the overflow condition to the reader. The problem is that
+     * at overflow, things have wrapped around, so tail == head, and the
+     * reader will detect overflow immediately instead of waiting until
+     * it reads everything in the buffer, wrapping around again to the
+     * point where tail == head. So the condition also checks that
+     * queue->buffer[head] is zero -- if so, then the buffer is now
+     * empty, and we're at the point in the msg stream where overflow
+     * occurred. It's time to signal overflow to the reader. If 
+     * queue->buffer[head] is non-zero, there's a message there and we
+     * should read all the way around the buffer before signalling overflow.
+     * There is a write-order dependency here, but to fail, the overflow
+     * field would have to be written while an entire buffer full of 
+     * writes are still pending. I'm assuming out-of-order writes are
+     * possible, but not that many.
+     */
+    if (queue->overflow == head + 1 && !queue->buffer[head]) {
+        queue->overflow = 0; /* non-overflow condition */
+        return pmBufferOverflow;
+    }
+
+    /* test to see if there is data in the queue -- test from back
+     * to front so if writer is simultaneously writing, we don't
+     * waste time discovering the write is not finished 
+     */
+    for (i = queue->msg_size - 1; i >= 0; i--) {
+        if (!queue->buffer[head + i]) {
+            return pmNoData;
+        }
+    }
+    memcpy(msg, (char *) &queue->buffer[head + 1], 
+           sizeof(int32_t) * (queue->msg_size - 1));
+    /* fix up zeros */
+    i = queue->buffer[head];
+    while (i < queue->msg_size) {
+        int32_t j;
+        i--; /* msg does not have extra word so shift down */
+        j = msg_as_int32[i];
+        msg_as_int32[i] = 0;
+        i = j;
+    }
+    /* signal that data has been removed by zeroing: */
+    bzero((char *) &queue->buffer[head], sizeof(int32_t) * queue->msg_size);
+
+    /* update head */
+    head += queue->msg_size;
+    if (head == queue->len) head = 0;
+    queue->head = head;
+    return pmGotData; /* success */
+}
+
+
+
+PMEXPORT PmError Pm_SetOverflow(PmQueue *q)
+{
+    PmQueueRep *queue = (PmQueueRep *) q;
+    long tail;
+    /* arg checking */
+    if (!queue)
+        return pmBadPtr;
+    /* no more enqueue until receiver acknowledges overflow */
+    if (queue->overflow) return pmBufferOverflow;
+    tail = queue->tail;
+    queue->overflow = tail + 1;
+    return pmBufferOverflow;
+}
+
+
+PMEXPORT PmError Pm_Enqueue(PmQueue *q, void *msg)
+{
+    PmQueueRep *queue = (PmQueueRep *) q;
+    long tail;
+    int i;
+    int32_t *src = (int32_t *) msg;
+    int32_t *ptr;
+    int32_t *dest;
+    int rslt;
+    if (!queue) 
+        return pmBadPtr;
+    /* no more enqueue until receiver acknowledges overflow */
+    if (queue->overflow) return pmBufferOverflow;
+    rslt = Pm_QueueFull(q);
+    /* already checked above: if (rslt == pmBadPtr) return rslt; */
+    tail = queue->tail;
+    if (rslt) {
+        queue->overflow = tail + 1;
+        return pmBufferOverflow;
+    }
+
+    /* queue is has room for message, and overflow flag is cleared */
+    ptr = &queue->buffer[tail];
+    dest = ptr + 1;
+    for (i = 1; i < queue->msg_size; i++) {
+        int32_t j = src[i - 1];
+        if (!j) {
+            *ptr = i;
+            ptr = dest;
+        } else {
+            *dest = j;
+        }
+        dest++;
+    }
+    *ptr = i;
+    tail += queue->msg_size;
+    if (tail == queue->len) tail = 0;
+    queue->tail = tail;
+    return pmNoError;
+}
+
+
+PMEXPORT int Pm_QueueEmpty(PmQueue *q)
+{
+    PmQueueRep *queue = (PmQueueRep *) q;
+    return (!queue) ||  /* null pointer -> return "empty" */
+           (queue->buffer[queue->head] == 0 && !queue->peek_flag);
+}
+
+
+PMEXPORT int Pm_QueueFull(PmQueue *q)
+{
+    long tail;
+    int i; 
+    PmQueueRep *queue = (PmQueueRep *) q;
+    /* arg checking */
+    if (!queue)
+        return pmBadPtr;
+    tail = queue->tail;
+    /* test to see if there is space in the queue */
+    for (i = 0; i < queue->msg_size; i++) {
+        if (queue->buffer[tail + i]) {
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+
+PMEXPORT void *Pm_QueuePeek(PmQueue *q)
+{
+    PmError rslt;
+    int32_t temp;
+    PmQueueRep *queue = (PmQueueRep *) q;
+    /* arg checking */
+    if (!queue)
+        return NULL;
+
+    if (queue->peek_flag) {
+        return queue->peek;
+    }
+    /* this is ugly: if peek_overflow is set, then Pm_Dequeue() 
+     * returns immediately with pmBufferOverflow, but here, we
+     * want Pm_Dequeue() to really check for data. If data is
+     * there, we can return it
+     */
+    temp = queue->peek_overflow;
+    queue->peek_overflow = FALSE;
+    rslt = Pm_Dequeue(q, queue->peek);
+    queue->peek_overflow = temp;
+
+    if (rslt == 1) {
+        queue->peek_flag = TRUE;
+        return queue->peek;
+    } else if (rslt == pmBufferOverflow) {
+        /* when overflow is indicated, the queue is empty and the 
+         * first message that was dropped by Enqueue (signalling
+         * pmBufferOverflow to its caller) would have been the next
+         * message in the queue. Pm_QueuePeek will return NULL, but
+         * remember that an overflow occurred. (see Pm_Dequeue)
+         */
+        queue->peek_overflow = TRUE;
+    }
+    return NULL;
+}
+
diff --git a/portmidi/pm_common/pmutil.h b/portmidi/pm_common/pmutil.h
--- a/portmidi/pm_common/pmutil.h
+++ b/portmidi/pm_common/pmutil.h
@@ -1,127 +1,184 @@
-/* pmutil.h -- some helpful utilities for building midi 
-               applications that use PortMidi 
- */
-
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-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.
-
-    This queue implementation uses the "light pipe" algorithm which
-    operates correctly even with multi-processors and out-of-order
-    memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
-    Communication," Dr. Dobbs Portal, http://www.ddj.com/, 
-    articleID=189401457, June 15, 2006. This algorithm requires
-    that messages be translated to a form where no words contain
-    zeros. Each word becomes its own "data valid" tag. Because of
-    this translation, we cannot return a pointer to data still in 
-    the queue when the "peek" method is called. Instead, a buffer 
-    is preallocated so that data can be copied there. Pm_QueuePeek() 
-    dequeues a message into this buffer and returns a pointer to 
-    it. A subsequent Pm_Dequeue() will copy from this buffer.
-
-    This implementation does not try to keep reader/writer data in
-    separate cache lines or prevent thrashing on cache lines. 
-    However, this algorithm differs by doing inserts/removals in
-    units of messages rather than units of machine words. Some
-    performance improvement might be obtained by not clearing data
-    immediately after a read, but instead by waiting for the end
-    of the cache line, especially if messages are smaller than
-    cache lines. See the Dokumentov article for explanation.
-
-    The algorithm is extended to handle "overflow" reporting. To report
-    an overflow, the sender writes the current tail position to a field.
-    The receiver must acknowlege receipt by zeroing the field. The sender
-    will not send more until the field is zeroed.
-    
-    Pm_QueueDestroy() destroys the queue and frees its storage.
- */
-
-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 if what would have been the next thing
-    in the queue was dropped due to overflow. (So when overflow occurs,
-    the receiver can receive a queue full of messages before getting the
-    overflow report. This protocol ensures that the reader will be 
-    notified when data is lost due to overflow.
- */
-PmError Pm_Dequeue(PmQueue *queue, void *msg);
-
-
-/*
-    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. Furthermore,
-    Pm_QueueEmpty() is optimistic: it may say false, when due to 
-    out-of-order writes, the full message has not arrived. Therefore,
-    Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns
-    false. On the other hand, Pm_QueueFull() is pessimistic: if it
-    returns false, then Pm_Enqueue() is guaranteed to succeed. 
-
-    Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL.
-    Pm_QueueEmpty() returns FALSE if queue is NULL.
- */
-int Pm_QueueFull(PmQueue *queue);
-int Pm_QueueEmpty(PmQueue *queue);
-
-
-/*
-    Pm_QueuePeek() returns a pointer to the item at the head of the queue,
-    or NULL if the queue is empty. The item is not removed from the queue.
-    Pm_QueuePeek() will not indicate when an overflow occurs. If you want
-    to get and check pmBufferOverflow messages, use the return value of
-    Pm_QueuePeek() *only* as an indication that you should call 
-    Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would
-    return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally
-    clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume
-    enqueuing messages. A subsequent call to Pm_QueuePeek()
-    will return a pointer to the first message *after* the overflow. 
-    Using this as an indication to call Pm_Dequeue(), the first call
-    to Pm_Dequeue() will return pmBufferOverflow. The second call will
-    return success, copying the same message pointed to by the previous
-    Pm_QueuePeek().
-
-    When to use Pm_QueuePeek(): (1) when you need to look at the message
-    data to decide who should be called to receive it. (2) when you need
-    to know a message is ready but cannot accept the message.
-
-    Note that Pm_QueuePeek() is not a fast check, so if possible, you 
-    might as well just call Pm_Dequeue() and accept the data if it is there.
- */
-void *Pm_QueuePeek(PmQueue *queue);
-
-/*
-    Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow
-    condition to the reader (dequeuer). E.g. when transfering data from 
-    the OS to an application, if the OS indicates a buffer overrun,
-    Pm_SetOverflow() can be used to insure that the reader receives a
-    pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue
-    is NULL, returns pmBufferOverflow if buffer is already in an overflow
-    state, returns pmNoError if successfully set overflow state.
- */
-PmError Pm_SetOverflow(PmQueue *queue);
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
+/** @file pmutil.h lock-free queue for building MIDI 
+                   applications with PortMidi. 
+
+    PortMidi is not reentrant, and locks can suffer from priority
+    inversion.  To support coordination between system callbacks, a
+    high-priority thread created with PortTime, and the main
+    application thread, PortMidi uses a lock-free, non-blocking
+    queue. The queue implementation is not particular to MIDI and is
+    available for other uses.
+ */
+
+#ifndef PORTMIDI_PMUTIL_H
+#define PORTMIDI_PMUTIL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/** @defgroup grp_pmutil Lock-free Queue
+    @{
+*/
+
+/** The queue representation is opaque. Declare a queue as PmQueue * */
+typedef void PmQueue;
+
+/** create a single-reader, single-writer queue. 
+
+    @param num_msgs the number of messages the queue can hold
+
+    @param bytes_per_msg the fixed message size
+
+    @return the allocated and initialized queue, or NULL if memory
+    cannot be allocated. Allocation uses pm_alloc().
+
+    The queue only accepts fixed sized messages. 
+
+    This queue implementation uses the "light pipe" algorithm which
+    operates correctly even with multi-processors and out-of-order
+    memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
+    Communication," Dr. Dobbs Portal, http://www.ddj.com/,
+    articleID=189401457, June 15, 2006. This algorithm requires that
+    messages be translated to a form where no words contain
+    zeros. Each word becomes its own "data valid" tag. Because of this
+    translation, we cannot return a pointer to data still in the queue
+    when the "peek" method is called. Instead, a buffer is
+    preallocated so that data can be copied there. Pm_QueuePeek()
+    dequeues a message into this buffer and returns a pointer to it. A
+    subsequent Pm_Dequeue() will copy from this buffer.
+
+    This implementation does not try to keep reader/writer data in
+    separate cache lines or prevent thrashing on cache lines. 
+    However, this algorithm differs by doing inserts/removals in
+    units of messages rather than units of machine words. Some
+    performance improvement might be obtained by not clearing data
+    immediately after a read, but instead by waiting for the end
+    of the cache line, especially if messages are smaller than
+    cache lines. See the Dokumentov article for explanation.
+
+    The algorithm is extended to handle "overflow" reporting. To
+    report an overflow, the sender writes the current tail position to
+    a field.  The receiver must acknowlege receipt by zeroing the
+    field. The sender will not send more until the field is zeroed.
+ */
+PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg);
+    
+/** destroy a queue and free its storage. 
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @return pmNoError or an error code.
+
+    Uses pm_free(). 
+
+ */
+PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue);
+
+/** remove one message from the queue, copying it into \p msg.
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @param msg address to which the message, if any, is copied.
+
+    @return 1 if successful, and 0 if the queue is empty.  Returns
+    #pmBufferOverflow if what would have been the next thing in the
+    queue was dropped due to overflow. (So when overflow occurs, the
+    receiver can receive a queue full of messages before getting the
+    overflow report. This protocol ensures that the reader will be
+    notified when data is lost due to overflow.
+ */
+PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg);
+
+/** insert one message into the queue, copying it from \p msg.
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @param msg address of the message to be enqueued.
+
+    @return #pmNoError if successful and #pmBufferOverflow if the
+    queue was already full. If #pmBufferOverflow is returned, the
+    overflow flag is set.
+ */
+PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg);
+
+/** test if the queue is full.
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @return non-zero iff the queue is empty, and #pmBadPtr if \p queue
+    is NULL.
+
+    The full condition may change immediately because a parallel
+    dequeue operation could be in progress.  The result is
+    pessimistic: if it returns false (zero) to the single writer, then
+    #Pm_Enqueue() is guaranteed to succeed.
+ */
+PMEXPORT int Pm_QueueFull(PmQueue *queue);
+
+/** test if the queue is empty.
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @return zero iff the queue is either empty or NULL.
+
+    The empty condition may change immediately because a parallel
+    enqueue operation could be in progress. Furthermore, the 
+    result is optimistic: it may say false, when due to 
+    out-of-order writes, the full message has not arrived. Therefore,
+    #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns
+    false.
+*/
+PMEXPORT int Pm_QueueEmpty(PmQueue *queue);
+
+/** get a pointer to the item at the head of the queue.
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @result a pointer to the head message or NULL if the queue is empty.
+
+    The message is not removed from the queue.  #Pm_QueuePeek() will
+    not indicate when an overflow occurs. If you want to get and check
+    #pmBufferOverflow messages, use the return value of
+    #Pm_QueuePeek() *only* as an indication that you should call
+    #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue()
+    would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL,
+    but internally clear the #pmBufferOverflow flag, enabling
+    #Pm_Enqueue() to resume enqueuing messages. A subsequent call to
+    #Pm_QueuePeek() will return a pointer to the first message *after*
+    the overflow.  Using this as an indication to call #Pm_Dequeue(),
+    the first call to #Pm_Dequeue() will return #pmBufferOverflow. The
+    second call will return success, copying the same message pointed
+    to by the previous #Pm_QueuePeek().
+
+    When to use #Pm_QueuePeek(): (1) when you need to look at the message
+    data to decide who should be called to receive it. (2) when you need
+    to know a message is ready but cannot accept the message.
+
+    Note that #Pm_QueuePeek() is not a fast check, so if possible, you 
+    might as well just call #Pm_Dequeue() and accept the data if it is there.
+ */
+PMEXPORT void *Pm_QueuePeek(PmQueue *queue);
+
+/** allows the writer (enqueuer) to signal an overflow
+    condition to the reader (dequeuer). 
+
+    @param queue a queue created by #Pm_QueueCreate().
+
+    @return #pmNoError if overflow is set, or #pmBadPtr if queue is
+    NULL, or #pmBufferOverflow if buffer is already in an overflow
+    state.
+
+    E.g., when transfering data from the OS to an application, if the
+    OS indicates a buffer overrun, #Pm_SetOverflow() can be used to
+    insure that the reader receives a #pmBufferOverflow result from
+    #Pm_Dequeue().
+ */
+PMEXPORT PmError Pm_SetOverflow(PmQueue *queue);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PORTMIDI_PMUTIL_H */
diff --git a/portmidi/pm_common/portmidi.c b/portmidi/pm_common/portmidi.c
--- a/portmidi/pm_common/portmidi.c
+++ b/portmidi/pm_common/portmidi.c
@@ -1,1084 +1,1477 @@
-#include "stdlib.h"
-#include "string.h"
-#include "portmidi.h"
-#include "porttime.h"
-#include "pmutil.h"
-#include "pminternal.h"
-#include <assert.h>
-
-#define MIDI_CLOCK      0xf8
-#define MIDI_ACTIVE     0xfe
-#define MIDI_STATUS_MASK 0x80
-#define MIDI_SYSEX      0xf0
-#define MIDI_EOX        0xf7
-#define MIDI_START      0xFA
-#define MIDI_STOP       0xFC
-#define MIDI_CONTINUE   0xFB
-#define MIDI_F9         0xF9
-#define MIDI_FD         0xFD
-#define MIDI_RESET      0xFF
-#define MIDI_NOTE_ON    0x90
-#define MIDI_NOTE_OFF   0x80
-#define MIDI_CHANNEL_AT 0xD0
-#define MIDI_POLY_AT    0xA0
-#define MIDI_PROGRAM    0xC0
-#define MIDI_CONTROL    0xB0
-#define MIDI_PITCHBEND  0xE0
-#define MIDI_MTC        0xF1
-#define MIDI_SONGPOS    0xF2
-#define MIDI_SONGSEL    0xF3
-#define MIDI_TUNE       0xF6
-
-#define is_empty(midi) ((midi)->tail == (midi)->head)
-
-static int pm_initialized = FALSE;
-int pm_hosterror;
-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 = (descriptor_type) 
-            pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
-        if (!new_descriptors) return pmInsufficientMemory;
-        if (descriptors) {
-            memcpy(new_descriptors, descriptors, 
-                   sizeof(descriptor_node) * pm_descriptor_max);
-            free(descriptors);
-        }
-        pm_descriptor_max += 32;
-        descriptors = new_descriptors;
-    }
-    descriptors[pm_descriptor_index].pub.interf = interf;
-    descriptors[pm_descriptor_index].pub.name = name;
-    descriptors[pm_descriptor_index].pub.input = input;
-    descriptors[pm_descriptor_index].pub.output = !input;
-
-    /* default state: nothing to close (for automatic device closing) */
-    descriptors[pm_descriptor_index].pub.opened = FALSE;
-
-    /* ID number passed to win32 multimedia API open */
-    descriptors[pm_descriptor_index].descriptor = descriptor;
-    
-    /* points to PmInternal, allows automatic device closing */
-    descriptors[pm_descriptor_index].internalDescriptor = NULL;
-
-    descriptors[pm_descriptor_index].dictionary = dictionary;
-    
-    pm_descriptor_index++;
-    
-    return pmNoError;
-}
-
-
-/*
-====================================================================
-portmidi implementation
-====================================================================
-*/
-
-int Pm_CountDevices( void ) {
-    Pm_Initialize();
-    /* no error checking -- Pm_Initialize() does not fail */
-    return pm_descriptor_index;
-}
-
-
-const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ) {
-    Pm_Initialize(); /* no error check needed */
-    if (id >= 0 && id < pm_descriptor_index) {
-        return &descriptors[id].pub;
-    }
-    return NULL;
-}
-
-/* pm_success_fn -- "noop" function pointer */
-PmError pm_success_fn(PmInternal *midi) {
-    return pmNoError;
-}
-
-/* none_write -- returns an error if called */
-PmError none_write_short(PmInternal *midi, PmEvent *buffer) {
-    return pmBadPtr;
-}
-
-/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */
-PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp) {
-    return pmBadPtr;
-}
-
-PmError none_write_byte(PmInternal *midi, unsigned char byte, 
-                        PmTimestamp timestamp) {
-    return pmBadPtr;
-}
-
-/* pm_fail_fn -- generic function, returns error if called */
-PmError pm_fail_fn(PmInternal *midi) {
-    return pmBadPtr;
-}
-
-static PmError none_open(PmInternal *midi, void *driverInfo) {
-    return pmBadPtr;
-}
-static void none_get_host_error(PmInternal * midi, char * msg, unsigned int len) {
-    strcpy(msg, "");
-}
-static unsigned int none_has_host_error(PmInternal * midi) {
-    return FALSE;
-}
-PmTimestamp none_synchronize(PmInternal *midi) {
-    return 0;
-}
-
-#define none_abort pm_fail_fn
-#define none_close pm_fail_fn
-
-pm_fns_node pm_none_dictionary = {
-    none_write_short,
-    none_sysex,
-    none_sysex,
-    none_write_byte,
-    none_write_short,
-    none_write_flush,
-    none_synchronize,
-    none_open,
-    none_abort, 
-    none_close,
-    none_poll,
-    none_has_host_error,
-    none_get_host_error 
-};
-
-
-const char *Pm_GetErrorText( PmError errnum ) {
-    const char *msg;
-
-    switch(errnum)
-    {
-    case pmNoError:                  
-        msg = ""; 
-        break;
-    case pmHostError:                
-        msg = "PortMidi: `Host error'"; 
-        break;
-    case pmInvalidDeviceId:          
-        msg = "PortMidi: `Invalid device ID'"; 
-        break;
-    case pmInsufficientMemory:       
-        msg = "PortMidi: `Insufficient memory'"; 
-        break;
-    case pmBufferTooSmall:           
-        msg = "PortMidi: `Buffer too small'"; 
-        break;
-    case pmBadPtr:                   
-        msg = "PortMidi: `Bad pointer'"; 
-        break;
-    case pmInternalError:            
-        msg = "PortMidi: `Internal PortMidi Error'"; 
-        break;
-    case pmBufferOverflow:
-        msg = "PortMidi: `Buffer overflow'";
-        break;
-    case pmBadData:
-        msg = "PortMidi: `Invalid MIDI message Data'";
-        break;
-    case pmBufferMaxSize:
-        msg = "PortMidi: `Buffer cannot be made larger'";
-        break;
-    default:                         
-        msg = "PortMidi: `Illegal error number'"; 
-        break;
-    }
-    return msg;
-}
-
-
-/* This can be called whenever you get a pmHostError return value.
- * The error will always be in the global pm_hosterror_text.
- */
-void Pm_GetHostErrorText(char * msg, unsigned int len) {
-    assert(msg);
-    assert(len > 0);
-    if (pm_hosterror) {
-        strncpy(msg, (char *) pm_hosterror_text, len);
-        pm_hosterror = FALSE;
-        pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
-                                     might help with debugging */
-        msg[len - 1] = 0; /* make sure string is terminated */
-    } else {
-        msg[0] = 0; /* no string to return */
-    }
-}
-
-
-int Pm_HasHostError(PortMidiStream * stream) {
-    if (pm_hosterror) return TRUE;
-    if (stream) {
-        PmInternal * midi = (PmInternal *) stream;
-        pm_hosterror = (*midi->dictionary->has_host_error)(midi);
-        if (pm_hosterror) {
-            midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                         PM_HOST_ERROR_MSG_LEN);
-            /* now error message is global */
-            return TRUE;
-        }
-    }
-    return FALSE;
-}
-
-
-PmError Pm_Initialize( void ) {
-    if (!pm_initialized) {
-        pm_hosterror = FALSE;
-        pm_hosterror_text[0] = 0; /* the null string */
-        pm_init();
-        pm_initialized = TRUE;
-    }
-    return pmNoError;
-}
-
-
-PmError Pm_Terminate( void ) {
-    if (pm_initialized) {
-        pm_term();
-        // if there are no devices, descriptors might still be NULL
-        if (descriptors != NULL) {
-            free(descriptors);
-            descriptors = NULL;
-        }
-        pm_descriptor_index = 0;
-        pm_descriptor_max = 0;
-        pm_initialized = FALSE;
-    }
-    return pmNoError;
-}
-
-
-/* Pm_Read -- read up to length longs from source into buffer */
-/*
- * returns number of longs actually read, or error code
- */
-int Pm_Read(PortMidiStream *stream, PmEvent *buffer, long length) {
-    PmInternal *midi = (PmInternal *) stream;
-    int n = 0;
-    PmError err = pmNoError;
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if(midi == NULL)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.input)
-        err = pmBadPtr;    
-    /* First poll for data in the buffer...
-     * This either simply checks for data, or attempts first to fill the buffer
-     * with data from the MIDI hardware; this depends on the implementation.
-     * We could call Pm_Poll here, but that would redo a lot of redundant
-     * parameter checking, so I copied some code from Pm_Poll to here: */
-    else err = (*(midi->dictionary->poll))(midi);
-
-    if (err != pmNoError) {
-        if (err == pmHostError) {
-            midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                         PM_HOST_ERROR_MSG_LEN);
-          pm_hosterror = TRUE;
-        }
-        return pm_errmsg(err);
-    }
-
-    while (n < length) {
-        PmError err = Pm_Dequeue(midi->queue, buffer++);
-        if (err == pmBufferOverflow) {
-            /* ignore the data we have retreived so far */
-            return pm_errmsg(pmBufferOverflow);
-        } else if (err == 0) { /* empty queue */
-            break;
-        }
-        n++;
-    }
-    return n;
-}
-
-PmError Pm_Poll( PortMidiStream *stream )
-{
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err;
-
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if(midi == NULL)
-        err = pmBadPtr;
-    else if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else if (!descriptors[midi->device_id].pub.input)
-        err = pmBadPtr;
-    else
-        err = (*(midi->dictionary->poll))(midi);
-
-    if (err != pmNoError) {
-        if (err == pmHostError) {
-            midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                         PM_HOST_ERROR_MSG_LEN);
-           pm_hosterror = TRUE;
-        }
-        return pm_errmsg(err);
-    }
-
-    return !Pm_QueueEmpty(midi->queue);
-}
-
-
-/* this is called from Pm_Write and Pm_WriteSysEx to issue a
- * call to the system-dependent end_sysex function and handle 
- * the error return
- */
-static PmError pm_end_sysex(PmInternal *midi)
-{
-    PmError err = (*midi->dictionary->end_sysex)(midi, 0);
-    midi->sysex_in_progress = FALSE;
-    if (err == pmHostError) {
-        midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                     PM_HOST_ERROR_MSG_LEN);
-        pm_hosterror = TRUE;
-    }
-    return err;
-}
-
-
-/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
-   Pm_WriteSysEx all operate a state machine that "outputs" calls to
-   write_short, begin_sysex, write_byte, end_sysex, and write_realtime */
-
-PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length)
-{
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-    int i;
-    int bits;
-    
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if(midi == NULL)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else if(!descriptors[midi->device_id].pub.output)
-        err = pmBadPtr;
-    else
-        err = pmNoError;
-    
-    if (err != pmNoError) goto pm_write_error;
-    
-    if (midi->latency == 0) {
-        midi->now = 0;
-    } else {
-        midi->now = (*(midi->time_proc))(midi->time_info);
-        if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
-            /* time to resync */
-            midi->now = (*midi->dictionary->synchronize)(midi);
-            midi->first_message = FALSE;
-        }
-    }
-    /* error recovery: when a sysex is detected, we call
-     *   dictionary->begin_sysex() followed by calls to
-     *   dictionary->write_byte() and dictionary->write_realtime()
-     *   until an end-of-sysex is detected, when we call
-     *   dictionary->end_sysex(). After an error occurs, 
-     *   Pm_Write() continues to call functions. For example,
-     *   it will continue to call write_byte() even after
-     *   an error sending a sysex message, and end_sysex() will be
-     *   called when an EOX or non-real-time status is found.
-     * When errors are detected, Pm_Write() returns immediately, 
-     *   so it is possible that this will drop data and leave
-     *   sysex messages in a partially transmitted state.
-     */
-    for (i = 0; i < length; i++) {
-        unsigned long msg = buffer[i].message;
-        bits = 0;
-        /* is this a sysex message? */
-        if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
-            if (midi->sysex_in_progress) {
-                /* error: previous sysex was not terminated by EOX */
-                midi->sysex_in_progress = FALSE;
-                err = pmBadData;
-                goto pm_write_error;
-            }
-            midi->sysex_in_progress = TRUE;
-            if ((err = (*midi->dictionary->begin_sysex)(midi, 
-                               buffer[i].timestamp)) != pmNoError)
-                goto pm_write_error;
-            if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
-                               buffer[i].timestamp)) != pmNoError) 
-                goto pm_write_error;
-            bits = 8;
-            /* fall through to continue sysex processing */
-        } else if ((msg & MIDI_STATUS_MASK) && 
-                   (Pm_MessageStatus(msg) != MIDI_EOX)) {
-            /* a non-sysex message */
-            if (midi->sysex_in_progress) {
-                /* this should be a realtime message */
-                if (is_real_time(msg)) {
-                    if ((err = (*midi->dictionary->write_realtime)(midi, 
-                                       &(buffer[i]))) != pmNoError)
-                        goto pm_write_error;
-                } else {
-                    midi->sysex_in_progress = FALSE;
-                    err = pmBadData;
-                    /* ignore any error from this, because we already have one */
-                    /* pass 0 as timestamp -- it's ignored */
-                    (*midi->dictionary->end_sysex)(midi, 0);
-                    goto pm_write_error;
-                }
-            } else { /* regular short midi message */
-                if ((err = (*midi->dictionary->write_short)(midi, 
-                                   &(buffer[i]))) != pmNoError)
-                    goto pm_write_error;
-                continue;
-            }
-        }
-        if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
-            /* see if we can accelerate data transfer */
-            if (bits == 0 && midi->fill_base && /* 4 bytes to copy */
-                (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&
-                (msg & 0x80808080) == 0) { /* all data */
-                    /* copy 4 bytes from msg to fill_base + fill_offset */
-                    unsigned char *ptr = midi->fill_base + 
-                                         *(midi->fill_offset_ptr);
-                    ptr[0] = msg; ptr[1] = msg >> 8; 
-                    ptr[2] = msg >> 18; ptr[3] = msg >> 24;
-                    (*midi->fill_offset_ptr) += 4;
-                     continue;
-            }
-            /* no acceleration, so do byte-by-byte copying */
-            while (bits < 32) {
-                unsigned char midi_byte = (unsigned char) (msg >> bits);
-                if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, 
-                                   buffer[i].timestamp)) != pmNoError)
-                    goto pm_write_error;
-                if (midi_byte == MIDI_EOX) {
-                    err = pm_end_sysex(midi);
-                    if (err != pmNoError) goto error_exit;
-                    break; /* from while loop */
-                }
-                bits += 8;
-            }
-        } else {
-            /* not in sysex mode, but message did not start with status */
-            err = pmBadData;
-            goto pm_write_error;
-        }
-    }
-    /* after all messages are processed, send the data */
-    if (!midi->sysex_in_progress)
-        err = (*midi->dictionary->write_flush)(midi, 0);
-pm_write_error:
-    if (err == pmHostError) {
-        midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                     PM_HOST_ERROR_MSG_LEN);
-        pm_hosterror = TRUE;
-    }
-error_exit:
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_WriteShort(PortMidiStream *stream, long when, long msg)
-{
-    PmEvent event;
-    
-    event.timestamp = when;
-    event.message = msg;
-    return Pm_Write(stream, &event, 1);
-}
-
-
-PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, 
-                      unsigned char *msg)
-{
-    /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */
-    /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */
-    #define BUFLEN (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage))
-    PmEvent buffer[BUFLEN];
-    int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */
-    PmInternal *midi = (PmInternal *) stream;
-    /* the next byte in the buffer is represented by an index, bufx, and
-       a shift in bits */
-    int shift = 0;
-    int bufx = 0;
-    buffer[0].message = 0;
-    buffer[0].timestamp = when;
-
-    while (1) {
-        /* insert next byte into buffer */
-        buffer[bufx].message |= ((*msg) << shift);
-        shift += 8;
-        if (*msg++ == MIDI_EOX) break;
-        if (shift == 32) {
-            shift = 0;
-            bufx++;
-            if (bufx == buffer_size) {
-                PmError err = Pm_Write(stream, buffer, buffer_size);
-                /* note: Pm_Write has already called errmsg() */
-                if (err) return err;
-                /* prepare to fill another buffer */
-                bufx = 0;
-                buffer_size = BUFLEN;
-                /* optimization: maybe we can just copy bytes */
-                if (midi->fill_base) {
-                    PmError err;
-                    while (*(midi->fill_offset_ptr) < midi->fill_length) {
-                        midi->fill_base[(*midi->fill_offset_ptr)++] = *msg;
-                        if (*msg++ == MIDI_EOX) {
-                            err = pm_end_sysex(midi);
-                            if (err != pmNoError) return pm_errmsg(err);
-                            goto end_of_sysex;
-                        }
-                    }
-                    /* I thought that I could do a pm_Write here and
-                     * change this if to a loop, avoiding calls in Pm_Write
-                     * to the slower write_byte, but since 
-                     * sysex_in_progress is true, this will not flush
-                     * the buffer, and we'll infinite loop: */
-                    /* err = Pm_Write(stream, buffer, 0);
-                       if (err) return err; */
-                    /* instead, the way this works is that Pm_Write calls
-                     * write_byte on 4 bytes. The first, since the buffer
-                     * is full, will flush the buffer and allocate a new
-                     * one. This primes the buffer so
-                     * that we can return to the loop above and fill it
-                     * efficiently without a lot of function calls.
-                     */
-                    buffer_size = 1; /* get another message started */
-                }
-            }
-            buffer[bufx].message = 0;
-            buffer[bufx].timestamp = when;
-        } 
-        /* keep inserting bytes until you find MIDI_EOX */
-    }
-end_of_sysex:
-    /* we're finished sending full buffers, but there may
-     * be a partial one left.
-     */
-    if (shift != 0) bufx++; /* add partial message to buffer len */
-    if (bufx) { /* bufx is number of PmEvents to send from buffer */
-        PmError err = Pm_Write(stream, buffer, bufx);
-        if (err) return err;
-    }
-    return pmNoError;
-}
-
-
-
-PmError Pm_OpenInput(PortMidiStream** stream,
-                     PmDeviceID inputDevice,
-                     void *inputDriverInfo,
-                     long bufferSize,
-                     PmTimeProcPtr time_proc,
-                     void *time_info)
-{
-    PmInternal *midi;
-    PmError err = pmNoError;
-    pm_hosterror = FALSE;
-    *stream = NULL;
-    
-    /* arg checking */
-    if (inputDevice < 0 || inputDevice >= pm_descriptor_index) 
-        err = pmInvalidDeviceId;
-    else if (!descriptors[inputDevice].pub.input) 
-        err =  pmBadPtr;
-    else if(descriptors[inputDevice].pub.opened)
-        err =  pmBadPtr;
-    
-    if (err != pmNoError) 
-        goto error_return;
-
-    /* create portMidi internal data */
-    midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); 
-    *stream = midi;
-    if (!midi) {
-        err = pmInsufficientMemory;
-        goto error_return;
-    }
-    midi->device_id = inputDevice;
-    midi->write_flag = FALSE;
-    midi->time_proc = time_proc;
-    midi->time_info = time_info;
-    /* windows adds timestamps in the driver and these are more accurate than
-       using a time_proc, so do not automatically provide a time proc. Non-win
-       implementations may want to provide a default time_proc in their
-       system-specific midi_out_open() method.
-     */
-    if (bufferSize <= 0) bufferSize = 256; /* default buffer size */
-    midi->queue = Pm_QueueCreate(bufferSize, sizeof(PmEvent));
-    if (!midi->queue) {
-        /* free portMidi data */
-        *stream = NULL;
-        pm_free(midi); 
-        err = pmInsufficientMemory;
-        goto error_return;
-    }
-    midi->buffer_len = bufferSize; /* portMidi input storage */
-    midi->latency = 0; /* not used */
-    midi->sysex_in_progress = FALSE;
-    midi->sysex_message = 0; 
-    midi->sysex_message_count = 0; 
-    midi->filters = PM_FILT_ACTIVE;
-    midi->channel_mask = 0xFFFF;
-    midi->sync_time = 0;
-    midi->first_message = TRUE;
-    midi->dictionary = descriptors[inputDevice].dictionary;
-    midi->fill_base = NULL;
-    midi->fill_offset_ptr = NULL;
-    midi->fill_length = 0;
-    descriptors[inputDevice].internalDescriptor = midi;
-    /* open system dependent input device */
-    err = (*midi->dictionary->open)(midi, inputDriverInfo);
-    if (err) {
-        *stream = NULL;
-        descriptors[inputDevice].internalDescriptor = NULL;
-        /* free portMidi data */
-        Pm_QueueDestroy(midi->queue);
-        pm_free(midi);
-    } else {
-        /* portMidi input open successful */
-        descriptors[inputDevice].pub.opened = TRUE;
-    }
-error_return:
-    /* note: if there is a pmHostError, it is the responsibility
-     * of the system-dependent code (*midi->dictionary->open)()
-     * to set pm_hosterror and pm_hosterror_text
-     */
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_OpenOutput(PortMidiStream** stream,
-                      PmDeviceID outputDevice,
-                      void *outputDriverInfo,
-                      long bufferSize,
-                      PmTimeProcPtr time_proc,
-                      void *time_info,
-                      long latency ) 
-{
-    PmInternal *midi;
-    PmError err = pmNoError;
-    pm_hosterror = FALSE;
-    *stream =  NULL;
-    
-    /* arg checking */
-    if (outputDevice < 0 || outputDevice >= pm_descriptor_index)
-        err = pmInvalidDeviceId;
-    else if (!descriptors[outputDevice].pub.output) 
-        err = pmInvalidDeviceId;
-    else if (descriptors[outputDevice].pub.opened)
-        err = pmInvalidDeviceId;
-    if (err != pmNoError) 
-        goto error_return;
-
-    /* create portMidi internal data */
-    midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); 
-    *stream = midi;                 
-    if (!midi) {
-        err = pmInsufficientMemory;
-        goto error_return;
-    }
-    midi->device_id = outputDevice;
-    midi->write_flag = TRUE;
-    midi->time_proc = time_proc;
-    /* if latency > 0, we need a time reference. If none is provided,
-       use PortTime library */
-    if (time_proc == NULL && latency != 0) {
-        if (!Pt_Started()) 
-            Pt_Start(1, 0, 0);
-        /* time_get does not take a parameter, so coerce */
-        midi->time_proc = (PmTimeProcPtr) Pt_Time;
-    }
-    midi->time_info = time_info;
-    midi->buffer_len = bufferSize;
-    midi->queue = NULL; /* unused by output */
-    /* if latency zero, output immediate (timestamps ignored) */
-    /* if latency < 0, use 0 but don't return an error */
-    if (latency < 0) latency = 0;
-    midi->latency = latency;
-    midi->sysex_in_progress = FALSE;
-    midi->sysex_message = 0; /* unused by output */
-    midi->sysex_message_count = 0; /* unused by output */
-    midi->filters = 0; /* not used for output */
-    midi->channel_mask = 0xFFFF;
-    midi->sync_time = 0;
-    midi->first_message = TRUE;
-    midi->dictionary = descriptors[outputDevice].dictionary;
-    midi->fill_base = NULL;
-    midi->fill_offset_ptr = NULL;
-    midi->fill_length = 0;
-    descriptors[outputDevice].internalDescriptor = midi;
-    /* open system dependent output device */
-    err = (*midi->dictionary->open)(midi, outputDriverInfo);
-    if (err) {
-        *stream = NULL;
-        descriptors[outputDevice].internalDescriptor = NULL;
-        /* free portMidi data */
-        pm_free(midi); 
-    } else {
-        /* portMidi input open successful */
-        descriptors[outputDevice].pub.opened = TRUE;
-    }
-error_return:
-    /* note: system-dependent code must set pm_hosterror and
-     * pm_hosterror_text if a pmHostError occurs
-     */
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_SetChannelMask(PortMidiStream *stream, int mask)
-{
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-
-    if (midi == NULL)
-        err = pmBadPtr;
-    else
-        midi->channel_mask = mask;
-
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_SetFilter(PortMidiStream *stream, long filters) {
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-
-    /* arg checking */
-    if (midi == NULL)
-        err = pmBadPtr;
-    else if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else
-        midi->filters = filters;
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_Close( PortMidiStream *stream ) {
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err = pmNoError;
-
-    pm_hosterror = FALSE;
-    /* arg checking */
-    if (midi == NULL) /* midi must point to something */
-        err = pmBadPtr;
-    /* if it is an open device, the device_id will be valid */
-    else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_index)
-        err = pmBadPtr;
-    /* and the device should be in the opened state */
-    else if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    
-    if (err != pmNoError) 
-        goto error_return;
-
-    /* close the device */
-    err = (*midi->dictionary->close)(midi);
-    /* even if an error occurred, continue with cleanup */
-    descriptors[midi->device_id].internalDescriptor = NULL;
-    descriptors[midi->device_id].pub.opened = FALSE;
-    if (midi->queue) Pm_QueueDestroy(midi->queue);
-    pm_free(midi); 
-error_return:
-    /* system dependent code must set pm_hosterror and
-     * pm_hosterror_text if a pmHostError occurs.
-     */
-    return pm_errmsg(err);
-}
-
-
-PmError Pm_Abort( PortMidiStream* stream ) {
-    PmInternal *midi = (PmInternal *) stream;
-    PmError err;
-    /* arg checking */
-    if (midi == NULL)
-        err = pmBadPtr;
-    if (!descriptors[midi->device_id].pub.output)
-        err = pmBadPtr;
-    if (!descriptors[midi->device_id].pub.opened)
-        err = pmBadPtr;
-    else
-        err = (*midi->dictionary->abort)(midi);
-
-    if (err == pmHostError) {
-        midi->dictionary->host_error(midi, pm_hosterror_text, 
-                                     PM_HOST_ERROR_MSG_LEN);
-        pm_hosterror = TRUE;
-    }
-    return pm_errmsg(err);
-}
-
-
-
-/* pm_channel_filtered returns non-zero if the channel mask is blocking the current channel */
-#define pm_channel_filtered(status, mask) \
-    ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask))))
-
-
-/* The following two functions will checks to see if a MIDI message matches
-   the filtering criteria.  Since the sysex routines only want to filter realtime messages,
-   we need to have separate routines.
- */
-
-
-/* pm_realtime_filtered returns non-zero if the filter will kill the current message.
-   Note that only realtime messages are checked here.
- */
-#define pm_realtime_filtered(status, filters) \
-    ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters)))
-
-/*
-    return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))
-            ||  ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))
-            ||  ((status == MIDI_START) && (filters & PM_FILT_PLAY))
-            ||  ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))
-            ||  ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))
-            ||  ((status == MIDI_F9) && (filters & PM_FILT_F9))
-            ||  ((status == MIDI_FD) && (filters & PM_FILT_FD))
-            ||  ((status == MIDI_RESET) && (filters & PM_FILT_RESET))
-            ||  ((status == MIDI_MTC) && (filters & PM_FILT_MTC))
-            ||  ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))
-            ||  ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))
-            ||  ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));
-}*/
-
-
-/* pm_status_filtered returns non-zero if a filter will kill the current message, based on status.
-   Note that sysex and real time are not checked.  It is up to the subsystem (winmm, core midi, alsa)
-   to filter sysex, as it is handled more easily and efficiently at that level.
-   Realtime message are filtered in pm_realtime_filtered.
- */
-#define pm_status_filtered(status, filters) ((1 << (16 + ((status) >> 4))) & (filters))
-
-
-/*
-    return  ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))
-            ||  ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))
-            ||  ((status == MIDI_CHANNEL_AT) && (filters & PM_FILT_CHANNEL_AFTERTOUCH))
-            ||  ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))
-            ||  ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))
-            ||  ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))
-            ||  ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
-
-}
-*/
-
-static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
-    PmEvent event;
-    
-    /* there may be nothing in the buffer */
-    if (midi->sysex_message_count == 0) return; /* nothing to flush */
-    
-    event.message = midi->sysex_message;
-    event.timestamp = timestamp;
-    /* copied from pm_read_short, avoids filtering */
-    if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
-        midi->sysex_in_progress = FALSE;
-    }
-    midi->sysex_message_count = 0;
-    midi->sysex_message = 0;
-}
-
-
-/* pm_read_short and pm_read_bytes
-   are the interface between system-dependent MIDI input handlers
-   and the system-independent PortMIDI code.
-   The input handler MUST obey these rules:
-   1) all short input messages must be sent to pm_read_short, which
-      enqueues them to a FIFO for the application.
-   2) eash buffer of sysex bytes should be reported by calling pm_read_bytes
-      (which sets midi->sysex_in_progress). After the eox byte, 
-      pm_read_bytes will clear sysex_in_progress
- */
-
-/* pm_read_short is the place where all input messages arrive from 
-   system-dependent code such as pmwinmm.c. Here, the messages
-   are entered into the PortMidi input buffer. 
- */
-void pm_read_short(PmInternal *midi, PmEvent *event)
-{ 
-    int status;
-    /* arg checking */
-    assert(midi != NULL);
-    /* midi filtering is applied here */
-    status = Pm_MessageStatus(event->message);
-    if (!pm_status_filtered(status, midi->filters)
-        && (!is_real_time(status) || 
-            !pm_realtime_filtered(status, midi->filters))
-        && !pm_channel_filtered(status, midi->channel_mask)) {
-        /* if sysex is in progress and we get a status byte, it had
-           better be a realtime message or the starting SYSEX byte;
-           otherwise, we exit the sysex_in_progress state
-         */
-        if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) {
-            /* two choices: real-time or not. If it's real-time, then
-             * this should be delivered as a sysex byte because it is
-             * embedded in a sysex message
-             */
-            if (is_real_time(status)) {
-                midi->sysex_message |= 
-                        (status << (8 * midi->sysex_message_count++));
-                if (midi->sysex_message_count == 4) {
-                    pm_flush_sysex(midi, event->timestamp);
-                }
-            } else { /* otherwise, it's not real-time. This interrupts
-                      * a sysex message in progress */
-                midi->sysex_in_progress = FALSE;
-            }
-        } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {
-            midi->sysex_in_progress = FALSE;
-        }
-    }
-}
-
-/* pm_read_bytes -- read one (partial) sysex msg from MIDI data */
-/*
- * returns how many bytes processed
- */
-unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, 
-                    int len, PmTimestamp timestamp)
-{
-    unsigned int i = 0; /* index into data */
-    PmEvent event;
-    event.timestamp = timestamp;
-    assert(midi);
-    /* note that since buffers may not have multiples of 4 bytes,
-     * pm_read_bytes may be called in the middle of an outgoing
-     * 4-byte PortMidi message. sysex_in_progress indicates that
-     * a sysex has been sent but no eox.
-     */
-    if (len == 0) return 0; /* sanity check */
-    if (!midi->sysex_in_progress) {
-        while (i < len) { /* process all data */
-            unsigned char byte = data[i++];
-            if (byte == MIDI_SYSEX &&
-                !pm_realtime_filtered(byte, midi->filters)) {
-                midi->sysex_in_progress = TRUE;
-                i--; /* back up so code below will get SYSEX byte */
-                break; /* continue looping below to process msg */
-            } else if (byte == MIDI_EOX) {
-                midi->sysex_in_progress = FALSE;
-                return i; /* done with one message */
-            } else if (byte & MIDI_STATUS_MASK) {
-                /* We're getting MIDI but no sysex in progress.
-                 * Either the SYSEX status byte was dropped or
-                 * the message was filtered. Drop the data, but
-                 * send any embedded realtime bytes.
-                 */
-                /* assume that this is a real-time message:
-                 * it is an error to pass non-real-time messages
-                 * to pm_read_bytes
-                 */
-                event.message = byte;
-                pm_read_short(midi, &event);
-            }
-        } /* all bytes in the buffer are processed */
-    }
-    /* Now, i<len implies sysex_in_progress. If sysex_in_progress
-     * becomes false in the loop, there must have been an overflow
-     * and we can just drop all remaining bytes 
-     */
-    while (i < len && midi->sysex_in_progress) {
-        if (midi->sysex_message_count == 0 && i <= len - 4 &&
-            ((event.message = (((long) data[i]) | 
-                             (((long) data[i+1]) << 8) |
-                             (((long) data[i+2]) << 16) |
-                             (((long) data[i+3]) << 24))) &
-             0x80808080) == 0) { /* all data, no status */ 
-            if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
-                midi->sysex_in_progress = FALSE;
-            }
-            i += 4;
-        } else {
-            while (i < len) {
-                /* send one byte at a time */
-                unsigned char byte = data[i++];
-                if (is_real_time(byte) && 
-                    pm_realtime_filtered(byte, midi->filters)) {
-                    continue; /* real-time data is filtered, so omit */
-                }
-                midi->sysex_message |= 
-                    (byte << (8 * midi->sysex_message_count++));
-                if (byte == MIDI_EOX) {
-                    midi->sysex_in_progress = FALSE;
-                    pm_flush_sysex(midi, event.timestamp);
-                    return i;
-                } else if (midi->sysex_message_count == 4) {
-                    pm_flush_sysex(midi, event.timestamp);
-                    /* after handling at least one non-data byte
-                     * and reaching a 4-byte message boundary,
-                     * resume trying to send 4 at a time in outer loop
-                     */
-                    break;
-                }
-            }
-        }
-    }
-    return i;
-}
-
-
+/* portmidi.c -- cross-platform MIDI I/O library */
+/* see license.txt for license */
+
+#include "stdlib.h"
+#include "string.h"
+#include "portmidi.h"
+#include "porttime.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include <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)
+
+/* these are not static so that (possibly) some system-dependent code
+ * could override the portmidi.c default which is to use the first
+ * device added using pm_add_device()
+ */
+PmDeviceID pm_default_input_device_id = -1;
+PmDeviceID pm_default_output_device_id = -1;
+
+/* this is not static so that pm_init can set it directly
+ *   (see pmmac.c:pm_init())
+ */
+int pm_initialized = FALSE;
+
+int pm_hosterror;  /* boolean */
+
+/* if PM_CHECK_ERRORS is enabled, but the caller wants to
+ * handle an error condition, declare this as extern and 
+ * set to FALSE (this override is provided specifically
+ * for the test program virttest.c, where pmNameConflict 
+ * is expected in a call to Pm_CreateVirtualInput()):
+ */
+int pm_check_errors = TRUE;
+
+char pm_hosterror_text[PM_HOST_ERROR_MSG_LEN];
+
+#ifdef PM_CHECK_ERRORS
+
+#include <stdio.h>
+
+#define STRING_MAX 80
+
+static void prompt_and_exit(void)
+{
+    char line[STRING_MAX];
+    printf("type ENTER...");
+    char *rslt = fgets(line, STRING_MAX, stdin);
+    /* this will clean up open ports: */
+    exit(-1);
+}
+
+static PmError pm_errmsg(PmError err)
+{
+    if (!pm_check_errors) { /* see pm_check_errors declaration above */
+        ;
+    } else if (err == pmHostError) {
+        /* it seems pointless to allocate memory and copy the string,
+         * so I will do the work of Pm_GetHostErrorText directly
+         */
+        printf("PortMidi found host error...\n  %s\n", pm_hosterror_text);
+        pm_hosterror = FALSE;
+        pm_hosterror_text[0] = 0; /* clear the message */
+        prompt_and_exit();
+    } else if (err < 0) {
+        printf("PortMidi call failed...\n  %s\n", Pm_GetErrorText(err));
+        prompt_and_exit();
+    }
+    return err;
+}
+#else
+#define pm_errmsg(err) err
+#endif
+
+
+int pm_midi_length(PmMessage msg)
+{
+    int status, high, low;
+    static int high_lengths[] = {
+        1, 1, 1, 1, 1, 1, 1, 1,         /* 0x00 through 0x70 */
+        3, 3, 3, 3, 2, 2, 3, 1          /* 0x80 through 0xf0 */
+    };
+    static int low_lengths[] = {
+        1, 2, 3, 2, 1, 1, 1, 1,         /* 0xf0 through 0xf8 */
+        1, 1, 1, 1, 1, 1, 1, 1          /* 0xf9 through 0xff */
+    };
+
+    status = msg & 0xFF;
+    high = status >> 4;
+    low = status & 15;
+
+    return (high != 0xF) ? high_lengths[high] : low_lengths[low];
+}
+
+
+/*
+====================================================================
+system implementation of portmidi interface
+====================================================================
+*/
+
+int pm_descriptor_max = 0;
+int pm_descriptor_len = 0;
+descriptor_type pm_descriptors = NULL;
+
+/* interface pm_descriptors are simple: an array of string/fnptr pairs: */
+#define MAX_INTERF 4
+static struct {
+    const char *interf;
+    pm_create_fn create_fn;
+    pm_delete_fn delete_fn;
+} pm_interf_list[MAX_INTERF];
+
+static int pm_interf_list_len = 0;
+
+
+/* pm_add_interf -- describe an interface to library
+ *
+ * This is called at initialization time, once for each
+ * supported interface (e.g., CoreMIDI). The strings
+ * are retained but NOT COPIED, so do not destroy them!
+ *
+ * The purpose is to register functions that create/delete
+ * a virtual input or output device.
+ *
+ * returns pmInsufficientMemor if interface memory is
+ * exceeded, otherwise returns pmNoError.
+ */
+PmError pm_add_interf(const char *interf, pm_create_fn create_fn,
+                      pm_delete_fn delete_fn)
+{
+    if (pm_interf_list_len >= MAX_INTERF) {
+        return pmInsufficientMemory;
+    }
+    pm_interf_list[pm_interf_list_len].interf = interf;
+    pm_interf_list[pm_interf_list_len].create_fn = create_fn;
+    pm_interf_list[pm_interf_list_len].delete_fn = delete_fn;
+    pm_interf_list_len++;
+    return pmNoError;
+}
+
+
+PmError pm_create_virtual(PmInternal *midi, int is_input, const char *interf,
+                          const char *name, void *device_info)
+{
+    int i;
+    if (pm_interf_list_len == 0) {
+        return pmNotImplemented;
+    }
+    if (!interf) {
+        /* default interface is the first one */
+        interf = pm_interf_list[0].interf;
+    }
+    for (i = 0; i < pm_interf_list_len; i++) {
+        if (strcmp(pm_interf_list[i].interf,
+                   interf) == 0) {
+            int id = (*pm_interf_list[i].create_fn)(is_input, name,
+                                                        device_info);
+            pm_descriptors[id].pub.is_virtual = TRUE;
+            return id;
+        }
+    }
+    return pmInterfaceNotSupported;
+}
+
+
+/* pm_add_device -- describe interface/device pair to library 
+ *
+ * This is called at intialization time, once for each 
+ * interface (e.g. DirectSound) and device (e.g. SoundBlaster 1).
+ * This is also called when user creates a virtual device.
+ * 
+ * Normally, increasing integer indices are returned. If the device
+ * is virtual, a linear search is performed to ensure that the name
+ * is unique. If the name is already taken, the call will fail and
+ * no device is added.
+ *
+ * interf is assumed to be static memory, so it is NOT COPIED and 
+ * NOT FREED.
+ * name is owned by caller, COPIED if needed, and FREED by PortMidi.
+ * Caller is resposible for freeing name when pm_add_device returns.
+ *
+ * returns pmInvalidDeviceId if device memory is exceeded or a virtual
+ * device would take the name of an existing device.
+ * otherwise returns index (portmidi device_id) of the added device
+ */
+PmError pm_add_device(const char *interf, const char *name, int is_input, 
+                int is_virtual, void *descriptor, pm_fns_type dictionary) {
+    /* printf("pm_add_device: %s %s %d %p %p\n",
+           interf, name, is_input, descriptor, dictionary); */
+    int device_id;
+    PmDeviceInfo *d;
+    /* if virtual, search for duplicate name or unused ID; otherwise,
+     * just add a new device at the next integer available:
+     */
+    for (device_id = (is_virtual ? 0 : pm_descriptor_len);
+         device_id < pm_descriptor_len; device_id++) {
+        d = &pm_descriptors[device_id].pub;
+        d->structVersion = PM_DEVICEINFO_VERS;
+        if (strcmp(d->interf, interf) == 0 && strcmp(d->name, name) == 0) {
+            /* only reuse a name if it is a deleted virtual device with
+             * a matching direction (input or output) */
+            if (pm_descriptors[device_id].deleted && is_input == d->input) {
+                /* here, we know d->is_virtual because only virtual devices
+                 * can be deleted, and we know is_virtual because we are
+                 * in this loop. 
+                 */
+                pm_free((void *) d->name);  /* reuse this device entry */
+                d->name = NULL;
+                break;
+            /* name conflict exists if the new device appears to others as
+             * the same direction (input or output) as the existing device.
+             * Note that virtual inputs appear to others as outputs and
+             * vice versa.
+             * The direction of the new virtual device to others is "output" 
+             * if is_input, i.e., virtual inputs appear to others as outputs. 
+             * The existing device appears to others as "output" if
+             *     (d->is_virtual == d->input) by the same logic.
+             * The compare will detect if device directions are the same:
+             */
+            } else if (is_input == (d->is_virtual == d->input)) {
+                return pmNameConflict;
+            }
+        }
+    }
+    if (device_id >= pm_descriptor_max) {
+        /* expand pm_descriptors */
+        descriptor_type new_descriptors = (descriptor_type) 
+            pm_alloc(sizeof(descriptor_node) * (pm_descriptor_max + 32));
+        if (!new_descriptors) return pmInsufficientMemory;
+        if (pm_descriptors) {
+            memcpy(new_descriptors, pm_descriptors, 
+                   sizeof(descriptor_node) * pm_descriptor_max);
+            pm_free(pm_descriptors);
+        }
+        pm_descriptor_max += 32;
+        pm_descriptors = new_descriptors;
+    }
+    if (device_id == pm_descriptor_len) {
+        pm_descriptor_len++;  /* extending array of pm_descriptors */
+    }
+    d = &pm_descriptors[device_id].pub;
+    d->interf = interf;
+    d->name = pm_alloc(strlen(name) + 1);
+    if (!d->name) {
+        return pmInsufficientMemory;
+    }
+#if defined(WIN32) && !defined(_WIN32)
+#pragma warning(suppress: 4996)  /* don't use suggested strncpy_s */
+#endif
+    strcpy(d->name, name);
+    d->input = is_input;
+    d->output = !is_input;
+    d->is_virtual = FALSE;  /* caller should set to TRUE if this is virtual */
+
+    /* default state: nothing to close (for automatic device closing) */
+    d->opened = FALSE;
+
+    pm_descriptors[device_id].deleted = FALSE;
+
+    /* ID number passed to win32 multimedia API open */
+    pm_descriptors[device_id].descriptor = descriptor;
+    
+    /* points to PmInternal, allows automatic device closing */
+    pm_descriptors[device_id].pm_internal = NULL;
+
+    pm_descriptors[device_id].dictionary = dictionary;
+
+    /* set the defaults to the first input and output we see */
+    if (is_input && pm_default_input_device_id == -1) {
+        pm_default_input_device_id = device_id;
+    } else if (!is_input && pm_default_output_device_id == -1) {
+        pm_default_output_device_id = device_id;
+    }
+    
+    return device_id;
+}
+
+
+/* Undo a successful call to pm_add_device(). If a new device was
+ * allocated, it must be the last device in pm_descriptors, so it is
+ * easy to delete by decrementing the length of pm_descriptors, but
+ * first free the name (which was copied to the heap). Otherwise,
+ * the device must be a virtual device that was created previously
+ * and is in the interior of the array of pm_descriptors. Leave it,
+ * but mark it as deleted.
+ */
+void pm_undo_add_device(int id)
+{
+    /* Clear some fields (not all are strictly necessary) */ 
+    pm_descriptors[id].deleted = TRUE;
+    pm_descriptors[id].descriptor = NULL;
+    pm_descriptors[id].pm_internal = NULL;
+
+    if (id == pm_descriptor_len - 1) {
+        pm_free(pm_descriptors[id].pub.name);
+        pm_descriptor_len--;
+    }
+}
+
+
+/* utility to look up device, given a pattern, 
+   note: pattern is modified
+ */
+int Pm_FindDevice(char *pattern, int is_input)
+{
+    int id = pmNoDevice;
+    int i;
+    /* first parse pattern into name, interf parts */
+    const char *interf_pref = ""; /* initially assume it is not there */
+    char *name_pref = strstr(pattern, ", ");
+
+    if (name_pref) { /* found separator, adjust the pointer */
+        interf_pref = pattern;
+        name_pref[0] = 0;
+        name_pref += 2;
+    } else {
+        name_pref = pattern; /* whole string is the name pattern */
+    }
+    for (i = 0; i < pm_descriptor_len; i++) {
+        const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+        if (info &&
+            info->input == is_input &&
+            strstr(info->name, name_pref) &&
+            strstr(info->interf, interf_pref)) {
+            id = i;
+            break;
+        }
+    }    
+    return id;
+}
+
+
+/*
+====================================================================
+portmidi implementation
+====================================================================
+*/
+
+PMEXPORT int Pm_CountDevices(void)
+{
+    Pm_Initialize();
+    /* no error checking -- Pm_Initialize() does not fail */
+    return pm_descriptor_len;
+}
+
+
+PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo(PmDeviceID id)
+{
+    Pm_Initialize(); /* no error check needed */
+    if (id >= 0 && id < pm_descriptor_len && !pm_descriptors[id].deleted) {
+        return &pm_descriptors[id].pub;
+    }
+    return NULL;
+}
+
+/* pm_success_fn -- "noop" function pointer */
+PmError pm_success_fn(PmInternal *midi)
+{
+    return pmNoError;
+}
+
+/* none_write -- returns an error if called */
+PmError none_write_short(PmInternal *midi, PmEvent *buffer)
+{
+    return pmBadPtr;
+}
+
+/* pm_fail_timestamp_fn -- placeholder for begin_sysex and flush */
+PmError pm_fail_timestamp_fn(PmInternal *midi, PmTimestamp timestamp)
+{
+    return pmBadPtr;
+}
+
+PmError none_write_byte(PmInternal *midi, unsigned char byte, 
+                        PmTimestamp timestamp)
+{
+    return pmBadPtr;
+}
+
+/* pm_fail_fn -- generic function, returns error if called */
+PmError pm_fail_fn(PmInternal *midi)
+{
+    return pmBadPtr;
+}
+
+static PmError none_open(PmInternal *midi, void *driverInfo)
+{
+    return pmBadPtr;
+}
+
+static unsigned int none_check_host_error(PmInternal * midi)
+{
+    return FALSE;
+}
+
+PmTimestamp none_synchronize(PmInternal *midi)
+{
+    return 0;
+}
+
+#define none_abort pm_fail_fn
+#define none_close pm_fail_fn
+
+pm_fns_node pm_none_dictionary = {
+    none_write_short,
+    none_sysex,
+    none_sysex,
+    none_write_byte,
+    none_write_short,
+    none_write_flush,
+    none_synchronize,
+    none_open,
+    none_abort,
+    none_close,
+    none_poll,
+    none_check_host_error,
+};
+
+
+PMEXPORT const char *Pm_GetErrorText(PmError errnum)
+{
+    const char *msg;
+
+    switch(errnum)
+    {
+    case pmNoError:                  
+        msg = ""; 
+        break;
+    case pmHostError:                
+        msg = "PortMidi: Host error"; 
+        break;
+    case pmInvalidDeviceId:          
+        msg = "PortMidi: Invalid device ID"; 
+        break;
+    case pmInsufficientMemory:       
+        msg = "PortMidi: Insufficient memory"; 
+        break;
+    case pmBufferTooSmall:           
+        msg = "PortMidi: Buffer too small"; 
+        break;
+    case pmBadPtr:                   
+        msg = "PortMidi: Bad pointer";
+        break;
+    case pmInternalError:            
+        msg = "PortMidi: Internal PortMidi Error";
+        break;
+    case pmBufferOverflow:
+        msg = "PortMidi: Buffer overflow";
+        break;
+    case pmBadData:
+        msg = "PortMidi: Invalid MIDI message Data";
+        break;
+    case pmBufferMaxSize:
+        msg = "PortMidi: Buffer cannot be made larger";
+        break;
+    case pmNotImplemented:
+        msg = "PortMidi: Function is not implemented";
+        break;
+    case pmInterfaceNotSupported:
+        msg = "PortMidi: Interface not supported";
+        break;
+    case pmNameConflict:
+        msg = "PortMidi: Cannot create virtual device: name is taken";
+        break;
+    case pmDeviceRemoved:
+        msg = "PortMidi: Output attempted after (USB) device removed";
+        break;
+    default:
+        msg = "PortMidi: Illegal error number";
+        break;
+    }
+    return msg;
+}
+
+
+/* This can be called whenever you get a pmHostError return value
+ * or TRUE from Pm_HasHostError().
+ * The error will always be in the global pm_hosterror_text.
+ */
+PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len)
+{
+    assert(msg);
+    assert(len > 0);
+    if (pm_hosterror) {
+#if defined(WIN32) && !defined(_WIN32)
+#pragma warning(suppress: 4996)  /* don't use suggested strncpy_s */
+#endif
+        strncpy(msg, (char *) pm_hosterror_text, len);
+        pm_hosterror = FALSE;
+        pm_hosterror_text[0] = 0; /* clear the message; not necessary, but it
+                                     might help with debugging */
+        msg[len - 1] = 0; /* make sure string is terminated */
+    } else {
+        msg[0] = 0; /* no string to return */
+    }
+}
+
+
+PMEXPORT int Pm_HasHostError(PortMidiStream * stream)
+{
+    if (pm_hosterror) return TRUE;
+    if (stream) {
+        PmInternal * midi = (PmInternal *) stream;
+        return (*midi->dictionary->check_host_error)(midi);
+    }
+    return FALSE;
+}
+
+
+PMEXPORT PmError Pm_Initialize(void)
+{
+    if (!pm_initialized) {
+        pm_descriptor_len = 0;
+        pm_interf_list_len = 0;
+        pm_hosterror = FALSE;
+        pm_hosterror_text[0] = 0; /* the null string */
+        pm_init();
+        pm_initialized = TRUE;
+    }
+    return pmNoError;
+}
+
+
+PMEXPORT PmError Pm_Terminate(void)
+{
+    if (pm_initialized) {
+        pm_term();
+        /* if there are no devices, pm_descriptors might still be NULL */
+        if (pm_descriptors != NULL) {
+            int i;  /* free names copied into pm_descriptors */
+            for (i = 0; i < pm_descriptor_len; i++) {
+                if (pm_descriptors[i].pub.name) {
+                    pm_free(pm_descriptors[i].pub.name);
+                }
+            }
+            pm_free(pm_descriptors);
+            pm_descriptors = NULL;
+        }
+        pm_descriptor_len = 0;
+        pm_descriptor_max = 0;
+        pm_interf_list_len = 0;
+        pm_initialized = FALSE;
+    }
+    return pmNoError;
+}
+
+
+/* Pm_Read -- read up to length messages from source into buffer */
+/*
+ * returns number of messages actually read, or error code
+ */
+PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    int n = 0;
+    PmError err = pmNoError;
+    pm_hosterror = FALSE;
+    /* arg checking */
+    if(midi == NULL)
+        err = pmBadPtr;
+    else if(!pm_descriptors[midi->device_id].pub.opened)
+        err = pmBadPtr;
+    else if(!pm_descriptors[midi->device_id].pub.input)
+        err = pmBadPtr;    
+    /* First poll for data in the buffer...
+     * This either simply checks for data, or attempts first to fill the buffer
+     * with data from the MIDI hardware; this depends on the implementation.
+     * We could call Pm_Poll here, but that would redo a lot of redundant
+     * parameter checking, so I copied some code from Pm_Poll to here: */
+    else err = (*(midi->dictionary->poll))(midi);
+
+    if (err != pmNoError) {
+        if (err == pmHostError) {
+            midi->dictionary->check_host_error(midi);
+        }
+        return pm_errmsg(err);
+    }
+
+    while (n < length) {
+        err = Pm_Dequeue(midi->queue, buffer++);
+        if (err == pmBufferOverflow) {
+            /* ignore the data we have retreived so far */
+            return pm_errmsg(pmBufferOverflow);
+        } else if (err == 0) { /* empty queue */
+            break;
+        }
+        n++;
+    }
+    return n;
+}
+
+PMEXPORT PmError Pm_Poll(PortMidiStream *stream)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err;
+
+    pm_hosterror = FALSE;
+    /* arg checking */
+    if(midi == NULL)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.opened)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.input)
+        err = pmBadPtr;
+    else
+        err = (*(midi->dictionary->poll))(midi);
+
+    if (err != pmNoError) {
+        return pm_errmsg(err);
+    }
+
+    return (PmError) !Pm_QueueEmpty(midi->queue);
+}
+
+
+/* this is called from Pm_Write and Pm_WriteSysEx to issue a
+ * call to the system-dependent end_sysex function and handle 
+ * the error return
+ */
+static PmError pm_end_sysex(PmInternal *midi)
+{
+    PmError err = (*midi->dictionary->end_sysex)(midi, 0);
+    midi->sysex_in_progress = FALSE;
+    return err;
+}
+
+
+/* to facilitate correct error-handling, Pm_Write, Pm_WriteShort, and
+   Pm_WriteSysEx all operate a state machine that "outputs" calls to
+   write_short, begin_sysex, write_byte, end_sysex, and write_realtime */
+
+PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,
+                          int32_t length)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err = pmNoError;
+    int i;
+    int bits;
+    
+    pm_hosterror = FALSE;
+    /* arg checking */
+    if (midi == NULL) {
+        err = pmBadPtr;
+    } else {
+        descriptor_type desc = &pm_descriptors[midi->device_id]; 
+        if (!desc || !desc->pub.opened ||
+            !desc->pub.output || !desc->pm_internal) {
+            err = pmBadPtr;
+        } else if (desc->pm_internal->is_removed) {
+            err = pmDeviceRemoved;
+        }
+    }
+    if (err != pmNoError) goto pm_write_error;
+    
+    if (midi->latency == 0) {
+        midi->now = 0;
+    } else {
+        midi->now = (*(midi->time_proc))(midi->time_info);
+        if (midi->first_message || midi->sync_time + 100 /*ms*/ < midi->now) {
+            /* time to resync */
+            midi->now = (*midi->dictionary->synchronize)(midi);
+            midi->first_message = FALSE;
+        }
+    }
+    /* error recovery: when a sysex is detected, we call
+     *   dictionary->begin_sysex() followed by calls to
+     *   dictionary->write_byte() and dictionary->write_realtime()
+     *   until an end-of-sysex is detected, when we call
+     *   dictionary->end_sysex(). After an error occurs, 
+     *   Pm_Write() continues to call functions. For example,
+     *   it will continue to call write_byte() even after
+     *   an error sending a sysex message, and end_sysex() will be
+     *   called when an EOX or non-real-time status is found.
+     * When errors are detected, Pm_Write() returns immediately, 
+     *   so it is possible that this will drop data and leave
+     *   sysex messages in a partially transmitted state.
+     */
+    for (i = 0; i < length; i++) {
+        uint32_t msg = buffer[i].message;
+        bits = 0;
+        /* is this a sysex message? */
+        if (Pm_MessageStatus(msg) == MIDI_SYSEX) {
+            if (midi->sysex_in_progress) {
+                /* error: previous sysex was not terminated by EOX */
+                midi->sysex_in_progress = FALSE;
+                err = pmBadData;
+                goto pm_write_error;
+            }
+            midi->sysex_in_progress = TRUE;
+            if ((err = (*midi->dictionary->begin_sysex)(midi, 
+                               buffer[i].timestamp)) != pmNoError)
+                goto pm_write_error;
+            if ((err = (*midi->dictionary->write_byte)(midi, MIDI_SYSEX,
+                               buffer[i].timestamp)) != pmNoError) 
+                goto pm_write_error;
+            bits = 8;
+            /* fall through to continue sysex processing */
+        } else if ((msg & MIDI_STATUS_MASK) && 
+                   (Pm_MessageStatus(msg) != MIDI_EOX)) {
+            /* a non-sysex message */
+            if (midi->sysex_in_progress) {
+                /* this should be a realtime message */
+                if (is_real_time(msg)) {
+                    if ((err = (*midi->dictionary->write_realtime)(midi, 
+                                       &(buffer[i]))) != pmNoError)
+                        goto pm_write_error;
+                } else {
+                    midi->sysex_in_progress = FALSE;
+                    err = pmBadData;
+                    /* ignore any error from this, because we already have one */
+                    /* pass 0 as timestamp -- it's ignored */
+                    (*midi->dictionary->end_sysex)(midi, 0);
+                    goto pm_write_error;
+                }
+            } else { /* regular short midi message */
+                if ((err = (*midi->dictionary->write_short)(midi, 
+                                   &(buffer[i]))) != pmNoError)
+                    goto pm_write_error;
+                continue;
+            }
+        }
+        if (midi->sysex_in_progress) { /* send sysex bytes until EOX */
+            /* see if we can accelerate data transfer */
+            if (bits == 0 && midi->fill_base && /* 4 bytes to copy */
+                (*midi->fill_offset_ptr) + 4 <= midi->fill_length &&
+                (msg & 0x80808080) == 0) { /* all data */
+                    /* copy 4 bytes from msg to fill_base + fill_offset */
+                    unsigned char *ptr = midi->fill_base + 
+                                         *(midi->fill_offset_ptr);
+                    ptr[0] = msg; ptr[1] = msg >> 8; 
+                    ptr[2] = msg >> 16; ptr[3] = msg >> 24;
+                    (*midi->fill_offset_ptr) += 4;
+                     continue;
+            }
+            /* no acceleration, so do byte-by-byte copying */
+            while (bits < 32) {
+                unsigned char midi_byte = (unsigned char) (msg >> bits);
+                if ((err = (*midi->dictionary->write_byte)(midi, midi_byte, 
+                                   buffer[i].timestamp)) != pmNoError)
+                    goto pm_write_error;
+                if (midi_byte == MIDI_EOX) {
+                    err = pm_end_sysex(midi);
+                    if (err != pmNoError) goto error_exit;
+                    break; /* from while loop */
+                }
+                bits += 8;
+            }
+        } else {
+            /* not in sysex mode, but message did not start with status */
+            err = pmBadData;
+            goto pm_write_error;
+        }
+    }
+    /* after all messages are processed, send the data */
+    if (!midi->sysex_in_progress)
+        err = (*midi->dictionary->write_flush)(midi, 0);
+pm_write_error:
+    if (err == pmHostError) {
+        midi->dictionary->check_host_error(midi);
+    }
+error_exit:
+    return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,
+                               PmMessage msg)
+{
+    PmEvent event;
+    
+    event.timestamp = when;
+    event.message = msg;
+    return Pm_Write(stream, &event, 1);
+}
+
+
+PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, 
+                      unsigned char *msg)
+{
+    /* allocate buffer space for PM_DEFAULT_SYSEX_BUFFER_SIZE bytes */
+    /* each PmEvent holds sizeof(PmMessage) bytes of sysex data */
+    #define BUFLEN ((int) (PM_DEFAULT_SYSEX_BUFFER_SIZE / sizeof(PmMessage)))
+    PmEvent buffer[BUFLEN];
+    int buffer_size = 1; /* first time, send 1. After that, it's BUFLEN */
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err = pmNoError;
+    /* the next byte in the buffer is represented by an index, bufx, and
+       a shift in bits */
+    int shift = 0;
+    int bufx = 0;
+    buffer[0].message = 0;
+    buffer[0].timestamp = when;
+
+    while (1) {
+        /* insert next byte into buffer */
+        buffer[bufx].message |= ((*msg) << shift);
+        shift += 8;
+        if (*msg++ == MIDI_EOX) break;
+        if (shift == 32) {
+            shift = 0;
+            bufx++;
+            if (bufx == buffer_size) {
+                err = Pm_Write(stream, buffer, buffer_size);
+                /* note: Pm_Write has already called errmsg() */
+                if (err) return err;
+                /* prepare to fill another buffer */
+                bufx = 0;
+                buffer_size = BUFLEN;
+                /* optimization: maybe we can just copy bytes */
+                if (midi->fill_base) {
+                    while (*(midi->fill_offset_ptr) < midi->fill_length) {
+                        midi->fill_base[(*midi->fill_offset_ptr)++] = *msg;
+                        if (*msg++ == MIDI_EOX) {
+                            err = pm_end_sysex(midi);
+                            if (err != pmNoError) return pm_errmsg(err);
+                            goto end_of_sysex;
+                        }
+                    }
+                    /* I thought that I could do a pm_Write here and
+                     * change this if to a loop, avoiding calls in Pm_Write
+                     * to the slower write_byte, but since 
+                     * sysex_in_progress is true, this will not flush
+                     * the buffer, and we'll infinite loop: */
+                    /* err = Pm_Write(stream, buffer, 0);
+                       if (err) return err; */
+                    /* instead, the way this works is that Pm_Write calls
+                     * write_byte on 4 bytes. The first, since the buffer
+                     * is full, will flush the buffer and allocate a new
+                     * one. This primes the buffer so
+                     * that we can return to the loop above and fill it
+                     * efficiently without a lot of function calls.
+                     */
+                    buffer_size = 1; /* get another message started */
+                }
+            }
+            buffer[bufx].message = 0;
+            buffer[bufx].timestamp = when;
+        } 
+        /* keep inserting bytes until you find MIDI_EOX */
+    }
+end_of_sysex:
+    /* we're finished sending full buffers, but there may
+     * be a partial one left.
+     */
+    if (shift != 0) bufx++; /* add partial message to buffer len */
+    if (bufx) { /* bufx is number of PmEvents to send from buffer */
+        err = Pm_Write(stream, buffer, bufx);
+        if (err) return err;
+    }
+    return pmNoError;
+}
+
+
+
+PmError pm_create_internal(PmInternal **stream, PmDeviceID device_id,
+                           int is_input, int latency, PmTimeProcPtr time_proc,
+                           void *time_info, int buffer_size)
+{
+    PmInternal *midi;  /* initialized below */
+    if (device_id < 0 || device_id >= pm_descriptor_len) {
+       return pmInvalidDeviceId;
+    }
+    if (latency < 0) {  /* force a legal value */
+        latency = 0;
+    }
+    /* create portMidi internal data */
+    midi = (PmInternal *) pm_alloc(sizeof(PmInternal)); 
+    *stream = midi;
+    if (!midi) {
+        return pmInsufficientMemory;
+    }
+    midi->device_id = device_id;
+    midi->is_input = is_input;
+    midi->is_removed = FALSE;
+    midi->time_proc = time_proc;
+    /* if latency != 0, we need a time reference for output.
+       we always need a time reference for input.
+       If none is provided, use PortTime library */
+    if (time_proc == NULL && (latency != 0 || is_input)) {
+        if (!Pt_Started()) 
+            Pt_Start(1, 0, 0);
+        /* time_get does not take a parameter, so coerce */
+        midi->time_proc = (PmTimeProcPtr) Pt_Time;
+    }
+    midi->time_info = time_info;
+    if (is_input) {
+        midi->latency = 0;  /* unused by input */
+        if (buffer_size <= 0) buffer_size = 256; /* default buffer size */
+        midi->queue = Pm_QueueCreate(buffer_size, (int32_t) sizeof(PmEvent));
+        if (!midi->queue) {
+            /* free portMidi data */
+            *stream = NULL;
+            pm_free(midi);
+            return pmInsufficientMemory;
+        }
+    } else {
+        /* if latency zero, output immediate (timestamps ignored) */
+        /* if latency < 0, use 0 but don't return an error */
+        if (latency < 0) latency = 0;
+        midi->latency = latency;
+        midi->queue = NULL;  /* unused by output; input needs to allocate: */
+    }
+    midi->buffer_len = buffer_size; /* portMidi input storage */
+    midi->sysex_in_progress = FALSE;
+    midi->message = 0; 
+    midi->message_count = 0; 
+    midi->filters = (is_input ? PM_FILT_ACTIVE : 0);
+    midi->channel_mask = 0xFFFF;
+    midi->sync_time = 0;
+    midi->first_message = TRUE;
+    midi->api_info = NULL;
+    midi->fill_base = NULL;
+    midi->fill_offset_ptr = NULL;
+    midi->fill_length = 0;
+    midi->dictionary = pm_descriptors[device_id].dictionary;
+    pm_descriptors[device_id].pm_internal = midi;
+    return pmNoError;
+}
+
+
+PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,
+                              PmDeviceID inputDevice,
+                              void *inputDriverInfo,
+                              int32_t bufferSize,
+                              PmTimeProcPtr time_proc,
+                              void *time_info)
+{
+    PmInternal *midi;
+    PmError err = pmNoError;
+    pm_hosterror = FALSE;
+    *stream = NULL;  /* invariant: *stream == midi */
+    
+    /* arg checking */
+    if (!pm_descriptors[inputDevice].pub.input) 
+        err =  pmInvalidDeviceId;
+    else if (pm_descriptors[inputDevice].pub.opened)
+        err =  pmInvalidDeviceId;
+    if (err != pmNoError) 
+        goto error_return;
+
+    /* common initialization of PmInternal structure (midi): */
+    err = pm_create_internal(&midi, inputDevice, TRUE, 0, time_proc,
+                             time_info, bufferSize);
+    if (err) {
+        goto error_return;  /* will return with *stream == NULL */
+    }
+    *stream = midi;  /* no error, so midi is valid */
+
+    /* open system dependent input device */
+    err = (*midi->dictionary->open)(midi, inputDriverInfo);
+    if (err) {
+        *stream = NULL;
+        pm_descriptors[inputDevice].pm_internal = NULL;
+        /* free portMidi data */
+        Pm_QueueDestroy(midi->queue);
+        pm_free(midi);
+    } else {
+        /* portMidi input open successful */
+        pm_descriptors[inputDevice].pub.opened = TRUE;
+    }
+error_return:
+    /* note: if there is a pmHostError, it is the responsibility
+     * of the system-dependent code (*midi->dictionary->open)()
+     * to set pm_hosterror and pm_hosterror_text
+     */
+    return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,
+                               PmDeviceID outputDevice,
+                               void *outputDriverInfo,
+                               int32_t bufferSize,
+                               PmTimeProcPtr time_proc,
+                               void *time_info,
+                               int32_t latency)
+{
+    PmInternal *midi;
+    PmError err = pmNoError;
+    pm_hosterror = FALSE;
+    *stream =  NULL;
+    
+    /* arg checking */
+    if (outputDevice < 0 || outputDevice >= pm_descriptor_len)
+        err = pmInvalidDeviceId;
+    else if (!pm_descriptors[outputDevice].pub.output) 
+        err = pmInvalidDeviceId;
+    else if (pm_descriptors[outputDevice].pub.opened)
+        err = pmInvalidDeviceId;
+    if (err != pmNoError) 
+        goto error_return;
+
+    /* common initialization of PmInternal structure (midi): */
+    err = pm_create_internal(&midi, outputDevice, FALSE, latency, time_proc,
+                             time_info, bufferSize);
+    *stream = midi;
+    if (err) {
+        goto error_return;
+    }
+
+    /* open system dependent output device */
+    err = (*midi->dictionary->open)(midi, outputDriverInfo);
+    if (err) {
+        *stream = NULL;
+        pm_descriptors[outputDevice].pm_internal = NULL;
+        /* free portMidi data */
+        pm_free(midi); 
+    } else {
+        /* portMidi input open successful */
+        pm_descriptors[outputDevice].pub.opened = TRUE;
+    }
+error_return:
+    /* note: system-dependent code must set pm_hosterror and
+     * pm_hosterror_text if a pmHostError occurs
+     */
+    return pm_errmsg(err);
+}
+
+
+static PmError create_virtual_device(const char *name, const char *interf,
+                                     void *device_info, int is_input)
+{
+    PmError err = pmNoError;
+    int i;
+    pm_hosterror = FALSE;
+    
+    /* arg checking */
+    if (!name) {
+        err = pmInvalidDeviceId;
+        goto error_return;
+    }
+
+    Pm_Initialize();  /* just in case */
+
+    /* create the virtual device */
+    if (pm_interf_list_len == 0) {
+        return pmNotImplemented;
+    }
+    if (!interf) {
+        /* default interface is the first one */
+        interf = pm_interf_list[0].interf;
+    }
+    /* look up and call the create_fn for interf(ace), e.g. "CoreMIDI" */
+    for (i = 0; i < pm_interf_list_len; i++) {
+        if (strcmp(pm_interf_list[i].interf, interf) == 0) {
+            int id = (*pm_interf_list[i].create_fn)(is_input,
+                                                        name, device_info);
+            /* id could be pmNameConflict or an actual descriptor index */
+            if (id >= 0) {
+                pm_descriptors[id].pub.is_virtual = TRUE;
+            }
+            err = id;
+            goto error_return;
+        }
+    }
+    err = pmInterfaceNotSupported;
+
+error_return:
+    /* note: if there is a pmHostError, it is the responsibility
+     * of the system-dependent code (*midi->dictionary->open)()
+     * to set pm_hosterror and pm_hosterror_text
+     */
+    return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_CreateVirtualInput(const char *name,
+                                       const char *interf,
+                                       void *deviceInfo)
+{
+    return create_virtual_device(name, interf, deviceInfo, TRUE);
+}
+
+PMEXPORT PmError Pm_CreateVirtualOutput(const char *name, const char *interf,
+                                        void *deviceInfo)
+{
+    return create_virtual_device(name, interf, deviceInfo, FALSE);
+}
+
+PmError Pm_DeleteVirtualDevice(PmDeviceID id)
+{
+    int i;
+    const char *interf = pm_descriptors[id].pub.interf;
+    PmError err = pmBadData;  /* returned if we cannot find the interface-
+                               * specific delete function */
+    /* arg checking */
+    if (id < 0 || id >= pm_descriptor_len ||
+        pm_descriptors[id].pub.opened || pm_descriptors[id].deleted) {
+        return pmInvalidDeviceId;
+    }
+    /* delete function pointer is in interfaces list */
+    for (i = 0; i < pm_interf_list_len; i++) {
+        if (strcmp(pm_interf_list[i].interf, interf) == 0) {
+            err = (*pm_interf_list[i].delete_fn)(id);
+            break;
+        }
+    }
+    pm_descriptors[id].deleted = TRUE;
+    /* (pm_internal should already be NULL because !pub.opened) */
+    pm_descriptors[id].pm_internal = NULL;
+    pm_descriptors[id].descriptor = NULL;
+    return err;
+}
+
+PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err = pmNoError;
+
+    if (midi == NULL)
+        err = pmBadPtr;
+    else
+        midi->channel_mask = mask;
+
+    return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_SetFilter(PortMidiStream *stream, int32_t filters)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err = pmNoError;
+
+    /* arg checking */
+    if (midi == NULL)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.opened)
+        err = pmBadPtr;
+    else
+        midi->filters = filters;
+    return pm_errmsg(err);
+}
+
+
+PMEXPORT PmError Pm_Close(PortMidiStream *stream)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err = pmNoError;
+
+    pm_hosterror = FALSE;
+    /* arg checking */
+    if (midi == NULL) /* midi must point to something */
+        err = pmBadPtr;
+    /* if it is an open device, the device_id will be valid */
+    else if (midi->device_id < 0 || midi->device_id >= pm_descriptor_len)
+        err = pmBadPtr;
+    /* and the device should be in the opened state */
+    else if (!pm_descriptors[midi->device_id].pub.opened)
+        err = pmBadPtr;
+    
+    if (err != pmNoError) 
+        goto error_return;
+
+    /* close the device */
+    err = (*midi->dictionary->close)(midi);
+    /* even if an error occurred, continue with cleanup */
+    pm_descriptors[midi->device_id].pm_internal = NULL;
+    pm_descriptors[midi->device_id].pub.opened = FALSE;
+    if (midi->queue) Pm_QueueDestroy(midi->queue);
+    pm_free(midi); 
+error_return:
+    /* system dependent code must set pm_hosterror and
+     * pm_hosterror_text if a pmHostError occurs.
+     */
+    return pm_errmsg(err);
+}
+
+PmError Pm_Synchronize(PortMidiStream* stream)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err = pmNoError;
+    if (midi == NULL)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.output)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.opened)
+        err = pmBadPtr;
+    else
+        midi->first_message = TRUE;
+    return err;
+}
+
+PMEXPORT PmError Pm_Abort(PortMidiStream* stream)
+{
+    PmInternal *midi = (PmInternal *) stream;
+    PmError err;
+    /* arg checking */
+    if (midi == NULL)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.output)
+        err = pmBadPtr;
+    else if (!pm_descriptors[midi->device_id].pub.opened)
+        err = pmBadPtr;
+    else
+        err = (*midi->dictionary->abort)(midi);
+
+    if (err == pmHostError) {
+        midi->dictionary->check_host_error(midi);
+    }
+    return pm_errmsg(err);
+}
+
+
+
+/* pm_channel_filtered returns non-zero if the channel mask is
+   blocking the current channel */
+#define pm_channel_filtered(status, mask) \
+    ((((status) & 0xF0) != 0xF0) && (!(Pm_Channel((status) & 0x0F) & (mask))))
+
+
+/* The following two functions will checks to see if a MIDI message
+   matches the filtering criteria.  Since the sysex routines only want
+   to filter realtime messages, we need to have separate routines.
+ */
+
+
+/* pm_realtime_filtered returns non-zero if the filter will kill the
+   current message. Note that only realtime messages are checked here.
+ */
+#define pm_realtime_filtered(status, filters) \
+    ((((status) & 0xF0) == 0xF0) && ((1 << ((status) & 0xF)) & (filters)))
+
+/*
+    return ((status == MIDI_ACTIVE) && (filters & PM_FILT_ACTIVE))
+            ||  ((status == MIDI_CLOCK) && (filters & PM_FILT_CLOCK))
+            ||  ((status == MIDI_START) && (filters & PM_FILT_PLAY))
+            ||  ((status == MIDI_STOP) && (filters & PM_FILT_PLAY))
+            ||  ((status == MIDI_CONTINUE) && (filters & PM_FILT_PLAY))
+            ||  ((status == MIDI_F9) && (filters & PM_FILT_F9))
+            ||  ((status == MIDI_FD) && (filters & PM_FILT_FD))
+            ||  ((status == MIDI_RESET) && (filters & PM_FILT_RESET))
+            ||  ((status == MIDI_MTC) && (filters & PM_FILT_MTC))
+            ||  ((status == MIDI_SONGPOS) && (filters & PM_FILT_SONG_POSITION))
+            ||  ((status == MIDI_SONGSEL) && (filters & PM_FILT_SONG_SELECT))
+            ||  ((status == MIDI_TUNE) && (filters & PM_FILT_TUNE));
+}*/
+
+
+/* pm_status_filtered returns non-zero if a filter will kill the
+   current message, based on status. Note that sysex and real time are
+   not checked.  It is up to the subsystem (winmm, core midi, alsa) to
+   filter sysex, as it is handled more easily and efficiently at that
+   level. Realtime message are filtered in pm_realtime_filtered.
+ */
+#define pm_status_filtered(status, filters) \
+                ((1 << (16 + ((status) >> 4))) & (filters))
+
+
+/*
+    return  ((status == MIDI_NOTE_ON) && (filters & PM_FILT_NOTE))
+            ||  ((status == MIDI_NOTE_OFF) && (filters & PM_FILT_NOTE))
+            ||  ((status == MIDI_CHANNEL_AT) &&
+                 (filters & PM_FILT_CHANNEL_AFTERTOUCH))
+            ||  ((status == MIDI_POLY_AT) && (filters & PM_FILT_POLY_AFTERTOUCH))
+            ||  ((status == MIDI_PROGRAM) && (filters & PM_FILT_PROGRAM))
+            ||  ((status == MIDI_CONTROL) && (filters & PM_FILT_CONTROL))
+            ||  ((status == MIDI_PITCHBEND) && (filters & PM_FILT_PITCHBEND));
+
+}
+*/
+
+static void pm_flush_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+    PmEvent event;
+    
+    /* there may be nothing in the buffer */
+    if (midi->message_count == 0) return; /* nothing to flush */
+    
+    event.message = midi->message;
+    event.timestamp = timestamp;
+    /* copied from pm_read_short, avoids filtering */
+    if (Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
+        midi->sysex_in_progress = FALSE;
+    }
+    midi->message_count = 0;
+    midi->message = 0;
+}
+
+
+/* pm_read_short and pm_read_bytes
+   are the interface between system-dependent MIDI input handlers
+   and the system-independent PortMIDI code.
+   The input handler MUST obey these rules:
+   1) all short input messages must be sent to pm_read_short, which
+      enqueues them to a FIFO for the application.
+   2) each buffer of sysex bytes should be reported by calling pm_read_bytes
+      (which sets midi->sysex_in_progress). After the eox byte, 
+      pm_read_bytes will clear sysex_in_progress
+ */
+
+/* pm_read_short is the place where all input messages arrive from 
+   system-dependent code such as pmwinmm.c. Here, the messages
+   are entered into the PortMidi input buffer. 
+ */
+void pm_read_short(PmInternal *midi, PmEvent *event)
+{ 
+    int status;
+    /* arg checking */
+    assert(midi != NULL);
+    /* midi filtering is applied here */
+    status = Pm_MessageStatus(event->message);
+    if (!pm_status_filtered(status, midi->filters)
+        && (!is_real_time(status) || 
+            !pm_realtime_filtered(status, midi->filters))
+        && !pm_channel_filtered(status, midi->channel_mask)) {
+        /* if sysex is in progress and we get a status byte, it had
+           better be a realtime message or the starting SYSEX byte;
+           otherwise, we exit the sysex_in_progress state
+         */
+        if (midi->sysex_in_progress && (status & MIDI_STATUS_MASK)) {
+            /* two choices: real-time or not. If it's real-time, then
+             * this should be delivered as a sysex byte because it is
+             * embedded in a sysex message
+             */
+            if (is_real_time(status)) {
+                midi->message |= (status << (8 * midi->message_count++));
+                if (midi->message_count == 4) {
+                    pm_flush_sysex(midi, event->timestamp);
+                }
+            } else { /* otherwise, it's not real-time. This interrupts
+                      * a sysex message in progress */
+                midi->sysex_in_progress = FALSE;
+            }
+        } else if (Pm_Enqueue(midi->queue, event) == pmBufferOverflow) {
+            midi->sysex_in_progress = FALSE;
+        }
+    }
+}
+
+
+/* pm_read_bytes -- a sequence of bytes has been read from a device.
+ *        parse the bytes into PmEvents and put them in the queue.
+ * midi - the midi device
+ * data - the bytes
+ * len - the number of bytes
+ * timestamp - when were the bytes received?
+ *
+ * returns how many bytes processed, which is always the len parameter
+ */
+unsigned int pm_read_bytes(PmInternal *midi, const unsigned char *data, 
+                    int len, PmTimestamp timestamp)
+{
+    int i = 0; /* index into data, must not be unsigned (!) */
+    PmEvent event;
+    event.timestamp = timestamp;
+    assert(midi);
+
+    /* Since sysex messages may have embedded real-time messages, we
+     * cannot simply send every consecutive group of 4 bytes as sysex
+     * data. Instead, we insert each data byte into midi->message and
+     * keep count using midi->message_count. If we encounter a
+     * real-time message, it is sent immediately as a 1-byte PmEvent,
+     * while sysex bytes are sent as PmEvents in groups of 4 bytes
+     * until the sysex is either terminated by EOX (F7) or a
+     * non-real-time message is encountered, indicating that the EOX
+     * was dropped.
+     */
+
+    /* This is a finite state machine so that we can accept any number
+     * of bytes, even if they contain partial messages.
+     *
+     * midi->sysex_in_progress says we are expecting sysex message bytes
+     *    (otherwise, expect a short message or real-time message)
+     * midi->message accumulates bytes to enqueue for application
+     * midi->message_count is the number of bytes accumulated
+     * midi->short_message_count is how many bytes we need in midi->message,
+     *    therefore midi->message_count, before we have a complete message
+     * midi->running_status is running status or 0 if there is none
+     *
+     * Set running status when: A status byte < F0 is received.
+     * Clear running status when: A status byte from F0 through F7 is
+     * received.
+     * Ignore (drop) data bytes when running status is 0.
+     *
+     * Our output buffer (the application input buffer) can overflow
+     * at any time. If that occurs, we have to clear sysex_in_progress
+     * (otherwise, the buffer could be flushed and we could resume
+     * inserting sysex bytes into the buffer, resulting in a continuation
+     * of a sysex message even though a buffer full of bytes was dropped.)
+     *
+     * Since we have to parse everything and form <=4-byte PmMessages,
+     * we send all messages via pm_read_short, which filters messages
+     * according to midi->filters and clears sysex_in_progress on
+     * buffer overflow. This also provides a "short cut" for short
+     * messages that are already parsed, allowing API-specific code
+     * to bypass this more expensive state machine. What if we are
+     * getting a sysex message, but it is interrupted by a short
+     * message (status 80-EF) and a direct call to pm_read_short?
+     * Without some care, the state machine would still be in
+     * sysex_in_progress mode, and subsequent data bytes would be
+     * accumulated as more sysex data, which is wrong since you
+     * cannot have a short message in the middle of a sysex message.
+     * To avoid this problem, pm_read_short clears sysex_in_progress
+     * when a non-real-time short message arrives.
+     */
+
+    while (i < len) {
+        unsigned char byte = data[i++];
+        if (is_real_time(byte)) {
+            event.message = byte;
+            pm_read_short(midi, &event);
+        } else if (byte & MIDI_STATUS_MASK && byte != MIDI_EOX) {
+            midi->message = byte;
+            midi->message_count = 1;
+            if (byte == MIDI_SYSEX) {
+                midi->sysex_in_progress = TRUE;
+            } else {
+                /* If Sysex is in progress, we cancel it because we encountered
+                   a status byte:
+                */
+                midi->sysex_in_progress = FALSE;
+                midi->short_message_count = pm_midi_length(midi->message);
+                /* maybe we're done already with a 1-byte message: */
+                if (midi->short_message_count == 1) {
+                    event.message = byte;
+                    pm_read_short(midi, &event);
+                    midi->message_count = 0;
+                }
+            }
+        } else if (midi->sysex_in_progress) {  /* sysex data byte */
+            /* accumulate sysex message data or EOX */
+            midi->message |= (byte << (8 * midi->message_count++));
+            if (midi->message_count == 4 || byte == MIDI_EOX) {
+                event.message = midi->message;
+                /* enqueue if not filtered, and then if there is overflow,
+                   stop sysex_in_progress */
+                if (!(midi->filters & PM_FILT_SYSEX) &&
+                    Pm_Enqueue(midi->queue, &event) == pmBufferOverflow) {
+                    midi->sysex_in_progress = FALSE;
+                } else if (byte == MIDI_EOX) {  /* continue unless EOX */
+                    midi->sysex_in_progress = FALSE;
+                }
+                midi->message_count = 0;
+                midi->message = 0;
+            }
+        } else {  /* no sysex in progress, must be short message */
+            if (midi->message_count == 0) {  /* need a running status */
+                if (midi->running_status) {
+                    midi->message = midi->running_status;
+                    midi->message_count = 1;
+                } else {  /* drop data byte - not sysex and no status byte */
+                    continue;
+                }
+            }
+            midi->message |= (byte << (8 * midi->message_count++));
+            if (midi->message_count == midi->short_message_count) {
+                event.message = midi->message;
+                pm_read_short(midi, &event);
+            }
+        }
+    }
+    return i;
+}
diff --git a/portmidi/pm_common/portmidi.h b/portmidi/pm_common/portmidi.h
--- a/portmidi/pm_common/portmidi.h
+++ b/portmidi/pm_common/portmidi.h
@@ -1,612 +1,1009 @@
-#ifndef PORT_MIDI_H
-#define PORT_MIDI_H
-#ifdef __cplusplus
-extern "C" {
-#endif /* __cplusplus */
-
-/*
- * PortMidi Portable Real-Time MIDI Library
- * PortMidi API Header File
- * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/
- *
- * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
- * Copyright (c) 2001-2006 Roger B. Dannenberg
- *
- * Latest version available at: http://www.cs.cmu.edu/~music/portmidi/
- *
- * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
- * Copyright (c) 2001-2006 Roger B. Dannenberg
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files
- * (the "Software"), to deal in the Software without restriction,
- * including without limitation the rights to use, copy, modify, merge,
- * publish, distribute, sublicense, and/or sell copies of the Software,
- * and to permit persons to whom the Software is furnished to do so,
- * subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
- * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
- * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
-/*
- * The text above constitutes the entire PortMidi license; however, 
- * the PortMusic community also makes the following non-binding requests:
- *
- * Any person wishing to distribute modifications to the Software is
- * requested to send the modifications to the original developer so that
- * they can be incorporated into the canonical version. It is also
- * requested that these non-binding requests be included along with the 
- * license above.
- */
-
-/* CHANGELOG FOR PORTMIDI
- *     (see ../CHANGELOG.txt)
- *
- * IMPORTANT INFORMATION ABOUT A WIN32 BUG:
- *
- *    Windows apparently has a serious midi bug -- if you do not close ports, Windows
- *    may crash. PortMidi tries to protect against this by using a DLL to clean up.
- *
- *    If client exits for example with:
- *      i)  assert 
- *      ii) Ctrl^c, 
- *    then DLL clean-up routine called. However, when client does something 
- *    really bad (e.g. assigns value to NULL pointer) then DLL CLEANUP ROUTINE
- *    NEVER RUNS! In this state, if you wait around long enough, you will
- *    probably get the blue screen of death. Can also go into Pview and there will
- *    exist zombie process that you can't kill.
- *
- *    You can enable the DLL cleanup routine by defining USE_DLL_FOR_CLEANUP.
- *    Do not define this preprocessor symbol if you do not want to use this
- *    feature.
- *
- * NOTES ON HOST ERROR REPORTING: 
- *
- *    PortMidi errors (of type PmError) are generic, system-independent errors.
- *    When an error does not map to one of the more specific PmErrors, the
- *    catch-all code pmHostError is returned. This means that PortMidi has
- *    retained a more specific system-dependent error code. The caller can
- *    get more information by calling Pm_HasHostError() to test if there is
- *    a pending host error, and Pm_GetHostErrorText() to get a text string
- *    describing the error. Host errors are reported on a per-device basis 
- *    because only after you open a device does PortMidi have a place to 
- *    record the host error code. I.e. only 
- *    those routines that receive a (PortMidiStream *) argument check and 
- *    report errors. One exception to this is that Pm_OpenInput() and 
- *    Pm_OpenOutput() can report errors even though when an error occurs,
- *    there is no PortMidiStream* to hold the error. Fortunately, both
- *    of these functions return any error immediately, so we do not really
- *    need per-device error memory. Instead, any host error code is stored
- *    in a global, pmHostError is returned, and the user can call 
- *    Pm_GetHostErrorText() to get the error message (and the invalid stream
- *    parameter will be ignored.) The functions 
- *    pm_init and pm_term do not fail or raise
- *    errors. The job of pm_init is to locate all available devices so that
- *    the caller can get information via PmDeviceInfo(). If an error occurs,
- *    the device is simply not listed as available.
- *
- *    Host errors come in two flavors:
- *      a) host error 
- *      b) host error during callback
- *    These can occur w/midi input or output devices. (b) can only happen 
- *    asynchronously (during callback routines), whereas (a) only occurs while
- *    synchronously running PortMidi and any resulting system dependent calls.
- *    Both (a) and (b) are reported by the next read or write call. You can
- *    also query for asynchronous errors (b) at any time by calling
- *    Pm_HasHostError().
- *
- * NOTES ON COMPILE-TIME SWITCHES
- *
- *    DEBUG assumes stdio and a console. Use this if you want automatic, simple
- *        error reporting, e.g. for prototyping. If you are using MFC or some 
- *        other graphical interface with no console, DEBUG probably should be
- *        undefined.
- *    PM_CHECK_ERRORS more-or-less takes over error checking for return values,
- *        stopping your program and printing error messages when an error
- *        occurs. This also uses stdio for console text I/O.
- *    USE_DLL_FOR_CLEANUP is described above. (Windows only.)
- *    
- */ 
-
-#ifndef FALSE
-    #define FALSE 0
-#endif
-#ifndef TRUE
-    #define TRUE 1
-#endif
-
-/* default size of buffers for sysex transmission: */
-#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
-
-
-typedef enum {
-    pmNoError = 0,
-    pmNoData = 0, /* A "no error" return that also indicates no data avail. */
-    pmGotData = 1, /* A "no error" return that also indicates data available */
-    pmHostError = -10000,
-    pmInvalidDeviceId, /* out of range or 
-                        * output device when input is requested or 
-                        * input device when output is requested or
-                        * device is already opened 
-                        */
-    pmInsufficientMemory,
-    pmBufferTooSmall,
-    pmBufferOverflow,
-    pmBadPtr,
-    pmBadData, /* illegal midi data, e.g. missing EOX */
-    pmInternalError,
-    pmBufferMaxSize /* buffer is already as large as it can be */
-    /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */
-} PmError;
-
-/*
-    Pm_Initialize() is the library initialisation function - call this before
-    using the library.
-*/
-
-PmError Pm_Initialize( void );
-
-/*
-    Pm_Terminate() is the library termination function - call this after
-    using the library.
-*/
-
-PmError Pm_Terminate( void );
-
-/*  A single PortMidiStream is a descriptor for an open MIDI device.
-*/
-typedef void PortMidiStream;
-#define PmStream PortMidiStream
-
-/*
-    Test whether stream has a pending host error. Normally, the client finds
-    out about errors through returned error codes, but some errors can occur
-    asynchronously where the client does not
-    explicitly call a function, and therefore cannot receive an error code.
-    The client can test for a pending error using Pm_HasHostError(). If true,
-    the error can be accessed and cleared by calling Pm_GetErrorText(). 
-    Errors are also cleared by calling other functions that can return
-    errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The
-    client does not need to call Pm_HasHostError(). Any pending error will be
-    reported the next time the client performs an explicit function call on 
-    the stream, e.g. an input or output operation. Until the error is cleared,
-    no new error codes will be obtained, even for a different stream.
-*/
-int Pm_HasHostError( PortMidiStream * stream );
-
-
-/*  Translate portmidi error number into human readable message.
-    These strings are constants (set at compile time) so client has 
-    no need to allocate storage
-*/
-const char *Pm_GetErrorText( PmError errnum );
-
-/*  Translate portmidi host error into human readable message.
-    These strings are computed at run time, so client has to allocate storage.
-    After this routine executes, the host error is cleared. 
-*/
-void Pm_GetHostErrorText(char * msg, unsigned int len);
-
-#define HDRLENGTH 50
-#define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less 
-                                      than this number of characters */
-
-/*
-    Device enumeration mechanism.
-
-    Device ids range from 0 to Pm_CountDevices()-1.
-
-*/
-typedef int PmDeviceID;
-#define pmNoDevice -1
-typedef struct {
-    int structVersion; 
-    const char *interf; /* underlying MIDI API, e.g. MMSystem or DirectX */
-    const char *name;   /* device name, e.g. USB MidiSport 1x1 */
-    int input; /* true iff input is available */
-    int output; /* true iff output is available */
-    int opened; /* used by generic PortMidi code to do error checking on arguments */
-
-} PmDeviceInfo;
-
-
-int Pm_CountDevices( void );
-/*
-    Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID()
-
-    Return the default device ID or pmNoDevice if there are no devices.
-    The result can be passed to Pm_OpenMidi().
-    
-    On the PC, the user can specify a default device by
-    setting an environment variable. For example, to use device #1.
-
-        set PM_RECOMMENDED_OUTPUT_DEVICE=1
-    
-    The user should first determine the available device ID by using
-    the supplied application "testin" or "testout".
-
-    In general, the registry is a better place for this kind of info,
-    and with USB devices that can come and go, using integers is not
-    very reliable for device identification. Under Windows, if
-    PM_RECOMMENDED_OUTPUT_DEVICE (or PM_RECOMMENDED_INPUT_DEVICE) is
-    *NOT* found in the environment, then the default device is obtained
-    by looking for a string in the registry under:
-        HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Input_Device
-    and HKEY_LOCAL_MACHINE/SOFTWARE/PortMidi/Recommended_Output_Device
-    for a string. The number of the first device with a substring that
-    matches the string exactly is returned. For example, if the string
-    in the registry is "USB", and device 1 is named 
-    "In USB MidiSport 1x1", then that will be the default
-    input because it contains the string "USB".
-
-    In addition to the name, PmDeviceInfo has the member "interf", which
-    is the interface name. (The "interface" is the underlying software
-    system or API used by PortMidi to access devices. Examples are 
-    MMSystem, DirectX (not implemented), ALSA, OSS (not implemented), etc.)
-    At present, the only Win32 interface is "MMSystem", the only Linux
-    interface is "ALSA", and the only Max OS X interface is "CoreMIDI".
-    To specify both the interface and the device name in the registry,
-    separate the two with a comma and a space, e.g.:
-        MMSystem, In USB MidiSport 1x1
-    In this case, the string before the comma must be a substring of
-    the "interf" string, and the string after the space must be a 
-    substring of the "name" name string in order to match the device.
-
-    Note: in the current release, the default is simply the first device
-    (the input or output device with the lowest PmDeviceID).
-*/
-PmDeviceID Pm_GetDefaultInputDeviceID( void );
-PmDeviceID Pm_GetDefaultOutputDeviceID( void );
-
-/*
-    PmTimestamp is used to represent a millisecond clock with arbitrary
-    start time. The type is used for all MIDI timestampes and clocks.
-*/
-typedef long PmTimestamp;
-typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
-
-/* TRUE if t1 before t2 */
-#define PmBefore(t1,t2) ((t1-t2) < 0)
-
-/*
-    Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure
-    referring to the device specified by id.
-    If id is out of range the function returns NULL.
-
-    The returned structure is owned by the PortMidi implementation and must
-    not be manipulated or freed. The pointer is guaranteed to be valid
-    between calls to Pm_Initialize() and Pm_Terminate().
-*/
-const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id );
-
-/*
-    Pm_OpenInput() and Pm_OpenOutput() open devices.
-
-    stream is the address of a PortMidiStream pointer which will receive
-    a pointer to the newly opened stream.
-
-    inputDevice is the id of the device used for input (see PmDeviceID above).
-
-    inputDriverInfo is a pointer to an optional driver specific data structure
-    containing additional information for device setup or handle processing.
-    inputDriverInfo is never required for correct operation. If not used
-    inputDriverInfo should be NULL.
-
-    outputDevice is the id of the device used for output (see PmDeviceID above.)
-
-    outputDriverInfo is a pointer to an optional driver specific data structure
-    containing additional information for device setup or handle processing.
-    outputDriverInfo is never required for correct operation. If not used
-    outputDriverInfo should be NULL.
-
-    For input, the buffersize specifies the number of input events to be 
-    buffered waiting to be read using Pm_Read(). For output, buffersize 
-    specifies the number of output events to be buffered waiting for output. 
-    (In some cases -- see below -- PortMidi does not buffer output at all
-    and merely passes data to a lower-level API, in which case buffersize
-    is ignored.)
-    
-    latency is the delay in milliseconds applied to timestamps to determine 
-    when the output should actually occur. (If latency is < 0, 0 is assumed.) 
-    If latency is zero, timestamps are ignored and all output is delivered
-    immediately. If latency is greater than zero, output is delayed until the
-    message timestamp plus the latency. (NOTE: the time is measured relative 
-    to the time source indicated by time_proc. Timestamps are absolute,
-    not relative delays or offsets.) In some cases, PortMidi can obtain
-    better timing than your application by passing timestamps along to the
-    device driver or hardware. Latency may also help you to synchronize midi
-    data to audio data by matching midi latency to the audio buffer latency.
-
-    time_proc is a pointer to a procedure that returns time in milliseconds. It
-    may be NULL, in which case a default millisecond timebase (PortTime) is 
-    used. If the application wants to use PortTime, it should start the timer
-    (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the
-    application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput,
-    it may get a ptAlreadyStarted error from Pt_Start, and the application's
-    preferred time resolution and callback function will be ignored.
-    time_proc result values are appended to incoming MIDI data, and time_proc
-    times are used to schedule outgoing MIDI data (when latency is non-zero).
-
-    time_info is a pointer passed to time_proc.
-
-    Example: If I provide a timestamp of 5000, latency is 1, and time_proc
-    returns 4990, then the desired output time will be when time_proc returns
-    timestamp+latency = 5001. This will be 5001-4990 = 11ms from now.
-
-    return value:
-    Upon success Pm_Open() returns PmNoError and places a pointer to a
-    valid PortMidiStream in the stream argument.
-    If a call to Pm_Open() fails a nonzero error code is returned (see
-    PMError above) and the value of port is invalid.
-
-    Any stream that is successfully opened should eventually be closed
-    by calling Pm_Close().
-
-*/
-PmError Pm_OpenInput( PortMidiStream** stream,
-                PmDeviceID inputDevice,
-                void *inputDriverInfo,
-                long bufferSize,
-                PmTimeProcPtr time_proc,
-                void *time_info );
-
-PmError Pm_OpenOutput( PortMidiStream** stream,
-                PmDeviceID outputDevice,
-                void *outputDriverInfo,
-                long bufferSize,
-                PmTimeProcPtr time_proc,
-                void *time_info,
-                long latency );
-
-/* 
-    Pm_SetFilter() sets filters on an open input stream to drop selected
-    input types. By default, only active sensing messages are filtered.
-    To prohibit, say, active sensing and sysex messages, call
-    Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
-
-    Filtering is useful when midi routing or midi thru functionality is being
-    provided by the user application.
-    For example, you may want to exclude timing messages (clock, MTC, start/stop/continue),
-    while allowing note-related messages to pass.
-    Or you may be using a sequencer or drum-machine for MIDI clock information but want to
-    exclude any notes it may play.
- */
-
-/* filter active sensing messages (0xFE): */
-#define PM_FILT_ACTIVE (1 << 0x0E)
-/* filter system exclusive messages (0xF0): */
-#define PM_FILT_SYSEX (1 << 0x00)
-/* filter MIDI clock message (0xF8) */
-#define PM_FILT_CLOCK (1 << 0x08)
-/* filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
-#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
-/* filter tick messages (0xF9) */
-#define PM_FILT_TICK (1 << 0x09)
-/* filter undefined FD messages */
-#define PM_FILT_FD (1 << 0x0D)
-/* filter undefined real-time messages */
-#define PM_FILT_UNDEFINED PM_FILT_FD
-/* filter reset messages (0xFF) */
-#define PM_FILT_RESET (1 << 0x0F)
-/* filter all real-time messages */
-#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
-    PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
-/* filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
-#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
-/* filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
-#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
-/* per-note aftertouch (0xA0-0xAF) */
-#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
-/* filter both channel and poly aftertouch */
-#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH)
-/* Program changes (0xC0-0xCF) */
-#define PM_FILT_PROGRAM (1 << 0x1C)
-/* Control Changes (CC's) (0xB0-0xBF)*/
-#define PM_FILT_CONTROL (1 << 0x1B)
-/* Pitch Bender (0xE0-0xEF*/
-#define PM_FILT_PITCHBEND (1 << 0x1E)
-/* MIDI Time Code (0xF1)*/
-#define PM_FILT_MTC (1 << 0x01)
-/* Song Position (0xF2) */
-#define PM_FILT_SONG_POSITION (1 << 0x02)
-/* Song Select (0xF3)*/
-#define PM_FILT_SONG_SELECT (1 << 0x03)
-/* Tuning request (0xF6)*/
-#define PM_FILT_TUNE (1 << 0x06)
-/* All System Common messages (mtc, song position, song select, tune request) */
-#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE)
-
-
-PmError Pm_SetFilter( PortMidiStream* stream, long filters );
-
-/*
-    Pm_SetChannelMask() filters incoming messages based on channel.
-    The mask is a 16-bit bitfield corresponding to appropriate channels
-    The Pm_Channel macro can assist in calling this function.
-    i.e. to set receive only input on channel 1, call with
-    Pm_SetChannelMask(Pm_Channel(1));
-    Multiple channels should be OR'd together, like
-    Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
-
-    All channels are allowed by default
-*/
-#define Pm_Channel(channel) (1<<(channel))
-
-PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
-
-/*
-    Pm_Abort() terminates outgoing messages immediately
-    The caller should immediately close the output port;
-    this call may result in transmission of a partial midi message.
-    There is no abort for Midi input because the user can simply
-    ignore messages in the buffer and close an input device at
-    any time.
- */
-PmError Pm_Abort( PortMidiStream* stream );
-     
-/*
-    Pm_Close() closes a midi stream, flushing any pending buffers.
-    (PortMidi attempts to close open streams when the application 
-    exits -- this is particularly difficult under Windows.)
-*/
-PmError Pm_Close( PortMidiStream* stream );
-
-/*
-    Pm_Message() encodes a short Midi message into a long word. If data1
-    and/or data2 are not present, use zero.
-
-    Pm_MessageStatus(), Pm_MessageData1(), and 
-    Pm_MessageData2() extract fields from a long-encoded midi message.
-*/
-#define Pm_Message(status, data1, data2) \
-         ((((data2) << 16) & 0xFF0000) | \
-          (((data1) << 8) & 0xFF00) | \
-          ((status) & 0xFF))
-#define Pm_MessageStatus(msg) ((msg) & 0xFF)
-#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
-#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
-
-/* All midi data comes in the form of PmEvent structures. A sysex
-   message is encoded as a sequence of PmEvent structures, with each
-   structure carrying 4 bytes of the message, i.e. only the first
-   PmEvent carries the status byte.
-
-   Note that MIDI allows nested messages: the so-called "real-time" MIDI 
-   messages can be inserted into the MIDI byte stream at any location, 
-   including within a sysex message. MIDI real-time messages are one-byte
-   messages used mainly for timing (see the MIDI spec). PortMidi retains 
-   the order of non-real-time MIDI messages on both input and output, but 
-   it does not specify exactly how real-time messages are processed. This
-   is particulary problematic for MIDI input, because the input parser 
-   must either prepare to buffer an unlimited number of sysex message 
-   bytes or to buffer an unlimited number of real-time messages that 
-   arrive embedded in a long sysex message. To simplify things, the input
-   parser is allowed to pass real-time MIDI messages embedded within a 
-   sysex message, and it is up to the client to detect, process, and 
-   remove these messages as they arrive.
-
-   When receiving sysex messages, the sysex message is terminated
-   by either an EOX status byte (anywhere in the 4 byte messages) or
-   by a non-real-time status byte in the low order byte of the message.
-   If you get a non-real-time status byte but there was no EOX byte, it 
-   means the sysex message was somehow truncated. This is not
-   considered an error; e.g., a missing EOX can result from the user
-   disconnecting a MIDI cable during sysex transmission.
-
-   A real-time message can occur within a sysex message. A real-time 
-   message will always occupy a full PmEvent with the status byte in 
-   the low-order byte of the PmEvent message field. (This implies that
-   the byte-order of sysex bytes and real-time message bytes may not
-   be preserved -- for example, if a real-time message arrives after
-   3 bytes of a sysex message, the real-time message will be delivered
-   first. The first word of the sysex message will be delivered only
-   after the 4th byte arrives, filling the 4-byte PmEvent message field.
-   
-   The timestamp field is observed when the output port is opened with
-   a non-zero latency. A timestamp of zero means "use the current time",
-   which in turn means to deliver the message with a delay of
-   latency (the latency parameter used when opening the output port.)
-   Do not expect PortMidi to sort data according to timestamps -- 
-   messages should be sent in the correct order, and timestamps MUST 
-   be non-decreasing. See also "Example" for Pm_OpenOutput() above.
-
-   A sysex message will generally fill many PmEvent structures. On 
-   output to a PortMidiStream with non-zero latency, the first timestamp
-   on sysex message data will determine the time to begin sending the 
-   message. PortMidi implementations may ignore timestamps for the 
-   remainder of the sysex message. 
-   
-   On input, the timestamp ideally denotes the arrival time of the 
-   status byte of the message. The first timestamp on sysex message 
-   data will be valid. Subsequent timestamps may denote 
-   when message bytes were actually received, or they may be simply 
-   copies of the first timestamp.
-
-   Timestamps for nested messages: If a real-time message arrives in 
-   the middle of some other message, it is enqueued immediately with 
-   the timestamp corresponding to its arrival time. The interrupted 
-   non-real-time message or 4-byte packet of sysex data will be enqueued 
-   later. The timestamp of interrupted data will be equal to that of
-   the interrupting real-time message to insure that timestamps are
-   non-decreasing.
- */
-typedef long PmMessage;
-typedef struct {
-    PmMessage      message;
-    PmTimestamp    timestamp;
-} PmEvent;
-
-/*
-    Pm_Read() retrieves midi data into a buffer, and returns the number
-    of events read. Result is a non-negative number unless an error occurs, 
-    in which case a PmError value will be returned.
-
-    Buffer Overflow
-
-    The problem: if an input overflow occurs, data will be lost, ultimately 
-    because there is no flow control all the way back to the data source. 
-    When data is lost, the receiver should be notified and some sort of 
-    graceful recovery should take place, e.g. you shouldn't resume receiving 
-    in the middle of a long sysex message.
-
-    With a lock-free fifo, which is pretty much what we're stuck with to 
-    enable portability to the Mac, it's tricky for the producer and consumer 
-    to synchronously reset the buffer and resume normal operation.
-
-    Solution: the buffer managed by PortMidi will be flushed when an overflow
-    occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow)
-    and ordinary processing resumes as soon as a new message arrives. The
-    remainder of a partial sysex message is not considered to be a "new
-    message" and will be flushed as well.
-
-*/
-int Pm_Read( PortMidiStream *stream, PmEvent *buffer, long length );
-
-/*
-    Pm_Poll() tests whether input is available, 
-    returning TRUE, FALSE, or an error value.
-*/
-PmError Pm_Poll( PortMidiStream *stream);
-
-/* 
-    Pm_Write() writes midi data from a buffer. This may contain:
-        - short messages 
-    or 
-        - sysex messages that are converted into a sequence of PmEvent
-          structures, e.g. sending data from a file or forwarding them
-          from midi input.
-
-    Use Pm_WriteSysEx() to write a sysex message stored as a contiguous 
-    array of bytes.
-
-    Sysex data may contain embedded real-time messages.
-*/
-PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, long length );
-
-/*
-    Pm_WriteShort() writes a timestamped non-system-exclusive midi message.
-    Messages are delivered in order as received, and timestamps must be 
-    non-decreasing. (But timestamps are ignored if the stream was opened
-    with latency = 0.)
-*/
-PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, long msg);
-
-/*
-    Pm_WriteSysEx() writes a timestamped system-exclusive midi message.
-*/
-PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg);
-
-
-#ifdef __cplusplus
-}
-#endif /* __cplusplus */
-#endif /* PORT_MIDI_H */
+#ifndef PORTMIDI_PORTMIDI_H
+#define PORTMIDI_PORTMIDI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/*
+ * PortMidi Portable Real-Time MIDI Library
+ * PortMidi API Header File
+ * Latest version available at: http://sourceforge.net/projects/portmedia
+ *
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
+ * Copyright (c) 2001-2006 Roger B. Dannenberg
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * The text above constitutes the entire PortMidi license; however, 
+ * the PortMusic community also makes the following non-binding requests:
+ *
+ * Any person wishing to distribute modifications to the Software is
+ * requested to send the modifications to the original developer so that
+ * they can be incorporated into the canonical version. It is also
+ * requested that these non-binding requests be included along with the 
+ * license above.
+ */
+
+/* CHANGELOG FOR PORTMIDI
+ *     (see ../CHANGELOG.txt)
+ *
+ * NOTES ON HOST ERROR REPORTING: 
+ *
+ *    PortMidi errors (of type PmError) are generic,
+ *    system-independent errors.  When an error does not map to one of
+ *    the more specific PmErrors, the catch-all code pmHostError is
+ *    returned. This means that PortMidi has retained a more specific
+ *    system-dependent error code. The caller can get more information
+ *    by calling Pm_GetHostErrorText() to get a text string describing
+ *    the error. Host errors can arise asynchronously from callbacks,
+ *    * so there is no specific return code. Asynchronous errors are
+ *    checked and reported by Pm_Poll. You can also check by calling
+ *    Pm_HasHostError().  If this returns TRUE, Pm_GetHostErrorText()
+ *    will return a text description of the error.
+ *
+ * NOTES ON COMPILE-TIME SWITCHES
+ *
+ *    DEBUG assumes stdio and a console. Use this if you want
+ *        automatic, simple error reporting, e.g. for prototyping. If
+ *        you are using MFC or some other graphical interface with no
+ *        console, DEBUG probably should be undefined.
+ *    PM_CHECK_ERRORS more-or-less takes over error checking for
+ *        return values, stopping your program and printing error
+ *        messages when an error occurs. This also uses stdio for
+ *        console text I/O. You can selectively disable this error
+ *        checking by declaring extern int pm_check_errors; and 
+ *        setting pm_check_errors = FALSE; You can also reenable.
+ */
+/** 
+    \defgroup grp_basics Basic Definitions
+    @{
+*/
+
+#include <stdint.h>
+
+#ifdef _WINDLL
+#define PMEXPORT __declspec(dllexport)
+#else
+#define PMEXPORT 
+#endif
+
+#ifndef FALSE
+    #define FALSE 0
+#endif
+#ifndef TRUE
+    #define TRUE 1
+#endif
+
+/** default size of buffers for sysex transmission: */
+#define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024
+
+
+typedef enum {
+    pmNoError = 0, /**< Normal return value indicating no error. */
+    pmNoData = 0, /**< @brief No error, also indicates no data available.
+                   * Use this constant where a value greater than zero would
+                   * indicate data is available.
+                   */
+    pmGotData = 1, /**< A "no error" return also indicating data available. */
+    pmHostError = -10000,  /**< error was returned from the system level below
+                                PortMidi. See ##Pm_GetHostErrorText() */
+    pmInvalidDeviceId, /**< Out of range or 
+                        * output device when input is requested or 
+                        * input device when output is requested or
+                        * device is already opened. 
+                        */
+    pmInsufficientMemory,
+    pmBufferTooSmall,
+    pmBufferOverflow, /**< buffer overflow (see #Pm_Read) */
+    pmBadPtr, /**< #PortMidiStream parameter is NULL or
+               * stream is not opened or
+               * stream is output when input is required or
+               * stream is input when output is required. */
+    pmBadData, /**< Illegal midi data, e.g., missing EOX. */
+    pmInternalError,
+    pmBufferMaxSize, /**< Buffer is already as large as it can be. */
+    pmNotImplemented, /**< The function is not implemented, nothing was done. */
+    pmInterfaceNotSupported, /**< The requested interface is not supported. */
+    pmNameConflict, /**< Cannot create virtual device because name is taken. */
+    pmDeviceRemoved  /**< Output attempted after (USB) device was removed. */
+    /* NOTE: If you add a new error type, you must update Pm_GetErrorText(). */
+} PmError; /**< @brief @enum PmError PortMidi error code; a common return type. 
+            * No error is indicated by zero; errors are indicated by < 0.
+            */
+
+/**
+    Pm_Initialize() is the library initialization function - call this before
+    using the library.
+
+    *NOTE:* PortMidi scans for available devices when #Pm_Initialize
+    is called.  To observe subsequent changes in the available
+    devices, you must shut down PortMidi by calling #Pm_Terminate and
+    then restart by calling #Pm_Initialize again. *IMPORTANT*: On
+    MacOS, #Pm_Initialize *must* always be called on the same
+    thread. Otherwise, changes in the available MIDI devices will
+    *not* be seen by PortMidi. As an example, if you start PortMidi in
+    a thread for processing MIDI, do not try to rescan devices by
+    calling #Pm_Initialize in a GUI thread. Instead, start PortMidi
+    the first time and every time in the GUI thread.  Alternatively,
+    let the GUI request a restart in the MIDI thread.  (These
+    restrictions only apply to macOS.) Speaking of threads, on all
+    platforms, you are allowed to call #Pm_Initialize in one thread,
+    yet send MIDI or poll for incoming MIDI in another
+    thread. However, PortMidi is not "thread safe," which means you
+    cannot allow threads to call PortMidi functions concurrently.
+
+    @return pmNoError.
+
+    PortMidi is designed to support multiple interfaces (such as ALSA,
+    CoreMIDI and WinMM). It is possible to return pmNoError because there
+    are no supported interfaces. In that case, zero devices will be 
+    available.
+*/
+PMEXPORT PmError Pm_Initialize(void);
+
+/**
+    Pm_Terminate() is the library termination function - call this after
+    using the library.
+*/
+PMEXPORT PmError Pm_Terminate(void);
+
+/** Represents an open MIDI device. */
+typedef void PortMidiStream;
+
+/** A shorter form of #PortMidiStream. */
+#define PmStream PortMidiStream
+
+/** Test whether stream has a pending host error. Normally, the client
+    finds out about errors through returned error codes, but some
+    errors can occur asynchronously where the client does not
+    explicitly call a function, and therefore cannot receive an error
+    code.  The client can test for a pending error using
+    #Pm_HasHostError(). If true, the error can be accessed by calling
+    #Pm_GetHostErrorText().  Pm_Poll() is similar to Pm_HasHostError(),
+    but if there is no error, it will return TRUE (1) if there is a
+    pending input message.
+*/
+PMEXPORT int Pm_HasHostError(PortMidiStream * stream);
+
+
+/** Translate portmidi error number into human readable message.
+    These strings are constants (set at compile time) so client has 
+    no need to allocate storage.
+*/
+PMEXPORT const char *Pm_GetErrorText(PmError errnum);
+
+/** Translate portmidi host error into human readable message.
+    These strings are computed at run time, so client has to allocate storage.
+    After this routine executes, the host error is cleared. 
+*/
+PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len);
+
+/** Any host error msg has at most this many characters, including EOS. */
+#define PM_HOST_ERROR_MSG_LEN 256u 
+
+/** Devices are represented as small integers. Device ids range from 0
+    to Pm_CountDevices()-1. Pm_GetDeviceInfo() is used to get information
+    about the device, and Pm_OpenInput() and PmOpenOutput() are used to
+    open the device.
+*/
+typedef int PmDeviceID;
+
+/** This PmDeviceID (constant) value represents no device and may be
+    returned by  Pm_GetDefaultInputDeviceID() or
+    Pm_GetDefaultOutputDeviceID() if no default exists.
+*/
+#define pmNoDevice -1
+
+/** MIDI device information is returned in this structure, which is
+    owned by PortMidi and read-only to applications. See Pm_GetDeviceInfo().
+*/
+typedef struct {
+    int structVersion; /**< @brief this internal structure version */ 
+    const char *interf; /**< @brief underlying MIDI API, e.g. 
+                                    "MMSystem" or "DirectX" */
+    char *name;   /**< @brief device name, e.g. "USB MidiSport 1x1" */
+    int input; /**< @brief true iff input is available */
+    int output; /**< @brief true iff output is available */
+    int opened; /**< @brief used by generic PortMidi for error checking */
+    int is_virtual; /**< @brief true iff this is/was a virtual device */
+} PmDeviceInfo;
+#define PM_DEVICEINFO_VERS 200
+
+/** Version number of PmDeviceInfo, stored in #PmDeviceInfo::structVersion 
+    field */
+#define PM_DEVICEINFO_VERS 200
+
+/** MIDI system-dependent device or driver info is passed in this
+    structure, which is owned by the caller.
+*/
+enum PmSysDepPropertyKey {
+    pmKeyNone = 0,  /**< a "noop" key value */
+    /** CoreMIDI Manufacturer name, value is string */
+    pmKeyCoreMidiManufacturer = 1,
+    /** Linux ALSA snd_seq_port_info_set_name, value is a string. Can be 
+        passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput when opening
+        a device. The created port will be named accordingly and will be 
+        visible for externally made connections (subscriptions). (Linux ALSA
+        ports are always enabled for this, but only get application-specific
+        names if you give it one.) This key/value is ignored when opening
+        virtual ports, which are named when they are created.) */
+    pmKeyAlsaPortName = 2,
+    /** Linux ALSA snd_seq_set_client_name, value is a string.
+        Can be passed in PmSysDepInfo to Pm_OpenInput or Pm_OpenOutput.
+        Pm_CreateVirtualInput or Pm_CreateVirtualOutput. Will override
+        any previously set client name and applies to all ports. */
+    pmKeyAlsaClientName = 3
+    /* if system-dependent code introduces more options, register
+       the key here to avoid conflicts. */
+};
+#define PM_SYSDEPINFO_VERS 210
+
+/** System-dependent information can be passed when creating and opening
+    ports using this data structure, which stores alternating keys and
+    values (addresses). See `pm_test/sendvirtual.c`, `pm_test/recvvirtual.c`,
+    and `pm_test/testio.c` for examples.
+ */
+typedef struct {
+    int structVersion;  /**< this structure version */
+    int length;  /**< number of properties in this structure */
+    struct {
+        enum PmSysDepPropertyKey key;
+        const void *value;
+    } properties[];  /**< array of key/value pairs */
+} PmSysDepInfo;
+
+/** Version number of PmSysDepInfo, stored in #PmSysDepInfo::structVersion
+    field */
+#define PM_SYSDEPINFO_VERS 210
+
+
+
+/** Get devices count, ids range from 0 to Pm_CountDevices()-1. */
+PMEXPORT int Pm_CountDevices(void);
+
+/**
+    Return the default device ID or pmNoDevice if there are no devices.
+    The result (but not pmNoDevice) can be passed to Pm_OpenMidi().
+    
+    The use of these functions is not recommended. There is no natural
+    "default device" on any system, so defaults must be set by users.
+    (Currently, PortMidi just returns the first device it finds as
+    "default", so if there *is* a default, implementors should use
+    pm_add_device to add system default input and output devices
+    first.)
+
+    The recommended solution is pass the burden to applications. It is
+    easy to scan devices with PortMidi and build a device menu, and to
+    save menu selections in application preferences for next
+    time. This is my recommendation for any GUI program. For simple
+    command-line applications and utilities, see pm_test where all the
+    test programs now accept device numbers on the command line and/or
+    prompt for their entry.
+
+    On linux, you can create virtual ports and use an external program
+    to set up inter-application and device connections.
+
+    Some advice for preferences: MIDI devices used to be built-in or
+    plug-in cards, so the numbers rarely changed. Now MIDI devices are
+    often plug-in USB devices, so device numbers change, and you
+    probably need to design to reinitialize PortMidi to rescan
+    devices. MIDI is pretty stateless, so this isn't a big problem,
+    although it means you cannot find a new device while playing or
+    recording MIDI.
+
+    Since device numbering can change whenever a USB device is plugged
+    in, preferences should record *names* of devices rather than
+    device numbers. It is simple enough to use string matching to find
+    a prefered device, so PortMidi does not provide any built-in
+    lookup function.
+*/
+PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID(void);
+
+/** @brief see PmDeviceID Pm_GetDefaultInputDeviceID() */
+PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID(void);
+
+/** Find a device that matches a pattern. 
+
+    @param pattern a substring of the device name, or if the pattern
+    contains the two-character separator ", ", then the first part of
+    the pattern represents a device interface substring and the second
+    part after the separator represents a device name substring. 
+
+    @param is_input restricts the search to an input when true, or an
+    output when false.
+
+    @return the number of the first device whose device interface
+    contains the interface pattern (if any), whose device name
+    contains the name pattern, and whose direction (input or output)
+    matches the \p is_input parameter. If no match is found, #pmNoDevice
+    (-1) is returned.
+*/
+PMEXPORT PmDeviceID Pm_FindDevice(char *pattern, int is_input);
+
+
+/** Represents a millisecond clock with arbitrary start time. 
+    This type is used for all MIDI timestamps and clocks.
+*/
+typedef int32_t PmTimestamp;
+
+/** @brief function pointer to retrieve the time in milliseconds.
+    This is the time used in all PortMidi timestamps. The use of
+    a function pointer allows the user to derive time from an audio
+    sample count to synchronize MIDI to audio, or from a remote
+    machine through clock synchronization protocols to synchronize
+    MIDI across multiple machine. The time is independent of the
+    internal system timestamps (e.g., MacOS CoreMIDI uses its own
+    clock and timestamp representation, and PortMidi translates
+    between different clocks, which need not be synchronized.) */
+typedef PmTimestamp (*PmTimeProcPtr)(void *time_info);
+
+/** TRUE if t1 before t2 */
+#define PmBefore(t1,t2) (((t1)-(t2)) < 0)
+/** @} */
+/** 
+    \defgroup grp_device Input/Output Devices Handling
+    @{
+*/
+/** Get a PmDeviceInfo structure describing a MIDI device.
+    
+    @param id the device to be queried.
+
+    If \p id is out of range or if the device designates a deleted
+    virtual device, the function returns NULL.
+
+    The returned structure is owned by the PortMidi implementation and
+    must not be manipulated or freed. The pointer is guaranteed to be
+    valid between calls to Pm_Initialize() and Pm_Terminate().
+*/
+PMEXPORT const PmDeviceInfo *Pm_GetDeviceInfo(PmDeviceID id);
+
+/** Open a MIDI device for input.
+
+    @param stream the address of a #PortMidiStream pointer which will
+    receive a pointer to the newly opened stream.
+
+    @param inputDevice the ID of the device to be opened (see #PmDeviceID).
+
+    @param inputSysDepInfo a pointer to an optional system-dependent
+    data structure (a #PmSysDepInfo struct) containing additional
+    information for device setup or handle processing. This parameter
+    is never required for correct operation. If not used, specify
+    NULL.  Declared `void *` here for backward compatibility. Note that
+    with Linux ALSA, you can use this parameter to specify a client name
+    and port name.
+
+    @param bufferSize the number of input events to be buffered
+    waiting to be read using #Pm_Read(). Messages will be lost if the
+    number of unread messages exceeds this value.
+
+    @param time_proc (address of) a procedure that returns time in
+    milliseconds. It may be NULL, in which case a default millisecond
+    timebase (PortTime) is used. If the application wants to use
+    PortTime, it should start the timer (call Pt_Start) before calling
+    Pm_OpenInput or Pm_OpenOutput. If the application tries to start
+    the timer *after* Pm_OpenInput or Pm_OpenOutput, it may get a
+    ptAlreadyStarted error from Pt_Start, and the application's
+    preferred time resolution and callback function will be ignored.
+    \p time_proc result values are appended to incoming MIDI data,
+    normally by mapping system-provided timestamps to the \p time_proc
+    timestamps to maintain the precision of system-provided
+    timestamps.
+
+    @param time_info is a pointer passed to time_proc.
+
+    @return #pmNoError and places a pointer to a valid
+    #PortMidiStream in the stream argument.  If the open operation
+    fails, a nonzero error code is returned (see #PmError) and
+    the value of stream is invalid.
+
+    Any stream that is successfully opened should eventually be closed
+    by calling Pm_Close().
+*/
+PMEXPORT PmError Pm_OpenInput(PortMidiStream** stream,
+                PmDeviceID inputDevice,
+                void *inputSysDepInfo,
+                int32_t bufferSize,
+                PmTimeProcPtr time_proc,
+                void *time_info);
+
+/** Open a MIDI device for output.
+
+    @param stream the address of a #PortMidiStream pointer which will
+    receive a pointer to the newly opened stream.
+
+    @param outputDevice the ID of the device to be opened (see #PmDeviceID).
+
+    @param outputSysDepInfo a pointer to an optional system-specific
+    data structure (a #PmSysDepInfo struct) containing additional
+    information for device setup or handle processing. This parameter
+    is never required for correct operation. If not used, specify
+    NULL. Declared `void *` here for backward compatibility. Note that
+    with Linux ALSA, you can use this parameter to specify a client name
+    and port name.
+
+    @param bufferSize the number of output events to be buffered
+    waiting for output. In some cases -- see below -- PortMidi does
+    not buffer output at all and merely passes data to a lower-level
+    API, in which case \p bufferSize is ignored. Since MIDI speeds now
+    vary from 1 to 50 or more messages per ms (over USB), put some
+    thought into this number. E.g. if latency is 20ms and you want to
+    burst 100 messages in that time (5000 messages per second), you
+    should set \p bufferSize to at least 100. The default on Windows
+    assumes an average rate of 500 messages per second and in this
+    example, output would be slowed waiting for free buffers.
+    
+    @param latency the delay in milliseconds applied to timestamps
+    to determine when the output should actually occur. (If latency is
+    < 0, 0 is assumed.)  If latency is zero, timestamps are ignored
+    and all output is delivered immediately. If latency is greater
+    than zero, output is delayed until the message timestamp plus the
+    latency. (NOTE: the time is measured relative to the time source
+    indicated by time_proc. Timestamps are absolute, not relative
+    delays or offsets.) In some cases, PortMidi can obtain better
+    timing than your application by passing timestamps along to the
+    device driver or hardware, so the best strategy to minimize jitter
+    is: wait until the real time to send the message, compute the
+    message, attach the *ideal* output time (not the current real
+    time, because some time may have elapsed), and send the
+    message. The \p latency will be added to the timestamp, and
+    provided the elapsed computation time has not exceeded \p latency,
+    the message will be delivered according to the timestamp. If the
+    real time is already past the timestamp, the message will be
+    delivered as soon as possible. Latency may also help you to
+    synchronize MIDI data to audio data by matching \p latency to the
+    audio buffer latency.
+
+    @param time_proc (address of) a pointer to a procedure that
+    returns time in milliseconds. It may be NULL, in which case a
+    default millisecond timebase (PortTime) is used. If the
+    application wants to use PortTime, it should start the timer (call
+    Pt_Start) before calling #Pm_OpenInput or #Pm_OpenOutput. If the
+    application tries to start the timer *after* #Pm_OpenInput or
+    #Pm_OpenOutput, it may get a #ptAlreadyStarted error from #Pt_Start,
+    and the application's preferred time resolution and callback
+    function will be ignored.  \p time_proc times are used to schedule
+    outgoing MIDI data (when latency is non-zero), usually by mapping
+    from time_proc timestamps to internal system timestamps to
+    maintain the precision of system-supported timing.
+
+    @param time_info a pointer passed to time_proc.
+
+    @return #pmNoError and places a pointer to a valid #PortMidiStream
+    in the stream argument.  If the operation fails, a nonzero error
+    code is returned (see PMError) and the value of \p stream is
+    invalid.
+
+    Note: ALSA appears to have a fixed-size priority queue for timed
+    output messages. Testing indicates the queue can hold a little
+    over 400 3-byte MIDI messages. Thus, you can send 10,000
+    messages/second if the latency is 30ms (30ms * 10000 msgs/sec *
+    0.001 sec/ms = 300 msgs), but not if the latency is 50ms
+    (resulting in about 500 pending messages, which is greater than
+    the 400 message limit). Since timestamps in ALSA are relative,
+    they are of less value than absolute timestamps in macOS and
+    Windows. This is a limitation of ALSA and apparently a design
+    flaw.
+
+    Example 1: If I provide a timestamp of 5000, latency is 1, and
+    time_proc returns 4990, then the desired output time will be when
+    time_proc returns timestamp+latency = 5001. This will be 5001-4990
+    = 11ms from now.
+
+    Example 2: If I want to send at exactly 5010, and latency is 10, I
+    should wait until 5000, compute the messages and provide a
+    timestamp of 5000. As long as computation takes less than 10ms,
+    the message will be delivered at time 5010.
+
+    Example 3 (recommended): It is often convenient to ignore latency.
+    E.g. if a sequence says to output at time 5010, just wait until
+    5010, compute the message and use 5010 for the timestamp. Delivery
+    will then be at 5010+latency, but unless you are synchronizing to
+    something else, the absolute delay by latency will not matter.
+
+    Any stream that is successfully opened should eventually be closed
+    by calling Pm_Close().
+*/
+PMEXPORT PmError Pm_OpenOutput(PortMidiStream** stream,
+                PmDeviceID outputDevice,
+                void *outputSysDepInfo,
+                int32_t bufferSize,
+                PmTimeProcPtr time_proc,
+                void *time_info,
+                int32_t latency);
+
+/** Create  a virtual input device.
+
+    @param name gives the virtual device name, which is visible to
+    other applications.
+
+    @param interf is the interface (System API) used to create the
+    device Default interfaces are "MMSystem", "CoreMIDI" and
+    "ALSA". Currently, these are the only ones implemented, but future
+    implementations could support DirectMusic, Jack, sndio, or others.
+
+    @param sysDepInfo contains interface-dependent additional
+    information (a #PmSysDepInfo struct), e.g., hints or options. This
+    parameter is never required for correct operation. If not used,
+    specify NULL. Declared `void *` here for backward compatibility.
+
+    @return a device ID or #pmNameConflict (\p name is invalid or
+    already exists) or #pmInterfaceNotSupported (\p interf is does not
+    match a supported interface).
+
+    The created virtual device appears to other applications as if it
+    is an output device. The device must be opened to obtain a stream
+    and read from it.
+
+    Virtual devices are not supported by Windows (Multimedia API). Calls
+    on Windows do nothing except return #pmNotImplemented.
+*/
+PMEXPORT PmError Pm_CreateVirtualInput(const char *name,
+                                       const char *interf,
+                                       void *sysDepInfo);
+
+/** Create a virtual output device.
+
+    @param name gives the virtual device name, which is visible to
+    other applications.
+
+    @param interf is the interface (System API) used to create the
+    device Default interfaces are "MMSystem", "CoreMIDI" and
+    "ALSA". Currently, these are the only ones implemented, but future
+    implementations could support DirectMusic, Jack, sndio, or others.
+
+    @param sysDepInfo contains interface-dependent additional
+    information (a #PmSysDepInfo struct), e.g., hints or options. This
+    parameter is never required for correct operation. If not used,
+    specify NULL. Declared `void *` here for backward compatibility.
+
+    @return a device ID or #pmInvalidDeviceId (\p name is invalid or
+    already exists) or #pmInterfaceNotSupported (\p interf is does not
+    match a supported interface).
+
+    The created virtual device appears to other applications as if it
+    is an input device. The device must be opened to obtain a stream
+    and write to it.
+
+    Virtual devices are not supported by Windows (Multimedia API). Calls
+    on Windows do nothing except return #pmNotImplemented.
+*/
+PMEXPORT PmError Pm_CreateVirtualOutput(const char *name,
+                                        const char *interf,
+                                        void *sysDepInfo);
+
+/** Remove a virtual device.
+
+   @param device a device ID (small integer) designating the device.
+
+   The device is removed; other applications can no longer see or open
+   this virtual device, which may be either for input or output. The
+   device must not be open. The device ID may be reused, but existing
+   devices are not renumbered. This means that the device ID could be
+   in the range from 0 to #Pm_CountDevices(), yet the device ID does
+   not designate a device. In that case, passing the ID to
+   #Pm_GetDeviceInfo() will return NULL.
+
+   @return #pmNoError if the device was deleted or #pmInvalidDeviceId
+   if the device is open, already deleted, or \p device is out of
+   range.
+*/
+PMEXPORT PmError Pm_DeleteVirtualDevice(PmDeviceID device);
+  /** @} */
+
+/**
+   @defgroup grp_events_filters Events and Filters Handling
+   @{
+*/
+
+/* Filter bit-mask definitions */
+/** filter active sensing messages (0xFE): */
+#define PM_FILT_ACTIVE (1 << 0x0E)
+/** filter system exclusive messages (0xF0): */
+#define PM_FILT_SYSEX (1 << 0x00)
+/** filter MIDI clock message (0xF8) */
+#define PM_FILT_CLOCK (1 << 0x08)
+/** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */
+#define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B))
+/** filter tick messages (0xF9) */
+#define PM_FILT_TICK (1 << 0x09)
+/** filter undefined FD messages */
+#define PM_FILT_FD (1 << 0x0D)
+/** filter undefined real-time messages */
+#define PM_FILT_UNDEFINED PM_FILT_FD
+/** filter reset messages (0xFF) */
+#define PM_FILT_RESET (1 << 0x0F)
+/** filter all real-time messages */
+#define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \
+    PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK)
+/** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */
+#define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18))
+/** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/
+#define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D)
+/** per-note aftertouch (0xA0-0xAF) */
+#define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A)
+/** filter both channel and poly aftertouch */
+#define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | \
+                            PM_FILT_POLY_AFTERTOUCH)
+/** Program changes (0xC0-0xCF) */
+#define PM_FILT_PROGRAM (1 << 0x1C)
+/** Control Changes (CC's) (0xB0-0xBF)*/
+#define PM_FILT_CONTROL (1 << 0x1B)
+/** Pitch Bender (0xE0-0xEF*/
+#define PM_FILT_PITCHBEND (1 << 0x1E)
+/** MIDI Time Code (0xF1)*/
+#define PM_FILT_MTC (1 << 0x01)
+/** Song Position (0xF2) */
+#define PM_FILT_SONG_POSITION (1 << 0x02)
+/** Song Select (0xF3)*/
+#define PM_FILT_SONG_SELECT (1 << 0x03)
+/** Tuning request (0xF6) */
+#define PM_FILT_TUNE (1 << 0x06)
+/** All System Common messages (mtc, song position, song select, tune request) */
+#define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | \
+                              PM_FILT_SONG_SELECT | PM_FILT_TUNE)
+
+
+/** Set filters on an open input stream to drop selected input types.
+    
+    @param stream an open MIDI input stream.
+
+    @param filters indicate message types to filter (block).
+
+    @return #pmNoError or an error code.
+
+    By default, only active sensing messages are filtered.
+    To prohibit, say, active sensing and sysex messages, call
+    Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX);
+
+    Filtering is useful when midi routing or midi thru functionality
+    is being provided by the user application.
+    For example, you may want to exclude timing messages (clock, MTC,
+    start/stop/continue), while allowing note-related messages to pass.
+    Or you may be using a sequencer or drum-machine for MIDI clock
+    information but want to exclude any notes it may play.
+ */
+PMEXPORT PmError Pm_SetFilter(PortMidiStream* stream, int32_t filters);
+
+/** Create a mask that filters one channel. */
+#define Pm_Channel(channel) (1<<(channel))
+
+/** Filter incoming messages based on channel.
+
+    @param stream an open MIDI input stream.
+
+    @param mask indicates channels to be received.
+
+    @return #pmNoError or an error code.
+
+    The \p mask is a 16-bit bitfield corresponding to appropriate channels.
+    The #Pm_Channel macro can assist in calling this function.
+    I.e. to receive only input on channel 1, call with
+    Pm_SetChannelMask(Pm_Channel(1));
+    Multiple channels should be OR'd together, like
+    Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11))
+
+    Note that channels are numbered 0 to 15 (not 1 to 16). Most 
+    synthesizer and interfaces number channels starting at 1, but
+    PortMidi numbers channels starting at 0.
+
+    All channels are allowed by default
+*/
+PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask);
+
+/** Terminate outgoing messages immediately.
+
+    @param stream an open MIDI output stream.
+
+    @result #pmNoError or an error code.
+
+    The caller should immediately close the output port; this call may
+    result in transmission of a partial MIDI message.  There is no
+    abort for Midi input because the user can simply ignore messages
+    in the buffer and close an input device at any time. If the
+    specified behavior cannot be achieved through the system-level
+    interface (ALSA, CoreMIDI, etc.), the behavior may be that of 
+    Pm_Close().
+ */
+PMEXPORT PmError Pm_Abort(PortMidiStream* stream);
+     
+/** Close a midi stream, flush any pending buffers if possible.
+
+    @param stream an open MIDI input or output stream.
+
+    @result #pmNoError or an error code.
+
+    If the system-level interface (ALSA, CoreMIDI, etc.) does not
+    support flushing remaining messages, the behavior may be one of
+    the following (most preferred first): block until all pending
+    timestamped messages are delivered; deliver messages to a server
+    or kernel process for later delivery but return immediately; drop
+    messages (as in Pm_Abort()).  Therefore, to be safe, applications
+    should wait until the output queue is empty before calling
+    Pm_Close(). E.g. calling Pt_Sleep(100 + latency); will give a
+    100ms "cushion" beyond latency (if any) before closing.
+*/
+PMEXPORT PmError Pm_Close(PortMidiStream* stream);
+
+/** (re)synchronize to the time_proc passed when the stream was opened.
+
+    @param stream an open MIDI input or output stream.
+
+    @result #pmNoError or an error code.
+
+    Typically, this is used when the stream must be opened before the
+    time_proc reference is actually advancing. In this case, message
+    timing may be erratic, but since timestamps of zero mean "send
+    immediately," initialization messages with zero timestamps can be
+    written without a functioning time reference and without
+    problems. Before the first MIDI message with a non-zero timestamp
+    is written to the stream, the time reference must begin to advance
+    (for example, if the time_proc computes time based on audio
+    samples, time might begin to advance when an audio stream becomes
+    active). After time_proc return values become valid, and BEFORE
+    writing the first non-zero timestamped MIDI message, call
+    Pm_Synchronize() so that PortMidi can observe the difference
+    between the current time_proc value and its MIDI stream time.
+    
+    In the more normal case where time_proc values advance
+    continuously, there is no need to call #Pm_Synchronize. PortMidi
+    will always synchronize at the first output message and
+    periodically thereafter.
+*/
+PMEXPORT PmError Pm_Synchronize(PortMidiStream* stream);
+
+
+/** Encode a short Midi message into a 32-bit word. If data1
+    and/or data2 are not present, use zero.
+*/
+#define Pm_Message(status, data1, data2) \
+         ((((data2) << 16) & 0xFF0000) | \
+          (((data1) << 8) & 0xFF00) | \
+          ((status) & 0xFF))
+/** Extract the status field from a 32-bit midi message. */
+#define Pm_MessageStatus(msg) ((msg) & 0xFF)
+/** Extract the 1st data field (e.g., pitch) from a 32-bit midi message. */
+#define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF)
+/** Extract the 2nd data field (e.g., velocity) from a 32-bit midi message. */
+#define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF)
+
+typedef uint32_t PmMessage; /**< @brief see #PmEvent */
+/**
+   All MIDI data comes in the form of PmEvent structures. A sysex
+   message is encoded as a sequence of PmEvent structures, with each
+   structure carrying 4 bytes of the message, i.e. only the first
+   PmEvent carries the status byte.
+
+   All other MIDI messages take 1 to 3 bytes and are encoded in a whole
+   PmMessage with status in the low-order byte and remaining bytes 
+   unused, i.e., a 3-byte note-on message will occupy 3 low-order bytes
+   of PmMessage, leaving the high-order byte unused.
+
+   Note that MIDI allows nested messages: the so-called "real-time" MIDI 
+   messages can be inserted into the MIDI byte stream at any location, 
+   including within a sysex message. MIDI real-time messages are one-byte
+   messages used mainly for timing (see the MIDI spec). PortMidi retains 
+   the order of non-real-time MIDI messages on both input and output, but 
+   it does not specify exactly how real-time messages are processed. This
+   is particulary problematic for MIDI input, because the input parser 
+   must either prepare to buffer an unlimited number of sysex message 
+   bytes or to buffer an unlimited number of real-time messages that 
+   arrive embedded in a long sysex message. To simplify things, the input
+   parser is allowed to pass real-time MIDI messages embedded within a 
+   sysex message, and it is up to the client to detect, process, and 
+   remove these messages as they arrive.
+
+   When receiving sysex messages, the sysex message is terminated
+   by either an EOX status byte (anywhere in the 4 byte messages) or
+   by a non-real-time status byte in the low order byte of the message.
+   If you get a non-real-time status byte but there was no EOX byte, it 
+   means the sysex message was somehow truncated. This is not
+   considered an error; e.g., a missing EOX can result from the user
+   disconnecting a MIDI cable during sysex transmission.
+
+   A real-time message can occur within a sysex message. A real-time 
+   message will always occupy a full PmEvent with the status byte in 
+   the low-order byte of the PmEvent message field. (This implies that
+   the byte-order of sysex bytes and real-time message bytes may not
+   be preserved -- for example, if a real-time message arrives after
+   3 bytes of a sysex message, the real-time message will be delivered
+   first. The first word of the sysex message will be delivered only
+   after the 4th byte arrives, filling the 4-byte PmEvent message field.
+   
+   The timestamp field is observed when the output port is opened with
+   a non-zero latency. A timestamp of zero means "use the current time",
+   which in turn means to deliver the message with a delay of
+   latency (the latency parameter used when opening the output port.)
+   Do not expect PortMidi to sort data according to timestamps -- 
+   messages should be sent in the correct order, and timestamps MUST 
+   be non-decreasing. See also "Example" for Pm_OpenOutput() above.
+
+   A sysex message will generally fill many #PmEvent structures. On 
+   output to a #PortMidiStream with non-zero latency, the first timestamp
+   on sysex message data will determine the time to begin sending the 
+   message. PortMidi implementations may ignore timestamps for the 
+   remainder of the sysex message. 
+   
+   On input, the timestamp ideally denotes the arrival time of the 
+   status byte of the message. The first timestamp on sysex message 
+   data will be valid. Subsequent timestamps may denote 
+   when message bytes were actually received, or they may be simply 
+   copies of the first timestamp.
+
+   Timestamps for nested messages: If a real-time message arrives in 
+   the middle of some other message, it is enqueued immediately with 
+   the timestamp corresponding to its arrival time. The interrupted 
+   non-real-time message or 4-byte packet of sysex data will be enqueued 
+   later. The timestamp of interrupted data will be equal to that of
+   the interrupting real-time message to insure that timestamps are
+   non-decreasing.
+ */
+typedef struct {
+    PmMessage      message;    /**< up to 4 bytes of MIDI data */
+    PmTimestamp    timestamp;  /**< PortMidi time of message */
+} PmEvent;
+
+/** @} */
+
+/** \defgroup grp_io Reading and Writing Midi Messages
+    @{
+*/
+/** Retrieve midi data into a buffer. 
+
+    @param stream the open input stream.
+
+    @param buffer input data is stored here
+
+    @param length the length of buffer (number of #PmEvent, not bytes)
+
+    @return the number of events read, or, if the result is negative,
+    a #PmError value will be returned.
+
+    The Buffer Overflow Problem
+
+    The problem: if an input overflow occurs, data will be lost,
+    ultimately because there is no flow control all the way back to
+    the data source.  When data is lost, the receiver should be
+    notified and some sort of graceful recovery should take place,
+    e.g. you shouldn't resume receiving in the middle of a long sysex
+    message.
+
+    With a lock-free fifo, which is pretty much what we're stuck with
+    to enable portability to the Mac, it's tricky for the producer and
+    consumer to synchronously reset the buffer and resume normal
+    operation.
+
+    Solution: the entire buffer managed by PortMidi will be flushed
+    when an overflow occurs. The consumer (#Pm_Read()) gets an error
+    message (#pmBufferOverflow) and ordinary processing resumes as
+    soon as a new message arrives. The remainder of a partial sysex
+    message is not considered to be a "new message" and will be
+    flushed as well.
+*/
+PMEXPORT int Pm_Read(PortMidiStream *stream, PmEvent *buffer, int32_t length);
+
+/** Test whether input is available.
+
+    @param stream an open input stream.
+
+    @return TRUE, FALSE, or an error value. 
+
+    If there was an asynchronous error, pmHostError is returned and you must
+    call again to determine if input is (also) available.
+
+    You should probably *not* use this function. Call Pm_Read()
+    instead. If it returns 0, then there is no data available. It is
+    possible for Pm_Poll() to return TRUE before the complete message
+    is available, so Pm_Read() could return 0 even after Pm_Poll()
+    returns TRUE. Only call Pm_Poll() if you want to know that data is
+    probably available even though you are not ready to receive data.
+*/
+PMEXPORT PmError Pm_Poll(PortMidiStream *stream);
+
+/** Write MIDI data from a buffer. 
+
+    @param stream an open output stream.
+
+    @param buffer (address of) an array of MIDI event data.
+
+    @param length the length of the \p buffer.
+
+    @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),
+        #pmDeviceRemoved (if the MIDI device no longer exists),
+        #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,
+        nested SYSEX messages, or #pmHostError (error returned from API's
+        MIDI write operation, see #Pm_GetHostErrorText). 
+
+    \b buffer may contain:
+        - short messages 
+        - sysex messages that are converted into a sequence of PmEvent
+          structures, e.g. sending data from a file or forwarding them
+          from midi input, with 4 SysEx bytes per PmEvent message,
+          low-order byte first, until the last message, which may
+          contain from 1 to 4 bytes ending in MIDI EOX (0xF7).
+        - PortMidi allows 1-byte real-time messages to be embedded
+          within SysEx messages, but only on 4-byte boundaries so
+          that SysEx data always uses a full 4 bytes (except possibly
+          at the end). Each real-time message always occupies a full
+          PmEvent (3 of the 4 bytes in the PmEvent's message are
+          ignored) even when embedded in a SysEx message.
+
+    Use Pm_WriteSysEx() to write a sysex message stored as a contiguous 
+    array of bytes.
+
+    Sysex data may contain embedded real-time messages.
+
+    \p buffer is managed by the caller. The buffer may be destroyed
+    as soon as this call returns.
+*/
+PMEXPORT PmError Pm_Write(PortMidiStream *stream, PmEvent *buffer,
+                          int32_t length);
+
+/** Write a timestamped non-system-exclusive midi message.
+
+    @param stream an open output stream.
+
+    @param when timestamp for the event.
+
+    @param msg the data for the event.
+
+    @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),
+        #pmDeviceRemoved (if the MIDI device no longer exists),
+        #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,
+        nested SYSEX messages, or #pmHostError (error returned from API's
+        MIDI write operation, see #Pm_GetHostErrorText). 
+
+    Messages are delivered in order, and timestamps must be
+    non-decreasing. (But timestamps are ignored if the stream was
+    opened with latency = 0, and otherwise, non-decreasing timestamps
+    are "corrected" to the lowest valid value.)
+*/
+PMEXPORT PmError Pm_WriteShort(PortMidiStream *stream, PmTimestamp when,
+                               PmMessage msg);
+
+/** Write a timestamped system-exclusive midi message.
+
+    @param stream an open output stream.
+
+    @param when timestamp for the event.
+
+    @param msg the sysex message, terminated with an EOX status byte.
+
+    @return #pmNoError, #pmBadPtr (if \p stream is not valid and opened),
+        #pmDeviceRemoved (if the MIDI device no longer exists),
+        #pmBadData (if \p buffer data does not represent valid MIDI, e.g.,
+        nested SYSEX messages, or #pmHostError (error returned from API's
+        MIDI write operation, see #Pm_GetHostErrorText). 
+
+    \p msg is managed by the caller and may be destroyed when this
+    call returns.
+*/
+PMEXPORT PmError Pm_WriteSysEx(PortMidiStream *stream, PmTimestamp when, 
+                               unsigned char *msg);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* PORTMIDI_PORTMIDI_H */
diff --git a/portmidi/pm_linux/pmlinux.c b/portmidi/pm_linux/pmlinux.c
--- a/portmidi/pm_linux/pmlinux.c
+++ b/portmidi/pm_linux/pmlinux.c
@@ -1,60 +1,68 @@
-/* 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.
-
-   NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
-   in pmlinuxalsa.c, which assumes all input devices are ALSA.
- */
-
-#include "stdlib.h"
-#include "portmidi.h"
-#ifdef PMALSA
-  #include "pmlinuxalsa.h"
-#endif
-
-#ifdef PMNULL
-  #include "pmlinuxnull.h"
-#endif
-
-PmError pm_init()
-{
-    /* Note: it is not an error for PMALSA to fail to initialize. 
-     * It may be a design error that the client cannot query what subsystems
-     * are working properly other than by looking at the list of available
-     * devices.
-     */
-    #ifdef PMALSA
-	pm_linuxalsa_init();
-    #endif
-    #ifdef PMNULL
-        pm_linuxnull_init();
-    #endif
-    return pmNoError;
-}
-
-void pm_term(void)
-{
-    #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); }
-
+/* 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.
+
+   NOTE: if you add non-ALSA support, you need to fix :alsa_poll()
+   in pmlinuxalsa.c, which assumes all input devices are ALSA.
+ */
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+
+#ifdef PMALSA
+  #include "pmlinuxalsa.h"
+#endif
+
+#ifdef PMNULL
+  #include "pmlinuxnull.h"
+#endif
+
+#if !(defined(PMALSA) || defined(PMNULL))
+#error One of PMALSA or PMNULL must be defined
+#endif
+
+void pm_init(void)
+{
+    /* Note: it is not an error for PMALSA to fail to initialize. 
+     * It may be a design error that the client cannot query what subsystems
+     * are working properly other than by looking at the list of available
+     * devices.
+     */
+#ifdef PMALSA
+    pm_linuxalsa_init();
+#endif
+#ifdef PMNULL
+        pm_linuxnull_init();
+#endif
+}
+
+void pm_term(void)
+{
+    #ifdef PMALSA
+        pm_linuxalsa_term();
+    #endif
+    #ifdef PMNULL
+        pm_linuxnull_term();
+    #endif
+}
+
+PmDeviceID Pm_GetDefaultInputDeviceID(void) { 
+    Pm_Initialize();
+    return pm_default_input_device_id; 
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID(void) { 
+    Pm_Initialize();
+    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/portmidi/pm_linux/pmlinux.h b/portmidi/pm_linux/pmlinux.h
deleted file mode 100644
--- a/portmidi/pm_linux/pmlinux.h
+++ /dev/null
@@ -1,5 +0,0 @@
-/* pmlinux.h */
-
-extern PmDeviceID pm_default_input_device_id;
-extern PmDeviceID pm_default_output_device_id;
-
diff --git a/portmidi/pm_linux/pmlinuxalsa.c b/portmidi/pm_linux/pmlinuxalsa.c
--- a/portmidi/pm_linux/pmlinuxalsa.c
+++ b/portmidi/pm_linux/pmlinuxalsa.c
@@ -1,789 +1,913 @@
-/*
- * 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 "pmutil.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*)(size_t)(((client) << 8) | (port)))
-#define GET_DESCRIPTOR_CLIENT(info) ((((size_t)(info)) >> 8) & 0xff)
-#define GET_DESCRIPTOR_PORT(info) (((size_t)(info)) & 0xff)
-
-#define 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 = NULL; // all input comes here, 
-                              // output queue allocated on seq
-static int queue, queue_used; /* one for all ports, reference counted */
-
-typedef struct alsa_descriptor_struct {
-    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 %ld, ", 
-                           (int) timestamp, (int) now, midi->latency);
-            VERBOSE printf("scheduling event after %d\n", when);
-            /* message is sent in relative ticks, where 1 tick = 1 ms */
-            snd_seq_ev_schedule_tick(&ev, queue, 1, when);
-            /* 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)
-{
-    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);
-    midi->descriptor = NULL; /* destroy the pointer to signify "closed" */
-    pm_free(desc);
-    if (pm_hosterror) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, 
-                            pm_hosterror);
-        return pmHostError;
-    }
-    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);
-    /* bugfix: without the following, it won't generate timestamp - PaulLiu */
-    snd_seq_port_info_set_timestamping(info, 1);
-    snd_seq_port_info_set_timestamp_real(info, 0);
-    snd_seq_port_info_set_timestamp_queue(info, queue);
-    err = snd_seq_create_port(seq, info);
-    if (err < 0) goto free_queue;
-
-    /* fill in fields of desc, which is passed to pm_write routines */
-    midi->descriptor = desc;
-    desc->client = GET_DESCRIPTOR_CLIENT(client_port);
-    desc->port = GET_DESCRIPTOR_PORT(client_port);
-    desc->this_port = midi->device_id;
-    desc->in_sysex = 0;
-
-    desc->error = 0;
-
-    VERBOSE printf("snd_seq_connect_from: %d %d %d\n", 
-                   desc->this_port, desc->client, desc->port);
-    snd_seq_port_subscribe_alloca(&sub);
-    addr.client = snd_seq_client_id(seq);
-    addr.port = desc->this_port;
-    snd_seq_port_subscribe_set_dest(sub, &addr);
-    addr.client = desc->client;
-    addr.port = desc->port;
-    snd_seq_port_subscribe_set_sender(sub, &addr);
-    snd_seq_port_subscribe_set_time_update(sub, 1);
-    /* this doesn't seem to work: messages come in with real timestamps */
-    snd_seq_port_subscribe_set_time_real(sub, 0);
-    err = snd_seq_subscribe_port(seq, sub);
-    /* err = 
-       snd_seq_connect_from(seq, desc->this_port, desc->client, desc->port); */
-    if (err < 0) goto free_this_port;  /* clean up and return on error */
-    return pmNoError;
-
- 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)
-{
-    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)
-{
-    /* NOTE: ALSA documentation is vague. This is supposed to 
-     * remove any pending output messages. If you can test and 
-     * confirm this code is correct, please update this comment. -RBD
-     */
-    /* Unfortunately, I can't even compile it -- my ALSA version 
-     * does not implement snd_seq_remove_events_t, so this does
-     * not compile. I'll try again, but it looks like I'll need to
-     * upgrade my entire Linux OS -RBD
-     */
-    /*
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    snd_seq_remove_events_t info;
-    snd_seq_addr_t addr;
-    addr.client = desc->client;
-    addr.port = desc->port;
-    snd_seq_remove_events_set_dest(&info, &addr);
-    snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
-    pm_hosterror = snd_seq_remove_events(seq, &info);
-    if (pm_hosterror) {
-        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, 
-                            pm_hosterror);
-        return pmHostError;
-    }
-    */
-    printf("WARNING: alsa_abort not implemented\n");
-    return pmNoError;
-}
-
-
-#ifdef GARBAGE
-This is old code here temporarily for reference
-static PmError alsa_write(PmInternal *midi, PmEvent *buffer, long length)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    int i, bytes;
-    unsigned char byte;
-    long msg;
-
-    desc->error = 0;
-    for (; length > 0; length--, buffer++) {
-        VERBOSE printf("message 0x%x\n", buffer->message);
-        if (Pm_MessageStatus(buffer->message) == MIDI_SYSEX)
-            desc->in_sysex = TRUE;
-        if (desc->in_sysex) {
-            msg = buffer->message;
-            for (i = 0; i < 4; i++) {
-                byte = msg;  /* extract next byte to send */
-                alsa_write_byte(midi, byte, buffer->timestamp);
-                if (byte == MIDI_EOX) {
-                    desc->in_sysex = FALSE;
-                    break;
-                }
-                if (desc->error < 0) break;
-                msg >>= 8; /* shift next byte into position */
-            }
-        } else {
-            bytes = midi_message_length(buffer->message);
-            msg = buffer->message;
-            for (i = 0; i < bytes; i++) {
-                byte = msg; /* extract next byte to send */
-                VERBOSE printf("sending 0x%x\n", byte);
-                alsa_write_byte(midi, byte, buffer->timestamp);
-                if (desc->error < 0) break;
-                msg >>= 8; /* shift next byte into position */
-            }
-        }
-    }
-    if (desc->error < 0) return pmHostError;
-
-    VERBOSE printf("snd_seq_drain_output: 0x%x\n", seq);
-    desc->error = snd_seq_drain_output(seq);
-    if (desc->error < 0) return pmHostError;
-
-    desc->error = pmNoError;
-    return pmNoError;
-}
-#endif
-
-
-static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
-{
-    alsa_descriptor_type desc = (alsa_descriptor_type) midi->descriptor;
-    VERBOSE printf("snd_seq_drain_output: 0x%p\n", seq);
-    desc->error = snd_seq_drain_output(seq);
-    if (desc->error < 0) return pmHostError;
-
-    desc->error = pmNoError;
-    return pmNoError;
-}
-
-
-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;
-        /* assume there is one sysex byte to process */
-        pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
-        break;
-    }
-    /* TODO: must handle port close messsage - PaulLiu */
-    //case SND_SEQ_EVENT_UNSUBSCRIBE: {
-    //}
-    default:
-        printf("not handled type %x\n", ev->type);
-    }
-}
-
-
-static PmError alsa_poll(PmInternal *midi)
-{
-    snd_seq_event_t *ev;
-    /* expensive check for input data, gets data from device: */
-    while (snd_seq_event_input_pending(seq, TRUE) > 0) {
-        /* cheap check on local input buffer */
-        while (snd_seq_event_input_pending(seq, FALSE) > 0) {
-            /* check for and ignore errors, e.g. input overflow */
-            /* note: if there's overflow, this should be reported
-             * all the way through to client. Since input from all
-             * devices is merged, we need to find all input devices
-             * and set all to the overflow state.
-             * NOTE: this assumes every input is ALSA based.
-             */
-            int rslt = snd_seq_event_input(seq, &ev);
-            if (rslt >= 0) {
-                handle_event(ev);
-            } else if (rslt == -ENOSPC) {
-                int i;
-                for (i = 0; i < pm_descriptor_index; i++) {
-                    if (descriptors[i].pub.input) {
-                        PmInternal *midi = (PmInternal *) 
-                                descriptors[i].internalDescriptor;
-                        /* careful, device may not be open! */
-                        if (midi) Pm_SetOverflow(midi->queue);
-                    }
-                }
-            }
-        }
-    }
-    return pmNoError;
-}
-
-
-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;
-
-    /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this 
-     * would cause messages to be dropped if the ALSA buffer fills up.
-     * The correct behavior is for writes to block until there is 
-     * room to send all the data. The client should normally allocate
-     * a large enough buffer to avoid blocking on output. 
-     * Now that blocking is enabled, the seq_event_input() will block
-     * if there is no input data. This is not what we want, so must
-     * call seq_event_input_pending() to avoid blocking.
-     */
-    err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
-    if (err < 0) return err;
-    
-    snd_seq_client_info_alloca(&cinfo);
-    snd_seq_port_info_alloca(&pinfo);
-
-    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);
-            }
-        }
-    }
-    return pmNoError;
-}
-    
-
-void pm_linuxalsa_term(void)
-{
-    if (seq) {
-        snd_seq_close(seq);
-        pm_free(descriptors);
-        descriptors = NULL;
-        pm_descriptor_index = 0;
-        pm_descriptor_max = 0;
-    }
-}
+/*
+ * 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)
+ */ 
+
+/* omit this code if PMALSA is not defined -- this mechanism allows
+ * selection of different MIDI interfaces (at compile time).
+ */
+#ifdef PMALSA
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "pmlinuxalsa.h"
+#include "string.h"
+#include "porttime.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*)(long)(((client) << 8) | (port)))
+#define GET_DESCRIPTOR_CLIENT(info) ((((long)(info)) >> 8) & 0xff)
+#define GET_DESCRIPTOR_PORT(info) (((long)(info)) & 0xff)
+
+#define BYTE unsigned char
+
+extern pm_fns_node pm_linuxalsa_in_dictionary;
+extern pm_fns_node pm_linuxalsa_out_dictionary;
+
+static snd_seq_t *seq = NULL; /* all input comes here, 
+                                 output queue allocated on seq */
+static int queue, queue_used; /* one for all ports, reference counted */
+
+#define PORT_IS_CLOSED -999999
+
+typedef struct alsa_info_struct {
+    int is_virtual;
+    int client;
+    int port;
+    int this_port;
+    int in_sysex;
+    snd_midi_event_t *parser;
+} alsa_info_node, *alsa_info_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 > 0 && errlen < len) {
+        strcpy(msg, snd_strerror(err));
+    } else if (len > 20) {
+        sprintf(msg, "Alsa error %d", err);
+    } else {
+        msg[0] = 0;
+    }
+}
+
+
+static PmError check_hosterror(int err)
+{
+    if (err < 0) {
+        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, err);
+        pm_hosterror = TRUE;
+        return pmHostError;
+    }
+    return pmNoError;
+}
+
+
+/* queue is shared by both input and output, reference counted */
+static PmError alsa_use_queue(void)
+{
+    int err = 0;
+    if (queue_used == 0) {
+        snd_seq_queue_tempo_t *tempo;
+
+        queue = snd_seq_alloc_queue(seq);
+        if (queue < 0) {
+            return check_hosterror(queue);
+        }
+        snd_seq_queue_tempo_alloca(&tempo);
+        snd_seq_queue_tempo_set_tempo(tempo, 480000);
+        snd_seq_queue_tempo_set_ppq(tempo, 480);
+        err = snd_seq_set_queue_tempo(seq, queue, tempo);
+        if (err < 0) {
+            return check_hosterror(err);
+        }
+        snd_seq_start_queue(seq, queue, NULL);
+        snd_seq_drain_output(seq);
+    }
+    ++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 alsa_info_type alsa_info_create(int client_port, long id, int is_virtual)
+{
+    alsa_info_type info = (alsa_info_type) pm_alloc(sizeof(alsa_info_node));
+    info->is_virtual = is_virtual;
+    info->this_port = id;
+    info->client = GET_DESCRIPTOR_CLIENT(client_port);
+    info->port = GET_DESCRIPTOR_PORT(client_port);
+    info->in_sysex = 0;
+    return info;
+}    
+
+
+/* search system dependent extra parameters for string */
+static const char *get_sysdep_name(enum PmSysDepPropertyKey key,
+                                   PmSysDepInfo *info)
+{
+    /* the version where all current properties were introduced is 210 */
+    if (info && info->structVersion >= 210) {
+        int i;
+        for (i = 0; i < info->length; i++) {  /* search for key */
+            if (info->properties[i].key == key) {
+                return info->properties[i].value;
+            }
+        }
+    }
+    return NULL;
+}
+
+
+static void maybe_set_client_name(PmSysDepInfo *driverInfo)
+{
+    /* make sure seq is created and we have info */
+    if (!seq || !driverInfo) {
+        return;
+    }
+    
+    const char *client_name = get_sysdep_name(pmKeyAlsaClientName,
+                                              (PmSysDepInfo *) driverInfo);
+    if (client_name) {
+        snd_seq_set_client_name(seq, client_name);
+        VERBOSE printf("maybe_set_client_name set client to %s\n", client_name);
+    }
+}    
+
+
+static PmError alsa_out_open(PmInternal *midi, void *driverInfo) 
+{
+    int id = midi->device_id;
+    void *client_port = pm_descriptors[id].descriptor;
+    alsa_info_type ainfo = alsa_info_create((long) client_port, id,
+                                            pm_descriptors[id].pub.is_virtual);
+    snd_seq_port_info_t *pinfo;
+    int err = 0;
+    int using_the_queue = 0;
+
+    if (!ainfo) return pmInsufficientMemory;
+    midi->api_info = ainfo;
+
+    snd_seq_port_info_alloca(&pinfo);
+    if (!ainfo->is_virtual) {
+        snd_seq_port_info_set_port(pinfo, id);
+        snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
+                SND_SEQ_PORT_CAP_READ  | SND_SEQ_PORT_CAP_SUBS_READ);
+        snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | 
+                                          SND_SEQ_PORT_TYPE_APPLICATION);
+        const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
+                                                (PmSysDepInfo *) driverInfo);
+        if (port_name) {
+            snd_seq_port_info_set_name(pinfo, port_name);
+        }
+        snd_seq_port_info_set_port_specified(pinfo, 1);
+
+        err = snd_seq_create_port(seq, pinfo);
+        if (err < 0) goto free_ainfo;
+
+    }
+    
+    err = snd_midi_event_new(PM_DEFAULT_SYSEX_BUFFER_SIZE, &ainfo->parser);
+    if (err < 0) goto free_this_port;
+
+    if (midi->latency > 0) { /* must delay output using a queue */
+        err = alsa_use_queue();
+        if (err < 0) goto free_parser;
+        using_the_queue++;
+    }
+
+    if (!ainfo->is_virtual) {
+        err = snd_seq_connect_to(seq, ainfo->this_port, ainfo->client,
+                                 ainfo->port);
+        if (err < 0) goto unuse_queue;  /* clean up and return on error */
+    }
+
+    maybe_set_client_name(driverInfo);
+
+    return pmNoError;
+
+ unuse_queue:
+    if (using_the_queue > 0)  /* only for latency>0 case */
+        alsa_unuse_queue();
+ free_parser:
+    snd_midi_event_free(ainfo->parser);
+ free_this_port:
+    snd_seq_delete_port(seq, ainfo->this_port);
+ free_ainfo:
+    pm_free(ainfo);
+    return check_hosterror(err);
+}
+    
+
+static PmError alsa_write_byte(PmInternal *midi, unsigned char byte, 
+                               PmTimestamp timestamp)
+{
+    alsa_info_type info = (alsa_info_type) midi->api_info;
+    snd_seq_event_t ev;
+    int err = 0;
+
+    snd_seq_ev_clear(&ev);
+    if (snd_midi_event_encode_byte(info->parser, byte, &ev) == 1) {
+        if (info->is_virtual) {
+            snd_seq_ev_set_subs(&ev);
+        } else {
+            snd_seq_ev_set_dest(&ev, info->client, info->port);
+        }
+        snd_seq_ev_set_source(&ev, info->this_port);
+        if (midi->latency > 0) {
+            /* compute relative time of event = timestamp - now + latency */
+            PmTimestamp now = (midi->time_proc ? 
+                               midi->time_proc(midi->time_info) : 
+                               Pt_Time());
+            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, ", 
+                           (int) timestamp, (int) now, midi->latency);
+            VERBOSE printf("scheduling event after %d\n", when);
+            /* message is sent in relative ticks, where 1 tick = 1 ms */
+            snd_seq_ev_schedule_tick(&ev, queue, 1, when);
+            /* 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, timestamp %d (%d+%dns) (%s, %s)\n",
+                       ev.time.tick, ev.time.time.tv_sec, ev.time.time.tv_nsec,
+                       (ev.flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
+                       (ev.flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
+        err = snd_seq_event_output(seq, &ev);
+    }
+    return check_hosterror(err);
+}
+
+
+static PmError alsa_out_close(PmInternal *midi)
+{
+    alsa_info_type info = (alsa_info_type) midi->api_info;
+    int err = 0;
+    int error2 = 0;
+    if (!info) return pmBadPtr;
+
+    if (info->this_port != PORT_IS_CLOSED) {
+        if (!info->is_virtual) {
+            err = snd_seq_disconnect_to(seq, info->this_port,
+                                        info->client, info->port);
+            /* even if there was an error, we still try to delete the port */
+            error2 = snd_seq_delete_port(seq, info->this_port);
+
+            if (!err) {  /* retain original error if there was one */
+                err = error2; /* otherwise, use port delete status */
+            }
+        }
+    }
+    if (midi->latency > 0) alsa_unuse_queue();
+    snd_midi_event_free(info->parser);
+    midi->api_info = NULL; /* destroy the pointer to signify "closed" */
+    pm_free(info);
+    return check_hosterror(err);
+}
+
+
+static PmError alsa_create_virtual(int is_input, const char *name,
+                                   void *device_info)
+{
+    snd_seq_port_info_t *pinfo;
+    int err;
+    int client, port;
+    
+    /* we need the id to set the port. */
+    PmDeviceID id = pm_add_device("ALSA", name, is_input, TRUE, NULL,
+                                  (is_input ? &pm_linuxalsa_in_dictionary :
+                                              &pm_linuxalsa_out_dictionary));
+    if (id < 0) { /* error -- out of memory? */
+        return id;
+    }
+    snd_seq_port_info_alloca(&pinfo);
+    snd_seq_port_info_set_capability(pinfo,
+            (is_input ? SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE :
+                        SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ));
+    snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC |
+                                     SND_SEQ_PORT_TYPE_APPLICATION);
+    snd_seq_port_info_set_name(pinfo, name);
+    snd_seq_port_info_set_port(pinfo, id);
+    snd_seq_port_info_set_port_specified(pinfo, 1);
+    /* next 3 lines needed to generate timestamp - PaulLiu */
+    snd_seq_port_info_set_timestamping(pinfo, 1);
+    snd_seq_port_info_set_timestamp_real(pinfo, 0);
+    snd_seq_port_info_set_timestamp_queue(pinfo, queue);
+
+    err = snd_seq_create_port(seq, pinfo);
+    if (err < 0) {
+        pm_undo_add_device(id);
+        return check_hosterror(err);
+    }
+
+    client = snd_seq_port_info_get_client(pinfo);
+    port = snd_seq_port_info_get_port(pinfo);
+    pm_descriptors[id].descriptor = MAKE_DESCRIPTOR(client, port);
+    return id;
+}
+
+
+ static PmError alsa_delete_virtual(PmDeviceID id)
+ {
+     int err = snd_seq_delete_port(seq, id);
+     return check_hosterror(err);
+ }
+ 
+
+static PmError alsa_in_open(PmInternal *midi, void *driverInfo)
+{
+    int id = midi->device_id;
+    void *client_port = pm_descriptors[id].descriptor;
+    alsa_info_type ainfo = alsa_info_create((long) client_port, id,
+                                            pm_descriptors[id].pub.is_virtual);
+    snd_seq_port_info_t *pinfo;
+    snd_seq_port_subscribe_t *sub;
+    snd_seq_addr_t addr;
+    int err = 0;
+    int is_virtual = pm_descriptors[id].pub.is_virtual;
+    
+    if (!ainfo) return pmInsufficientMemory;
+    midi->api_info = ainfo;
+
+    err = alsa_use_queue();
+    if (err < 0) goto free_ainfo;
+
+    snd_seq_port_info_alloca(&pinfo);
+    if (is_virtual) {
+        ainfo->is_virtual = TRUE;
+        if (snd_seq_get_port_info(seq, ainfo->port, pinfo)) {
+            pinfo = NULL;
+            goto free_ainfo;
+        }
+    } else {
+        /* create a port for this alsa client (seq) where the port
+           number matches the portmidi device ID of the input device */
+        snd_seq_port_info_set_port(pinfo, id);
+        snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE |
+                SND_SEQ_PORT_CAP_READ  | SND_SEQ_PORT_CAP_SUBS_WRITE);
+
+        snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC | 
+                                          SND_SEQ_PORT_TYPE_APPLICATION);
+        snd_seq_port_info_set_port_specified(pinfo, 1);
+        
+        const char *port_name = get_sysdep_name(pmKeyAlsaPortName,
+                                                (PmSysDepInfo *) driverInfo);
+        if (port_name) {
+            snd_seq_port_info_set_name(pinfo, port_name);
+        }
+        
+        err = snd_seq_create_port(seq, pinfo);
+        if (err < 0) goto free_queue;
+
+        /* forward messages from input to this alsa client, so this
+         * alsa client is the destination, and the destination port is the
+         * port we just created using the device ID as port number
+         */
+        snd_seq_port_subscribe_alloca(&sub);
+        addr.client = snd_seq_client_id(seq);
+        addr.port = ainfo->this_port;
+        snd_seq_port_subscribe_set_dest(sub, &addr);
+
+        /* forward from the sender which is the device named by 
+           client and port */
+        addr.client = ainfo->client;
+        addr.port = ainfo->port;
+        snd_seq_port_subscribe_set_sender(sub, &addr);
+        snd_seq_port_subscribe_set_time_update(sub, 1);
+        /* this doesn't seem to work: messages come in with real timestamps */
+        snd_seq_port_subscribe_set_time_real(sub, 0);
+        err = snd_seq_subscribe_port(seq, sub);
+        if (err < 0) goto free_this_port;  /* clean up and return on error */
+    }
+
+    maybe_set_client_name(driverInfo);
+
+    return pmNoError;
+ free_this_port:
+    snd_seq_delete_port(seq, ainfo->this_port);
+ free_queue:
+    alsa_unuse_queue();
+ free_ainfo:
+    pm_free(ainfo);
+    return check_hosterror(err);
+}
+
+static PmError alsa_in_close(PmInternal *midi)
+{
+    int err = 0;
+    alsa_info_type info = (alsa_info_type) midi->api_info;
+    if (!info) return pmBadPtr;
+    /* virtual ports stay open because the represent devices */
+    if (!info->is_virtual && info->this_port != PORT_IS_CLOSED) {
+        err = snd_seq_delete_port(seq, info->this_port);
+    }
+    alsa_unuse_queue();
+    midi->api_info = NULL;
+    pm_free(info);
+    return check_hosterror(err);
+}
+        
+
+static PmError alsa_abort(PmInternal *midi)
+{
+    /* NOTE: ALSA documentation is vague. This is supposed to 
+     * remove any pending output messages. If you can test and 
+     * confirm this code is correct, please update this comment. -RBD
+     */
+    /* Unfortunately, I can't even compile it -- my ALSA version 
+     * does not implement snd_seq_remove_events_t, so this does
+     * not compile. I'll try again, but it looks like I'll need to
+     * upgrade my entire Linux OS -RBD
+     */
+    /*
+    info_type info = (info_type) midi->api_info;
+    snd_seq_remove_events_t info;
+    snd_seq_addr_t addr;
+    addr.client = info->client;
+    addr.port = info->port;
+    snd_seq_remove_events_set_dest(&info, &addr);
+    snd_seq_remove_events_set_condition(&info, SND_SEQ_REMOVE_DEST);
+    pm_hosterror = snd_seq_remove_events(seq, &info);
+    if (pm_hosterror) {
+        get_alsa_error_text(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, 
+                            pm_hosterror);
+        return pmHostError;
+    }
+    */
+    printf("WARNING: alsa_abort not implemented\n");
+    return pmNoError;
+}
+
+
+static PmError alsa_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+    int err;
+    alsa_info_type info = (alsa_info_type) midi->api_info;
+    if (!info) return pmBadPtr;
+    VERBOSE printf("snd_seq_drain_output: %p\n", seq);
+    err = snd_seq_drain_output(seq);
+    return check_hosterror(err);
+}
+
+
+static PmError alsa_write_short(PmInternal *midi, PmEvent *event)
+{
+    int bytes = midi_message_length(event->message);
+    PmMessage msg = event->message;
+    int i;
+    alsa_info_type info = (alsa_info_type) midi->api_info;
+    if (!info) return pmBadPtr;
+    for (i = 0; i < bytes; i++) {
+        unsigned char byte = msg;
+        VERBOSE printf("sending 0x%x\n", byte);
+        alsa_write_byte(midi, byte, event->timestamp);
+        if (pm_hosterror) break;
+        msg >>= 8; /* shift next byte into position */
+    }
+    if (pm_hosterror) return pmHostError;
+    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;
+
+    assert(device_id >= 0 && device_id < pm_descriptor_len);
+    assert(pm_descriptors[device_id].pub.name);
+
+    PmInternal *midi = pm_descriptors[device_id].pm_internal;
+    /* There is a race condition when closing a device and
+       continuing to poll other open devices. The closed device may
+       have outstanding events from before the close operation.
+    */
+    if (!midi) {
+        return;
+    }
+    PmEvent pm_ev;
+
+    /* Copilot says: "snd_seq_event_input() will not return events for a
+     * MIDI output device." However, this does not seem to be true, e.g.,
+     * for MIDIFLEX 4 on Arch Linux, so we want to check and ignore the
+     * call if this is an output device.
+     */
+    if (!midi->is_input) {
+        return;
+    }
+
+    if (!midi->time_proc) {  /* extra sanity check */
+        return;
+    }
+
+    PmTimestamp timestamp = midi->time_proc(midi->time_info);
+
+    /* time stamp should be in ticks, using our queue where 1 tick = 1ms */
+    /* assert((ev->flags & SND_SEQ_TIME_STAMP_MASK) == SND_SEQ_TIME_STAMP_TICK);
+       Currently, event timestamp is ignored. See long note below. 
+    */
+    VERBOSE {
+        /* translate time to time_proc basis */
+        snd_seq_queue_status_t *queue_status;
+        snd_seq_queue_status_alloca(&queue_status);
+        snd_seq_get_queue_status(seq, queue, queue_status);
+        printf("handle_event: alsa_now %d, "
+               "event timestamp %d (%d+%dns) (%s, %s)\n",
+               snd_seq_queue_status_get_tick_time(queue_status),
+               ev->time.tick, ev->time.time.tv_sec, ev->time.time.tv_nsec,
+               (ev->flags & SND_SEQ_TIME_STAMP_MASK ? "real" : "tick"),
+               (ev->flags & SND_SEQ_TIME_MODE_MASK ? "rel" : "abs"));
+        /* OLD: portmidi timestamp is (now - alsa_now) + alsa_timestamp */
+        /* timestamp = (*time_proc)(midi->time_info) + ev->time.tick -
+                       snd_seq_queue_status_get_tick_time(queue_status); */
+    }
+    /* CURRENT: portmidi timestamp is "now". In a test, timestamps from
+     * hardware (MIDI over USB) were timestamped with the current ALSA
+     * time (snd_seq_queue_status_get_tick_time) and flags indicating 
+     * absolute ticks, but timestamps from another application's virtual
+     * port, sent direct with 0 absolute ticks, were received with a 
+     * large value that is apparently the time since the start time of
+     * the other application. Without any reference to our local time,
+     * this seems useless. PortMidi is supposed to return the local
+     * PortMidi time of the arrival of the message, so the best we can
+     * do is set the timestamp to our local clock.  This seems to be a
+     * design flaw in ALSA -- I pointed this out a decade ago, but if
+     * there is a workaround, I'd still like to know. Maybe there is a
+     * way to use absolute real time and maybe that's sharable across
+     * applications by referencing the system time?
+     */
+    pm_ev.timestamp = timestamp;
+    switch (ev->type) {
+    case SND_SEQ_EVENT_NOTEON:
+        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;
+        /* assume there is one sysex byte to process */
+        pm_read_bytes(midi, ptr, ev->data.ext.len, timestamp);
+        break;
+    }
+    case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: {
+        /* this happens if you have an input port open and the 
+         * device or application with virtual ports closes. We
+         * mark the port as closed to avoid closing a 2nd time
+         * when Pm_Close() is called.
+         */
+        alsa_info_type info = (alsa_info_type) midi->api_info;
+        /* printf("SND_SEQ_EVENT_UNSUBSCRIBE message\n"); */
+        info->this_port = PORT_IS_CLOSED;
+        break;
+    }
+    case SND_SEQ_EVENT_PORT_SUBSCRIBED:
+        break;  /* someone connected to a virtual output port, not reported */
+    default:
+        printf("portmidi handle_event: not handled type %x\n", ev->type);
+        break;
+    }
+}
+
+
+static PmError alsa_poll(PmInternal *midi)
+{
+    if (!midi) {
+        return pmBadPtr;
+    }
+    snd_seq_event_t *ev;
+    /* expensive check for input data, gets data from device: */
+    while (snd_seq_event_input_pending(seq, TRUE) > 0) {
+        /* cheap check on local input buffer */
+        while (snd_seq_event_input_pending(seq, FALSE) > 0) {
+            /* check for and ignore errors, e.g. input overflow */
+            /* note: if there's overflow, this should be reported
+             * all the way through to client. Since input from all
+             * devices is merged, we need to find all input devices
+             * and set all to the overflow state.
+             * NOTE: this assumes every input is ALSA based.
+             */
+            int rslt = snd_seq_event_input(seq, &ev);
+            if (rslt >= 0) {
+                handle_event(ev);
+            } else if (rslt == -ENOSPC) {
+                int i;
+                for (i = 0; i < pm_descriptor_len; i++) {
+                    if (pm_descriptors[i].pub.input) {
+                        PmInternal *midi_i = pm_descriptors[i].pm_internal;
+                        /* careful, device may not be open! */
+                        if (midi_i) Pm_SetOverflow(midi_i->queue);
+                    }
+                }
+            }
+        }
+    }
+    return pmNoError;
+}
+
+
+static unsigned int alsa_check_host_error(PmInternal *midi)
+{
+    return FALSE;
+}
+
+
+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_check_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_check_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;
+
+    /* Register interface ALSA with create_virtual fn */
+    pm_add_interf("ALSA", &alsa_create_virtual, &alsa_delete_virtual);
+
+    /* Previously, the last parameter was SND_SEQ_NONBLOCK, but this 
+     * would cause messages to be dropped if the ALSA buffer fills up.
+     * The correct behavior is for writes to block until there is 
+     * room to send all the data. The client should normally allocate
+     * a large enough buffer to avoid blocking on output. 
+     * Now that blocking is enabled, the seq_event_input() will block
+     * if there is no input data. This is not what we want, so must
+     * call seq_event_input_pending() to avoid blocking.
+     */
+    err = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, 0);
+    if (err < 0) goto error_return;
+    
+    snd_seq_client_info_alloca(&cinfo);
+    snd_seq_port_info_alloca(&pinfo);
+
+    snd_seq_client_info_set_client(cinfo, -1);
+    while (snd_seq_query_next_client(seq, cinfo) == 0) {
+        snd_seq_port_info_set_client(pinfo,
+                                     snd_seq_client_info_get_client(cinfo));
+        snd_seq_port_info_set_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_len;
+                pm_add_device("ALSA",
+                        pm_strdup(snd_seq_port_info_get_name(pinfo)),
+                        FALSE, FALSE,
+                        MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
+                                        snd_seq_port_info_get_port(pinfo)),
+                        &pm_linuxalsa_out_dictionary);
+            }
+            if (caps & SND_SEQ_PORT_CAP_SUBS_READ) {
+                if (pm_default_input_device_id == -1) 
+                    pm_default_input_device_id = pm_descriptor_len;
+                pm_add_device("ALSA",
+                        pm_strdup(snd_seq_port_info_get_name(pinfo)),
+                        TRUE, FALSE,
+                        MAKE_DESCRIPTOR(snd_seq_port_info_get_client(pinfo),
+                                        snd_seq_port_info_get_port(pinfo)),
+                        &pm_linuxalsa_in_dictionary);
+            }
+        }
+    }
+    return pmNoError;
+ error_return:
+    pm_linuxalsa_term();  /* clean up */
+    return check_hosterror(err);
+}
+
+
+void pm_linuxalsa_term(void)
+{
+    if (seq) {
+        snd_seq_close(seq);
+        pm_free(pm_descriptors);
+        pm_descriptors = NULL;
+        pm_descriptor_len = 0;
+        pm_descriptor_max = 0;
+    }
+}
+
+#endif
diff --git a/portmidi/pm_linux/pmlinuxnull.c b/portmidi/pm_linux/pmlinuxnull.c
new file mode 100644
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.c
@@ -0,0 +1,31 @@
+/*
+ * pmlinuxnull.c -- system specific definitions
+ * 
+ * written by:
+ *  Roger Dannenberg
+ * 
+ * If there is no ALSA, you can define PMNULL and build PortMidi. It will
+ * not report any devices, so you will not be able to open any, but if
+ * you wanted to disable MIDI from some application, this could be used.
+ *    Mainly, this code shows the possibility of supporting multiple
+ * interfaces, e.g., ALSA and Sndio on BSD, or ALSA and Jack on Linux.
+ * But as of Dec, 2021, the only supported MIDI API for Linux is ALSA.
+ */
+
+#ifdef PMNULL
+
+#include "portmidi.h"
+#include "pmlinuxnull.h"
+
+
+PmError pm_linuxnull_init(void)
+{
+    return pmNoError;
+}
+
+
+void pm_linuxnull_term(void)
+{
+}
+
+#endif
diff --git a/portmidi/pm_linux/pmlinuxnull.h b/portmidi/pm_linux/pmlinuxnull.h
new file mode 100644
--- /dev/null
+++ b/portmidi/pm_linux/pmlinuxnull.h
@@ -0,0 +1,6 @@
+/* pmlinuxnull.h -- system-specific definitions */
+
+PmError pm_linuxnull_init(void);
+void pm_linuxnull_term(void);
+
+
diff --git a/portmidi/pm_mac/pmmac.c b/portmidi/pm_mac/pmmac.c
--- a/portmidi/pm_mac/pmmac.c
+++ b/portmidi/pm_mac/pmmac.c
@@ -1,42 +1,44 @@
-/* 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); }
-
-
+/* 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 "pmutil.h"
+#include "pminternal.h"
+#include "pmmacosxcm.h"
+
+void pm_init(void)
+{
+    pm_macosxcm_init();
+}
+
+
+void pm_term(void)
+{
+    pm_macosxcm_term();
+}
+
+PmDeviceID Pm_GetDefaultInputDeviceID(void) 
+{
+    Pm_Initialize();
+    return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID(void) {
+    Pm_Initialize();
+    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/portmidi/pm_mac/pmmac.h b/portmidi/pm_mac/pmmac.h
deleted file mode 100644
--- a/portmidi/pm_mac/pmmac.h
+++ /dev/null
@@ -1,4 +0,0 @@
-/* pmmac.h */
-
-extern PmDeviceID pm_default_input_device_id;
-extern PmDeviceID pm_default_output_device_id;
diff --git a/portmidi/pm_mac/pmmacosxcm.c b/portmidi/pm_mac/pmmacosxcm.c
--- a/portmidi/pm_mac/pmmacosxcm.c
+++ b/portmidi/pm_mac/pmmacosxcm.c
@@ -9,927 +9,1188 @@
  */
  
 /* Notes:
-    since the input and output streams are represented by MIDIEndpointRef
-    values and almost no other state, we store the MIDIEndpointRef on
-    descriptors[midi->device_id].descriptor. The only other state we need
-    is for errors: we need to know if there is an error and if so, what is
-    the error text. We use a structure with two kinds of
-    host error: "error" and "callback_error". That way, asynchronous callbacks
-    do not interfere with other error information.
-    
-    OS X does not seem to have an error-code-to-text function, so we will
-    just use text messages instead of error codes.
- */
-
-#include <stdlib.h>
-
-//#define CM_DEBUG 1
-
-#include "portmidi.h"
-#include "pmutil.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 */
-    /* allow for running status (is running status possible here? -rbd): -cpr */
-    unsigned char last_command; 
-    long last_msg_length;
-} midi_macosxcm_node, *midi_macosxcm_type;
-
-/* private function declarations */
-MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
-PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
-
-char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
-
-
-static int
-midi_length(long msg)
-{
-    int status, high, low;
-    static int high_lengths[] = {
-        1, 1, 1, 1, 1, 1, 1, 1,         /* 0x00 through 0x70 */
-        3, 3, 3, 3, 2, 2, 3, 1          /* 0x80 through 0xf0 */
-    };
-    static int low_lengths[] = {
-        1, 2, 3, 2, 1, 1, 1, 1,         /* 0xf0 through 0xf8 */
-        1, 1, 1, 1, 1, 1, 1, 1          /* 0xf9 through 0xff */
-    };
-
-    status = msg & 0xFF;
-    high = status >> 4;
-    low = status & 15;
-
-    return (high != 0xF) ? high_lengths[high] : low_lengths[low];
-}
-
-static PmTimestamp midi_synchronize(PmInternal *midi)
-{
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    UInt64 pm_stream_time_2 = 
-            AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
-    PmTimestamp real_time;
-    UInt64 pm_stream_time;
-    /* if latency is zero and this is an output, there is no 
-       time reference and midi_synchronize should never be called */
-    assert(midi->time_proc);
-    assert(!(midi->write_flag && midi->latency == 0));
-    do {
-         /* read real_time between two reads of stream time */
-         pm_stream_time = pm_stream_time_2;
-         real_time = (*midi->time_proc)(midi->time_info);
-         pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
-         /* repeat if more than 0.5 ms has elapsed */
-    } while (pm_stream_time_2 > pm_stream_time + 500000);
-    m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
-    m->sync_time = real_time;
-    return real_time;
-}
-
-
-static void
-process_packet(MIDIPacket *packet, PmEvent *event, 
-	       PmInternal *midi, midi_macosxcm_type m)
-{
-    /* handle a packet of MIDI messages from CoreMIDI */
-    /* there may be multiple short messages in one packet (!) */
-    unsigned int remaining_length = packet->length;
-    unsigned char *cur_packet_data = packet->data;
-    while (remaining_length > 0) {
-        if (cur_packet_data[0] == MIDI_SYSEX ||
-            /* are we in the middle of a sysex message? */
-            (m->last_command == 0 &&
-             !(cur_packet_data[0] & MIDI_STATUS_MASK))) {
-            m->last_command = 0; /* no running status */
-            unsigned int amt = pm_read_bytes(midi, cur_packet_data, 
-                                             remaining_length, 
-                                             event->timestamp);
-            remaining_length -= amt;
-            cur_packet_data += amt;
-        } else if (cur_packet_data[0] == MIDI_EOX) {
-            /* this should never happen, because pm_read_bytes should
-             * get and read all EOX bytes*/
-            midi->sysex_in_progress = FALSE;
-            m->last_command = 0;
-        } else if (cur_packet_data[0] & MIDI_STATUS_MASK) {
-            /* compute the length of the next (short) msg in packet */
-	    unsigned int cur_message_length = midi_length(cur_packet_data[0]);
-            if (cur_message_length > remaining_length) {
-#ifdef DEBUG
-                printf("PortMidi debug msg: not enough data");
-#endif
-		/* since there's no more data, we're done */
-		return;
-	    }
-	    m->last_msg_length = cur_message_length;
-	    m->last_command = cur_packet_data[0];
-	    switch (cur_message_length) {
-	    case 1:
-	        event->message = Pm_Message(cur_packet_data[0], 0, 0);
-		break; 
-	    case 2:
-	        event->message = Pm_Message(cur_packet_data[0], 
-					    cur_packet_data[1], 0);
-		break;
-	    case 3:
-	        event->message = Pm_Message(cur_packet_data[0],
-					    cur_packet_data[1], 
-					    cur_packet_data[2]);
-		break;
-	    default:
-                /* PortMIDI internal error; should never happen */
-                assert(cur_message_length == 1);
-	        return; /* give up on packet if continued after assert */
-	    }
-	    pm_read_short(midi, event);
-	    remaining_length -= m->last_msg_length;
-	    cur_packet_data += m->last_msg_length;
-	} else if (m->last_msg_length > remaining_length + 1) {
-	    /* we have running status, but not enough data */
-#ifdef DEBUG
-	    printf("PortMidi debug msg: not enough data in CoreMIDI packet");
-#endif
-	    /* since there's no more data, we're done */
-	    return;
-	} else { /* output message using running status */
-	    switch (m->last_msg_length) {
-	    case 1:
-	        event->message = Pm_Message(m->last_command, 0, 0);
-		break;
-	    case 2:
-	        event->message = Pm_Message(m->last_command, 
-					    cur_packet_data[0], 0);
-		break;
-	    case 3:
-	        event->message = Pm_Message(m->last_command, 
-					    cur_packet_data[0], 
-					    cur_packet_data[1]);
-		break;
-	    default:
-	        /* last_msg_length is invalid -- internal PortMIDI error */
-	        assert(m->last_msg_length == 1);
-	    }
-	    pm_read_short(midi, event);
-	    remaining_length -= (m->last_msg_length - 1);
-	    cur_packet_data += (m->last_msg_length - 1);
-	}
-    }
-}
-
-
-
-/* called when MIDI packets are received */
-static void
-readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
-{
-    PmInternal *midi;
-    midi_macosxcm_type m;
-    PmEvent event;
-    MIDIPacket *packet;
-    unsigned int packetIndex;
-    unsigned long now;
-    unsigned int status;
-    
-#ifdef CM_DEBUG
-    printf("readProc: numPackets %d: ", newPackets->numPackets);
-#endif
-
-    /* Retrieve the context for this connection */
-    midi = (PmInternal *) connRefCon;
-    m = (midi_macosxcm_type) midi->descriptor;
-    assert(m);
-    
-    /* synchronize time references every 100ms */
-    now = (*midi->time_proc)(midi->time_info);
-    if (m->first_message || m->sync_time + 100 /*ms*/ < now) { 
-        /* time to resync */
-        now = midi_synchronize(midi);
-        m->first_message = FALSE;
-    }
-    
-    packet = (MIDIPacket *) &newPackets->packet[0];
-    /* printf("readproc packet status %x length %d\n", packet->data[0], 
-               packet->length); */
-    for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
-        /* Set the timestamp and dispatch this message */
-        event.timestamp = 
-                (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) / 
-                (UInt64) 1000000;
-        status = packet->data[0];
-        /* process packet as sysex data if it begins with MIDI_SYSEX, or
-           MIDI_EOX or non-status byte with no running status */
-#ifdef CM_DEBUG
-        printf(" %d", packet->length);
-#endif
-        if (status == MIDI_SYSEX || status == MIDI_EOX || 
-            ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) {
-	    /* previously was: !(status & MIDI_STATUS_MASK)) {
-             * but this could mistake running status for sysex data
-             */
-            /* reset running status data -cpr */
-	    m->last_command = 0;
-	    m->last_msg_length = 0;
-            /* printf("sysex packet length: %d\n", packet->length); */
-            pm_read_bytes(midi, packet->data, packet->length, event.timestamp);
-        } else {
-            process_packet(packet, &event, midi, m);
-	}
-        packet = MIDIPacketNext(packet);
-    }
-#ifdef CM_DEBUG
-    printf("\n");
-#endif
-}
-
-static PmError
-midi_in_open(PmInternal *midi, void *driverInfo)
-{
-    MIDIEndpointRef endpoint;
-    midi_macosxcm_type m;
-    OSStatus macHostError;
-    
-    /* insure that we have a time_proc for timing */
-    if (midi->time_proc == NULL) {
-        if (!Pt_Started()) 
-            Pt_Start(1, 0, 0);
-        /* time_get does not take a parameter, so coerce */
-        midi->time_proc = (PmTimeProcPtr) Pt_Time;
-    }
-    
-    endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
-    if (endpoint == NULL) {
-        return pmInvalidDeviceId;
-    }
-
-    m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
-    midi->descriptor = m;
-    if (!m) {
-        return pmInsufficientMemory;
-    }
-    m->error[0] = 0;
-    m->callback_error[0] = 0;
-    m->sync_time = 0;
-    m->delta = 0;
-    m->last_time = 0;
-    m->first_message = TRUE;
-    m->sysex_mode = FALSE;
-    m->sysex_word = 0;
-    m->sysex_byte_count = 0;
-    m->packetList = NULL;
-    m->packet = NULL;
-    m->last_command = 0;
-    m->last_msg_length = 0;
-
-    macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
-    if (macHostError != noErr) {
-        pm_hosterror = macHostError;
-        sprintf(pm_hosterror_text, 
-                "Host error %ld: MIDIPortConnectSource() in midi_in_open()",
-                macHostError);
-        midi->descriptor = NULL;
-        pm_free(m);
-        return pmHostError;
-    }
-    
-    return pmNoError;
-}
-
-static PmError
-midi_in_close(PmInternal *midi)
-{
-    MIDIEndpointRef endpoint;
-    OSStatus macHostError;
-    PmError err = pmNoError;
-    
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    
-    if (!m) return pmBadPtr;
-
-    endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
-    if (endpoint == NULL) {
-        pm_hosterror = pmBadPtr;
-    }
-    
-    /* shut off the incoming messages before freeing data structures */
-    macHostError = MIDIPortDisconnectSource(portIn, endpoint);
-    if (macHostError != noErr) {
-        pm_hosterror = macHostError;
-        sprintf(pm_hosterror_text, 
-                "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()",
-                macHostError);
-        err = pmHostError;
-    }
-    
-    midi->descriptor = NULL;
-    pm_free(midi->descriptor);
-    
-    return err;
-}
-
-
-static PmError
-midi_out_open(PmInternal *midi, void *driverInfo)
-{
-    midi_macosxcm_type m;
-
-    m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
-    midi->descriptor = m;
-    if (!m) {
-        return pmInsufficientMemory;
-    }
-    m->error[0] = 0;
-    m->callback_error[0] = 0;
-    m->sync_time = 0;
-    m->delta = 0;
-    m->last_time = 0;
-    m->first_message = TRUE;
-    m->sysex_mode = FALSE;
-    m->sysex_word = 0;
-    m->sysex_byte_count = 0;
-    m->packetList = (MIDIPacketList *) m->packetBuffer;
-    m->packet = NULL;
-    m->last_command = 0;
-    m->last_msg_length = 0;
-
-    return pmNoError;
-}
-
-
-static PmError
-midi_out_close(PmInternal *midi)
-{
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    if (!m) return pmBadPtr;
-    
-    midi->descriptor = NULL;
-    pm_free(midi->descriptor);
-    
-    return pmNoError;
-}
-
-static PmError
-midi_abort(PmInternal *midi)
-{
-    return pmNoError;
-}
-
-
-static PmError
-midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
-{
-    OSStatus macHostError;
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    MIDIEndpointRef endpoint = 
-            (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
-    assert(m);
-    assert(endpoint);
-    if (m->packet != NULL) {
-        /* out of space, send the buffer and start refilling it */
-        macHostError = MIDISend(portOut, endpoint, m->packetList);
-        m->packet = NULL; /* indicate no data in packetList now */
-        if (macHostError != noErr) goto send_packet_error;
-    }
-    return pmNoError;
-    
-send_packet_error:
-    pm_hosterror = macHostError;
-    sprintf(pm_hosterror_text, 
-            "Host error %ld: MIDISend() in midi_write()",
-            macHostError);
-    return pmHostError;
-
-}
-
-
-static PmError
-send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, 
-            MIDITimeStamp timestamp)
-{
-    PmError err;
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    assert(m);
-    
-    /* printf("add %d to packet %lx len %d\n", message[0], m->packet, messageLength); */
-    m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer), 
-                                  m->packet, timestamp, messageLength, 
-                                  message);
-    if (m->packet == NULL) {
-        /* out of space, send the buffer and start refilling it */
-        /* make midi->packet non-null to fool midi_write_flush into sending */
-        m->packet = (MIDIPacket *) 4; 
-        if ((err = midi_write_flush(midi, timestamp)) != pmNoError) return err;
-        m->packet = MIDIPacketListInit(m->packetList);
-        assert(m->packet); /* if this fails, it's a programming error */
-        m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
-                                      m->packet, timestamp, messageLength, 
-                                      message);
-        assert(m->packet); /* can't run out of space on first message */           
-    }
-    return pmNoError;
-}    
-
-
-static PmError
-midi_write_short(PmInternal *midi, PmEvent *event)
-{
-    long when = event->timestamp;
-    long what = event->message;
-    MIDITimeStamp timestamp;
-    UInt64 when_ns;
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    Byte message[4];
-    unsigned int messageLength;
-
-    if (m->packet == NULL) {
-        m->packet = MIDIPacketListInit(m->packetList);
-        /* this can never fail, right? failure would indicate something 
-           unrecoverable */
-        assert(m->packet);
-    }
-    
-    /* compute timestamp */
-    if (when == 0) when = midi->now;
-    /* if latency == 0, midi->now is not valid. We will just set it to zero */
-    if (midi->latency == 0) when = 0;
-    when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
-    /* make sure we don't go backward in time */
-    if (when_ns < m->last_time) when_ns = m->last_time;
-    m->last_time = when_ns;
-    timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
-
-    message[0] = Pm_MessageStatus(what);
-    message[1] = Pm_MessageData1(what);
-    message[2] = Pm_MessageData2(what);
-    messageLength = midi_length(what);
-        
-    /* Add this message to the packet list */
-    return send_packet(midi, message, messageLength, timestamp);
-}
-
-
-static PmError 
-midi_begin_sysex(PmInternal *midi, PmTimestamp when)
-{
-    UInt64 when_ns;
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    assert(m);
-    m->sysex_byte_count = 0;
-    
-    /* compute timestamp */
-    if (when == 0) when = midi->now;
-    /* if latency == 0, midi->now is not valid. We will just set it to zero */
-    if (midi->latency == 0) when = 0;
-    when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
-    m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
-
-    if (m->packet == NULL) {
-        m->packet = MIDIPacketListInit(m->packetList);
-        /* this can never fail, right? failure would indicate something 
-           unrecoverable */
-        assert(m->packet);
-    }
-    return pmNoError;
-}
-
-
-static PmError
-midi_end_sysex(PmInternal *midi, PmTimestamp when)
-{
-    PmError err;
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    assert(m);
-    
-    /* make sure we don't go backward in time */
-    if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time;
-    
-    /* now send what's in the buffer */
-    err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
-                      m->sysex_timestamp);
-    m->sysex_byte_count = 0;
-    if (err != pmNoError) {
-        m->packet = NULL; /* flush everything in the packet list */
-        return err;
-    }
-    return pmNoError;
-}
-
-
-static PmError
-midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
-{
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    assert(m);
-    if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
-        PmError err = midi_end_sysex(midi, timestamp);
-        if (err != pmNoError) return err;
-    }
-    m->sysex_buffer[m->sysex_byte_count++] = byte;
-    return pmNoError;
-}
-
-
-static PmError
-midi_write_realtime(PmInternal *midi, PmEvent *event)
-{
-    /* to send a realtime message during a sysex message, first
-       flush all pending sysex bytes into packet list */
-    PmError err = midi_end_sysex(midi, 0);
-    if (err != pmNoError) return err;
-    /* then we can just do a normal midi_write_short */
-    return midi_write_short(midi, event);
-}
-
-static unsigned int midi_has_host_error(PmInternal *midi)
-{
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    return (m->callback_error[0] != 0) || (m->error[0] != 0);
-}
-
-
-static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)
-{
-    midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
-    msg[0] = 0; /* initialize to empty string */
-    if (m) { /* make sure there is an open device to examine */
-        if (m->error[0]) {
-            strncpy(msg, m->error, len);
-            m->error[0] = 0; /* clear the error */
-        } else if (m->callback_error[0]) {
-            strncpy(msg, m->callback_error, len);
-            m->callback_error[0] = 0; /* clear the error */
-        }
-        msg[len - 1] = 0; /* make sure string is terminated */
-    }
-}
-
-
-MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
-{
-    UInt64 nanos;
-    if (timestamp <= 0) {
-        return (MIDITimeStamp)0;
-    } else {
-        nanos = (UInt64)timestamp * (UInt64)1000000;
-        return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
-    }
-}
-
-PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
-{
-    UInt64 nanos;
-    nanos = AudioConvertHostTimeToNanos(timestamp);
-    return (PmTimestamp)(nanos / (UInt64)1000000);
-}
-
-
-//
-// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
-//////////////////////////////////////
-// Obtain the name of an endpoint without regard for whether it has connections.
-// The result should be released by the caller.
-CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
-{
-  CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
-  CFStringRef str;
-  
-  // begin with the endpoint's name
-  str = NULL;
-  MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
-  if (str != NULL) {
-    CFStringAppend(result, str);
-    CFRelease(str);
-  }
-  
-  MIDIEntityRef entity = NULL;
-  MIDIEndpointGetEntity(endpoint, &entity);
-  if (entity == NULL)
-    // probably virtual
-    return result;
-  
-  if (CFStringGetLength(result) == 0) {
-    // endpoint name has zero length -- try the entity
-    str = NULL;
-    MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
-    if (str != NULL) {
-      CFStringAppend(result, str);
-      CFRelease(str);
-    }
-  }
-  // now consider the device's name
-  MIDIDeviceRef device = NULL;
-  MIDIEntityGetDevice(entity, &device);
-  if (device == NULL)
-    return result;
-  
-  str = NULL;
-  MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
-  if (CFStringGetLength(result) == 0) {
-      CFRelease(result);
-      return str;
-  }
-  if (str != NULL) {
-    // if an external device has only one entity, throw away
-    // the endpoint name and just use the device name
-    if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
-      CFRelease(result);
-      return str;
-    } else {
-      if (CFStringGetLength(str) == 0) {
-        CFRelease(str);
-        return result;
-      }
-      // does the entity name already start with the device name?
-      // (some drivers do this though they shouldn't)
-      // if so, do not prepend
-        if (CFStringCompareWithOptions( result, /* endpoint name */
-             str /* device name */,
-             CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) {
-        // prepend the device name to the entity name
-        if (CFStringGetLength(result) > 0)
-          CFStringInsert(result, 0, CFSTR(" "));
-        CFStringInsert(result, 0, str);
-      }
-      CFRelease(str);
-    }
-  }
-  return result;
-}
-
-
-// Obtain the name of an endpoint, following connections.
-// The result should be released by the caller.
-static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)
-{
-  CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
-  CFStringRef str;
-  OSStatus err;
-  int i;
-  
-  // Does the endpoint have connections?
-  CFDataRef connections = NULL;
-  int nConnected = 0;
-  bool anyStrings = false;
-  err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);
-  if (connections != NULL) {
-    // It has connections, follow them
-    // Concatenate the names of all connected devices
-    nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);
-    if (nConnected) {
-      const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
-      for (i = 0; i < nConnected; ++i, ++pid) {
-        MIDIUniqueID id = EndianS32_BtoN(*pid);
-        MIDIObjectRef connObject;
-        MIDIObjectType connObjectType;
-        err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);
-        if (err == noErr) {
-          if (connObjectType == kMIDIObjectType_ExternalSource  ||
-              connObjectType == kMIDIObjectType_ExternalDestination) {
-            // Connected to an external device's endpoint (10.3 and later).
-            str = EndpointName((MIDIEndpointRef)(connObject), true);
-          } else {
-            // Connected to an external device (10.2) (or something else, catch-all)
-            str = NULL;
-            MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);
-          }
-          if (str != NULL) {
-            if (anyStrings)
-              CFStringAppend(result, CFSTR(", "));
-            else anyStrings = true;
-            CFStringAppend(result, str);
-            CFRelease(str);
-          }
-        }
-      }
-    }
-    CFRelease(connections);
-  }
-  if (anyStrings)
-    return result;
-  
-  // Here, either the endpoint had no connections, or we failed to obtain names for any of them.
-  return EndpointName(endpoint, false);
-}
-
-
-char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
-{
-#ifdef OLDCODE
-    MIDIEntityRef entity;
-    MIDIDeviceRef device;
-
-    CFStringRef endpointName = NULL;
-    CFStringRef deviceName = NULL;
-#endif
-    CFStringRef fullName = NULL;
-    CFStringEncoding defaultEncoding;
-    char* newName;
-
-    /* get the default string encoding */
-    defaultEncoding = CFStringGetSystemEncoding();
-
-    fullName = ConnectedEndpointName(endpoint);
-    
-#ifdef OLDCODE
-    /* get the entity and device info */
-    MIDIEndpointGetEntity(endpoint, &entity);
-    MIDIEntityGetDevice(entity, &device);
-
-    /* create the nicely formated name */
-    MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);
-    MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);
-    if (deviceName != NULL) {
-        fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),
-                                            deviceName, endpointName);
-    } else {
-        fullName = endpointName;
-    }
-#endif    
-    /* copy the string into our buffer */
-    newName = (char *) malloc(CFStringGetLength(fullName) + 1);
-    CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
-                       defaultEncoding);
-
-    /* clean up */
-#ifdef OLDCODE
-    if (endpointName) CFRelease(endpointName);
-    if (deviceName) CFRelease(deviceName);
-#endif
-    if (fullName) CFRelease(fullName);
-
-    return newName;
-}
-
- 
-
-pm_fns_node pm_macosx_in_dictionary = {
-    none_write_short,
-    none_sysex,
-    none_sysex,
-    none_write_byte,
-    none_write_short,
-    none_write_flush,
-    none_synchronize,
-    midi_in_open,
-    midi_abort,
-    midi_in_close,
-    success_poll,
-    midi_has_host_error,
-    midi_get_host_error,
-};
-
-pm_fns_node pm_macosx_out_dictionary = {
-    midi_write_short,
-    midi_begin_sysex,
-    midi_end_sysex,
-    midi_write_byte,
-    midi_write_realtime,
-    midi_write_flush,
-    midi_synchronize,
-    midi_out_open,
-    midi_abort,
-    midi_out_close,
-    success_poll,
-    midi_has_host_error,
-    midi_get_host_error,
-};
-
-
-PmError pm_macosxcm_init(void)
-{
-    ItemCount numInputs, numOutputs, numDevices;
-    MIDIEndpointRef endpoint;
-    int i;
-    OSStatus macHostError;
-    char *error_text;
-
-    /* Determine the number of MIDI devices on the system */
-    numDevices = MIDIGetNumberOfDevices();
-    numInputs = MIDIGetNumberOfSources();
-    numOutputs = MIDIGetNumberOfDestinations();
-
-    /* Return prematurely if no devices exist on the system
-       Note that this is not an error. There may be no devices.
-       Pm_CountDevices() will return zero, which is correct and
-       useful information
-     */
-    if (numDevices <= 0) {
-        return pmNoError;
-    }
-
-
-    /* Initialize the client handle */
-    macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);
-    if (macHostError != noErr) {
-        error_text = "MIDIClientCreate() in pm_macosxcm_init()";
-        goto error_return;
-    }
-
-    /* Create the input port */
-    macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
-                                          NULL, &portIn);
-    if (macHostError != noErr) {
-        error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
-        goto error_return;
-    }
-        
-    /* Create the output port */
-    macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
-    if (macHostError != noErr) {
-        error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
-        goto error_return;
-    }
-
-    /* Iterate over the MIDI input devices */
-    for (i = 0; i < numInputs; i++) {
-        endpoint = MIDIGetSource(i);
-        if (endpoint == NULL) {
-            continue;
-        }
-
-        /* set the first input we see to the default */
-        if (pm_default_input_device_id == -1)
-            pm_default_input_device_id = pm_descriptor_index;
-        
-        /* Register this device with PortMidi */
-        pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
-                      TRUE, (void*)endpoint, &pm_macosx_in_dictionary);
-    }
-
-    /* Iterate over the MIDI output devices */
-    for (i = 0; i < numOutputs; i++) {
-        endpoint = MIDIGetDestination(i);
-        if (endpoint == NULL) {
-            continue;
-        }
-
-        /* set the first output we see to the default */
-        if (pm_default_output_device_id == -1)
-            pm_default_output_device_id = pm_descriptor_index;
-
-        /* Register this device with PortMidi */
-        pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
-                      FALSE, (void*)endpoint, &pm_macosx_out_dictionary);
-    }
-    return pmNoError;
-    
-error_return:
-    pm_hosterror = macHostError;
-    sprintf(pm_hosterror_text, "Host error %ld: %s\n", macHostError, error_text);
-    pm_macosxcm_term(); /* clear out any opened ports */
-    return pmHostError;
-}
-
-void pm_macosxcm_term(void)
-{
-    if (client != NULL)	 MIDIClientDispose(client);
-    if (portIn != NULL)	 MIDIPortDispose(portIn);
-    if (portOut != NULL) MIDIPortDispose(portOut);
+
+    Since the input and output streams are represented by
+    MIDIEndpointRef values and almost no other state, we store the
+    MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor.
+    
+    OS X does not seem to have an error-code-to-text function, so we
+    will just use text messages instead of error codes.
+
+    Virtual device input synchronization: Once we create a virtual
+    device, it is always "on" and receiving messages, but it must drop
+    messages unless the device has been opened with Pm_OpenInput. To
+    open, the main thread should create all the data structures, then
+    call OSMemoryBarrier so that writes are observed, then set
+    is_opened = TRUE.  To close without locks, we need to get the
+    callback to set is_opened to FALSE before we free data structures;
+    otherwise, there's a race condition where closing could delete
+    structures in use by the virtual_read_callback function. We send
+    8 MIDI resets (FF) in a single packet to our own port to signal
+    the virtual_read_callback to close it. Then, we wait for the
+    callback to recognize the "close" packet and reset is_opened.
+ 
+    Device scanning is done when you first open an application.
+    PortMIDI does not actively update the devices. Instead, you must
+    Pm_Terminate() and Pm_Initialize(), basically starting over. But
+    CoreMIDI does not have a way to shut down(!), and even
+    MIDIClientDispose() somehow retains state (and docs say do not
+    call it even if it worked). The solution, apparently, is to
+    call CFRunLoopRunInMode(), which somehow updates CoreMIDI
+    state.
+ 
+    But when do we call CFRunLoopRunInMode()? I tried calling it
+    in midi_in_poll() which is called when you call Pm_Read() since
+    that is called often. I observed that this caused the program
+    to block for as long as 50ms and fairly often for 2 or 3ms.
+    What was Apple thinking? Is it really OK to design systems that
+    can only function with a tricky multi-threaded, non-blocking
+    priority-based solution, and then not provide a proof of concept
+    or documentation? Or is Apple's design really flawed? If anyone
+    at Apple reads this, please let me know -- I'm curious.
+ 
+    But I digress... Here's the PortMidi approach: Since
+    CFRunLoopRunInMode() is potentially a non-realtime operation,
+    we only call it in Pm_Initialize(), where other calls to look
+    up devices and device names are quite slow to begin with. Again,
+    PortMidi does not actively scan for new or deleted devices, so
+    if devices change, you won't see it until the next Pm_Terminate
+    and Pm_Initialize.
+ 
+    Calling CFRunLoopRunInMode() once is probably not enough. There
+    might be better way, but it seems to work to just call it 100
+    times and insert 20 1ms delays (in case some inter-process
+    communication or synchronization is going on).
+    This adds 20ms to the wall time of Pm_Initialize(), but it
+    typically runs 30ms to much more (~4s), so this has little impact.
+ */
+
+#include <stdlib.h>
+
+/* turn on lots of debugging print statements */
+#define CM_DEBUG if (0)
+/* #define CM_DEBUG if (1) */
+
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "porttime.h"
+#include "pmmacosxcm.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <CoreServices/CoreServices.h>
+#include <CoreMIDI/MIDIServices.h>
+#include <CoreAudio/HostTime.h>
+#include <unistd.h>
+#include <libkern/OSAtomic.h>
+
+#define PACKET_BUFFER_SIZE 1024
+/* maximum overall data rate (OS X limits MIDI rate in case there
+ * is a cycle among IAC ports.
+ */
+
+#define MAX_BYTES_PER_S 5400
+
+/* Apple reports that packets are dropped when the MIDI bytes/sec
+   exceeds 15000. This is computed by "tracking the number of MIDI
+   bytes scheduled into 1-second buckets over the last six seconds and
+   averaging these counts." This was confirmed in measurements
+   (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with
+   macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec.
+   Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to
+   5400 based on testing (which shows 5700 is too high) to fix the
+   packet loss problem that showed up with macOS 12.
+ 
+   Experiments show this restriction applies to IAC bus MIDI, but not
+   to hardware interfaces. (I measured 0.5 Mbps each way over USB to a
+   Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe
+   it would get 1 Mbps one-way, which would make the CoreMIDI
+   restriction 18x slower than USB. Maybe other USB MIDI
+   implementations are faster -- USB top speed for other protocols is
+   certainly higher than 1 Mbps!)
+
+   This is apparently based on timestamps, not on real time, so we
+   have to avoid constructing packets that schedule high speed output
+   regardless of when writes occur. The solution is to alter
+   timestamps to limit data rates.  This adds a slight time
+   distortion, e.g. an 11 note chord with all notes on the same
+   timestamp will be altered so that the last message is delayed by
+   11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms.
+   Note that this is about 2x MIDI speed, but at least 18x slower 
+   than USB MIDI.
+ 
+   Altering timestamps creates another problem, which is that a sender
+   that exceeds the maximum rate can queue up an unbounded number of
+   messages. With non-USB MIDI devices, you could be writing 5x faster
+   to CoreMIDI than the hardware interface can send, causing an
+   unbounded backlog, not to mention that the output stream will be a
+   steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms),
+   losing any original timing or rhythm. PortMidi does not guarantee
+   delivery if, over the long run, you write faster than the hardware
+   can send.
+   
+   The LIMIT_RATE symbol, if defined (which is the default), enables
+   code to modify timestamps for output to an IAC device as follows:
+
+   Before a packet is formed, the message timestamp is set to the
+   maximum of the PortMidi timestamp (converted to CoreMIDI time)
+   and min_next_time. After each send, min_next_time is updated to
+   the packet time + packet length * delay_per_byte, which limits
+   the scheduled bytes-per-second. Also, after each packet list
+   flush, min_next_time is updated to the maximum of min_next_time
+   and the real time, which prevents many bytes to be scheduled in
+   the past. (We could more directly just say packets are never
+   scheduled in the past, but we prefer to get the current time -- a
+   system call -- only when we perform the more expensive operation
+   of flushing packets, so that's when we update min_next_time to
+   the current real time. If we are sending a lot, we have to flush
+   a lot, so the time will be updated frequently when it matters.)
+
+   This possible adjustment to timestamps can distort accurate
+   timestamps by up to 0.556 us per 3-byte MIDI message.
+ 
+   Nothing blocks the sender from queueing up an arbitrary number of
+   messages. Timestamps should be used for accurate timing by sending
+   timestamped messages a little ahead of real time, not for
+   scheduling an entire MIDI sequence at once!
+ */
+#define LIMIT_RATE 1
+
+#define SYSEX_BUFFER_SIZE 128
+/* What is the maximum PortMidi device number for an IAC device? A
+ * cleaner design would be to not use the endpoint as our device
+ * representation. Instead, we could have a private extensible struct
+ * to keep all device information, including whether the device is
+ * implemented with the AppleMIDIIACDriver, which we need because we
+ * have to limit the data rate to this particular driver to avoid
+ * dropping messages. Rather than rewrite a lot of code, I am just
+ * allocating 64 bytes to flag which devices are IAC ones. If an IAC
+ * device number is greater than 63, PortMidi will fail to limit
+ * writes to it, but will not complain and will not access memory
+ * outside the 64-element array of char.
+ */
+#define MAX_IAC_NUM 63
+
+#define VERBOSE_ON 1
+#define VERBOSE if (VERBOSE_ON)
+
+#define MIDI_SYSEX       0xf0
+#define MIDI_EOX         0xf7
+#define MIDI_CLOCK       0xf8
+#define MIDI_STATUS_MASK 0x80
+
+/* "Ref"s are pointers on 32-bit machines and ints on 64 bit machines
+   NULL_REF is our representation of either 0 or NULL
+*/
+#ifdef __LP64__
+#define NULL_REF 0
+#else
+#define NULL_REF NULL
+#endif
+
+static MIDIClientRef client = NULL_REF;  /* Client handle to the MIDI server */
+static MIDIPortRef   portIn = NULL_REF;  /* Input port handle */
+static MIDIPortRef   portOut = NULL_REF;  /* Output port handle */
+static char          isIAC[MAX_IAC_NUM + 1];  /* is device an IAC device */
+
+extern pm_fns_node pm_macosx_in_dictionary;
+extern pm_fns_node pm_macosx_out_dictionary;
+
+typedef struct coremidi_info_struct {
+    int is_virtual;     /* virtual device (TRUE) or actual device (FALSE)? */
+    UInt64 delta;	/* difference between stream time and real time in ns */
+    int sysex_mode;     /* middle of sending sysex */
+    uint32_t sysex_word; /* accumulate data when receiving sysex */
+    uint32_t sysex_byte_count; /* count how many received */
+    char error[PM_HOST_ERROR_MSG_LEN];
+    char callback_error[PM_HOST_ERROR_MSG_LEN];
+    Byte packetBuffer[PACKET_BUFFER_SIZE];
+    MIDIPacketList *packetList; /* a pointer to packetBuffer */
+    MIDIPacket *packet;
+    Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
+    MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */
+    /* allow for running status (is running status possible here? -rbd): -cpr */
+    UInt64 min_next_time; /* when can the next send take place? (host time) */
+    int isIACdevice;
+    Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
+    UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
+} coremidi_info_node, *coremidi_info_type;
+
+/* private function declarations */
+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); /* returns host time */
+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); /* returns ms */
+
+char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag);
+
+static PmError check_hosterror(OSStatus err, const char *msg)
+{
+    if (err != noErr) {
+        snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg);
+        pm_hosterror = TRUE;
+        return pmHostError;
+    }
+    return pmNoError;
+}
+
+
+static PmTimestamp midi_synchronize(PmInternal *midi)
+{
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    UInt64 pm_stream_time_2 = /* current time in ns */
+            AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+    PmTimestamp real_time;  /* in ms */
+    UInt64 pm_stream_time;  /* in ns */
+    /* if latency is zero and this is an output, there is no 
+       time reference and midi_synchronize should never be called */
+    assert(midi->time_proc);
+    assert(midi->is_input || midi->latency != 0);
+    do {
+         /* read real_time between two reads of stream time */
+         pm_stream_time = pm_stream_time_2;
+         real_time = (*midi->time_proc)(midi->time_info);
+         pm_stream_time_2 = AudioConvertHostTimeToNanos(
+                                    AudioGetCurrentHostTime());
+         /* repeat if more than 0.5 ms has elapsed */
+    } while (pm_stream_time_2 > pm_stream_time + 500000);
+    info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
+    midi->sync_time = real_time;
+    return real_time;
+}
+
+
+/* called when MIDI packets are received */
+static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi)
+{
+    PmTimestamp timestamp;
+    MIDIPacket *packet;
+    unsigned int packetIndex;
+    uint32_t now;
+    /* Retrieve the context for this connection */
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    assert(info);
+    
+    CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets);
+    
+    /* synchronize time references every 100ms */
+    now = (*midi->time_proc)(midi->time_info);
+    if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) {
+        /* time to resync */
+        now = midi_synchronize(midi);
+        midi->first_message = FALSE;
+    }
+    
+    packet = (MIDIPacket *) &newPackets->packet[0];
+    /* hardware devices get untimed messages and apply timestamps. We
+     * want to preserve them because they should be more accurate than
+     * applying the current time here. virtual devices just pass on the
+     * packet->timeStamp, which could be anything. PortMidi says the 
+     * PortMidi timestamp is the time the message is received. We do not
+     * know if we are receiving from a device driver or a virtual device.
+     * PortMidi sends to virtual devices get a current timestamp, so we
+     * can treat them as the receive time. If the timestamp is zero,
+     * suggested by CoreMIDI as the value to use for immediate delivery,
+     * then we plug in `now` which is obtained above. If another
+     * application sends bogus non-zero timestamps, we will convert them
+     * to this port's reference time and pass them as event.timestamp.
+     * Receiver beware.
+     */
+    CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) "
+                    "status %x length %d\n",
+                    AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
+                    AudioGetCurrentHostTime(),
+                    packet->data[0], packet->length);
+    for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
+        /* Set the timestamp and dispatch this message */
+        CM_DEBUG printf("    packet->timeStamp %lld ns %lld host\n",
+                        packet->timeStamp,
+                        AudioConvertHostTimeToNanos(packet->timeStamp));
+        if (packet->timeStamp == 0) {
+            timestamp = now;
+        } else {
+            timestamp = (PmTimestamp) /* explicit conversion */ (
+                (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /
+                (UInt64) 1000000);
+        }
+        pm_read_bytes(midi, packet->data, packet->length, timestamp);
+        packet = MIDIPacketNext(packet);
+    }
+}
+
+/* callback for real devices - redirects to read_callback */
+static void device_read_callback(const MIDIPacketList *newPackets, 
+                                 void *refCon, void *connRefCon)
+{
+    read_callback(newPackets, (PmInternal *) connRefCon);
+}
+
+
+/* callback for virtual devices - redirects to read_callback */
+static void virtual_read_callback(const MIDIPacketList *newPackets, 
+                                  void *refCon, void *connRefCon)
+{
+    /* this refCon is the device ID -- if there is a valid ID and 
+       the pm_descriptors table has a non-null pointer to a PmInternal,
+       then then device is open and should receive this data */
+    PmDeviceID id = (PmDeviceID) (size_t) refCon;
+    if (id >= 0 && id < pm_descriptor_len) {
+        if (pm_descriptors[id].pub.opened) {
+            /* check for close request (7 reset status bytes): */
+            if (newPackets->numPackets == 1 &&
+                newPackets->packet[0].length == 8 &&
+                /* CoreMIDI declares packets with 4-byte alignment, so we
+                 * should be safe to test for 8 0xFF's as 2 32-bit values: */
+                *(SInt32 *) &newPackets->packet[0].data[0] == -1 &&
+                *(SInt32 *) &newPackets->packet[0].data[4] == -1) {
+                CM_DEBUG printf("got close request packet\n");
+                pm_descriptors[id].pub.opened = FALSE;
+                return;
+            } else {
+                read_callback(newPackets, pm_descriptors[id].pm_internal);
+            }
+        }
+    }
+}
+
+
+/* allocate and initialize our internal coremidi connection info */
+static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input)
+{
+    coremidi_info_type info = (coremidi_info_type)
+            pm_alloc(sizeof(coremidi_info_node));
+    if (!info) {
+        return NULL;
+    }
+    info->is_virtual = is_virtual;
+    info->delta = 0;
+    info->sysex_mode = FALSE;
+    info->sysex_word = 0;
+    info->sysex_byte_count = 0;
+    info->packet = NULL;
+    info->min_next_time = 0;
+    info->isIACdevice = FALSE;
+    info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();
+    info->host_ticks_per_byte =
+            (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));
+    info->packetList = (is_input ? NULL :
+                                   (MIDIPacketList *) info->packetBuffer);
+    return info;
+}
+
+
+static PmError midi_in_open(PmInternal *midi, void *driverInfo)
+{
+    MIDIEndpointRef endpoint;
+    coremidi_info_type info;
+    OSStatus macHostError;
+    int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
+    
+    /* if this is an external device, descriptor is a MIDIEndpointRef.
+     * if this is a virtual device for this application, descriptor is NULL.
+     */
+    if (!is_virtual) {
+        endpoint = (MIDIEndpointRef) (intptr_t)
+                   pm_descriptors[midi->device_id].descriptor;
+        if (endpoint == NULL_REF) {
+            return pmInvalidDeviceId;
+        }
+    }
+
+    info = create_macosxcm_info(is_virtual, TRUE);
+    midi->api_info = info;
+    if (!info) {
+        return pmInsufficientMemory;
+    }
+    if (!is_virtual) {
+        macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
+        if (macHostError != noErr) {
+            midi->api_info = NULL;
+            pm_free(info);
+            return  check_hosterror(macHostError,
+                            "MIDIPortConnectSource() in midi_in_open()");
+        }
+    }
+    return pmNoError;
+}
+
+static PmError midi_in_close(PmInternal *midi)
+{
+    MIDIEndpointRef endpoint;
+    OSStatus macHostError;
+    PmError err = pmNoError;
+    
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    
+    if (!info) return pmBadPtr;
+
+    endpoint = (MIDIEndpointRef) (intptr_t)
+               pm_descriptors[midi->device_id].descriptor;
+    if (endpoint == NULL_REF) {
+        return pmBadPtr;
+    }
+    
+    if (!info->is_virtual) {
+        /* shut off the incoming messages before freeing data structures */
+        macHostError = MIDIPortDisconnectSource(portIn, endpoint);
+        /* If the source closes, you get paramErr == -50 here. It seems
+         * possible to monitor changes like sources closing by getting
+         * notifications ALL changes, but the CoreMIDI documentation is
+         * really terrible overall, and it seems easier to just ignore
+         * this host error.
+         */
+        if (macHostError != noErr && macHostError != -50) {
+            pm_hosterror = TRUE;
+            err = check_hosterror(macHostError, 
+                          "MIDIPortDisconnectSource() in midi_in_close()");
+        }
+    } else {
+        /* make "close virtual port" message */
+        SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;
+        /* memory requirements: packet count (4), timestamp (8), length (2),
+         *     data (8). Total: 22, but we allocate plenty more:
+         */
+        Byte packetBuffer[64];
+        MIDIPacketList *plist = (MIDIPacketList *) packetBuffer;
+        MIDIPacket *packet = MIDIPacketListInit(plist);
+        MIDIPacketListAdd(plist, 64, packet, 0, 8,
+                          (const Byte *) &close_port_bytes);
+        macHostError = MIDISend(portOut, endpoint, plist);
+        if (macHostError != noErr) {
+            err = check_hosterror(macHostError, "MIDISend() (PortMidi close "
+                                  "port packet) in midi_in_close()");
+        }            
+        /* when packet is delivered, callback thread will clear opened;
+         * we must wait for that before removing the input queues etc.
+         * Maybe this could use signals of some kind, but if signals use
+         * locks, locks can cause priority inversion problems, so we will
+         * just sleep as needed. On the MIDI timescale, inserting a 0.5ms
+         * latency should be OK, as the application has no business
+         * opening/closing devices during time-critical moments.
+         *
+         * We expect the MIDI thread to close the device quickly (<0.5ms),
+         * but we wait up to 50ms in case something terrible happens like
+         * getting paged out in the middle of deliving packets to this
+         * virtual device. If there is still no response, we time out and
+         * force the close without the MIDI thread (even this will probably
+         * succeed - the problem would be: this thread proceeds to delete
+         * the input queues, and the freed memory is reallocated and
+         * overwritten so that queues are no longer usable. Meanwhile,
+         * the MIDI thread has already begun to deliver packets, so the
+         * check for opened == TRUE passed, but MIDI thread does not insert
+         * into queue until queue is freed, reallocated and overwritten.
+         */
+        for (int i = 0; i < 100; i++) {  /* up to 50ms delay */
+            if (!pm_descriptors[midi->device_id].pub.opened) {
+                break;
+            }
+            usleep(500);  /* 0.5ms */
+        }
+        pm_descriptors[midi->device_id].pub.opened = FALSE;  /* force it */
+    }
+    midi->api_info = NULL;
+    pm_free(info);
+    return err;
+}
+
+
+static PmError midi_out_open(PmInternal *midi, void *driverInfo)
+{
+    coremidi_info_type info;
+    int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
+
+    info = create_macosxcm_info(is_virtual, FALSE);
+    if (midi->device_id <= MAX_IAC_NUM) {
+        info->isIACdevice = isIAC[midi->device_id];
+        CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice);
+    }
+    midi->api_info = info;
+    if (!info) {
+        return pmInsufficientMemory;
+    }
+    return pmNoError;
+}
+
+
+static PmError midi_out_close(PmInternal *midi)
+{
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    if (!info) return pmBadPtr;
+    midi->api_info = NULL;
+    pm_free(info);
+    return pmNoError;
+}
+
+
+/* MIDIDestinationCreate apparently cannot create a virtual device
+ * without a callback and a "refCon" parameter, but when we create
+ * a virtual device, we do not want a PortMidi stream yet -- that
+ * should wait for the user to open the stream. So, for the refCon,
+ * use the PortMidi device ID. The callback will check if the
+ * device is opened within PortMidi, and if so, use the pm_descriptors
+ * table to locate the corresponding PmStream.
+ */
+static PmError midi_create_virtual(int is_input, const char *name,
+                                   void *device_info)
+{
+    OSStatus macHostError;
+    MIDIEndpointRef endpoint;
+    CFStringRef nameRef;
+    PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL,
+                                  (is_input ? &pm_macosx_in_dictionary :
+                                              &pm_macosx_out_dictionary));
+    if (id < 0) {  /* error -- out of memory or name conflict? */
+        return id;
+    }
+
+    nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
+    if (is_input) {
+        macHostError = MIDIDestinationCreate(client, nameRef, 
+                virtual_read_callback, (void *) (intptr_t) id, &endpoint);
+    } else {
+        macHostError = MIDISourceCreate(client, nameRef, &endpoint);
+    }
+    CFRelease(nameRef);
+
+    if (macHostError != noErr) {
+        /* undo the device we just allocated */
+        pm_undo_add_device(id);
+        return check_hosterror(macHostError, (is_input ?
+                "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" :
+                "MIDISourceCreateWithProtocol() in midi_create_virtual()"));
+    }
+
+    /* Do we have a manufacturer name? If not, set to "PortMidi" */
+    const char *mfr_name = "PortMidi";
+    PmSysDepInfo *info = (PmSysDepInfo *) device_info;
+    /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */
+    if (info && info->structVersion >= 210) {
+        int i;
+        for (i = 0; i < info->length; i++) {  /* search for key */
+            if (info->properties[i].key == pmKeyCoreMidiManufacturer) {
+                mfr_name = info->properties[i].value;
+                break;
+            }  /* no other keys are recognized; they are ignored */
+        }
+    }
+    nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8);
+    MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef);
+    CFRelease(nameRef);
+
+    pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint;
+    return id;
+}
+
+
+static PmError midi_delete_virtual(PmDeviceID id)
+{
+    MIDIEndpointRef endpoint;
+    OSStatus macHostError;
+    
+    endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor;
+    if (endpoint == NULL_REF) {
+        return pmBadPtr;
+    }
+    macHostError = MIDIEndpointDispose(endpoint);
+    return check_hosterror(macHostError,
+                           "MIDIEndpointDispose() in midi_in_close()");
+}
+
+
+static PmError midi_abort(PmInternal *midi)
+{
+    OSStatus macHostError;
+    MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
+                               pm_descriptors[midi->device_id].descriptor;
+    macHostError = MIDIFlushOutput(endpoint);
+    return check_hosterror(macHostError,
+                           "MIDIFlushOutput() in midi_abort()");
+}
+
+
+static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+    OSStatus macHostError = noErr;
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
+                               pm_descriptors[midi->device_id].descriptor;
+    assert(info);
+    assert(endpoint);
+    if (info->packet != NULL) {
+        /* out of space, send the buffer and start refilling it */
+        /* update min_next_time each flush to support rate limit */
+        UInt64 host_now =  AudioGetCurrentHostTime();
+        if (host_now > info->min_next_time) 
+            info->min_next_time = host_now;
+        if (info->is_virtual) {
+            macHostError = MIDIReceived(endpoint, info->packetList);
+        } else {
+            macHostError = MIDISend(portOut, endpoint, info->packetList);
+        }
+        info->packet = NULL; /* indicate no data in packetList now */
+    }
+    return check_hosterror(macHostError, (info->is_virtual ?
+                                          "MIDIReceived() in midi_write()" :
+                                          "MIDISend() in midi_write()"));
+}
+
+
+static PmError send_packet(PmInternal *midi, Byte *message,
+                           unsigned int messageLength, MIDITimeStamp timestamp)
+{
+    PmError err;
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    assert(info);
+    
+    CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns "
+                    "(host %lld)\n",
+                    message[0], info->packet, messageLength, timestamp,
+                    AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
+                    AudioGetCurrentHostTime());
+    info->packet = MIDIPacketListAdd(info->packetList,
+                                     sizeof(info->packetBuffer), info->packet,
+                                     timestamp, messageLength, message);
+    if (info->packet == NULL) {
+        /* out of space, send the buffer and start refilling it */
+        /* make midi->packet non-null to fool midi_write_flush into sending */
+        info->packet = (MIDIPacket *) 4;
+        /* timestamp is 0 because midi_write_flush ignores timestamp since
+         * timestamps are already in packets. The timestamp parameter is here
+         * because other API's need it. midi_write_flush can be called 
+         * from system-independent code that must be cross-API.
+         */
+        if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;
+        info->packet = MIDIPacketListInit(info->packetList);
+        assert(info->packet); /* if this fails, it's a programming error */
+        info->packet = MIDIPacketListAdd(info->packetList,
+                               sizeof(info->packetBuffer), info->packet,
+                               timestamp, messageLength, message);
+        assert(info->packet); /* can't run out of space on first message */
+    }
+    return pmNoError;
+}    
+
+
+static PmError midi_write_short(PmInternal *midi, PmEvent *event)
+{
+    PmTimestamp when = event->timestamp;
+    PmMessage what = event->message;
+    MIDITimeStamp timestamp;
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    Byte message[4];
+    unsigned int messageLength;
+
+    if (info->packet == NULL) {
+        info->packet = MIDIPacketListInit(info->packetList);
+        /* this can never fail, right? failure would indicate something 
+           unrecoverable */
+        assert(info->packet);
+    }
+    
+    /* PortMidi specifies that incoming timestamps are the receive
+     * time.  Devices attach their receive times, but virtual devices
+     * do not. Instead, they pass along whatever timestamp was sent to
+     * them. We do not know if we are connected to real or virtual
+     * device. To avoid wild timestamps on the receiving end, we
+     * consider 2 cases: PortMidi timestamp is zero or latency is
+     * zero. Both mean send immediately, so we attach the current time
+     * which will go out immediately and arrive with a sensible
+     * timestamp (not zero and not zero mapped to the client's local
+     * time). Otherwise, we assume the timestamp is reasonable. It
+     * might be slighly in the past, but we pass it along after
+     * translation to MIDITimeStamp units. 
+     *
+     * Compute timestamp: use current time if timestamp is zero or
+     * latency is zero. Both mean no timing and send immediately.
+     */
+    if (when == 0 || midi->latency == 0) {
+        timestamp = AudioGetCurrentHostTime();
+    } else {  /* translate PortMidi time + latency to CoreMIDI time */
+        timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
+                    info->delta;
+        timestamp = AudioConvertNanosToHostTime(timestamp);
+    }
+
+    message[0] = Pm_MessageStatus(what);
+    message[1] = Pm_MessageData1(what);
+    message[2] = Pm_MessageData2(what);
+    messageLength = pm_midi_length((int32_t) what);
+
+#ifdef LIMIT_RATE
+    /* Make sure we go forward in time. */
+    if (timestamp < info->min_next_time) {
+        timestamp = info->min_next_time;
+    }
+    /* Note that if application is way behind and slowly catching up, then
+     * timestamps could be increasing faster than real time, and since 
+     * timestamps are used to estimate data rate, our estimate could be
+     * low, causing CoreMIDI to drop packets. This seems very unlikely.
+     */
+    if (info->isIACdevice || info->is_virtual) {
+        info->min_next_time = timestamp + messageLength * 
+                                          info->host_ticks_per_byte;
+    }
+#endif
+    /* Add this message to the packet list */
+    return send_packet(midi, message, messageLength, timestamp);
+}
+
+
+static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)
+{
+    UInt64 when_ns;
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    assert(info);
+    info->sysex_byte_count = 0;
+    
+    /* compute timestamp */
+    if (when == 0) when = midi->now;
+    /* if latency == 0, midi->now is not valid. We will just set it to zero */
+    if (midi->latency == 0) when = 0;
+    when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
+              info->delta;
+    info->sysex_timestamp =
+              (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
+    UInt64 now; /* only make system time call when writing a virtual port */
+    if (info->is_virtual && info->sysex_timestamp <
+        (now = AudioGetCurrentHostTime())) {
+        info->sysex_timestamp = now;
+    }
+
+    if (info->packet == NULL) {
+        info->packet = MIDIPacketListInit(info->packetList);
+        /* this can never fail, right? failure would indicate something 
+           unrecoverable */
+        assert(info->packet);
+    }
+    return pmNoError;
+}
+
+
+static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when)
+{
+    PmError err;
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    assert(info);
+    
+#ifdef LIMIT_RATE
+    /* make sure we go foreward in time */
+    if (info->sysex_timestamp < info->min_next_time)
+        info->sysex_timestamp = info->min_next_time;
+
+    if (info->isIACdevice) {
+        info->min_next_time = info->sysex_timestamp + info->sysex_byte_count *
+                                                      info->host_ticks_per_byte;
+    }
+#endif
+    
+    /* now send what's in the buffer */
+    err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count,
+                      info->sysex_timestamp);
+    info->sysex_byte_count = 0;
+    if (err != pmNoError) {
+        info->packet = NULL; /* flush everything in the packet list */
+    }
+    return err;
+}
+
+
+static PmError midi_write_byte(PmInternal *midi, unsigned char byte, 
+                               PmTimestamp timestamp)
+{
+    coremidi_info_type info = (coremidi_info_type) midi->api_info;
+    assert(info);
+    if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
+        PmError err = midi_end_sysex(midi, timestamp);
+        if (err != pmNoError) return err;
+    }
+    info->sysex_buffer[info->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_check_host_error(PmInternal *midi)
+{
+    return FALSE;
+}
+
+
+MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
+{
+    UInt64 nanos;
+    if (timestamp <= 0) {
+        return (MIDITimeStamp)0;
+    } else {
+        nanos = (UInt64)timestamp * (UInt64)1000000;
+        return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
+    }
+}
+
+
+PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
+{
+    UInt64 nanos;
+    nanos = AudioConvertHostTimeToNanos(timestamp);
+    return (PmTimestamp)(nanos / (UInt64)1000000);
+}
+
+
+/* Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
+ *
+ * Obtain the name of an endpoint without regard for whether it has connections.
+ * The result should be released by the caller.
+ */
+CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal,
+                         int *iac_flag)
+{
+    CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
+    CFStringRef str;
+    *iac_flag = FALSE;
+  
+    /* begin with the endpoint's name */
+    str = NULL;
+    MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
+    if (str != NULL) {
+        CFStringAppend(result, str);
+        CFRelease(str);
+    }
+    MIDIEntityRef entity = NULL_REF;
+    MIDIEndpointGetEntity(endpoint, &entity);
+    if (entity == NULL_REF) {
+        /* probably virtual */
+        return result;
+    }
+    if (!isExternal) { /* detect IAC devices */
+        /* extern const CFStringRef kMIDIPropertyDriverOwner; */
+        MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str);
+        if (str != NULL) {
+            char s[32]; /* driver name may truncate, but that's OK */
+            CFStringGetCString(str, s, 31, kCFStringEncodingUTF8);
+            s[31] = 0;  /* make sure it is terminated just to be safe */
+            CM_DEBUG printf("driver %s\n", s);
+            *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0);
+        }
+    }
+
+    if (CFStringGetLength(result) == 0) {
+        /* endpoint name has zero length -- try the entity */
+        str = NULL;
+        MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
+        if (str != NULL) {
+            CFStringAppend(result, str);
+            CFRelease(str);
+        }
+    }
+    /* now consider the device's name */
+    MIDIDeviceRef device = NULL_REF;
+    MIDIEntityGetDevice(entity, &device);
+    if (device == NULL_REF)
+        return result;
+  
+    str = NULL;
+    MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
+    if (CFStringGetLength(result) == 0) {
+        CFRelease(result);
+        return str;
+    }
+    if (str != NULL) {
+        /* if an external device has only one entity, throw away
+           the endpoint name and just use the device name
+        */
+        if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
+            CFRelease(result);
+            return str;
+        } else {
+            if (CFStringGetLength(str) == 0) {
+                CFRelease(str);
+                return result;
+            }
+            /* does the entity name already start with the device name?
+               (some drivers do this though they shouldn't)
+               if so, do not prepend
+            */
+            if (CFStringCompareWithOptions(result, /* endpoint name */
+                        str, /* device name */
+                        CFRangeMake(0, CFStringGetLength(str)), 0) != 
+                kCFCompareEqualTo) {
+                /* prepend the device name to the entity name */
+                if (CFStringGetLength(result) > 0)
+                    CFStringInsert(result, 0, CFSTR(" "));
+                CFStringInsert(result, 0, str);
+            }
+            CFRelease(str);
+        }
+    }
+    return result;
+}
+
+
+/* Obtain the name of an endpoint, following connections.
+   The result should be released by the caller.
+*/
+static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,
+                                         int *iac_flag)
+{
+    CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
+    CFStringRef str;
+    OSStatus err;
+    long i;
+  
+    /* Does the endpoint have connections? */
+    CFDataRef connections = NULL;
+    long nConnected = 0;
+    bool anyStrings = false;
+    err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID,
+                                    &connections);
+    if (connections != NULL) {
+        /* It has connections, follow them
+           Concatenate the names of all connected devices
+        */
+        nConnected = CFDataGetLength(connections) / 
+                     (int32_t) sizeof(MIDIUniqueID);
+        if (nConnected) {
+            const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
+            for (i = 0; i < nConnected; ++i, ++pid) {
+                MIDIUniqueID id = EndianS32_BtoN(*pid);
+                MIDIObjectRef connObject;
+                MIDIObjectType connObjectType;
+                err = MIDIObjectFindByUniqueID(id, &connObject, 
+                                               &connObjectType);
+                if (err == noErr) {
+                    if (connObjectType == kMIDIObjectType_ExternalSource  ||
+                        connObjectType == kMIDIObjectType_ExternalDestination) {
+                        /* Connected to an external device's endpoint (>=10.3) */
+                        str = EndpointName((MIDIEndpointRef)(connObject), true,
+                                           iac_flag);
+                    } else {
+                        /* Connected to an external device (10.2) 
+                           (or something else, catch-all)
+                        */
+                        str = NULL;
+                        MIDIObjectGetStringProperty(connObject, 
+                                                    kMIDIPropertyName, &str);
+                    }
+                    if (str != NULL) {
+                        if (anyStrings)
+                            CFStringAppend(result, CFSTR(", "));
+                        else anyStrings = true;
+                        CFStringAppend(result, str);
+                        CFRelease(str);
+                    }
+                }
+            }
+        }
+        CFRelease(connections);
+    }
+    if (anyStrings)
+        return result; /* caller should release result */
+
+    CFRelease(result);
+
+    /* Here, either the endpoint had no connections, or we failed to
+       obtain names for any of them.
+    */
+    return EndpointName(endpoint, false, iac_flag);
+}
+
+
+/* cm_get_full_endpoint_name -- returns UTF-8 namem for endpoint.
+ *     caller is owner and responsible for pm_free'ing the string
+ */
+char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag)
+{
+    /* Thanks to Dan Wilcox for fixes for Unicode handling
+     * and kichikuou at github for fixing buffer size calculation.
+     */
+    CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag);
+    CFIndex len = CFStringGetLength(fullName) + 1;
+    /* (len seems to be length in Unicode characters, although docs add
+     *  the confusing explanation "in terms of UTF-16 code pairs")
+     */
+    CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding(
+                                   len, kCFStringEncodingUTF8) + 1;
+    char* pmname = (char *) pm_alloc(max_byte_len);
+
+    /* copy the string into our buffer; note that there may be some wasted
+       space, but the total waste is not large */
+    CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8);
+
+    /* clean up */
+    if (fullName) CFRelease(fullName);
+    return pmname;
+}
+
+
+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_check_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_check_host_error
+};
+
+
+/* We do nothing with callbacks, but generating the callbacks also
+ * updates CoreMIDI state. Callback may not be essential, but calling
+ * the CFRunLoopRunInMode is necessary.
+ */
+void cm_notify(const MIDINotification *msg, void *refCon)
+{
+    /*  for debugging, trace change notifications: 
+    const char *descr[] = {
+        "undefined (0)",
+      	"kMIDIMsgSetupChanged",
+        "kMIDIMsgObjectAdded",
+        "kMIDIMsgObjectRemoved",
+        "kMIDIMsgPropertyChanged",
+        "kMIDIMsgThruConnectionsChanged",
+        "kMIDIMsgSerialPortOwnerChanged",
+        "kMIDIMsgIOError"};
+
+    printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID,
+           descr[(int) msg->messageID]);
+    */
+    return;
+}
+
+
+PmError pm_macosxcm_init(void)
+{
+    ItemCount numInputs, numOutputs, numDevices;
+    MIDIEndpointRef endpoint;
+    OSStatus macHostError = noErr;
+    const char *error_text;
+
+    memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */
+
+    /* Register interface CoreMIDI with create_virtual fn */
+    pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual);
+    /* no check for error return because this always succeeds */
+
+    /* Determine the number of MIDI devices on the system */
+    numDevices = MIDIGetNumberOfDevices();
+
+    /* 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 */
+    if (client == NULL_REF) {
+        macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL,
+                                        &client);
+    } else {  /* see notes above on device scanning */
+        for (int i = 0; i < 100; i++) {
+            /* look for any changes before scanning for devices: */
+            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
+            if (i % 5 == 0) Pt_Sleep(1);  /* insert 20 delays */
+        }
+    }
+    if (macHostError != noErr) {
+        error_text = "MIDIClientCreate() in pm_macosxcm_init()";
+        goto error_return;
+    }
+    numInputs = MIDIGetNumberOfSources();
+    numOutputs = MIDIGetNumberOfDestinations();
+
+    /* Create the input port */
+    macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), 
+                                       device_read_callback, NULL, &portIn);
+    if (macHostError != noErr) {
+        error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
+        goto error_return;
+    }
+
+    /* Create the output port */
+    macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
+    if (macHostError != noErr) {
+        error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
+        goto error_return;
+    }
+
+    /* Iterate over the MIDI input devices */
+    for (int i = 0; i < numInputs; i++) {
+        int iac_flag;
+        endpoint = MIDIGetSource(i);
+        if (endpoint == NULL_REF) {
+            continue;
+        }
+        /* Register this device with PortMidi */
+        char *name = cm_get_full_endpoint_name(endpoint, &iac_flag);
+        pm_add_device("CoreMIDI", name, TRUE, FALSE,
+                      (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary);
+        /* pm_add_device copies name, so it is no longer needed */
+        pm_free(name);
+    }
+
+    /* Iterate over the MIDI output devices */
+    for (int i = 0; i < numOutputs; i++) {
+        int iac_flag;
+        PmDeviceID id;
+        endpoint = MIDIGetDestination(i);
+        if (endpoint == NULL_REF) {
+            continue;
+        }
+        /* Register this device with PortMidi */
+        char *name = cm_get_full_endpoint_name(endpoint, &iac_flag);
+        id = pm_add_device("CoreMIDI", name, FALSE, FALSE,
+                (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary);
+        /* pm_add_device copies name, so it is no longer needed */
+        pm_free(name);
+
+        /* if this is an IAC device, tuck that info away for write functions */
+        if (iac_flag && id <= MAX_IAC_NUM) {
+            isIAC[id] = TRUE;
+        }
+    }
+    return pmNoError;
+    
+error_return:
+    pm_macosxcm_term(); /* clear out any opened ports */
+    return check_hosterror(macHostError, error_text);
+}
+
+void pm_macosxcm_term(void)
+{
+    /* docs say do not explicitly dispose of client
+       if (client != NULL_REF) MIDIClientDispose(client); */
+    if (portIn != NULL_REF) MIDIPortDispose(portIn);
+    if (portOut != NULL_REF) MIDIPortDispose(portOut);
 }
diff --git a/portmidi/pm_mac/pmmacosxcm.h b/portmidi/pm_mac/pmmacosxcm.h
--- a/portmidi/pm_mac/pmmacosxcm.h
+++ b/portmidi/pm_mac/pmmacosxcm.h
@@ -1,4 +1,4 @@
-/* system-specific definitions */
-
-PmError pm_macosxcm_init(void);
+/* system-specific definitions */
+
+PmError pm_macosxcm_init(void);
 void pm_macosxcm_term(void);
diff --git a/portmidi/pm_win/pmwin.c b/portmidi/pm_win/pmwin.c
--- a/portmidi/pm_win/pmwin.c
+++ b/portmidi/pm_win/pmwin.c
@@ -1,114 +1,98 @@
-/* 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 "pmutil.h"
-#include "pminternal.h"
-#include "pmwinmm.h"
-#ifdef USE_DLL_FOR_CLEANUP
-#include "pmdll.h" /* used to close ports on exit */
-#endif
-#ifdef DEBUG
-#include "stdio.h"
-#endif
-
-/* 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); 
-}
-
+/* 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 "pmutil.h"
+#include "pminternal.h"
+#include "pmwinmm.h"
+#ifdef DEBUG
+#include "stdio.h"
+#endif
+#include <windows.h>
+
+/* 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();
+}
+
+
+static BOOL WINAPI ctrl_c_handler(DWORD fdwCtrlType)
+{
+    exit(1);  /* invokes pm_exit() */
+    ExitProcess(1);  /* probably never called */
+    return TRUE;
+}
+
+/* pm_init is the windows-dependent initialization.*/
+void pm_init(void)
+{
+    atexit(pm_exit);
+    SetConsoleCtrlHandler(ctrl_c_handler, TRUE);
+#ifdef DEBUG
+    printf("registered pm_exit with atexit()\n");
+#endif
+    pm_winmm_init();
+    /* initialize other APIs (DirectX?) here */
+}
+
+
+void pm_term(void) {
+    pm_winmm_term();
+}
+
+
+static PmDeviceID pm_get_default_device_id(int is_input, char *key) {
+#define PATTERN_MAX 256
+    /* Find first input or device -- this is the default. */
+    PmDeviceID id = pmNoDevice;
+    int i;
+    Pm_Initialize(); /* make sure descriptors exist! */
+    for (i = 0; i < pm_descriptor_len; i++) {
+        if (pm_descriptors[i].pub.input == is_input) {
+            id = i;
+            break;
+        }
+    }
+    return id;
+}
+
+
+PmDeviceID Pm_GetDefaultInputDeviceID(void) {
+    return pm_get_default_device_id(TRUE, 
+           "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/I/N/P/U/T_/D/E/V/I/C/E");
+}
+
+
+PmDeviceID Pm_GetDefaultOutputDeviceID(void) {
+  return pm_get_default_device_id(FALSE,
+          "/P/M_/R/E/C/O/M/M/E/N/D/E/D_/O/U/T/P/U/T_/D/E/V/I/C/E");
+}
+
+
+#include "stdio.h" 
+
+void *pm_alloc(size_t s) {
+    return malloc(s); 
+}
+
+
+void pm_free(void *ptr) { 
+    free(ptr); 
+}
+
+
diff --git a/portmidi/pm_win/pmwinmm.c b/portmidi/pm_win/pmwinmm.c
--- a/portmidi/pm_win/pmwinmm.c
+++ b/portmidi/pm_win/pmwinmm.c
@@ -1,1449 +1,1196 @@
-/* pmwinmm.c -- system specific definitions */
-
-#ifndef _WIN32_WINNT
-    /* without this define, InitializeCriticalSectionAndSpinCount is 
-     * undefined. This version level means "Windows 2000 and higher" 
-     */
-    #define _WIN32_WINNT 0x0500
-#endif
-
-#include "windows.h"
-#include "mmsystem.h"
-#include "portmidi.h"
-#include "pmutil.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>
-#define DEBUG 1
-#ifdef DEBUG
-/* this printf stuff really important for debugging client app w/host errors.
-    probably want to do something else besides read/write from/to console
-    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_PTR dwInstance, 
-                                       DWORD_PTR dwParam1, DWORD_PTR dwParam2);
-static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
-                                              DWORD_PTR dwInstance, DWORD_PTR dwParam1, 
-                                              DWORD_PTR dwParam2);
-static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
-                                        DWORD_PTR dwInstance, DWORD_PTR dwParam1, 
-                                        DWORD_PTR dwParam2);
-
-extern pm_fns_node pm_winmm_in_dictionary;
-extern pm_fns_node pm_winmm_out_dictionary;
-
-static void winmm_out_delete(PmInternal *midi); /* forward reference */
-
-/*
-A note about buffers: WinMM seems to hold onto buffers longer than
-one would expect, e.g. when I tried using 2 small buffers to send
-long sysex messages, at some point WinMM held both buffers. This problem
-was fixed by making buffers bigger. Therefore, it seems that there should 
-be enough buffer space to hold a whole sysex message. 
-
-The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
-will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
-Call that the max_sysex_len = buffer_len * 4.
-
-For simple midi output (latency == 0), allocate 3 buffers, each with half
-the size of max_sysex_len, but each at least 256 bytes.
-
-For stream output, there will already be enough space in very short
-buffers, so use them, but make sure there are at least 16.
-
-For input, use many small buffers rather than 2 large ones so that when 
-there are short sysex messages arriving frequently (as in control surfaces)
-there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
-but at least 16, of size 64 bytes each.
-
-The following constants help to represent these design parameters:
-*/
-#define NUM_SIMPLE_SYSEX_BUFFERS 3
-#define MIN_SIMPLE_SYSEX_LEN 256
-
-#define MIN_STREAM_BUFFERS 16
-#define STREAM_BUFFER_LEN 24
-
-#define INPUT_SYSEX_LEN 64
-#define MIN_INPUT_BUFFERS 16
-
-/* if we run out of space for output (assume this is due to a sysex msg,
-   expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
- */
-#define NUM_EXPANSION_BUFFERS 128
-#define EXPANSION_BUFFER_LEN 1024
-
-/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
-#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
-/* A MIDIHDR with a sysex message is the buffer length plus the header size */
-#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
-#ifdef USE_SYSEX_BUFFERS
-/* Size of a MIDIHDR with a buffer contaning multiple MIDIEVENT structures */
-#define MIDIHDR_SIZE(x) ((x) + sizeof(MIDIHDR))
-#endif
-
-/*
-==============================================================================
-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 max_buffers;        /* length of buffers array */
-    int buffers_expanded;   /* buffers array expanded for extra msgs? */
-    int num_buffers;        /* how many buffers allocated in buffers array */
-    int next_buffer;        /* index of next buffer to send */
-    HANDLE buffer_signal;   /* used to wait for buffer to become free */
-#ifdef USE_SYSEX_BUFFERS
-    /* sysex buffers will be allocated only when
-     * a sysex message is sent. The size of the buffer is fixed.
-     */
-    LPMIDIHDR sysex_buffers[NUM_SYSEX_BUFFERS]; /* pool of buffers for sysex data */
-    int next_sysex_buffer;      /* index of next sysexbuffer to send */
-#endif
-    unsigned long last_time;    /* last output time */
-    int first_message;          /* flag: treat first message differently */
-    int sysex_mode;             /* middle of sending sysex */
-    unsigned long sysex_word;   /* accumulate data when receiving sysex */
-    unsigned int sysex_byte_count; /* count how many received */
-    LPMIDIHDR hdr;              /* the message accumulating sysex to send */
-    unsigned long sync_time;    /* when did we last determine delta? */
-    long delta;                 /* difference between stream time and
-                                       real time */
-    int error;                  /* host error from doing port midi call */
-    CRITICAL_SECTION lock;      /* prevents reentrant callbacks (input only) */
-} 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 = (MIDIINCAPS *) 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.
-         */
-        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->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 { /* 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;
-            }
-        }
-    }
-}
-
-
-/*
-=============================================================================
-buffer handling
-=============================================================================
-*/
-static MIDIHDR *allocate_buffer(long data_size)
-{
-    LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
-    MIDIEVENT *evt;
-    if (!hdr) return NULL;
-    evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
-    hdr->lpData = (LPSTR) evt;
-    hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
-    hdr->dwBytesRecorded = 0;
-    hdr->dwFlags = 0;
-    hdr->dwUser = hdr->dwBufferLength;
-    return hdr;
-}
-
-#ifdef USE_SYSEX_BUFFERS
-static MIDIHDR *allocate_sysex_buffer(long data_size)
-{
-    /* we're actually allocating more than data_size because the buffer 
-     * will include the MIDIEVENT header in addition to the data 
-     */
-    LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
-    MIDIEVENT *evt;
-    if (!hdr) return NULL;
-    evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
-    hdr->lpData = (LPSTR) evt;
-    hdr->dwFlags = 0;
-    hdr->dwUser = 0;
-    return hdr;
-}
-#endif
-
-static PmError allocate_buffers(midiwinmm_type m, long data_size, long count)
-{
-    int i;
-    /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
-    m->num_buffers = 0; /* in case no memory can be allocated */
-    m->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
-    if (!m->buffers) return pmInsufficientMemory;
-    m->max_buffers = count;
-    for (i = 0; i < count; i++) {
-        LPMIDIHDR hdr = allocate_buffer(data_size);
-        if (!hdr) { /* free everything allocated so far and return */
-            for (i = i - 1; i >= 0; i--) pm_free(m->buffers[i]);
-            pm_free(m->buffers);
-            m->max_buffers = 0;
-            return pmInsufficientMemory;
-        }
-        m->buffers[i] = hdr; /* this may be NULL if allocation fails */
-    }
-    m->num_buffers = count;
-    return pmNoError;
-}
-
-#ifdef USE_SYSEX_BUFFERS
-static PmError allocate_sysex_buffers(midiwinmm_type m, long data_size)
-{
-    PmError rslt = pmNoError;
-    /* sysex_buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
-    int i;
-    for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-        LPMIDIHDR hdr = allocate_sysex_buffer(data_size);
-
-        if (!hdr) rslt = pmInsufficientMemory;
-        m->sysex_buffers[i] = hdr; /* this may be NULL if allocation fails */
-        hdr->dwFlags = 0; /* mark as free */
-    }
-    return rslt;
-}
-#endif
-
-#ifdef USE_SYSEX_BUFFERS
-static LPMIDIHDR get_free_sysex_buffer(PmInternal *midi)
-{
-    LPMIDIHDR r = NULL;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    if (!m->sysex_buffers[0]) {
-        if (allocate_sysex_buffers(m, SYSEX_BYTES_PER_BUFFER)) {
-            return NULL;
-        }
-    }
-    /* busy wait until we find a free buffer */
-    while (TRUE) {
-        int i;
-        for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-            /* cycle through buffers, modulo NUM_SYSEX_BUFFERS */
-            m->next_sysex_buffer++;
-            if (m->next_sysex_buffer >= NUM_SYSEX_BUFFERS) m->next_sysex_buffer = 0;
-            r = m->sysex_buffers[m->next_sysex_buffer];
-            if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_sysex_buffer;
-        }
-        /* after scanning every buffer and not finding anything, block */
-        if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) {
-#ifdef DEBUG
-            printf("PortMidi warning: get_free_sysex_buffer() wait timed out after 1000ms\n");
-#endif
-        }
-    }
-found_sysex_buffer:
-    r->dwBytesRecorded = 0;
-    r->dwBufferLength = 0; /* changed to correct value later */
-    return r;
-}
-#endif
-
-static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
-{
-    LPMIDIHDR r = NULL;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    while (TRUE) {
-        int i;
-        for (i = 0; i < m->num_buffers; i++) {
-            /* cycle through buffers, modulo m->num_buffers */
-            m->next_buffer++;
-            if (m->next_buffer >= m->num_buffers) m->next_buffer = 0;
-            r = m->buffers[m->next_buffer];
-            if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
-        }
-        /* after scanning every buffer and not finding anything, block */
-        if (WaitForSingleObject(m->buffer_signal, 1000) == WAIT_TIMEOUT) {
-#ifdef DEBUG
-            printf("PortMidi warning: get_free_output_buffer() wait timed out after 1000ms\n");
-#endif
-            /* if we're trying to send a sysex message, maybe the 
-             * message is too big and we need more message buffers.
-             * Expand the buffer pool by 128KB using 1024-byte buffers.
-             */
-            /* first, expand the buffers array if necessary */
-            if (!m->buffers_expanded) {
-                LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
-                        (m->num_buffers + NUM_EXPANSION_BUFFERS) * 
-                        sizeof(LPMIDIHDR));
-                /* if no memory, we could return a no-memory error, but user
-                 * probably will be unprepared to deal with it. Maybe the
-                 * MIDI driver is temporarily hung so we should just wait.
-                 * I don't know the right answer, but waiting is easier.
-                 */
-                if (!new_buffers) continue;
-                /* copy buffers to new_buffers and replace buffers */
-                memcpy(new_buffers, m->buffers, 
-                       m->num_buffers * sizeof(LPMIDIHDR));
-                pm_free(m->buffers);
-                m->buffers = new_buffers;
-                m->max_buffers = m->num_buffers + NUM_EXPANSION_BUFFERS;
-                m->buffers_expanded = TRUE;
-            }
-            /* next, add one buffer and return it */
-            if (m->num_buffers < m->max_buffers) {
-                r = allocate_buffer(EXPANSION_BUFFER_LEN);
-                /* again, if there's no memory, we may not really be 
-                 * dead -- maybe the system is temporarily hung and
-                 * we can just wait longer for a message buffer */
-                if (!r) continue;
-                m->buffers[m->num_buffers++] = r;
-                goto found_buffer; /* break out of 2 loops */
-            }
-            /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
-             * and we have no free buffers to send. We'll just keep
-             * polling to see if any buffers show up.
-             */
-        }
-    }
-found_buffer:
-    r->dwBytesRecorded = 0;
-    /* actual buffer length is saved in dwUser field */
-    r->dwBufferLength = (DWORD) r->dwUser;
-    return r;
-}
-
-#ifdef EXPANDING_SYSEX_BUFFERS
-note: this is not working code, but might be useful if you want
-      to grow sysex buffers.
-static PmError resize_sysex_buffer(PmInternal *midi, long old_size, long new_size)
-{
-    LPMIDIHDR big;
-    int i;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    /* buffer must be smaller than 64k, but be also a multiple of 4 */
-    if (new_size > 65520) {
-        if (old_size >= 65520)
-            return pmBufferMaxSize;
-        else
-            new_size = 65520;
-    }
-    /* allocate a bigger message  */
-    big = allocate_sysex_buffer(new_size);
-    /* printf("expand to %d bytes\n", new_size);*/
-    if (!big) return pmInsufficientMemory;
-    m->error = midiOutPrepareHeader(m->handle.out, big, sizeof(MIDIHDR));
-    if (m->error) {
-        pm_free(big);
-        return pmHostError;
-    }
-    /* make sure we're not going to overwrite any memory */
-    assert(old_size <= new_size);
-    memcpy(big->lpData, m->hdr->lpData, old_size);
-    /* keep track of how many sysex bytes are in message so far */
-    big->dwBytesRecorded = m->hdr->dwBytesRecorded;
-    big->dwBufferLength = new_size;
-    /* find which buffer this was, and replace it */
-    for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-        if (m->sysex_buffers[i] == m->hdr) {
-            m->sysex_buffers[i] = big;
-            m->sysex_buffer_size[i] = new_size;
-            pm_free(m->hdr);
-            m->hdr = big;
-            break;
-        }
-    }
-    assert(i != NUM_SYSEX_BUFFERS);
-
-    return pmNoError;
-}
-#endif
-
-/*
-=========================================================================================
-begin midi input implementation
-=========================================================================================
-*/
-
-
-static PmError allocate_input_buffer(HMIDIIN h, long buffer_len)
-{
-    LPMIDIHDR hdr = allocate_buffer(buffer_len);
-    if (!hdr) return pmInsufficientMemory;
-    pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
-    if (pm_hosterror) {
-        pm_free(hdr);
-        return pm_hosterror;
-    }
-    pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
-    return pm_hosterror;
-}
-
-
-static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
-{
-    DWORD dwDevice;
-    int i = midi->device_id;
-    int max_sysex_len = midi->buffer_len * 4;
-    int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;
-    midiwinmm_type m;
-
-    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; /* not used for input */
-    m->num_buffers = 0; /* not used for input */
-    m->max_buffers = FALSE; /* not used for input */
-    m->buffers_expanded = 0; /* not used for input */
-    m->next_buffer = 0; /* not used for input */
-    m->buffer_signal = 0; /* not used for input */
-#ifdef USE_SYSEX_BUFFERS
-    for (i = 0; i < NUM_SYSEX_BUFFERS; i++) 
-        m->sysex_buffers[i] = NULL; /* not used for input */
-    m->next_sysex_buffer = 0; /* not used for input */
-#endif
-    m->last_time = 0;
-    m->first_message = TRUE; /* not used for input */
-    m->sysex_mode = FALSE;
-    m->sysex_word = 0;
-    m->sysex_byte_count = 0;
-    m->hdr = NULL; /* not used for input */
-    m->sync_time = 0;
-    m->delta = 0;
-    m->error = MMSYSERR_NOERROR;
-    /* 4000 is based on Windows documentation -- that's the value used in the
-       memory manager. It's small enough that it should not hurt performance even
-       if it's not optimal.
-     */
-    InitializeCriticalSectionAndSpinCount(&m->lock, 4000);
-    /* open device */
-    pm_hosterror = midiInOpen(&(m->handle.in),  /* input device handle */
-                              dwDevice,  /* device ID */
-                              (DWORD_PTR) winmm_in_callback,  /* callback address */
-                              (DWORD_PTR) midi,  /* callback instance data */
-                              CALLBACK_FUNCTION); /* callback is a procedure */
-    if (pm_hosterror) goto free_descriptor;
-
-    if (num_input_buffers < MIN_INPUT_BUFFERS)
-        num_input_buffers = MIN_INPUT_BUFFERS;
-    for (i = 0; i < num_input_buffers; i++) {
-        if (allocate_input_buffer(m->handle.in, INPUT_SYSEX_LEN)) {
-            /* either pm_hosterror was set, or the proper return code
-               is pmInsufficientMemory */
-            goto close_device;
-        }
-    }
-    /* start device */
-    pm_hosterror = midiInStart(m->handle.in);
-    if (pm_hosterror) goto reset_device;
-    return pmNoError;
-
-    /* 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 */
-}
-
-static PmError winmm_in_poll(PmInternal *midi) {
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    return m->error;
-}
-
-
-
-/* winmm_in_close -- close an open midi input device */
-/*
- * 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;
-    DeleteCriticalSection(&m->lock);
-    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_PTR dwInstance,   /* application data */
-    DWORD_PTR dwParam1,     /* MIDI data */
-    DWORD_PTR dwParam2)    /* device timestamp (wrt most recent midiInStart) */
-{
-    static int entry = 0;
-    PmInternal *midi = (PmInternal *) dwInstance;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-
-    /* NOTE: we do not just EnterCriticalSection() here because an
-     * MIM_CLOSE message arrives when the port is closed, but then
-     * the m->lock has been destroyed.
-     */
-
-    switch (wMsg) {
-    case MIM_DATA: {
-        /* if this callback is reentered with data, we're in trouble. 
-         * It's hard to imagine that Microsoft would allow callbacks 
-         * to be reentrant -- isn't the model that this is like a 
-         * hardware interrupt? -- but I've seen reentrant behavior 
-         * using a debugger, so it happens.
-         */
-        EnterCriticalSection(&m->lock);
-
-        /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
-                message LOB;
-           dwParam2 is time message received by input device driver, specified
-            in [ms] from when midiInStart called.
-           each message is expanded to include the status byte */
-
-        long new_driver_time = dwParam2;
-
-        if ((dwParam1 & 0x80) == 0) {
-            /* not a status byte -- ignore it. This happened running the
-               sysex.c test under Win2K with MidiMan USB 1x1 interface,
-               but I can't reproduce it. -RBD
-             */
-            /* printf("non-status byte found\n"); */
-        } else { /* data to process */
-            PmEvent event;
-            if (midi->time_proc)
-                dwParam2 = (*midi->time_proc)(midi->time_info);
-            event.timestamp = dwParam2;
-            event.message = dwParam1;
-            pm_read_short(midi, &event);
-        }
-        LeaveCriticalSection(&m->lock);
-        break;
-    }
-    case MIM_LONGDATA: {
-        MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
-        unsigned char *data = (unsigned char *) lpMidiHdr->lpData;
-        unsigned int processed = 0;
-        int remaining = lpMidiHdr->dwBytesRecorded;
-
-        EnterCriticalSection(&m->lock);
-        /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", 
-                lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */
-        if (midi->time_proc)
-            dwParam2 = (*midi->time_proc)(midi->time_info);
-        /* can there be more than one message in one buffer? */
-        /* assume yes and iterate through them */
-        while (remaining > 0) {
-            unsigned int amt = pm_read_bytes(midi, data + processed, 
-                                             remaining, dwParam2);
-            remaining -= amt;
-            processed += amt;
-        }
-
-        /* when a device is closed, the pending MIM_LONGDATA buffers are
-           returned to this callback with dwBytesRecorded == 0. In this
-           case, we do not want to send them back to the interface (if
-           we do, the interface will not close, and Windows OS may hang). */
-        if (lpMidiHdr->dwBytesRecorded > 0) {
-            lpMidiHdr->dwBytesRecorded = 0;
-            lpMidiHdr->dwFlags = 0;
-			
-            /* note: no error checking -- can this actually fail? */
-            assert(midiInPrepareHeader(hMidiIn, lpMidiHdr, 
-                        sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-            /* note: I don't think this can fail except possibly for
-             * MMSYSERR_NOMEM, but the pain of reporting this
-             * unlikely but probably catastrophic error does not seem
-             * worth it.
-             */
-            assert(midiInAddBuffer(hMidiIn, lpMidiHdr, 
-                        sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-            LeaveCriticalSection(&m->lock);
-        } else {
-            midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));
-            LeaveCriticalSection(&m->lock);
-            pm_free(lpMidiHdr);
-        }
-        break;
-    }
-    case MIM_OPEN:
-        break;
-    case MIM_CLOSE:
-        break;
-    case MIM_ERROR:
-        /* printf("MIM_ERROR\n"); */
-        break;
-    case MIM_LONGERROR:
-        /* printf("MIM_LONGERROR\n"); */
-        break;
-    default:
-        break;
-    }
-}
-
-/*
-=========================================================================================
-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;
-}
-
-
-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;
-}
-
-
-/* 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;
-    int max_sysex_len = midi->buffer_len * 4;
-    int output_buffer_len;
-    int num_buffers;
-    dwDevice = (DWORD) descriptors[i].descriptor;
-
-    /* create system dependent device data */
-    m = (midiwinmm_type) pm_alloc(sizeof(midiwinmm_node)); /* create */
-    midi->descriptor = m;
-    if (!m) goto no_memory;
-    m->handle.out = NULL;
-    m->buffers = NULL;
-    m->num_buffers = 0;
-    m->max_buffers = 0;
-    m->buffers_expanded = FALSE;
-    m->next_buffer = 0;
-#ifdef USE_SYSEX_BUFFERS
-    m->sysex_buffers[0] = NULL;
-    m->sysex_buffers[1] = NULL;
-    m->next_sysex_buffer = 0;
-#endif
-    m->last_time = 0;
-    m->first_message = TRUE; /* we treat first message as special case */
-    m->sysex_mode = FALSE;
-    m->sysex_word = 0;
-    m->sysex_byte_count = 0;
-    m->hdr = NULL;
-    m->sync_time = 0;
-    m->delta = 0;
-    m->error = MMSYSERR_NOERROR;
-
-    /* create a signal */
-    m->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
-
-    /* this should only fail when there are very serious problems */
-    assert(m->buffer_signal);
-
-    /* open device */
-    if (midi->latency == 0) {
-        /* use simple midi out calls */
-        pm_hosterror = midiOutOpen((LPHMIDIOUT) & m->handle.out,  /* device Handle */
-                                   dwDevice,  /* device ID  */
-                                   /* note: same callback fn as for StreamOpen: */
-                                   (DWORD_PTR) winmm_streamout_callback, /* callback fn */
-                                   (DWORD_PTR) midi,  /* callback instance data */
-                                   CALLBACK_FUNCTION); /* callback type */
-    } else {
-        /* use stream-based midi output (schedulable in future) */
-        pm_hosterror = midiStreamOpen(&m->handle.stream,  /* device Handle */
-                                      (LPUINT) & dwDevice,  /* device ID pointer */
-                                      1,  /* reserved, must be 1 */
-                                      (DWORD_PTR) winmm_streamout_callback,
-                                      (DWORD_PTR) midi,  /* callback instance data */
-                                      CALLBACK_FUNCTION);
-    }
-    if (pm_hosterror != MMSYSERR_NOERROR) {
-        goto free_descriptor;
-    }
-
-    if (midi->latency == 0) {
-        num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
-        output_buffer_len = max_sysex_len / num_buffers;
-        if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
-            output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
-    } else {
-        long dur = 0;
-        num_buffers = max(midi->buffer_len, midi->latency / 2);
-        if (num_buffers < MIN_STREAM_BUFFERS)
-            num_buffers = MIN_STREAM_BUFFERS;
-        output_buffer_len = STREAM_BUFFER_LEN;
-
-        propdata.cbStruct = sizeof(MIDIPROPTEMPO);
-        propdata.dwTempo = 480000; /* microseconds per quarter */
-        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 buffers */
-    if (allocate_buffers(m, output_buffer_len, num_buffers)) 
-        goto free_buffers;
-    /* start device */
-    if (midi->latency != 0) {
-        pm_hosterror = midiStreamRestart(m->handle.stream);
-        if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
-    }
-    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)
-{
-    int i;
-    /* 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 using stream output, free buffers */
-        for (i = 0; i < m->num_buffers; i++) {
-            if (m->buffers[i]) pm_free(m->buffers[i]);
-        }
-        m->num_buffers = 0;
-        pm_free(m->buffers);
-        m->max_buffers = 0;
-#ifdef USE_SYSEX_BUFFERS
-        /* free sysex buffers */
-        for (i = 0; i < NUM_SYSEX_BUFFERS; i++) {
-            if (m->sysex_buffers[i]) pm_free(m->sysex_buffers[i]);
-        }
-#endif
-    }
-    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;
-}
-
-
-static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    assert(m);
-    if (m->hdr) {
-        m->error = midiOutPrepareHeader(m->handle.out, m->hdr, 
-                                        sizeof(MIDIHDR));
-        if (m->error) {
-            /* do not send message */
-        } else if (midi->latency == 0) {
-            /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded
-             * should be zero. This is set in get_free_sysex_buffer(). 
-             * The msg length goes in dwBufferLength in spite of what
-             * Microsoft documentation says (or doesn't say). */
-            m->hdr->dwBufferLength = m->hdr->dwBytesRecorded;
-            m->hdr->dwBytesRecorded = 0;
-            m->error = midiOutLongMsg(m->handle.out, m->hdr, sizeof(MIDIHDR));
-        } else {
-            m->error = midiStreamOut(m->handle.stream, m->hdr, 
-                                     sizeof(MIDIHDR));
-        }
-        midi->fill_base = NULL;
-        m->hdr = NULL;
-        if (m->error) {
-            m->hdr->dwFlags = 0; /* release the buffer */
-            return pmHostError;
-        }
-    }
-    return pmNoError;
-}
-
-
-
-#ifdef GARBAGE
-static PmError winmm_write_sysex_byte(PmInternal *midi, unsigned char byte)
-{
-    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) rslt = winmm_write_flush(midi, when);
-    }
-    return rslt;
-}
-
-#define winmm_begin_sysex winmm_write_flush
-#ifndef winmm_begin_sysex
-static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    PmError rslt = pmNoError;
-
-    if (midi->latency == 0) {
-        /* do nothing -- it's handled in winmm_write_byte */
-    } else {
-        /* sysex expects an empty sysex buffer, so send whatever is here */
-        rslt = winmm_write_flush(midi);
-    }
-    return rslt;
-}
-#endif
-
-static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
-{
-    /* could check for callback_error here, but I haven't checked
-     * what happens if we exit early and don't finish the sysex msg
-     * and clean up
-     */
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    PmError rslt = pmNoError;
-    LPMIDIHDR hdr = m->hdr;
-    if (!hdr) return rslt; /* something bad happened earlier,
-            do not report an error because it would have been 
-            reported (at least) once already */
-    /* a(n old) version of MIDI YOKE requires a zero byte after
-     * the sysex message, but do not increment dwBytesRecorded: */
-    hdr->lpData[hdr->dwBytesRecorded] = 0;
-    if (midi->latency == 0) {
-#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
-        /* DEBUG CODE: */
-        { int i; int len = m->hdr->dwBufferLength;
-          printf("OutLongMsg %d ", len);
-          for (i = 0; i < len; i++) {
-              printf("%2x ", (unsigned char) (m->hdr->lpData[i]));
-          }
-        }
-#endif
-    } else {
-        /* Using stream interface. There are accumulated bytes in m->hdr
-           to send using midiStreamOut
-         */
-        /* add bytes recorded to MIDIEVENT length, but don't
-           count the MIDIEVENT data (3 longs) */
-        MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);
-        evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
-        /* round up BytesRecorded to multiple of 4 */
-        hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
-    }
-    rslt = winmm_write_flush(midi, timestamp);
-    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;
-    LPMIDIHDR hdr = m->hdr;
-    unsigned char *msg_buffer;
-    assert(m);
-    if (!hdr) {
-        m->hdr = hdr = get_free_output_buffer(midi);
-        assert(hdr);
-        midi->fill_base = (unsigned char *) m->hdr->lpData;
-        midi->fill_offset_ptr = &(hdr->dwBytesRecorded);
-        /* when buffer fills, Pm_WriteSysEx will revert to calling
-         * pmwin_write_byte, which expect to have space, so leave
-         * one byte free for pmwin_write_byte. Leave another byte
-         * of space for zero after message to make early version of 
-         * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
-        midi->fill_length = hdr->dwBufferLength - 2;
-        if (midi->latency != 0) {
-            unsigned long when = (unsigned long) timestamp;
-            unsigned long delta;
-            unsigned long *ptr;
-            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;
-
-            ptr = (unsigned long *) hdr->lpData;
-            *ptr++ = delta;
-            *ptr++ = 0;
-            *ptr = MEVT_F_LONG;
-            hdr->dwBytesRecorded = 3 * sizeof(long);
-            /* data will be added at an offset of dwBytesRecorded ... */
-        }
-    }
-    /* add the data byte */
-    msg_buffer = (unsigned char *) (hdr->lpData);
-    msg_buffer[hdr->dwBytesRecorded++] = byte;
-
-    /* see if buffer is full, leave one byte extra for pad */
-    if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
-        /* write what we've got and continue */
-        rslt = winmm_end_sysex(midi, timestamp); 
-    }
-    return rslt;
-}
-
-#ifdef EXPANDING_SYSEX_BUFFERS
-note: this code is here as an aid in case you want sysex buffers
-      to expand to hold large messages completely. If so, you
-      will want to change SYSEX_BYTES_PER_BUFFER above to some
-      variable that remembers the buffer size. A good place to 
-      put this value would be in the hdr->dwUser field.
-
-            rslt = resize_sysex_buffer(midi, m->sysex_byte_count, 
-                                       m->sysex_byte_count * 2);
-
-            if (rslt == pmBufferMaxSize) /* if the buffer can't be resized */
-#endif
-#ifdef EXPANDING_SYSEX_BUFFERS
-            int bytesRecorded = hdr->dwBytesRecorded; /* this field gets wiped out, so we'll save it */
-            rslt = resize_sysex_buffer(midi, bytesRecorded, 2 * bytesRecorded);
-            hdr->dwBytesRecorded = bytesRecorded;
-
-            if (rslt == pmBufferMaxSize) /* if buffer can't be resized */
-#endif
-
-
-
-static PmTimestamp winmm_synchronize(PmInternal *midi)
-{
-    midiwinmm_type m;
-    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 USE_SYSEX_BUFFERS
-/* winmm_out_callback -- recycle sysex buffers */
-static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
-                                        DWORD_PTR dwInstance, DWORD_PTR dwParam1, 
-                                        DWORD_PTR dwParam2)
-{
-    PmInternal *midi = (PmInternal *) dwInstance;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
-    int err = 0;  /* set to 0 so that no buffer match will also be an error */
-
-    /* Future optimization: eliminate UnprepareHeader calls -- they aren't
-       necessary; however, this code uses the prepared-flag to indicate which
-       buffers are free, so we need to do something to flag empty buffers if
-       we leave them prepared
-     */
-    printf("out_callback: hdr %x, wMsg %x, MOM_DONE %x\n", 
-           hdr, wMsg, MOM_DONE);
-    if (wMsg == MOM_DONE)
-    assert(midiOutUnprepareHeader(m->handle.out, hdr,
-                        sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-    /* notify waiting sender that a buffer is available */
-    err = SetEvent(m->buffer_signal);
-    assert(err); /* false -> error */
-}
-#endif
-
-/* winmm_streamout_callback -- unprepare (free) buffer header */
-static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
-        DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
-{
-    PmInternal *midi = (PmInternal *) dwInstance;
-    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
-    LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
-    int err;
-
-    /* Even if an error is pending, I think we should unprepare msgs and
-       signal their arrival
-     */
-    /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", 
-           hdr, wMsg, MOM_DONE); */
-    if (wMsg == MOM_DONE) {
-        assert(midiOutUnprepareHeader(m->handle.out, hdr, 
-                    sizeof(MIDIHDR)) == MMSYSERR_NOERROR);
-    }
-    /* signal client in case it is blocked waiting for buffer */
-    err = SetEvent(m->buffer_signal);
-    assert(err); /* false -> error */
-}
-
-
-/*
-=========================================================================================
-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,
-                                         winmm_in_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("%s\n", msg);
-                }
-#endif
-                /* close all open ports */
-                (*midi->dictionary->close)(midi);
-            }
-        }
-    }
-    if (midi_in_caps) {
-        pm_free(midi_in_caps);
-        midi_in_caps = NULL;
-    }
-    if (midi_out_caps) {
-        pm_free(midi_out_caps);
-        midi_out_caps = NULL;
-    }
-#ifdef DEBUG
-    if (doneAny) {
-        printf("warning: devices were left open. They have been closed.\n");
-    }
-    printf("pm_winmm_term exiting\n");
-#endif
-    pm_descriptor_index = 0;
-}
+/* pmwinmm.c -- system specific definitions */
+
+#ifndef _WIN32_WINNT
+    /* without this define, InitializeCriticalSectionAndSpinCount is 
+     * undefined. This version level means "Windows 2000 and higher" 
+     */
+    #define _WIN32_WINNT 0x0500
+#endif
+
+#define UNICODE 1
+#include <wchar.h>
+#include "windows.h"
+#include "mmsystem.h"
+#include "portmidi.h"
+#include "pmutil.h"
+#include "pminternal.h"
+#include "pmwinmm.h"
+#include <string.h>
+#include "porttime.h"
+#ifndef UNICODE
+#error Expected UNICODE to be defined
+#endif
+
+
+/* asserts used to verify portMidi code logic is sound; later may want
+    something more graceful */
+#include <assert.h>
+#ifdef MMDEBUG
+/* this printf stuff really important for debugging client app w/host errors.
+    probably want to do something else besides read/write from/to console
+    for portability, however */
+#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,
+                                       UINT wMsg, DWORD_PTR dwInstance, 
+                                       DWORD_PTR dwParam1, DWORD_PTR dwParam2);
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+                                              DWORD_PTR dwInstance, 
+                                              DWORD_PTR dwParam1,
+                                              DWORD_PTR 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 */
+
+/*
+A note about buffers: WinMM seems to hold onto buffers longer than
+one would expect, e.g. when I tried using 2 small buffers to send
+long sysex messages, at some point WinMM held both buffers. This problem
+was fixed by making buffers bigger. Therefore, it seems that there should 
+be enough buffer space to hold a whole sysex message. 
+
+The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
+will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
+Call that the max_sysex_len = buffer_len * 4.
+
+For simple midi output (latency == 0), allocate 3 buffers, each with half
+the size of max_sysex_len, but each at least 256 bytes.
+
+For stream output, there will already be enough space in very short
+buffers, so use them, but make sure there are at least 16.
+
+For input, use many small buffers rather than 2 large ones so that when 
+there are short sysex messages arriving frequently (as in control surfaces)
+there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
+but at least 16, of size 64 bytes each.
+
+The following constants help to represent these design parameters:
+*/
+#define NUM_SIMPLE_SYSEX_BUFFERS 3
+#define MIN_SIMPLE_SYSEX_LEN 256
+
+#define MIN_STREAM_BUFFERS 16
+#define STREAM_BUFFER_LEN 24
+
+#define INPUT_SYSEX_LEN 64
+#define MIN_INPUT_BUFFERS 16
+
+/* if we run out of space for output (assume this is due to a sysex msg,
+   expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
+ */
+#define NUM_EXPANSION_BUFFERS 128
+#define EXPANSION_BUFFER_LEN 1024
+
+/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
+#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
+/* A MIDIHDR with a sysex message is the buffer length plus the header size */
+#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
+
+/*
+==============================================================================
+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 winmm_info_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 max_buffers;        /* length of buffers array */
+    int buffers_expanded;   /* buffers array expanded for extra msgs? */
+    int num_buffers;        /* how many buffers allocated in buffers array */
+    int next_buffer;        /* index of next buffer to send */
+    HANDLE buffer_signal;   /* used to wait for buffer to become free */
+    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 */
+    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 */
+    CRITICAL_SECTION lock;      /* prevents reentrant callbacks (input only) */
+} winmm_info_node, *winmm_info_type;
+
+
+/*
+=============================================================================
+general MIDI device queries
+=============================================================================
+*/
+
+/* add a device after converting device (product) name to UTF-8 */
+static void pm_add_device_w(char *api, WCHAR *device_name, int is_input,
+                            int is_virtual, void *descriptor, pm_fns_type dictionary)
+{
+    char utf8name[4 * MAXPNAMELEN];
+    WideCharToMultiByte(CP_UTF8, 0, device_name, -1, 
+                        utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL);
+    /* ignore errors here -- if pm_descriptor_max is exceeded, 
+       some devices will not be accessible. */
+    pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary);
+}
+
+
+static void pm_winmm_general_inputs(void)
+{
+    UINT i;
+    WORD wRtn;
+    midi_num_inputs = midiInGetNumDevs();
+    midi_in_caps = (MIDIINCAPS *) 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.
+         */
+        return;
+    }
+
+    for (i = 0; i < midi_num_inputs; i++) {
+        wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],
+                                sizeof(MIDIINCAPS));
+        if (wRtn == MMSYSERR_NOERROR) {
+            pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE,
+                              (void *) (intptr_t) i, &pm_winmm_in_dictionary);
+        }
+    }
+}
+
+
+static void pm_winmm_mapper_input(void)
+{
+    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 form of
+        setup info. */
+    wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
+                            (LPMIDIINCAPS) & midi_in_mapper_caps, 
+                            sizeof(MIDIINCAPS));
+    if (wRtn == MMSYSERR_NOERROR) {
+        pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE,
+                        (void *) (intptr_t) MIDIMAPPER, 
+                        &pm_winmm_in_dictionary);
+    }
+}
+
+
+static void pm_winmm_general_outputs(void)
+{
+    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_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE,
+                            (void *) (intptr_t) i, &pm_winmm_out_dictionary);
+        }
+    }
+}
+
+
+static void pm_winmm_mapper_output(void)
+{
+    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_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE,
+                        (void *) (intptr_t) MIDIMAPPER, 
+                        &pm_winmm_out_dictionary);
+    }
+}
+
+
+/*
+============================================================================
+host error handling
+============================================================================
+*/
+
+static unsigned int winmm_check_host_error(PmInternal *midi)
+{
+    return FALSE;
+}
+
+
+/*
+=============================================================================
+buffer handling
+=============================================================================
+*/
+static MIDIHDR *allocate_buffer(long data_size)
+{
+    LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
+    MIDIEVENT *evt;
+    if (!hdr) return NULL;
+    evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
+    hdr->lpData = (LPSTR) evt;
+    hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
+    hdr->dwBytesRecorded = 0;
+    hdr->dwFlags = 0;
+    hdr->dwUser = hdr->dwBufferLength;
+    return hdr;
+}
+
+
+static PmError allocate_buffers(winmm_info_type info, long data_size, 
+                                long count)
+{
+    int i;
+    /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
+    info->num_buffers = 0; /* in case no memory can be allocated */
+    info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
+    if (!info->buffers) return pmInsufficientMemory;
+    info->max_buffers = count;
+    for (i = 0; i < count; i++) {
+        LPMIDIHDR hdr = allocate_buffer(data_size);
+        if (!hdr) { /* free everything allocated so far and return */
+            for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]);
+            pm_free(info->buffers);
+            info->max_buffers = 0;
+            return pmInsufficientMemory;
+        }
+        info->buffers[i] = hdr; /* this may be NULL if allocation fails */
+    }
+    info->num_buffers = count;
+    return pmNoError;
+}
+
+
+static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
+{
+    LPMIDIHDR r = NULL;
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    while (TRUE) {
+        int i;
+        for (i = 0; i < info->num_buffers; i++) {
+            /* cycle through buffers, modulo info->num_buffers */
+            info->next_buffer++;
+            if (info->next_buffer >= info->num_buffers) info->next_buffer = 0;
+            r = info->buffers[info->next_buffer];
+            if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
+        }
+        /* after scanning every buffer and not finding anything, block */
+        if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) {
+#ifdef MMDEBUG
+            printf("PortMidi warning: get_free_output_buffer() "
+                   "wait timed out after 1000ms\n");
+#endif
+            /* if we're trying to send a sysex message, maybe the 
+             * message is too big and we need more message buffers.
+             * Expand the buffer pool by 128KB using 1024-byte buffers.
+             */
+            /* first, expand the buffers array if necessary */
+            if (!info->buffers_expanded) {
+                LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
+                               (info->num_buffers + NUM_EXPANSION_BUFFERS) *
+                               sizeof(LPMIDIHDR));
+                /* if no memory, we could return a no-memory error, but user
+                 * probably will be unprepared to deal with it. Maybe the
+                 * MIDI driver is temporarily hung so we should just wait.
+                 * I don't know the right answer, but waiting is easier.
+                 */
+                if (!new_buffers) continue;
+                /* copy buffers to new_buffers and replace buffers */
+                memcpy(new_buffers, info->buffers, 
+                       info->num_buffers * sizeof(LPMIDIHDR));
+                pm_free(info->buffers);
+                info->buffers = new_buffers;
+                info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS;
+                info->buffers_expanded = TRUE;
+            }
+            /* next, add one buffer and return it */
+            if (info->num_buffers < info->max_buffers) {
+                r = allocate_buffer(EXPANSION_BUFFER_LEN);
+                /* again, if there's no memory, we may not really be 
+                 * dead -- maybe the system is temporarily hung and
+                 * we can just wait longer for a message buffer */
+                if (!r) continue;
+                info->buffers[info->num_buffers++] = r;
+                goto found_buffer; /* break out of 2 loops */
+            }
+            /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
+             * and we have no free buffers to send. We'll just keep
+             * polling to see if any buffers show up.
+             */
+        }
+    }
+found_buffer:
+    r->dwBytesRecorded = 0;
+    /* actual buffer length is saved in dwUser field */
+    r->dwBufferLength = (DWORD) r->dwUser;
+    return r;
+}
+
+/*
+============================================================================
+begin midi input implementation
+============================================================================
+*/
+
+
+static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len)
+{
+    LPMIDIHDR hdr = allocate_buffer(buffer_len);
+    if (!hdr) return pmInsufficientMemory;
+    /* note: pm_hosterror is normally a boolean, but here, we store Win
+     * error code. The caller must test the value for nonzero, set
+     * pm_hosterror_text, and then set pm_hosterror to TRUE */
+    pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
+    if (pm_hosterror) {
+        pm_free(hdr);
+        return pm_hosterror;
+    }
+    pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
+    return pm_hosterror;
+}
+
+
+static winmm_info_type winmm_info_create(void)
+{
+    winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node));
+    info->handle.in = NULL;
+    info->handle.out = NULL;
+    info->buffers = NULL; /* not used for input */
+    info->num_buffers = 0; /* not used for input */
+    info->max_buffers = 0; /* not used for input */
+    info->buffers_expanded = FALSE; /* not used for input */
+    info->next_buffer = 0; /* not used for input */
+    info->buffer_signal = 0; /* not used for input */
+    info->last_time = 0;
+    info->first_message = TRUE; /* not used for input */
+    info->sysex_mode = FALSE;
+    info->sysex_word = 0;
+    info->sysex_byte_count = 0;
+    info->hdr = NULL; /* not used for input */
+    info->sync_time = 0;
+    info->delta = 0;
+    return info;
+}
+
+
+static void report_hosterror(LPWCH error_msg)
+{
+    WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text,
+                        sizeof(pm_hosterror_text), NULL, NULL);
+    if (pm_hosterror == MMSYSERR_NOMEM) {
+        /* add explanation to Window's confusing error message */
+        /* if there's room: */
+        if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) {
+            strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
+                     " Probably this MIDI device is open "
+                     "in another application.");
+        }
+    }
+    pm_hosterror = TRUE;
+}
+
+
+static void report_hosterror_in(void)
+{
+    WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
+    int err = midiInGetErrorText(pm_hosterror, error_msg,
+                                 PM_HOST_ERROR_MSG_LEN);
+    assert(err == MMSYSERR_NOERROR);
+    report_hosterror(error_msg);
+}
+
+
+static void report_hosterror_out(void)
+{
+    WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
+    int err = midiOutGetErrorText(pm_hosterror, error_msg,
+                                  PM_HOST_ERROR_MSG_LEN);
+    assert(err == MMSYSERR_NOERROR);
+    report_hosterror(error_msg);
+}
+
+
+static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
+{
+    DWORD dwDevice;
+    int i = midi->device_id;
+    int max_sysex_len = midi->buffer_len * 4;
+    int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;
+    winmm_info_type info;
+
+    dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
+
+    /* create system dependent device data */
+    info = winmm_info_create();
+    midi->api_info = info;
+    if (!info) goto no_memory;
+    /* 4000 is based on Windows documentation -- that's the value used
+       in the memory manager. It's small enough that it should not
+       hurt performance even if it's not optimal.
+     */
+    InitializeCriticalSectionAndSpinCount(&info->lock, 4000);
+    /* open device */
+    pm_hosterror = midiInOpen(
+	                &(info->handle.in),  /* input device handle */
+	                dwDevice,  /* device ID */
+	                (DWORD_PTR) winmm_in_callback,  /* callback address */
+	                (DWORD_PTR) midi,  /* callback instance data */
+	                CALLBACK_FUNCTION); /* callback is a procedure */
+    if (pm_hosterror) goto free_descriptor;
+
+    if (num_input_buffers < MIN_INPUT_BUFFERS)
+        num_input_buffers = MIN_INPUT_BUFFERS;
+    for (i = 0; i < num_input_buffers; i++) {
+        if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) {
+            /* either pm_hosterror was set, or the proper return code
+               is pmInsufficientMemory */
+            goto close_device;
+        }
+    }
+    /* start device */
+    pm_hosterror = midiInStart(info->handle.in);
+    if (!pm_hosterror) {
+        return pmNoError;
+    }
+
+    /* undo steps leading up to the detected error */
+
+    /* ignore return code (we already have an error to report) */
+    midiInReset(info->handle.in);
+close_device:
+    midiInClose(info->handle.in); /* ignore return code */
+free_descriptor:
+    midi->api_info = NULL;
+    pm_free(info);
+no_memory:
+    if (pm_hosterror) {
+        report_hosterror_in();
+        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)
+{
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    if (!info) return pmBadPtr;
+    /* device to close */
+    if ((pm_hosterror = midiInStop(info->handle.in))) {
+        midiInReset(info->handle.in); /* try to reset and close port */
+        midiInClose(info->handle.in);
+    } else if ((pm_hosterror = midiInReset(info->handle.in))) {
+        midiInClose(info->handle.in); /* best effort to close midi port */
+    } else {
+        pm_hosterror = midiInClose(info->handle.in);
+    }
+    midi->api_info = NULL;
+    DeleteCriticalSection(&info->lock);
+    pm_free(info); /* delete */
+    if (pm_hosterror) {
+        report_hosterror_in();
+        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 */
+    UINT wMsg,             /* midi msg */
+    DWORD_PTR dwInstance,  /* application data */
+    DWORD_PTR dwParam1,    /* MIDI data */
+    DWORD_PTR dwParam2)    /* device timestamp (wrt most recent midiInStart) */
+{
+    PmInternal *midi = (PmInternal *) dwInstance;
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+
+    /* NOTE: we do not just EnterCriticalSection() here because an
+     * MIM_CLOSE message arrives when the port is closed, but then
+     * the info->lock has been destroyed.
+     */
+
+    switch (wMsg) {
+    case MIM_DATA: {
+        /* if this callback is reentered with data, we're in trouble. 
+         * It's hard to imagine that Microsoft would allow callbacks 
+         * to be reentrant -- isn't the model that this is like a 
+         * hardware interrupt? -- but I've seen reentrant behavior 
+         * using a debugger, so it happens.
+         */
+        EnterCriticalSection(&info->lock);
+
+        /* 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 */
+
+        if ((dwParam1 & 0x80) == 0) {
+            /* not a status byte -- ignore it. This happened running the
+               sysex.c test under Win2K with MidiMan USB 1x1 interface,
+               but I can't reproduce it. -RBD
+             */
+            /* printf("non-status byte found\n"); */
+        } else { /* data to process */
+            PmEvent event;
+            if (midi->time_proc)
+                dwParam2 = (*midi->time_proc)(midi->time_info);
+            event.timestamp = (PmTimestamp)dwParam2;
+            event.message = (PmMessage)dwParam1;
+            pm_read_short(midi, &event);
+        }
+        LeaveCriticalSection(&info->lock);
+        break;
+    }
+    case MIM_LONGDATA: {
+        MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
+        unsigned char *data = (unsigned char *) lpMidiHdr->lpData;
+        unsigned int processed = 0;
+        int remaining = lpMidiHdr->dwBytesRecorded;
+
+        EnterCriticalSection(&info->lock);
+        /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n", 
+                lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */
+        if (midi->time_proc)
+            dwParam2 = (*midi->time_proc)(midi->time_info);
+        /* can there be more than one message in one buffer? */
+        /* assume yes and iterate through them */
+        pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2);
+
+        /* when a device is closed, the pending MIM_LONGDATA buffers are
+           returned to this callback with dwBytesRecorded == 0. In this
+           case, we do not want to send them back to the interface (if
+           we do, the interface will not close, and Windows OS may hang). */
+        if (lpMidiHdr->dwBytesRecorded > 0) {
+            MMRESULT rslt;
+            lpMidiHdr->dwBytesRecorded = 0;
+            lpMidiHdr->dwFlags = 0;
+			
+            /* note: no error checking -- can this actually fail? */
+            rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
+            assert(rslt == MMSYSERR_NOERROR);
+            /* note: I don't think this can fail except possibly for
+             * MMSYSERR_NOMEM, but the pain of reporting this
+             * unlikely but probably catastrophic error does not seem
+             * worth it.
+             */
+            rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
+            assert(rslt == MMSYSERR_NOERROR);
+            LeaveCriticalSection(&info->lock);
+        } else {
+            midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));
+            LeaveCriticalSection(&info->lock);
+            pm_free(lpMidiHdr);
+        }
+        break;
+    }
+    case MIM_OPEN:
+        break;
+    case MIM_CLOSE:
+        break;
+    case MIM_ERROR:
+        /* printf("MIM_ERROR\n"); */
+        break;
+    case MIM_LONGERROR:
+        /* printf("MIM_LONGERROR\n"); */
+        break;
+    default:
+        break;
+    }
+}
+
+/*
+===========================================================================
+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(winmm_info_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;
+}
+
+
+static PmTimestamp pm_time_get(winmm_info_type info)
+{
+    MMTIME mmtime;
+    MMRESULT wRtn;
+    mmtime.wType = TIME_TICKS;
+    mmtime.u.ticks = 0;
+    wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime));
+    assert(wRtn == MMSYSERR_NOERROR);
+    return mmtime.u.ticks;
+}
+
+
+/* end helper routines used by midiOutStream interface */
+
+
+static PmError winmm_out_open(PmInternal *midi, void *driverInfo)
+{
+    DWORD dwDevice;
+    int i = midi->device_id;
+    winmm_info_type info;
+    MIDIPROPTEMPO propdata;
+    MIDIPROPTIMEDIV divdata;
+    int max_sysex_len = midi->buffer_len * 4;
+    int output_buffer_len;
+    int num_buffers;
+    dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
+
+    /* create system dependent device data */
+    info = winmm_info_create();
+    midi->api_info = info;
+    if (!info) goto no_memory;
+    /* create a signal */
+    info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+    /* this should only fail when there are very serious problems */
+    assert(info->buffer_signal);
+    /* open device */
+    if (midi->latency == 0) {
+        /* use simple midi out calls */
+        pm_hosterror = midiOutOpen(
+                (LPHMIDIOUT) & info->handle.out,  /* device Handle */
+		        dwDevice,  /* device ID  */
+		        /* note: same callback fn as for StreamOpen: */
+		        (DWORD_PTR) winmm_streamout_callback, /* callback fn */
+		        (DWORD_PTR) midi,  /* callback instance data */
+		        CALLBACK_FUNCTION); /* callback type */
+    } else {
+        /* use stream-based midi output (schedulable in future) */
+        pm_hosterror = midiStreamOpen(
+	            &info->handle.stream,  /* device Handle */
+		        (LPUINT) & dwDevice,  /* device ID pointer */
+		        1,  /* reserved, must be 1 */
+		        (DWORD_PTR) winmm_streamout_callback,
+		        (DWORD_PTR) midi,  /* callback instance data */
+		        CALLBACK_FUNCTION);
+    }
+    if (pm_hosterror != MMSYSERR_NOERROR) {
+        goto free_descriptor;
+    }
+
+    if (midi->latency == 0) {
+        num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
+        output_buffer_len = max_sysex_len / num_buffers;
+        if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
+            output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
+    } else {
+        num_buffers = max(midi->buffer_len, midi->latency / 2);
+        if (num_buffers < MIN_STREAM_BUFFERS)
+            num_buffers = MIN_STREAM_BUFFERS;
+        output_buffer_len = STREAM_BUFFER_LEN;
+
+        propdata.cbStruct = sizeof(MIDIPROPTEMPO);
+        propdata.dwTempo = 480000; /* microseconds per quarter */
+        pm_hosterror = midiStreamProperty(info->handle.stream,
+                                          (LPBYTE) & propdata,
+                                          MIDIPROP_SET | MIDIPROP_TEMPO);
+        if (pm_hosterror) goto close_device;
+
+        divdata.cbStruct = sizeof(MIDIPROPTEMPO);
+        divdata.dwTimeDiv = 480;   /* divisions per quarter */
+        pm_hosterror = midiStreamProperty(info->handle.stream,
+                                          (LPBYTE) & divdata,
+                                          MIDIPROP_SET | MIDIPROP_TIMEDIV);
+        if (pm_hosterror) goto close_device;
+    }
+    /* allocate buffers */
+    if (allocate_buffers(info, output_buffer_len, num_buffers)) 
+        goto free_buffers;
+    /* start device */
+    if (midi->latency != 0) {
+        pm_hosterror = midiStreamRestart(info->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(info->handle.out);
+free_descriptor:
+    midi->api_info = NULL;
+    winmm_out_delete(midi); /* frees buffers and m */
+no_memory:
+    if (pm_hosterror) {
+        report_hosterror_out();
+        return pmHostError;
+    }
+    return pmInsufficientMemory;
+}
+
+
+/* winmm_out_delete -- carefully free data associated with midi */
+/**/
+static void winmm_out_delete(PmInternal *midi)
+{
+    int i;
+    /* delete system dependent device data */
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    if (info) {
+        if (info->buffer_signal) {
+            /* don't report errors -- better not to stop cleanup */
+            CloseHandle(info->buffer_signal);
+        }
+        /* if using stream output, free buffers */
+        for (i = 0; i < info->num_buffers; i++) {
+            if (info->buffers[i]) pm_free(info->buffers[i]);
+        }
+        info->num_buffers = 0;
+        pm_free(info->buffers);
+        info->max_buffers = 0;
+    }
+    midi->api_info = NULL;
+    pm_free(info); /* delete */
+}
+
+
+/* see comments for winmm_in_close */
+static PmError winmm_out_close(PmInternal *midi)
+{
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    if (info->handle.out) {
+        /* device to close */
+        if (midi->latency == 0) {
+            pm_hosterror = midiOutClose(info->handle.out);
+        } else {
+            pm_hosterror = midiStreamClose(info->handle.stream);
+        }
+        /* regardless of outcome, free memory */
+        winmm_out_delete(midi);
+    }
+    if (pm_hosterror) {
+        report_hosterror_out();
+        return pmHostError;
+    }
+    return pmNoError;
+}
+
+
+static PmError winmm_out_abort(PmInternal *midi)
+{
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+
+    /* only stop output streams */
+    if (midi->latency > 0) {
+        pm_hosterror = midiStreamStop(info->handle.stream);
+        if (pm_hosterror) {
+            report_hosterror_out();
+            return pmHostError;
+        }
+    }
+    return pmNoError;
+}
+
+
+static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)
+{
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    assert(info);
+    if (info->hdr) {
+        pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr, 
+                                            sizeof(MIDIHDR));
+        if (pm_hosterror) {
+            /* do not send message */
+        } else if (midi->latency == 0) {
+            /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded
+             * should be zero. This is set in get_free_sysex_buffer(). 
+             * The msg length goes in dwBufferLength in spite of what
+             * Microsoft documentation says (or doesn't say). */
+            info->hdr->dwBufferLength = info->hdr->dwBytesRecorded;
+            info->hdr->dwBytesRecorded = 0;
+            pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr, 
+                                          sizeof(MIDIHDR));
+        } else {
+            pm_hosterror = midiStreamOut(info->handle.stream, info->hdr, 
+                                         sizeof(MIDIHDR));
+        }
+        midi->fill_base = NULL;
+        info->hdr = NULL;
+        if (pm_hosterror) {
+            report_hosterror_out();
+            return pmHostError;
+        }
+    }
+    return pmNoError;
+}
+
+
+static PmError winmm_write_short(PmInternal *midi, PmEvent *event)
+{
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    PmError rslt = pmNoError;
+    assert(info);
+
+    if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
+        pm_hosterror = midiOutShortMsg(info->handle.out, event->message);
+        if (pm_hosterror) {
+            if (info->hdr) {  /* device disconnect may delete hdr */
+                info->hdr->dwFlags = 0; /* release the buffer */
+            }
+            report_hosterror_out();
+            return pmHostError;
+        }
+    } else {  /* use midiStream interface -- pass data through buffers */
+        unsigned long when = event->timestamp;
+        unsigned long delta;
+        int full;
+        if (when == 0) when = midi->now;
+        /* when is in real_time; translate to intended stream time */
+        when = when + info->delta + midi->latency;
+        /* make sure we don't go backward in time */
+        if (when < info->last_time) when = info->last_time;
+        delta = when - info->last_time;
+        info->last_time = when;
+        /* before we insert any data, we must have a buffer */
+        if (info->hdr == NULL) {
+            /* stream interface: buffers allocated when stream is opened */
+            info->hdr = get_free_output_buffer(midi);
+        }
+        full = add_to_buffer(info, info->hdr, delta, event->message);
+        /* note: winmm_write_flush sets pm_hosterror etc. on host error */
+        if (full) rslt = winmm_write_flush(midi, when);
+    }
+    return rslt;
+}
+
+#define winmm_begin_sysex winmm_write_flush
+#ifndef winmm_begin_sysex
+static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+    winmm_info_type m = (winmm_info_type) midi->api_info;
+    PmError rslt = pmNoError;
+
+    if (midi->latency == 0) {
+        /* do nothing -- it's handled in winmm_write_byte */
+    } else {
+        /* sysex expects an empty sysex buffer, so send whatever is here */
+        rslt = winmm_write_flush(midi);
+    }
+    return rslt;
+}
+#endif
+
+static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
+{
+    /* could check for callback_error here, but I haven't checked
+     * what happens if we exit early and don't finish the sysex msg
+     * and clean up
+     */
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    PmError rslt = pmNoError;
+    LPMIDIHDR hdr = info->hdr;
+    if (!hdr) return rslt; /* something bad happened earlier,
+            do not report an error because it would have been 
+            reported (at least) once already */
+    /* a(n old) version of MIDI YOKE requires a zero byte after
+     * the sysex message, but do not increment dwBytesRecorded: */
+    hdr->lpData[hdr->dwBytesRecorded] = 0;
+    if (midi->latency == 0) {
+#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
+        /* DEBUG CODE: */
+        { int i; int len = info->hdr->dwBufferLength;
+          printf("OutLongMsg %d ", len);
+          for (i = 0; i < len; i++) {
+              printf("%2x ", (unsigned char) (info->hdr->lpData[i]));
+          }
+        }
+#endif
+    } else {
+        /* Using stream interface. There are accumulated bytes in info->hdr
+           to send using midiStreamOut
+         */
+        /* 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;
+    }
+    rslt = winmm_write_flush(midi, timestamp);
+    return rslt;
+}
+
+
+static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,
+                                PmTimestamp timestamp)
+{
+    /* write a sysex byte */
+    PmError rslt = pmNoError;
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    LPMIDIHDR hdr = info->hdr;
+    unsigned char *msg_buffer;
+    assert(info);
+    if (!hdr) {
+        info->hdr = hdr = get_free_output_buffer(midi);
+        assert(hdr);
+        midi->fill_base = (unsigned char *) info->hdr->lpData;
+        midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded);
+        /* when buffer fills, Pm_WriteSysEx will revert to calling
+         * pmwin_write_byte, which expect to have space, so leave
+         * one byte free for pmwin_write_byte. Leave another byte
+         * of space for zero after message to make early version of 
+         * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
+        midi->fill_length = hdr->dwBufferLength - 2;
+        if (midi->latency != 0) {
+            unsigned long when = (unsigned long) timestamp;
+            unsigned long delta;
+            unsigned long *ptr;
+            if (when == 0) when = midi->now;
+            /* when is in real_time; translate to intended stream time */
+            when = when + info->delta + midi->latency;
+            /* make sure we don't go backward in time */
+            if (when < info->last_time) when = info->last_time;
+            delta = when - info->last_time;
+            info->last_time = when;
+
+            ptr = (unsigned long *) hdr->lpData;
+            *ptr++ = delta;
+            *ptr++ = 0;
+            *ptr = MEVT_F_LONG;
+            hdr->dwBytesRecorded = 3 * sizeof(long);
+            /* data will be added at an offset of dwBytesRecorded ... */
+        }
+    }
+    /* add the data byte */
+    msg_buffer = (unsigned char *) (hdr->lpData);
+    msg_buffer[hdr->dwBytesRecorded++] = byte;
+
+    /* see if buffer is full, leave one byte extra for pad */
+    if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
+        /* write what we've got and continue */
+        rslt = winmm_end_sysex(midi, timestamp); 
+    }
+    return rslt;
+}
+
+
+static PmTimestamp winmm_synchronize(PmInternal *midi)
+{
+    winmm_info_type info;
+    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 */
+    info = (winmm_info_type) midi->api_info;
+    pm_stream_time_2 = pm_time_get(info);
+
+    do {
+        /* read real_time between two reads of stream time */
+        pm_stream_time = pm_stream_time_2;
+        real_time = (*midi->time_proc)(midi->time_info);
+        pm_stream_time_2 = pm_time_get(info);
+        /* repeat if more than 1ms elapsed */
+    } while (pm_stream_time_2 > pm_stream_time + 1);
+    info->delta = pm_stream_time - real_time;
+    info->sync_time = real_time;
+    return real_time;
+}
+
+
+/* winmm_streamout_callback -- unprepare (free) buffer header */
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+        DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
+{
+    PmInternal *midi = (PmInternal *) dwInstance;
+    winmm_info_type info = (winmm_info_type) midi->api_info;
+    LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
+    int err;
+
+    /* Even if an error is pending, I think we should unprepare msgs and
+       signal their arrival
+     */
+    /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n", 
+           hdr, wMsg, MOM_DONE); */
+    if (wMsg == MOM_DONE) {
+        MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr, 
+                                              sizeof(MIDIHDR));
+        assert(ret == MMSYSERR_NOERROR);
+    } else if (wMsg == MOM_CLOSE) {
+        /* The streaming API gets a callback when the device is closed.
+         * The non-streaming API gets a callback when the device is
+         * removed or closed. It is misleading to set is_removed when
+         * the device is closed normally, but in that case, midi itself
+         * will be freed immediately, so there should be no way to
+         * observe is_removed == TRUE. On the other hand, if the device
+         * is removed, setting is_removed will cause PortMidi to return
+         * the pmDeviceRemoved error on attempts to output to the device.
+         *     In the case of normal closing, due to midiOutClose(), 
+         * the call below is reentrant (!), but for some reason this does
+         * not cause an error or infinite recursion, so we are not taking
+         * any precautions to flag midi as "in the process of closing."
+         */
+        midi->is_removed = TRUE;
+        midiOutClose(info->handle.out);
+    }
+    /* signal client in case it is blocked waiting for buffer */
+    err = SetEvent(info->buffer_signal);
+    assert(err); /* false -> error */
+}
+
+
+/*
+===========================================================================
+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_check_host_error
+                                     };
+
+pm_fns_node pm_winmm_out_dictionary = {
+                                          winmm_write_short,
+                                          winmm_begin_sysex,
+                                          winmm_end_sysex,
+                                          winmm_write_byte,
+            /* short realtime message: */ winmm_write_short,  
+                                          winmm_write_flush,
+                                          winmm_synchronize,
+                                          winmm_out_open,
+                                          winmm_out_abort,
+                                          winmm_out_close,
+                                          none_poll,
+                                          winmm_check_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 MMDEBUG
+    int doneAny = 0;
+    printf("pm_winmm_term called\n");
+#endif
+    for (i = 0; i < pm_descriptor_len; i++) {
+        PmInternal *midi = pm_descriptors[i].pm_internal;
+        if (midi) {
+            winmm_info_type info = (winmm_info_type) midi->api_info;
+            if (info->handle.out) {
+                /* close next open device*/
+#ifdef MMDEBUG
+                if (doneAny == 0) {
+                    printf("begin closing open devices...\n");
+                    doneAny = 1;
+                }
+#endif
+                /* close all open ports */
+                (*midi->dictionary->close)(midi);
+            }
+        }
+    }
+    if (midi_in_caps) {
+        pm_free(midi_in_caps);
+        midi_in_caps = NULL;
+    }
+    if (midi_out_caps) {
+        pm_free(midi_out_caps);
+        midi_out_caps = NULL;
+    }
+#ifdef MMDEBUG
+    if (doneAny) {
+        printf("warning: devices were left open. They have been closed.\n");
+    }
+    printf("pm_winmm_term exiting\n");
+#endif
+    pm_descriptor_len = 0;
+}
diff --git a/portmidi/pm_win/pmwinmm.h b/portmidi/pm_win/pmwinmm.h
--- a/portmidi/pm_win/pmwinmm.h
+++ b/portmidi/pm_win/pmwinmm.h
@@ -1,5 +1,5 @@
-/* midiwin32.h -- system-specific definitions */
-
-void pm_winmm_init( void );
-void pm_winmm_term( void );
-
+/* pmwinmm.h -- system-specific definitions for windows multimedia API */
+
+void pm_winmm_init( void );
+void pm_winmm_term( void );
+
diff --git a/portmidi/porttime/porttime.h b/portmidi/porttime/porttime.h
--- a/portmidi/porttime/porttime.h
+++ b/portmidi/porttime/porttime.h
@@ -1,72 +1,106 @@
-/* porttime.h -- portable interface to millisecond timer */
-
-/* CHANGE LOG FOR PORTTIME
-  10-Jun-03 Mark Nelson & RBD
-    boost priority of timer thread in ptlinux.c implementation
- */
-
-/* Should there be a way to choose the source of time here? */
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-typedef enum {
-    ptNoError = 0,         /* success */
-    ptHostError = -10000,  /* a system-specific error occurred */
-    ptAlreadyStarted,      /* cannot start timer because it is already started */
-    ptAlreadyStopped,      /* cannot stop timer because it is already stopped */
-    ptInsufficientMemory   /* memory could not be allocated */
-} PtError;
-
-
-typedef long PtTimestamp;
-
-typedef void (PtCallback)( PtTimestamp timestamp, void *userData );
-
-/*
-    Pt_Start() starts a real-time service.
-
-    resolution is the timer resolution in ms. The time will advance every
-    resolution ms.
-
-    callback is a function pointer to be called every resolution ms.
-
-    userData is passed to callback as a parameter.
-
-    return value:
-    Upon success, returns ptNoError. See PtError for other values.
-*/
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
-
-/*
-    Pt_Stop() stops the timer.
-
-    return value:
-    Upon success, returns ptNoError. See PtError for other values.
-*/
-PtError Pt_Stop();
-
-/*
-    Pt_Started() returns true iff the timer is running.
-*/
-int Pt_Started();
-
-/* 
-    Pt_Time() returns the current time in ms.
-*/
-PtTimestamp Pt_Time();
-
-/*
-    Pt_Sleep() pauses, allowing other threads to run.
-
-    duration is the length of the pause in ms. The true duration 
-    of the pause may be rounded to the nearest or next clock tick
-    as determined by resolution in Pt_Start().
-*/
-void Pt_Sleep(long duration);
-
-#ifdef __cplusplus
-}
-#endif
+/** @file porttime.h portable interface to millisecond timer. */
+
+/* CHANGE LOG FOR PORTTIME
+  10-Jun-03 Mark Nelson & RBD
+    boost priority of timer thread in ptlinux.c implementation
+ */
+
+#ifndef PORTMIDI_PORTTIME_H
+#define PORTMIDI_PORTTIME_H
+
+/* Should there be a way to choose the source of time here? */
+
+#ifdef WIN32
+#ifndef INT32_DEFINED
+/* rather than having users install a special .h file for windows, 
+   just put the required definitions inline here. portmidi.h uses
+   these too, so the definitions are (unfortunately) duplicated there
+*/
+typedef int int32_t;
+typedef unsigned int uint32_t;
+#define INT32_DEFINED
+#endif
+#else
+#include <stdint.h> /* needed for int32_t */
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef PMEXPORT
+#ifdef _WINDLL
+#define PMEXPORT __declspec(dllexport)
+#else
+#define PMEXPORT 
+#endif
+#endif
+
+/** @defgroup grp_porttime PortTime: Millisecond Timer
+    @{
+*/
+
+typedef enum {
+    ptNoError = 0,         /**< success */
+    ptHostError = -10000,  /**< a system-specific error occurred */
+    ptAlreadyStarted,      /**< cannot start timer because it is already
+                              started */
+    ptAlreadyStopped,      /**< cannot stop timer because it is already
+                              stopped */
+    ptInsufficientMemory   /**< memory could not be allocated */
+} PtError; /**< @brief @enum  PtError PortTime error code; a common return type.
+            * No error is indicated by zero; errors are indicated by < 0.
+            */
+
+/** real time or time offset in milliseconds. */
+typedef int32_t PtTimestamp;
+
+/** a function that gets a current time */
+typedef void (PtCallback)(PtTimestamp timestamp, void *userData);
+
+/** start a real-time clock service.
+
+    @param resolution the timer resolution in ms. The time will advance every
+    \p resolution ms.
+
+    @param callback a function pointer to be called every resolution ms.
+
+    @param userData is passed to \p callback as a parameter.
+
+    @return #ptNoError on success. See #PtError for other values.
+*/
+PMEXPORT PtError Pt_Start(int resolution, PtCallback *callback, void *userData);
+
+/** stop the timer.
+
+    @return #ptNoError on success. See #PtError for other values.
+*/
+PMEXPORT PtError Pt_Stop(void);
+
+/** test if the timer is running.
+ 
+    @return TRUE or FALSE
+*/
+PMEXPORT int Pt_Started(void);
+
+/** get the current time in ms.
+
+    @return the current time
+*/
+PMEXPORT PtTimestamp Pt_Time(void);
+
+/** pauses the current thread, allowing other threads to run.
+
+    @param duration the length of the pause in ms. The true duration 
+    of the pause may be rounded to the nearest or next clock tick
+    as determined by resolution in #Pt_Start().
+*/
+PMEXPORT void Pt_Sleep(int32_t duration);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PORTMIDI_PORTTIME_H */
diff --git a/portmidi/porttime/ptlinux.c b/portmidi/porttime/ptlinux.c
--- a/portmidi/porttime/ptlinux.c
+++ b/portmidi/porttime/ptlinux.c
@@ -1,133 +1,139 @@
-/* ptlinux.c -- portable timer implementation for linux */
-
-
-/* IMPLEMENTATION NOTES (by Mark Nelson): 
-
-Unlike Windows, Linux has no system call to request a periodic callback,
-so if Pt_Start() receives a callback parameter, it must create a thread
-that wakes up periodically and calls the provided callback function.
-If running as superuser, use setpriority() to renice thread to -20.  
-One could also set the timer thread to a real-time priority (SCHED_FIFO
-and SCHED_RR), but this is dangerous for This is necessary because  
-if the callback hangs it'll never return. A more serious reason
-is that the current scheduler implementation busy-waits instead 
-of sleeping when realtime threads request a sleep of <=2ms (as a way 
-to get around the 10ms granularity), which means the thread would never 
-let anyone else on the CPU.
-
-CHANGE LOG
-
-18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer
-            thread. Simplified implementation notes. 
-
-*/
-/* stdlib, stdio, unistd, and sys/types were added because they appeared
- * in a Gentoo patch, but I'm not sure why they are needed. -RBD
- */
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include "porttime.h"
-#include "sys/time.h"
-#include "sys/resource.h"
-#include "sys/timeb.h"
-#include "pthread.h"
-
-#define TRUE 1
-#define FALSE 0
-
-static int time_started_flag = FALSE;
-static struct timeb time_offset = {0, 0, 0, 0};
-static pthread_t pt_thread_pid;
-
-/* note that this is static data -- we only need one copy */
-typedef struct {
-    int id;
-    int resolution;
-    PtCallback *callback;
-    void *userData;
-} pt_callback_parameters;
-
-static int pt_callback_proc_id = 0;
-
-static void *Pt_CallbackProc(void *p)
-{
-    pt_callback_parameters *parameters = (pt_callback_parameters *) p;
-    int mytime = 1;
-    /* to kill a process, just increment the pt_callback_proc_id */
-    /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
-           parameters->id); */
-    if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
-	while (pt_callback_proc_id == parameters->id) {
-        /* wait for a multiple of resolution ms */
-        struct timeval timeout;
-        int delay = mytime++ * parameters->resolution - Pt_Time();
-        if (delay < 0) delay = 0;
-        timeout.tv_sec = 0;
-        timeout.tv_usec = delay * 1000;
-        select(0, NULL, NULL, NULL, &timeout);
-        (*(parameters->callback))(Pt_Time(), parameters->userData);
-    }
-    /* printf("Pt_CallbackProc exiting\n"); */
-    // free(parameters);
-    return NULL;
-}
-
-
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
-{
-    if (time_started_flag) return ptNoError;
-    ftime(&time_offset); /* need this set before process runs */
-    if (callback) {
-        int res;
-        pt_callback_parameters *parms = (pt_callback_parameters *) 
-            malloc(sizeof(pt_callback_parameters));
-        if (!parms) return ptInsufficientMemory;
-        parms->id = pt_callback_proc_id;
-        parms->resolution = resolution;
-        parms->callback = callback;
-        parms->userData = userData;
-        res = pthread_create(&pt_thread_pid, NULL, 
-                             Pt_CallbackProc, parms);
-        if (res != 0) return ptHostError;
-    }
-    time_started_flag = TRUE;
-    return ptNoError;
-}
-
-
-PtError Pt_Stop()
-{
-    /* printf("Pt_Stop called\n"); */
-    pt_callback_proc_id++;
-    pthread_join(pt_thread_pid, NULL);
-    time_started_flag = FALSE;
-    return ptNoError;
-}
-
-
-int Pt_Started()
-{
-    return time_started_flag;
-}
-
-
-PtTimestamp Pt_Time()
-{
-    long seconds, milliseconds;
-    struct timeb now;
-    ftime(&now);
-    seconds = now.time - time_offset.time;
-    milliseconds = now.millitm - time_offset.millitm;
-    return seconds * 1000 + milliseconds;
-}
-
-
-void Pt_Sleep(long duration)
-{
-    usleep(duration * 1000);
-}
-
-
-
+/* ptlinux.c -- portable timer implementation for linux */
+
+
+/* IMPLEMENTATION NOTES (by Mark Nelson): 
+
+Unlike Windows, Linux has no system call to request a periodic callback,
+so if Pt_Start() receives a callback parameter, it must create a thread
+that wakes up periodically and calls the provided callback function.
+If running as superuser, use setpriority() to renice thread to -20.  
+One could also set the timer thread to a real-time priority (SCHED_FIFO
+and SCHED_RR), but this is dangerous for This is necessary because  
+if the callback hangs it'll never return. A more serious reason
+is that the current scheduler implementation busy-waits instead 
+of sleeping when realtime threads request a sleep of <=2ms (as a way 
+to get around the 10ms granularity), which means the thread would never 
+let anyone else on the CPU.
+
+CHANGE LOG
+
+18-Jul-03 Roger Dannenberg -- Simplified code to set priority of timer
+            thread. Simplified implementation notes. 
+
+*/
+/* stdlib, stdio, unistd, and sys/types were added because they appeared
+ * in a Gentoo patch, but I'm not sure why they are needed. -RBD
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include "porttime.h"
+#include "time.h"
+#include "sys/resource.h"
+#include "pthread.h"
+
+#define TRUE 1
+#define FALSE 0
+
+#ifndef CLOCK_MONOTONIC_RAW
+#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
+#endif
+
+static int time_started_flag = FALSE;
+static struct timespec time_offset = {0, 0};
+static pthread_t pt_thread_pid;
+static int pt_thread_created = FALSE;
+
+/* note that this is static data -- we only need one copy */
+typedef struct {
+    int id;
+    int resolution;
+    PtCallback *callback;
+    void *userData;
+} pt_callback_parameters;
+
+static int pt_callback_proc_id = 0;
+
+static void *Pt_CallbackProc(void *p)
+{
+    pt_callback_parameters *parameters = (pt_callback_parameters *) p;
+    int mytime = 1;
+    /* to kill a process, just increment the pt_callback_proc_id */
+    /* printf("pt_callback_proc_id %d, id %d\n", pt_callback_proc_id,
+           parameters->id); */
+    if (geteuid() == 0) setpriority(PRIO_PROCESS, 0, -20);
+    while (pt_callback_proc_id == parameters->id) {
+        /* wait for a multiple of resolution ms */
+        struct timeval timeout;
+        int delay = mytime++ * parameters->resolution - Pt_Time();
+        if (delay < 0) delay = 0;
+        timeout.tv_sec = 0;
+        timeout.tv_usec = delay * 1000;
+        select(0, NULL, NULL, NULL, &timeout);
+        (*(parameters->callback))(Pt_Time(), parameters->userData);
+    }
+    /* printf("Pt_CallbackProc exiting\n"); */
+    // free(parameters);
+    return NULL;
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+    if (time_started_flag) return ptNoError;
+    /* need this set before process runs: */
+    clock_gettime(CLOCK_MONOTONIC_RAW, &time_offset);
+    if (callback) {
+        int res;
+        pt_callback_parameters *parms = (pt_callback_parameters *)
+            malloc(sizeof(pt_callback_parameters));
+        if (!parms) return ptInsufficientMemory;
+        parms->id = pt_callback_proc_id;
+        parms->resolution = resolution;
+        parms->callback = callback;
+        parms->userData = userData;
+        res = pthread_create(&pt_thread_pid, NULL,
+                             Pt_CallbackProc, parms);
+        if (res != 0) return ptHostError;
+        pt_thread_created = TRUE;
+    }
+    time_started_flag = TRUE;
+    return ptNoError;
+}
+
+
+PtError Pt_Stop(void)
+{
+    /* printf("Pt_Stop called\n"); */
+    pt_callback_proc_id++;
+    if (pt_thread_created) {
+        pthread_join(pt_thread_pid, NULL);
+        pt_thread_created = FALSE;
+    }
+    time_started_flag = FALSE;
+    return ptNoError;
+}
+
+
+int Pt_Started(void)
+{
+    return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time(void)
+{
+    long seconds, ms;
+    struct timespec now;
+    clock_gettime(CLOCK_MONOTONIC_RAW, &now);
+    seconds = now.tv_sec - time_offset.tv_sec;
+    ms = (now.tv_nsec - time_offset.tv_nsec) / 1000000; /* round down */
+    return seconds * 1000 + ms;
+}
+
+
+void Pt_Sleep(int32_t duration)
+{
+    usleep(duration * 1000);
+}
diff --git a/portmidi/porttime/ptmacosx_cf.c b/portmidi/porttime/ptmacosx_cf.c
new file mode 100644
--- /dev/null
+++ b/portmidi/porttime/ptmacosx_cf.c
@@ -0,0 +1,140 @@
+/* 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(void)
+{
+    printf("Pt_Stop called\n");
+
+    CFRunLoopStop(timerRunLoop);
+    time_started_flag = FALSE;
+    return ptNoError;
+}
+
+
+int Pt_Started(void)
+{
+    return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time(void)
+{
+    CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
+    return (PtTimestamp) ((now - startTime) * 1000.0);
+}
+
+
+void Pt_Sleep(int32_t duration)
+{
+    usleep(duration * 1000);
+}
diff --git a/portmidi/porttime/ptmacosx_mach.c b/portmidi/porttime/ptmacosx_mach.c
--- a/portmidi/porttime/ptmacosx_mach.c
+++ b/portmidi/porttime/ptmacosx_mach.c
@@ -1,130 +1,205 @@
-/* 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 <unistd.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();
-	long timestamp;
-        if (delay < 0) delay = 0;
-        wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
-        wait_time += AudioGetCurrentHostTime();
-        error = mach_wait_until(wait_time);
-	timestamp = Pt_Time();
-        (*(parameters->callback))(timestamp, parameters->userData);
-    }
-    free(parameters);
-    return NULL;
-}
-
-
-PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
-{
-    if (time_started_flag) return ptAlreadyStarted;
-    start_time = AudioGetCurrentHostTime();
-    
-    if (callback) {
-        int res;
-        pt_callback_parameters *parms;
-
-        parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
-        if (!parms) return ptInsufficientMemory;
-        parms->id = pt_callback_proc_id;
-        parms->resolution = resolution;
-        parms->callback = callback;
-        parms->userData = userData;
-        res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
-        if (res != 0) return ptHostError;
-    }
-    
-    time_started_flag = TRUE;
-    return ptNoError;
-}
-
-
-PtError Pt_Stop()
-{
-    /* printf("Pt_Stop called\n"); */
-    pt_callback_proc_id++;
-    time_started_flag = FALSE;
-    return ptNoError;
-}
-
-
-int Pt_Started()
-{
-    return time_started_flag;
-}
-
-
-PtTimestamp Pt_Time()
-{
-    UInt64 clock_time, nsec_time;
-    clock_time = AudioGetCurrentHostTime() - start_time;
-    nsec_time = AudioConvertHostTimeToNanos(clock_time);
-    return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
-}
-
-
-void Pt_Sleep(long duration)
-{
-    usleep(duration * 1000);
-}
+/* 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 <unistd.h>
+#include <AvailabilityMacros.h>
+
+#include "porttime.h"
+#include "sys/time.h"
+#include "pthread.h"
+
+#ifndef NSEC_PER_MSEC
+#define NSEC_PER_MSEC 1000000
+#endif
+#define THREAD_IMPORTANCE 63
+
+/* QOS headers are available as of macOS 10.10 */
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000
+#include "sys/qos.h"
+#define HAVE_APPLE_QOS 1
+#else
+#undef HAVE_APPLE_QOS
+#endif
+
+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);
+    }
+    
+    /* Most important, set real-time constraints.
+       Define the guaranteed and max fraction of time for the audio thread.
+       These "duty cycle" values can range from 0 to 1.  A value of 0.5
+       means the scheduler would give half the time to the thread.
+       These values have empirically been found to yield good behavior.
+       Good means that audio performance is high and other threads won't starve.
+    */
+    const double kGuaranteedAudioDutyCycle = 0.75;
+    const double kMaxAudioDutyCycle = 0.85;
+    
+    /* Define constants determining how much time the audio thread can
+       use in a given time quantum.  All times are in milliseconds.
+    */
+    /* About 128 frames @44.1KHz */
+    const double kTimeQuantum = 2.9;
+    
+    /* Time guaranteed each quantum. */
+    const double kAudioTimeNeeded = kGuaranteedAudioDutyCycle * kTimeQuantum;
+    
+    /* Maximum time each quantum. */
+    const double kMaxTimeAllowed = kMaxAudioDutyCycle * kTimeQuantum;
+    
+    /* Get the conversion factor from milliseconds to absolute time
+       which is what the time-constraints call needs.
+    */
+    mach_timebase_info_data_t tb_info;
+    mach_timebase_info(&tb_info);
+    double ms_to_abs_time =
+    ((double)tb_info.denom / (double)tb_info.numer) * 1000000;
+    
+    thread_time_constraint_policy_data_t time_constraints;
+    time_constraints.period = (uint32_t)(kTimeQuantum * ms_to_abs_time);
+    time_constraints.computation = (uint32_t)(kAudioTimeNeeded * ms_to_abs_time);
+    time_constraints.constraint = (uint32_t)(kMaxTimeAllowed * ms_to_abs_time);
+    time_constraints.preemptible = 0;
+    
+    error = thread_policy_set(mach_thread_self(),
+                               THREAD_TIME_CONSTRAINT_POLICY,
+                               (thread_policy_t)&time_constraints,
+                               THREAD_TIME_CONSTRAINT_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();
+        PtTimestamp timestamp;
+        if (delay < 0) delay = 0;
+        wait_time = AudioConvertNanosToHostTime((UInt64)delay * NSEC_PER_MSEC);
+        wait_time += AudioGetCurrentHostTime();
+        mach_wait_until(wait_time);
+        timestamp = Pt_Time();
+        (*(parameters->callback))(timestamp, parameters->userData);
+    }
+    free(parameters);
+    return NULL;
+}
+
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+    if (time_started_flag) return ptAlreadyStarted;
+    start_time = AudioGetCurrentHostTime();
+    
+    if (callback) {
+        int res;
+        pt_callback_parameters *parms;
+
+        parms = (pt_callback_parameters *) malloc(sizeof(pt_callback_parameters));
+        if (!parms) return ptInsufficientMemory;
+        parms->id = pt_callback_proc_id;
+        parms->resolution = resolution;
+        parms->callback = callback;
+        parms->userData = userData;
+        
+#ifdef HAVE_APPLE_QOS
+        pthread_attr_t qosAttribute;
+        pthread_attr_init(&qosAttribute);
+        pthread_attr_set_qos_class_np(&qosAttribute, 
+                                      QOS_CLASS_USER_INTERACTIVE, 0);
+
+        res = pthread_create(&pt_thread_pid, &qosAttribute, Pt_CallbackProc, 
+                             parms);
+#else
+        res = pthread_create(&pt_thread_pid, NULL, Pt_CallbackProc, parms);
+#endif
+        
+        struct sched_param sp;
+        memset(&sp, 0, sizeof(struct sched_param));
+        sp.sched_priority = sched_get_priority_max(SCHED_RR);
+        if (pthread_setschedparam(pthread_self(), SCHED_RR, &sp)  == -1) {
+            return ptHostError;
+        }
+        
+        if (res != 0) return ptHostError;
+    }
+    
+    time_started_flag = TRUE;
+    return ptNoError;
+}
+
+
+PtError Pt_Stop(void)
+{
+    /* printf("Pt_Stop called\n"); */
+    pt_callback_proc_id++;
+    pthread_join(pt_thread_pid, NULL);
+    time_started_flag = FALSE;
+    return ptNoError;
+}
+
+
+int Pt_Started(void)
+{
+    return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time(void)
+{
+    UInt64 clock_time, nsec_time;
+    clock_time = AudioGetCurrentHostTime() - start_time;
+    nsec_time = AudioConvertHostTimeToNanos(clock_time);
+    return (PtTimestamp)(nsec_time / NSEC_PER_MSEC);
+}
+
+
+void Pt_Sleep(int32_t duration)
+{
+    usleep(duration * 1000);
+}
diff --git a/portmidi/porttime/ptwinmm.c b/portmidi/porttime/ptwinmm.c
--- a/portmidi/porttime/ptwinmm.c
+++ b/portmidi/porttime/ptwinmm.c
@@ -1,70 +1,70 @@
-/* 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_PTR dwUser, 
-                                  DWORD_PTR dw1, DWORD_PTR 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_PTR) 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;
-}
-
-
-void Pt_Sleep(long duration)
-{
-    Sleep(duration);
-}
+/* 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_PTR dwUser, 
+                                  DWORD_PTR dw1, DWORD_PTR dw2)
+{
+    (*time_callback)(Pt_Time(), (void *) dwUser);
+}
+ 
+
+PMEXPORT 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_PTR) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
+        if (!timer_id) return ptHostError;
+    }
+    return ptNoError;
+}
+
+
+PMEXPORT PtError Pt_Stop(void)
+{
+    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;
+}
+
+
+PMEXPORT int Pt_Started(void)
+{
+    return time_started_flag;
+}
+
+
+PMEXPORT PtTimestamp Pt_Time(void)
+{
+    return timeGetTime() - time_offset;
+}
+
+
+PMEXPORT void Pt_Sleep(int32_t duration)
+{
+    Sleep(duration);
+}
