launchpad-control-0.0.1.0: System/MIDI/Launchpad/Apps/FXControl.hs
-- | A very simple generic live FX control surface.
--
-- There are 8 CCs (the 8 columns of the grid), and 8 trigger buttons
-- (the side buttons).
--
-- The trigger buttons can function either as an on/off switch or
-- as gate (active only during pressed). This is selectable by pressing
-- down the side button(s) and at the same time the up/down button.
-- By default, the top four are switched, the bottom four gate.
--
-- Example usage:
--
-- > main = runPureApp defaultGlobalConfig $ fxControl defaultCfg
--
{-# LANGUAGE BangPatterns #-}
module System.MIDI.Launchpad.Apps.FXControl where
--------------------------------------------------------------------------------
import Data.List
import Control.Monad
import System.MIDI
import Data.Array.Unboxed
import Data.Array.IArray
import System.MIDI.Launchpad.Control
import System.MIDI.Launchpad.AppFramework
--------------------------------------------------------------------------------
data Cfg = Cfg
{ ccFrom :: !Int -- ^ the first CC number, corresponding to the first column on the Launchpad
, onOffFrom :: !Int -- ^ the first note (used as on/off switch), corresponding the topmost triangle button on Launchpad
}
deriving Show
defaultCfg :: Cfg
defaultCfg = Cfg
{ ccFrom = 100
, onOffFrom = 100
}
--------------------------------------------------------------------------------
data Mode = FX deriving (Eq,Ord,Show)
data Trigger = Switch | Gate deriving (Eq,Ord,Show) {- InvGate -}
data State = State
{ _onOff :: !(UArray Int Bool)
, _isPressed :: !(UArray Int Bool)
, _triggerMode :: !(Array Int Trigger)
, _params :: !(UArray Int Int)
, _playing :: !Bool
}
deriving (Eq,Ord,Show)
-- | A very simple generic live FX control surface
fxControl :: Cfg -> PureApp Cfg Mode State
fxControl cfg = PureApp
{ pAppConfig = cfg
, pAppIniState = (FX,initialState)
, pAppRender = render
, pAppButton = button
, pAppStartStop = startStop
, pAppSync = sync
}
--------------------------------------------------------------------------------
initialState :: State
initialState = State
{ _onOff = listArray (0,7) (repeat False)
, _isPressed = listArray (0,7) (repeat False)
, _triggerMode = listArray (0,7) (replicate 4 Switch ++ replicate 4 Gate)
, _params = listArray (0,7) (repeat 4)
, _playing = False
}
startStop :: Cfg -> Bool -> State -> State
startStop _ playing state = state { _playing = playing }
--------------------------------------------------------------------------------
button :: Cfg -> ButtonPress -> (Mode,State) -> ((Mode,State),[MidiMessage'])
button (Cfg ccfrom notefrom) press (mode,state) =
case but of
Dir d -> case d of
U -> ((mode, trigger' Switch),[])
D -> ((mode, trigger' Gate ),[])
_ -> ((mode,state),[])
Side k -> case (trigger!k) of
Switch -> if not down
then ( ( mode , state' k ) , [] )
else ( ( mode , (state' k) { _onOff = onOff // [(k,new)] } ) , [ noteOnOff k new ] )
where old = onOff ! k
new = not old
Gate -> ( ( mode , (state' k) { _onOff = onOff // [(k,down)] } ) , [ noteOnOff k down ] )
Pad x y -> if down
then ( ( mode, state { _params = params // [(x, 7-y)] } ) , [CC (ccNumber x) ((7-y)*16)] )
else ((mode,state),[])
_ -> ((mode,state),[])
where
state' k = state { _isPressed = isPressed // [(k,down)] }
trigger' Gate = state { _triggerMode = trigger // [ (k,Gate ) | (k,True) <- pressedList ]
, _onOff = onOff // [ (k,True ) | (k,True) <- pressedList ]
}
trigger' Switch = state { _triggerMode = trigger // [ (k,Switch) | (k,True) <- pressedList ]
, _onOff = onOff // [ (k,True ) | (k,True) <- pressedList, trigger!k == Gate ]
}
noteOnOff k b = if b
then NoteOn (notefrom+k) 127
else NoteOn (notefrom+k) 127 -- hmm, Ableton seems to work like that (only changes on NoteOn)
ccNumber x = ccfrom + x
onOff = _onOff state
params = _params state
trigger = _triggerMode state
isPressed = _isPressed state
pressedList = assocs isPressed
(but,down) = case press of
Press b -> (b,True )
Release b -> (b,False)
--------------------------------------------------------------------------------
sync :: Cfg -> Mode -> State -> Int -> (State,[MidiMessage'])
sync _ mode state counter = (state,[])
--------------------------------------------------------------------------------
render :: Cfg -> Mode -> State -> Maybe Int -> [(Button,Color)]
render _ mode state msync = stuff where
onOff = _onOff state
params = _params state
trigger = _triggerMode state
isPressed = _isPressed state
stuff = par ++ side
par = [ (Pad x y, color) | x<-[0..7], let p = params!x, p>0, y<-[7-p..7]
, let color = green ]
side = [ (Side y, color) | y<-[0..7], let b = onOff!y, let color = trigColor y b ]
trigColor y b = case trigger!y of
Switch -> if b then amber else None
Gate -> if b then red else None
-- InvGate -> if not b then green else None
--------------------------------------------------------------------------------