packages feed

PortMidi (empty) → 0.1.1

raw patch · 26 files changed

+6547/−0 lines, 26 filesdep +basesetup-changed

Dependencies added: base

Files

+ LICENSE view
@@ -0,0 +1,21 @@+Copyright (c) 2008 Paul H. Liu <paul@thev.net>++This software is provided 'as-is', without any express or implied+warranty. In no event will the authors be held liable for any damages+arising from the use of this software.++Permission is granted to anyone to use this software for any purpose,+including commercial applications, and to alter it and redistribute it+freely, subject to the following restrictions:++1. The origin of this software must not be misrepresented; you must not+   claim that you wrote the original software. If you use this software+   in a product, an acknowledgment in the product documentation would+   be appreciated but is not required.++2. Altered source versions must be plainly marked as such, and must not+   be misrepresented as being the original software.++3. This notice may not be removed or altered from any source+   distribution.+
+ PortMidi.cabal view
@@ -0,0 +1,62 @@+name:		PortMidi+version:	0.1.1+Cabal-Version: >= 1.2+license:	BSD3+license-file:	LICENSE+maintainer:	Paul H. Liu <paul@thev.net>+homepage:	http://haskell.org/haskellwiki/PortMidi+category:	Sound+stability:      experimental+synopsis:	A binding for PortMedia/PortMidi+build-type:     Simple+description:+ A Haskell binding for PortMedia/PortMidi+extra-source-files:+	README.txt+        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_mac/pmmacosxcm.h+        portmidi/pm_win/pmwinmm.h+        portmidi/porttime/porttime.h+Library+  exposed-modules: 	Sound.PortMidi+  build-depends:	base >= 3 && < 5+  extensions:	CPP, ForeignFunctionInterface+  if os(linux)+    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/porttime/porttime.c+          portmidi/porttime/ptlinux.c+   extra-libraries: asound+  else+    if os(darwin)+      include-dirs: portmidi/pm_common portmidi/pm_mac portmidi/porttime+     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_mach.c+     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 +
+ README.txt view
@@ -0,0 +1,52 @@+This is a Haskell module for PortMidi audio library, which supports+real-time MIDI input and output. ++========+ChangLog+========++- Mon Sep 7 EST 2009++  Package up for Hackage DB.++- Thu Sep 4 EST 2008++  Initial 0.1 release, very experimental, and lack proper documentation.++============+Installation+============++The usual cabal installation steps:++1. To configure the module:++       runhaskell Setup.hs configure+   or++       runhaskell Setup.hs configure --user --prefix=DIR++   if you want to install the package to your user's directory instead+   of the system one (replace DIR with your own directory of choice).+2. To build the module:++       runhaskell Setup.hs build++3. To install the module:++       runhaskell Setup.hs install++   This will install a Sound.PortMidi module for GHC.++===================+Contact Information+===================++You may send your bug report and feature request to the package +maintainer: Paul H. Liu <paul@thev.net>.++--+Last Update: Tue Sep 7 EST 2009+ ++
+ Setup.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Distribution.Simple++main :: IO ()+main = defaultMainWithHooks defaultUserHooks
+ Sound/PortMidi.hs view
@@ -0,0 +1,299 @@+{- |+    Interface to PortMidi+-}++{-# OPTIONS_GHC -fglasgow-exts #-}++module Sound.PortMidi ( +  -- * Data Types+    PMError(..)+  , PMStream+  , DeviceInfo(..)+  , DeviceID+  , PMMsg(..)+  , PMEvent(..) +  -- * Constants+  , filterActive+  , filterSysex+  , filterClock+  , filterPlay+  , filterTick+  , filterFD+  , filterUndefined+  , filterReset+  , filterRealtime+  , filterNote+  , filterChannelAftertouch+  , filterPolyAftertouch+  , filterAftertouch+  , filterProgram+  , filterControl+  , filterPitchBend+  , filterMTC+  , filterSongPosition+  , filterSongSelect+  , filterTune+  , filterSystemCommon+  -- * PortMid functions+  , initialize+  , terminate+  , hasHostError+  , getErrorText+  , countDevices+  , getDefaultInputDeviceID+  , getDefaultOutputDeviceID+  , getDeviceInfo+  , openInput+  , openOutput+  , setFilter+  , channel+  , setChannelMask+  , abort+  , close+  , readEvents+  , writeEvents+  , writeShort+  , writeSysEx +  -- * Time function+  , time+  --+  , encodeMsg+  , decodeMsg+  ) where+++import Foreign+import Foreign.C+import Foreign.Marshal+import Foreign.Storable+import Data.IORef+import Data.Bits+import System.IO.Unsafe++data PMError+  = NoError+  | GotData+  | HostError+  | InvalidDeviceId+  | InsufficientMemory+  | BufferTooSmall+  | BufferOverflow+  | BadPtr+  | BadData+  | InternalError+  | BufferMaxSize+  deriving (Eq, Show)++instance Enum PMError where+  fromEnum NoError = 0+  fromEnum GotData = 1+  fromEnum HostError = -10000+  fromEnum InvalidDeviceId = -9999+  fromEnum InsufficientMemory = -9998+  fromEnum BufferTooSmall = -9997+  fromEnum BufferOverflow = -9996+  fromEnum BadPtr = -9995+  fromEnum BadData = -9994+  fromEnum InternalError = -9993+  fromEnum BufferMaxSize = -9992+  toEnum 0 = NoError+  toEnum 1 = GotData+  toEnum (-10000) = HostError+  toEnum (-9999) = InvalidDeviceId+  toEnum (-9998) = InsufficientMemory+  toEnum (-9997) = BufferTooSmall+  toEnum (-9996) = BufferOverflow+  toEnum (-9995) = BadPtr+  toEnum (-9994) = BadData+  toEnum (-9993) = InternalError+  toEnum (-9992) = BufferMaxSize+  toEnum _ = error "PortMidi: toEnum out of bound"++data PortMidiStream+type PMStreamPtr = Ptr PortMidiStream+type PMStream = ForeignPtr PortMidiStream++data DeviceInfo +  =  DeviceInfo+  { interface :: String+  , name      :: String+  , input     :: Bool+  , output    :: Bool+  , opened    :: Bool+  } deriving (Eq, Show)++peekDeviceInfo ptr = do+  let p0 = sizeOf (0 :: Int)+  f <- peekByteOff ptr p0+  s <- peekCString f+  let p1 = p0 + sizeOf f+  n <- peekByteOff ptr p1+  u <- peekCString n+  let p2 = p1 + sizeOf n +  i <- peekByteOff ptr p2+  let p3 = p2 + sizeOf i+  o <- peekByteOff ptr p3+  let p4 = p3 + sizeOf o+  d <- peekByteOff ptr p4+  return $ DeviceInfo s u i o d++type DeviceID = Int++(.<.) = shiftL+(.>.) = shiftR++filterActive, filterSysex, filterClock, filterPlay, filterTick, filterFD, filterUndefined, filterReset, filterRealtime, filterNote, filterChannelAftertouch, filterPolyAftertouch, filterAftertouch, filterProgram, filterControl, filterPitchBend, filterMTC, filterSongPosition, filterSongSelect, filterTune, filterSystemCommon :: CLong++filterActive = 1 .<. 0x0e +filterSysex = 1 .<. 0x00+filterClock = 1 .<. 0x08+filterPlay = (1 .<. 0x0A) .|. (1 .<. 0x0C) .|. (1 .<. 0x0B)+filterTick = 1 .<. 0x09+filterFD = 1 .<. 0x0D+filterUndefined = filterFD+filterReset = 1 .<. 0x0F+filterRealtime = filterActive .|. filterSysex .|. filterClock .|. filterPlay .|. filterUndefined .|. filterReset .|. filterTick +filterNote = (1 .<. 0x19) .|. (1 .<. 0x18)+filterChannelAftertouch = 1 .<. 0x1D+filterPolyAftertouch = 1 .<. 0x1A+filterAftertouch = filterChannelAftertouch .|. filterPolyAftertouch+filterProgram = 1 .<. 0x1C+filterControl = 1 .<. 0x1B+filterPitchBend = 1 .<. 0x1E+filterMTC = 1 .<. 0x01+filterSongPosition = 1 .<. 0x02+filterSongSelect = 1 .<. 0x03+filterTune = 1 .<. 0x06+filterSystemCommon = filterMTC .|. filterSongPosition .|. filterSongSelect .|. filterTune++data PMMsg +  =  PMMsg+  { status :: CLong+  , data1  :: CLong+  , data2  :: CLong +  } deriving (Eq, Show)++encodeMsg (PMMsg s d1 d2) = ((d2 .&. 0xFF) .<. 16) .|. ((d1 .&. 0xFF) .<. 8) .|. (s .&. 0xFF)+decodeMsg i = PMMsg (i .&. 0xFF) ((i .>. 8) .&. 0xFF) ((i .>. 16) .&. 0xFF)++type Timestamp = CULong++data PMEvent +  =  PMEvent +  { message   :: PMMsg+  , timestamp :: Timestamp+  } deriving (Eq, Show)++instance Storable PMEvent where+  sizeOf _ = sizeOf (0::CLong) * 2+  alignment _ = alignment (0::CLong)+  peek ptr = do+    m <- peekByteOff ptr 0+    t <- peekByteOff ptr (sizeOf m)+    return $ PMEvent (decodeMsg m) t+  poke ptr (PMEvent m t) = do+    let v = encodeMsg m :: CLong+    pokeByteOff ptr 0 v +    pokeByteOff ptr (sizeOf v) t+++foreign import ccall "portmidi.h Pm_Initialize" pm_Initialize :: IO Int+initialize :: IO PMError+initialize = pm_Initialize >>= return . toEnum++foreign import ccall "portmidi.h Pm_Terminate" pm_Terminate :: IO Int+terminate :: IO PMError+terminate = pm_Terminate >>= return . toEnum++foreign import ccall "portmidi.h Pm_HasHostError" pm_HasHostError :: PMStreamPtr -> IO Int+hasHostError :: PMStream -> IO Bool+hasHostError = flip withForeignPtr (\stream -> pm_HasHostError stream >>= return . toEnum)++foreign import ccall "portmidi.h Pm_GetErrorText" pm_GetErrorText :: Int -> IO CString+getErrorText :: PMError -> IO String+getErrorText err = pm_GetErrorText (fromEnum err) >>= peekCString++foreign import ccall "portmidi.h Pm_CountDevices" countDevices :: IO DeviceID+foreign import ccall "portmidi.h Pm_GetDefaultInputDeviceID" pm_GetDefaultInputDeviceID :: IO DeviceID +getDefaultInputDeviceID = do+  i <- pm_GetDefaultInputDeviceID+  return $ if i == -1 then Nothing else Just i+foreign import ccall "portmidi.h Pm_GetDefaultOutputDeviceID" pm_GetDefaultOutputDeviceID :: IO DeviceID +getDefaultOutputDeviceID = do+  i <- pm_GetDefaultOutputDeviceID+  return $ if i == -1 then Nothing else Just i++foreign import ccall "portmidi.h Pm_GetDeviceInfo" pm_GetDeviceInfo :: DeviceID -> IO (Ptr ()) +getDeviceInfo :: DeviceID -> IO DeviceInfo+getDeviceInfo deviceID = pm_GetDeviceInfo deviceID >>= peekDeviceInfo++foreign import ccall "portmidi.h Pm_OpenInput" pm_OpenInput :: Ptr PMStreamPtr -> DeviceID -> Ptr () -> CLong -> Ptr () -> Ptr () -> IO Int+openInput :: DeviceID -> IO (Either PMStream PMError)+openInput inputDevice = +  with nullPtr (\ptr -> do+    e <- pm_OpenInput ptr inputDevice nullPtr 0 nullPtr nullPtr+    if e == 0 +      then do+        stream <- peek ptr+        ptr' <- newForeignPtr_ stream+        return $ Left ptr'+      else return (Right (toEnum e)))+      +foreign import ccall "portmidi.h Pm_OpenOutput" pm_OpenOutput :: Ptr PMStreamPtr -> DeviceID -> Ptr () -> CLong -> Ptr () -> Ptr () -> CLong -> IO Int+openOutput :: DeviceID -> Int -> IO (Either PMStream PMError)+openOutput outputDevice latency =+  with nullPtr (\ptr -> do+    e <- pm_OpenOutput ptr outputDevice nullPtr 0 nullPtr nullPtr (fromIntegral latency)+    if e == 0 +      then do+        stream <- peek ptr+        ptr' <- newForeignPtr_ stream+        return $ Left ptr'+      else return (Right (toEnum e)))++foreign import ccall "portmidi.h Pm_SetFilter" pm_SetFilter :: PMStreamPtr -> CLong -> IO Int+setFilter :: PMStream -> CLong -> IO PMError+setFilter stream filter = withForeignPtr stream (\s -> pm_SetFilter s filter >>= return . toEnum)++channel :: Int -> CLong+channel i = 1 .<. i++foreign import ccall "portmidi.h Pm_SetChannelMask" pm_SetChannelMask :: PMStreamPtr -> CLong -> IO Int+setChannelMask :: PMStream -> CLong -> IO PMError+setChannelMask stream mask = withForeignPtr stream (\s -> pm_SetChannelMask s mask >>= return . toEnum)++foreign import ccall "portmidi.h Pm_Abort" pm_Abort :: PMStreamPtr -> IO Int+abort :: PMStream -> IO PMError+abort = flip withForeignPtr (\s -> pm_Abort s >>= return . toEnum)++foreign import ccall "portmidi.h Pm_Close" pm_Close :: PMStreamPtr -> IO Int+close :: PMStream -> IO PMError+close = flip withForeignPtr (\s -> pm_Close s >>= return . toEnum)++foreign import ccall "portmidi.h Pm_Read" pm_Read :: PMStreamPtr -> Ptr PMEvent -> CLong -> IO Int+readEvents :: PMStream -> IO (Either [PMEvent] PMError)+readEvents = flip withForeignPtr (\s -> allocaArray (fromIntegral defaultBufferSize) (\arr -> do+  r <- pm_Read s arr defaultBufferSize+  if r > 0+    then peekArray r arr >>= return . Left+    else return $ Right (toEnum r)))+  where+    defaultBufferSize = 256++foreign import ccall "portmidi.h Pm_Write" pm_Write :: PMStreamPtr -> Ptr PMEvent -> CLong -> IO Int+writeEvents :: PMStream -> [PMEvent] -> IO PMError+writeEvents stream events = withForeignPtr stream (\s -> +  withArrayLen events (\len arr -> pm_Write s arr (fromIntegral len) >>= return . toEnum))++foreign import ccall "portmidi.h Pm_WriteShort" pm_WriteShort :: PMStreamPtr -> CULong -> CLong -> IO Int+writeShort :: PMStream -> PMEvent -> IO PMError+writeShort stream (PMEvent msg time) = withForeignPtr stream (\s ->+  pm_WriteShort s time (encodeMsg msg) >>= return . toEnum)++foreign import ccall "portmidi.h Pm_WriteSysEx" pm_WriteSysEx :: PMStreamPtr -> CULong -> CString -> IO Int+writeSysEx :: PMStream -> Timestamp -> String -> IO PMError+writeSysEx stream time str = withForeignPtr stream (\st ->+  withCString str (\s -> pm_WriteSysEx st time s >>= return . toEnum))++foreign import ccall "porttime.h Pt_Time" time :: IO Timestamp      +
+ portmidi/pm_common/pminternal.h view
@@ -0,0 +1,174 @@+/* 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, 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
+
+ portmidi/pm_common/pmutil.c view
@@ -0,0 +1,289 @@+/* 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;
+}
+
+ portmidi/pm_common/pmutil.h view
@@ -0,0 +1,127 @@+/* 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 */
+ portmidi/pm_common/portmidi.c view
@@ -0,0 +1,1084 @@+#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, 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/pm_common/portmidi.h view
@@ -0,0 +1,612 @@+#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 */
+ portmidi/pm_linux/pmlinux.c view
@@ -0,0 +1,60 @@+/* 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); }
+
+ portmidi/pm_linux/pmlinux.h view
@@ -0,0 +1,5 @@+/* pmlinux.h */
+
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id;
+
+ portmidi/pm_linux/pmlinuxalsa.c view
@@ -0,0 +1,789 @@+/*
+ * 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*)(((client) << 8) | (port)))
+#define GET_DESCRIPTOR_CLIENT(info) ((((int)(info)) >> 8) & 0xff)
+#define GET_DESCRIPTOR_PORT(info) (((int)(info)) & 0xff)
+
+#define BYTE unsigned char
+#define UINT unsigned long
+
+extern pm_fns_node pm_linuxalsa_in_dictionary;
+extern pm_fns_node pm_linuxalsa_out_dictionary;
+
+static snd_seq_t *seq = 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 %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\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%x\n", (unsigned int) 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;
+    }
+}
+ portmidi/pm_linux/pmlinuxalsa.h view
@@ -0,0 +1,6 @@+/* pmlinuxalsa.h -- system-specific definitions */
+
+PmError pm_linuxalsa_init(void);
+void pm_linuxalsa_term(void);
+
+
+ portmidi/pm_mac/pmmac.c view
@@ -0,0 +1,42 @@+/* pmmac.c -- PortMidi os-dependent code */
+
+/* This file only needs to implement:
+pm_init(), which calls various routines to register the 
+available midi devices,
+Pm_GetDefaultInputDeviceID(), and
+Pm_GetDefaultOutputDeviceID().
+It is seperate from pmmacosxcm because we might want to register
+non-CoreMIDI devices.
+*/
+
+#include "stdlib.h"
+#include "portmidi.h"
+#include "pmmacosxcm.h"
+
+PmError pm_init()
+{
+    return pm_macosxcm_init();
+}
+
+void pm_term(void)
+{
+    pm_macosxcm_term();
+}
+
+PmDeviceID pm_default_input_device_id = -1;
+PmDeviceID pm_default_output_device_id = -1;
+
+PmDeviceID Pm_GetDefaultInputDeviceID()
+{
+    return pm_default_input_device_id;
+}
+
+PmDeviceID Pm_GetDefaultOutputDeviceID() {
+    return pm_default_output_device_id;
+}
+
+void *pm_alloc(size_t s) { return malloc(s); }
+
+void pm_free(void *ptr) { free(ptr); }
+
+
+ portmidi/pm_mac/pmmac.h view
@@ -0,0 +1,4 @@+/* pmmac.h */
+
+extern PmDeviceID pm_default_input_device_id;
+extern PmDeviceID pm_default_output_device_id;
+ portmidi/pm_mac/pmmacosxcm.c view
@@ -0,0 +1,935 @@+/*+ * Platform interface to the MacOS X CoreMIDI framework+ * + * Jon Parise <jparise at cmu.edu>+ * and subsequent work by Andrew Zeldis and Zico Kolter+ * and Roger B. Dannenberg+ *+ * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $+ */+ +/* Notes:+    since the input and output streams are represented by MIDIEndpointRef+    values and almost no other state, we store the MIDIEndpointRef on+    descriptors[midi->device_id].descriptor. The only other state we need+    is for errors: we need to know if there is an error and if so, what is+    the error text. 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);+}
+ portmidi/pm_mac/pmmacosxcm.h view
@@ -0,0 +1,4 @@+/* system-specific definitions */
+
+PmError pm_macosxcm_init(void);
+void pm_macosxcm_term(void);
+ portmidi/pm_win/pmwin.c view
@@ -0,0 +1,114 @@+/* 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); 
+}
+
+ portmidi/pm_win/pmwinmm.c view
@@ -0,0 +1,1449 @@+/* 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 dwInstance, 
+                                       DWORD dwParam1, DWORD dwParam2);
+static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
+                                              DWORD dwInstance, DWORD dwParam1, 
+                                              DWORD dwParam2);
+static void CALLBACK winmm_out_callback(HMIDIOUT hmo, UINT wMsg,
+                                        DWORD dwInstance, DWORD dwParam1, 
+                                        DWORD dwParam2);
+
+extern pm_fns_node pm_winmm_in_dictionary;
+extern pm_fns_node pm_winmm_out_dictionary;
+
+static void winmm_out_delete(PmInternal *midi); /* forward reference */
+
+/*
+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) winmm_in_callback,  /* callback address */
+                              (DWORD) 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 dwInstance,   /* application data */
+    DWORD dwParam1,     /* MIDI data */
+    DWORD dwParam2)    /* device timestamp (wrt most recent midiInStart) */
+{
+    static int entry = 0;
+    PmInternal *midi = (PmInternal *) dwInstance;
+    midiwinmm_type m = (midiwinmm_type) midi->descriptor;
+
+    /* 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) winmm_streamout_callback, /* callback fn */
+                                   (DWORD) midi,  /* callback instance data */
+                                   CALLBACK_FUNCTION); /* callback type */
+    } else {
+        /* use stream-based midi output (schedulable in future) */
+        pm_hosterror = midiStreamOpen(&m->handle.stream,  /* device Handle */
+                                      (LPUINT) & dwDevice,  /* device ID pointer */
+                                      1,  /* reserved, must be 1 */
+                                      (DWORD) winmm_streamout_callback,
+                                      (DWORD) midi,  /* callback instance data */
+                                      CALLBACK_FUNCTION);
+    }
+    if (pm_hosterror != MMSYSERR_NOERROR) {
+        goto free_descriptor;
+    }
+
+    if (midi->latency == 0) {
+        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 dwInstance, DWORD dwParam1, 
+                                        DWORD 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 dwInstance, DWORD dwParam1, DWORD 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;
+}
+ portmidi/pm_win/pmwinmm.h view
@@ -0,0 +1,5 @@+/* midiwin32.h -- system-specific definitions */
+
+void pm_winmm_init( void );
+void pm_winmm_term( void );
+
+ portmidi/porttime/porttime.c view
@@ -0,0 +1,3 @@+/* porttime.c -- portable API for millisecond timer */
+
+/* There is no machine-independent implementation code to put here */
+ portmidi/porttime/porttime.h view
@@ -0,0 +1,72 @@+/* 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
+ portmidi/porttime/ptlinux.c view
@@ -0,0 +1,133 @@+/* 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);
+}
+
+
+
+ portmidi/porttime/ptmacosx_mach.c view
@@ -0,0 +1,130 @@+/* 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);
+}
+ portmidi/porttime/ptwinmm.c view
@@ -0,0 +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 dwUser, 
+                                  DWORD dw1, DWORD dw2)
+{
+    (*time_callback)(Pt_Time(), (void *) dwUser);
+}
+ 
+
+PtError Pt_Start(int resolution, PtCallback *callback, void *userData)
+{
+    if (time_started_flag) return ptAlreadyStarted;
+    timeBeginPeriod(resolution);
+    time_resolution = resolution;
+    time_offset = timeGetTime();
+    time_started_flag = TRUE;
+    time_callback = callback;
+    if (callback) {
+        timer_id = timeSetEvent(resolution, 1, winmm_time_callback, 
+            (DWORD) userData, TIME_PERIODIC | TIME_CALLBACK_FUNCTION);
+        if (!timer_id) return ptHostError;
+    }
+    return ptNoError;
+}
+
+
+PtError Pt_Stop()
+{
+    if (!time_started_flag) return ptAlreadyStopped;
+    if (time_callback && timer_id) {
+        timeKillEvent(timer_id);
+        time_callback = NULL;
+        timer_id = 0;
+    }
+    time_started_flag = FALSE;
+    timeEndPeriod(time_resolution);
+    return ptNoError;
+}
+
+
+int Pt_Started()
+{
+    return time_started_flag;
+}
+
+
+PtTimestamp Pt_Time()
+{
+    return timeGetTime() - time_offset;
+}
+
+
+void Pt_Sleep(long duration)
+{
+    Sleep(duration);
+}