midi-utils-0.1.0.0: src/Sound/MIDI/Utils/Run.hs
module Sound.MIDI.Utils.Run
( playMidi
, playMidiFile
, listAvailableUsers
, User(userPort, userClientName, userPortName)
, Port, mkPort
) where
import Control.Monad ((>=>))
import Data.Function (on)
import System.Process
import qualified System.IO as SIO
import qualified Data.ByteString as BS
import qualified Text.Parsec as Ps
import qualified Text.Parsec.Token as Ps
import qualified Sound.MIDI.File.Save as Save
import qualified Sound.MIDI.File.Load as Load
import Sound.MIDI.File (T)
newtype Port = Port { getPort :: (Int, Int) }
deriving (Eq)
mkPort :: (Int, Int) -> Port
mkPort = Port
instance Show Port where
show (Port (c, p)) = concat [show c, ":", show p]
data User = User { userPort :: Port
, userClientName :: String
, userPortName :: String
} deriving (Show, Eq)
data PlayMidi = PlayMidi { midiPort :: Port, midiFile :: FilePath }
toProcess :: PlayMidi -> CreateProcess
toProcess (PlayMidi { midiPort = p, midiFile = f }) = shell $ concat ["aplaymidi", " -p ", show p, " ", f]
-- | Play MIDI data to the specified ALSA sequencer port.
playMidi :: Port -> T -> IO ()
playMidi p m = do
(b1,b2) <- createPipe
SIO.hSetBinaryMode b2 True
BS.hPutStr b2 midiBS
createProcess ((midiProc "-") { std_in=UseHandle b1 })
pure ()
where midiBS = BS.pack $ Save.toByteList m
midiProc f = toProcess (PlayMidi p f)
playMidiFile :: Port -> FilePath -> IO ()
playMidiFile p = Load.fromFile >=> playMidi p
availableUsers :: IO (Either String [User])
availableUsers = (checkParse <$> runParse <$> usersRaw)
where usersRaw = readProcess "aplaymidi" ["-l"] ""
parsePort = ((curry Port `on` fromInteger) <$> decimal <*> (Ps.char ':' *> decimal))
parseCname = Ps.manyTill Ps.anyChar (Ps.try (Ps.space *> Ps.space))
parsePname = Ps.manyTill Ps.anyChar (Ps.try Ps.newline)
parseUser = User
<$> Ps.between Ps.spaces Ps.spaces parsePort
<*> (parseCname <* Ps.spaces) <*> parsePname
parseUsers = Ps.manyTill Ps.anyChar Ps.newline *> Ps.many parseUser
runParse = Ps.parse parseUsers ""
checkParse (Left _) = Left "Failed to parse users"
checkParse (Right r) = Right r
decimal = read <$> Ps.many1 Ps.digit
listAvailableUsers :: IO [User]
listAvailableUsers = either (const []) id <$> availableUsers