{-# LANGUAGE FlexibleContexts, MultiParamTypeClasses #-} -- Contains all the information and functions necessary to run a Jack -- port and exchange information through reactive values and Yampa. module RMCA.Translator.Jack ( jackSetup ) where import Control.Arrow import qualified Control.Monad.Exception.Synchronous as Sync import qualified Control.Monad.Trans.Class as Trans import Data.CBRef import Data.Foldable import qualified Data.IntMap as M import Data.ReactiveValue import qualified Foreign.C.Error as E import Graphics.UI.Gtk import RMCA.IOClockworks import RMCA.ReactiveValueAtomicUpdate import RMCA.Semantics import RMCA.Translator.Message import RMCA.Translator.RV import RMCA.Translator.Translator import qualified Sound.JACK as Jack import qualified Sound.JACK.Exception as JackExc import qualified Sound.JACK.MIDI as JMIDI rmcaName :: String rmcaName = "RMCA" inPortName :: String inPortName = "input" outPortName :: String outPortName = "output" handleErrorJack :: JackExc.All -> IO () handleErrorJack _ = postGUIAsync $ do diag <- messageDialogNewWithMarkup Nothing [] MessageError ButtonsClose "No running instance of Jack could be found!" widgetShow diag resp <- dialogRun diag print resp mainQuit -- Starts a default client with an input and an output port. Doesn't -- do anything as such. jackSetup :: (ReactiveValueAtomicUpdate board (M.IntMap ([Note],[Message])) IO , ReactiveValueRead tempo Tempo IO) => IOTick -> board -> tempo -> IO () jackSetup tc boardQueue tempoRV = Sync.resolveT handleErrorJack $ do toProcessRV <- Trans.lift $ newCBRef [] Jack.withClientDefault rmcaName $ \client -> Jack.withPort client outPortName $ \output -> Jack.withPort client inPortName $ \input -> Jack.withProcess client (jackCallBack tc input output toProcessRV boardQueue tempoRV) $ Jack.withActivation client $ Trans.lift $ do putStrLn $ "Started " ++ rmcaName ++ " JACK client." --newEmptyMVar >>= takeMVar Jack.waitForBreak return () -- The callback function. It pumps value out of the input port, mix -- them with value coming from the machine itself and stuff them into -- the output port. When this function is not running, events are -- processed. jackCallBack :: ( ReactiveValueAtomicUpdate toProcess [(Frames, RawMessage)] IO , ReactiveValueAtomicUpdate board (M.IntMap ([Note],[Message])) IO , ReactiveValueRead tempo Tempo IO) => IOTick -> JMIDI.Port Jack.Input -> JMIDI.Port Jack.Output -> toProcess -> board -> tempo -> Jack.NFrames -> Sync.ExceptionalT E.Errno IO () jackCallBack tc input output toProcessRV boardQueue tempoRV nframes@(Jack.NFrames nframesInt') = do let inMIDIRV = inMIDIEvent input nframes outMIDIRV = outMIDIEvent output nframes nframesInt = fromIntegral nframesInt' :: Int Trans.lift $ do tempo <- reactiveValueRead tempoRV concat . toList . gatherMessages tempo nframesInt <$> reactiveValueRead boardQueue >>= \bq -> reactiveValueAppend toProcessRV bq-- >> putStrLn ("BoardQueue: " ++ show (map fst bq)) reactiveValueEmpty boardQueue (go, old') <- schedule nframesInt <$> reactiveValueRead toProcessRV let old = map (first (+ (- nframesInt))) old' --putStrLn ("Out: " ++ show (map fst go)) reactiveValueWrite outMIDIRV go reactiveValueWrite toProcessRV old tickIOTick tc --------------