{-# LANGUAGE Arrows #-} {-# LANGUAGE RecordWildCards #-} module LiveCoding.Cell.Util where -- base import Control.Arrow import Control.Monad.IO.Class import Data.Data (Data) 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 -- * 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', @keep a@ will output the stored indefinitely. -- A new value can be stored by inputting @'Just' a@. keep :: (Data a, Monad m) => a -> Cell m (Maybe a) a keep a = feedback a $ proc (ma, aOld) -> do let aNew = fromMaybe aOld ma returnA -< (aNew, aNew) -- | Like 'keep', but returns 'Nothing' until it is initialised by a @'Just' a@ value. keepJust :: (Monad m, Data a) => Cell m (Maybe a) (Maybe a) keepJust = 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) -- | @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 -- | 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 = Push a -- ^ Add an 'a' to the buffer. | Pop -- ^ Remove the oldest element from the buffer. -- | 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')