-- |
-- 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 :: forall a. [a] -> [DTime] -> SignalSampleStream a
groupDeltas [a]
xs [DTime]
ds = forall a b. [a] -> [b] -> [(a, b)]
zip (DTime
0forall a. a -> [a] -> [a]
:[DTime]
ds) [a]
xs

-- * Obtain samples

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

-- | Return the first sample in a signal sample stream.
firstSample :: SignalSampleStream a -> a
firstSample :: forall a. SignalSampleStream a -> a
firstSample = forall a. [a] -> a
head forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. SignalSampleStream a -> [a]
samples

-- | Return the last sample in a signal sample stream.
lastSample :: SignalSampleStream a -> a
lastSample :: forall a. SignalSampleStream a -> a
lastSample = forall a. [a] -> a
last forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. SignalSampleStream a -> [a]
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 :: forall a.
(a -> a -> a)
-> SignalSampleStream a
-> SignalSampleStream a
-> SignalSampleStream a
sMerge a -> a -> a
_ []                [(DTime, a)]
xs2               = [(DTime, a)]
xs2
sMerge a -> a -> a
_ [(DTime, a)]
xs1               []                = [(DTime, a)]
xs1
sMerge a -> a -> a
f ((DTime
dt1, a
x1) : [(DTime, a)]
xs1) ((DTime
dt2, a
x2) : [(DTime, a)]
xs2)
  | DTime
dt1 forall a. Eq a => a -> a -> Bool
== DTime
dt2 = (DTime
dt1, a -> a -> a
f a
x1 a
x2) forall a. a -> [a] -> [a]
: forall a.
(a -> a -> a)
-> SignalSampleStream a
-> SignalSampleStream a
-> SignalSampleStream a
sMerge a -> a -> a
f [(DTime, a)]
xs1 [(DTime, a)]
xs2
  | DTime
dt1 forall a. Ord a => a -> a -> Bool
<  DTime
dt2 = (DTime
dt1, a
x1) forall a. a -> [a] -> [a]
: forall a.
(a -> a -> a)
-> SignalSampleStream a
-> SignalSampleStream a
-> SignalSampleStream a
sMerge a -> a -> a
f [(DTime, a)]
xs1 ((DTime
dt2 forall a. Num a => a -> a -> a
- DTime
dt1, a
x2) forall a. a -> [a] -> [a]
: [(DTime, a)]
xs2)
  | Bool
otherwise  = (DTime
dt2, a
x2) forall a. a -> [a] -> [a]
: forall a.
(a -> a -> a)
-> SignalSampleStream a
-> SignalSampleStream a
-> SignalSampleStream a
sMerge a -> a -> a
f ((DTime
dt1 forall a. Num a => a -> a -> a
- DTime
dt2, a
x1) forall a. a -> [a] -> [a]
: [(DTime, a)]
xs1) [(DTime, a)]
xs2

-- ** Concatenating

-- | Concatenate two sample streams, separating them by a given time delta.
sConcat :: SignalSampleStream a -> SignalSampleStream a -> SignalSampleStream a
sConcat :: forall a.
SignalSampleStream a
-> SignalSampleStream a -> SignalSampleStream a
sConcat SignalSampleStream a
xs1 SignalSampleStream a
xs2 = SignalSampleStream a
xs1 forall a. [a] -> [a] -> [a]
++ SignalSampleStream a
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 :: forall a.
DTime -> a -> SignalSampleStream a -> SignalSampleStream a
sRefine DTime
_     a
_  []             = []
sRefine DTime
maxDT a
a0 ((DTime
dt, a
a) : [(DTime, a)]
as)
  | DTime
dt forall a. Ord a => a -> a -> Bool
> DTime
maxDT = (DTime
maxDT, a
a0) forall a. a -> [a] -> [a]
: forall a.
DTime -> a -> SignalSampleStream a -> SignalSampleStream a
sRefine DTime
maxDT a
a0 ((DTime
dt forall a. Num a => a -> a -> a
- DTime
maxDT, a
a) forall a. a -> [a] -> [a]
: [(DTime, a)]
as)
  | Bool
otherwise  = (DTime
dt, a
a) forall a. a -> [a] -> [a]
: forall a.
DTime -> a -> SignalSampleStream a -> SignalSampleStream a
sRefine DTime
maxDT a
a [(DTime, 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 :: forall a.
(a -> a -> a)
-> DTime -> a -> SignalSampleStream a -> SignalSampleStream a
refineWith a -> a -> a
_           DTime
_     a
_  []             = []
refineWith a -> a -> a
interpolate DTime
maxDT a
a0 ((DTime
dt, a
a) : [(DTime, a)]
as)
    | DTime
dt forall a. Ord a => a -> a -> Bool
> DTime
maxDT
    = (DTime
maxDT, a -> a -> a
interpolate a
a0 a
a) forall a. a -> [a] -> [a]
:
        forall a.
(a -> a -> a)
-> DTime -> a -> SignalSampleStream a -> SignalSampleStream a
refineWith a -> a -> a
interpolate DTime
maxDT a
a' ((DTime
dt forall a. Num a => a -> a -> a
- DTime
maxDT, a
a) forall a. a -> [a] -> [a]
: [(DTime, a)]
as)
    | Bool
otherwise
    = (DTime
dt, a
a) forall a. a -> [a] -> [a]
: forall a.
(a -> a -> a)
-> DTime -> a -> SignalSampleStream a -> SignalSampleStream a
refineWith a -> a -> a
interpolate DTime
maxDT a
a [(DTime, a)]
as
  where
    a' :: a
a' = a -> a -> a
interpolate a
a0 a
a

-- ** Clipping (dropping samples)

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

-- | Clip a signal sample stream after a certain (non-zero) time.
sClipAfterTime :: DTime -> SignalSampleStream a -> SignalSampleStream a
sClipAfterTime :: forall a. DTime -> SignalSampleStream a -> SignalSampleStream a
sClipAfterTime DTime
_  []              = []
sClipAfterTime DTime
dt ((DTime
dt', a
x) : [(DTime, a)]
xs)
  | DTime
dt forall a. Ord a => a -> a -> Bool
< DTime
dt'  = []
  | Bool
otherwise = (DTime
dt', a
x) forall a. a -> [a] -> [a]
: forall a. DTime -> SignalSampleStream a -> SignalSampleStream a
sClipAfterTime (DTime
dt forall a. Num a => a -> a -> a
- DTime
dt') [(DTime, a)]
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 :: forall a. Int -> SignalSampleStream a -> SignalSampleStream a
sClipBeforeFrame Int
0 xs :: SignalSampleStream a
xs@((DTime, a)
_:SignalSampleStream a
_) = SignalSampleStream a
xs
sClipBeforeFrame Int
_ xs :: SignalSampleStream a
xs@[(DTime, a)
_]   = SignalSampleStream a
xs
sClipBeforeFrame Int
n SignalSampleStream a
xs       = forall a. Int -> SignalSampleStream a -> SignalSampleStream a
sClipBeforeFrame (Int
n forall a. Num a => a -> a -> a
- Int
1) SignalSampleStream a
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 :: forall a. DTime -> SignalSampleStream a -> SignalSampleStream a
sClipBeforeTime DTime
dt SignalSampleStream a
xs
    | DTime
dt forall a. Ord a => a -> a -> Bool
<= DTime
0        = SignalSampleStream a
xs
    | forall (t :: * -> *) a. Foldable t => t a -> Int
length SignalSampleStream a
xs forall a. Eq a => a -> a -> Bool
== Int
1 = SignalSampleStream a
xs
    | DTime
dt forall a. Ord a => a -> a -> Bool
< DTime
dt'       = (DTime
dt' forall a. Num a => a -> a -> a
- DTime
dt, a
x') forall a. a -> [a] -> [a]
: SignalSampleStream a
xs'
    | Bool
otherwise      = forall a. DTime -> SignalSampleStream a -> SignalSampleStream a
sClipBeforeTime (DTime
dt forall a. Num a => a -> a -> a
- DTime
dt') ((DTime
0, a
x') forall a. a -> [a] -> [a]
: SignalSampleStream a
xs')
  where
    ((DTime, a)
_ : (DTime
dt', a
x') : SignalSampleStream a
xs') = SignalSampleStream a
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 :: forall (m :: * -> *) a b.
Monad m =>
MSF (ReaderT DTime m) a b
-> SignalSampleStream a
-> m (SampleStream b, MSF (ReaderT DTime m) a b)
evalSF MSF (ReaderT DTime m) a b
fsf SignalSampleStream a
as = do
    (SampleStream b
ss, MSF m (DTime, a) b
msf') <- forall (m :: * -> *) a b.
Monad m =>
MSF m a b -> SampleStream a -> m (SampleStream b, MSF m a b)
evalMSF MSF m (DTime, a) b
msf'' SignalSampleStream a
as
    forall (m :: * -> *) a. Monad m => a -> m a
return (SampleStream b
ss, forall (m :: * -> *) r a b.
Monad m =>
MSF m (r, a) b -> MSF (ReaderT r m) a b
readerS MSF m (DTime, a) b
msf')
  where
    msf'' :: MSF m (DTime, a) b
msf'' = forall (m :: * -> *) r a b.
Monad m =>
MSF (ReaderT r m) a b -> MSF m (r, a) b
runReaderS MSF (ReaderT DTime m) a b
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 :: forall (m :: * -> *) a b.
Monad m =>
MSF m a b -> SampleStream a -> m (SampleStream b, MSF m a b)
evalMSF MSF m a b
fsf []     = forall (m :: * -> *) a. Monad m => a -> m a
return ([], MSF m a b
fsf)
evalMSF MSF m a b
fsf (a
a:[a]
as) = do
  (b
b, MSF m a b
fsf')   <- forall (m :: * -> *) a b. MSF m a b -> a -> m (b, MSF m a b)
unMSF MSF m a b
fsf a
a
  (SampleStream b
bs, MSF m a b
fsf'') <- forall (m :: * -> *) a b.
Monad m =>
MSF m a b -> SampleStream a -> m (SampleStream b, MSF m a b)
evalMSF MSF m a b
fsf' [a]
as
  let outputStrm :: SampleStream b
outputStrm = b
b forall a. a -> [a] -> [a]
: SampleStream b
bs
  forall (m :: * -> *) a. Monad m => a -> m a
return (SampleStream b
outputStrm, MSF m a b
fsf'')