packages feed

dunai-test-0.11.0: src/FRP/Dunai/Stream.hs

-- |
-- Copyright  : (c) Ivan Perez, 2017-2023
-- License    : BSD3
-- Maintainer : ivan.perez@keera.co.uk
--
-- Streams and stream manipulation API.
--
-- The evaluation of Dunai MSFs, especially for testing purposes, needs the
-- generation of suitable input streams.
--
-- While some streams can be generated randomly using QuickCheck, it is
-- sometimes useful to be able to preprend or adapt an input stream. It is also
-- useful to debug programs when you have recorded input streams using Haskell
-- Titan.
--
-- This module defines types for input streams, as well as an API to create,
-- examine and combine streams. It also provides evaluation functions that are
-- needed to apply an MSF to a stream and obtain an output stream and a
-- continuation MSF.
module FRP.Dunai.Stream where

-- External imports
import Control.Monad.Trans.MSF.Reader          (ReaderT, readerS, runReaderS)
import Data.MonadicStreamFunction              (MSF)
import Data.MonadicStreamFunction.InternalCore (unMSF)

-- * Types

-- | A stream of samples, with their sampling times.
type SignalSampleStream a = SampleStream (DTime, a)

-- | A stream of samples, with no sampling time.
type SampleStream a = [a]

-- | DTime is the time type for lengths of sample intervals. Conceptually,
-- DTime = R+ = { x in R | x > 0 }.
type DTime = Double

-- ** Creation

-- | Group a series of samples with a series of time deltas.
--
-- The first sample will have no delta. Unused samples and deltas will be
-- dropped.
groupDeltas :: [a] -> [DTime] -> SignalSampleStream a
groupDeltas xs ds = zip (0:ds) xs

-- * Obtain samples

-- | Turn a stream with sampling times into a list of values.
samples :: SignalSampleStream a -> [a]
samples = map snd

-- | Return the first sample in a signal sample stream.
firstSample :: SignalSampleStream a -> a
firstSample = head . samples

-- | Return the last sample in a signal sample stream.
lastSample :: SignalSampleStream a -> a
lastSample = last . samples

-- * Stream manipulation

-- ** Merging

-- | Merge two streams, using an auxiliary function to merge samples that fall
-- at the exact same sampling time.
sMerge :: (a -> a -> a)
       -> SignalSampleStream a
       -> SignalSampleStream a
       -> SignalSampleStream a
sMerge _ []                xs2               = xs2
sMerge _ xs1               []                = xs1
sMerge f ((dt1, x1) : xs1) ((dt2, x2) : xs2)
  | dt1 == dt2 = (dt1, f x1 x2) : sMerge f xs1 xs2
  | dt1 <  dt2 = (dt1, x1) : sMerge f xs1 ((dt2 - dt1, x2) : xs2)
  | otherwise  = (dt2, x2) : sMerge f ((dt1 - dt2, x1) : xs1) xs2

-- ** Concatenating

-- | Concatenate two sample streams, separating them by a given time delta.
sConcat :: SignalSampleStream a -> SignalSampleStream a -> SignalSampleStream a
sConcat xs1 xs2 = xs1 ++ xs2

-- ** Refining

-- | Refine a signal sample stream by establishing the maximum time delta.
--
-- If two samples are separated by a time delta bigger than the given max DT,
-- the former is replicated as many times as necessary.
sRefine :: DTime -> a -> SignalSampleStream a -> SignalSampleStream a
sRefine _     _  []             = []
sRefine maxDT a0 ((dt, a) : as)
  | dt > maxDT = (maxDT, a0) : sRefine maxDT a0 ((dt - maxDT, a) : as)
  | otherwise  = (dt, a) : sRefine maxDT a as

-- | Refine a stream by establishing the maximum time delta.
--
-- If two samples are separated by a time delta bigger than the given max DT,
-- the auxiliary interpolation function is used to determine the intermediate
-- sample.
refineWith :: (a -> a -> a)
           -> DTime
           -> a
           -> SignalSampleStream a
           -> SignalSampleStream a
refineWith _           _     _  []             = []
refineWith interpolate maxDT a0 ((dt, a) : as)
    | dt > maxDT
    = (maxDT, interpolate a0 a) :
        refineWith interpolate maxDT a' ((dt - maxDT, a) : as)
    | otherwise
    = (dt, a) : refineWith interpolate maxDT a as
  where
    a' = interpolate a0 a

-- ** Clipping (dropping samples)

-- | Clip a signal sample stream at a given number of samples.
sClipAfterFrame :: Int -> SignalSampleStream a -> SignalSampleStream a
sClipAfterFrame = take

-- | Clip a signal sample stream after a certain (non-zero) time.
sClipAfterTime :: DTime -> SignalSampleStream a -> SignalSampleStream a
sClipAfterTime _  []              = []
sClipAfterTime dt ((dt', x) : xs)
  | dt < dt'  = []
  | otherwise = (dt', x) : sClipAfterTime (dt - dt') xs

-- | Drop the first n samples of a signal sample stream. The time deltas are
-- not re-calculated.
sClipBeforeFrame :: Int -> SignalSampleStream a -> SignalSampleStream a
sClipBeforeFrame 0 xs@(_:_) = xs
sClipBeforeFrame _ xs@[_]   = xs
sClipBeforeFrame n xs       = sClipBeforeFrame (n - 1) xs

-- | Drop the first samples of a signal sample stream up to a given time. The
-- time deltas are not re-calculated to match the original stream.
sClipBeforeTime :: DTime -> SignalSampleStream a -> SignalSampleStream a
sClipBeforeTime dt xs
    | dt <= 0        = xs
    | length xs == 1 = xs
    | dt < dt'       = (dt' - dt, x') : xs'
    | otherwise      = sClipBeforeTime (dt - dt') ((0, x') : xs')
  where
    (_ : (dt', x') : xs') = xs

-- | Evaluate an SF with a 'SignalSampleStream', obtaining an output stream and
-- a continuation.
--
-- You should never use this for actual execution in your applications, only
-- for testing.
evalSF :: Monad m
       => MSF (ReaderT DTime m) a b
       -> SignalSampleStream a
       -> m (SampleStream b, MSF (ReaderT DTime m) a b)
evalSF fsf as = do
    (ss, msf') <- evalMSF msf'' as
    return (ss, readerS msf')
  where
    msf'' = runReaderS fsf

-- | Evaluate an MSF with a 'SampleStream', obtaining an output stream and a
-- continuation.
--
-- You should never use this for actual execution in your applications, only
-- for testing.
evalMSF :: Monad m
        => MSF m a b
        -> SampleStream a
        -> m (SampleStream b, MSF m a b)
evalMSF fsf []     = return ([], fsf)
evalMSF fsf (a:as) = do
  (b, fsf')   <- unMSF fsf a
  (bs, fsf'') <- evalMSF fsf' as
  let outputStrm = b : bs
  return (outputStrm, fsf'')