packages feed

rhine-1.7: src/FRP/Rhine/ResamplingBuffer/Util.hs

{-# LANGUAGE RankNTypes #-}

{- |
Several utilities to create 'ResamplingBuffer's.
-}
module FRP.Rhine.ResamplingBuffer.Util where

-- base
import Data.Function ((&))

-- transformers
import Control.Monad.Trans.Reader (runReaderT)

-- time-domain
import Data.TimeDomain (TimeDomain (..))

-- automaton
import Data.Stream (StreamT (..))
import Data.Stream.Internal (JointState (..))
import Data.Stream.Optimized (toStreamT)
import Data.Stream.Result (Result (..), mapResultState)

-- rhine
import FRP.Rhine.ClSF hiding (step, toStreamT)
import FRP.Rhine.Clock
import FRP.Rhine.ResamplingBuffer
import FRP.Rhine.Schedule (ParallelClock)

-- * Utilities to build 'ResamplingBuffer's from smaller components

infix 2 >>-^

{- FOURMOLU_DISABLE -}

-- | Postcompose a 'ResamplingBuffer' with a matching 'ClSF'.
(>>-^) ::
  Monad m =>
  ResamplingBuffer m cl1 cl2 a b   ->
  ClSF             m     cl2   b c ->
  ResamplingBuffer m cl1 cl2 a   c
resbuf  >>-^ clsf = helper resbuf $ toStreamT $ getAutomaton clsf
  where
    helper ResamplingBuffer { buffer, put, get} StreamT { state, step} = ResamplingBuffer
      { buffer = JointState buffer state,
      put = \theTimeInfo a (JointState b s) -> (`JointState` s) <$> put theTimeInfo a b
      , get = \theTimeInfo (JointState b s) -> do
          Result b' b <- get theTimeInfo b
          Result s' c <- step s `runReaderT` b `runReaderT` theTimeInfo
          pure $! Result (JointState b' s') c
      }

infix 1 ^->>

-- | Precompose a 'ResamplingBuffer' with a matching 'ClSF'.
(^->>) ::
  Monad m =>
  ClSF             m cl1     a b   ->
  ResamplingBuffer m cl1 cl2   b c ->
  ResamplingBuffer m cl1 cl2 a   c
clsf ^->> resBuf = helper (toStreamT (getAutomaton clsf)) resBuf
  where
   helper StreamT {state, step} ResamplingBuffer {buffer, put, get} = ResamplingBuffer
      {
        buffer = JointState buffer state
    , put = \theTimeInfo a (JointState buf s) -> do
      Result s' b <- step s `runReaderT` a `runReaderT` theTimeInfo
      buf' <- put theTimeInfo b buf
      pure $! JointState buf' s'
    , get = \theTimeInfo (JointState buf s) -> mapResultState (`JointState` s) <$> get theTimeInfo buf
      }

infixl 4 *-*

-- | Parallely compose two 'ResamplingBuffer's.
(*-*) ::
  Monad m =>
  ResamplingBuffer m cl1 cl2  a      b    ->
  ResamplingBuffer m cl1 cl2     c      d ->
  ResamplingBuffer m cl1 cl2 (a, c) (b, d)
ResamplingBuffer buf1 put1 get1 *-* ResamplingBuffer buf2 put2 get2 = ResamplingBuffer
  {
    buffer = JointState buf1 buf2
  , put = \theTimeInfo (a, c) (JointState s1 s2) -> do
      s1' <- put1 theTimeInfo a s1
      s2' <- put2 theTimeInfo c s2
      pure $! JointState s1' s2'
  , get = \theTimeInfo (JointState s1 s2) -> do
      Result s1' b <- get1 theTimeInfo s1
      Result s2' d <- get2 theTimeInfo s2
      pure $! Result (JointState s1' s2') (b, d)
  }

infixl 4 &-&

-- | Parallely compose two 'ResamplingBuffer's, duplicating the input.
(&-&) ::
  Monad m =>
  ResamplingBuffer m cl1 cl2  a  b    ->
  ResamplingBuffer m cl1 cl2  a     c ->
  ResamplingBuffer m cl1 cl2  a (b, c)
resBuf1 &-& resBuf2 = arr (\a -> (a, a)) ^->> resBuf1 *-* resBuf2

{- | Given a 'ResamplingBuffer' where the output type depends on the input type polymorphically,
   we can produce a timestamped version that simply annotates every input value
   with the 'TimeInfo' when it arrived.
-}
timestamped ::
  Monad m =>
  (forall b. ResamplingBuffer m cl clf b (f b)) ->
  ResamplingBuffer m cl clf a (f (a, TimeInfo cl))
timestamped resBuf = (clId &&& timeInfo) ^->> resBuf

infixl 4 |-|

-- | Combine two 'ResamplingBuffer's in parallel input time.
--
-- The resulting 'ResamplingBuffer' will consume input whenever either of the input clocks ticks.
--
-- Caution: The time differences are split up between the two buffers, so the total passed time on the inputs is not the same as on the output.
(|-|) ::
  ( Monad m,
    TimeDomain (Time cl),
    Time clL ~ Time cl,
    Time clR ~ Time cl
  ) =>
  ResamplingBuffer m clL cl a b ->
  ResamplingBuffer m clR cl a c ->
  ResamplingBuffer m (ParallelClock clL clR) cl a (b, c)
ResamplingBuffer stateL putL getL |-| ResamplingBuffer stateR putR getR =
  ResamplingBuffer
    { buffer = JointState (JointState Nothing stateL) (JointState Nothing stateR),
      put = \theTimeInfo a (JointState (JointState lastTimeMaybeL sL) (JointState lastTimeMaybeR sR)) -> do
        let now = absolute theTimeInfo
        case tag theTimeInfo of
          Left tagL -> do
            sL' <- putL (theTimeInfo & retag (const tagL) & fixSinceLast lastTimeMaybeL) a sL
            pure $! JointState (JointState (Just now) sL') (JointState lastTimeMaybeR sR)
          Right tagR -> do
            sR' <- putR (theTimeInfo & retag (const tagR) & fixSinceLast lastTimeMaybeR) a sR
            pure $! JointState (JointState lastTimeMaybeL sL) (JointState (Just now) sR'),
      get = \theTimeInfo (JointState (JointState lastTimeMaybeL sL) (JointState lastTimeMaybeR sR)) -> do
        Result sL' b <- getL theTimeInfo sL
        Result sR' c <- getR theTimeInfo sR
        pure $! Result (JointState (JointState lastTimeMaybeL sL') (JointState lastTimeMaybeR sR')) (b, c)
    }

infixl 4 ||-||

-- | Combine two 'ResamplingBuffer's in parallel output time.
--
-- The resulting 'ResamplingBuffer' will produce output whenever either of the output clocks ticks.
--
-- Caution: The time differences are split up between the two buffers, so the total passed time on the input is not the same as on the outputs.
(||-||) ::
  ( Monad m,
    TimeDomain (Time cl),
    Time clL ~ Time cl,
    Time clR ~ Time cl
  ) =>
  ResamplingBuffer m cl                clL      a b ->
  ResamplingBuffer m cl                    clR  a b ->
  ResamplingBuffer m cl (ParallelClock clL clR) a b
ResamplingBuffer stateL putL getL ||-|| ResamplingBuffer stateR putR getR =
  ResamplingBuffer
    { buffer = JointState (JointState Nothing stateL) (JointState Nothing stateR),
      put = \theTimeInfo a (JointState (JointState lastTimeMaybeL sL) (JointState lastTimeMaybeR sR)) -> do
        sL' <- putL theTimeInfo a sL
        sR' <- putR theTimeInfo a sR
        pure $! JointState (JointState lastTimeMaybeL sL') (JointState lastTimeMaybeR sR'),
      get = \theTimeInfo (JointState (JointState lastTimeMaybeL sL) (JointState lastTimeMaybeR sR)) -> case tag theTimeInfo of
        Left tagL -> do
          Result sL' b <- getL (theTimeInfo & retag (const tagL) & fixSinceLast lastTimeMaybeL) sL
          pure $! Result (JointState (JointState lastTimeMaybeL sL') (JointState lastTimeMaybeR sR)) b
        Right tagR -> do
          Result sR' b <- getR (theTimeInfo & retag (const tagR) & fixSinceLast lastTimeMaybeR) sR
          pure $! Result (JointState (JointState lastTimeMaybeL sL) (JointState lastTimeMaybeR sR')) b
    }

-- | Helper function for 'ResamplingBuffer's over 'ParallelClock's to fix the 'sinceLast' field of the 'TimeInfo'.
fixSinceLast :: (TimeDomain (Time cl)) => Maybe (Time cl) -> TimeInfo cl -> TimeInfo cl
fixSinceLast lastTimeMaybe theTimeInfo = case lastTimeMaybe of
  Nothing -> theTimeInfo
  Just lastTime -> theTimeInfo {sinceLast = absolute theTimeInfo `diffTime` lastTime}