RtMidi 0.1.1.0 → 0.2.0.0
raw patch · 13 files changed
+4431/−3485 lines, 13 filesdep +RtMididep +tastydep +tasty-hunitdep ~basenew-component:exe:rtmidi-callbacknew-component:exe:rtmidi-playbacknew-component:exe:rtmidi-pollnew-component:exe:rtmidi-querynew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: RtMidi, tasty, tasty-hunit, unliftio-core
Dependency ranges changed: base
API changes (from Hackage documentation)
- Sound.RtMidi: DebugWarning :: ErrorType
- Sound.RtMidi: DriverError :: ErrorType
- Sound.RtMidi: InvalidDevice :: ErrorType
- Sound.RtMidi: InvalidParameter :: ErrorType
- Sound.RtMidi: InvalidUse :: ErrorType
- Sound.RtMidi: MemoryError :: ErrorType
- Sound.RtMidi: NoDevicesFound :: ErrorType
- Sound.RtMidi: SystemError :: ErrorType
- Sound.RtMidi: ThreadError :: ErrorType
- Sound.RtMidi: UnspecifiedError :: ErrorType
- Sound.RtMidi: Warning :: ErrorType
- Sound.RtMidi: closeInput :: Device -> IO ()
- Sound.RtMidi: closeOutput :: Device -> IO ()
- Sound.RtMidi: data Device
- Sound.RtMidi: data Error
- Sound.RtMidi: data ErrorType
- Sound.RtMidi: instance Foreign.Storable.Storable Sound.RtMidi.Wrapper
- Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.ErrorType
- Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.Wrapper
- Sound.RtMidi: instance GHC.Enum.Enum Sound.RtMidi.ErrorType
- Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.ErrorType
- Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.Wrapper
- Sound.RtMidi: reportError :: Device -> ErrorType -> String -> IO ()
- Sound.RtMidi: setCallbackWithUserData :: Device -> (CDouble -> [CUChar] -> Ptr () -> IO ()) -> Ptr () -> IO ()
+ Sound.RtMidi: InputDeviceType :: DeviceType
+ Sound.RtMidi: OutputDeviceType :: DeviceType
+ Sound.RtMidi: class IsDevice d
+ Sound.RtMidi: closeDevice :: MonadIO m => IsDevice d => d -> m ()
+ Sound.RtMidi: data DeviceType
+ Sound.RtMidi: data InputDevice
+ Sound.RtMidi: data OutputDevice
+ Sound.RtMidi: findPort :: (MonadIO m, IsDevice d) => d -> (String -> Bool) -> m (Maybe Int)
+ Sound.RtMidi: getDeviceType :: IsDevice d => d -> DeviceType
+ Sound.RtMidi: getMessageSized :: MonadIO m => InputDevice -> Int -> m (Double, [Word8])
+ Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.Device
+ Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.DeviceType
+ Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.Error
+ Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.InputDevice
+ Sound.RtMidi: instance GHC.Classes.Eq Sound.RtMidi.OutputDevice
+ Sound.RtMidi: instance GHC.Exception.Type.Exception Sound.RtMidi.Error
+ Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.Device
+ Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.DeviceType
+ Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.Error
+ Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.InputDevice
+ Sound.RtMidi: instance GHC.Show.Show Sound.RtMidi.OutputDevice
+ Sound.RtMidi: instance Sound.RtMidi.IsDevice Sound.RtMidi.InputDevice
+ Sound.RtMidi: instance Sound.RtMidi.IsDevice Sound.RtMidi.OutputDevice
+ Sound.RtMidi: listPorts :: (MonadIO m, IsDevice d) => d -> m [(Int, String)]
+ Sound.RtMidi: newtype Error
- Sound.RtMidi: Error :: ErrorType -> String -> Error
+ Sound.RtMidi: Error :: String -> Error
- Sound.RtMidi: cancelCallback :: Device -> IO ()
+ Sound.RtMidi: cancelCallback :: MonadIO m => InputDevice -> m ()
- Sound.RtMidi: closePort :: Device -> IO ()
+ Sound.RtMidi: closePort :: (MonadIO m, IsDevice d) => d -> m ()
- Sound.RtMidi: compiledApis :: IO [Api]
+ Sound.RtMidi: compiledApis :: MonadIO m => m [Api]
- Sound.RtMidi: createInput :: Api -> String -> Int -> IO Device
+ Sound.RtMidi: createInput :: MonadIO m => Api -> String -> Int -> m InputDevice
- Sound.RtMidi: createOutput :: Api -> String -> IO Device
+ Sound.RtMidi: createOutput :: MonadIO m => Api -> String -> m OutputDevice
- Sound.RtMidi: currentApi :: Device -> IO Api
+ Sound.RtMidi: currentApi :: MonadIO m => IsDevice d => d -> m Api
- Sound.RtMidi: defaultInput :: IO Device
+ Sound.RtMidi: defaultInput :: MonadIO m => m InputDevice
- Sound.RtMidi: defaultOutput :: IO Device
+ Sound.RtMidi: defaultOutput :: MonadIO m => m OutputDevice
- Sound.RtMidi: getMessage :: Device -> IO ([CUChar], Double)
+ Sound.RtMidi: getMessage :: MonadIO m => InputDevice -> m (Double, [Word8])
- Sound.RtMidi: ignoreTypes :: Device -> Bool -> Bool -> Bool -> IO ()
+ Sound.RtMidi: ignoreTypes :: MonadIO m => InputDevice -> Bool -> Bool -> Bool -> m ()
- Sound.RtMidi: openPort :: Device -> Int -> String -> IO ()
+ Sound.RtMidi: openPort :: (MonadIO m, IsDevice d) => d -> Int -> String -> m ()
- Sound.RtMidi: openVirtualPort :: Device -> String -> IO ()
+ Sound.RtMidi: openVirtualPort :: (MonadIO m, IsDevice d) => d -> String -> m ()
- Sound.RtMidi: portCount :: Device -> IO Int
+ Sound.RtMidi: portCount :: (MonadIO m, IsDevice d) => d -> m Int
- Sound.RtMidi: portName :: Device -> Int -> IO String
+ Sound.RtMidi: portName :: (MonadIO m, IsDevice d) => d -> Int -> m (Maybe String)
- Sound.RtMidi: ready :: Device -> IO Bool
+ Sound.RtMidi: ready :: (MonadIO m, IsDevice d) => d -> m Bool
- Sound.RtMidi: sendMessage :: Device -> [CUChar] -> IO ()
+ Sound.RtMidi: sendMessage :: MonadIO m => OutputDevice -> [Word8] -> m ()
- Sound.RtMidi: setCallback :: Device -> (CDouble -> [CUChar] -> IO ()) -> IO ()
+ Sound.RtMidi: setCallback :: MonadUnliftIO m => InputDevice -> (Double -> [Word8] -> m ()) -> m ()
Files
- README.md +35/−0
- RtMidi.cabal +78/−21
- Sound/RtMidi.hs +265/−271
- Sound/RtMidi/Foreign.hsc +110/−0
- examples/callback.hs +7/−4
- examples/playback.hs +4/−3
- examples/poll.hs +21/−0
- examples/query.hs +6/−8
- rtmidi/RtMidi.cpp +3434/−2832
- rtmidi/RtMidi.h +129/−259
- rtmidi/rtmidi_c.cpp +100/−63
- rtmidi/rtmidi_c.h +191/−24
- test/Main.hs +51/−0
+ README.md view
@@ -0,0 +1,35 @@+# RtMidi++Haskell wrapper for [RtMidi](http://www.music.mcgill.ca/~gary/rtmidi/), the lightweight, cross-platform MIDI I/O library.++## Development++This project is tested with Cabal (latest versions of last two compiler lines) and Stack (latest LTS).++You can get started with development like so:++ # Build on OSX CoreMIDI support or Linux with ALSA support+ # You can also pass `--flag RtMidi:jack` for Jack support+ stack build++ # Verify that it works:+ stack exec -- rtmidi-query++There is also a `Dockerfile` in the `docker` directory and some `make` targets that can help you verify Linux builds:++ # Build the image and tag it `haskell-rtmidi-dev`+ make docker-build++ # Enter the docker image+ make docker-repl++ # Inside the docker image:+ cabal update && cabal build -fjack++(Note that you can't use any `RtMidi` functions in the containerized env unless you are running a Linux host, and+even then you'd probably have to start the process with something like `docker run --device /dev/snd`.)++## TODO++* Add Windows MM support. This should only require a few changes to the Cabal file.+* See if there is a way to autodetect Jack in the Cabal file.
RtMidi.cabal view
@@ -1,23 +1,26 @@ name: RtMidi-version: 0.1.1.0-description: Haskell wrapper for RtMidi, the lightweight, cross-platform MIDI I/O library.+version: 0.2.0.0+synopsis: Haskell wrapper for RtMidi, the lightweight, cross-platform MIDI I/O library.+description: Please see the README on GitHub at <https://github.com/riottracker/RtMidi#readme> category: Sound build-type: Simple-author: kohlrabi+author: kohlrabi, Eric Conlon license-file: LICENSE homepage: https://github.com/riottracker/RtMidi-maintainer: kohlrabi@kohlra.biz-extra-source-files: rtmidi/rtmidi_c.h+maintainer: ejconlon@gmail.com+extra-source-files: README.md+ rtmidi/rtmidi_c.h rtmidi/RtMidi.h+ examples/callback.hs examples/playback.hs+ examples/poll.hs examples/query.hs- examples/callback.hs cabal-version: >=1.10 license: MIT Flag alsa { Description: Enable ALSA sequencer api- Default: False+ Default: True } Flag jack {@@ -27,37 +30,91 @@ Flag core { Description: Enable CoreMIDI api- Default: False+ Default: True } -Flag mm {- Description: Enable Windows Multimedia Library api- Default: False-}+-- TODO(ejconlon) Add windows support+-- Flag mm {+-- Description: Enable Windows Multimedia Library api+-- Default: False+-- } library exposed-modules: Sound.RtMidi- -- other-modules: - -- other-extensions: - build-depends: base >=4.9 && <4.13+ other-modules: Sound.RtMidi.Foreign+ build-depends: base >=4.9 && <4.15+ , unliftio-core >= 0.1.2.0 && < 1 default-language: Haskell2010 include-dirs: rtmidi extra-libraries: stdc++ c-sources: rtmidi/RtMidi.cpp rtmidi/rtmidi_c.cpp- if flag(alsa)+ if flag(alsa) && os(linux) cc-options: -D__LINUX_ALSA__ extra-libraries: asound pthread- if flag(jack)+ if flag(jack) && (os(linux) || os(darwin)) cc-options: -D__UNIX_JACK__ extra-libraries: jack- if flag(core)- cc-options: -D__MAXOSX_CORE__- extra-libraries: CoreMIDI CoreAudio CoreFoundation+ if flag(core) && os(darwin)+ cc-options: -D__MACOSX_CORE__+ frameworks: CoreMIDI CoreAudio CoreFoundation+ -- NOTE(ejconlon) This is to make the c ffi wrapper actually catch+ -- the c++ exceptions instead of simply aborting.+ -- Cribbed from https://github.com/fpco/inline-c/pull/89+ -- avoid https://gitlab.haskell.org/ghc/ghc/issues/11829+ ld-options: -Wl,-keep_dwarf_unwind+ ghc-options: -pgmc=clang+++ -- TODO(ejconlon) Add windows support +executable rtmidi-callback+ main-is: callback.hs+ hs-source-dirs: examples+ build-depends:+ base+ , RtMidi+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts +executable rtmidi-playback+ main-is: playback.hs+ hs-source-dirs: examples+ build-depends:+ base+ , RtMidi+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts++executable rtmidi-poll+ main-is: poll.hs+ hs-source-dirs: examples+ build-depends:+ base+ , RtMidi+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts++executable rtmidi-query+ main-is: query.hs+ hs-source-dirs: examples+ build-depends:+ base+ , RtMidi+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts++test-suite rtmidi-test+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: test+ build-depends:+ base+ , RtMidi+ , tasty >= 1.2.3 && < 2+ , tasty-hunit >= 0.10.0.2 && < 1+ default-language: Haskell2010+ ghc-options: -threaded -rtsopts+ source-repository head type: git location: https://github.com/riottracker/RtMidi-
Sound/RtMidi.hs view
@@ -1,54 +1,84 @@-{-# LANGUAGE ForeignFunctionInterface #-}+-- | Interface to RtMidi+module Sound.RtMidi+ ( InputDevice+ , OutputDevice+ , IsDevice (getDeviceType)+ , DeviceType (..)+ , Api (..)+ , Error (..)+ , ready+ , compiledApis+ , openPort+ , openVirtualPort+ , closePort+ , portCount+ , portName+ , listPorts+ , findPort+ , defaultInput+ , createInput+ , setCallback+ , cancelCallback+ , ignoreTypes+ , getMessage+ , getMessageSized+ , defaultOutput+ , createOutput+ , sendMessage+ , closeDevice+ , currentApi+ ) where +import Control.Exception (Exception, throwIO)+import Control.Monad (unless)+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.IO.Unlift (MonadUnliftIO (..))+import Data.Coerce (coerce)+import Data.Word (Word8)+import Foreign (FunPtr, Ptr, Storable (..), alloca, allocaArray, nullPtr, peekArray, with, withArrayLen)+import Foreign.C (CDouble (..), CInt (..), CSize, CString, CUChar (..), peekCString, withCString)+import Sound.RtMidi.Foreign --- | Interface to RtMidi-module Sound.RtMidi (- Device- , Error (..)- , ErrorType (..)- , Api(..)- , ready- , reportError--- , checkForErrors- , compiledApis- , openPort- , openVirtualPort- , closePort- , portCount- , portName- , defaultInput- , createInput- , setCallback- , setCallbackWithUserData- , cancelCallback- , ignoreTypes- , getMessage- , defaultOutput- , createOutput- , sendMessage- , closeInput- , closeOutput- , currentApi- ) where+-- The default message size (in bytes) expected from 'getMessage'+-- Mostly just needs to be bigger than the max MIDI message size, which is 3 bytes+-- However, sysex messages can be quite large, so you might have to use the 'getMessageSized' variant.+defaultMessageSize :: Int+defaultMessageSize = 4 -import Control.Monad-import Foreign-import Foreign.C-import Foreign.C.String+-- | Allows us to discriminate in/out functions in generic contexts+data DeviceType =+ InputDeviceType+ | OutputDeviceType+ deriving (Eq, Show) +newtype Device = Device { unDevice :: Ptr Wrapper }+ deriving (Eq, Show) -data Device = Input (Ptr Wrapper) | Output (Ptr Wrapper)+-- | Generalizes 'InputDevice' and 'OutputDevice' for use in common functions+class IsDevice d where+ toDevice :: d -> Device+ getDeviceType :: d -> DeviceType -device :: Device -> Ptr Wrapper-device (Input x) = x-device (Output x) = x+toDevicePtr :: IsDevice d => d -> Ptr Wrapper+toDevicePtr = unDevice . toDevice -toInput :: Device -> Ptr Wrapper-toInput (Input x) = x+-- | A handle to a device to be used for input+newtype InputDevice = InputDevice { unInputDevice :: Device }+ deriving (Eq, Show) -toOutput :: Device -> Ptr Wrapper-toOutput (Output x) = x+instance IsDevice InputDevice where+ toDevice = unInputDevice+ getDeviceType _ = InputDeviceType +-- | A handle to a device to be used for input+newtype OutputDevice = OutputDevice { unOutputDevice :: Device }+ deriving (Eq, Show)++instance IsDevice OutputDevice where+ toDevice = unOutputDevice+ getDeviceType _ = OutputDeviceType++-- | Enum of RtMidi-supported APIs data Api = UnspecifiedApi | CoreMidiApi@@ -73,279 +103,243 @@ toEnum 4 = MultimediaApi toEnum 5 = DummyApi -data ErrorType- = Warning- | DebugWarning- | UnspecifiedError- | NoDevicesFound- | InvalidDevice- | MemoryError- | InvalidParameter- | InvalidUse- | DriverError- | SystemError- | ThreadError- deriving (Eq, Show)--instance Enum ErrorType where- fromEnum Warning = 0- fromEnum DebugWarning = 1- fromEnum UnspecifiedError = 2- fromEnum NoDevicesFound = 3- fromEnum InvalidDevice = 4- fromEnum MemoryError = 5- fromEnum InvalidParameter = 6- fromEnum InvalidUse = 7- fromEnum DriverError = 8- fromEnum SystemError = 9- fromEnum ThreadError = 10- toEnum 0 = Warning- toEnum 1 = DebugWarning- toEnum 2 = UnspecifiedError- toEnum 3 = NoDevicesFound- toEnum 4 = InvalidDevice- toEnum 5 = MemoryError- toEnum 6 = InvalidParameter- toEnum 7 = InvalidUse- toEnum 8 = DriverError- toEnum 9 = SystemError- toEnum 10 = ThreadError---data Error = Error ErrorType String--data Wrapper = Wrapper- { ptr :: Ptr ()- , ok :: Bool- , msg :: CString- } deriving (Show, Eq)--instance Storable Wrapper where- sizeOf _ = 24- alignment = sizeOf- peek ptr = do- a <- peekByteOff ptr 0- b <- peekByteOff ptr 8- c <- peekByteOff ptr 16- return $ Wrapper a b c- -- | Check if a device is ok-ready :: Device -> IO Bool-ready d = fmap ok $ peek (device d)---checkForErrors :: Device -> IO [Char]-checkForErrors d = peek (device d) >>= (\w -> do- (putStrLn $ show w)- a <- peekArray0 0 $ plusPtr (castPtr (msg w)) 10- return a) >>= return . map castCCharToChar--reportError :: Device -> ErrorType -> String -> IO ()-reportError d et em = withCString em $ rtmidi_error (device d) (toEnum . fromEnum $ et)--foreign import ccall "rtmidi_c.h rtmidi_error"- rtmidi_error :: Ptr Wrapper -> CInt -> CString -> IO ()- -foreign import ccall "rtmidi_c.h rtmidi_sizeof_rtmidi_api"- rtmidi_sizeof_rtmidi_api :: IO CInt---foreign import ccall "rtmidi_c.h rtmidi_get_compiled_api"- rtmidi_get_compiled_api :: Ptr (Ptr CInt) -> IO CInt---foreign import ccall "rtmidi_c.h rtmidi_open_port"- rtmidi_open_port :: Ptr Wrapper -> CInt -> CString -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_open_virtual_port"- rtmidi_open_virtual_port :: Ptr Wrapper -> CString -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_close_port"- rtmidi_close_port :: Ptr Wrapper -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_get_port_count"- rtmidi_get_port_count :: Ptr Wrapper -> IO CInt--foreign import ccall "rtmidi_c.h rtmidi_get_port_name"- rtmidi_get_port_name :: Ptr Wrapper -> CInt -> IO CString---foreign import ccall "rtmidi_c.h rtmidi_in_create_default"- rtmidi_in_create_default :: IO (Ptr Wrapper)--foreign import ccall "rtmidi_c.h rtmidi_in_create"- rtmidi_in_create :: CInt -> CString -> CInt -> IO (Ptr Wrapper)--foreign import ccall "rtmidi_c.h rtmidi_in_free"- rtmidi_in_free :: Ptr Wrapper -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_in_get_current_api"- rtmidi_in_get_current_api :: Ptr Wrapper -> IO CInt--foreign import ccall "rtmidi_c.h rtmidi_in_set_callback"- rtmidi_in_set_callback :: Ptr Wrapper -> FunPtr (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ()) -> Ptr () -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_in_cancel_callback"- rtmidi_in_cancel_callback :: Ptr Wrapper -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_in_ignore_types"- rtmidi_in_ignore_types :: Ptr Wrapper -> Bool -> Bool -> Bool -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_in_get_message"- rtmidi_in_get_message :: Ptr Wrapper -> Ptr (Ptr CUChar) -> Ptr CSize -> IO CDouble--foreign import ccall "rtmidi_c.h rtmidi_out_create_default"- rtmidi_out_create_default :: IO (Ptr Wrapper)--foreign import ccall "rtmidi_c.h rtmidi_out_create"- rtmidi_out_create :: CInt -> CString -> IO (Ptr Wrapper)--foreign import ccall "rtmidi_c.h rtmidi_out_free"- rtmidi_out_free :: Ptr Wrapper -> IO ()--foreign import ccall "rtmidi_c.h rtmidi_out_get_current_api"- rtmidi_out_get_current_api :: Ptr Wrapper -> IO CInt--foreign import ccall "rtmidi_c.h rtmidi_out_send_message"- rtmidi_out_send_message :: Ptr Wrapper -> Ptr CUChar -> CInt -> IO CInt-+ready :: (MonadIO m, IsDevice d) => d -> m Bool+ready = liftIO . fmap ok . peek . toDevicePtr -apiSize :: IO Int-apiSize = fromEnum <$> rtmidi_sizeof_rtmidi_api+-- | An internal RtMidi error+newtype Error = Error String deriving (Eq, Show)+instance Exception Error --- |A static function to determine MIDI 'Api's built in.-compiledApis :: IO [Api]-compiledApis = fmap (map (toEnum . fromEnum)) $ do- n <- fromIntegral <$> rtmidi_get_compiled_api nullPtr- allocaArray n $ flip with $ \ptr -> do- rtmidi_get_compiled_api ptr- peekArray n =<< peek ptr+-- Detects and throws internal errors+guardError :: Ptr Wrapper -> IO ()+guardError dptr = do+ w <- peek dptr+ unless (ok w) $ do+ e <- peekCString (msg w)+ throwIO (Error e) --- -- |Report an error--- reportError :: Error -> IO ()--- reportError (Error e s) = withCString s $ rtmidi_error (toEnum $ fromEnum $ e)+-- | A static function to determine MIDI 'Api's built in.+compiledApis :: MonadIO m => m [Api]+compiledApis = liftIO $ do+ n <- fmap fromIntegral (rtmidi_get_compiled_api nullPtr)+ as <- allocaArray n $ flip with $ \ptr -> do+ rtmidi_get_compiled_api ptr+ x <- peek ptr+ peekArray n x+ pure (map (toEnum . fromEnum) as) --- |Open a MIDI connection-openPort :: Device+-- | Open a MIDI connection+openPort :: (MonadIO m, IsDevice d)+ => d -> Int -- ^ port number -> String -- ^ name for the application port that is used- -> IO ()-openPort d n name = withCString name $ rtmidi_open_port (device d) (toEnum n)+ -> m ()+openPort d n name = liftIO $ do+ let dptr = toDevicePtr d+ withCString name (rtmidi_open_port dptr (toEnum n))+ guardError dptr --- |This function creates a virtual MIDI output port to which other software applications can connect.+-- | This function creates a virtual MIDI output port to which other software applications can connect. -- -- This type of functionality is currently only supported by the Macintosh OS X, Linux ALSA and JACK APIs -- (the function does nothing with the other APIs).-openVirtualPort :: Device -> String -> IO ()-openVirtualPort d name = withCString name $ rtmidi_open_virtual_port (device d)+openVirtualPort :: (MonadIO m, IsDevice d) => d -> String -> m ()+openVirtualPort d name = liftIO $ do+ let dptr = toDevicePtr d+ withCString name (rtmidi_open_virtual_port dptr)+ guardError dptr --- |Close an open MIDI connection (if one exists).-closePort :: Device -> IO ()-closePort d = rtmidi_close_port $ device d+-- | Close an open MIDI connection (if one exists).+closePort :: (MonadIO m, IsDevice d) => d -> m ()+closePort d = liftIO $ do+ let dptr = toDevicePtr d+ rtmidi_close_port dptr+ guardError dptr --- |Return the number of MIDI ports available to the 'Device'.-portCount :: Device -> IO Int-portCount d = fromIntegral <$> (rtmidi_get_port_count $ device d)+-- | Return the number of MIDI ports available to the 'Device'.+portCount :: (MonadIO m, IsDevice d) => d -> m Int+portCount d = liftIO$ do+ let dptr = toDevicePtr d+ x <- rtmidi_get_port_count dptr+ guardError dptr+ pure (fromIntegral x) --- |Return a string identifier for the specified MIDI port number.+-- | Return a string identifier for the specified MIDI port number. ----- An empty string is returned if an invalid port specifier is provided. -portName :: Device -> Int -> IO String-portName d n = peekCString =<< rtmidi_get_port_name (device d) (toEnum n)+-- 'Nothing' is returned if an invalid port specifier is provided.+portName :: (MonadIO m, IsDevice d) => d -> Int -> m (Maybe String)+portName d n = liftIO $ do+ let dptr = toDevicePtr d+ x <- rtmidi_get_port_name dptr (toEnum n)+ guardError dptr+ s <- peekCString x+ case s of+ [] -> pure Nothing+ _ -> pure (Just s) --- |Default constructor for a 'Device' to use for input.-defaultInput :: IO Device-defaultInput = Input <$> rtmidi_in_create_default+-- | Convenience function to list ports.+--+-- Note that the underlying library does not offer an "atomic" interface for this+-- so results may be inconsistent if you connect/disconnect ports during this call.+listPorts :: (MonadIO m, IsDevice d) => d -> m [(Int, String)]+listPorts d = liftIO $ portCount d >>= go [] 0 where+ go acc i c =+ if i >= c+ then pure (reverse acc)+ else do+ mn <- portName d i+ let acc' = maybe acc (\n -> (i, n):acc) mn+ go acc' (succ i) c --- |Create a new 'Device' to use for input.-createInput :: Api -- ^ API to use+-- | Convenience function to lookup the first port satisfying the predicate.+--+-- You may want to find an exact name:+--+-- > findPort d (== name)+--+-- Or you may want to match part of a name:+--+-- > findPort d (isInfixOf name)+--+-- Note that if you are performing many lookups, it's better to use 'listPorts' and+-- do the lookups yourself (see the caveats there too).+findPort :: (MonadIO m, IsDevice d) => d -> (String -> Bool) -> m (Maybe Int)+findPort d f = liftIO $ portCount d >>= go 0 where+ go i c =+ if i >= c+ then pure Nothing+ else do+ mn <- portName d i+ case mn of+ Just n | f n -> pure (Just i)+ _ -> go (succ i) c++-- | Default constructor for a 'Device' to use for input.+defaultInput :: MonadIO m => m InputDevice+defaultInput = liftIO $ do+ dptr <- rtmidi_in_create_default+ guardError dptr+ pure (InputDevice (Device dptr))++-- | Create a new 'Device' to use for input.+createInput :: MonadIO m+ => Api -- ^ API to use -> String -- ^ client name -> Int -- ^ size of the MIDI input queue- -> IO Device-createInput api clientName queueSizeLimit = Input <$>- (withCString clientName $ \str -> rtmidi_in_create (toEnum $ fromEnum api) str (toEnum queueSizeLimit))+ -> m InputDevice+createInput api clientName queueSizeLimit = liftIO $ do+ dptr <- withCString clientName (\str -> rtmidi_in_create (toEnum $ fromEnum api) str (toEnum queueSizeLimit))+ guardError dptr+ pure (InputDevice (Device dptr)) foreign import ccall "wrapper"- wrap :: (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ()) -> IO (FunPtr (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ()))+ mkCallbackPointer :: (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ()) -> IO (FunPtr (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ())) -proxy :: (CDouble -> [CUChar] -> Ptr () -> IO ()) -> (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ())-proxy f t d s p = peekArray (fromIntegral s) d >>= \a -> f t a p+adaptCallbackCTypes :: (Double -> [Word8] -> IO ()) -> (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ())+adaptCallbackCTypes f (CDouble t) d s _ = peekArray (fromIntegral s) d >>= \a -> f t (coerce a) --- |Set a callback function to be invoked for incoming MIDI messages.--- +-- | Set a callback function to be invoked for incoming MIDI messages.+-- -- The callback function will be called whenever an incoming MIDI message is received. -- While not absolutely necessary, it is best to set the callback function before opening a MIDI port to avoid leaving -- some messages in the queue.-setCallback :: Device- -> (CDouble -> [CUChar] -> IO ()) -- ^ Function that takes a timestamp and a MIDI message as arguments- -> IO ()-setCallback d c = flip (rtmidi_in_set_callback (toInput d)) nullPtr =<< wrap (proxy ((const .) . c))----- |See `setCallback`.------ Additionally a 'Ptr ()' is passed to the callback function whenever it is called.-setCallbackWithUserData :: Device- -> (CDouble -> [CUChar] -> Ptr () -> IO ())- -> Ptr ()- -> IO ()-setCallbackWithUserData d c u = flip (rtmidi_in_set_callback (toInput d)) u =<< (wrap $ proxy c)+setCallback :: MonadUnliftIO m+ => InputDevice+ -> (Double -> [Word8] -> m ()) -- ^ Function that takes a timestamp and a MIDI message as arguments+ -> m ()+setCallback d c = withRunInIO $ \run -> do+ let dptr = toDevicePtr d+ f <- mkCallbackPointer (adaptCallbackCTypes (\ts bytes -> run (c ts bytes)))+ rtmidi_in_set_callback dptr f nullPtr+ guardError dptr --- |Cancel use of the current callback function (if one exists).+-- | Cancel use of the current callback function (if one exists). -- -- Subsequent incoming MIDI messages will be written to the queue and can be retrieved with the `getMessage` function.-cancelCallback :: Device -> IO ()-cancelCallback d = rtmidi_in_cancel_callback (toInput d)+cancelCallback :: MonadIO m => InputDevice -> m ()+cancelCallback d = liftIO $ do+ let dptr = toDevicePtr d+ rtmidi_in_cancel_callback dptr+ guardError dptr --- |Specify whether certain MIDI message types should be queued or ignored during input. +-- | Specify whether certain MIDI message types should be queued or ignored during input. -- -- By default, MIDI timing and active sensing messages are ignored during message input because of their -- relative high data rates. MIDI sysex messages are ignored by default as well. -- Variable values of `true` imply that the respective message type will be ignored.-ignoreTypes :: Device+ignoreTypes :: MonadIO m+ => InputDevice -> Bool -- ^ SysEx messages -> Bool -- ^ Time messages -> Bool -- ^ Sense messages- -> IO ()-ignoreTypes d sysex time sense = rtmidi_in_ignore_types (toInput d) sysex time sense+ -> m ()+ignoreTypes d x y z = liftIO (rtmidi_in_ignore_types (toDevicePtr d) x y z) --- |Return data bytes for the next available MIDI message in the input queue and the event delta-time in seconds.+-- | Variant of 'getMessage' that allows you to set message buffer size (typically for large sysex messages).+getMessageSized :: MonadIO m => InputDevice -> Int -> m (Double, [Word8])+getMessageSized d n = liftIO $ alloca $ \s -> allocaArray n $ flip with $ \m -> do+ let dptr = toDevicePtr d+ poke s (fromIntegral n)+ CDouble timestamp <- rtmidi_in_get_message dptr m s+ guardError dptr+ size <- peek s+ message <-+ case size of+ 0 -> pure []+ _ -> do+ x <- peek m+ y <- peekArray (fromIntegral size) x+ pure (coerce y)+ pure (timestamp, message)++-- | Return data bytes for the next available MIDI message in the input queue and the event delta-time in seconds. -- -- This function returns immediately whether a new message is available or not. -- A valid message is indicated by whether the list contains any elements.-getMessage :: Device -> IO ([CUChar], Double)-getMessage d = alloca $ \m -> alloca $ \s -> do- timestamp <- rtmidi_in_get_message (toInput d) m s- size <- peek s- message <- peekArray (fromIntegral size) =<< peek m- return (message, toEnum $ fromEnum timestamp)+-- Note that large sysex messages will be silently dropped! Use 'getMessageSized' or use a callback to get these safely.+getMessage :: MonadIO m => InputDevice -> m (Double, [Word8])+getMessage d = liftIO (getMessageSized d defaultMessageSize) --- |Default constructor for a 'Device' to use for output.-defaultOutput :: IO Device-defaultOutput = Output <$> rtmidi_out_create_default+-- | Default constructor for a 'Device' to use for output.+defaultOutput :: MonadIO m => m OutputDevice+defaultOutput = liftIO $ do+ dptr <- rtmidi_out_create_default+ guardError dptr+ pure (OutputDevice (Device dptr)) --- |Create a new 'Device' to use for output.-createOutput :: Api -- ^ API to use+-- | Create a new 'Device' to use for output.+createOutput :: MonadIO m+ => Api -- ^ API to use -> String -- ^ client name- -> IO Device-createOutput api clientName = Output <$>- (withCString clientName $ rtmidi_out_create (toEnum (fromEnum api)))---- |Immediately send a single message out an open MIDI output port. -sendMessage :: Device -> [CUChar] -> IO ()-sendMessage d m = withArrayLen m $- \n ptr -> rtmidi_out_send_message (toOutput d) ptr (fromIntegral n) >> return ()---- |If a MIDI connection is still open, it will be closed-closeInput (Input x) = rtmidi_in_free x+ -> m OutputDevice+createOutput api clientName = liftIO $ do+ dptr <- withCString clientName (rtmidi_out_create (toEnum (fromEnum api)))+ guardError dptr+ pure (OutputDevice (Device dptr)) --- |Close any open MIDI connections-closeOutput (Output x) = rtmidi_out_free x+-- | Immediately send a single message out an open MIDI output port.+sendMessage :: MonadIO m => OutputDevice -> [Word8] -> m ()+sendMessage d m = liftIO $ withArrayLen m $ \n ptr -> do+ let dptr = toDevicePtr d+ rtmidi_out_send_message dptr (coerce ptr) (fromIntegral n)+ guardError dptr --- |Returns the specifier for the MIDI 'Api' in use-currentApi :: Device -> IO Api-currentApi d = (toEnum . fromEnum) <$>- case d of- Input x -> rtmidi_in_get_current_api x- Output x -> rtmidi_out_get_current_api x+-- | If a MIDI connection is still open, it will be closed+closeDevice :: MonadIO m => IsDevice d => d -> m ()+closeDevice d = liftIO $+ let dptr = toDevicePtr d+ in case getDeviceType d of+ InputDeviceType -> rtmidi_in_free dptr+ OutputDeviceType -> rtmidi_out_free dptr +-- | Returns the specifier for the MIDI 'Api' in use+currentApi :: MonadIO m => IsDevice d => d -> m Api+currentApi d = liftIO $ do+ let dptr = toDevicePtr d+ res <-+ case getDeviceType d of+ InputDeviceType -> rtmidi_in_get_current_api dptr+ OutputDeviceType -> rtmidi_out_get_current_api dptr+ guardError dptr+ pure (toEnum (fromEnum res))
+ Sound/RtMidi/Foreign.hsc view
@@ -0,0 +1,110 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE ForeignFunctionInterface #-}++-- | FFI defs for RtMidi+module Sound.RtMidi.Foreign+ ( Wrapper (..)+ , rtmidi_close_port+ , rtmidi_get_compiled_api+ , rtmidi_get_port_count+ , rtmidi_get_port_name+ , rtmidi_in_cancel_callback+ , rtmidi_in_create+ , rtmidi_in_create_default+ , rtmidi_in_free+ , rtmidi_in_get_current_api+ , rtmidi_in_get_message+ , rtmidi_in_ignore_types+ , rtmidi_in_set_callback+ , rtmidi_open_port+ , rtmidi_open_virtual_port+ , rtmidi_out_create+ , rtmidi_out_create_default+ , rtmidi_out_free+ , rtmidi_out_get_current_api+ , rtmidi_out_send_message+ ) where++#include "rtmidi_c.h"++import Foreign (FunPtr, Ptr, Storable (..))+import Foreign.C (CDouble (..), CInt (..), CString, CSize, CUChar)++data Wrapper = Wrapper+ { ptr :: !(Ptr ())+ , dat :: !(Ptr ())+ , ok :: !Bool+ , msg :: !CString+ } deriving (Eq, Show)++instance Storable Wrapper where+ sizeOf _ = #{size struct RtMidiWrapper}+ alignment _ = #{alignment struct RtMidiWrapper}+ poke ptr (Wrapper a b c d) = do+ #{poke struct RtMidiWrapper, ptr} ptr a+ #{poke struct RtMidiWrapper, data} ptr b+ #{poke struct RtMidiWrapper, ok} ptr c+ #{poke struct RtMidiWrapper, msg} ptr d+ peek ptr = do+ a <- #{peek struct RtMidiWrapper, ptr} ptr+ b <- #{peek struct RtMidiWrapper, data} ptr+ c <- #{peek struct RtMidiWrapper, ok} ptr+ d <- #{peek struct RtMidiWrapper, msg} ptr+ pure (Wrapper a b c d)++foreign import ccall "rtmidi_c.h rtmidi_close_port"+ rtmidi_close_port :: Ptr Wrapper -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_get_compiled_api"+ rtmidi_get_compiled_api :: Ptr (Ptr CInt) -> IO CInt++foreign import ccall "rtmidi_c.h rtmidi_get_port_count"+ rtmidi_get_port_count :: Ptr Wrapper -> IO CInt++foreign import ccall "rtmidi_c.h rtmidi_get_port_name"+ rtmidi_get_port_name :: Ptr Wrapper -> CInt -> IO CString++foreign import ccall "rtmidi_c.h rtmidi_in_cancel_callback"+ rtmidi_in_cancel_callback :: Ptr Wrapper -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_in_create"+ rtmidi_in_create :: CInt -> CString -> CInt -> IO (Ptr Wrapper)++foreign import ccall "rtmidi_c.h rtmidi_in_create_default"+ rtmidi_in_create_default :: IO (Ptr Wrapper)++foreign import ccall "rtmidi_c.h rtmidi_in_free"+ rtmidi_in_free :: Ptr Wrapper -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_in_get_current_api"+ rtmidi_in_get_current_api :: Ptr Wrapper -> IO CInt++foreign import ccall "rtmidi_c.h rtmidi_in_get_message"+ rtmidi_in_get_message :: Ptr Wrapper -> Ptr (Ptr CUChar) -> Ptr CSize -> IO CDouble++foreign import ccall "rtmidi_c.h rtmidi_in_ignore_types"+ rtmidi_in_ignore_types :: Ptr Wrapper -> Bool -> Bool -> Bool -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_in_set_callback"+ rtmidi_in_set_callback :: Ptr Wrapper -> FunPtr (CDouble -> Ptr CUChar -> CInt -> Ptr () -> IO ()) -> Ptr () -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_open_port"+ rtmidi_open_port :: Ptr Wrapper -> CInt -> CString -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_open_virtual_port"+ rtmidi_open_virtual_port :: Ptr Wrapper -> CString -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_out_create"+ rtmidi_out_create :: CInt -> CString -> IO (Ptr Wrapper)++foreign import ccall "rtmidi_c.h rtmidi_out_create_default"+ rtmidi_out_create_default :: IO (Ptr Wrapper)++foreign import ccall "rtmidi_c.h rtmidi_out_free"+ rtmidi_out_free :: Ptr Wrapper -> IO ()++foreign import ccall "rtmidi_c.h rtmidi_out_get_current_api"+ rtmidi_out_get_current_api :: Ptr Wrapper -> IO CInt++foreign import ccall "rtmidi_c.h rtmidi_out_send_message"+ rtmidi_out_send_message :: Ptr Wrapper -> Ptr CUChar -> CInt -> IO CInt
examples/callback.hs view
@@ -1,13 +1,16 @@-import Sound.RtMidi-import Numeric-import Foreign.C+import Data.Word (Word8)+import Sound.RtMidi (closeDevice, closePort, defaultInput, openPort, setCallback)+import Numeric (showHex) -callback :: CDouble -> [CUChar] -> IO ()+callback :: Double -> [Word8] -> IO () callback delta msg = putStrLn $ (foldr (showHex . fromEnum) "" msg) ++ " - " ++ (show delta) +main :: IO () main = do i <- defaultInput openPort i 0 "RtMidi" setCallback i callback _ <- getLine+ closePort i+ closeDevice i return ()
examples/playback.hs view
@@ -1,6 +1,7 @@-import Sound.RtMidi-import Control.Concurrent+import Sound.RtMidi (closeDevice, closePort, defaultOutput, openPort, sendMessage, portCount, portName)+import Control.Concurrent (threadDelay) +main :: IO () main = do device <- defaultOutput numPorts <- portCount device@@ -17,4 +18,4 @@ mapM_ (\x -> sendMessage device [0x90, x, 0x7f] >> threadDelay 120000) $ take 240 song putStrLn "exiting..." closePort device- closeOutput device+ closeDevice device
+ examples/poll.hs view
@@ -0,0 +1,21 @@+import Sound.RtMidi (InputDevice, closeDevice, closePort, defaultInput, getMessage, openPort)+import Control.Concurrent (forkIO, killThread)+import Control.Monad (forever)++mainLoop :: InputDevice -> IO ()+mainLoop d = do+ m <- getMessage d+ if length (snd m) > 0 then+ putStrLn $ show m+ else return ()++main :: IO ()+main = do+ i <- defaultInput+ openPort i 0 "RtMidi"+ id <- forkIO $ forever $ mainLoop i+ _ <- getLine+ killThread id+ closePort i+ closeDevice i+ return ()
examples/query.hs view
@@ -1,14 +1,12 @@-import Sound.RtMidi-import Control.Concurrent+import Sound.RtMidi (closeDevice, compiledApis, createOutput, currentApi, defaultOutput, listPorts) +main :: IO () main = do- device <- createOutput AlsaApi "test"+ device <- defaultOutput api <- currentApi device builtin <- compiledApis- numPorts <- portCount device- portNames <- mapM (portName device) [0..numPorts-1]+ portPairs <- listPorts device putStrLn $ "RtMidi output using " ++ (show api) putStrLn $ "built-in: " ++ (show builtin)- putStrLn $ "available Ports: " ++ (show numPorts)- putStrLn (show portNames)- closeOutput device+ putStrLn $ "available ports: " ++ (show portPairs)+ closeDevice device
rtmidi/RtMidi.cpp view
@@ -5,2838 +5,3440 @@ This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. - RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/-- RtMidi: realtime MIDI i/o C++ classes- Copyright (c) 2003-2016 Gary P. Scavone-- Permission is hereby granted, free of charge, to any person- obtaining a copy of this software and associated documentation files- (the "Software"), to deal in the Software without restriction,- including without limitation the rights to use, copy, modify, merge,- publish, distribute, sublicense, and/or sell copies of the Software,- and to permit persons to whom the Software is furnished to do so,- subject to the following conditions:-- The above copyright notice and this permission notice shall be- included in all copies or substantial portions of the Software.-- Any person wishing to distribute modifications to the Software is- asked to send the modifications to the original developer so that- they can be incorporated into the canonical version. This is,- however, not a binding provision of this license.-- 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.-*/-/**********************************************************************/--#include "RtMidi.h"-#include <sstream>--#if defined(__MACOSX_CORE__)- #if TARGET_OS_IPHONE- #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime- #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos- #endif-#endif--//*********************************************************************//-// RtMidi Definitions-//*********************************************************************//--RtMidi :: RtMidi()- : rtapi_(0)-{-}--RtMidi :: ~RtMidi()-{- if ( rtapi_ )- delete rtapi_;- rtapi_ = 0;-}--std::string RtMidi :: getVersion( void ) throw()-{- return std::string( RTMIDI_VERSION );-}--void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw()-{- apis.clear();-- // The order here will control the order of RtMidi's API search in- // the constructor.-#if defined(__MACOSX_CORE__)- apis.push_back( MACOSX_CORE );-#endif-#if defined(__LINUX_ALSA__)- apis.push_back( LINUX_ALSA );-#endif-#if defined(__UNIX_JACK__)- apis.push_back( UNIX_JACK );-#endif-#if defined(__WINDOWS_MM__)- apis.push_back( WINDOWS_MM );-#endif-#if defined(__RTMIDI_DUMMY__)- apis.push_back( RTMIDI_DUMMY );-#endif-}--//*********************************************************************//-// RtMidiIn Definitions-//*********************************************************************//--void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )-{- if ( rtapi_ )- delete rtapi_;- rtapi_ = 0;--#if defined(__UNIX_JACK__)- if ( api == UNIX_JACK )- rtapi_ = new MidiInJack( clientName, queueSizeLimit );-#endif-#if defined(__LINUX_ALSA__)- if ( api == LINUX_ALSA )- rtapi_ = new MidiInAlsa( clientName, queueSizeLimit );-#endif-#if defined(__WINDOWS_MM__)- if ( api == WINDOWS_MM )- rtapi_ = new MidiInWinMM( clientName, queueSizeLimit );-#endif-#if defined(__MACOSX_CORE__)- if ( api == MACOSX_CORE )- rtapi_ = new MidiInCore( clientName, queueSizeLimit );-#endif-#if defined(__RTMIDI_DUMMY__)- if ( api == RTMIDI_DUMMY )- rtapi_ = new MidiInDummy( clientName, queueSizeLimit );-#endif-}--RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit )- : RtMidi()-{- if ( api != UNSPECIFIED ) {- // Attempt to open the specified API.- openMidiApi( api, clientName, queueSizeLimit );- if ( rtapi_ ) return;-- // No compiled support for specified API value. Issue a warning- // and continue as if no API was specified.- std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl;- }-- // Iterate through the compiled APIs and return as soon as we find- // one with at least one port or we reach the end of the list.- std::vector< RtMidi::Api > apis;- getCompiledApi( apis );- for ( unsigned int i=0; i<apis.size(); i++ ) {- openMidiApi( apis[i], clientName, queueSizeLimit );- if ( rtapi_->getPortCount() ) break;- }-- if ( rtapi_ ) return;-- // It should not be possible to get here because the preprocessor- // definition __RTMIDI_DUMMY__ is automatically defined if no- // API-specific definitions are passed to the compiler. But just in- // case something weird happens, we'll throw an error.- std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!";- throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );-}--RtMidiIn :: ~RtMidiIn() throw()-{-}---//*********************************************************************//-// RtMidiOut Definitions-//*********************************************************************//--void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string clientName )-{- if ( rtapi_ )- delete rtapi_;- rtapi_ = 0;--#if defined(__UNIX_JACK__)- if ( api == UNIX_JACK )- rtapi_ = new MidiOutJack( clientName );-#endif-#if defined(__LINUX_ALSA__)- if ( api == LINUX_ALSA )- rtapi_ = new MidiOutAlsa( clientName );-#endif-#if defined(__WINDOWS_MM__)- if ( api == WINDOWS_MM )- rtapi_ = new MidiOutWinMM( clientName );-#endif-#if defined(__MACOSX_CORE__)- if ( api == MACOSX_CORE )- rtapi_ = new MidiOutCore( clientName );-#endif-#if defined(__RTMIDI_DUMMY__)- if ( api == RTMIDI_DUMMY )- rtapi_ = new MidiOutDummy( clientName );-#endif-}--RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string clientName )-{- if ( api != UNSPECIFIED ) {- // Attempt to open the specified API.- openMidiApi( api, clientName );- if ( rtapi_ ) return;-- // No compiled support for specified API value. Issue a warning- // and continue as if no API was specified.- std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl;- }-- // Iterate through the compiled APIs and return as soon as we find- // one with at least one port or we reach the end of the list.- std::vector< RtMidi::Api > apis;- getCompiledApi( apis );- for ( unsigned int i=0; i<apis.size(); i++ ) {- openMidiApi( apis[i], clientName );- if ( rtapi_->getPortCount() ) break;- }-- if ( rtapi_ ) return;-- // It should not be possible to get here because the preprocessor- // definition __RTMIDI_DUMMY__ is automatically defined if no- // API-specific definitions are passed to the compiler. But just in- // case something weird happens, we'll thrown an error.- std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!";- throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );-}--RtMidiOut :: ~RtMidiOut() throw()-{-}--//*********************************************************************//-// Common MidiApi Definitions-//*********************************************************************//--MidiApi :: MidiApi( void )- : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0)-{-}--MidiApi :: ~MidiApi( void )-{-}--void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 )-{- errorCallback_ = errorCallback;- errorCallbackUserData_ = userData;-}--void MidiApi :: error( RtMidiError::Type type, std::string errorString )-{- if ( errorCallback_ ) {-- if ( firstErrorOccurred_ )- return;-- firstErrorOccurred_ = true;- const std::string errorMessage = errorString;-- errorCallback_( type, errorMessage, errorCallbackUserData_);- firstErrorOccurred_ = false;- return;- }-- if ( type == RtMidiError::WARNING ) {- std::cerr << '\n' << errorString << "\n\n";- }- else if ( type == RtMidiError::DEBUG_WARNING ) {-#if defined(__RTMIDI_DEBUG__)- std::cerr << '\n' << errorString << "\n\n";-#endif- }- else {- std::cerr << '\n' << errorString << "\n\n";- throw RtMidiError( errorString, type );- }-}--//*********************************************************************//-// Common MidiInApi Definitions-//*********************************************************************//--MidiInApi :: MidiInApi( unsigned int queueSizeLimit )- : MidiApi()-{- // Allocate the MIDI queue.- inputData_.queue.ringSize = queueSizeLimit;- if ( inputData_.queue.ringSize > 0 )- inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ];-}--MidiInApi :: ~MidiInApi( void )-{- // Delete the MIDI queue.- if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring;-}--void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData )-{- if ( inputData_.usingCallback ) {- errorString_ = "MidiInApi::setCallback: a callback function is already set!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- if ( !callback ) {- errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- inputData_.userCallback = callback;- inputData_.userData = userData;- inputData_.usingCallback = true;-}--void MidiInApi :: cancelCallback()-{- if ( !inputData_.usingCallback ) {- errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- inputData_.userCallback = 0;- inputData_.userData = 0;- inputData_.usingCallback = false;-}--void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )-{- inputData_.ignoreFlags = 0;- if ( midiSysex ) inputData_.ignoreFlags = 0x01;- if ( midiTime ) inputData_.ignoreFlags |= 0x02;- if ( midiSense ) inputData_.ignoreFlags |= 0x04;-}--double MidiInApi :: getMessage( std::vector<unsigned char> *message )-{- message->clear();-- if ( inputData_.usingCallback ) {- errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";- error( RtMidiError::WARNING, errorString_ );- return 0.0;- }-- if ( inputData_.queue.size == 0 ) return 0.0;-- // Copy queued message to the vector pointer argument and then "pop" it.- std::vector<unsigned char> *bytes = &(inputData_.queue.ring[inputData_.queue.front].bytes);- message->assign( bytes->begin(), bytes->end() );- double deltaTime = inputData_.queue.ring[inputData_.queue.front].timeStamp;- inputData_.queue.size--;- inputData_.queue.front++;- if ( inputData_.queue.front == inputData_.queue.ringSize )- inputData_.queue.front = 0;-- return deltaTime;-}--//*********************************************************************//-// Common MidiOutApi Definitions-//*********************************************************************//--MidiOutApi :: MidiOutApi( void )- : MidiApi()-{-}--MidiOutApi :: ~MidiOutApi( void )-{-}--// *************************************************** //-//-// OS/API-specific methods.-//-// *************************************************** //--#if defined(__MACOSX_CORE__)--// The CoreMIDI API is based on the use of a callback function for-// MIDI input. We convert the system specific time stamps to delta-// time values.--// OS-X CoreMIDI header files.-#include <CoreMIDI/CoreMIDI.h>-#include <CoreAudio/HostTime.h>-#include <CoreServices/CoreServices.h>--// A structure to hold variables related to the CoreMIDI API-// implementation.-struct CoreMidiData {- MIDIClientRef client;- MIDIPortRef port;- MIDIEndpointRef endpoint;- MIDIEndpointRef destinationId;- unsigned long long lastTime;- MIDISysexSendRequest sysexreq;-};--//*********************************************************************//-// API: OS-X-// Class Definitions: MidiInCore-//*********************************************************************//--static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ )-{- MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef);- CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData);-- unsigned char status;- unsigned short nBytes, iByte, size;- unsigned long long time;-- bool& continueSysex = data->continueSysex;- MidiInApi::MidiMessage& message = data->message;-- const MIDIPacket *packet = &list->packet[0];- for ( unsigned int i=0; i<list->numPackets; ++i ) {-- // My interpretation of the CoreMIDI documentation: all message- // types, except sysex, are complete within a packet and there may- // be several of them in a single packet. Sysex messages can be- // broken across multiple packets and PacketLists but are bundled- // alone within each packet (these packets do not contain other- // message types). If sysex messages are split across multiple- // MIDIPacketLists, they must be handled by multiple calls to this- // function.-- nBytes = packet->length;- if ( nBytes == 0 ) continue;-- // Calculate time stamp.-- if ( data->firstMessage ) {- message.timeStamp = 0.0;- data->firstMessage = false;- }- else {- time = packet->timeStamp;- if ( time == 0 ) { // this happens when receiving asynchronous sysex messages- time = AudioGetCurrentHostTime();- }- time -= apiData->lastTime;- time = AudioConvertHostTimeToNanos( time );- if ( !continueSysex )- message.timeStamp = time * 0.000000001;- }- apiData->lastTime = packet->timeStamp;- if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages- apiData->lastTime = AudioGetCurrentHostTime();- }- //std::cout << "TimeStamp = " << packet->timeStamp << std::endl;-- iByte = 0;- if ( continueSysex ) {- // We have a continuing, segmented sysex message.- if ( !( data->ignoreFlags & 0x01 ) ) {- // If we're not ignoring sysex messages, copy the entire packet.- for ( unsigned int j=0; j<nBytes; ++j )- message.bytes.push_back( packet->data[j] );- }- continueSysex = packet->data[nBytes-1] != 0xF7;-- if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) {- // If not a continuing sysex message, invoke the user callback function or queue the message.- if ( data->usingCallback ) {- RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;- callback( message.timeStamp, &message.bytes, data->userData );- }- else {- // As long as we haven't reached our queue size limit, push the message.- if ( data->queue.size < data->queue.ringSize ) {- data->queue.ring[data->queue.back++] = message;- if ( data->queue.back == data->queue.ringSize )- data->queue.back = 0;- data->queue.size++;- }- else- std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";- }- message.bytes.clear();- }- }- else {- while ( iByte < nBytes ) {- size = 0;- // We are expecting that the next byte in the packet is a status byte.- status = packet->data[iByte];- if ( !(status & 0x80) ) break;- // Determine the number of bytes in the MIDI message.- if ( status < 0xC0 ) size = 3;- else if ( status < 0xE0 ) size = 2;- else if ( status < 0xF0 ) size = 3;- else if ( status == 0xF0 ) {- // A MIDI sysex- if ( data->ignoreFlags & 0x01 ) {- size = 0;- iByte = nBytes;- }- else size = nBytes - iByte;- continueSysex = packet->data[nBytes-1] != 0xF7;- }- else if ( status == 0xF1 ) {- // A MIDI time code message- if ( data->ignoreFlags & 0x02 ) {- size = 0;- iByte += 2;- }- else size = 2;- }- else if ( status == 0xF2 ) size = 3;- else if ( status == 0xF3 ) size = 2;- else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {- // A MIDI timing tick message and we're ignoring it.- size = 0;- iByte += 1;- }- else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {- // A MIDI active sensing message and we're ignoring it.- size = 0;- iByte += 1;- }- else size = 1;-- // Copy the MIDI data to our vector.- if ( size ) {- message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );- if ( !continueSysex ) {- // If not a continuing sysex message, invoke the user callback function or queue the message.- if ( data->usingCallback ) {- RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;- callback( message.timeStamp, &message.bytes, data->userData );- }- else {- // As long as we haven't reached our queue size limit, push the message.- if ( data->queue.size < data->queue.ringSize ) {- data->queue.ring[data->queue.back++] = message;- if ( data->queue.back == data->queue.ringSize )- data->queue.back = 0;- data->queue.size++;- }- else- std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";- }- message.bytes.clear();- }- iByte += size;- }- }- }- packet = MIDIPacketNext(packet);- }-}--MidiInCore :: MidiInCore( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )-{- initialize( clientName );-}--MidiInCore :: ~MidiInCore( void )-{- // Close a connection if it exists.- closePort();-- // Cleanup.- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- MIDIClientDispose( data->client );- if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );- delete data;-}--void MidiInCore :: initialize( const std::string& clientName )-{- // Set up our client.- MIDIClientRef client;- CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );- OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );- if ( result != noErr ) {- std::ostringstream ost;- ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";- errorString_ = ost.str();- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Save our api-specific connection information.- CoreMidiData *data = (CoreMidiData *) new CoreMidiData;- data->client = client;- data->endpoint = 0;- apiData_ = (void *) data;- inputData_.apiData = (void *) data;- CFRelease(name);-}--void MidiInCore :: openPort( unsigned int portNumber, const std::string portName )-{- if ( connected_ ) {- errorString_ = "MidiInCore::openPort: a valid connection already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );- unsigned int nSrc = MIDIGetNumberOfSources();- if (nSrc < 1) {- errorString_ = "MidiInCore::openPort: no MIDI input sources found!";- error( RtMidiError::NO_DEVICES_FOUND, errorString_ );- return;- }-- if ( portNumber >= nSrc ) {- std::ostringstream ost;- ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::INVALID_PARAMETER, errorString_ );- return;- }-- MIDIPortRef port;- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- OSStatus result = MIDIInputPortCreate( data->client, - CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),- midiInputCallback, (void *)&inputData_, &port );- if ( result != noErr ) {- MIDIClientDispose( data->client );- errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Get the desired input source identifier.- MIDIEndpointRef endpoint = MIDIGetSource( portNumber );- if ( endpoint == 0 ) {- MIDIPortDispose( port );- MIDIClientDispose( data->client );- errorString_ = "MidiInCore::openPort: error getting MIDI input source reference.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Make the connection.- result = MIDIPortConnectSource( port, endpoint, NULL );- if ( result != noErr ) {- MIDIPortDispose( port );- MIDIClientDispose( data->client );- errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Save our api-specific port information.- data->port = port;-- connected_ = true;-}--void MidiInCore :: openVirtualPort( const std::string portName )-{- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);-- // Create a virtual MIDI input destination.- MIDIEndpointRef endpoint;- OSStatus result = MIDIDestinationCreate( data->client,- CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),- midiInputCallback, (void *)&inputData_, &endpoint );- if ( result != noErr ) {- errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Save our api-specific connection information.- data->endpoint = endpoint;-}--void MidiInCore :: closePort( void )-{- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);-- if ( data->endpoint ) {- MIDIEndpointDispose( data->endpoint );- }-- if ( data->port ) {- MIDIPortDispose( data->port );- }-- connected_ = false;-}--unsigned int MidiInCore :: getPortCount()-{- CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );- return MIDIGetNumberOfSources();-}--// This function was submitted by Douglas Casey Tucker and apparently-// derived largely from PortMidi.-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 = 0;- MIDIEndpointGetEntity( endpoint, &entity );- if ( entity == 0 )- // 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 = 0;- MIDIEntityGetDevice( entity, &device );- if ( device == 0 )- 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;-}--// This function was submitted by Douglas Casey Tucker and apparently-// derived largely from PortMidi.-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-- 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;-- CFRelease( result );-- // Here, either the endpoint had no connections, or we failed to obtain names - return EndpointName( endpoint, false );-}--std::string MidiInCore :: getPortName( unsigned int portNumber )-{- CFStringRef nameRef;- MIDIEndpointRef portRef;- char name[128];-- std::string stringName;- CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );- if ( portNumber >= MIDIGetNumberOfSources() ) {- std::ostringstream ost;- ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::WARNING, errorString_ );- return stringName;- }-- portRef = MIDIGetSource( portNumber );- nameRef = ConnectedEndpointName(portRef);- CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());- CFRelease( nameRef );-- return stringName = name;-}--//*********************************************************************//-// API: OS-X-// Class Definitions: MidiOutCore-//*********************************************************************//--MidiOutCore :: MidiOutCore( const std::string clientName ) : MidiOutApi()-{- initialize( clientName );-}--MidiOutCore :: ~MidiOutCore( void )-{- // Close a connection if it exists.- closePort();-- // Cleanup.- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- MIDIClientDispose( data->client );- if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );- delete data;-}--void MidiOutCore :: initialize( const std::string& clientName )-{- // Set up our client.- MIDIClientRef client;- CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );- OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );- if ( result != noErr ) {- std::ostringstream ost;- ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";- errorString_ = ost.str();- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Save our api-specific connection information.- CoreMidiData *data = (CoreMidiData *) new CoreMidiData;- data->client = client;- data->endpoint = 0;- apiData_ = (void *) data;- CFRelease( name );-}--unsigned int MidiOutCore :: getPortCount()-{- CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );- return MIDIGetNumberOfDestinations();-}--std::string MidiOutCore :: getPortName( unsigned int portNumber )-{- CFStringRef nameRef;- MIDIEndpointRef portRef;- char name[128];-- std::string stringName;- CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );- if ( portNumber >= MIDIGetNumberOfDestinations() ) {- std::ostringstream ost;- ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::WARNING, errorString_ );- return stringName;- }-- portRef = MIDIGetDestination( portNumber );- nameRef = ConnectedEndpointName(portRef);- CFStringGetCString( nameRef, name, sizeof(name), CFStringGetSystemEncoding());- CFRelease( nameRef );- - return stringName = name;-}--void MidiOutCore :: openPort( unsigned int portNumber, const std::string portName )-{- if ( connected_ ) {- errorString_ = "MidiOutCore::openPort: a valid connection already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );- unsigned int nDest = MIDIGetNumberOfDestinations();- if (nDest < 1) {- errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!";- error( RtMidiError::NO_DEVICES_FOUND, errorString_ );- return;- }-- if ( portNumber >= nDest ) {- std::ostringstream ost;- ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::INVALID_PARAMETER, errorString_ );- return;- }-- MIDIPortRef port;- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- OSStatus result = MIDIOutputPortCreate( data->client, - CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),- &port );- if ( result != noErr ) {- MIDIClientDispose( data->client );- errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Get the desired output port identifier.- MIDIEndpointRef destination = MIDIGetDestination( portNumber );- if ( destination == 0 ) {- MIDIPortDispose( port );- MIDIClientDispose( data->client );- errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Save our api-specific connection information.- data->port = port;- data->destinationId = destination;- connected_ = true;-}--void MidiOutCore :: closePort( void )-{- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);-- if ( data->endpoint ) {- MIDIEndpointDispose( data->endpoint );- }-- if ( data->port ) {- MIDIPortDispose( data->port );- }-- connected_ = false;-}--void MidiOutCore :: openVirtualPort( std::string portName )-{- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);-- if ( data->endpoint ) {- errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- // Create a virtual MIDI output source.- MIDIEndpointRef endpoint;- OSStatus result = MIDISourceCreate( data->client,- CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ),- &endpoint );- if ( result != noErr ) {- errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Save our api-specific connection information.- data->endpoint = endpoint;-}--void MidiOutCore :: sendMessage( std::vector<unsigned char> *message )-{- // We use the MIDISendSysex() function to asynchronously send sysex- // messages. Otherwise, we use a single CoreMidi MIDIPacket.- unsigned int nBytes = message->size();- if ( nBytes == 0 ) {- errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; - error( RtMidiError::WARNING, errorString_ );- return;- }-- MIDITimeStamp timeStamp = AudioGetCurrentHostTime();- CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);- OSStatus result;-- if ( message->at(0) != 0xF0 && nBytes > 3 ) {- errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";- error( RtMidiError::WARNING, errorString_ );- return;- }-- Byte buffer[nBytes+(sizeof(MIDIPacketList))];- ByteCount listSize = sizeof(buffer);- MIDIPacketList *packetList = (MIDIPacketList*)buffer;- MIDIPacket *packet = MIDIPacketListInit( packetList );-- ByteCount remainingBytes = nBytes;- while (remainingBytes && packet) {- ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket- const Byte* dataStartPtr = (const Byte *) &message->at( nBytes - remainingBytes );- packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr);- remainingBytes -= bytesForPacket; - }-- if ( !packet ) {- errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; - error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Send to any destinations that may have connected to us.- if ( data->endpoint ) {- result = MIDIReceived( data->endpoint, packetList );- if ( result != noErr ) {- errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";- error( RtMidiError::WARNING, errorString_ );- }- }-- // And send to an explicit destination port if we're connected.- if ( connected_ ) {- result = MIDISend( data->port, data->destinationId, packetList );- if ( result != noErr ) {- errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";- error( RtMidiError::WARNING, errorString_ );- }- }-}--#endif // __MACOSX_CORE__---//*********************************************************************//-// API: LINUX ALSA SEQUENCER-//*********************************************************************//--// API information found at:-// - http://www.alsa-project.org/documentation.php#Library--#if defined(__LINUX_ALSA__)--// The ALSA Sequencer API is based on the use of a callback function for-// MIDI input.-//-// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer-// time stamps and other assorted fixes!!!--// If you don't need timestamping for incoming MIDI events, define the-// preprocessor definition AVOID_TIMESTAMPING to save resources-// associated with the ALSA sequencer queues.--#include <pthread.h>-#include <sys/time.h>--// ALSA header file.-#include <alsa/asoundlib.h>--// A structure to hold variables related to the ALSA API-// implementation.-struct AlsaMidiData {- snd_seq_t *seq;- unsigned int portNum;- int vport;- snd_seq_port_subscribe_t *subscription;- snd_midi_event_t *coder;- unsigned int bufferSize;- unsigned char *buffer;- pthread_t thread;- pthread_t dummy_thread_id;- unsigned long long lastTime;- int queue_id; // an input queue is needed to get timestamped events- int trigger_fds[2];-};--#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))--//*********************************************************************//-// API: LINUX ALSA-// Class Definitions: MidiInAlsa-//*********************************************************************//--static void *alsaMidiHandler( void *ptr )-{- MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr);- AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);-- long nBytes;- unsigned long long time, lastTime;- bool continueSysex = false;- bool doDecode = false;- MidiInApi::MidiMessage message;- int poll_fd_count;- struct pollfd *poll_fds;-- snd_seq_event_t *ev;- int result;- apiData->bufferSize = 32;- result = snd_midi_event_new( 0, &apiData->coder );- if ( result < 0 ) {- data->doInput = false;- std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";- return 0;- }- unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );- if ( buffer == NULL ) {- data->doInput = false;- snd_midi_event_free( apiData->coder );- apiData->coder = 0;- std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n";- return 0;- }- snd_midi_event_init( apiData->coder );- snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages-- poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;- poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));- snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );- poll_fds[0].fd = apiData->trigger_fds[0];- poll_fds[0].events = POLLIN;-- while ( data->doInput ) {-- if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {- // No data pending- if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {- if ( poll_fds[0].revents & POLLIN ) {- bool dummy;- int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );- (void) res;- }- }- continue;- }-- // If here, there should be data.- result = snd_seq_event_input( apiData->seq, &ev );- if ( result == -ENOSPC ) {- std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";- continue;- }- else if ( result <= 0 ) {- std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";- perror("System reports");- continue;- }-- // This is a bit weird, but we now have to decode an ALSA MIDI- // event (back) into MIDI bytes. We'll ignore non-MIDI types.- if ( !continueSysex ) message.bytes.clear();-- doDecode = false;- switch ( ev->type ) {-- case SND_SEQ_EVENT_PORT_SUBSCRIBED:-#if defined(__RTMIDI_DEBUG__)- std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";-#endif- break;-- case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:-#if defined(__RTMIDI_DEBUG__)- std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";- std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"- << (int) ev->data.connect.sender.port- << ", dest = " << (int) ev->data.connect.dest.client << ":"- << (int) ev->data.connect.dest.port- << std::endl;-#endif- break;-- case SND_SEQ_EVENT_QFRAME: // MIDI time code- if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;- break;-- case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick- if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;- break;-- case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick- if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;- break;-- case SND_SEQ_EVENT_SENSING: // Active sensing- if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;- break;-- case SND_SEQ_EVENT_SYSEX:- if ( (data->ignoreFlags & 0x01) ) break;- if ( ev->data.ext.len > apiData->bufferSize ) {- apiData->bufferSize = ev->data.ext.len;- free( buffer );- buffer = (unsigned char *) malloc( apiData->bufferSize );- if ( buffer == NULL ) {- data->doInput = false;- std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";- break;- }- }-- default:- doDecode = true;- }-- if ( doDecode ) {-- nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );- if ( nBytes > 0 ) {- // The ALSA sequencer has a maximum buffer size for MIDI sysex- // events of 256 bytes. If a device sends sysex messages larger- // than this, they are segmented into 256 byte chunks. So,- // we'll watch for this and concatenate sysex chunks into a- // single sysex message if necessary.- if ( !continueSysex )- message.bytes.assign( buffer, &buffer[nBytes] );- else- message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );-- continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );- if ( !continueSysex ) {-- // Calculate the time stamp:- message.timeStamp = 0.0;-- // Method 1: Use the system time.- //(void)gettimeofday(&tv, (struct timezone *)NULL);- //time = (tv.tv_sec * 1000000) + tv.tv_usec;-- // Method 2: Use the ALSA sequencer event time data.- // (thanks to Pedro Lopez-Cabanillas!).- time = ( ev->time.time.tv_sec * 1000000 ) + ( ev->time.time.tv_nsec/1000 );- lastTime = time;- time -= apiData->lastTime;- apiData->lastTime = lastTime;- if ( data->firstMessage == true )- data->firstMessage = false;- else- message.timeStamp = time * 0.000001;- }- else {-#if defined(__RTMIDI_DEBUG__)- std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";-#endif- }- }- }-- snd_seq_free_event( ev );- if ( message.bytes.size() == 0 || continueSysex ) continue;-- if ( data->usingCallback ) {- RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;- callback( message.timeStamp, &message.bytes, data->userData );- }- else {- // As long as we haven't reached our queue size limit, push the message.- if ( data->queue.size < data->queue.ringSize ) {- data->queue.ring[data->queue.back++] = message;- if ( data->queue.back == data->queue.ringSize )- data->queue.back = 0;- data->queue.size++;- }- else- std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";- }- }-- if ( buffer ) free( buffer );- snd_midi_event_free( apiData->coder );- apiData->coder = 0;- apiData->thread = apiData->dummy_thread_id;- return 0;-}--MidiInAlsa :: MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )-{- initialize( clientName );-}--MidiInAlsa :: ~MidiInAlsa()-{- // Close a connection if it exists.- closePort();-- // Shutdown the input thread.- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( inputData_.doInput ) {- inputData_.doInput = false;- int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );- (void) res;- if ( !pthread_equal(data->thread, data->dummy_thread_id) )- pthread_join( data->thread, NULL );- }-- // Cleanup.- close ( data->trigger_fds[0] );- close ( data->trigger_fds[1] );- if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );-#ifndef AVOID_TIMESTAMPING- snd_seq_free_queue( data->seq, data->queue_id );-#endif- snd_seq_close( data->seq );- delete data;-}--void MidiInAlsa :: initialize( const std::string& clientName )-{- // Set up the ALSA sequencer client.- snd_seq_t *seq;- int result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);- if ( result < 0 ) {- errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Set client name.- snd_seq_set_client_name( seq, clientName.c_str() );-- // Save our api-specific connection information.- AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;- data->seq = seq;- data->portNum = -1;- data->vport = -1;- data->subscription = 0;- data->dummy_thread_id = pthread_self();- data->thread = data->dummy_thread_id;- data->trigger_fds[0] = -1;- data->trigger_fds[1] = -1;- apiData_ = (void *) data;- inputData_.apiData = (void *) data;-- if ( pipe(data->trigger_fds) == -1 ) {- errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Create the input queue-#ifndef AVOID_TIMESTAMPING- data->queue_id = snd_seq_alloc_named_queue(seq, "RtMidi Queue");- // Set arbitrary tempo (mm=100) and resolution (240)- snd_seq_queue_tempo_t *qtempo;- snd_seq_queue_tempo_alloca(&qtempo);- snd_seq_queue_tempo_set_tempo(qtempo, 600000);- snd_seq_queue_tempo_set_ppq(qtempo, 240);- snd_seq_set_queue_tempo(data->seq, data->queue_id, qtempo);- snd_seq_drain_output(data->seq);-#endif-}--// This function is used to count or get the pinfo structure for a given port number.-unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )-{- snd_seq_client_info_t *cinfo;- int client;- int count = 0;- snd_seq_client_info_alloca( &cinfo );-- snd_seq_client_info_set_client( cinfo, -1 );- while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {- client = snd_seq_client_info_get_client( cinfo );- if ( client == 0 ) continue;- // Reset query info- snd_seq_port_info_set_client( pinfo, client );- snd_seq_port_info_set_port( pinfo, -1 );- while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {- unsigned int atyp = snd_seq_port_info_get_type( pinfo );- if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) &&- ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) ) continue;- unsigned int caps = snd_seq_port_info_get_capability( pinfo );- if ( ( caps & type ) != type ) continue;- if ( count == portNumber ) return 1;- ++count;- }- }-- // If a negative portNumber was used, return the port count.- if ( portNumber < 0 ) return count;- return 0;-}--unsigned int MidiInAlsa :: getPortCount()-{- snd_seq_port_info_t *pinfo;- snd_seq_port_info_alloca( &pinfo );-- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );-}--std::string MidiInAlsa :: getPortName( unsigned int portNumber )-{- snd_seq_client_info_t *cinfo;- snd_seq_port_info_t *pinfo;- snd_seq_client_info_alloca( &cinfo );- snd_seq_port_info_alloca( &pinfo );-- std::string stringName;- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {- int cnum = snd_seq_port_info_get_client( pinfo );- snd_seq_get_any_client_info( data->seq, cnum, cinfo );- std::ostringstream os;- os << snd_seq_client_info_get_name( cinfo );- os << " "; // These lines added to make sure devices are listed- os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names- os << ":";- os << snd_seq_port_info_get_port( pinfo );- stringName = os.str();- return stringName;- }-- // If we get here, we didn't find a match.- errorString_ = "MidiInAlsa::getPortName: error looking for port name!";- error( RtMidiError::WARNING, errorString_ );- return stringName;-}--void MidiInAlsa :: openPort( unsigned int portNumber, const std::string portName )-{- if ( connected_ ) {- errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- unsigned int nSrc = this->getPortCount();- if ( nSrc < 1 ) {- errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";- error( RtMidiError::NO_DEVICES_FOUND, errorString_ );- return;- }-- snd_seq_port_info_t *src_pinfo;- snd_seq_port_info_alloca( &src_pinfo );- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {- std::ostringstream ost;- ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::INVALID_PARAMETER, errorString_ );- return;- }-- snd_seq_addr_t sender, receiver;- sender.client = snd_seq_port_info_get_client( src_pinfo );- sender.port = snd_seq_port_info_get_port( src_pinfo );- receiver.client = snd_seq_client_id( data->seq );-- snd_seq_port_info_t *pinfo;- snd_seq_port_info_alloca( &pinfo );- if ( data->vport < 0 ) {- snd_seq_port_info_set_client( pinfo, 0 );- snd_seq_port_info_set_port( pinfo, 0 );- snd_seq_port_info_set_capability( pinfo,- SND_SEQ_PORT_CAP_WRITE |- SND_SEQ_PORT_CAP_SUBS_WRITE );- snd_seq_port_info_set_type( pinfo,- SND_SEQ_PORT_TYPE_MIDI_GENERIC |- SND_SEQ_PORT_TYPE_APPLICATION );- snd_seq_port_info_set_midi_channels(pinfo, 16);-#ifndef AVOID_TIMESTAMPING- snd_seq_port_info_set_timestamping(pinfo, 1);- snd_seq_port_info_set_timestamp_real(pinfo, 1); - snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);-#endif- snd_seq_port_info_set_name(pinfo, portName.c_str() );- data->vport = snd_seq_create_port(data->seq, pinfo);- - if ( data->vport < 0 ) {- errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- data->vport = snd_seq_port_info_get_port(pinfo);- }-- receiver.port = data->vport;-- if ( !data->subscription ) {- // Make subscription- if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {- errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- snd_seq_port_subscribe_set_sender(data->subscription, &sender);- snd_seq_port_subscribe_set_dest(data->subscription, &receiver);- if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {- snd_seq_port_subscribe_free( data->subscription );- data->subscription = 0;- errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- }-- if ( inputData_.doInput == false ) {- // Start the input queue-#ifndef AVOID_TIMESTAMPING- snd_seq_start_queue( data->seq, data->queue_id, NULL );- snd_seq_drain_output( data->seq );-#endif- // Start our MIDI input thread.- pthread_attr_t attr;- pthread_attr_init(&attr);- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);- pthread_attr_setschedpolicy(&attr, SCHED_OTHER);-- inputData_.doInput = true;- int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);- pthread_attr_destroy(&attr);- if ( err ) {- snd_seq_unsubscribe_port( data->seq, data->subscription );- snd_seq_port_subscribe_free( data->subscription );- data->subscription = 0;- inputData_.doInput = false;- errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";- error( RtMidiError::THREAD_ERROR, errorString_ );- return;- }- }-- connected_ = true;-}--void MidiInAlsa :: openVirtualPort( std::string portName )-{- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( data->vport < 0 ) {- snd_seq_port_info_t *pinfo;- snd_seq_port_info_alloca( &pinfo );- snd_seq_port_info_set_capability( pinfo,- SND_SEQ_PORT_CAP_WRITE |- SND_SEQ_PORT_CAP_SUBS_WRITE );- snd_seq_port_info_set_type( pinfo,- SND_SEQ_PORT_TYPE_MIDI_GENERIC |- SND_SEQ_PORT_TYPE_APPLICATION );- snd_seq_port_info_set_midi_channels(pinfo, 16);-#ifndef AVOID_TIMESTAMPING- snd_seq_port_info_set_timestamping(pinfo, 1);- snd_seq_port_info_set_timestamp_real(pinfo, 1); - snd_seq_port_info_set_timestamp_queue(pinfo, data->queue_id);-#endif- snd_seq_port_info_set_name(pinfo, portName.c_str());- data->vport = snd_seq_create_port(data->seq, pinfo);-- if ( data->vport < 0 ) {- errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- data->vport = snd_seq_port_info_get_port(pinfo);- }-- if ( inputData_.doInput == false ) {- // Wait for old thread to stop, if still running- if ( !pthread_equal(data->thread, data->dummy_thread_id) )- pthread_join( data->thread, NULL );-- // Start the input queue-#ifndef AVOID_TIMESTAMPING- snd_seq_start_queue( data->seq, data->queue_id, NULL );- snd_seq_drain_output( data->seq );-#endif- // Start our MIDI input thread.- pthread_attr_t attr;- pthread_attr_init(&attr);- pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);- pthread_attr_setschedpolicy(&attr, SCHED_OTHER);-- inputData_.doInput = true;- int err = pthread_create(&data->thread, &attr, alsaMidiHandler, &inputData_);- pthread_attr_destroy(&attr);- if ( err ) {- if ( data->subscription ) {- snd_seq_unsubscribe_port( data->seq, data->subscription );- snd_seq_port_subscribe_free( data->subscription );- data->subscription = 0;- }- inputData_.doInput = false;- errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";- error( RtMidiError::THREAD_ERROR, errorString_ );- return;- }- }-}--void MidiInAlsa :: closePort( void )-{- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);-- if ( connected_ ) {- if ( data->subscription ) {- snd_seq_unsubscribe_port( data->seq, data->subscription );- snd_seq_port_subscribe_free( data->subscription );- data->subscription = 0;- }- // Stop the input queue-#ifndef AVOID_TIMESTAMPING- snd_seq_stop_queue( data->seq, data->queue_id, NULL );- snd_seq_drain_output( data->seq );-#endif- connected_ = false;- }-- // Stop thread to avoid triggering the callback, while the port is intended to be closed- if ( inputData_.doInput ) {- inputData_.doInput = false;- int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof(inputData_.doInput) );- (void) res;- if ( !pthread_equal(data->thread, data->dummy_thread_id) )- pthread_join( data->thread, NULL );- }-}--//*********************************************************************//-// API: LINUX ALSA-// Class Definitions: MidiOutAlsa-//*********************************************************************//--MidiOutAlsa :: MidiOutAlsa( const std::string clientName ) : MidiOutApi()-{- initialize( clientName );-}--MidiOutAlsa :: ~MidiOutAlsa()-{- // Close a connection if it exists.- closePort();-- // Cleanup.- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );- if ( data->coder ) snd_midi_event_free( data->coder );- if ( data->buffer ) free( data->buffer );- snd_seq_close( data->seq );- delete data;-}--void MidiOutAlsa :: initialize( const std::string& clientName )-{- // Set up the ALSA sequencer client.- snd_seq_t *seq;- int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );- if ( result1 < 0 ) {- errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Set client name.- snd_seq_set_client_name( seq, clientName.c_str() );-- // Save our api-specific connection information.- AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;- data->seq = seq;- data->portNum = -1;- data->vport = -1;- data->bufferSize = 32;- data->coder = 0;- data->buffer = 0;- int result = snd_midi_event_new( data->bufferSize, &data->coder );- if ( result < 0 ) {- delete data;- errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- data->buffer = (unsigned char *) malloc( data->bufferSize );- if ( data->buffer == NULL ) {- delete data;- errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";- error( RtMidiError::MEMORY_ERROR, errorString_ );- return;- }- snd_midi_event_init( data->coder );- apiData_ = (void *) data;-}--unsigned int MidiOutAlsa :: getPortCount()-{- snd_seq_port_info_t *pinfo;- snd_seq_port_info_alloca( &pinfo );-- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );-}--std::string MidiOutAlsa :: getPortName( unsigned int portNumber )-{- snd_seq_client_info_t *cinfo;- snd_seq_port_info_t *pinfo;- snd_seq_client_info_alloca( &cinfo );- snd_seq_port_info_alloca( &pinfo );-- std::string stringName;- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {- int cnum = snd_seq_port_info_get_client(pinfo);- snd_seq_get_any_client_info( data->seq, cnum, cinfo );- std::ostringstream os;- os << snd_seq_client_info_get_name(cinfo);- os << " "; // These lines added to make sure devices are listed- os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names- os << ":";- os << snd_seq_port_info_get_port(pinfo);- stringName = os.str();- return stringName;- }-- // If we get here, we didn't find a match.- errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";- error( RtMidiError::WARNING, errorString_ );- return stringName;-}--void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string portName )-{- if ( connected_ ) {- errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- unsigned int nSrc = this->getPortCount();- if (nSrc < 1) {- errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";- error( RtMidiError::NO_DEVICES_FOUND, errorString_ );- return;- }-- snd_seq_port_info_t *pinfo;- snd_seq_port_info_alloca( &pinfo );- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {- std::ostringstream ost;- ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::INVALID_PARAMETER, errorString_ );- return;- }-- snd_seq_addr_t sender, receiver;- receiver.client = snd_seq_port_info_get_client( pinfo );- receiver.port = snd_seq_port_info_get_port( pinfo );- sender.client = snd_seq_client_id( data->seq );-- if ( data->vport < 0 ) {- data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),- SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,- SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );- if ( data->vport < 0 ) {- errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- }-- sender.port = data->vport;-- // Make subscription- if (snd_seq_port_subscribe_malloc( &data->subscription ) < 0) {- snd_seq_port_subscribe_free( data->subscription );- errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- snd_seq_port_subscribe_set_sender(data->subscription, &sender);- snd_seq_port_subscribe_set_dest(data->subscription, &receiver);- snd_seq_port_subscribe_set_time_update(data->subscription, 1);- snd_seq_port_subscribe_set_time_real(data->subscription, 1);- if ( snd_seq_subscribe_port(data->seq, data->subscription) ) {- snd_seq_port_subscribe_free( data->subscription );- errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- connected_ = true;-}--void MidiOutAlsa :: closePort( void )-{- if ( connected_ ) {- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- snd_seq_unsubscribe_port( data->seq, data->subscription );- snd_seq_port_subscribe_free( data->subscription );- connected_ = false;- }-}--void MidiOutAlsa :: openVirtualPort( std::string portName )-{- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- if ( data->vport < 0 ) {- data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),- SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,- SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );-- if ( data->vport < 0 ) {- errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- }- }-}--void MidiOutAlsa :: sendMessage( std::vector<unsigned char> *message )-{- int result;- AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);- unsigned int nBytes = message->size();- if ( nBytes > data->bufferSize ) {- data->bufferSize = nBytes;- result = snd_midi_event_resize_buffer ( data->coder, nBytes);- if ( result != 0 ) {- errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- free (data->buffer);- data->buffer = (unsigned char *) malloc( data->bufferSize );- if ( data->buffer == NULL ) {- errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";- error( RtMidiError::MEMORY_ERROR, errorString_ );- return;- }- }-- snd_seq_event_t ev;- snd_seq_ev_clear(&ev);- snd_seq_ev_set_source(&ev, data->vport);- snd_seq_ev_set_subs(&ev);- snd_seq_ev_set_direct(&ev);- for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message->at(i);- result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );- if ( result < (int)nBytes ) {- errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- // Send the event.- result = snd_seq_event_output(data->seq, &ev);- if ( result < 0 ) {- errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";- error( RtMidiError::WARNING, errorString_ );- return;- }- snd_seq_drain_output(data->seq);-}--#endif // __LINUX_ALSA__---//*********************************************************************//-// API: Windows Multimedia Library (MM)-//*********************************************************************//--// API information deciphered from:-// - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp--// Thanks to Jean-Baptiste Berruchon for the sysex code.--#if defined(__WINDOWS_MM__)--// The Windows MM API is based on the use of a callback function for-// MIDI input. We convert the system specific time stamps to delta-// time values.--// Windows MM MIDI header files.-#include <windows.h>-#include <mmsystem.h>--#define RT_SYSEX_BUFFER_SIZE 1024-#define RT_SYSEX_BUFFER_COUNT 4--// A structure to hold variables related to the CoreMIDI API-// implementation.-struct WinMidiData {- HMIDIIN inHandle; // Handle to Midi Input Device- HMIDIOUT outHandle; // Handle to Midi Output Device- DWORD lastTime;- MidiInApi::MidiMessage message;- LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];- CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo-};--//*********************************************************************//-// API: Windows MM-// Class Definitions: MidiInWinMM-//*********************************************************************//--static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,- UINT inputStatus, - DWORD_PTR instancePtr,- DWORD_PTR midiMessage,- DWORD timestamp )-{- if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;-- //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr);- MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;- WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);-- // Calculate time stamp.- if ( data->firstMessage == true ) {- apiData->message.timeStamp = 0.0;- data->firstMessage = false;- }- else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;- apiData->lastTime = timestamp;-- if ( inputStatus == MIM_DATA ) { // Channel or system message-- // Make sure the first byte is a status byte.- unsigned char status = (unsigned char) (midiMessage & 0x000000FF);- if ( !(status & 0x80) ) return;-- // Determine the number of bytes in the MIDI message.- unsigned short nBytes = 1;- if ( status < 0xC0 ) nBytes = 3;- else if ( status < 0xE0 ) nBytes = 2;- else if ( status < 0xF0 ) nBytes = 3;- else if ( status == 0xF1 ) {- if ( data->ignoreFlags & 0x02 ) return;- else nBytes = 2;- }- else if ( status == 0xF2 ) nBytes = 3;- else if ( status == 0xF3 ) nBytes = 2;- else if ( status == 0xF8 && (data->ignoreFlags & 0x02) ) {- // A MIDI timing tick message and we're ignoring it.- return;- }- else if ( status == 0xFE && (data->ignoreFlags & 0x04) ) {- // A MIDI active sensing message and we're ignoring it.- return;- }-- // Copy bytes to our MIDI message.- unsigned char *ptr = (unsigned char *) &midiMessage;- for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ );- }- else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )- MIDIHDR *sysex = ( MIDIHDR *) midiMessage; - if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { - // Sysex message and we're not ignoring it- for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )- apiData->message.bytes.push_back( sysex->lpData[i] );- }-- // The WinMM API requires that the sysex buffer be requeued after- // input of each sysex message. Even if we are ignoring sysex- // messages, we still need to requeue the buffer in case the user- // decides to not ignore sysex messages in the future. However,- // it seems that WinMM calls this function with an empty sysex- // buffer when an application closes and in this case, we should- // avoid requeueing it, else the computer suddenly reboots after- // one or two minutes.- if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) {- //if ( sysex->dwBytesRecorded > 0 ) {- EnterCriticalSection( &(apiData->_mutex) );- MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) );- LeaveCriticalSection( &(apiData->_mutex) );- if ( result != MMSYSERR_NOERROR )- std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";-- if ( data->ignoreFlags & 0x01 ) return;- }- else return;- }-- if ( data->usingCallback ) {- RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;- callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData );- }- else {- // As long as we haven't reached our queue size limit, push the message.- if ( data->queue.size < data->queue.ringSize ) {- data->queue.ring[data->queue.back++] = apiData->message;- if ( data->queue.back == data->queue.ringSize )- data->queue.back = 0;- data->queue.size++;- }- else- std::cerr << "\nRtMidiIn: message queue limit reached!!\n\n";- }-- // Clear the vector for the next input message.- apiData->message.bytes.clear();-}--MidiInWinMM :: MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )-{- initialize( clientName );-}--MidiInWinMM :: ~MidiInWinMM()-{- // Close a connection if it exists.- closePort();-- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- DeleteCriticalSection( &(data->_mutex) );-- // Cleanup.- delete data;-}--void MidiInWinMM :: initialize( const std::string& /*clientName*/ )-{- // We'll issue a warning here if no devices are available but not- // throw an error since the user can plugin something later.- unsigned int nDevices = midiInGetNumDevs();- if ( nDevices == 0 ) {- errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available.";- error( RtMidiError::WARNING, errorString_ );- }-- // Save our api-specific connection information.- WinMidiData *data = (WinMidiData *) new WinMidiData;- apiData_ = (void *) data;- inputData_.apiData = (void *) data;- data->message.bytes.clear(); // needs to be empty for first input message-- if ( !InitializeCriticalSectionAndSpinCount(&(data->_mutex), 0x00000400) ) {- errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed.";- error( RtMidiError::WARNING, errorString_ );- }-}--void MidiInWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )-{- if ( connected_ ) {- errorString_ = "MidiInWinMM::openPort: a valid connection already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- unsigned int nDevices = midiInGetNumDevs();- if (nDevices == 0) {- errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!";- error( RtMidiError::NO_DEVICES_FOUND, errorString_ );- return;- }-- if ( portNumber >= nDevices ) {- std::ostringstream ost;- ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::INVALID_PARAMETER, errorString_ );- return;- }-- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- MMRESULT result = midiInOpen( &data->inHandle,- portNumber,- (DWORD_PTR)&midiInputCallback,- (DWORD_PTR)&inputData_,- CALLBACK_FUNCTION );- if ( result != MMSYSERR_NOERROR ) {- errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Allocate and init the sysex buffers.- for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {- data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];- data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];- data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE;- data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator- data->sysexBuffer[i]->dwFlags = 0;-- result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );- if ( result != MMSYSERR_NOERROR ) {- midiInClose( data->inHandle );- errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader).";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Register the buffer.- result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );- if ( result != MMSYSERR_NOERROR ) {- midiInClose( data->inHandle );- errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer).";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- }-- result = midiInStart( data->inHandle );- if ( result != MMSYSERR_NOERROR ) {- midiInClose( data->inHandle );- errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- connected_ = true;-}--void MidiInWinMM :: openVirtualPort( std::string /*portName*/ )-{- // This function cannot be implemented for the Windows MM MIDI API.- errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";- error( RtMidiError::WARNING, errorString_ );-}--void MidiInWinMM :: closePort( void )-{- if ( connected_ ) {- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- EnterCriticalSection( &(data->_mutex) );- midiInReset( data->inHandle );- midiInStop( data->inHandle );-- for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {- int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR));- delete [] data->sysexBuffer[i]->lpData;- delete [] data->sysexBuffer[i];- if ( result != MMSYSERR_NOERROR ) {- midiInClose( data->inHandle );- errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }- }-- midiInClose( data->inHandle );- connected_ = false;- LeaveCriticalSection( &(data->_mutex) );- }-}--unsigned int MidiInWinMM :: getPortCount()-{- return midiInGetNumDevs();-}--std::string MidiInWinMM :: getPortName( unsigned int portNumber )-{- std::string stringName;- unsigned int nDevices = midiInGetNumDevs();- if ( portNumber >= nDevices ) {- std::ostringstream ost;- ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::WARNING, errorString_ );- return stringName;- }-- MIDIINCAPS deviceCaps;- midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));--#if defined( UNICODE ) || defined( _UNICODE )- int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;- stringName.assign( length, 0 );- length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);-#else- stringName = std::string( deviceCaps.szPname );-#endif-- // Next lines added to add the portNumber to the name so that - // the device's names are sure to be listed with individual names- // even when they have the same brand name- std::ostringstream os;- os << " ";- os << portNumber;- stringName += os.str();-- return stringName;-}--//*********************************************************************//-// API: Windows MM-// Class Definitions: MidiOutWinMM-//*********************************************************************//--MidiOutWinMM :: MidiOutWinMM( const std::string clientName ) : MidiOutApi()-{- initialize( clientName );-}--MidiOutWinMM :: ~MidiOutWinMM()-{- // Close a connection if it exists.- closePort();-- // Cleanup.- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- delete data;-}--void MidiOutWinMM :: initialize( const std::string& /*clientName*/ )-{- // We'll issue a warning here if no devices are available but not- // throw an error since the user can plug something in later.- unsigned int nDevices = midiOutGetNumDevs();- if ( nDevices == 0 ) {- errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available.";- error( RtMidiError::WARNING, errorString_ );- }-- // Save our api-specific connection information.- WinMidiData *data = (WinMidiData *) new WinMidiData;- apiData_ = (void *) data;-}--unsigned int MidiOutWinMM :: getPortCount()-{- return midiOutGetNumDevs();-}--std::string MidiOutWinMM :: getPortName( unsigned int portNumber )-{- std::string stringName;- unsigned int nDevices = midiOutGetNumDevs();- if ( portNumber >= nDevices ) {- std::ostringstream ost;- ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::WARNING, errorString_ );- return stringName;- }-- MIDIOUTCAPS deviceCaps;- midiOutGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIOUTCAPS));--#if defined( UNICODE ) || defined( _UNICODE )- int length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, -1, NULL, 0, NULL, NULL) - 1;- stringName.assign( length, 0 );- length = WideCharToMultiByte(CP_UTF8, 0, deviceCaps.szPname, static_cast<int>(wcslen(deviceCaps.szPname)), &stringName[0], length, NULL, NULL);-#else- stringName = std::string( deviceCaps.szPname );-#endif-- // Next lines added to add the portNumber to the name so that - // the device's names are sure to be listed with individual names- // even when they have the same brand name- std::ostringstream os;- os << " ";- os << portNumber;- stringName += os.str();-- return stringName;-}--void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string /*portName*/ )-{- if ( connected_ ) {- errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- unsigned int nDevices = midiOutGetNumDevs();- if (nDevices < 1) {- errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!";- error( RtMidiError::NO_DEVICES_FOUND, errorString_ );- return;- }-- if ( portNumber >= nDevices ) {- std::ostringstream ost;- ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::INVALID_PARAMETER, errorString_ );- return;- }-- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- MMRESULT result = midiOutOpen( &data->outHandle,- portNumber,- (DWORD)NULL,- (DWORD)NULL,- CALLBACK_NULL );- if ( result != MMSYSERR_NOERROR ) {- errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- connected_ = true;-}--void MidiOutWinMM :: closePort( void )-{- if ( connected_ ) {- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- midiOutReset( data->outHandle );- midiOutClose( data->outHandle );- connected_ = false;- }-}--void MidiOutWinMM :: openVirtualPort( std::string /*portName*/ )-{- // This function cannot be implemented for the Windows MM MIDI API.- errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";- error( RtMidiError::WARNING, errorString_ );-}--void MidiOutWinMM :: sendMessage( std::vector<unsigned char> *message )-{- if ( !connected_ ) return;-- unsigned int nBytes = static_cast<unsigned int>(message->size());- if ( nBytes == 0 ) {- errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- MMRESULT result;- WinMidiData *data = static_cast<WinMidiData *> (apiData_);- if ( message->at(0) == 0xF0 ) { // Sysex message-- // Allocate buffer for sysex data.- char *buffer = (char *) malloc( nBytes );- if ( buffer == NULL ) {- errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!";- error( RtMidiError::MEMORY_ERROR, errorString_ );- return;- }-- // Copy data to buffer.- for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message->at(i);-- // Create and prepare MIDIHDR structure.- MIDIHDR sysex;- sysex.lpData = (LPSTR) buffer;- sysex.dwBufferLength = nBytes;- sysex.dwFlags = 0;- result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof(MIDIHDR) ); - if ( result != MMSYSERR_NOERROR ) {- free( buffer );- errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Send the message.- result = midiOutLongMsg( data->outHandle, &sysex, sizeof(MIDIHDR) );- if ( result != MMSYSERR_NOERROR ) {- free( buffer );- errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Unprepare the buffer and MIDIHDR.- while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof (MIDIHDR) ) ) Sleep( 1 );- free( buffer );- }- else { // Channel or system message.-- // Make sure the message size isn't too big.- if ( nBytes > 3 ) {- errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!";- error( RtMidiError::WARNING, errorString_ );- return;- }-- // Pack MIDI bytes into double word.- DWORD packet;- unsigned char *ptr = (unsigned char *) &packet;- for ( unsigned int i=0; i<nBytes; ++i ) {- *ptr = message->at(i);- ++ptr;- }-- // Send the message immediately.- result = midiOutShortMsg( data->outHandle, packet );- if ( result != MMSYSERR_NOERROR ) {- errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";- error( RtMidiError::DRIVER_ERROR, errorString_ );- }- }-}--#endif // __WINDOWS_MM__---//*********************************************************************//-// API: UNIX JACK-//-// Written primarily by Alexander Svetalkin, with updates for delta-// time by Gary Scavone, April 2011.-//-// *********************************************************************//--#if defined(__UNIX_JACK__)--// JACK header files-#include <jack/jack.h>-#include <jack/midiport.h>-#include <jack/ringbuffer.h>--#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer--struct JackMidiData {- jack_client_t *client;- jack_port_t *port;- jack_ringbuffer_t *buffSize;- jack_ringbuffer_t *buffMessage;- jack_time_t lastTime;- MidiInApi :: RtMidiInData *rtMidiIn;- };--//*********************************************************************//-// API: JACK-// Class Definitions: MidiInJack-//*********************************************************************//--static int jackProcessIn( jack_nframes_t nframes, void *arg )-{- JackMidiData *jData = (JackMidiData *) arg;- MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn;- jack_midi_event_t event;- jack_time_t time;-- // Is port created?- if ( jData->port == NULL ) return 0;- void *buff = jack_port_get_buffer( jData->port, nframes );-- // We have midi events in buffer- int evCount = jack_midi_get_event_count( buff );- for (int j = 0; j < evCount; j++) {- MidiInApi::MidiMessage message;- message.bytes.clear();-- jack_midi_event_get( &event, buff, j );-- for ( unsigned int i = 0; i < event.size; i++ )- message.bytes.push_back( event.buffer[i] );-- // Compute the delta time.- time = jack_get_time();- if ( rtData->firstMessage == true )- rtData->firstMessage = false;- else- message.timeStamp = ( time - jData->lastTime ) * 0.000001;-- jData->lastTime = time;-- if ( !rtData->continueSysex ) {- if ( rtData->usingCallback ) {- RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback;- callback( message.timeStamp, &message.bytes, rtData->userData );- }- else {- // As long as we haven't reached our queue size limit, push the message.- if ( rtData->queue.size < rtData->queue.ringSize ) {- rtData->queue.ring[rtData->queue.back++] = message;- if ( rtData->queue.back == rtData->queue.ringSize )- rtData->queue.back = 0;- rtData->queue.size++;- }- else- std::cerr << "\nMidiInJack: message queue limit reached!!\n\n";- }- }- }-- return 0;-}--MidiInJack :: MidiInJack( const std::string clientName, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit )-{- initialize( clientName );-}--void MidiInJack :: initialize( const std::string& clientName )-{- JackMidiData *data = new JackMidiData;- apiData_ = (void *) data;-- data->rtMidiIn = &inputData_;- data->port = NULL;- data->client = NULL;- this->clientName = clientName;-- connect();-}--void MidiInJack :: connect()-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- if ( data->client )- return;-- // Initialize JACK client- if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {- errorString_ = "MidiInJack::initialize: JACK server not running?";- error( RtMidiError::WARNING, errorString_ );- return;- }-- jack_set_process_callback( data->client, jackProcessIn, data );- jack_activate( data->client );-}--MidiInJack :: ~MidiInJack()-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- closePort();-- if ( data->client )- jack_client_close( data->client );- delete data;-}--void MidiInJack :: openPort( unsigned int portNumber, const std::string portName )-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- connect();-- // Creating new port- if ( data->port == NULL)- data->port = jack_port_register( data->client, portName.c_str(),- JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );-- if ( data->port == NULL) {- errorString_ = "MidiInJack::openPort: JACK error creating port";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Connecting to the output- std::string name = getPortName( portNumber );- jack_connect( data->client, name.c_str(), jack_port_name( data->port ) );-}--void MidiInJack :: openVirtualPort( const std::string portName )-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- connect();- if ( data->port == NULL )- data->port = jack_port_register( data->client, portName.c_str(),- JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );-- if ( data->port == NULL ) {- errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";- error( RtMidiError::DRIVER_ERROR, errorString_ );- }-}--unsigned int MidiInJack :: getPortCount()-{- int count = 0;- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- connect();- if ( !data->client )- return 0;-- // List of available ports- const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );-- if ( ports == NULL ) return 0;- while ( ports[count] != NULL )- count++;-- free( ports );-- return count;-}--std::string MidiInJack :: getPortName( unsigned int portNumber )-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- std::string retStr("");-- connect();-- // List of available ports- const char **ports = jack_get_ports( data->client, NULL,- JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );-- // Check port validity- if ( ports == NULL ) {- errorString_ = "MidiInJack::getPortName: no ports available!";- error( RtMidiError::WARNING, errorString_ );- return retStr;- }-- if ( ports[portNumber] == NULL ) {- std::ostringstream ost;- ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::WARNING, errorString_ );- }- else retStr.assign( ports[portNumber] );-- free( ports );- return retStr;-}--void MidiInJack :: closePort()-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- if ( data->port == NULL ) return;- jack_port_unregister( data->client, data->port );- data->port = NULL;-}--//*********************************************************************//-// API: JACK-// Class Definitions: MidiOutJack-//*********************************************************************//--// Jack process callback-static int jackProcessOut( jack_nframes_t nframes, void *arg )-{- JackMidiData *data = (JackMidiData *) arg;- jack_midi_data_t *midiData;- int space;-- // Is port created?- if ( data->port == NULL ) return 0;-- void *buff = jack_port_get_buffer( data->port, nframes );- jack_midi_clear_buffer( buff );-- while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) {- jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof(space) );- midiData = jack_midi_event_reserve( buff, 0, space );-- jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space );- }-- return 0;-}--MidiOutJack :: MidiOutJack( const std::string clientName ) : MidiOutApi()-{- initialize( clientName );-}--void MidiOutJack :: initialize( const std::string& clientName )-{- JackMidiData *data = new JackMidiData;- apiData_ = (void *) data;-- data->port = NULL;- data->client = NULL;- this->clientName = clientName;-- connect();-}--void MidiOutJack :: connect()-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- if ( data->client )- return;- - // Initialize output ringbuffers - data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );- data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );-- // Initialize JACK client- if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {- errorString_ = "MidiOutJack::initialize: JACK server not running?";- error( RtMidiError::WARNING, errorString_ );- return;- }-- jack_set_process_callback( data->client, jackProcessOut, data );- jack_activate( data->client );-}--MidiOutJack :: ~MidiOutJack()-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- closePort();- - // Cleanup- jack_ringbuffer_free( data->buffSize );- jack_ringbuffer_free( data->buffMessage );- if ( data->client ) {- jack_client_close( data->client );- }-- delete data;-}--void MidiOutJack :: openPort( unsigned int portNumber, const std::string portName )-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- connect();-- // Creating new port- if ( data->port == NULL )- data->port = jack_port_register( data->client, portName.c_str(),- JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );-- if ( data->port == NULL ) {- errorString_ = "MidiOutJack::openPort: JACK error creating port";- error( RtMidiError::DRIVER_ERROR, errorString_ );- return;- }-- // Connecting to the output- std::string name = getPortName( portNumber );- jack_connect( data->client, jack_port_name( data->port ), name.c_str() );-}--void MidiOutJack :: openVirtualPort( const std::string portName )-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- connect();- if ( data->port == NULL )- data->port = jack_port_register( data->client, portName.c_str(),- JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );-- if ( data->port == NULL ) {- errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";- error( RtMidiError::DRIVER_ERROR, errorString_ );- }-}--unsigned int MidiOutJack :: getPortCount()-{- int count = 0;- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- connect();- if ( !data->client )- return 0;-- // List of available ports- const char **ports = jack_get_ports( data->client, NULL,- JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );-- if ( ports == NULL ) return 0;- while ( ports[count] != NULL )- count++;-- free( ports );-- return count;-}--std::string MidiOutJack :: getPortName( unsigned int portNumber )-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);- std::string retStr("");-- connect();-- // List of available ports- const char **ports = jack_get_ports( data->client, NULL,- JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );-- // Check port validity- if ( ports == NULL) {- errorString_ = "MidiOutJack::getPortName: no ports available!";- error( RtMidiError::WARNING, errorString_ );- return retStr;- }-- if ( ports[portNumber] == NULL) {- std::ostringstream ost;- ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";- errorString_ = ost.str();- error( RtMidiError::WARNING, errorString_ );- }- else retStr.assign( ports[portNumber] );-- free( ports );- return retStr;-}--void MidiOutJack :: closePort()-{- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- if ( data->port == NULL ) return;- jack_port_unregister( data->client, data->port );- data->port = NULL;-}--void MidiOutJack :: sendMessage( std::vector<unsigned char> *message )-{- int nBytes = message->size();- JackMidiData *data = static_cast<JackMidiData *> (apiData_);-- // Write full message to buffer- jack_ringbuffer_write( data->buffMessage, ( const char * ) &( *message )[0],- message->size() );+ RtMidi GitHub site: https://github.com/thestk/rtmidi+ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/++ RtMidi: realtime MIDI i/o C++ classes+ Copyright (c) 2003-2019 Gary P. Scavone++ Permission is hereby granted, free of charge, to any person+ obtaining a copy of this software and associated documentation files+ (the "Software"), to deal in the Software without restriction,+ including without limitation the rights to use, copy, modify, merge,+ publish, distribute, sublicense, and/or sell copies of the Software,+ and to permit persons to whom the Software is furnished to do so,+ subject to the following conditions:++ The above copyright notice and this permission notice shall be+ included in all copies or substantial portions of the Software.++ Any person wishing to distribute modifications to the Software is+ asked to send the modifications to the original developer so that+ they can be incorporated into the canonical version. This is,+ however, not a binding provision of this license.++ 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.+*/+/**********************************************************************/++#include "RtMidi.h"+#include <sstream>++#if defined(__MACOSX_CORE__)+ #if TARGET_OS_IPHONE+ #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime+ #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos+ #endif+#endif++// Default for Windows is to add an identifier to the port names; this+// flag can be defined (e.g. in your project file) to disable this behaviour.+//#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES++// **************************************************************** //+//+// MidiInApi and MidiOutApi subclass prototypes.+//+// **************************************************************** //++#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__)+ #define __RTMIDI_DUMMY__+#endif++#if defined(__MACOSX_CORE__)++class MidiInCore: public MidiInApi+{+ public:+ MidiInCore( const std::string &clientName, unsigned int queueSizeLimit );+ ~MidiInCore( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );++ protected:+ void initialize( const std::string& clientName );+};++class MidiOutCore: public MidiOutApi+{+ public:+ MidiOutCore( const std::string &clientName );+ ~MidiOutCore( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );+ void sendMessage( const unsigned char *message, size_t size );++ protected:+ void initialize( const std::string& clientName );+};++#endif++#if defined(__UNIX_JACK__)++class MidiInJack: public MidiInApi+{+ public:+ MidiInJack( const std::string &clientName, unsigned int queueSizeLimit );+ ~MidiInJack( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName);+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );++ protected:+ std::string clientName;++ void connect( void );+ void initialize( const std::string& clientName );+};++class MidiOutJack: public MidiOutApi+{+ public:+ MidiOutJack( const std::string &clientName );+ ~MidiOutJack( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName);+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );+ void sendMessage( const unsigned char *message, size_t size );++ protected:+ std::string clientName;++ void connect( void );+ void initialize( const std::string& clientName );+};++#endif++#if defined(__LINUX_ALSA__)++class MidiInAlsa: public MidiInApi+{+ public:+ MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit );+ ~MidiInAlsa( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName);+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );++ protected:+ void initialize( const std::string& clientName );+};++class MidiOutAlsa: public MidiOutApi+{+ public:+ MidiOutAlsa( const std::string &clientName );+ ~MidiOutAlsa( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );+ void sendMessage( const unsigned char *message, size_t size );++ protected:+ void initialize( const std::string& clientName );+};++#endif++#if defined(__WINDOWS_MM__)++class MidiInWinMM: public MidiInApi+{+ public:+ MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit );+ ~MidiInWinMM( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );++ protected:+ void initialize( const std::string& clientName );+};++class MidiOutWinMM: public MidiOutApi+{+ public:+ MidiOutWinMM( const std::string &clientName );+ ~MidiOutWinMM( void );+ RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };+ void openPort( unsigned int portNumber, const std::string &portName );+ void openVirtualPort( const std::string &portName );+ void closePort( void );+ void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ unsigned int getPortCount( void );+ std::string getPortName( unsigned int portNumber );+ void sendMessage( const unsigned char *message, size_t size );++ protected:+ void initialize( const std::string& clientName );+};++#endif++#if defined(__RTMIDI_DUMMY__)++class MidiInDummy: public MidiInApi+{+ public:+ MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }+ RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }+ void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {}+ void openVirtualPort( const std::string &/*portName*/ ) {}+ void closePort( void ) {}+ void setClientName( const std::string &/*clientName*/ ) {};+ void setPortName( const std::string &/*portName*/ ) {};+ unsigned int getPortCount( void ) { return 0; }+ std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }++ protected:+ void initialize( const std::string& /*clientName*/ ) {}+};++class MidiOutDummy: public MidiOutApi+{+ public:+ MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }+ RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }+ void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {}+ void openVirtualPort( const std::string &/*portName*/ ) {}+ void closePort( void ) {}+ void setClientName( const std::string &/*clientName*/ ) {};+ void setPortName( const std::string &/*portName*/ ) {};+ unsigned int getPortCount( void ) { return 0; }+ std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }+ void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {}++ protected:+ void initialize( const std::string& /*clientName*/ ) {}+};++#endif++//*********************************************************************//+// RtMidi Definitions+//*********************************************************************//++RtMidi :: RtMidi()+ : rtapi_(0)+{+}++RtMidi :: ~RtMidi()+{+ delete rtapi_;+ rtapi_ = 0;+}++std::string RtMidi :: getVersion( void ) throw()+{+ return std::string( RTMIDI_VERSION );+}++// Define API names and display names.+// Must be in same order as API enum.+extern "C" {+const char* rtmidi_api_names[][2] = {+ { "unspecified" , "Unknown" },+ { "core" , "CoreMidi" },+ { "alsa" , "ALSA" },+ { "jack" , "Jack" },+ { "winmm" , "Windows MultiMedia" },+ { "dummy" , "Dummy" },+};+const unsigned int rtmidi_num_api_names =+ sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]);++// The order here will control the order of RtMidi's API search in+// the constructor.+extern "C" const RtMidi::Api rtmidi_compiled_apis[] = {+#if defined(__MACOSX_CORE__)+ RtMidi::MACOSX_CORE,+#endif+#if defined(__LINUX_ALSA__)+ RtMidi::LINUX_ALSA,+#endif+#if defined(__UNIX_JACK__)+ RtMidi::UNIX_JACK,+#endif+#if defined(__WINDOWS_MM__)+ RtMidi::WINDOWS_MM,+#endif+#if defined(__RTMIDI_DUMMY__)+ RtMidi::RTMIDI_DUMMY,+#endif+ RtMidi::UNSPECIFIED,+};+extern "C" const unsigned int rtmidi_num_compiled_apis =+ sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1;+}++// This is a compile-time check that rtmidi_num_api_names == RtMidi::NUM_APIS.+// If the build breaks here, check that they match.+template<bool b> class StaticAssert { private: StaticAssert() {} };+template<> class StaticAssert<true>{ public: StaticAssert() {} };+class StaticAssertions { StaticAssertions() {+ StaticAssert<rtmidi_num_api_names == RtMidi::NUM_APIS>();+}};++void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw()+{+ apis = std::vector<RtMidi::Api>(rtmidi_compiled_apis,+ rtmidi_compiled_apis + rtmidi_num_compiled_apis);+}++std::string RtMidi :: getApiName( RtMidi::Api api )+{+ if (api < 0 || api >= RtMidi::NUM_APIS)+ return "";+ return rtmidi_api_names[api][0];+}++std::string RtMidi :: getApiDisplayName( RtMidi::Api api )+{+ if (api < 0 || api >= RtMidi::NUM_APIS)+ return "Unknown";+ return rtmidi_api_names[api][1];+}++RtMidi::Api RtMidi :: getCompiledApiByName( const std::string &name )+{+ unsigned int i=0;+ for (i = 0; i < rtmidi_num_compiled_apis; ++i)+ if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0])+ return rtmidi_compiled_apis[i];+ return RtMidi::UNSPECIFIED;+}++void RtMidi :: setClientName( const std::string &clientName )+{+ rtapi_->setClientName( clientName );+}++void RtMidi :: setPortName( const std::string &portName )+{+ rtapi_->setPortName( portName );+}+++//*********************************************************************//+// RtMidiIn Definitions+//*********************************************************************//++void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit )+{+ delete rtapi_;+ rtapi_ = 0;++#if defined(__UNIX_JACK__)+ if ( api == UNIX_JACK )+ rtapi_ = new MidiInJack( clientName, queueSizeLimit );+#endif+#if defined(__LINUX_ALSA__)+ if ( api == LINUX_ALSA )+ rtapi_ = new MidiInAlsa( clientName, queueSizeLimit );+#endif+#if defined(__WINDOWS_MM__)+ if ( api == WINDOWS_MM )+ rtapi_ = new MidiInWinMM( clientName, queueSizeLimit );+#endif+#if defined(__MACOSX_CORE__)+ if ( api == MACOSX_CORE )+ rtapi_ = new MidiInCore( clientName, queueSizeLimit );+#endif+#if defined(__RTMIDI_DUMMY__)+ if ( api == RTMIDI_DUMMY )+ rtapi_ = new MidiInDummy( clientName, queueSizeLimit );+#endif+}++RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit )+ : RtMidi()+{+ if ( api != UNSPECIFIED ) {+ // Attempt to open the specified API.+ openMidiApi( api, clientName, queueSizeLimit );+ if ( rtapi_ ) return;++ // No compiled support for specified API value. Issue a warning+ // and continue as if no API was specified.+ std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl;+ }++ // Iterate through the compiled APIs and return as soon as we find+ // one with at least one port or we reach the end of the list.+ std::vector< RtMidi::Api > apis;+ getCompiledApi( apis );+ for ( unsigned int i=0; i<apis.size(); i++ ) {+ openMidiApi( apis[i], clientName, queueSizeLimit );+ if ( rtapi_ && rtapi_->getPortCount() ) break;+ }++ if ( rtapi_ ) return;++ // It should not be possible to get here because the preprocessor+ // definition __RTMIDI_DUMMY__ is automatically defined if no+ // API-specific definitions are passed to the compiler. But just in+ // case something weird happens, we'll throw an error.+ std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!";+ throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );+}++RtMidiIn :: ~RtMidiIn() throw()+{+}+++//*********************************************************************//+// RtMidiOut Definitions+//*********************************************************************//++void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName )+{+ delete rtapi_;+ rtapi_ = 0;++#if defined(__UNIX_JACK__)+ if ( api == UNIX_JACK )+ rtapi_ = new MidiOutJack( clientName );+#endif+#if defined(__LINUX_ALSA__)+ if ( api == LINUX_ALSA )+ rtapi_ = new MidiOutAlsa( clientName );+#endif+#if defined(__WINDOWS_MM__)+ if ( api == WINDOWS_MM )+ rtapi_ = new MidiOutWinMM( clientName );+#endif+#if defined(__MACOSX_CORE__)+ if ( api == MACOSX_CORE )+ rtapi_ = new MidiOutCore( clientName );+#endif+#if defined(__RTMIDI_DUMMY__)+ if ( api == RTMIDI_DUMMY )+ rtapi_ = new MidiOutDummy( clientName );+#endif+}++RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName)+{+ if ( api != UNSPECIFIED ) {+ // Attempt to open the specified API.+ openMidiApi( api, clientName );+ if ( rtapi_ ) return;++ // No compiled support for specified API value. Issue a warning+ // and continue as if no API was specified.+ std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl;+ }++ // Iterate through the compiled APIs and return as soon as we find+ // one with at least one port or we reach the end of the list.+ std::vector< RtMidi::Api > apis;+ getCompiledApi( apis );+ for ( unsigned int i=0; i<apis.size(); i++ ) {+ openMidiApi( apis[i], clientName );+ if ( rtapi_ && rtapi_->getPortCount() ) break;+ }++ if ( rtapi_ ) return;++ // It should not be possible to get here because the preprocessor+ // definition __RTMIDI_DUMMY__ is automatically defined if no+ // API-specific definitions are passed to the compiler. But just in+ // case something weird happens, we'll thrown an error.+ std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!";+ throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) );+}++RtMidiOut :: ~RtMidiOut() throw()+{+}++//*********************************************************************//+// Common MidiApi Definitions+//*********************************************************************//++MidiApi :: MidiApi( void )+ : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0)+{+}++MidiApi :: ~MidiApi( void )+{+}++void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 )+{+ errorCallback_ = errorCallback;+ errorCallbackUserData_ = userData;+}++void MidiApi :: error( RtMidiError::Type type, std::string errorString )+{+ if ( errorCallback_ ) {++ if ( firstErrorOccurred_ )+ return;++ firstErrorOccurred_ = true;+ const std::string errorMessage = errorString;++ errorCallback_( type, errorMessage, errorCallbackUserData_ );+ firstErrorOccurred_ = false;+ return;+ }++ if ( type == RtMidiError::WARNING ) {+ std::cerr << '\n' << errorString << "\n\n";+ }+ else if ( type == RtMidiError::DEBUG_WARNING ) {+#if defined(__RTMIDI_DEBUG__)+ std::cerr << '\n' << errorString << "\n\n";+#endif+ }+ else {+ std::cerr << '\n' << errorString << "\n\n";+ throw RtMidiError( errorString, type );+ }+}++//*********************************************************************//+// Common MidiInApi Definitions+//*********************************************************************//++MidiInApi :: MidiInApi( unsigned int queueSizeLimit )+ : MidiApi()+{+ // Allocate the MIDI queue.+ inputData_.queue.ringSize = queueSizeLimit;+ if ( inputData_.queue.ringSize > 0 )+ inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ];+}++MidiInApi :: ~MidiInApi( void )+{+ // Delete the MIDI queue.+ if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring;+}++void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData )+{+ if ( inputData_.usingCallback ) {+ errorString_ = "MidiInApi::setCallback: a callback function is already set!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ if ( !callback ) {+ errorString_ = "RtMidiIn::setCallback: callback function value is invalid!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ inputData_.userCallback = callback;+ inputData_.userData = userData;+ inputData_.usingCallback = true;+}++void MidiInApi :: cancelCallback()+{+ if ( !inputData_.usingCallback ) {+ errorString_ = "RtMidiIn::cancelCallback: no callback function was set!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ inputData_.userCallback = 0;+ inputData_.userData = 0;+ inputData_.usingCallback = false;+}++void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense )+{+ inputData_.ignoreFlags = 0;+ if ( midiSysex ) inputData_.ignoreFlags = 0x01;+ if ( midiTime ) inputData_.ignoreFlags |= 0x02;+ if ( midiSense ) inputData_.ignoreFlags |= 0x04;+}++double MidiInApi :: getMessage( std::vector<unsigned char> *message )+{+ message->clear();++ if ( inputData_.usingCallback ) {+ errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port.";+ error( RtMidiError::WARNING, errorString_ );+ return 0.0;+ }++ double timeStamp;+ if ( !inputData_.queue.pop( message, &timeStamp ) )+ return 0.0;++ return timeStamp;+}++unsigned int MidiInApi::MidiQueue::size( unsigned int *__back,+ unsigned int *__front )+{+ // Access back/front members exactly once and make stack copies for+ // size calculation+ unsigned int _back = back, _front = front, _size;+ if ( _back >= _front )+ _size = _back - _front;+ else+ _size = ringSize - _front + _back;++ // Return copies of back/front so no new and unsynchronized accesses+ // to member variables are needed.+ if ( __back ) *__back = _back;+ if ( __front ) *__front = _front;+ return _size;+}++// As long as we haven't reached our queue size limit, push the message.+bool MidiInApi::MidiQueue::push( const MidiInApi::MidiMessage& msg )+{+ // Local stack copies of front/back+ unsigned int _back, _front, _size;++ // Get back/front indexes exactly once and calculate current size+ _size = size( &_back, &_front );++ if ( _size < ringSize-1 )+ {+ ring[_back] = msg;+ back = (back+1)%ringSize;+ return true;+ }++ return false;+}++bool MidiInApi::MidiQueue::pop( std::vector<unsigned char> *msg, double* timeStamp )+{+ // Local stack copies of front/back+ unsigned int _back, _front, _size;++ // Get back/front indexes exactly once and calculate current size+ _size = size( &_back, &_front );++ if ( _size == 0 )+ return false;++ // Copy queued message to the vector pointer argument and then "pop" it.+ msg->assign( ring[_front].bytes.begin(), ring[_front].bytes.end() );+ *timeStamp = ring[_front].timeStamp;++ // Update front+ front = (front+1)%ringSize;+ return true;+}++//*********************************************************************//+// Common MidiOutApi Definitions+//*********************************************************************//++MidiOutApi :: MidiOutApi( void )+ : MidiApi()+{+}++MidiOutApi :: ~MidiOutApi( void )+{+}++// *************************************************** //+//+// OS/API-specific methods.+//+// *************************************************** //++#if defined(__MACOSX_CORE__)++// The CoreMIDI API is based on the use of a callback function for+// MIDI input. We convert the system specific time stamps to delta+// time values.++// OS-X CoreMIDI header files.+#include <CoreMIDI/CoreMIDI.h>+#include <CoreAudio/HostTime.h>+#include <CoreServices/CoreServices.h>++// A structure to hold variables related to the CoreMIDI API+// implementation.+struct CoreMidiData {+ MIDIClientRef client;+ MIDIPortRef port;+ MIDIEndpointRef endpoint;+ MIDIEndpointRef destinationId;+ unsigned long long lastTime;+ MIDISysexSendRequest sysexreq;+};++//*********************************************************************//+// API: OS-X+// Class Definitions: MidiInCore+//*********************************************************************//++static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ )+{+ MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef);+ CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData);++ unsigned char status;+ unsigned short nBytes, iByte, size;+ unsigned long long time;++ bool& continueSysex = data->continueSysex;+ MidiInApi::MidiMessage& message = data->message;++ const MIDIPacket *packet = &list->packet[0];+ for ( unsigned int i=0; i<list->numPackets; ++i ) {++ // My interpretation of the CoreMIDI documentation: all message+ // types, except sysex, are complete within a packet and there may+ // be several of them in a single packet. Sysex messages can be+ // broken across multiple packets and PacketLists but are bundled+ // alone within each packet (these packets do not contain other+ // message types). If sysex messages are split across multiple+ // MIDIPacketLists, they must be handled by multiple calls to this+ // function.++ nBytes = packet->length;+ if ( nBytes == 0 ) {+ packet = MIDIPacketNext( packet );+ continue;+ }++ // Calculate time stamp.+ if ( data->firstMessage ) {+ message.timeStamp = 0.0;+ data->firstMessage = false;+ }+ else {+ time = packet->timeStamp;+ if ( time == 0 ) { // this happens when receiving asynchronous sysex messages+ time = AudioGetCurrentHostTime();+ }+ time -= apiData->lastTime;+ time = AudioConvertHostTimeToNanos( time );+ if ( !continueSysex )+ message.timeStamp = time * 0.000000001;+ }++ // Track whether any non-filtered messages were found in this+ // packet for timestamp calculation+ bool foundNonFiltered = false;++ iByte = 0;+ if ( continueSysex ) {+ // We have a continuing, segmented sysex message.+ if ( !( data->ignoreFlags & 0x01 ) ) {+ // If we're not ignoring sysex messages, copy the entire packet.+ for ( unsigned int j=0; j<nBytes; ++j )+ message.bytes.push_back( packet->data[j] );+ }+ continueSysex = packet->data[nBytes-1] != 0xF7;++ if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) {+ // If not a continuing sysex message, invoke the user callback function or queue the message.+ if ( data->usingCallback ) {+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;+ callback( message.timeStamp, &message.bytes, data->userData );+ }+ else {+ // As long as we haven't reached our queue size limit, push the message.+ if ( !data->queue.push( message ) )+ std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";+ }+ message.bytes.clear();+ }+ }+ else {+ while ( iByte < nBytes ) {+ size = 0;+ // We are expecting that the next byte in the packet is a status byte.+ status = packet->data[iByte];+ if ( !(status & 0x80) ) break;+ // Determine the number of bytes in the MIDI message.+ if ( status < 0xC0 ) size = 3;+ else if ( status < 0xE0 ) size = 2;+ else if ( status < 0xF0 ) size = 3;+ else if ( status == 0xF0 ) {+ // A MIDI sysex+ if ( data->ignoreFlags & 0x01 ) {+ size = 0;+ iByte = nBytes;+ }+ else size = nBytes - iByte;+ continueSysex = packet->data[nBytes-1] != 0xF7;+ }+ else if ( status == 0xF1 ) {+ // A MIDI time code message+ if ( data->ignoreFlags & 0x02 ) {+ size = 0;+ iByte += 2;+ }+ else size = 2;+ }+ else if ( status == 0xF2 ) size = 3;+ else if ( status == 0xF3 ) size = 2;+ else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {+ // A MIDI timing tick message and we're ignoring it.+ size = 0;+ iByte += 1;+ }+ else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {+ // A MIDI active sensing message and we're ignoring it.+ size = 0;+ iByte += 1;+ }+ else size = 1;++ // Copy the MIDI data to our vector.+ if ( size ) {+ foundNonFiltered = true;+ message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] );+ if ( !continueSysex ) {+ // If not a continuing sysex message, invoke the user callback function or queue the message.+ if ( data->usingCallback ) {+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;+ callback( message.timeStamp, &message.bytes, data->userData );+ }+ else {+ // As long as we haven't reached our queue size limit, push the message.+ if ( !data->queue.push( message ) )+ std::cerr << "\nMidiInCore: message queue limit reached!!\n\n";+ }+ message.bytes.clear();+ }+ iByte += size;+ }+ }+ }++ // Save the time of the last non-filtered message+ if ( foundNonFiltered ) {+ apiData->lastTime = packet->timeStamp;+ if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages+ apiData->lastTime = AudioGetCurrentHostTime();+ }+ }++ packet = MIDIPacketNext(packet);+ }+}++MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit )+ : MidiInApi( queueSizeLimit )+{+ MidiInCore::initialize( clientName );+}++MidiInCore :: ~MidiInCore( void )+{+ // Close a connection if it exists.+ MidiInCore::closePort();++ // Cleanup.+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);+ MIDIClientDispose( data->client );+ if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );+ delete data;+}++void MidiInCore :: initialize( const std::string& clientName )+{+ // Set up our client.+ MIDIClientRef client;+ CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );+ if ( result != noErr ) {+ std::ostringstream ost;+ ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";+ errorString_ = ost.str();+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Save our api-specific connection information.+ CoreMidiData *data = (CoreMidiData *) new CoreMidiData;+ data->client = client;+ data->endpoint = 0;+ apiData_ = (void *) data;+ inputData_.apiData = (void *) data;+ CFRelease( name );+}++void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName )+{+ if ( connected_ ) {+ errorString_ = "MidiInCore::openPort: a valid connection already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );+ unsigned int nSrc = MIDIGetNumberOfSources();+ if ( nSrc < 1 ) {+ errorString_ = "MidiInCore::openPort: no MIDI input sources found!";+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );+ return;+ }++ if ( portNumber >= nSrc ) {+ std::ostringstream ost;+ ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::INVALID_PARAMETER, errorString_ );+ return;+ }++ MIDIPortRef port;+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIInputPortCreate( data->client,+ portNameRef,+ midiInputCallback, (void *)&inputData_, &port );+ CFRelease( portNameRef );++ if ( result != noErr ) {+ MIDIClientDispose( data->client );+ errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Get the desired input source identifier.+ MIDIEndpointRef endpoint = MIDIGetSource( portNumber );+ if ( endpoint == 0 ) {+ MIDIPortDispose( port );+ MIDIClientDispose( data->client );+ errorString_ = "MidiInCore::openPort: error getting MIDI input source reference.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Make the connection.+ result = MIDIPortConnectSource( port, endpoint, NULL );+ if ( result != noErr ) {+ MIDIPortDispose( port );+ MIDIClientDispose( data->client );+ errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Save our api-specific port information.+ data->port = port;++ connected_ = true;+}++void MidiInCore :: openVirtualPort( const std::string &portName )+{+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);++ // Create a virtual MIDI input destination.+ MIDIEndpointRef endpoint;+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIDestinationCreate( data->client,+ portNameRef,+ midiInputCallback, (void *)&inputData_, &endpoint );+ CFRelease( portNameRef );++ if ( result != noErr ) {+ errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Save our api-specific connection information.+ data->endpoint = endpoint;+}++void MidiInCore :: closePort( void )+{+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);++ if ( data->endpoint ) {+ MIDIEndpointDispose( data->endpoint );+ data->endpoint = 0;+ }++ if ( data->port ) {+ MIDIPortDispose( data->port );+ data->port = 0;+ }++ connected_ = false;+}++void MidiInCore :: setClientName ( const std::string& )+{++ errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiInCore :: setPortName ( const std::string& )+{++ errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!";+ error( RtMidiError::WARNING, errorString_ );++}++unsigned int MidiInCore :: getPortCount()+{+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );+ return MIDIGetNumberOfSources();+}++// This function was submitted by Douglas Casey Tucker and apparently+// derived largely from PortMidi.+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 = 0;+ MIDIEndpointGetEntity( endpoint, &entity );+ if ( entity == 0 )+ // 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 = 0;+ MIDIEntityGetDevice( entity, &device );+ if ( device == 0 )+ 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;+}++// This function was submitted by Douglas Casey Tucker and apparently+// derived largely from PortMidi.+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-+ 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;++ CFRelease( result );++ // Here, either the endpoint had no connections, or we failed to obtain names+ return EndpointName( endpoint, false );+}++std::string MidiInCore :: getPortName( unsigned int portNumber )+{+ CFStringRef nameRef;+ MIDIEndpointRef portRef;+ char name[128];++ std::string stringName;+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );+ if ( portNumber >= MIDIGetNumberOfSources() ) {+ std::ostringstream ost;+ ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::WARNING, errorString_ );+ return stringName;+ }++ portRef = MIDIGetSource( portNumber );+ nameRef = ConnectedEndpointName( portRef );+ CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 );+ CFRelease( nameRef );++ return stringName = name;+}++//*********************************************************************//+// API: OS-X+// Class Definitions: MidiOutCore+//*********************************************************************//++MidiOutCore :: MidiOutCore( const std::string &clientName )+ : MidiOutApi()+{+ MidiOutCore::initialize( clientName );+}++MidiOutCore :: ~MidiOutCore( void )+{+ // Close a connection if it exists.+ MidiOutCore::closePort();++ // Cleanup.+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);+ MIDIClientDispose( data->client );+ if ( data->endpoint ) MIDIEndpointDispose( data->endpoint );+ delete data;+}++void MidiOutCore :: initialize( const std::string& clientName )+{+ // Set up our client.+ MIDIClientRef client;+ CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIClientCreate(name, NULL, NULL, &client );+ if ( result != noErr ) {+ std::ostringstream ost;+ ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ").";+ errorString_ = ost.str();+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Save our api-specific connection information.+ CoreMidiData *data = (CoreMidiData *) new CoreMidiData;+ data->client = client;+ data->endpoint = 0;+ apiData_ = (void *) data;+ CFRelease( name );+}++unsigned int MidiOutCore :: getPortCount()+{+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );+ return MIDIGetNumberOfDestinations();+}++std::string MidiOutCore :: getPortName( unsigned int portNumber )+{+ CFStringRef nameRef;+ MIDIEndpointRef portRef;+ char name[128];++ std::string stringName;+ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );+ if ( portNumber >= MIDIGetNumberOfDestinations() ) {+ std::ostringstream ost;+ ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::WARNING, errorString_ );+ return stringName;+ }++ portRef = MIDIGetDestination( portNumber );+ nameRef = ConnectedEndpointName(portRef);+ CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 );+ CFRelease( nameRef );++ return stringName = name;+}++void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName )+{+ if ( connected_ ) {+ errorString_ = "MidiOutCore::openPort: a valid connection already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false );+ unsigned int nDest = MIDIGetNumberOfDestinations();+ if (nDest < 1) {+ errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!";+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );+ return;+ }++ if ( portNumber >= nDest ) {+ std::ostringstream ost;+ ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::INVALID_PARAMETER, errorString_ );+ return;+ }++ MIDIPortRef port;+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port );+ CFRelease( portNameRef );+ if ( result != noErr ) {+ MIDIClientDispose( data->client );+ errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Get the desired output port identifier.+ MIDIEndpointRef destination = MIDIGetDestination( portNumber );+ if ( destination == 0 ) {+ MIDIPortDispose( port );+ MIDIClientDispose( data->client );+ errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Save our api-specific connection information.+ data->port = port;+ data->destinationId = destination;+ connected_ = true;+}++void MidiOutCore :: closePort( void )+{+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);++ if ( data->endpoint ) {+ MIDIEndpointDispose( data->endpoint );+ data->endpoint = 0;+ }++ if ( data->port ) {+ MIDIPortDispose( data->port );+ data->port = 0;+ }++ connected_ = false;+}++void MidiOutCore :: setClientName ( const std::string& )+{++ errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiOutCore :: setPortName ( const std::string& )+{++ errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiOutCore :: openVirtualPort( const std::string &portName )+{+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);++ if ( data->endpoint ) {+ errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ // Create a virtual MIDI output source.+ MIDIEndpointRef endpoint;+ CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII );+ OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint );+ CFRelease( portNameRef );++ if ( result != noErr ) {+ errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Save our api-specific connection information.+ data->endpoint = endpoint;+}++void MidiOutCore :: sendMessage( const unsigned char *message, size_t size )+{+ // We use the MIDISendSysex() function to asynchronously send sysex+ // messages. Otherwise, we use a single CoreMidi MIDIPacket.+ unsigned int nBytes = static_cast<unsigned int> (size);+ if ( nBytes == 0 ) {+ errorString_ = "MidiOutCore::sendMessage: no data in message argument!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ MIDITimeStamp timeStamp = AudioGetCurrentHostTime();+ CoreMidiData *data = static_cast<CoreMidiData *> (apiData_);+ OSStatus result;++ if ( message[0] != 0xF0 && nBytes > 3 ) {+ errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ Byte buffer[nBytes+(sizeof( MIDIPacketList ))];+ ByteCount listSize = sizeof( buffer );+ MIDIPacketList *packetList = (MIDIPacketList*)buffer;+ MIDIPacket *packet = MIDIPacketListInit( packetList );++ ByteCount remainingBytes = nBytes;+ while ( remainingBytes && packet ) {+ ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; // 65535 = maximum size of a MIDIPacket+ const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes];+ packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr );+ remainingBytes -= bytesForPacket;+ }++ if ( !packet ) {+ errorString_ = "MidiOutCore::sendMessage: could not allocate packet list";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Send to any destinations that may have connected to us.+ if ( data->endpoint ) {+ result = MIDIReceived( data->endpoint, packetList );+ if ( result != noErr ) {+ errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations.";+ error( RtMidiError::WARNING, errorString_ );+ }+ }++ // And send to an explicit destination port if we're connected.+ if ( connected_ ) {+ result = MIDISend( data->port, data->destinationId, packetList );+ if ( result != noErr ) {+ errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port.";+ error( RtMidiError::WARNING, errorString_ );+ }+ }+}++#endif // __MACOSX_CORE__+++//*********************************************************************//+// API: LINUX ALSA SEQUENCER+//*********************************************************************//++// API information found at:+// - http://www.alsa-project.org/documentation.php#Library++#if defined(__LINUX_ALSA__)++// The ALSA Sequencer API is based on the use of a callback function for+// MIDI input.+//+// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer+// time stamps and other assorted fixes!!!++// If you don't need timestamping for incoming MIDI events, define the+// preprocessor definition AVOID_TIMESTAMPING to save resources+// associated with the ALSA sequencer queues.++#include <pthread.h>+#include <sys/time.h>++// ALSA header file.+#include <alsa/asoundlib.h>++// A structure to hold variables related to the ALSA API+// implementation.+struct AlsaMidiData {+ snd_seq_t *seq;+ unsigned int portNum;+ int vport;+ snd_seq_port_subscribe_t *subscription;+ snd_midi_event_t *coder;+ unsigned int bufferSize;+ unsigned char *buffer;+ pthread_t thread;+ pthread_t dummy_thread_id;+ snd_seq_real_time_t lastTime;+ int queue_id; // an input queue is needed to get timestamped events+ int trigger_fds[2];+};++#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits))++//*********************************************************************//+// API: LINUX ALSA+// Class Definitions: MidiInAlsa+//*********************************************************************//++static void *alsaMidiHandler( void *ptr )+{+ MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr);+ AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData);++ long nBytes;+ double time;+ bool continueSysex = false;+ bool doDecode = false;+ MidiInApi::MidiMessage message;+ int poll_fd_count;+ struct pollfd *poll_fds;++ snd_seq_event_t *ev;+ int result;+ apiData->bufferSize = 32;+ result = snd_midi_event_new( 0, &apiData->coder );+ if ( result < 0 ) {+ data->doInput = false;+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n";+ return 0;+ }+ unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize );+ if ( buffer == NULL ) {+ data->doInput = false;+ snd_midi_event_free( apiData->coder );+ apiData->coder = 0;+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n";+ return 0;+ }+ snd_midi_event_init( apiData->coder );+ snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages++ poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1;+ poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd ));+ snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN );+ poll_fds[0].fd = apiData->trigger_fds[0];+ poll_fds[0].events = POLLIN;++ while ( data->doInput ) {++ if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) {+ // No data pending+ if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) {+ if ( poll_fds[0].revents & POLLIN ) {+ bool dummy;+ int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) );+ (void) res;+ }+ }+ continue;+ }++ // If here, there should be data.+ result = snd_seq_event_input( apiData->seq, &ev );+ if ( result == -ENOSPC ) {+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n";+ continue;+ }+ else if ( result <= 0 ) {+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n";+ perror("System reports");+ continue;+ }++ // This is a bit weird, but we now have to decode an ALSA MIDI+ // event (back) into MIDI bytes. We'll ignore non-MIDI types.+ if ( !continueSysex ) message.bytes.clear();++ doDecode = false;+ switch ( ev->type ) {++ case SND_SEQ_EVENT_PORT_SUBSCRIBED:+#if defined(__RTMIDI_DEBUG__)+ std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n";+#endif+ break;++ case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:+#if defined(__RTMIDI_DEBUG__)+ std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n";+ std::cout << "sender = " << (int) ev->data.connect.sender.client << ":"+ << (int) ev->data.connect.sender.port+ << ", dest = " << (int) ev->data.connect.dest.client << ":"+ << (int) ev->data.connect.dest.port+ << std::endl;+#endif+ break;++ case SND_SEQ_EVENT_QFRAME: // MIDI time code+ if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;+ break;++ case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick+ if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;+ break;++ case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick+ if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true;+ break;++ case SND_SEQ_EVENT_SENSING: // Active sensing+ if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true;+ break;++ case SND_SEQ_EVENT_SYSEX:+ if ( (data->ignoreFlags & 0x01) ) break;+ if ( ev->data.ext.len > apiData->bufferSize ) {+ apiData->bufferSize = ev->data.ext.len;+ free( buffer );+ buffer = (unsigned char *) malloc( apiData->bufferSize );+ if ( buffer == NULL ) {+ data->doInput = false;+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n";+ break;+ }+ }+ doDecode = true;+ break;++ default:+ doDecode = true;+ }++ if ( doDecode ) {++ nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev );+ if ( nBytes > 0 ) {+ // The ALSA sequencer has a maximum buffer size for MIDI sysex+ // events of 256 bytes. If a device sends sysex messages larger+ // than this, they are segmented into 256 byte chunks. So,+ // we'll watch for this and concatenate sysex chunks into a+ // single sysex message if necessary.+ if ( !continueSysex )+ message.bytes.assign( buffer, &buffer[nBytes] );+ else+ message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] );++ continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) );+ if ( !continueSysex ) {++ // Calculate the time stamp:+ message.timeStamp = 0.0;++ // Method 1: Use the system time.+ //(void)gettimeofday(&tv, (struct timezone *)NULL);+ //time = (tv.tv_sec * 1000000) + tv.tv_usec;++ // Method 2: Use the ALSA sequencer event time data.+ // (thanks to Pedro Lopez-Cabanillas!).++ // Using method from:+ // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html++ // Perform the carry for the later subtraction by updating y.+ // Temp var y is timespec because computation requires signed types,+ // while snd_seq_real_time_t has unsigned types.+ snd_seq_real_time_t &x( ev->time.time );+ struct timespec y;+ y.tv_nsec = apiData->lastTime.tv_nsec;+ y.tv_sec = apiData->lastTime.tv_sec;+ if ( x.tv_nsec < y.tv_nsec ) {+ int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1;+ y.tv_nsec -= 1000000000 * nsec;+ y.tv_sec += nsec;+ }+ if ( x.tv_nsec - y.tv_nsec > 1000000000 ) {+ int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000;+ y.tv_nsec += 1000000000 * nsec;+ y.tv_sec -= nsec;+ }++ // Compute the time difference.+ time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9;++ apiData->lastTime = ev->time.time;++ if ( data->firstMessage == true )+ data->firstMessage = false;+ else+ message.timeStamp = time;+ }+ else {+#if defined(__RTMIDI_DEBUG__)+ std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n";+#endif+ }+ }+ }++ snd_seq_free_event( ev );+ if ( message.bytes.size() == 0 || continueSysex ) continue;++ if ( data->usingCallback ) {+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;+ callback( message.timeStamp, &message.bytes, data->userData );+ }+ else {+ // As long as we haven't reached our queue size limit, push the message.+ if ( !data->queue.push( message ) )+ std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n";+ }+ }++ if ( buffer ) free( buffer );+ snd_midi_event_free( apiData->coder );+ apiData->coder = 0;+ apiData->thread = apiData->dummy_thread_id;+ return 0;+}++MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit )+ : MidiInApi( queueSizeLimit )+{+ MidiInAlsa::initialize( clientName );+}++MidiInAlsa :: ~MidiInAlsa()+{+ // Close a connection if it exists.+ MidiInAlsa::closePort();++ // Shutdown the input thread.+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( inputData_.doInput ) {+ inputData_.doInput = false;+ int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) );+ (void) res;+ if ( !pthread_equal(data->thread, data->dummy_thread_id) )+ pthread_join( data->thread, NULL );+ }++ // Cleanup.+ close ( data->trigger_fds[0] );+ close ( data->trigger_fds[1] );+ if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );+#ifndef AVOID_TIMESTAMPING+ snd_seq_free_queue( data->seq, data->queue_id );+#endif+ snd_seq_close( data->seq );+ delete data;+}++void MidiInAlsa :: initialize( const std::string& clientName )+{+ // Set up the ALSA sequencer client.+ snd_seq_t *seq;+ int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK );+ if ( result < 0 ) {+ errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Set client name.+ snd_seq_set_client_name( seq, clientName.c_str() );++ // Save our api-specific connection information.+ AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;+ data->seq = seq;+ data->portNum = -1;+ data->vport = -1;+ data->subscription = 0;+ data->dummy_thread_id = pthread_self();+ data->thread = data->dummy_thread_id;+ data->trigger_fds[0] = -1;+ data->trigger_fds[1] = -1;+ apiData_ = (void *) data;+ inputData_.apiData = (void *) data;++ if ( pipe(data->trigger_fds) == -1 ) {+ errorString_ = "MidiInAlsa::initialize: error creating pipe objects.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Create the input queue+#ifndef AVOID_TIMESTAMPING+ data->queue_id = snd_seq_alloc_named_queue( seq, "RtMidi Queue" );+ // Set arbitrary tempo (mm=100) and resolution (240)+ snd_seq_queue_tempo_t *qtempo;+ snd_seq_queue_tempo_alloca( &qtempo );+ snd_seq_queue_tempo_set_tempo( qtempo, 600000 );+ snd_seq_queue_tempo_set_ppq( qtempo, 240 );+ snd_seq_set_queue_tempo( data->seq, data->queue_id, qtempo );+ snd_seq_drain_output( data->seq );+#endif+}++// This function is used to count or get the pinfo structure for a given port number.+unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber )+{+ snd_seq_client_info_t *cinfo;+ int client;+ int count = 0;+ snd_seq_client_info_alloca( &cinfo );++ snd_seq_client_info_set_client( cinfo, -1 );+ while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) {+ client = snd_seq_client_info_get_client( cinfo );+ if ( client == 0 ) continue;+ // Reset query info+ snd_seq_port_info_set_client( pinfo, client );+ snd_seq_port_info_set_port( pinfo, -1 );+ while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) {+ unsigned int atyp = snd_seq_port_info_get_type( pinfo );+ if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) &&+ ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) &&+ ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue;++ unsigned int caps = snd_seq_port_info_get_capability( pinfo );+ if ( ( caps & type ) != type ) continue;+ if ( count == portNumber ) return 1;+ ++count;+ }+ }++ // If a negative portNumber was used, return the port count.+ if ( portNumber < 0 ) return count;+ return 0;+}++unsigned int MidiInAlsa :: getPortCount()+{+ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );++ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 );+}++std::string MidiInAlsa :: getPortName( unsigned int portNumber )+{+ snd_seq_client_info_t *cinfo;+ snd_seq_port_info_t *pinfo;+ snd_seq_client_info_alloca( &cinfo );+ snd_seq_port_info_alloca( &pinfo );++ std::string stringName;+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) {+ int cnum = snd_seq_port_info_get_client( pinfo );+ snd_seq_get_any_client_info( data->seq, cnum, cinfo );+ std::ostringstream os;+ os << snd_seq_client_info_get_name( cinfo );+ os << ":";+ os << snd_seq_port_info_get_name( pinfo );+ os << " "; // These lines added to make sure devices are listed+ os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names+ os << ":";+ os << snd_seq_port_info_get_port( pinfo );+ stringName = os.str();+ return stringName;+ }++ // If we get here, we didn't find a match.+ errorString_ = "MidiInAlsa::getPortName: error looking for port name!";+ error( RtMidiError::WARNING, errorString_ );+ return stringName;+}++void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portName )+{+ if ( connected_ ) {+ errorString_ = "MidiInAlsa::openPort: a valid connection already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ unsigned int nSrc = this->getPortCount();+ if ( nSrc < 1 ) {+ errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!";+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );+ return;+ }++ snd_seq_port_info_t *src_pinfo;+ snd_seq_port_info_alloca( &src_pinfo );+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) {+ std::ostringstream ost;+ ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::INVALID_PARAMETER, errorString_ );+ return;+ }++ snd_seq_addr_t sender, receiver;+ sender.client = snd_seq_port_info_get_client( src_pinfo );+ sender.port = snd_seq_port_info_get_port( src_pinfo );+ receiver.client = snd_seq_client_id( data->seq );++ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );+ if ( data->vport < 0 ) {+ snd_seq_port_info_set_client( pinfo, 0 );+ snd_seq_port_info_set_port( pinfo, 0 );+ snd_seq_port_info_set_capability( pinfo,+ SND_SEQ_PORT_CAP_WRITE |+ SND_SEQ_PORT_CAP_SUBS_WRITE );+ snd_seq_port_info_set_type( pinfo,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC |+ SND_SEQ_PORT_TYPE_APPLICATION );+ snd_seq_port_info_set_midi_channels(pinfo, 16);+#ifndef AVOID_TIMESTAMPING+ snd_seq_port_info_set_timestamping( pinfo, 1 );+ snd_seq_port_info_set_timestamp_real( pinfo, 1 );+ snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id );+#endif+ snd_seq_port_info_set_name( pinfo, portName.c_str() );+ data->vport = snd_seq_create_port( data->seq, pinfo );++ if ( data->vport < 0 ) {+ errorString_ = "MidiInAlsa::openPort: ALSA error creating input port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ data->vport = snd_seq_port_info_get_port( pinfo );+ }++ receiver.port = data->vport;++ if ( !data->subscription ) {+ // Make subscription+ if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) {+ errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ snd_seq_port_subscribe_set_sender( data->subscription, &sender );+ snd_seq_port_subscribe_set_dest( data->subscription, &receiver );+ if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) {+ snd_seq_port_subscribe_free( data->subscription );+ data->subscription = 0;+ errorString_ = "MidiInAlsa::openPort: ALSA error making port connection.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ }++ if ( inputData_.doInput == false ) {+ // Start the input queue+#ifndef AVOID_TIMESTAMPING+ snd_seq_start_queue( data->seq, data->queue_id, NULL );+ snd_seq_drain_output( data->seq );+#endif+ // Start our MIDI input thread.+ pthread_attr_t attr;+ pthread_attr_init( &attr );+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );++ inputData_.doInput = true;+ int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ );+ pthread_attr_destroy( &attr );+ if ( err ) {+ snd_seq_unsubscribe_port( data->seq, data->subscription );+ snd_seq_port_subscribe_free( data->subscription );+ data->subscription = 0;+ inputData_.doInput = false;+ errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";+ error( RtMidiError::THREAD_ERROR, errorString_ );+ return;+ }+ }++ connected_ = true;+}++void MidiInAlsa :: openVirtualPort( const std::string &portName )+{+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( data->vport < 0 ) {+ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );+ snd_seq_port_info_set_capability( pinfo,+ SND_SEQ_PORT_CAP_WRITE |+ SND_SEQ_PORT_CAP_SUBS_WRITE );+ snd_seq_port_info_set_type( pinfo,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC |+ SND_SEQ_PORT_TYPE_APPLICATION );+ snd_seq_port_info_set_midi_channels( pinfo, 16 );+#ifndef AVOID_TIMESTAMPING+ snd_seq_port_info_set_timestamping( pinfo, 1 );+ snd_seq_port_info_set_timestamp_real( pinfo, 1 );+ snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id );+#endif+ snd_seq_port_info_set_name( pinfo, portName.c_str() );+ data->vport = snd_seq_create_port( data->seq, pinfo );++ if ( data->vport < 0 ) {+ errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ data->vport = snd_seq_port_info_get_port( pinfo );+ }++ if ( inputData_.doInput == false ) {+ // Wait for old thread to stop, if still running+ if ( !pthread_equal( data->thread, data->dummy_thread_id ) )+ pthread_join( data->thread, NULL );++ // Start the input queue+#ifndef AVOID_TIMESTAMPING+ snd_seq_start_queue( data->seq, data->queue_id, NULL );+ snd_seq_drain_output( data->seq );+#endif+ // Start our MIDI input thread.+ pthread_attr_t attr;+ pthread_attr_init( &attr );+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );++ inputData_.doInput = true;+ int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ );+ pthread_attr_destroy( &attr );+ if ( err ) {+ if ( data->subscription ) {+ snd_seq_unsubscribe_port( data->seq, data->subscription );+ snd_seq_port_subscribe_free( data->subscription );+ data->subscription = 0;+ }+ inputData_.doInput = false;+ errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!";+ error( RtMidiError::THREAD_ERROR, errorString_ );+ return;+ }+ }+}++void MidiInAlsa :: closePort( void )+{+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);++ if ( connected_ ) {+ if ( data->subscription ) {+ snd_seq_unsubscribe_port( data->seq, data->subscription );+ snd_seq_port_subscribe_free( data->subscription );+ data->subscription = 0;+ }+ // Stop the input queue+#ifndef AVOID_TIMESTAMPING+ snd_seq_stop_queue( data->seq, data->queue_id, NULL );+ snd_seq_drain_output( data->seq );+#endif+ connected_ = false;+ }++ // Stop thread to avoid triggering the callback, while the port is intended to be closed+ if ( inputData_.doInput ) {+ inputData_.doInput = false;+ int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) );+ (void) res;+ if ( !pthread_equal( data->thread, data->dummy_thread_id ) )+ pthread_join( data->thread, NULL );+ }+}++void MidiInAlsa :: setClientName( const std::string &clientName )+{++ AlsaMidiData *data = static_cast<AlsaMidiData *> ( apiData_ );+ snd_seq_set_client_name( data->seq, clientName.c_str() );++}++void MidiInAlsa :: setPortName( const std::string &portName )+{+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );+ snd_seq_get_port_info( data->seq, data->vport, pinfo );+ snd_seq_port_info_set_name( pinfo, portName.c_str() );+ snd_seq_set_port_info( data->seq, data->vport, pinfo );+}++//*********************************************************************//+// API: LINUX ALSA+// Class Definitions: MidiOutAlsa+//*********************************************************************//++MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi()+{+ MidiOutAlsa::initialize( clientName );+}++MidiOutAlsa :: ~MidiOutAlsa()+{+ // Close a connection if it exists.+ MidiOutAlsa::closePort();++ // Cleanup.+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport );+ if ( data->coder ) snd_midi_event_free( data->coder );+ if ( data->buffer ) free( data->buffer );+ snd_seq_close( data->seq );+ delete data;+}++void MidiOutAlsa :: initialize( const std::string& clientName )+{+ // Set up the ALSA sequencer client.+ snd_seq_t *seq;+ int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK );+ if ( result1 < 0 ) {+ errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Set client name.+ snd_seq_set_client_name( seq, clientName.c_str() );++ // Save our api-specific connection information.+ AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData;+ data->seq = seq;+ data->portNum = -1;+ data->vport = -1;+ data->bufferSize = 32;+ data->coder = 0;+ data->buffer = 0;+ int result = snd_midi_event_new( data->bufferSize, &data->coder );+ if ( result < 0 ) {+ delete data;+ errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ data->buffer = (unsigned char *) malloc( data->bufferSize );+ if ( data->buffer == NULL ) {+ delete data;+ errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";+ error( RtMidiError::MEMORY_ERROR, errorString_ );+ return;+ }+ snd_midi_event_init( data->coder );+ apiData_ = (void *) data;+}++unsigned int MidiOutAlsa :: getPortCount()+{+ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );++ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 );+}++std::string MidiOutAlsa :: getPortName( unsigned int portNumber )+{+ snd_seq_client_info_t *cinfo;+ snd_seq_port_info_t *pinfo;+ snd_seq_client_info_alloca( &cinfo );+ snd_seq_port_info_alloca( &pinfo );++ std::string stringName;+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) {+ int cnum = snd_seq_port_info_get_client( pinfo );+ snd_seq_get_any_client_info( data->seq, cnum, cinfo );+ std::ostringstream os;+ os << snd_seq_client_info_get_name( cinfo );+ os << ":";+ os << snd_seq_port_info_get_name( pinfo );+ os << " "; // These lines added to make sure devices are listed+ os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names+ os << ":";+ os << snd_seq_port_info_get_port( pinfo );+ stringName = os.str();+ return stringName;+ }++ // If we get here, we didn't find a match.+ errorString_ = "MidiOutAlsa::getPortName: error looking for port name!";+ error( RtMidiError::WARNING, errorString_ );+ return stringName;+}++void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portName )+{+ if ( connected_ ) {+ errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ unsigned int nSrc = this->getPortCount();+ if ( nSrc < 1 ) {+ errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!";+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );+ return;+ }++ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) {+ std::ostringstream ost;+ ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::INVALID_PARAMETER, errorString_ );+ return;+ }++ snd_seq_addr_t sender, receiver;+ receiver.client = snd_seq_port_info_get_client( pinfo );+ receiver.port = snd_seq_port_info_get_port( pinfo );+ sender.client = snd_seq_client_id( data->seq );++ if ( data->vport < 0 ) {+ data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),+ SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );+ if ( data->vport < 0 ) {+ errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ }++ sender.port = data->vport;++ // Make subscription+ if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) {+ snd_seq_port_subscribe_free( data->subscription );+ errorString_ = "MidiOutAlsa::openPort: error allocating port subscription.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ snd_seq_port_subscribe_set_sender( data->subscription, &sender );+ snd_seq_port_subscribe_set_dest( data->subscription, &receiver );+ snd_seq_port_subscribe_set_time_update( data->subscription, 1 );+ snd_seq_port_subscribe_set_time_real( data->subscription, 1 );+ if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) {+ snd_seq_port_subscribe_free( data->subscription );+ errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ connected_ = true;+}++void MidiOutAlsa :: closePort( void )+{+ if ( connected_ ) {+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ snd_seq_unsubscribe_port( data->seq, data->subscription );+ snd_seq_port_subscribe_free( data->subscription );+ data->subscription = 0;+ connected_ = false;+ }+}++void MidiOutAlsa :: setClientName( const std::string &clientName )+{++ AlsaMidiData *data = static_cast<AlsaMidiData *> ( apiData_ );+ snd_seq_set_client_name( data->seq, clientName.c_str() );++}++void MidiOutAlsa :: setPortName( const std::string &portName )+{+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ snd_seq_port_info_t *pinfo;+ snd_seq_port_info_alloca( &pinfo );+ snd_seq_get_port_info( data->seq, data->vport, pinfo );+ snd_seq_port_info_set_name( pinfo, portName.c_str() );+ snd_seq_set_port_info( data->seq, data->vport, pinfo );+}++void MidiOutAlsa :: openVirtualPort( const std::string &portName )+{+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ if ( data->vport < 0 ) {+ data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(),+ SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,+ SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION );++ if ( data->vport < 0 ) {+ errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ }+ }+}++void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size )+{+ int result;+ AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_);+ unsigned int nBytes = static_cast<unsigned int> (size);+ if ( nBytes > data->bufferSize ) {+ data->bufferSize = nBytes;+ result = snd_midi_event_resize_buffer( data->coder, nBytes );+ if ( result != 0 ) {+ errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ free (data->buffer);+ data->buffer = (unsigned char *) malloc( data->bufferSize );+ if ( data->buffer == NULL ) {+ errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n";+ error( RtMidiError::MEMORY_ERROR, errorString_ );+ return;+ }+ }++ snd_seq_event_t ev;+ snd_seq_ev_clear( &ev );+ snd_seq_ev_set_source( &ev, data->vport );+ snd_seq_ev_set_subs( &ev );+ snd_seq_ev_set_direct( &ev );+ for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message[i];+ result = snd_midi_event_encode( data->coder, data->buffer, (long)nBytes, &ev );+ if ( result < (int)nBytes ) {+ errorString_ = "MidiOutAlsa::sendMessage: event parsing error!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ // Send the event.+ result = snd_seq_event_output( data->seq, &ev );+ if ( result < 0 ) {+ errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port.";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }+ snd_seq_drain_output( data->seq );+}++#endif // __LINUX_ALSA__+++//*********************************************************************//+// API: Windows Multimedia Library (MM)+//*********************************************************************//++// API information deciphered from:+// - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp++// Thanks to Jean-Baptiste Berruchon for the sysex code.++#if defined(__WINDOWS_MM__)++// The Windows MM API is based on the use of a callback function for+// MIDI input. We convert the system specific time stamps to delta+// time values.++// Windows MM MIDI header files.+#include <windows.h>+#include <mmsystem.h>++// Convert a null-terminated wide string or ANSI-encoded string to UTF-8.+static std::string ConvertToUTF8(const TCHAR *str)+{+ std::string u8str;+ const WCHAR *wstr = L"";+#if defined( UNICODE ) || defined( _UNICODE )+ wstr = str;+#else+ // Convert from ANSI encoding to wide string+ int wlength = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 );+ std::wstring wstrtemp;+ if ( wlength )+ {+ wstrtemp.assign( wlength - 1, 0 );+ MultiByteToWideChar( CP_ACP, 0, str, -1, &wstrtemp[0], wlength );+ wstr = &wstrtemp[0];+ }+#endif+ // Convert from wide string to UTF-8+ int length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL );+ if ( length )+ {+ u8str.assign( length - 1, 0 );+ length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL );+ }+ return u8str;+}++#define RT_SYSEX_BUFFER_SIZE 1024+#define RT_SYSEX_BUFFER_COUNT 4++// A structure to hold variables related to the CoreMIDI API+// implementation.+struct WinMidiData {+ HMIDIIN inHandle; // Handle to Midi Input Device+ HMIDIOUT outHandle; // Handle to Midi Output Device+ DWORD lastTime;+ MidiInApi::MidiMessage message;+ LPMIDIHDR sysexBuffer[RT_SYSEX_BUFFER_COUNT];+ CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo+};++//*********************************************************************//+// API: Windows MM+// Class Definitions: MidiInWinMM+//*********************************************************************//++static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/,+ UINT inputStatus,+ DWORD_PTR instancePtr,+ DWORD_PTR midiMessage,+ DWORD timestamp )+{+ if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return;++ //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr);+ MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr;+ WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData);++ // Calculate time stamp.+ if ( data->firstMessage == true ) {+ apiData->message.timeStamp = 0.0;+ data->firstMessage = false;+ }+ else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001;++ if ( inputStatus == MIM_DATA ) { // Channel or system message++ // Make sure the first byte is a status byte.+ unsigned char status = (unsigned char) (midiMessage & 0x000000FF);+ if ( !(status & 0x80) ) return;++ // Determine the number of bytes in the MIDI message.+ unsigned short nBytes = 1;+ if ( status < 0xC0 ) nBytes = 3;+ else if ( status < 0xE0 ) nBytes = 2;+ else if ( status < 0xF0 ) nBytes = 3;+ else if ( status == 0xF1 ) {+ if ( data->ignoreFlags & 0x02 ) return;+ else nBytes = 2;+ }+ else if ( status == 0xF2 ) nBytes = 3;+ else if ( status == 0xF3 ) nBytes = 2;+ else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) {+ // A MIDI timing tick message and we're ignoring it.+ return;+ }+ else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) {+ // A MIDI active sensing message and we're ignoring it.+ return;+ }++ // Copy bytes to our MIDI message.+ unsigned char *ptr = (unsigned char *) &midiMessage;+ for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ );+ }+ else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR )+ MIDIHDR *sysex = ( MIDIHDR *) midiMessage;+ if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) {+ // Sysex message and we're not ignoring it+ for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i )+ apiData->message.bytes.push_back( sysex->lpData[i] );+ }++ // The WinMM API requires that the sysex buffer be requeued after+ // input of each sysex message. Even if we are ignoring sysex+ // messages, we still need to requeue the buffer in case the user+ // decides to not ignore sysex messages in the future. However,+ // it seems that WinMM calls this function with an empty sysex+ // buffer when an application closes and in this case, we should+ // avoid requeueing it, else the computer suddenly reboots after+ // one or two minutes.+ if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) {+ //if ( sysex->dwBytesRecorded > 0 ) {+ EnterCriticalSection( &(apiData->_mutex) );+ MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) );+ LeaveCriticalSection( &(apiData->_mutex) );+ if ( result != MMSYSERR_NOERROR )+ std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n";++ if ( data->ignoreFlags & 0x01 ) return;+ }+ else return;+ }++ // Save the time of the last non-filtered message+ apiData->lastTime = timestamp;++ if ( data->usingCallback ) {+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback;+ callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData );+ }+ else {+ // As long as we haven't reached our queue size limit, push the message.+ if ( !data->queue.push( apiData->message ) )+ std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n";+ }++ // Clear the vector for the next input message.+ apiData->message.bytes.clear();+}++MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit )+ : MidiInApi( queueSizeLimit )+{+ MidiInWinMM::initialize( clientName );+}++MidiInWinMM :: ~MidiInWinMM()+{+ // Close a connection if it exists.+ MidiInWinMM::closePort();++ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ DeleteCriticalSection( &(data->_mutex) );++ // Cleanup.+ delete data;+}++void MidiInWinMM :: initialize( const std::string& /*clientName*/ )+{+ // We'll issue a warning here if no devices are available but not+ // throw an error since the user can plugin something later.+ unsigned int nDevices = midiInGetNumDevs();+ if ( nDevices == 0 ) {+ errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available.";+ error( RtMidiError::WARNING, errorString_ );+ }++ // Save our api-specific connection information.+ WinMidiData *data = (WinMidiData *) new WinMidiData;+ apiData_ = (void *) data;+ inputData_.apiData = (void *) data;+ data->message.bytes.clear(); // needs to be empty for first input message++ if ( !InitializeCriticalSectionAndSpinCount( &(data->_mutex), 0x00000400 ) ) {+ errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed.";+ error( RtMidiError::WARNING, errorString_ );+ }+}++void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ )+{+ if ( connected_ ) {+ errorString_ = "MidiInWinMM::openPort: a valid connection already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ unsigned int nDevices = midiInGetNumDevs();+ if (nDevices == 0) {+ errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!";+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );+ return;+ }++ if ( portNumber >= nDevices ) {+ std::ostringstream ost;+ ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::INVALID_PARAMETER, errorString_ );+ return;+ }++ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ MMRESULT result = midiInOpen( &data->inHandle,+ portNumber,+ (DWORD_PTR)&midiInputCallback,+ (DWORD_PTR)&inputData_,+ CALLBACK_FUNCTION );+ if ( result != MMSYSERR_NOERROR ) {+ errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Allocate and init the sysex buffers.+ for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {+ data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ];+ data->sysexBuffer[i]->lpData = new char[ RT_SYSEX_BUFFER_SIZE ];+ data->sysexBuffer[i]->dwBufferLength = RT_SYSEX_BUFFER_SIZE;+ data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator+ data->sysexBuffer[i]->dwFlags = 0;++ result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );+ if ( result != MMSYSERR_NOERROR ) {+ midiInClose( data->inHandle );+ data->inHandle = 0;+ errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader).";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Register the buffer.+ result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) );+ if ( result != MMSYSERR_NOERROR ) {+ midiInClose( data->inHandle );+ data->inHandle = 0;+ errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer).";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ }++ result = midiInStart( data->inHandle );+ if ( result != MMSYSERR_NOERROR ) {+ midiInClose( data->inHandle );+ data->inHandle = 0;+ errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ connected_ = true;+}++void MidiInWinMM :: openVirtualPort( const std::string &/*portName*/ )+{+ // This function cannot be implemented for the Windows MM MIDI API.+ errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";+ error( RtMidiError::WARNING, errorString_ );+}++void MidiInWinMM :: closePort( void )+{+ if ( connected_ ) {+ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ EnterCriticalSection( &(data->_mutex) );+ midiInReset( data->inHandle );+ midiInStop( data->inHandle );++ for ( int i=0; i<RT_SYSEX_BUFFER_COUNT; ++i ) {+ int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR));+ delete [] data->sysexBuffer[i]->lpData;+ delete [] data->sysexBuffer[i];+ if ( result != MMSYSERR_NOERROR ) {+ midiInClose( data->inHandle );+ data->inHandle = 0;+ errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader).";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }+ }++ midiInClose( data->inHandle );+ data->inHandle = 0;+ connected_ = false;+ LeaveCriticalSection( &(data->_mutex) );+ }+}++void MidiInWinMM :: setClientName ( const std::string& )+{++ errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiInWinMM :: setPortName ( const std::string& )+{++ errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!";+ error( RtMidiError::WARNING, errorString_ );++}++unsigned int MidiInWinMM :: getPortCount()+{+ return midiInGetNumDevs();+}++std::string MidiInWinMM :: getPortName( unsigned int portNumber )+{+ std::string stringName;+ unsigned int nDevices = midiInGetNumDevs();+ if ( portNumber >= nDevices ) {+ std::ostringstream ost;+ ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::WARNING, errorString_ );+ return stringName;+ }++ MIDIINCAPS deviceCaps;+ midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS));+ stringName = ConvertToUTF8( deviceCaps.szPname );++ // Next lines added to add the portNumber to the name so that+ // the device's names are sure to be listed with individual names+ // even when they have the same brand name+#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES+ std::ostringstream os;+ os << " ";+ os << portNumber;+ stringName += os.str();+#endif++ return stringName;+}++//*********************************************************************//+// API: Windows MM+// Class Definitions: MidiOutWinMM+//*********************************************************************//++MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi()+{+ MidiOutWinMM::initialize( clientName );+}++MidiOutWinMM :: ~MidiOutWinMM()+{+ // Close a connection if it exists.+ MidiOutWinMM::closePort();++ // Cleanup.+ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ delete data;+}++void MidiOutWinMM :: initialize( const std::string& /*clientName*/ )+{+ // We'll issue a warning here if no devices are available but not+ // throw an error since the user can plug something in later.+ unsigned int nDevices = midiOutGetNumDevs();+ if ( nDevices == 0 ) {+ errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available.";+ error( RtMidiError::WARNING, errorString_ );+ }++ // Save our api-specific connection information.+ WinMidiData *data = (WinMidiData *) new WinMidiData;+ apiData_ = (void *) data;+}++unsigned int MidiOutWinMM :: getPortCount()+{+ return midiOutGetNumDevs();+}++std::string MidiOutWinMM :: getPortName( unsigned int portNumber )+{+ std::string stringName;+ unsigned int nDevices = midiOutGetNumDevs();+ if ( portNumber >= nDevices ) {+ std::ostringstream ost;+ ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::WARNING, errorString_ );+ return stringName;+ }++ MIDIOUTCAPS deviceCaps;+ midiOutGetDevCaps( portNumber, &deviceCaps, sizeof( MIDIOUTCAPS ) );+ stringName = ConvertToUTF8( deviceCaps.szPname );++ // Next lines added to add the portNumber to the name so that+ // the device's names are sure to be listed with individual names+ // even when they have the same brand name+ std::ostringstream os;+#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES+ os << " ";+ os << portNumber;+ stringName += os.str();+#endif++ return stringName;+}++void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ )+{+ if ( connected_ ) {+ errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ unsigned int nDevices = midiOutGetNumDevs();+ if ( nDevices < 1 ) {+ errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!";+ error( RtMidiError::NO_DEVICES_FOUND, errorString_ );+ return;+ }++ if ( portNumber >= nDevices ) {+ std::ostringstream ost;+ ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::INVALID_PARAMETER, errorString_ );+ return;+ }++ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ MMRESULT result = midiOutOpen( &data->outHandle,+ portNumber,+ (DWORD)NULL,+ (DWORD)NULL,+ CALLBACK_NULL );+ if ( result != MMSYSERR_NOERROR ) {+ errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ connected_ = true;+}++void MidiOutWinMM :: closePort( void )+{+ if ( connected_ ) {+ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ midiOutReset( data->outHandle );+ midiOutClose( data->outHandle );+ data->outHandle = 0;+ connected_ = false;+ }+}++void MidiOutWinMM :: setClientName ( const std::string& )+{++ errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiOutWinMM :: setPortName ( const std::string& )+{++ errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ )+{+ // This function cannot be implemented for the Windows MM MIDI API.+ errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!";+ error( RtMidiError::WARNING, errorString_ );+}++void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size )+{+ if ( !connected_ ) return;++ unsigned int nBytes = static_cast<unsigned int>(size);+ if ( nBytes == 0 ) {+ errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ MMRESULT result;+ WinMidiData *data = static_cast<WinMidiData *> (apiData_);+ if ( message[0] == 0xF0 ) { // Sysex message++ // Allocate buffer for sysex data.+ char *buffer = (char *) malloc( nBytes );+ if ( buffer == NULL ) {+ errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!";+ error( RtMidiError::MEMORY_ERROR, errorString_ );+ return;+ }++ // Copy data to buffer.+ for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message[i];++ // Create and prepare MIDIHDR structure.+ MIDIHDR sysex;+ sysex.lpData = (LPSTR) buffer;+ sysex.dwBufferLength = nBytes;+ sysex.dwFlags = 0;+ result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof( MIDIHDR ) );+ if ( result != MMSYSERR_NOERROR ) {+ free( buffer );+ errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Send the message.+ result = midiOutLongMsg( data->outHandle, &sysex, sizeof( MIDIHDR ) );+ if ( result != MMSYSERR_NOERROR ) {+ free( buffer );+ errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Unprepare the buffer and MIDIHDR.+ while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof ( MIDIHDR ) ) ) Sleep( 1 );+ free( buffer );+ }+ else { // Channel or system message.++ // Make sure the message size isn't too big.+ if ( nBytes > 3 ) {+ errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ // Pack MIDI bytes into double word.+ DWORD packet;+ unsigned char *ptr = (unsigned char *) &packet;+ for ( unsigned int i=0; i<nBytes; ++i ) {+ *ptr = message[i];+ ++ptr;+ }++ // Send the message immediately.+ result = midiOutShortMsg( data->outHandle, packet );+ if ( result != MMSYSERR_NOERROR ) {+ errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message.";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ }+ }+}++#endif // __WINDOWS_MM__+++//*********************************************************************//+// API: UNIX JACK+//+// Written primarily by Alexander Svetalkin, with updates for delta+// time by Gary Scavone, April 2011.+//+// *********************************************************************//++#if defined(__UNIX_JACK__)++// JACK header files+#include <jack/jack.h>+#include <jack/midiport.h>+#include <jack/ringbuffer.h>+#ifdef HAVE_SEMAPHORE+ #include <semaphore.h>+#endif++#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer++struct JackMidiData {+ jack_client_t *client;+ jack_port_t *port;+ jack_ringbuffer_t *buffSize;+ jack_ringbuffer_t *buffMessage;+ jack_time_t lastTime;+#ifdef HAVE_SEMAPHORE+ sem_t sem_cleanup;+ sem_t sem_needpost;+#endif+ MidiInApi :: RtMidiInData *rtMidiIn;+ };++//*********************************************************************//+// API: JACK+// Class Definitions: MidiInJack+//*********************************************************************//++static int jackProcessIn( jack_nframes_t nframes, void *arg )+{+ JackMidiData *jData = (JackMidiData *) arg;+ MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn;+ jack_midi_event_t event;+ jack_time_t time;++ // Is port created?+ if ( jData->port == NULL ) return 0;++ void *buff = jack_port_get_buffer( jData->port, nframes );+ bool& continueSysex = rtData->continueSysex;+ unsigned char& ignoreFlags = rtData->ignoreFlags;++ // We have midi events in buffer+ int evCount = jack_midi_get_event_count( buff );+ for (int j = 0; j < evCount; j++) {+ MidiInApi::MidiMessage& message = rtData->message;+ jack_midi_event_get( &event, buff, j );++ // Compute the delta time.+ time = jack_get_time();+ if ( rtData->firstMessage == true ) {+ message.timeStamp = 0.0;+ rtData->firstMessage = false;+ } else+ message.timeStamp = ( time - jData->lastTime ) * 0.000001;++ jData->lastTime = time;++ if ( !continueSysex )+ message.bytes.clear();++ if ( !( ( continueSysex || event.buffer[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) {+ // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx,+ // copy the event buffer into the MIDI message struct.+ for ( unsigned int i = 0; i < event.size; i++ )+ message.bytes.push_back( event.buffer[i] );+ }++ switch ( event.buffer[0] ) {+ case 0xF0:+ // Start of a SysEx message+ continueSysex = event.buffer[event.size - 1] != 0xF7;+ if ( ignoreFlags & 0x01 ) continue;+ break;+ case 0xF1:+ case 0xF8:+ // MIDI Time Code or Timing Clock message+ if ( ignoreFlags & 0x02 ) continue;+ break;+ case 0xFE:+ // Active Sensing message+ if ( ignoreFlags & 0x04 ) continue;+ break;+ default:+ if ( continueSysex ) {+ // Continuation of a SysEx message+ continueSysex = event.buffer[event.size - 1] != 0xF7;+ if ( ignoreFlags & 0x01 ) continue;+ }+ // All other MIDI messages+ }++ if ( !continueSysex ) {+ // If not a continuation of a SysEx message,+ // invoke the user callback function or queue the message.+ if ( rtData->usingCallback ) {+ RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback;+ callback( message.timeStamp, &message.bytes, rtData->userData );+ }+ else {+ // As long as we haven't reached our queue size limit, push the message.+ if ( !rtData->queue.push( message ) )+ std::cerr << "\nMidiInJack: message queue limit reached!!\n\n";+ }+ }+ }++ return 0;+}++MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit )+ : MidiInApi( queueSizeLimit )+{+ MidiInJack::initialize( clientName );+}++void MidiInJack :: initialize( const std::string& clientName )+{+ JackMidiData *data = new JackMidiData;+ apiData_ = (void *) data;++ data->rtMidiIn = &inputData_;+ data->port = NULL;+ data->client = NULL;+ this->clientName = clientName;++ connect();+}++void MidiInJack :: connect()+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ if ( data->client )+ return;++ // Initialize JACK client+ if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) {+ errorString_ = "MidiInJack::initialize: JACK server not running?";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ jack_set_process_callback( data->client, jackProcessIn, data );+ jack_activate( data->client );+}++MidiInJack :: ~MidiInJack()+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ MidiInJack::closePort();++ if ( data->client )+ jack_client_close( data->client );+ delete data;+}++void MidiInJack :: openPort( unsigned int portNumber, const std::string &portName )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ connect();++ // Creating new port+ if ( data->port == NULL )+ data->port = jack_port_register( data->client, portName.c_str(),+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );++ if ( data->port == NULL ) {+ errorString_ = "MidiInJack::openPort: JACK error creating port";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Connecting to the output+ std::string name = getPortName( portNumber );+ jack_connect( data->client, name.c_str(), jack_port_name( data->port ) );++ connected_ = true;+}++void MidiInJack :: openVirtualPort( const std::string &portName )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ connect();+ if ( data->port == NULL )+ data->port = jack_port_register( data->client, portName.c_str(),+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 );++ if ( data->port == NULL ) {+ errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ }+}++unsigned int MidiInJack :: getPortCount()+{+ int count = 0;+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ connect();+ if ( !data->client )+ return 0;++ // List of available ports+ const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );++ if ( ports == NULL ) return 0;+ while ( ports[count] != NULL )+ count++;++ free( ports );++ return count;+}++std::string MidiInJack :: getPortName( unsigned int portNumber )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ std::string retStr( "" );++ connect();++ // List of available ports+ const char **ports = jack_get_ports( data->client, NULL,+ JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput );++ // Check port validity+ if ( ports == NULL ) {+ errorString_ = "MidiInJack::getPortName: no ports available!";+ error( RtMidiError::WARNING, errorString_ );+ return retStr;+ }++ unsigned int i;+ for ( i=0; i<portNumber && ports[i]; i++ ) {}+ if ( i < portNumber || !ports[portNumber] ) {+ std::ostringstream ost;+ ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::WARNING, errorString_ );+ }+ else retStr.assign( ports[portNumber] );++ jack_free( ports );+ return retStr;+}++void MidiInJack :: closePort()+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ if ( data->port == NULL ) return;+ jack_port_unregister( data->client, data->port );+ data->port = NULL;++ connected_ = false;+}++void MidiInJack:: setClientName( const std::string& )+{++ errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiInJack :: setPortName( const std::string &portName )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+#ifdef JACK_HAS_PORT_RENAME+ jack_port_rename( data->client, data->port, portName.c_str() );+#else+ jack_port_set_name( data->port, portName.c_str() );+#endif+}++//*********************************************************************//+// API: JACK+// Class Definitions: MidiOutJack+//*********************************************************************//++// Jack process callback+static int jackProcessOut( jack_nframes_t nframes, void *arg )+{+ JackMidiData *data = (JackMidiData *) arg;+ jack_midi_data_t *midiData;+ int space;++ // Is port created?+ if ( data->port == NULL ) return 0;++ void *buff = jack_port_get_buffer( data->port, nframes );+ jack_midi_clear_buffer( buff );++ while ( jack_ringbuffer_read_space( data->buffSize ) > 0 ) {+ jack_ringbuffer_read( data->buffSize, (char *) &space, (size_t) sizeof( space ) );+ midiData = jack_midi_event_reserve( buff, 0, space );++ jack_ringbuffer_read( data->buffMessage, (char *) midiData, (size_t) space );+ }++#ifdef HAVE_SEMAPHORE+ if ( !sem_trywait( &data->sem_needpost ) )+ sem_post( &data->sem_cleanup );+#endif++ return 0;+}++MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi()+{+ MidiOutJack::initialize( clientName );+}++void MidiOutJack :: initialize( const std::string& clientName )+{+ JackMidiData *data = new JackMidiData;+ apiData_ = (void *) data;++ data->port = NULL;+ data->client = NULL;+#ifdef HAVE_SEMAPHORE+ sem_init( &data->sem_cleanup, 0, 0 );+ sem_init( &data->sem_needpost, 0, 0 );+#endif+ this->clientName = clientName;++ connect();+}++void MidiOutJack :: connect()+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ if ( data->client )+ return;++ // Initialize output ringbuffers+ data->buffSize = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );+ data->buffMessage = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE );++ // Initialize JACK client+ if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) {+ errorString_ = "MidiOutJack::initialize: JACK server not running?";+ error( RtMidiError::WARNING, errorString_ );+ return;+ }++ jack_set_process_callback( data->client, jackProcessOut, data );+ jack_activate( data->client );+}++MidiOutJack :: ~MidiOutJack()+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ MidiOutJack::closePort();++ // Cleanup+ jack_ringbuffer_free( data->buffSize );+ jack_ringbuffer_free( data->buffMessage );+ if ( data->client ) {+ jack_client_close( data->client );+ }++#ifdef HAVE_SEMAPHORE+ sem_destroy( &data->sem_cleanup );+ sem_destroy( &data->sem_needpost );+#endif++ delete data;+}++void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portName )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ connect();++ // Creating new port+ if ( data->port == NULL )+ data->port = jack_port_register( data->client, portName.c_str(),+ JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );++ if ( data->port == NULL ) {+ errorString_ = "MidiOutJack::openPort: JACK error creating port";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ return;+ }++ // Connecting to the output+ std::string name = getPortName( portNumber );+ jack_connect( data->client, jack_port_name( data->port ), name.c_str() );++ connected_ = true;+}++void MidiOutJack :: openVirtualPort( const std::string &portName )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ connect();+ if ( data->port == NULL )+ data->port = jack_port_register( data->client, portName.c_str(),+ JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 );++ if ( data->port == NULL ) {+ errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port";+ error( RtMidiError::DRIVER_ERROR, errorString_ );+ }+}++unsigned int MidiOutJack :: getPortCount()+{+ int count = 0;+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ connect();+ if ( !data->client )+ return 0;++ // List of available ports+ const char **ports = jack_get_ports( data->client, NULL,+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );++ if ( ports == NULL ) return 0;+ while ( ports[count] != NULL )+ count++;++ free( ports );++ return count;+}++std::string MidiOutJack :: getPortName( unsigned int portNumber )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+ std::string retStr("");++ connect();++ // List of available ports+ const char **ports = jack_get_ports( data->client, NULL,+ JACK_DEFAULT_MIDI_TYPE, JackPortIsInput );++ // Check port validity+ if ( ports == NULL ) {+ errorString_ = "MidiOutJack::getPortName: no ports available!";+ error( RtMidiError::WARNING, errorString_ );+ return retStr;+ }++ if ( ports[portNumber] == NULL ) {+ std::ostringstream ost;+ ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid.";+ errorString_ = ost.str();+ error( RtMidiError::WARNING, errorString_ );+ }+ else retStr.assign( ports[portNumber] );++ free( ports );+ return retStr;+}++void MidiOutJack :: closePort()+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ if ( data->port == NULL ) return;++#ifdef HAVE_SEMAPHORE+ struct timespec ts;+ if ( clock_gettime( CLOCK_REALTIME, &ts ) != -1 ) {+ ts.tv_sec += 1; // wait max one second+ sem_post( &data->sem_needpost );+ sem_timedwait( &data->sem_cleanup, &ts );+ }+#endif++ jack_port_unregister( data->client, data->port );+ data->port = NULL;++ connected_ = false;+}++void MidiOutJack:: setClientName( const std::string& )+{++ errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!";+ error( RtMidiError::WARNING, errorString_ );++}++void MidiOutJack :: setPortName( const std::string &portName )+{+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);+#ifdef JACK_HAS_PORT_RENAME+ jack_port_rename( data->client, data->port, portName.c_str() );+#else+ jack_port_set_name( data->port, portName.c_str() );+#endif+}++void MidiOutJack :: sendMessage( const unsigned char *message, size_t size )+{+ int nBytes = static_cast<int>(size);+ JackMidiData *data = static_cast<JackMidiData *> (apiData_);++ // Write full message to buffer+ jack_ringbuffer_write( data->buffMessage, ( const char * ) message, nBytes ); jack_ringbuffer_write( data->buffSize, ( char * ) &nBytes, sizeof( nBytes ) ); }
rtmidi/RtMidi.h view
@@ -5,10 +5,11 @@ This class implements some common functionality for the realtime MIDI input/output subclasses RtMidiIn and RtMidiOut. - RtMidi WWW site: http://music.mcgill.ca/~gary/rtmidi/+ RtMidi GitHub site: https://github.com/thestk/rtmidi+ RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ RtMidi: realtime MIDI i/o C++ classes- Copyright (c) 2003-2016 Gary P. Scavone+ Copyright (c) 2003-2019 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files@@ -43,8 +44,22 @@ #ifndef RTMIDI_H #define RTMIDI_H -#define RTMIDI_VERSION "2.1.1"+#if defined _WIN32 || defined __CYGWIN__+ #if defined(RTMIDI_EXPORT)+ #define RTMIDI_DLL_PUBLIC __declspec(dllexport)+ #else+ #define RTMIDI_DLL_PUBLIC+ #endif+#else+ #if __GNUC__ >= 4+ #define RTMIDI_DLL_PUBLIC __attribute__( (visibility( "default" )) )+ #else+ #define RTMIDI_DLL_PUBLIC+ #endif+#endif +#define RTMIDI_VERSION "4.0.0"+ #include <exception> #include <iostream> #include <string>@@ -60,7 +75,7 @@ */ /************************************************************************/ -class RtMidiError : public std::exception+class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception { public: //! Defined RtMidiError types.@@ -79,8 +94,9 @@ }; //! The constructor.- RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() : message_(message), type_(type) {}- + RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw()+ : message_(message), type_(type) {}+ //! The destructor. virtual ~RtMidiError( void ) throw() {} @@ -88,10 +104,10 @@ virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } //! Returns the thrown error message type.- virtual const Type& getType(void) const throw() { return type_; }+ virtual const Type& getType( void ) const throw() { return type_; } //! Returns the thrown error message string.- virtual const std::string& getMessage(void) const throw() { return message_; }+ virtual const std::string& getMessage( void ) const throw() { return message_; } //! Returns the thrown error message as a c-style string. virtual const char* what( void ) const throw() { return message_.c_str(); }@@ -113,18 +129,18 @@ class MidiApi; -class RtMidi+class RTMIDI_DLL_PUBLIC RtMidi { public:- //! MIDI API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */- MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */+ MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */- RTMIDI_DUMMY /*!< A compilable but non-functional API. */+ RTMIDI_DUMMY, /*!< A compilable but non-functional API. */+ NUM_APIS /*!< Number of values in this enum. */ }; //! A static function to determine the current RtMidi version.@@ -138,11 +154,34 @@ */ static void getCompiledApi( std::vector<RtMidi::Api> &apis ) throw(); + //! Return the name of a specified compiled MIDI API.+ /*!+ This obtains a short lower-case name used for identification purposes.+ This value is guaranteed to remain identical across library versions.+ If the API is unknown, this function will return the empty string.+ */+ static std::string getApiName( RtMidi::Api api );++ //! Return the display name of a specified compiled MIDI API.+ /*!+ This obtains a long name used for display purposes.+ If the API is unknown, this function will return the empty string.+ */+ static std::string getApiDisplayName( RtMidi::Api api );++ //! Return the compiled MIDI API having the given name.+ /*!+ A case insensitive comparison will check the specified name+ against the list of compiled APIs, and return the one which+ matches. On failure, the function returns UNSPECIFIED.+ */+ static RtMidi::Api getCompiledApiByName( const std::string &name );+ //! Pure virtual openPort() function.- virtual void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi" ) ) = 0;+ virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0; //! Pure virtual openVirtualPort() function.- virtual void openVirtualPort( const std::string portName = std::string( "RtMidi" ) ) = 0;+ virtual void openVirtualPort( const std::string &portName = std::string( "RtMidi" ) ) = 0; //! Pure virtual getPortCount() function. virtual unsigned int getPortCount() = 0;@@ -153,7 +192,14 @@ //! Pure virtual closePort() function. virtual void closePort( void ) = 0; + void setClientName( const std::string &clientName );+ void setPortName( const std::string &portName );+ //! Returns true if a port is open and false if not.+ /*!+ Note that this only applies to connections made with the openPort()+ function, not to virtual ports.+ */ virtual bool isPortOpen( void ) const = 0; //! Set an error callback function to be invoked when an error has occured.@@ -164,10 +210,8 @@ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; protected:- RtMidi(); virtual ~RtMidi();- MidiApi *rtapi_; }; @@ -185,7 +229,7 @@ possible to open a virtual input port to which other MIDI software clients can connect. - by Gary P. Scavone, 2003-2014.+ by Gary P. Scavone, 2003-2017. */ /**********************************************************************/ @@ -203,12 +247,12 @@ // // **************************************************************** // -class RtMidiIn : public RtMidi+class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi { public: //! User callback function type definition.- typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData);+ typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData ); //! Default constructor that allows an optional api, client name and queue size. /*!@@ -229,7 +273,7 @@ \param queueSizeLimit An optional size of the MIDI input queue can be specified. */ RtMidiIn( RtMidi::Api api=UNSPECIFIED,- const std::string clientName = std::string( "RtMidi Input Client"),+ const std::string& clientName = "RtMidi Input Client", unsigned int queueSizeLimit = 100 ); //! If a MIDI connection is still open, it will be closed by the destructor.@@ -244,7 +288,7 @@ Otherwise, the default or first port found is opened. \param portName An optional name for the application port that is used to connect to portId can be specified. */- void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Input" ) );+ void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Input" ) ); //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). /*!@@ -256,7 +300,7 @@ \param portName An optional name for the application port that is used to connect to portId can be specified. */- void openVirtualPort( const std::string portName = std::string( "RtMidi Input" ) );+ void openVirtualPort( const std::string &portName = std::string( "RtMidi Input" ) ); //! Set a callback function to be invoked for incoming MIDI messages. /*!@@ -282,6 +326,10 @@ void closePort( void ); //! Returns true if a port is open and false if not.+ /*!+ Note that this only applies to connections made with the openPort()+ function, not to virtual ports.+ */ virtual bool isPortOpen() const; //! Return the number of available MIDI input ports.@@ -293,7 +341,8 @@ //! Return a string identifier for the specified MIDI input port number. /*! \return The name of the port with the given Id is returned.- \retval An empty string is returned if an invalid port specifier is provided.+ \retval An empty string is returned if an invalid port specifier+ is provided. User code should assume a UTF-8 encoding. */ std::string getPortName( unsigned int portNumber = 0 ); @@ -325,8 +374,7 @@ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); protected:- void openMidiApi( RtMidi::Api api, const std::string clientName, unsigned int queueSizeLimit );-+ void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ); }; /**********************************************************************/@@ -341,14 +389,13 @@ OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a virtual port to which other MIDI software clients can connect. - by Gary P. Scavone, 2003-2014.+ by Gary P. Scavone, 2003-2017. */ /**********************************************************************/ -class RtMidiOut : public RtMidi+class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi { public:- //! Default constructor that allows an optional client name. /*! An exception will be thrown if a MIDI system initialization error occurs.@@ -358,7 +405,7 @@ JACK (OS-X). */ RtMidiOut( RtMidi::Api api=UNSPECIFIED,- const std::string clientName = std::string( "RtMidi Output Client") );+ const std::string& clientName = "RtMidi Output Client" ); //! The destructor closes any open MIDI connections. ~RtMidiOut( void ) throw();@@ -373,12 +420,16 @@ exception is thrown if an error occurs while attempting to make the port connection. */- void openPort( unsigned int portNumber = 0, const std::string portName = std::string( "RtMidi Output" ) );+ void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Output" ) ); //! Close an open MIDI connection (if one exists). void closePort( void ); //! Returns true if a port is open and false if not.+ /*!+ Note that this only applies to connections made with the openPort()+ function, not to virtual ports.+ */ virtual bool isPortOpen() const; //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only).@@ -390,14 +441,16 @@ An exception is thrown if an error occurs while attempting to create the virtual port. */- void openVirtualPort( const std::string portName = std::string( "RtMidi Output" ) );+ void openVirtualPort( const std::string &portName = std::string( "RtMidi Output" ) ); //! Return the number of available MIDI output ports. unsigned int getPortCount( void ); //! Return a string identifier for the specified MIDI port type and number. /*!- An empty string is returned if an invalid port specifier is provided.+ \return The name of the port with the given Id is returned.+ \retval An empty string is returned if an invalid port specifier+ is provided. User code should assume a UTF-8 encoding. */ std::string getPortName( unsigned int portNumber = 0 ); @@ -406,8 +459,18 @@ An exception is thrown if an error occurs during output or an output connection was not previously established. */- void sendMessage( std::vector<unsigned char> *message );+ void sendMessage( const std::vector<unsigned char> *message ); + //! Immediately send a single message out an open MIDI output port.+ /*!+ An exception is thrown if an error occurs during output or an+ output connection was not previously established.++ \param message A pointer to the MIDI message as raw bytes+ \param size Length of the MIDI message in bytes+ */+ void sendMessage( const unsigned char *message, size_t size );+ //! Set an error callback function to be invoked when an error has occured. /*! The callback function will be called whenever an error has occured. It is best@@ -416,7 +479,7 @@ virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); protected:- void openMidiApi( RtMidi::Api api, const std::string clientName );+ void openMidiApi( RtMidi::Api api, const std::string &clientName ); }; @@ -433,16 +496,18 @@ // // **************************************************************** // -class MidiApi+class RTMIDI_DLL_PUBLIC MidiApi { public: MidiApi(); virtual ~MidiApi(); virtual RtMidi::Api getCurrentApi( void ) = 0;- virtual void openPort( unsigned int portNumber, const std::string portName ) = 0;- virtual void openVirtualPort( const std::string portName ) = 0;+ virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0;+ virtual void openVirtualPort( const std::string &portName ) = 0; virtual void closePort( void ) = 0;+ virtual void setClientName( const std::string &clientName ) = 0;+ virtual void setPortName( const std::string &portName ) = 0; virtual unsigned int getPortCount( void ) = 0; virtual std::string getPortName( unsigned int portNumber ) = 0;@@ -464,7 +529,7 @@ void *errorCallbackUserData_; }; -class MidiInApi : public MidiApi+class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi { public: @@ -477,25 +542,29 @@ // A MIDI structure used internally by the class to store incoming // messages. Each message represents one and only one MIDI message.- struct MidiMessage { - std::vector<unsigned char> bytes; + struct MidiMessage {+ std::vector<unsigned char> bytes;++ //! Time in seconds elapsed since the previous message double timeStamp; // Default constructor.- MidiMessage()- :bytes(0), timeStamp(0.0) {}+ MidiMessage()+ : bytes(0), timeStamp(0.0) {} }; struct MidiQueue { unsigned int front; unsigned int back;- unsigned int size; unsigned int ringSize; MidiMessage *ring; // Default constructor.- MidiQueue()- :front(0), back(0), size(0), ringSize(0) {}+ MidiQueue()+ : front(0), back(0), ringSize(0), ring(0) {}+ bool push( const MidiMessage& );+ bool pop( std::vector<unsigned char>*, double* );+ unsigned int size( unsigned int *back=0, unsigned int *front=0 ); }; // The RtMidiInData structure is used to pass private class data to@@ -513,23 +582,22 @@ bool continueSysex; // Default constructor.- RtMidiInData()- : ignoreFlags(7), doInput(false), firstMessage(true),- apiData(0), usingCallback(false), userCallback(0), userData(0),- continueSysex(false) {}+ RtMidiInData()+ : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false),+ userCallback(0), userData(0), continueSysex(false) {} }; protected: RtMidiInData inputData_; }; -class MidiOutApi : public MidiApi+class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi { public: MidiOutApi( void ); virtual ~MidiOutApi( void );- virtual void sendMessage( std::vector<unsigned char> *message ) = 0;+ virtual void sendMessage( const unsigned char *message, size_t size ) = 0; }; // **************************************************************** //@@ -539,225 +607,27 @@ // **************************************************************** // inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }-inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }-inline void RtMidiIn :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }+inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); }+inline void RtMidiIn :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); }-inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { ((MidiInApi *)rtapi_)->setCallback( callback, userData ); }-inline void RtMidiIn :: cancelCallback( void ) { ((MidiInApi *)rtapi_)->cancelCallback(); }+inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { static_cast<MidiInApi *>(rtapi_)->setCallback( callback, userData ); }+inline void RtMidiIn :: cancelCallback( void ) { static_cast<MidiInApi *>(rtapi_)->cancelCallback(); } inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }-inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { ((MidiInApi *)rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); }-inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return ((MidiInApi *)rtapi_)->getMessage( message ); }+inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { static_cast<MidiInApi *>(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); }+inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return static_cast<MidiInApi *>(rtapi_)->getMessage( message ); } inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); }-inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string portName ) { rtapi_->openPort( portNumber, portName ); }-inline void RtMidiOut :: openVirtualPort( const std::string portName ) { rtapi_->openVirtualPort( portName ); }+inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); }+inline void RtMidiOut :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); }-inline void RtMidiOut :: sendMessage( std::vector<unsigned char> *message ) { ((MidiOutApi *)rtapi_)->sendMessage( message ); }+inline void RtMidiOut :: sendMessage( const std::vector<unsigned char> *message ) { static_cast<MidiOutApi *>(rtapi_)->sendMessage( &message->at(0), message->size() ); }+inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { static_cast<MidiOutApi *>(rtapi_)->sendMessage( message, size ); } inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); }--// **************************************************************** //-//-// MidiInApi and MidiOutApi subclass prototypes.-//-// **************************************************************** //--#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__)- #define __RTMIDI_DUMMY__-#endif--#if defined(__MACOSX_CORE__)--class MidiInCore: public MidiInApi-{- public:- MidiInCore( const std::string clientName, unsigned int queueSizeLimit );- ~MidiInCore( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );-- protected:- void initialize( const std::string& clientName );-};--class MidiOutCore: public MidiOutApi-{- public:- MidiOutCore( const std::string clientName );- ~MidiOutCore( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );- void sendMessage( std::vector<unsigned char> *message );-- protected:- void initialize( const std::string& clientName );-};--#endif--#if defined(__UNIX_JACK__)--class MidiInJack: public MidiInApi-{- public:- MidiInJack( const std::string clientName, unsigned int queueSizeLimit );- ~MidiInJack( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );-- protected:- std::string clientName;-- void connect( void );- void initialize( const std::string& clientName );-};--class MidiOutJack: public MidiOutApi-{- public:- MidiOutJack( const std::string clientName );- ~MidiOutJack( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );- void sendMessage( std::vector<unsigned char> *message );-- protected:- std::string clientName;-- void connect( void );- void initialize( const std::string& clientName );-};--#endif--#if defined(__LINUX_ALSA__)--class MidiInAlsa: public MidiInApi-{- public:- MidiInAlsa( const std::string clientName, unsigned int queueSizeLimit );- ~MidiInAlsa( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );-- protected:- void initialize( const std::string& clientName );-};--class MidiOutAlsa: public MidiOutApi-{- public:- MidiOutAlsa( const std::string clientName );- ~MidiOutAlsa( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );- void sendMessage( std::vector<unsigned char> *message );-- protected:- void initialize( const std::string& clientName );-};--#endif--#if defined(__WINDOWS_MM__)--class MidiInWinMM: public MidiInApi-{- public:- MidiInWinMM( const std::string clientName, unsigned int queueSizeLimit );- ~MidiInWinMM( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );-- protected:- void initialize( const std::string& clientName );-};--class MidiOutWinMM: public MidiOutApi-{- public:- MidiOutWinMM( const std::string clientName );- ~MidiOutWinMM( void );- RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; };- void openPort( unsigned int portNumber, const std::string portName );- void openVirtualPort( const std::string portName );- void closePort( void );- unsigned int getPortCount( void );- std::string getPortName( unsigned int portNumber );- void sendMessage( std::vector<unsigned char> *message );-- protected:- void initialize( const std::string& clientName );-};--#endif--#if defined(__RTMIDI_DUMMY__)--class MidiInDummy: public MidiInApi-{- public:- MidiInDummy( const std::string /*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }- RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }- void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}- void openVirtualPort( const std::string /*portName*/ ) {}- void closePort( void ) {}- unsigned int getPortCount( void ) { return 0; }- std::string getPortName( unsigned int portNumber ) { return ""; }-- protected:- void initialize( const std::string& /*clientName*/ ) {}-};--class MidiOutDummy: public MidiOutApi-{- public:- MidiOutDummy( const std::string /*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); }- RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; }- void openPort( unsigned int /*portNumber*/, const std::string /*portName*/ ) {}- void openVirtualPort( const std::string /*portName*/ ) {}- void closePort( void ) {}- unsigned int getPortCount( void ) { return 0; }- std::string getPortName( unsigned int /*portNumber*/ ) { return ""; }- void sendMessage( std::vector<unsigned char> * /*message*/ ) {}-- protected:- void initialize( const std::string& /*clientName*/ ) {}-};--#endif #endif
rtmidi/rtmidi_c.cpp view
@@ -3,46 +3,87 @@ #include "rtmidi_c.h" #include "RtMidi.h" -/* misc */-int rtmidi_sizeof_rtmidi_api ()+/* Compile-time assertions that will break if the enums are changed in+ * the future without synchronizing them properly. If you get (g++)+ * "error: ‘StaticAssert<b>::StaticAssert() [with bool b = false]’ is+ * private within this context", it means enums are not aligned. */+template<bool b> class StaticAssert { private: StaticAssert() {} };+template<> class StaticAssert<true>{ public: StaticAssert() {} };+#define ENUM_EQUAL(x,y) StaticAssert<(int)x==(int)y>()+class StaticAssertions { StaticAssertions() {+ ENUM_EQUAL( RTMIDI_API_UNSPECIFIED, RtMidi::UNSPECIFIED );+ ENUM_EQUAL( RTMIDI_API_MACOSX_CORE, RtMidi::MACOSX_CORE );+ ENUM_EQUAL( RTMIDI_API_LINUX_ALSA, RtMidi::LINUX_ALSA );+ ENUM_EQUAL( RTMIDI_API_UNIX_JACK, RtMidi::UNIX_JACK );+ ENUM_EQUAL( RTMIDI_API_WINDOWS_MM, RtMidi::WINDOWS_MM );+ ENUM_EQUAL( RTMIDI_API_RTMIDI_DUMMY, RtMidi::RTMIDI_DUMMY );++ ENUM_EQUAL( RTMIDI_ERROR_WARNING, RtMidiError::WARNING );+ ENUM_EQUAL( RTMIDI_ERROR_DEBUG_WARNING, RtMidiError::DEBUG_WARNING );+ ENUM_EQUAL( RTMIDI_ERROR_UNSPECIFIED, RtMidiError::UNSPECIFIED );+ ENUM_EQUAL( RTMIDI_ERROR_NO_DEVICES_FOUND, RtMidiError::NO_DEVICES_FOUND );+ ENUM_EQUAL( RTMIDI_ERROR_INVALID_DEVICE, RtMidiError::INVALID_DEVICE );+ ENUM_EQUAL( RTMIDI_ERROR_MEMORY_ERROR, RtMidiError::MEMORY_ERROR );+ ENUM_EQUAL( RTMIDI_ERROR_INVALID_PARAMETER, RtMidiError::INVALID_PARAMETER );+ ENUM_EQUAL( RTMIDI_ERROR_INVALID_USE, RtMidiError::INVALID_USE );+ ENUM_EQUAL( RTMIDI_ERROR_DRIVER_ERROR, RtMidiError::DRIVER_ERROR );+ ENUM_EQUAL( RTMIDI_ERROR_SYSTEM_ERROR, RtMidiError::SYSTEM_ERROR );+ ENUM_EQUAL( RTMIDI_ERROR_THREAD_ERROR, RtMidiError::THREAD_ERROR );+}};++class CallbackProxyUserData {- return sizeof (RtMidiApi);-}+ public:+ CallbackProxyUserData (RtMidiCCallback cCallback, void *userData)+ : c_callback (cCallback), user_data (userData)+ {+ }+ RtMidiCCallback c_callback;+ void *user_data;+}; +extern "C" const enum RtMidiApi rtmidi_compiled_apis[]; // casting from RtMidi::Api[]+extern "C" const unsigned int rtmidi_num_compiled_apis;+ /* RtMidi API */-int rtmidi_get_compiled_api (enum RtMidiApi **apis) // return length for NULL argument.+int rtmidi_get_compiled_api (enum RtMidiApi *apis, unsigned int apis_size) {- if (!apis || !(*apis)) {- std::vector<RtMidi::Api> *v = new std::vector<RtMidi::Api> ();- try {- RtMidi::getCompiledApi (*v);- int size = v->size ();- delete v;- return size;- } catch (...) {- return -1;- }- } else {- try {- std::vector<RtMidi::Api> *v = new std::vector<RtMidi::Api> ();- RtMidi::getCompiledApi (*v);- for (unsigned int i = 0; i < v->size (); i++)- (*apis) [i] = (RtMidiApi) v->at (i);- delete v;- return 0;- } catch (...) {- return -1;- }- }+ unsigned num = rtmidi_num_compiled_apis;+ if (apis) {+ num = (num < apis_size) ? num : apis_size;+ memcpy(apis, rtmidi_compiled_apis, num * sizeof(enum RtMidiApi));+ }+ return (int)num; } -void rtmidi_error (RtMidiPtr device, enum RtMidiErrorType type, const char* errorString)+extern "C" const char* rtmidi_api_names[][2];+const char *rtmidi_api_name(enum RtMidiApi api) {+ if (api < 0 || api >= RTMIDI_API_NUM)+ return NULL;+ return rtmidi_api_names[api][0];+}++const char *rtmidi_api_display_name(enum RtMidiApi api) {- std::string msg = errorString;- ((MidiApi*) device->ptr)->error ((RtMidiError::Type) type, msg);+ if (api < 0 || api >= RTMIDI_API_NUM)+ return "Unknown";+ return rtmidi_api_names[api][1]; } +enum RtMidiApi rtmidi_compiled_api_by_name(const char *name) {+ RtMidi::Api api = RtMidi::UNSPECIFIED;+ if (name) {+ api = RtMidi::getCompiledApiByName(name);+ }+ return (enum RtMidiApi)api;+} +void rtmidi_error (MidiApi *api, enum RtMidiErrorType type, const char* errorString)+{+ std::string msg = errorString;+ api->error ((RtMidiError::Type) type, msg);+}+ void rtmidi_open_port (RtMidiPtr device, unsigned int portNumber, const char *portName) { std::string name = portName;@@ -113,11 +154,13 @@ RtMidiIn* rIn = new RtMidiIn (); wrp->ptr = (void*) rIn;+ wrp->data = 0; wrp->ok = true; wrp->msg = ""; } catch (const RtMidiError & err) { wrp->ptr = 0;+ wrp->data = 0; wrp->ok = false; wrp->msg = err.what (); }@@ -134,11 +177,13 @@ RtMidiIn* rIn = new RtMidiIn ((RtMidi::Api) api, name, queueSizeLimit); wrp->ptr = (void*) rIn;+ wrp->data = 0; wrp->ok = true; wrp->msg = ""; } catch (const RtMidiError & err) {- wrp->ptr = 0; + wrp->ptr = 0;+ wrp->data = 0; wrp->ok = false; wrp->msg = err.what (); }@@ -148,6 +193,8 @@ void rtmidi_in_free (RtMidiInPtr device) {+ if (device->data)+ delete (CallbackProxyUserData*) device->data; delete (RtMidiIn*) device->ptr; delete device; }@@ -161,21 +208,11 @@ device->ok = false; device->msg = err.what (); - return RT_MIDI_API_UNSPECIFIED;+ return RTMIDI_API_UNSPECIFIED; } } -class CallbackProxyUserData-{- public:- CallbackProxyUserData (RtMidiCCallback cCallback, void *userData)- : c_callback (cCallback), user_data (userData)- {- }- RtMidiCCallback c_callback;- void *user_data;-};-+static void callback_proxy (double timeStamp, std::vector<unsigned char> *message, void *userData) { CallbackProxyUserData* data = reinterpret_cast<CallbackProxyUserData*> (userData);@@ -184,13 +221,14 @@ void rtmidi_in_set_callback (RtMidiInPtr device, RtMidiCCallback callback, void *userData) {+ device->data = (void*) new CallbackProxyUserData (callback, userData); try {- void *data = (void *) new CallbackProxyUserData (callback, userData);- ((RtMidiIn*) device->ptr)->setCallback (callback_proxy, data);- + ((RtMidiIn*) device->ptr)->setCallback (callback_proxy, device->data); } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();+ delete (CallbackProxyUserData*) device->data;+ device->data = 0; } } @@ -198,7 +236,8 @@ { try { ((RtMidiIn*) device->ptr)->cancelCallback ();-+ delete (CallbackProxyUserData*) device->data;+ device->data = 0; } catch (const RtMidiError & err) { device->ok = false; device->msg = err.what ();@@ -211,21 +250,19 @@ } double rtmidi_in_get_message (RtMidiInPtr device, - unsigned char **message, - size_t * size)+ unsigned char *message,+ size_t *size) { try { // FIXME: use allocator to achieve efficient buffering- std::vector<unsigned char> *v = new std::vector<unsigned char> ();- double ret = ((RtMidiIn*) device->ptr)->getMessage (v);- *size = v->size ();+ std::vector<unsigned char> v;+ double ret = ((RtMidiIn*) device->ptr)->getMessage (&v); - if (v->size () > 0) {- *message = (unsigned char *) - malloc (sizeof (unsigned char *) * (int) v->size ());- memcpy (*message, v->data (), (int) v->size ());+ if (v.size () > 0 && v.size() <= *size) {+ memcpy (message, v.data (), (int) v.size ()); }- delete v;++ *size = v.size(); return ret; } catch (const RtMidiError & err) {@@ -249,11 +286,13 @@ RtMidiOut* rOut = new RtMidiOut (); wrp->ptr = (void*) rOut;+ wrp->data = 0; wrp->ok = true; wrp->msg = ""; } catch (const RtMidiError & err) { wrp->ptr = 0;+ wrp->data = 0; wrp->ok = false; wrp->msg = err.what (); }@@ -270,11 +309,13 @@ RtMidiOut* rOut = new RtMidiOut ((RtMidi::Api) api, name); wrp->ptr = (void*) rOut;+ wrp->data = 0; wrp->ok = true; wrp->msg = ""; } catch (const RtMidiError & err) { wrp->ptr = 0;+ wrp->data = 0; wrp->ok = false; wrp->msg = err.what (); }@@ -298,18 +339,14 @@ device->ok = false; device->msg = err.what (); - return RT_MIDI_API_UNSPECIFIED;+ return RTMIDI_API_UNSPECIFIED; } } int rtmidi_out_send_message (RtMidiOutPtr device, const unsigned char *message, int length) { try {- // FIXME: use allocator to achieve efficient buffering- std::vector<unsigned char> *v = new std::vector<unsigned char> (length);- memcpy (v->data (), message, length);- ((RtMidiOut*) device->ptr)->sendMessage (v);- delete v;+ ((RtMidiOut*) device->ptr)->sendMessage (message, length); return 0; } catch (const RtMidiError & err) {
rtmidi/rtmidi_c.h view
@@ -1,13 +1,33 @@+/************************************************************************/+/*! \defgroup C-interface+ @{ + \brief C interface to realtime MIDI input/output C++ classes.++ RtMidi offers a C-style interface, principally for use in binding+ RtMidi to other programming languages. All structs, enums, and+ functions listed here have direct analogs (and simply call to)+ items in the C++ RtMidi class and its supporting classes and+ types+*/+/************************************************************************/++/*!+ \file rtmidi_c.h+ */+ #include <stdbool.h>+#include <stddef.h> #ifndef RTMIDI_C_H #define RTMIDI_C_H -#include <stdlib.h>- #if defined(RTMIDI_EXPORT)+#if defined _WIN32 || defined __CYGWIN__ #define RTMIDIAPI __declspec(dllexport) #else+#define RTMIDIAPI __attribute__((visibility("default")))+#endif+#else #define RTMIDIAPI //__declspec(dllimport) #endif @@ -15,61 +35,206 @@ extern "C" { #endif +//! \brief Wraps an RtMidi object for C function return statuses. struct RtMidiWrapper {+ //! The wrapped RtMidi object. void* ptr;+ void* data;++ //! True when the last function call was OK. bool ok;++ //! If an error occured (ok != true), set to an error message. const char* msg; }; -typedef RtMidiWrapper* RtMidiPtr;-typedef RtMidiWrapper* RtMidiInPtr;-typedef RtMidiWrapper* RtMidiOutPtr;+//! \brief Typedef for a generic RtMidi pointer.+typedef struct RtMidiWrapper* RtMidiPtr; - enum RtMidiApi {- RT_MIDI_API_UNSPECIFIED, /*!< Search for a working compiled API. */- RT_MIDI_API_MACOSX_CORE, /*!< Macintosh OS-X Core Midi API. */- RT_MIDI_API_LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */- RT_MIDI_API_UNIX_JACK, /*!< The Jack Low-Latency MIDI Server API. */- RT_MIDI_API_WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */- RT_MIDI_API_WINDOWS_KS, /*!< The Microsoft Kernel Streaming MIDI API. */- RT_MIDI_API_RTMIDI_DUMMY /*!< A compilable but non-functional API. */- };+//! \brief Typedef for a generic RtMidiIn pointer.+typedef struct RtMidiWrapper* RtMidiInPtr; +//! \brief Typedef for a generic RtMidiOut pointer.+typedef struct RtMidiWrapper* RtMidiOutPtr;++//! \brief MIDI API specifier arguments. See \ref RtMidi::Api.+enum RtMidiApi {+ RTMIDI_API_UNSPECIFIED, /*!< Search for a working compiled API. */+ RTMIDI_API_MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */+ RTMIDI_API_LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */+ RTMIDI_API_UNIX_JACK, /*!< The Jack Low-Latency MIDI Server API. */+ RTMIDI_API_WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */+ RTMIDI_API_RTMIDI_DUMMY, /*!< A compilable but non-functional API. */+ RTMIDI_API_NUM /*!< Number of values in this enum. */+};++//! \brief Defined RtMidiError types. See \ref RtMidiError::Type. enum RtMidiErrorType {- RT_ERROR_WARNING, RT_ERROR_DEBUG_WARNING, RT_ERROR_UNSPECIFIED, RT_ERROR_NO_DEVICES_FOUND,- RT_ERROR_INVALID_DEVICE, RT_ERROR_MEMORY_ERROR, RT_ERROR_INVALID_PARAMETER, RT_ERROR_INVALID_USE,- RT_ERROR_DRIVER_ERROR, RT_ERROR_SYSTEM_ERROR, RT_ERROR_THREAD_ERROR+ RTMIDI_ERROR_WARNING, /*!< A non-critical error. */+ RTMIDI_ERROR_DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */+ RTMIDI_ERROR_UNSPECIFIED, /*!< The default, unspecified error type. */+ RTMIDI_ERROR_NO_DEVICES_FOUND, /*!< No devices found on system. */+ RTMIDI_ERROR_INVALID_DEVICE, /*!< An invalid device ID was specified. */+ RTMIDI_ERROR_MEMORY_ERROR, /*!< An error occured during memory allocation. */+ RTMIDI_ERROR_INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */+ RTMIDI_ERROR_INVALID_USE, /*!< The function was called incorrectly. */+ RTMIDI_ERROR_DRIVER_ERROR, /*!< A system driver error occured. */+ RTMIDI_ERROR_SYSTEM_ERROR, /*!< A system error occured. */+ RTMIDI_ERROR_THREAD_ERROR /*!< A thread error occured. */ }; -typedef void(* RtMidiCCallback) (double timeStamp, const unsigned char* message, unsigned int msg_len, void *userData);+/*! \brief The type of a RtMidi callback function.+ *+ * \param timeStamp The time at which the message has been received.+ * \param message The midi message.+ * \param userData Additional user data for the callback.+ *+ * See \ref RtMidiIn::RtMidiCallback.+ */+typedef void(* RtMidiCCallback) (double timeStamp, const unsigned char* message,+ size_t messageSize, void *userData); -RTMIDIAPI int rtmidi_sizeof_rtmidi_api (); /* RtMidi API */-RTMIDIAPI int rtmidi_get_compiled_api (enum RtMidiApi **apis); // return length for NULL argument.-RTMIDIAPI void rtmidi_error (RtMidiPtr device, enum RtMidiErrorType type, const char* errorString); +/*! \brief Determine the available compiled MIDI APIs.+ *+ * If the given `apis` parameter is null, returns the number of available APIs.+ * Otherwise, fill the given apis array with the RtMidi::Api values.+ *+ * \param apis An array or a null value.+ * \param apis_size Number of elements pointed to by apis+ * \return number of items needed for apis array if apis==NULL, or+ * number of items written to apis array otherwise. A negative+ * return value indicates an error.+ *+ * See \ref RtMidi::getCompiledApi().+*/+RTMIDIAPI int rtmidi_get_compiled_api (enum RtMidiApi *apis, unsigned int apis_size);++//! \brief Return the name of a specified compiled MIDI API.+//! See \ref RtMidi::getApiName().+RTMIDIAPI const char *rtmidi_api_name(enum RtMidiApi api);++//! \brief Return the display name of a specified compiled MIDI API.+//! See \ref RtMidi::getApiDisplayName().+RTMIDIAPI const char *rtmidi_api_display_name(enum RtMidiApi api);++//! \brief Return the compiled MIDI API having the given name.+//! See \ref RtMidi::getCompiledApiByName().+RTMIDIAPI enum RtMidiApi rtmidi_compiled_api_by_name(const char *name);++//! \internal Report an error.+RTMIDIAPI void rtmidi_error (enum RtMidiErrorType type, const char* errorString);++/*! \brief Open a MIDI port.+ *+ * \param port Must be greater than 0+ * \param portName Name for the application port.+ *+ * See RtMidi::openPort().+ */ RTMIDIAPI void rtmidi_open_port (RtMidiPtr device, unsigned int portNumber, const char *portName);++/*! \brief Creates a virtual MIDI port to which other software applications can + * connect. + *+ * \param portName Name for the application port.+ *+ * See RtMidi::openVirtualPort().+ */ RTMIDIAPI void rtmidi_open_virtual_port (RtMidiPtr device, const char *portName);++/*! \brief Close a MIDI connection.+ * See RtMidi::closePort().+ */ RTMIDIAPI void rtmidi_close_port (RtMidiPtr device);++/*! \brief Return the number of available MIDI ports.+ * See RtMidi::getPortCount().+ */ RTMIDIAPI unsigned int rtmidi_get_port_count (RtMidiPtr device);++/*! \brief Return a string identifier for the specified MIDI input port number.+ * See RtMidi::getPortName().+ */ RTMIDIAPI const char* rtmidi_get_port_name (RtMidiPtr device, unsigned int portNumber); /* RtMidiIn API */-RTMIDIAPI RtMidiInPtr rtmidi_in_create_default ();++//! \brief Create a default RtMidiInPtr value, with no initialization.+RTMIDIAPI RtMidiInPtr rtmidi_in_create_default (void);++/*! \brief Create a RtMidiInPtr value, with given api, clientName and queueSizeLimit.+ *+ * \param api An optional API id can be specified.+ * \param clientName An optional client name can be specified. This+ * will be used to group the ports that are created+ * by the application.+ * \param queueSizeLimit An optional size of the MIDI input queue can be+ * specified.+ *+ * See RtMidiIn::RtMidiIn().+ */ RTMIDIAPI RtMidiInPtr rtmidi_in_create (enum RtMidiApi api, const char *clientName, unsigned int queueSizeLimit);++//! \brief Free the given RtMidiInPtr. RTMIDIAPI void rtmidi_in_free (RtMidiInPtr device);++//! \brief Returns the MIDI API specifier for the given instance of RtMidiIn.+//! See \ref RtMidiIn::getCurrentApi(). RTMIDIAPI enum RtMidiApi rtmidi_in_get_current_api (RtMidiPtr device);++//! \brief Set a callback function to be invoked for incoming MIDI messages.+//! See \ref RtMidiIn::setCallback(). RTMIDIAPI void rtmidi_in_set_callback (RtMidiInPtr device, RtMidiCCallback callback, void *userData);++//! \brief Cancel use of the current callback function (if one exists).+//! See \ref RtMidiIn::cancelCallback(). RTMIDIAPI void rtmidi_in_cancel_callback (RtMidiInPtr device);++//! \brief Specify whether certain MIDI message types should be queued or ignored during input.+//! See \ref RtMidiIn::ignoreTypes(). RTMIDIAPI void rtmidi_in_ignore_types (RtMidiInPtr device, bool midiSysex, bool midiTime, bool midiSense);-RTMIDIAPI double rtmidi_in_get_message (RtMidiInPtr device, unsigned char **message, size_t * size); +/*! Fill the user-provided array with the data bytes for the next available+ * MIDI message in the input queue and return the event delta-time in seconds.+ *+ * \param message Must point to a char* that is already allocated.+ * SYSEX messages maximum size being 1024, a statically+ * allocated array could+ * be sufficient. + * \param size Is used to return the size of the message obtained. + *+ * See RtMidiIn::getMessage().+ */+RTMIDIAPI double rtmidi_in_get_message (RtMidiInPtr device, unsigned char *message, size_t *size);+ /* RtMidiOut API */-RTMIDIAPI RtMidiOutPtr rtmidi_out_create_default ();++//! \brief Create a default RtMidiInPtr value, with no initialization.+RTMIDIAPI RtMidiOutPtr rtmidi_out_create_default (void);++/*! \brief Create a RtMidiOutPtr value, with given and clientName.+ *+ * \param api An optional API id can be specified.+ * \param clientName An optional client name can be specified. This+ * will be used to group the ports that are created+ * by the application.+ *+ * See RtMidiOut::RtMidiOut().+ */ RTMIDIAPI RtMidiOutPtr rtmidi_out_create (enum RtMidiApi api, const char *clientName);++//! \brief Free the given RtMidiOutPtr. RTMIDIAPI void rtmidi_out_free (RtMidiOutPtr device);++//! \brief Returns the MIDI API specifier for the given instance of RtMidiOut.+//! See \ref RtMidiOut::getCurrentApi(). RTMIDIAPI enum RtMidiApi rtmidi_out_get_current_api (RtMidiPtr device);++//! \brief Immediately send a single message out an open MIDI output port.+//! See \ref RtMidiOut::sendMessage(). RTMIDIAPI int rtmidi_out_send_message (RtMidiOutPtr device, const unsigned char *message, int length); @@ -77,3 +242,5 @@ } #endif #endif++/*! }@ */
+ test/Main.hs view
@@ -0,0 +1,51 @@+module Main (main) where++import Control.Concurrent (threadDelay)+import Control.Monad (replicateM_)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef)+import Data.List (isInfixOf)+import Data.Word (Word8)+import Sound.RtMidi (closeDevice, closePort, defaultInput, defaultOutput, findPort, sendMessage, setCallback, openPort, openVirtualPort)+import Test.Tasty (TestTree, defaultMain, testGroup)+import Test.Tasty.HUnit (testCase, (@?=))++incIORef :: IORef Int -> IO ()+incIORef = flip modifyIORef succ++readerCallback :: IORef Int -> Double -> [Word8] -> IO ()+readerCallback countRef _ msg = incIORef countRef++testVirtualReadWrite :: TestTree+testVirtualReadWrite = testCase "virtual read write" $ do+ let expectedCount = 3+ -- a simple note-on message+ message = [0x90, 0x51, 0x7f]+ portName = "rtmidi-test-port"+ -- 0.01 second delay in us+ delayUs = 10000+ countRef <- newIORef 0+ -- Create reader with callback+ inDev <- defaultInput+ setCallback inDev (readerCallback countRef)+ openVirtualPort inDev portName+ -- Create writer and connect to reader virtual port+ outDev <- defaultOutput+ maybePortNum <- findPort outDev (isInfixOf portName)+ let portNum = maybe (error "Could not find port") id maybePortNum+ openPort outDev portNum portName+ -- Send messages+ replicateM_ expectedCount (sendMessage outDev message)+ -- Sleep a bit to ensure message delivery+ threadDelay delayUs+ -- Close writer+ closePort outDev+ closeDevice outDev+ -- Close reader+ closePort inDev+ closeDevice inDev+ -- Verify number of messages received+ actualCount <- readIORef countRef+ actualCount @?= expectedCount++main :: IO ()+main = defaultMain (testGroup "RtMidi" [testVirtualReadWrite])