-- | A framework to create functional Launchpad \"apps\". -- -- See the modules below @System.MIDI.Launchpad.Apps@ for examples. -- -- Notes: -- -- * Both Ableton and the Launchpad embedded software seems to be somewhat -- buggy... If you experience issues, try resetting the Launchpad, try to launch -- Ableton and your app in the opposite order, etc... -- -- * /ALWAYS/ compile with the threaded runtime (ghc option -threaded) -- -- * When the programs start, the Launchpad is reseted, and Session mode -- is assumed. Press User mode 2 to start playing with the app. Sometimes you have to -- press Session mode \/ User mode 2 a few times so that Ableton and the launchpad -- app thinks the same thing about the state... -- -- * How to setup Ableton: Use a loopback device (eg. IAC Bus 1 on OSX) to -- communicate between the app and Ableton. In Ableton midi setup, -- enable the track and remote MIDI /input/ for the loopback device, and -- enable the sync MIDI /output/ for the loopback device; disable everything else. -- Also disable all Launchpad MIDI inputs and outputs (it can remain a control -- surface). -- {-# LANGUAGE BangPatterns, GeneralizedNewtypeDeriving #-} module System.MIDI.Launchpad.AppFramework ( -- * simple colors red , green , amber , yellow , orange -- * pure interface , PureApp(..) , runPureApp -- * monadic interface , MonadicApp(..) , runMonadicApp , RenderMonad , ButtonMonad , SyncMonad , setButtonColor , setButtonColor' , setButtonColors , getMode , setMode , CanChangeState , setState , getState , modifyState , CanSendMessage , sendMessage , sendMessages -- * global configuration , GlobalConfig(..) , defaultGlobalConfig ) where -------------------------------------------------------------------------------- -- import Data.List import Control.Monad import System.MIDI import System.MIDI.Utility import Control.Concurrent import Control.Concurrent.MVar () import Control.Monad.Trans import Control.Monad.Identity import Control.Monad.Writer import Control.Monad.State -- import Data.Array.IO import System.IO.Unsafe as Unsafe import System.MIDI.Launchpad.Control import System.MIDI.Launchpad.AppFramework.Internal -------------------------------------------------------------------------------- -- * monadic interface -- | Monadic application (equivalent to the above pure application, -- but may be more convenient to use) data MonadicApp cfg mode state = MonadicApp { mAppConfig :: cfg , mAppIniState :: (mode,state) , mAppStartStop :: cfg -> Bool -> (state -> state) -- ^ start or stop playing , mAppRender :: cfg -> mode -> state -> Maybe Int -> RenderMonad () -- ^ render the screen (it will optimized, don't worry); the @Maybe Int@ is the sync signal , mAppButton :: cfg -> ButtonPress -> ButtonMonad mode state () -- ^ the user presses a button , mAppSync :: cfg -> mode -> Int -> SyncMonad state () -- ^ external MIDI sync signal (24 times per quarter note) } newtype RenderMonad a = RM { unRM :: WriterT [(Button,Color)] Identity a } deriving Monad newtype ButtonMonad mode state a = BM { unBM :: StateT (mode,state) (WriterT [ MidiMessage' ] Identity) a } deriving Monad newtype SyncMonad state a = SM { unSM :: StateT state (WriterT [ MidiMessage' ] Identity) a } deriving Monad --------------------- setButtonColor :: (Button,Color) -> RenderMonad () setButtonColor bc = RM $ tell [bc] setButtonColor' :: Button -> Color -> RenderMonad () setButtonColor' b c = setButtonColor (b,c) setButtonColors :: [(Button,Color)] -> RenderMonad () setButtonColors bcs = RM $ tell bcs --------------------- getMode :: ButtonMonad mode state mode getMode = BM $ do (mode,_) <- get return mode setMode :: mode -> ButtonMonad mode state () setMode newmode = BM $ do (_,b) <- get put (newmode,b) --------------------- class Monad m => CanSendMessage m where sendMessages :: [MidiMessage'] -> m () sendMessage :: MidiMessage' -> m () sendMessage msg = sendMessages [msg] instance CanSendMessage (ButtonMonad mode state) where sendMessages ms = BM $ lift $ tell ms instance CanSendMessage (SyncMonad state) where sendMessages ms = SM $ lift $ tell ms --------------------- class CanChangeState m where getState :: Monad (m state) => m state state setState :: Monad (m state) => state -> m state () modifyState :: (CanChangeState m, Monad (m state)) => (state -> state) -> m state () modifyState f = do old <- getState setState $! f old instance CanChangeState (ButtonMonad mode) where getState = BM $ do (_,state) <- get return state setState newstate = BM $ do (a,_) <- get put (a,newstate) instance CanChangeState SyncMonad where getState = SM $ get setState s = SM $ put $! s -------------------------------------------------------------------------------- -- * conversion monadicAppToPureApp :: MonadicApp cfg mode state -> PureApp cfg mode state monadicAppToPureApp mApp@(MonadicApp cfg ini startstop render button sync) = pApp where pApp = PureApp { pAppConfig = cfg , pAppIniState = ini , pAppStartStop = startstop , pAppRender = \c m s i -> runIdentity $ execWriterT (unRM $ render c m s i) , pAppButton = \c b ms -> runIdentity $ runWriterT (execStateT (unBM $ button c b ) ms) , pAppSync = \c m s i -> runIdentity $ runWriterT (execStateT (unSM $ sync c m i ) s) } -- pureAppToMonadicApp :: PureApp mode state -> MonadicApp mode state -------------------------------------------------------------------------------- -- * run applications -- | A default global state defaultGlobalConfig :: GlobalConfig defaultGlobalConfig = GlobalConfig { defaultLaunchpadDevice = "Launchpad" , defaultMidiOutputDevice = "IAC Bus 1" , outputChannel = 1 , onlyUserMode2 = True } -------------------------------------------------------------------------------- -- | Executes a monadic application runMonadicApp :: {- Show state => -} GlobalConfig -> MonadicApp cfg mode state -> IO () runMonadicApp globalConfig mApp = runPureApp globalConfig (monadicAppToPureApp mApp) --------------------------------------------------------------------------------