packages feed

PortMidi 0.2.0.0 → 0.2.0.1

raw patch · 26 files changed

+7455/−6149 lines, 26 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -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).
PortMidi.cabal view
@@ -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
+ README.md view
@@ -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
− README.txt
@@ -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.
portmidi/pm_common/pminternal.h view
@@ -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 */
portmidi/pm_common/pmutil.c view
@@ -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;+}+
portmidi/pm_common/pmutil.h view
@@ -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 */
portmidi/pm_common/portmidi.c view
@@ -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;+}
portmidi/pm_common/portmidi.h view
@@ -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 */
portmidi/pm_linux/pmlinux.c view
@@ -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); }+
− portmidi/pm_linux/pmlinux.h
@@ -1,5 +0,0 @@-/* pmlinux.h */
-
-extern PmDeviceID pm_default_input_device_id;
-extern PmDeviceID pm_default_output_device_id;
-
portmidi/pm_linux/pmlinuxalsa.c view
@@ -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
+ portmidi/pm_linux/pmlinuxnull.c view
@@ -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
+ portmidi/pm_linux/pmlinuxnull.h view
@@ -0,0 +1,6 @@+/* pmlinuxnull.h -- system-specific definitions */++PmError pm_linuxnull_init(void);+void pm_linuxnull_term(void);++
portmidi/pm_mac/pmmac.c view
@@ -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); }++
− portmidi/pm_mac/pmmac.h
@@ -1,4 +0,0 @@-/* pmmac.h */
-
-extern PmDeviceID pm_default_input_device_id;
-extern PmDeviceID pm_default_output_device_id;
portmidi/pm_mac/pmmacosxcm.c view
@@ -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); }
portmidi/pm_mac/pmmacosxcm.h view
@@ -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);
portmidi/pm_win/pmwin.c view
@@ -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); +}++
portmidi/pm_win/pmwinmm.c view
@@ -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;+}
portmidi/pm_win/pmwinmm.h view
@@ -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 );+
portmidi/porttime/porttime.h view
@@ -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 */
portmidi/porttime/ptlinux.c view
@@ -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);+}
+ portmidi/porttime/ptmacosx_cf.c view
@@ -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);+}
portmidi/porttime/ptmacosx_mach.c view
@@ -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);+}
portmidi/porttime/ptwinmm.c view
@@ -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);+}