-- | -- Module: FRP.Netwire.Analyze -- Copyright: (c) 2013 Ertugrul Soeylemez -- License: BSD3 -- Maintainer: Ertugrul Soeylemez <es@ertes.de> module FRP.Netwire.Analyze ( -- * Linear graphs lAvg, lGraph, lGraphN, -- * Staircase graphs sAvg, sGraph, sGraphN, -- * Peaks highPeak, highPeakBy, lowPeak, lowPeakBy, -- * Debug avgFps, framerate ) where import qualified FRP.Netwire.Utils.Timeline as Tl import qualified Data.Foldable as F import qualified Data.Sequence as Seq import Control.Wire import Prelude hiding ((.), id) -- | Average framerate over the last given number of samples. One -- important thing to note is that the value of this wire will generally -- disagree with 'sAvg' composed with 'framerate'. This is expected, -- because this wire simply calculates the arithmetic mean, whereas -- 'sAvg' will actually integrate the framerate graph. -- -- Note: This wire is for debugging purposes only, because it exposes -- discrete time. Do not taint your application with discrete time. -- -- * Complexity: O(n) time and space wrt number of samples. avgFps :: (RealFloat b, HasTime t s) => Int -- ^ Number of samples. -> Wire s e m a b avgFps int | int < 1 = error "avgFps: Non-positive number of samples" avgFps int = loop' Seq.empty where intf = fromIntegral int afps = (/ intf) . F.foldl' (+) 0 loop' ss' = mkSF $ \ds _ -> let fps = recip . realToFrac . dtime $ ds ss = Seq.take int (fps Seq.<| ss') in if isInfinite fps then (afps ss', loop' ss') else ss `seq` (afps ss, loop' ss) -- | Current framerate. -- -- Note: This wire is for debugging purposes only, because it exposes -- discrete time. Do not taint your application with discrete time. -- -- * Inhibits: when the clock stopped ticking. framerate :: (Eq b, Fractional b, HasTime t s, Monoid e) => Wire s e m a b framerate = mkPure $ \ds _ -> let dt = realToFrac (dtime ds) in (if dt == 0 then Left mempty else Right (recip dt), framerate) -- | High peak. -- -- * Depends: now. highPeak :: (Ord a) => Wire s e m a a highPeak = highPeakBy compare -- | High peak with respect to the given comparison function. -- -- * Depends: now. highPeakBy :: (a -> a -> Ordering) -> Wire s e m a a highPeakBy = peakBy GT -- | Calculate the average of the signal over the given interval (from -- now). This is done by calculating the integral of the corresponding -- linearly interpolated graph and dividing it by the interval length. -- See 'Tl.linAvg' for details. -- -- Linear interpolation can be slow. If you don't need it, you can use -- the staircase variant 'sAvg'. -- -- Example: @lAvg 2@ -- -- * Complexity: O(s) space, O(s) time wrt number of samples in the -- interval. -- -- * Depends: now. lAvg :: (Fractional a, Fractional t, HasTime t s) => t -- ^ Interval size. -> Wire s e m a a lAvg int = mkSF $ \ds x -> let t = dtime ds in (x, loop' t (Tl.singleton t x)) where loop' t' tl' = mkSF $ \ds x -> let t = t' + dtime ds t0 = t - int tl = Tl.linCutL t0 (Tl.insert t x tl') a = Tl.linAvg t0 t tl in (a, loop' t tl) -- | Produce a linearly interpolated graph for the given points in time, -- where the magnitudes of the points are distances from /now/. -- -- Linear interpolation can be slow. If you don't need it, you can use -- the faster staircase variant 'sGraph'. -- -- Example: @lGraph [0, 1, 2]@ will output the interpolated inputs at -- /now/, one second before now and two seconds before now. -- -- * Complexity: O(s) space, O(n * log s) time, where s = number of -- samples in the interval, n = number of requested data points. -- -- * Depends: now. lGraph :: (Fractional a, Fractional t, HasTime t s) => [t] -- ^ Data points to produce. -> Wire s e m a [a] lGraph qts = mkSF $ \ds x -> let t = dtime ds in (x <$ qts, loop' t (Tl.singleton t x)) where earliest = maximum (map abs qts) loop' t' tl' = mkSF $ \ds x -> let t = t' + dtime ds tl = Tl.linCutL (t - earliest) (Tl.insert t x tl') ps = map (\qt -> Tl.linLookup (t - abs qt) tl) qts in (ps, loop' t tl) -- | Graph the given interval from now with the given number of evenly -- distributed points in time. Convenience interface to 'lGraph'. -- -- Linear interpolation can be slow. If you don't need it, you can use -- the faster staircase variant 'sGraphN'. -- -- * Complexity: O(s) space, O(n * log s) time, where s = number of -- samples in the interval, n = number of requested data points. -- -- * Depends: now. lGraphN :: (Fractional a, Fractional t, HasTime t s) => t -- ^ Interval to graph from now. -> Int -- ^ Number of data points to produce. -> Wire s e m a [a] lGraphN int n | int <= 0 = error "lGraphN: Non-positive interval" | n <= 0 = error "lGraphN: Non-positive number of data points" lGraphN int n = let n1 = n - 1 f qt = realToFrac int * fromIntegral qt / fromIntegral n1 in lGraph (map f [0..n1]) -- | Low peak. -- -- * Depends: now. lowPeak :: (Ord a) => Wire s e m a a lowPeak = lowPeakBy compare -- | Low peak with respect to the given comparison function. -- -- * Depends: now. lowPeakBy :: (a -> a -> Ordering) -> Wire s e m a a lowPeakBy = peakBy LT -- | Given peak with respect to the given comparison function. peakBy :: (Eq o) => o -- ^ This ordering means the first argument is larger. -> (a -> a -> o) -- ^ Compare two elements. -> Wire s e m a a peakBy o comp = mkSFN $ \x -> (x, loop' x) where loop' x' = mkSFN $ \x -> id &&& loop' $ if comp x x' == o then x else x' -- | Calculate the average of the signal over the given interval (from -- now). This is done by calculating the integral of the corresponding -- staircase graph and dividing it by the interval length. See -- 'Tl.scAvg' for details. -- -- See also 'lAvg'. -- -- Example: @sAvg 2@ -- -- * Complexity: O(s) space, O(s) time wrt number of samples in the -- interval. -- -- * Depends: now. sAvg :: (Fractional a, Fractional t, HasTime t s) => t -- ^ Interval size. -> Wire s e m a a sAvg int = mkSF $ \ds x -> let t = dtime ds in (x, loop' t (Tl.singleton t x)) where loop' t' tl' = mkSF $ \ds x -> let t = t' + dtime ds t0 = t - int tl = Tl.scCutL t0 (Tl.insert t x tl') a = Tl.scAvg t0 t tl in (a, loop' t tl) -- | Produce a staircase graph for the given points in time, where the -- magnitudes of the points are distances from /now/. -- -- See also 'lGraph'. -- -- Example: @sGraph [0, 1, 2]@ will output the inputs at /now/, one -- second before now and two seconds before now. -- -- * Complexity: O(s) space, O(n * log s) time, where s = number of -- samples in the interval, n = number of requested data points. -- -- * Depends: now. sGraph :: (Fractional t, HasTime t s) => [t] -- ^ Data points to produce. -> Wire s e m a [a] sGraph qts = mkSF $ \ds x -> let t = dtime ds in (x <$ qts, loop' t (Tl.singleton t x)) where earliest = maximum (map abs qts) loop' t' tl' = mkSF $ \ds x -> let t = t' + dtime ds tl = Tl.scCutL (t - earliest) (Tl.insert t x tl') ps = map (\qt -> Tl.scLookup (t - abs qt) tl) qts in (ps, loop' t tl) -- | Graph the given interval from now with the given number of evenly -- distributed points in time. Convenience interface to 'sGraph'. -- -- See also 'lGraphN'. -- -- * Complexity: O(s) space, O(n * log s) time, where s = number of -- samples in the interval, n = number of requested data points. -- -- * Depends: now. sGraphN :: (Fractional t, HasTime t s) => t -- ^ Interval to graph from now. -> Int -- ^ Number of data points to produce. -> Wire s e m a [a] sGraphN int n | int <= 0 = error "sGraphN: Non-positive interval" | n <= 0 = error "sGraphN: Non-positive number of data points" sGraphN int n = let n1 = n - 1 f qt = realToFrac int * fromIntegral qt / fromIntegral n1 in sGraph (map f [0..n1])