packages feed

essence-of-live-coding-0.2.8: src/LiveCoding/Cell/Util.hs

{-# 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