packages feed

reactive-banana-wx-0.4.1.0: src/Wave.hs

{-----------------------------------------------------------------------------
    reactive-banana-wx
    
    Example: Guitar chord simulation
------------------------------------------------------------------------------}
import Control.Monad
import qualified Data.List as List
import Data.Maybe
import Data.Ord
import Graphics.UI.WX hiding (Event)
import Reactive.Banana
import Reactive.Banana.WX

{-----------------------------------------------------------------------------
    Main
------------------------------------------------------------------------------}
lightCount = 15  -- number of lights that comprise the wave
waveLength = 4   -- number of lights that are lit at once
dt         = 70  -- half the cycle duration

main = start $ do
    -- create window and widgets
    f        <- frame    [text := "Waves of Light"]
    left     <- button f [text := "Left"]
    right    <- button f [text := "Right"]
    lights   <- sequence $ replicate lightCount $ staticText f [text := "•"]
    
    set f [layout := margin 10 $
            column 10 [row 5 [widget left, widget right],
                       row 5 $ map widget lights]
          ]
    
    -- we're going to need a timer
    t  <- timer f []
    
    network <- compile $ do
        eLeft  <- event0 left command
        eRight <- event0 right command
        
        -- event describing all the lights
        eWave  <- scheduleQueue t $ (waveLeft  <$ eLeft ) `union` 
                                    (waveRight <$ eRight)
        
        -- animate the lights
        forM_ [1 .. lightCount] $ \k -> do
            let
                bulb  = lights !! (k-1)
                dBulb = stepperD False $ snd <$> filterE ((== k) . fst) eWave
                
                colorize True  = red
                colorize False = black
                
            sink bulb [ color :== colorize <$> dBulb ]        

    actuate network

type Index  = Int
type Action = (Index, Bool)

-- describe wave pattern as a list
wave :: (Index -> Index) -> [(Duration, Action)]
wave f = deltas $ merge ons offs
    where
    merge xs ys = List.sortBy (comparing fst) $ xs ++ ys
    deltas xs = zipWith relativize (0 : map fst xs) xs
        where relativize dt1 (dt2,x) = (dt2-dt1, x)
    
    ons  = [(k*2*dt, (f k, True)) | k <- [1..lightCount]]
    offs = [(dt+(waveLength+k)*2*dt, (f k, False)) | k <- [1..lightCount]]

waveLeft  = wave id
waveRight = wave (\k -> lightCount - k + 1)


{-----------------------------------------------------------------------------
    Timer magic
------------------------------------------------------------------------------}
type Duration  = Int -- in milliseconds
type Queue a   = [(Duration, a)] -- [(time to wait, occurrence to happen)]
type Enqueue a = Queue a

-- Schedule events to happen after a given duration from their occurrence
-- However, new events will *not* be scheduled before the old ones have finished.
scheduleQueue :: Timer -> Event (Enqueue a) -> NetworkDescription (Event a)
scheduleQueue t e = do
    liftIO $ set t [ enabled := False ]
    eAlarm <- event0 t command
    let
        -- (Queue that keeps track of events to schedule
        -- , duration of the new alarm if applicable) 
        (eSetNewAlarmDuration, bQueue) =
            mapAccum [] $ (remove <$ eAlarm) `union` (add <$> e)
        
        -- change queue and change timer
        remove (_:[]) = (stop, [])
        remove (_:xs) = (wait (fst $ head xs), xs)
        add    ys []  = (wait (fst $ head ys), ys)
        add    ys xs  = (idle, xs ++ ys)
        
        wait dt = set t [ enabled := True, interval := dt ]
        stop    = set t [ enabled := False ]
        idle    = return ()
        
        -- Return topmost value from the queue whenever the alarm rings.
        -- The queue is never empty when the alarm rings.
        eout = fmap (snd . head) $ bQueue <@ eAlarm
    
    reactimate $ eSetNewAlarmDuration
    return eout