module Reactive.Banana.MIDI.Process (
RelativeTicks,
AbsoluteTicks,
RelativeSeconds,
MomentIO(liftMomentIO),
Reactor(reserveSchedule),
scheduleQueue,
initialEvent,
beat,
beatQuant,
beatVar,
delaySchedule,
delay,
delayAdd,
pressed,
latch,
controllerRaw,
controllerExponential,
controllerLinear,
tempoCtrl,
snapSelect,
uniqueChanges,
sweep,
makeControllerLinear,
cyclePrograms,
cycleProgramsDefer,
noteSequence,
guitar,
trainer,
) where
import qualified Reactive.Banana.MIDI.Guitar as Guitar
import qualified Reactive.Banana.MIDI.Program as Program
import qualified Reactive.Banana.MIDI.Controller as Ctrl
import qualified Reactive.Banana.MIDI.Note as Note
import qualified Reactive.Banana.MIDI.Time as Time
import qualified Reactive.Banana.MIDI.KeySet as KeySet
import qualified Reactive.Banana.MIDI.Pitch as Pitch
import qualified Reactive.Banana.MIDI.Utility as RBU
import qualified Reactive.Banana.MIDI.Common as Common
import Reactive.Banana.MIDI.Common
(PitchChannel(PitchChannel),
PitchChannelVelocity(PitchChannelVelocity),
fraction, )
import qualified Reactive.Banana.Bunch.Combinators as RB
import qualified Reactive.Banana.Bunch.Frameworks as RBF
import Reactive.Banana.Bunch.Combinators ((<@>), )
import qualified Sound.MIDI.Message.Class.Construct as Construct
import qualified Sound.MIDI.Message.Class.Check as Check
import qualified Sound.MIDI.Message.Class.Query as Query
import Sound.MIDI.Message.Channel (Channel, )
import Sound.MIDI.Message.Channel.Voice
(Pitch, Velocity, Controller, Program, fromPitch, )
import qualified Data.EventList.Relative.TimeBody as EventList
import qualified Data.EventList.Absolute.TimeBody as EventListAbs
import qualified Data.Accessor.Monad.Trans.State as AccState
import qualified Data.Accessor.Tuple as AccTuple
import qualified Control.Monad.Trans.State as MS
import qualified Data.Traversable as Trav
import Control.Monad (join, mplus, when, liftM, )
import Control.Applicative (pure, liftA2, (<*>), (<$>), )
import Data.Monoid (mempty, mappend, )
import Data.Tuple.HT (mapPair, mapSnd, )
import Data.Ord.HT (comparing, limit, )
import Data.Maybe.HT (toMaybe, )
import Data.Maybe (catMaybes, )
import qualified Data.Map as Map
import qualified Data.List.Key as Key
import qualified Data.List.Match as Match
import qualified Data.List as List
import Prelude hiding (sequence, )
type RelativeTicks m = Time.T m Time.Relative Time.Ticks
type AbsoluteTicks m = Time.T m Time.Absolute Time.Ticks
type RelativeSeconds m = Time.T m Time.Relative Time.Seconds
class MomentIO moment where
liftMomentIO :: RBF.MomentIO a -> moment a
instance MomentIO RBF.MomentIO where
liftMomentIO = id
class (MomentIO reactor, Time.Timed reactor) => Reactor reactor where
reserveSchedule ::
reactor
([AbsoluteTicks reactor] -> IO (), IO (),
RB.Event (AbsoluteTicks reactor))
reactimate ::
(MomentIO reactor) =>
RB.Event (IO ()) -> reactor ()
reactimate = liftMomentIO . RBF.reactimate
reactimate' ::
(MomentIO reactor) =>
RB.Event (RBF.Future (IO ())) -> reactor ()
reactimate' = liftMomentIO . RBF.reactimate'
liftIO :: (MomentIO m) => IO a -> m a
liftIO = liftMomentIO . RBF.liftIO
scheduleQueue ::
(Reactor reactor) =>
RB.Behavior (AbsoluteTicks reactor) ->
RB.Event (Common.Bundle reactor a) -> reactor (RB.Event a)
scheduleQueue times e = do
(send, _cancel, eEcho) <- reserveSchedule
let
remove echoTime =
MS.state $ uncurry $ \_lastTime ->
EventList.switchL
(error "scheduleQueue: received more events than sent")
(\(_t,x) xs ->
((Just x, return () ),
(
echoTime, xs)))
add time new = do
MS.modify $ \(lastTime, old) ->
(time,
Common.mergeStable
(EventList.fromAbsoluteEventListGen Time.subSat mempty $
EventListAbs.fromPairList $
map (\(Common.Future dt a) -> (dt, a)) $
List.sortBy (comparing Common.futureTime) new) $
EventList.decreaseStart
(Time.subSat time lastTime) old)
return (Nothing, send $ map (flip Time.inc time . Common.futureTime) new)
(eEchoEvent, _bQueue) <-
RBU.sequence (mempty, EventList.empty) $
RB.union (fmap remove eEcho) (add <$> times <@> e)
reactimate $ fmap snd eEchoEvent
return $ RBU.mapMaybe fst eEchoEvent
initialEvent ::
(Reactor reactor) =>
a -> reactor (RB.Event a)
initialEvent x = do
(send, _cancel, eEcho) <- reserveSchedule
liftIO $ send [mempty]
return $ fmap (const x) eEcho
beat ::
(Reactor reactor) =>
RB.Behavior (RelativeTicks reactor) ->
reactor (RB.Event (AbsoluteTicks reactor))
beat tempo = do
(send, _cancel, eEcho) <- reserveSchedule
liftIO $ send [mempty]
let next dt time = (time, send [Time.inc dt time])
eEchoEvent = fmap next tempo <@> eEcho
reactimate $ fmap snd eEchoEvent
return $ fmap fst eEchoEvent
beatQuant ::
(Reactor reactor) =>
RelativeTicks reactor ->
RB.Behavior (RelativeTicks reactor) ->
reactor (RB.Event (AbsoluteTicks reactor))
beatQuant maxDur tempo = do
(send, _cancel, eEcho) <- reserveSchedule
liftIO $ send [mempty]
let next dt time = do
complete <- MS.gets (>=1)
when complete $ MS.modify (subtract 1)
portion <- MS.get
let dur = limit (mempty,maxDur) (Time.scaleCeiling (1-portion) dt)
MS.modify (Time.div dur dt +)
return
(toMaybe complete time,
send [Time.inc dur time]
)
eEchoEvent <- liftM fst $ RBU.sequence 0 $ fmap next tempo <@> eEcho
reactimate $ fmap snd eEchoEvent
return $ RBU.mapMaybe fst eEchoEvent
beatVarNext ::
AbsoluteTicks reactor ->
MS.State
(AbsoluteTicks reactor, Double, RelativeTicks reactor)
(Maybe (AbsoluteTicks reactor), AbsoluteTicks reactor)
beatVarNext _t = do
(t0,r,p) <- MS.get
let t1 = Time.inc (Time.scale r p) t0
MS.put (t1,1,p)
return (Just t1, Time.inc p t1)
beatVarChange ::
RelativeTicks reactor -> AbsoluteTicks reactor ->
MS.State
(AbsoluteTicks reactor, Double, RelativeTicks reactor)
(AbsoluteTicks reactor)
beatVarChange p1 t1 = do
(t0,r0,p0) <- MS.get
let r1 = max 0 $ r0 - Time.div (Time.subSat t1 t0) p0
MS.put (t1,r1,p1)
return (Time.inc (Time.scale r1 p1) t1)
beatVar ::
(Reactor reactor) =>
RB.Behavior (AbsoluteTicks reactor) ->
RB.Behavior (RelativeTicks reactor) ->
reactor (RB.Event (AbsoluteTicks reactor))
beatVar time tempo = do
(send, cancel, eEcho) <- reserveSchedule
let sendSingle = send . (:[])
liftIO $ sendSingle mempty
(tempoInit, tempoChanges) <-
liftMomentIO $
liftA2 (,) (RB.valueBLater tempo) (RBF.plainChanges tempo)
let next t = mapSnd (return . sendSingle) <$> beatVarNext t
change p1 t1 = do
ta <- beatVarChange p1 t1
return (Nothing, return $ cancel >> sendSingle ta)
eEchoEvent <-
liftM fst $ RBU.sequence (mempty, 0, tempoInit) $
RB.union (next <$> eEcho) (flip change <$> time <@> tempoChanges)
reactimate' $ fmap snd eEchoEvent
return $ RBU.mapMaybe fst eEchoEvent
delaySchedule ::
(Reactor reactor) =>
RelativeTicks reactor ->
RB.Behavior (AbsoluteTicks reactor) ->
RB.Event a -> reactor (RB.Event a)
delaySchedule dt times =
scheduleQueue times . fmap ((:[]) . Common.Future dt)
delay ::
RelativeTicks m ->
RB.Event ev -> RB.Event (Common.Future m ev)
delay dt =
fmap (Common.Future dt)
delayAdd ::
RelativeTicks m ->
RB.Event ev -> RB.Event (Common.Future m ev)
delayAdd dt evs =
RB.union (fmap Common.now evs) $ delay dt evs
pressed ::
(RB.MonadMoment m, KeySet.C set, Ord key) =>
set key value ->
RB.Event (Note.BoundaryExt key value) ->
m (RB.Event [Note.Boundary key value], RB.Behavior (set key value))
pressed empty =
RBU.traverse empty KeySet.changeExt
latch ::
(RB.MonadMoment m, Ord key) =>
RB.Event (Note.Boundary key value) ->
m (RB.Event (Note.Boundary key value),
RB.Behavior (Map.Map key value))
latch =
liftM (mapPair (RB.filterJust, fmap KeySet.deconsLatch)) .
RBU.traverse KeySet.latch KeySet.latchChange
controllerRaw ::
(RB.MonadMoment m, Check.C ev) =>
Channel ->
Controller ->
Int ->
RB.Event ev -> m (RB.Behavior Int)
controllerRaw chan ctrl deflt =
RB.stepper deflt . RBU.mapMaybe (Check.controller chan ctrl)
controllerExponential ::
(RB.MonadMoment m, Floating a, Check.C ev) =>
Channel ->
Controller ->
a -> (a,a) ->
RB.Event ev -> m (RB.Behavior a)
controllerExponential chan ctrl deflt (lower,upper) =
let k = log (upper/lower) / 127
in RB.stepper deflt .
RBU.mapMaybe
(fmap ((lower*) . exp . (k*) . fromIntegral)
. Check.controller chan ctrl)
controllerLinear ::
(RB.MonadMoment m, Fractional a, Check.C ev) =>
Channel ->
Controller ->
a -> (a,a) ->
RB.Event ev -> m (RB.Behavior a)
controllerLinear chan ctrl deflt (lower,upper) =
let k = (upper-lower) / 127
in RB.stepper deflt .
RBU.mapMaybe
(fmap ((lower+) . (k*) . fromIntegral)
. Check.controller chan ctrl)
mapFstM :: Monad m => (a -> m c) -> (a, b) -> m (c, b)
mapFstM f ~(a,b) = liftM (flip (,) b) $ f a
tempoCtrl ::
(RB.MonadMoment m, Check.C ev) =>
Channel ->
Controller ->
RelativeTicks m ->
(RelativeTicks m, RelativeTicks m) ->
RB.Event ev ->
m (RB.Behavior (RelativeTicks m), RB.Event ev)
tempoCtrl chan ctrl deflt (lower,upper) =
mapFstM (RB.stepper deflt) .
RBU.partitionMaybe
(fmap (Ctrl.duration (lower, upper))
. Check.controller chan ctrl)
snapSelect ::
(MomentIO moment, KeySet.C set, Pitch.C pitch, Eq pitch, Eq value) =>
RB.Behavior (set pitch value) ->
RB.Behavior Int ->
moment (RB.Event [Note.Boundary pitch value])
snapSelect set ctrl =
liftMomentIO $
(flip RBU.mapAdjacent Nothing
(\oldNote newNote ->
let note on (pc, v) = Note.Boundary pc v on
in catMaybes [fmap (note False) oldNote,
fmap (note True) newNote]) =<<) $
uniqueChanges $
liftA2
(\s x ->
toMaybe (not $ null s) $
Key.minimum (\(pc, _v) -> abs (fromPitch (Pitch.extract pc) - x)) $
map (\(pc, v) -> (Pitch.toClosestOctave x pc, v)) s)
(fmap KeySet.toList set) ctrl
uniqueChanges ::
(MomentIO moment, Eq a) => RB.Behavior a -> moment (RB.Event a)
uniqueChanges x = liftMomentIO $ do
x0 <- RB.valueBLater x
xs <- RBF.plainChanges x
fmap RB.filterJust $
flip RBU.mapAdjacent x0 (\old new -> toMaybe (new/=old) new) xs
sweep ::
(Reactor reactor) =>
RelativeSeconds reactor ->
(Double -> Double) ->
RB.Behavior Double ->
reactor
(RB.Event (AbsoluteTicks reactor),
RB.Behavior Double)
sweep durSecs wave speed = do
bt <- beat . pure =<< Time.ticksFromSeconds durSecs
let dur = realToFrac $ Time.unSeconds $ Time.decons durSecs
phases <-
RB.accumB 0 $
fmap (\d _ phase -> fraction (phase + dur * d)) speed <@> bt
return (bt, fmap wave phases)
makeControllerLinear ::
(Construct.C msg) =>
Channel -> Controller ->
RB.Behavior Int ->
RB.Behavior Int ->
RB.Event time -> RB.Behavior Double ->
RB.Event msg
makeControllerLinear chan cc depthCtrl centerCtrl bt ctrl =
pure
(\y depth center _time ->
curry (Construct.anyController chan) cc $
round $ limit (0,127) $
fromIntegral center + fromIntegral depth * y)
<*> ctrl
<*> depthCtrl
<*> centerCtrl
<@> bt
cyclePrograms ::
(RB.MonadMoment m, Construct.C msg, Query.C msg) =>
[Program] ->
RB.Event msg -> m (RB.Event (Maybe msg))
cyclePrograms pgms =
liftM fst . RBU.traverse (cycle pgms) (Program.traverseSeek (length pgms))
cycleProgramsDefer ::
(RB.MonadMoment m, Construct.C msg, Query.C msg) =>
RelativeTicks m -> [Program] ->
RB.Behavior (AbsoluteTicks m) ->
RB.Event msg -> m (RB.Event (Maybe msg))
cycleProgramsDefer defer pgms times =
liftM fst .
RBU.traverse (cycle pgms, mempty)
(\(eventTime,e) ->
fmap join $ Trav.sequence $
mplus
(flip fmap (Query.program e) $ \(_chan, pgm) ->
AccState.lift AccTuple.first $
Program.seek (length pgms) pgm)
(flip fmap (Program.maybeNoteOn e) $ \chan -> do
blockTime <- MS.gets snd
if eventTime < blockTime
then return Nothing
else do
AccState.set AccTuple.second $
Time.inc defer eventTime
AccState.lift AccTuple.first $
Program.next chan)) .
RB.apply (fmap (,) times)
noteSequence ::
RelativeTicks m ->
Bool -> [Bool -> msg] ->
Common.Bundle m msg
noteSequence stepTime on =
zipWith Common.Future (iterate (mappend stepTime) mempty) . map ($on)
guitar ::
(RB.MonadMoment m, Construct.C msg, KeySet.C set) =>
RelativeTicks m ->
RB.Behavior (set PitchChannel Velocity) ->
RB.Event Bool ->
m (RB.Event (Common.Bundle m msg))
guitar stepTime pressd trigger =
liftM fst $
RBU.traverse []
(\(set, on) -> do
played <- MS.get
let toPlay =
case KeySet.toList set of
[] -> []
list ->
fmap (\(PitchChannelVelocity pc v) -> Note.make pc v) $
Guitar.mapChordToString Guitar.stringPitches $
fmap (uncurry PitchChannelVelocity) list
MS.put toPlay
return $
if on
then
noteSequence stepTime False
(List.reverse played)
++
noteSequence stepTime True toPlay
else
noteSequence stepTime False played
++
noteSequence stepTime True
(List.reverse toPlay)) $
(,) <$> pressd <@> trigger
trainer ::
(Reactor reactor,
Query.C msg, Construct.C msg, Time.Quantity time) =>
Channel ->
Time.T reactor Time.Relative time ->
Time.T reactor Time.Relative time ->
[([Pitch], [Pitch])] ->
RB.Behavior (AbsoluteTicks reactor) ->
RB.Event msg ->
reactor (RB.Event (Common.Bundle reactor msg))
trainer chan pauseSecs durationSecs sets0 times evs0 = do
pause <- Time.ticksFromAny pauseSecs
duration <- Time.ticksFromAny durationSecs
let makeSeq sets =
case sets of
(target, _) : _ ->
(concat $
zipWith
(\t p ->
Note.bundle t duration
(PitchChannel p chan, Common.normalVelocity))
(iterate (mappend duration) pause) target,
mappend pause $ Time.scaleInt (length target) duration)
[] -> ([], mempty)
let (initial, initIgnoreUntil) = makeSeq sets0
initEv <- initialEvent initial
liftM (RB.union initEv . fst) $
flip (RBU.traverse (sets0, [], Time.inc initIgnoreUntil mempty))
(fmap (,) times <@> evs0) $ \(time,ev) ->
case Query.noteExplicitOff ev of
Just (_chan, (_vel, pitch, True)) -> do
ignoreUntil <- AccState.get AccTuple.third3
if time <= ignoreUntil
then return []
else do
pressd <- AccState.get AccTuple.second3
let newPressd = pitch : pressd
AccState.set AccTuple.second3 newPressd
sets <- AccState.get AccTuple.first3
case sets of
(_, target) : rest ->
if Match.lessOrEqualLength target newPressd
then do
AccState.set AccTuple.second3 []
when (newPressd == List.reverse target) $
AccState.set AccTuple.first3 rest
(notes, newIgnoreUntil) <-
fmap makeSeq $
AccState.get AccTuple.first3
AccState.set AccTuple.third3 $
Time.inc newIgnoreUntil time
return notes
else return []
_ -> return []
_ -> return []