RtMidi-0.1.1.0: Sound/RtMidi.hs
{-# LANGUAGE ForeignFunctionInterface #-}
-- | 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
import Control.Monad
import Foreign
import Foreign.C
import Foreign.C.String
data Device = Input (Ptr Wrapper) | Output (Ptr Wrapper)
device :: Device -> Ptr Wrapper
device (Input x) = x
device (Output x) = x
toInput :: Device -> Ptr Wrapper
toInput (Input x) = x
toOutput :: Device -> Ptr Wrapper
toOutput (Output x) = x
data Api
= UnspecifiedApi
| CoreMidiApi
| AlsaApi
| JackApi
| MultimediaApi
| KernelStreamingApi
| DummyApi
deriving (Eq, Show)
instance Enum Api where
fromEnum UnspecifiedApi = 0
fromEnum CoreMidiApi = 1
fromEnum AlsaApi = 2
fromEnum JackApi = 3
fromEnum MultimediaApi = 4
fromEnum DummyApi = 5
toEnum 0 = UnspecifiedApi
toEnum 1 = CoreMidiApi
toEnum 2 = AlsaApi
toEnum 3 = JackApi
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
apiSize :: IO Int
apiSize = fromEnum <$> rtmidi_sizeof_rtmidi_api
-- |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
-- -- |Report an error
-- reportError :: Error -> IO ()
-- reportError (Error e s) = withCString s $ rtmidi_error (toEnum $ fromEnum $ e)
-- |Open a MIDI connection
openPort :: Device
-> 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)
-- |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)
-- |Close an open MIDI connection (if one exists).
closePort :: Device -> IO ()
closePort d = rtmidi_close_port $ device d
-- |Return the number of MIDI ports available to the 'Device'.
portCount :: Device -> IO Int
portCount d = fromIntegral <$> (rtmidi_get_port_count $ device d)
-- |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)
-- |Default constructor for a 'Device' to use for input.
defaultInput :: IO Device
defaultInput = Input <$> rtmidi_in_create_default
-- |Create a new 'Device' to use for input.
createInput :: 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))
foreign import ccall "wrapper"
wrap :: (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
-- |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)
-- |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)
-- |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
-> Bool -- ^ SysEx messages
-> Bool -- ^ Time messages
-> Bool -- ^ Sense messages
-> IO ()
ignoreTypes d sysex time sense = rtmidi_in_ignore_types (toInput d) sysex time sense
-- |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)
-- |Default constructor for a 'Device' to use for output.
defaultOutput :: IO Device
defaultOutput = Output <$> rtmidi_out_create_default
-- |Create a new 'Device' to use for output.
createOutput :: 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
-- |Close any open MIDI connections
closeOutput (Output x) = rtmidi_out_free x
-- |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