rhine 1.6 → 1.7
raw patch · 14 files changed
+146/−46 lines, 14 filesdep ~automatondep ~monad-scheduledep ~time-domain
Dependency ranges changed: automaton, monad-schedule, time-domain
Files
- rhine.cabal +4/−4
- src/FRP/Rhine/ClSF/Except.hs +1/−0
- src/FRP/Rhine/ClSF/Util.hs +1/−0
- src/FRP/Rhine/Clock.hs +10/−7
- src/FRP/Rhine/Clock/FixedStep.hs +7/−4
- src/FRP/Rhine/Clock/Periodic.hs +8/−5
- src/FRP/Rhine/Clock/Realtime/Audio.hs +5/−5
- src/FRP/Rhine/Clock/Realtime/Millisecond.hs +5/−3
- src/FRP/Rhine/Clock/Select.hs +4/−3
- src/FRP/Rhine/ResamplingBuffer.hs +6/−4
- src/FRP/Rhine/ResamplingBuffer/ClSF.hs +5/−4
- src/FRP/Rhine/ResamplingBuffer/Interpolation.hs +3/−2
- src/FRP/Rhine/ResamplingBuffer/Util.hs +79/−0
- test/Schedule.hs +8/−5
rhine.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: rhine-version: 1.6+version: 1.7 synopsis: Functional Reactive Programming with type-level clocks description: Rhine is a library for synchronous and asynchronous Functional Reactive Programming (FRP).@@ -49,13 +49,14 @@ common opts build-depends:- automaton ^>=1.6,+ automaton ^>=1.7, base >=4.16 && <4.22,- monad-schedule ^>=1.6,+ monad-schedule ^>=1.7, mtl >=2.2 && <2.4, selective ^>=0.7, text >=1.2 && <2.2, time >=1.8,+ time-domain ^>=1.7, transformers >=0.5, vector-sized >=1.4, @@ -159,7 +160,6 @@ sop-core ^>=0.5, text >=1.2 && <2.2, time >=1.8,- time-domain ^>=1.6, transformers >=0.5, -- Directories containing source files.
src/FRP/Rhine/ClSF/Except.hs view
@@ -61,6 +61,7 @@ if b then throwS -< e else returnA -< ()+{-# INLINEABLE throwOn' #-} -- | Throw the exception 'e' whenever the function evaluates to 'True'. throwOnCond :: (Monad m) => (a -> Bool) -> e -> ClSF (ExceptT e m) cl a a
src/FRP/Rhine/ClSF/Util.hs view
@@ -45,6 +45,7 @@ {- | Utility to apply functions to the current 'TimeInfo', such as record selectors:+ @ printAbsoluteTime :: ClSF IO cl () () printAbsoluteTime = timeInfoOf absolute >>> arrMCl print
src/FRP/Rhine/Clock.hs view
@@ -58,13 +58,15 @@ -- | The time domain, i.e. type of the time stamps the clock creates. type Time cl - -- | Additional information that the clock may output at each tick,- -- e.g. if a realtime promise was met, if an event occurred,- -- if one of its subclocks (if any) ticked.+ {- | Additional information that the clock may output at each tick,+ e.g. if a realtime promise was met, if an event occurred,+ if one of its subclocks (if any) ticked.+ -} type Tag cl - -- | The method that produces to a clock value a running clock,- -- i.e. an effectful stream of tagged time stamps together with an initialisation time.+ {- | The method that produces to a clock value a running clock,+ i.e. an effectful stream of tagged time stamps together with an initialisation time.+ -} initClock :: -- | The clock value, containing e.g. settings or device parameters cl ->@@ -190,8 +192,9 @@ { unscaledClockS :: cl -- ^ The clock before the rescaling , rescaleS :: RescalingSInit m cl time tag- -- ^ The rescaling stream function, and rescaled initial time,- -- depending on the initial time before rescaling+ {- ^ The rescaling stream function, and rescaled initial time,+ depending on the initial time before rescaling+ -} } instance
src/FRP/Rhine/Clock/FixedStep.hs view
@@ -24,6 +24,9 @@ import Control.Monad.Schedule.Class import Control.Monad.Schedule.Trans (ScheduleT, wait) +-- time-domain+import Data.TimeDomain (Seconds (..))+ -- automaton import Data.Automaton (accumulateWith, arrM) @@ -43,11 +46,11 @@ FixedStep :: (KnownNat n) => FixedStep n -- TODO Does the constraint bring any benefit? -- | Extract the type-level natural number as an integer.-stepsize :: FixedStep n -> Integer-stepsize fixedStep@FixedStep = natVal fixedStep+stepsize :: FixedStep n -> Seconds Integer+stepsize fixedStep@FixedStep = Seconds $ natVal fixedStep -instance (MonadSchedule m, Monad m) => Clock (ScheduleT Integer m) (FixedStep n) where- type Time (FixedStep n) = Integer+instance (MonadSchedule m, Monad m) => Clock (ScheduleT (Seconds Integer) m) (FixedStep n) where+ type Time (FixedStep n) = Seconds Integer type Tag (FixedStep n) = () initClock cl = let step = stepsize cl
src/FRP/Rhine/Clock/Periodic.hs view
@@ -22,6 +22,9 @@ -- monad-schedule import Control.Monad.Schedule.Trans +-- time-domain+import Data.TimeDomain (Seconds (..))+ -- automaton import Data.Automaton (Automaton (..), accumulateWith, concatS, withSideEffect) @@ -43,9 +46,9 @@ instance (Monad m, NonemptyNatList v) =>- Clock (ScheduleT Integer m) (Periodic v)+ Clock (ScheduleT (Seconds Integer) m) (Periodic v) where- type Time (Periodic v) = Integer+ type Time (Periodic v) = Seconds Integer type Tag (Periodic v) = () initClock cl = return@@ -68,16 +71,16 @@ tailCl Periodic = Periodic class NonemptyNatList (v :: [Nat]) where- theList :: Periodic v -> NonEmpty Integer+ theList :: Periodic v -> NonEmpty (Seconds Integer) instance (KnownNat n) => NonemptyNatList '[n] where- theList cl = headCl cl :| []+ theList cl = Seconds (headCl cl) :| [] instance (KnownNat n1, KnownNat n2, NonemptyNatList (n2 : ns)) => NonemptyNatList (n1 : n2 : ns) where- theList cl = headCl cl <| theList (tailCl cl)+ theList cl = Seconds (headCl cl) <| theList (tailCl cl) -- * Utilities
src/FRP/Rhine/Clock/Realtime/Audio.hs view
@@ -34,7 +34,7 @@ import Data.Automaton.Trans.Except hiding (step) -- time-domain-import Data.TimeDomain (diffTime)+import Data.TimeDomain (Seconds (..), diffTime) -- rhine import FRP.Rhine.Clock@@ -119,7 +119,7 @@ currentTime <- once_ $ liftIO getCurrentTime let lateDiff = currentTime `diffTime` bufferFullTime- late = if lateDiff > 0 then Just lateDiff else Nothing+ late = if lateDiff > 0 then Just $ getSeconds lateDiff else Nothing safe $ runningClock bufferFullTime late initialTime <- liftIO getCurrentTime return@@ -148,12 +148,12 @@ thePureRateNum = fromInteger . thePureRateIntegral instance (Monad m, PureAudioClockRate rate) => Clock m (PureAudioClock rate) where- type Time (PureAudioClock rate) = Double+ type Time (PureAudioClock rate) = Seconds Double type Tag (PureAudioClock rate) = () initClock audioClock = return- ( arr (const (1 / thePureRateNum audioClock)) >>> sumS &&& arr (const ())+ ( arr (const (1 / thePureRateNum audioClock)) >>> sumN &&& arr (const ()) , 0 ) {-# INLINE initClock #-}@@ -170,5 +170,5 @@ pureAudioClockF = RescaledClock { unscaledClock = PureAudioClock- , rescale = double2Float+ , rescale = double2Float . getSeconds }
src/FRP/Rhine/Clock/Realtime/Millisecond.hs view
@@ -17,6 +17,8 @@ import Data.Time.Clock -- rhine++import Data.TimeDomain (Seconds (..)) import FRP.Rhine.Clock import FRP.Rhine.Clock.FixedStep import FRP.Rhine.Clock.Proxy@@ -35,16 +37,16 @@ where 'Nothing' represents successful realtime, and @'Just' lag@ a lag (in seconds). -}-newtype Millisecond (n :: Nat) = Millisecond (WaitUTCClock IO (RescaledClock (UnscheduleClock IO (FixedStep n)) Double))+newtype Millisecond (n :: Nat) = Millisecond (WaitUTCClock IO (RescaledClock (UnscheduleClock IO (FixedStep n)) (Seconds Double))) instance Clock IO (Millisecond n) where type Time (Millisecond n) = UTCTime type Tag (Millisecond n) = Maybe Double- initClock (Millisecond cl) = initClock cl <&> first (>>> arr (second snd))+ initClock (Millisecond cl) = initClock cl <&> first (>>> arr (second (fmap getSeconds . snd))) {-# INLINE initClock #-} instance GetClockProxy (Millisecond n) -- | Tries to achieve real time by using 'waitUTC', see its docs. waitClock :: (KnownNat n) => Millisecond n-waitClock = Millisecond $ waitUTC $ RescaledClock (unyieldClock FixedStep) ((/ 1000) . fromInteger)+waitClock = Millisecond $ waitUTC $ RescaledClock (unyieldClock FixedStep) ((/ 1000) . fromInteger . getSeconds)
src/FRP/Rhine/Clock/Select.hs view
@@ -34,9 +34,10 @@ -} data SelectClock cl a = SelectClock { mainClock :: cl- -- ^ The main clock- -- | Return 'Nothing' if no tick of the subclock is required,- -- or 'Just a' if the subclock should tick, with tag 'a'.+ {- ^ The main clock+ | Return 'Nothing' if no tick of the subclock is required,+ or 'Just a' if the subclock should tick, with tag 'a'.+ -} , select :: Tag cl -> Maybe a }
src/FRP/Rhine/ResamplingBuffer.hs view
@@ -53,14 +53,16 @@ a -> s -> m s- -- ^ Store one input value of type 'a' at a given time stamp,- -- and return an updated state.+ {- ^ Store one input value of type 'a' at a given time stamp,+ and return an updated state.+ -} , get :: TimeInfo clb -> s -> m (Result s b)- -- ^ Retrieve one output value of type 'b' at a given time stamp,- -- and an updated state.+ {- ^ Retrieve one output value of type 'b' at a given time stamp,+ and an updated state.+ -} } -- | A type synonym to allow for abbreviation.
src/FRP/Rhine/ResamplingBuffer/ClSF.hs view
@@ -24,10 +24,11 @@ -} clsfBuffer :: (Monad m) =>- -- | The clocked signal function that consumes- -- and a list of timestamped inputs,- -- and outputs a single value.- -- The list will contain the /newest/ element in the head.+ {- | The clocked signal function that consumes+ and a list of timestamped inputs,+ and outputs a single value.+ The list will contain the /newest/ element in the head.+ -} ClSF m cl2 [(TimeInfo cl1, a)] b -> ResamplingBuffer m cl1 cl2 a b clsfBuffer = clsfBuffer' . toStreamT . getAutomaton
src/FRP/Rhine/ResamplingBuffer/Interpolation.hs view
@@ -67,8 +67,9 @@ , s ~ Diff (Time cl1) , s ~ Diff (Time cl2) ) =>- -- | The size of the interpolation window- -- (for how long in the past to remember incoming values)+ {- | The size of the interpolation window+ (for how long in the past to remember incoming values)+ -} s -> ResamplingBuffer m cl1 cl2 v v sinc windowSize =
src/FRP/Rhine/ResamplingBuffer/Util.hs view
@@ -5,9 +5,15 @@ -} 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 (..))@@ -18,6 +24,7 @@ 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 @@ -102,3 +109,75 @@ (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}
test/Schedule.hs view
@@ -16,6 +16,9 @@ -- monad-schedule import Control.Monad.Schedule.Trans (Schedule, runScheduleT, wait) +-- time-domain+import Data.TimeDomain (Seconds)+ -- automaton import Data.Automaton (accumulateWith, constM, embed) @@ -31,10 +34,10 @@ [ testGroup "scheduleList" [ testCase "schedule waits chronologically" $ do- let output = runIdentity $ runScheduleT (const (pure ())) $ embed (scheduleList $ (\n -> constM (wait n $> n) >>> accumulateWith (+) 0) <$> [3 :: Integer, 5]) $ replicate 6 ()+ let output = runIdentity $ runScheduleT (const (pure ())) $ embed (scheduleList $ (\n -> constM (wait n $> n) >>> accumulateWith (+) 0) <$> [3 :: Seconds Integer, 5]) $ replicate 6 () output @?= pure <$> [3, 5, 6, 9, 10, 12] , testCase "schedule waits chronologically (mirrored)" $ do- let output = runSchedule $ embed (scheduleList $ (\n -> constM (wait n $> n) >>> accumulateWith (+) 0) <$> [5 :: Integer, 3]) $ replicate 6 ()+ let output = runSchedule $ embed (scheduleList $ (\n -> constM (wait n $> n) >>> accumulateWith (+) 0) <$> [5 :: Seconds Integer, 3]) $ replicate 6 () output @?= pure <$> [3, 5, 6, 9, 10, 12] ] , testGroup@@ -42,8 +45,8 @@ [ testCase "chronological ticks" $ do let clA = FixedStep @5 clB = FixedStep @3- (runningClockA, _) = runSchedule (initClock clA :: RunningClockInit (Schedule Integer) Integer ())- (runningClockB, _) = runSchedule (initClock clB :: RunningClockInit (Schedule Integer) Integer ())+ (runningClockA, _) = runSchedule (initClock clA :: RunningClockInit (Schedule (Seconds Integer)) (Seconds Integer) ())+ (runningClockB, _) = runSchedule (initClock clB :: RunningClockInit (Schedule (Seconds Integer)) (Seconds Integer) ()) output = runSchedule $ embed (runningSchedule clA clB runningClockA runningClockB) $ replicate 6 () output @?= [ (3, Right ())@@ -58,7 +61,7 @@ "ParallelClock" [ testCase "chronological ticks" $ do let- (runningClock, _time) = runSchedule (initClock $ ParallelClock (FixedStep @5) (FixedStep @3) :: RunningClockInit (Schedule Integer) Integer (Either () ()))+ (runningClock, _time) = runSchedule (initClock $ ParallelClock (FixedStep @5) (FixedStep @3) :: RunningClockInit (Schedule (Seconds Integer)) (Seconds Integer) (Either () ())) output = runSchedule $ embed runningClock $ replicate 6 () output @?= [ (3, Right ())