From 486260f8e13df06e1a30c54b691ea37f88a6d4ad Mon Sep 17 00:00:00 2001 From: Guerric Chupin <guerric.chupin@gmail.com> Date: Mon, 23 May 2016 13:39:18 +0100 Subject: [PATCH] Add metronome making function. --- Reactogon/Auxiliary/Auxiliary.hs | 22 ++++++++++++++++++++++ Reactogon/Global/Clock.hs | 23 +++++++++++++++++++---- Reactogon/Layer/Layer.hs | 5 ++++- Reactogon/Semantics.hs | 3 ++- Reactogon/tests/testClock.hs | 27 +++++++++++++++++++++++++++ Reactogon/tests/testOnChange.hs | 15 +++++++++++++++ 6 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 Reactogon/tests/testClock.hs create mode 100644 Reactogon/tests/testOnChange.hs diff --git a/Reactogon/Auxiliary/Auxiliary.hs b/Reactogon/Auxiliary/Auxiliary.hs index 90e25fa..cbc41e7 100644 --- a/Reactogon/Auxiliary/Auxiliary.hs +++ b/Reactogon/Auxiliary/Auxiliary.hs @@ -13,6 +13,13 @@ stepBack = sscan f (Nothing, Nothing) >>^ snd f (Nothing,Nothing) x' = (Just x', Nothing) f (Just x, _) x' = (Just x', Just x) +-- Just like stepBack but the output value is always defined and is +-- equal to the input at time 0. +stepBack' :: SF a a +stepBack' = proc x -> do + x' <- stepBack -< x + returnA -< maybe x id x' + -- Throws an Event when the incoming signal change. The Event is -- tagged with the new value. onChange :: (Eq a) => SF a (Event a) @@ -23,3 +30,18 @@ onChange = proc x -> do | isJust x' = let x'' = fromJust x' in if x'' == x then NoEvent else Event x returnA -< makeEvent x x' + +-- Similar to onChange but contains its initial value in the first +-- event. +onChange' :: (Eq a) => SF a (Event a) +onChange' = proc x -> do + x' <- stepBack -< x + -- If it's the first value, throw an Event, else behave like onChange. + let makeEvent x x' + | isNothing x' = Event x + | isJust x' = let x'' = fromJust x' in + if x'' == x then NoEvent else Event x + returnA -< makeEvent x x' + +discard :: a -> () +discard _ = () diff --git a/Reactogon/Global/Clock.hs b/Reactogon/Global/Clock.hs index 673ba15..41c0f00 100644 --- a/Reactogon/Global/Clock.hs +++ b/Reactogon/Global/Clock.hs @@ -1,10 +1,25 @@ module Reactogon.Global.Clock where +import Reactogon.Auxiliary.Auxiliary import Reactogon.Semantics import FRP.Yampa -tempo :: SF () Tempo -tempo = constant 96 +import Debug.Trace -metronome :: SF Tempo (Event Beat) -metronome = undefined +tempo :: Tempo -> SF () Tempo +tempo = constant + +-- The initial value is arbitrary but never appears because the switch +-- is immediate. +metronome :: SF () Tempo -> SF () (Event Beat) +metronome tempo = switch ((repeatedly (tempoToDTime 60) ()) + &&& + (discard ^>> tempo >>> onChange')) (metronome' tempo) + where metronome' :: SF () Tempo -> Tempo -> SF () (Event Beat) + metronome' tempo t = (switch ((repeatedly (tempoToDTime t) ()) + &&& + (discard ^>> tempo >>> onChange)) + (metronome' tempo)) + +tempoToDTime :: Tempo -> DTime +tempoToDTime = (60/) . fromIntegral diff --git a/Reactogon/Layer/Layer.hs b/Reactogon/Layer/Layer.hs index a9e8fe5..abb443d 100644 --- a/Reactogon/Layer/Layer.hs +++ b/Reactogon/Layer/Layer.hs @@ -13,10 +13,13 @@ data Layer = Layer { relTempo :: Double } layerClock :: SF () Tempo -> SF Layer Tempo -layerClock tempo = proc Layer { relTempo = r } -> do +layerClock globalTempo = proc Layer { relTempo = r } -> do t <- tempo -< () returnA -< floor $ r * fromIntegral t +layerMetronome :: a +layerMetronome = metronome layerClock + -- A layer is a producer of events triggered by the system beat clock. layer :: SF () (Event Beat) -> SF Layer (Event Note) layer beatSource = undefined diff --git a/Reactogon/Semantics.hs b/Reactogon/Semantics.hs index 725b6f1..a58af66 100644 --- a/Reactogon/Semantics.hs +++ b/Reactogon/Semantics.hs @@ -494,7 +494,7 @@ ppNotes bpb nss = ppnAux (zip [(br,bn) | br <- [1..], bn <- [1..bpb]] nss) leftJustify :: Int -> String -> String leftJustify w s = take (w - length s) (repeat ' ') ++ s - +{- ------------------------------------------------------------------------------ -- Simple test ------------------------------------------------------------------------------ @@ -530,3 +530,4 @@ main = ppNotes bpb (take 50 (runRMCA testBoard 0 0.8 [PlayHead (0,0) 1 N])) +-} diff --git a/Reactogon/tests/testClock.hs b/Reactogon/tests/testClock.hs new file mode 100644 index 0000000..43fe586 --- /dev/null +++ b/Reactogon/tests/testClock.hs @@ -0,0 +1,27 @@ +import Reactogon.Global.Clock +import Reactogon.Auxiliary.Auxiliary +import Reactogon.Semantics +import FRP.Yampa + +main :: IO () +main = do{- + putStr "Test tempo: " + print testTempo' + putStr "Test onChange': " + print testonChange'-} + putStr "Testing metronome: " + print testMetronome + +tempo' :: SF () Tempo +tempo' = switch ((constant 30) + &&& + (after 20 10)) (\t -> switch ((constant t) + &&& + (after 20 60)) (constant)) + +testTempo' = embed ((tempo')) + ((), take 120 $ repeat (1, Nothing)) +testonChange' = embed ((discard ^>> tempo' >>> onChange')) + ((), take 120 $ repeat (1, Nothing)) +testMetronome = embed (metronome (tempo')) + ((), take 120 $ repeat (1, Nothing)) diff --git a/Reactogon/tests/testOnChange.hs b/Reactogon/tests/testOnChange.hs new file mode 100644 index 0000000..aa85361 --- /dev/null +++ b/Reactogon/tests/testOnChange.hs @@ -0,0 +1,15 @@ +import Reactogon.Auxiliary.Auxiliary +import FRP.Yampa + +main :: IO () +main = do + putStr "Testing onChange: " + print testOnChange + putStr "Testing onChange': " + print testOnChange' + +testOnChange = + embed onChange (1, [(1, Just 1), (1, Just 1), (1, Just 2), (1, Just 3), (1, Just 3)]) + +testOnChange' = + embed onChange' (1, [(1, Just 1), (1, Just 1), (1, Just 2), (1, Just 3), (1, Just 3)]) -- 2.47.2