{-# LANGUAGE Arrows #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} module LiveCoding.Cell.Util where -- base import Control.Arrow import Control.Monad (guard, join) import Control.Monad.IO.Class import Data.Data (Data) import Data.Foldable (toList) import Data.Functor (void) import Data.Maybe -- containers import Data.Sequence hiding (take) import qualified Data.Sequence as Sequence -- time import Data.Time.Clock -- essence-of-live-coding import LiveCoding.Cell import LiveCoding.Cell.Feedback import LiveCoding.Cell.Resample (resampleMaybe) import LiveCoding.Cell.Util.Internal -- * State accumulation -- | Sum all past inputs, starting by the given number sumFrom :: Monad m => Integer -> Cell m Integer Integer sumFrom n0 = feedback n0 $ proc (n, acc) -> returnA -< (acc, acc + n) -- | Count the number of ticks, starting at 0 count :: Monad m => Cell m a Integer count = arr (const 1) >>> sumC {- | Accumulate all incoming data, using the given fold function and start value. For example, if @'foldC' f b@ receives inputs @a0@, @a1@,... it will output @b@, @f a0 b@, @f a1 $ f a0 b@, and so on. -} foldC :: (Data b, Monad m) => (a -> b -> b) -> b -> Cell m a b foldC step cellState = Cell {..} where cellStep b a = let b' = step a b in return (b, b') -- | Like 'foldC', but does not delay the output. foldC' :: (Data b, Monad m) => (a -> b -> b) -> b -> Cell m a b foldC' step cellState = Cell {..} where cellStep b a = let b' = step a b in return (b', b') {- | Initialise with a value 'a'. If the input is 'Nothing', @'hold' a@ will output the stored indefinitely. A new value can be stored by inputting @'Just' a@. -} hold :: (Data a, Monad m) => a -> Cell m (Maybe a) a hold a = feedback a $ proc (ma, aOld) -> do let aNew = fromMaybe aOld ma returnA -< (aNew, aNew) {- | Outputs @'Just' a@ whenever the the value a changes and 'Nothing' otherwise. The first output is always 'Nothing'. The following holds: @ delay a >>> changes >>> hold a == delay a @ -} changes :: (Data a, Eq a, Monad m) => Cell m a (Maybe a) changes = proc a -> do aLast <- delay Nothing -< Just a returnA -< do aLast' <- aLast guard $ a /= aLast' return a -- | Like 'hold', but returns 'Nothing' until it is initialised by a @'Just' a@ value. holdJust :: (Monad m, Data a) => Cell m (Maybe a) (Maybe a) holdJust = feedback Nothing $ arr keep where keep (Nothing, Nothing) = (Nothing, Nothing) keep (_, Just a) = (Just a, Just a) keep (Just a, Nothing) = (Just a, Just a) -- | Hold the first value and output it indefinitely. holdFirst :: (Data a, Monad m) => Cell m a a holdFirst = Cell {..} where cellState = Nothing cellStep Nothing x = return (x, Just x) cellStep (Just s) _ = return (s, Just s) -- | @boundedFIFO n@ keeps the first @n@ present values. boundedFIFO :: (Data a, Monad m) => Int -> Cell m (Maybe a) (Seq a) boundedFIFO n = foldC' step empty where step Nothing as = as step (Just a) as = Sequence.take n $ a <| as {- | Buffers and returns the elements in First-In-First-Out order, returning 'Nothing' whenever the buffer is empty. -} fifo :: (Monad m, Data a) => Cell m (Seq a) (Maybe a) fifo = feedback empty $ proc (as, accum) -> do let accum' = accum >< as returnA -< case accum' of Empty -> (Nothing, empty) a :<| as -> (Just a, as) {- | Like 'fifo', but accepts lists as input. Each step is O(n) in the length of the list. -} fifoList :: (Monad m, Data a) => Cell m [a] (Maybe a) fifoList = arr fromList >>> fifo -- | Like 'fifoList', but generalised to any 'Foldable'. fifoFoldable :: (Monad m, Data a, Foldable f) => Cell m (f a) (Maybe a) fifoFoldable = arr toList >>> fifoList -- | Returns 'True' iff the current input value is 'True' and the last input value was 'False'. edge :: Monad m => Cell m Bool Bool edge = proc b -> do bLast <- delay False -< b returnA -< b && not bLast -- * Debugging utilities -- | Print the current UTC time, prepended with the first 8 characters of the given message. printTime :: MonadIO m => String -> m () printTime msg = liftIO $ putStrLn . (take 8 msg ++) . show =<< getCurrentTime -- | Like 'printTime', but as a cell. printTimeC :: MonadIO m => String -> Cell m () () printTimeC msg = constM $ printTime msg -- * Buffers -- | A command to send to 'buffer'. data BufferCommand a = -- | Add an 'a' to the buffer. Push a | -- | Remove the oldest element from the buffer. Pop -- | Pushes @'Just' a@ and does nothing on 'Nothing'. maybePush :: Maybe a -> [BufferCommand a] maybePush = (Push <$>) . maybeToList -- | Pops on @'Just' a@ and does nothing on 'Nothing'. maybePop :: Maybe a -> [BufferCommand b] maybePop = (const Pop <$>) . maybeToList {- | Single-consumer, multi-producer buffer. The output value is the oldest value in the buffer, if it exists. * Add elements by inputting @'Push' a@. * Remove elements by inputting 'Pop'. -} buffer :: (Monad m, Data a) => Cell m [BufferCommand a] (Maybe a) buffer = Cell {..} where cellState = empty cellStep as commands = return (currentHead as, nextBuffer as commands) currentHead as = case viewl as of EmptyL -> Nothing a :< as' -> Just a nextBuffer as [] = as nextBuffer as (Push a : commands) = nextBuffer (as |> a) commands nextBuffer as (Pop : commands) = nextBuffer (Sequence.drop 1 as) commands {- | Equip a 'Cell' with a 'buffer'. * Whenever @'Just' a@ value enters @buffered cell@, it is added to the buffer. * Whenever @cell@ emits @'Just' b@, the oldest value is dropped from the buffer. * @cell@ is always fed with 'Just' the oldest value from the buffer, except when the buffer is empty, then it is fed 'Nothing'. This construction guarantees that @cell@ produces exactly one output for every input value. -} buffered :: (Monad m, Data a) => Cell m (Maybe a) (Maybe b) -> Cell m (Maybe a) (Maybe b) buffered cell = feedback Nothing $ proc (aMaybe, ticked) -> do aMaybe' <- buffer -< maybePop ticked ++ maybePush aMaybe bMaybe' <- cell -< aMaybe' returnA -< (bMaybe', void bMaybe') -- * Detecting change {- | Perform an action whenever the parameter @p@ changes, and the code is reloaded. Note that this does not trigger any actions when adding, or removing an 'onChange' cell. For this functionality, see "LiveCoding.Handle". Also, when moving such a cell, the action may not be triggered reliably. -} onChange :: (Monad m, Data p, Eq p) => -- | This parameter has to change during live coding to trigger an action p -> -- | This action gets passed the old parameter and the new parameter (p -> p -> a -> m b) -> Cell m a (Maybe b) onChange p action = proc a -> do pCurrent <- arr $ const p -< () pPrevious <- delay p -< pCurrent arrM $ whenDifferent action -< (pCurrent, pPrevious, a) -- | Like 'onChange'', but with a dynamic input. onChange' :: (Monad m, Data p, Eq p) => -- | This action gets passed the old parameter and the new parameter (p -> p -> a -> m b) -> Cell m (p, a) (Maybe b) onChange' action = proc (pCurrent, a) -> do pPrevious <- delay Nothing -< Just pCurrent bMaybeMaybe <- resampleMaybe $ arrM $ whenDifferent action -< (,pCurrent,a) <$> pPrevious returnA -< join bMaybeMaybe