packages feed

libremidi-0.4.2: src/Libremidi/Simple.hs

{-# LANGUAGE OverloadedStrings #-}

-- | High-level operations.
module Libremidi.Simple where

import Control.Monad (when)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (ReaderT (..), ask)
import Data.Default (def)
import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)
import Data.Maybe (isNothing)
import Data.Sequence (Seq (..))
import Data.Text (Text)
import Data.Text.IO qualified as TIO
import Foreign.ForeignPtr (ForeignPtr)
import Foreign.Ptr (Ptr)
import Libremidi.Api qualified as LMA
import Libremidi.Common (ErrM, newUniquePtr, rethrowErrM)
import System.IO qualified as SIO

type MidiM = ReaderT LMA.LogFun IO

runMidiM :: MidiM a -> LMA.LogFun -> IO a
runMidiM = runReaderT

nullLogFun :: LMA.LogFun
nullLogFun _ _ = pure ()

stderrLogFun :: LMA.LogFun
stderrLogFun lvl msg =
  let prefix = case lvl of
        LMA.LogLvlWarn -> "[WARN] "
        LMA.LogLvlErr -> "[ERR ] "
  in  TIO.hPutStrLn SIO.stderr (prefix <> msg)

newObsHandle :: MidiM LMA.ObsHandle
newObsHandle = do
  lf <- ask
  liftIO $ do
    oc <- LMA.setObsLogCb lf def
    rethrowErrM (LMA.newObsHandle LMA.ApiUnspecified oc)

listEnumFun
  :: (Ptr p -> ErrM Text)
  -> (Ptr p -> ErrM (ForeignPtr p))
  -> IORef (Seq (Text, ForeignPtr p))
  -> LMA.EnumFun p
listEnumFun name clone r p = do
  n <- rethrowErrM (name p)
  fp <- rethrowErrM (clone p)
  modifyIORef' r (:|> (n, fp))

listPorts
  :: (Ptr p -> ErrM Text)
  -> (Ptr p -> ErrM (ForeignPtr p))
  -> (LMA.ObsHandle -> LMA.EnumFun p -> ErrM ())
  -> MidiM (Seq (Text, ForeignPtr p))
listPorts name clone list = do
  r <- liftIO (newIORef Empty)
  let f = listEnumFun name clone r
  foh <- newObsHandle
  liftIO $ do
    rethrowErrM (list foh f)
    readIORef r

listInPorts :: MidiM (Seq (Text, LMA.InPort))
listInPorts = listPorts LMA.inPortName' LMA.cloneInPort' LMA.enumInPorts

listOutPorts :: MidiM (Seq (Text, LMA.OutPort))
listOutPorts = listPorts LMA.outPortName' LMA.cloneOutPort' LMA.enumOutPorts

findEnumFun
  :: (Ptr p -> ErrM Text)
  -> (Ptr p -> ErrM (ForeignPtr p))
  -> (Text -> Bool)
  -> IORef (Maybe (Text, ForeignPtr p))
  -> LMA.EnumFun p
findEnumFun name clone sel r p = do
  missing <- fmap isNothing (readIORef r)
  when missing $ do
    n <- rethrowErrM (name p)
    when (sel n) $ do
      fp <- rethrowErrM (clone p)
      writeIORef r (Just (n, fp))

findPort
  :: (Ptr p -> ErrM Text)
  -> (Ptr p -> ErrM (ForeignPtr p))
  -> (LMA.ObsHandle -> LMA.EnumFun p -> ErrM ())
  -> (Text -> Bool)
  -> MidiM (Maybe (Text, ForeignPtr p))
findPort name clone list sel = do
  r <- liftIO (newIORef Nothing)
  let f = findEnumFun name clone sel r
  foh <- newObsHandle
  liftIO $ do
    rethrowErrM (list foh f)
    readIORef r

findInPort :: (Text -> Bool) -> MidiM (Maybe (Text, LMA.InPort))
findInPort = findPort LMA.inPortName' LMA.cloneInPort' LMA.enumInPorts

findOutPort :: (Text -> Bool) -> MidiM (Maybe (Text, LMA.OutPort))
findOutPort = findPort LMA.outPortName' LMA.cloneOutPort' LMA.enumOutPorts

openOutPort :: LMA.OutPort -> MidiM LMA.OutHandle
openOutPort op = do
  lf <- ask
  liftIO $ rethrowErrM $ do
    op' <- LMA.cloneOutPort op >>= liftIO . newUniquePtr
    let mc = def {LMA.mcPort = Just (LMA.MidiPortOut op')}
    mc' <- liftIO (LMA.setMidiLogCb lf mc)
    LMA.newOutHandle LMA.ApiUnspecified mc'

listAvailFun :: IORef (Seq LMA.Api) -> LMA.AvailFun
listAvailFun r = maybe (pure ()) (modifyIORef' r . flip (:|>))

listAvailApis1 :: IO (Seq LMA.Api)
listAvailApis1 = do
  r <- newIORef Empty
  LMA.availApis1 (listAvailFun r)
  readIORef r

listAvailApis2 :: IO (Seq LMA.Api)
listAvailApis2 = do
  r <- newIORef Empty
  LMA.availApis2 (listAvailFun r)
  readIORef r