PortMidi-simple-0.1.0.0: src/Sound/PortMidi/SysEx.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
module Sound.PortMidi.SysEx where
import Data.Char (chr)
import Data.Word (Word8)
import Data.Either (partitionEithers)
import qualified Sound.PortMidi as Midi
import Sound.PortMidi.Simple (Timestamp, Message(..), midi_, waitInput)
data Handle = Handle
{ sysexOut :: Midi.PMStream
, sysexIn :: Midi.PMStream
, sysexReader :: IO [(Timestamp, Message)]
, sysexRate :: Int
}
class Encode a where
encode :: a -> [Word8]
instance Encode [Word8] where
encode = id
class Decode a where
type DecodeError a
decode :: [Word8] -> Either (DecodeError a) a
instance Decode [Word8] where
type DecodeError [Word8] = ()
decode = Right
type Messages = [(Timestamp, Message)]
type Results res = [(Timestamp, Either (DecodeError res) res)]
{- AKA @unsafePerformWhateverIsNeeded@
XXX: Access to streams should be externally synchronized.
-}
call
:: (Encode req, Decode res)
=> Handle
-> req
-> IO (Messages, Results res)
call Handle{..} req = do
time <- Midi.time
midi_ $ Midi.writeSysEx sysexOut time $
map (chr . fromIntegral) (encode req)
waitInput sysexRate sysexIn
messages <- sysexReader
pure $ partitionEithers do
msg@(timestamp, message) <- messages
case message of
SysEx bytes ->
pure . Right $ (timestamp, decode bytes)
_rest ->
pure $ Left msg
-- | Even more unsafe, when you REALLY don't care and just want to
call_
:: (Encode req, Decode res)
=> Handle
-> req
-> IO (Either (Messages, Results res) res)
call_ handle req = do
call handle req >>= \case
([], [(_ts, Right one)]) ->
pure $ Right one
rest ->
pure $ Left rest