PortMidi (empty) → 0.1.1
raw patch · 26 files changed
+6547/−0 lines, 26 filesdep +basesetup-changed
Dependencies added: base
Files
- LICENSE +21/−0
- PortMidi.cabal +62/−0
- README.txt +52/−0
- Setup.hs +6/−0
- Sound/PortMidi.hs +299/−0
- portmidi/pm_common/pminternal.h +174/−0
- portmidi/pm_common/pmutil.c +289/−0
- portmidi/pm_common/pmutil.h +127/−0
- portmidi/pm_common/portmidi.c +1084/−0
- portmidi/pm_common/portmidi.h +612/−0
- portmidi/pm_linux/pmlinux.c +60/−0
- portmidi/pm_linux/pmlinux.h +5/−0
- portmidi/pm_linux/pmlinuxalsa.c +789/−0
- portmidi/pm_linux/pmlinuxalsa.h +6/−0
- portmidi/pm_mac/pmmac.c +42/−0
- portmidi/pm_mac/pmmac.h +4/−0
- portmidi/pm_mac/pmmacosxcm.c +935/−0
- portmidi/pm_mac/pmmacosxcm.h +4/−0
- portmidi/pm_win/pmwin.c +114/−0
- portmidi/pm_win/pmwinmm.c +1449/−0
- portmidi/pm_win/pmwinmm.h +5/−0
- portmidi/porttime/porttime.c +3/−0
- portmidi/porttime/porttime.h +72/−0
- portmidi/porttime/ptlinux.c +133/−0
- portmidi/porttime/ptmacosx_mach.c +130/−0
- portmidi/porttime/ptwinmm.c +70/−0
+ 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); +}