mediabus-0.3.0.0: src/Data/MediaBus/Basics/Ticks.hs
-- | This module contains the 'Ticks' time unit as well as the corresponding
-- known-at-compile-time 'StaticTicks' time unit. The time stamps are given as
-- quotient of a 'Rate' that indicates the number of 'Ticks' per second.
module Data.MediaBus.Basics.Ticks
( Rate(..)
, type Hz
, type OnePerPicoSecond
, KnownRate(..)
, HasRate(..)
, CoerceRate(..)
, getRate
, getRateProxy
, RateProxy(..)
, type PeriodDuration
, getPeriodDuration
, coerceRateTo8kHz
, coerceRateTo16kHz
, coerceRateTo48kHz
, coerceToDoubleRate
, Ticks(..)
, CanBeTicks
, type PicoSeconds
, type Ticks32
, mkTicks32
, type Ticks64
, mkTicks64
, type Ticks32At8000
, mkTicks32At8000
, type Ticks32At16000
, mkTicks32At16000
, type Ticks32At48000
, mkTicks32At48000
, type Ticks64At8000
, mkTicks64At8000
, type Ticks64At16000
, mkTicks64At16000
, type Ticks64At48000
, mkTicks64At48000
, nominalDiffTime
, convertTicks
, StaticTicks(..)
, KnownStaticTicks(..)
, type StaticTicksRate
, type StaticTicksTicks
, HasDuration(..)
, HasTimestamp(..)
, HasStaticDuration(..)
, getStaticDurationTicks
, getStaticDuration
, toStaticDurationProxy
, ticksFromStaticDuration
, (:/)
) where
import Control.DeepSeq
import Control.Lens
import Data.Default
import Data.Kind
import Data.MediaBus.Basics.Monotone
import Data.MediaBus.Basics.Series
import Data.Proxy
import Data.Time.Clock
import Data.Word
import GHC.Generics (Generic)
import GHC.TypeLits
import System.Random
import Test.QuickCheck
import Text.Printf
-- * Time data types
-- ** A basic Time Unit
-- | The known at ompile time, time unit in units per second.
newtype Rate =
Hertz Nat
-- | A more beautiful operator for 'Hertz'
type Hz r = 'Hertz r
-- | Analogous to 'KnownNat' this (kind-)class is for 'StaticTicks' with a runtime
-- 'Ticks' value.
class KnownRate (s :: Rate) where
-- | Return the runtime rate value in Hertz
rateVal :: proxy s -> Integer
-- | Return the compile time rate value in Hertz
type RateVal s :: Nat
instance (KnownNat r) =>
KnownRate ('Hertz r) where
rateVal _ = natVal (Proxy :: Proxy r)
type RateVal ('Hertz r) = r
-- | A proxy type for 'Rate's useful to prevent orphan instances, is seen in
-- the 'Show' instance for 'RateProxy'. If the instance were defined as
-- @instance KnownRate r => Show (proxy r) where ... @ it would be an orphan
-- instance.
data RateProxy :: Rate -> Type where
MkRateProxy :: RateProxy rate
ConvertRateProxy :: proxy rate -> RateProxy rate
instance KnownRate r =>
Show (RateProxy r) where
showsPrec p _ =
showParen (p > 10) (shows (rateVal (Proxy :: Proxy r)) . showString " Hz")
-- | Return the 'StaticTicks' representing the shortest representable duration
-- of something sampled at a 'Rate'
type PeriodDuration i = 1 :/ GetRate i
-- | The maximum representable frequency is @10e12 1/s@ which corresponds to
-- the resolution of 'NominalDiffTime', i.e. 1 pico second.
type OnePerPicoSecond = Hz 1000000000000
-- ** Types that have a /known/ 'Rate'
-- | Types with a known 'Rate', e.g. audio media has a sample rate.
class (KnownRate (GetRate i), SetRate i (GetRate i) ~ i) =>
HasRate i where
-- | Set the static sample rate of the media
type SetRate i (r :: Rate)
-- | The static sample rate of the media
type GetRate i :: Rate
-- | Types which contain a rate, but are agnostic of it. The counter example would be
-- if the rate was a type index of a data family.
class (HasRate i, GetRate i ~ ri, SetRate i rj ~ j, KnownRate rj) =>
CoerceRate i j ri rj
where
-- | Change the static sample rate, without e.g. resampling
coerceRate :: proxy rj -> i -> SetRate i rj
-- | Return the 'Rate' as an 'Integer' from a proxy for an instance of 'HasRate'
getRate
:: forall i proxy.
HasRate i
=> proxy i -> Integer
getRate = rateVal . getRateProxy
-- | Return a 'Proxy' for the 'GetRate' from a proxy for an instance of 'HasRate'
getRateProxy
:: HasRate i
=> proxy i -> RateProxy (GetRate i)
getRateProxy _ = MkRateProxy
-- | Return the reciprocal of the sample rate, i.e. the duration that one sample spans
getPeriodDuration
:: forall i proxy.
HasRate i
=> proxy i -> NominalDiffTime
getPeriodDuration _ = 1 / fromInteger (rateVal (Proxy :: Proxy (GetRate i)))
-- | Utility around 'coerceRate' to set the sample rate to 8000 Hz.
coerceRateTo8kHz
:: CoerceRate x y rx (Hz 8000)
=> x -> y
coerceRateTo8kHz = coerceRate (Proxy :: Proxy (Hz 8000))
-- | Utility around 'coerceRate' to set the sample rate to 16000 Hz.
coerceRateTo16kHz
:: CoerceRate x y rx (Hz 16000)
=> x -> y
coerceRateTo16kHz = coerceRate (Proxy :: Proxy (Hz 16000))
-- | Utility around 'coerceRate' to set the sample rate to 48000 Hz.
coerceRateTo48kHz
:: CoerceRate x y rx (Hz 48000)
=> x -> y
coerceRateTo48kHz = coerceRate (Proxy :: Proxy (Hz 48000))
-- | Utility around 'coerceRate' to double the sample rate.
coerceToDoubleRate
:: forall r s x y.
( CoerceRate x y r (Hz (s + s))
, KnownRate r
, RateVal r ~ s
, KnownNat (s + s)
)
=> x -> y
coerceToDoubleRate = coerceRate (Proxy :: Proxy (Hz (s + s)))
-- ** Arbitrary resolution (aka rate) integral time
-- | An integral time unit such that (time_in_seconds = _ticks * 1/rate)
newtype Ticks (rate :: Rate) w = MkTicks
{ _ticks :: w
} deriving ( Eq
, Real
, Integral
, Enum
, LocalOrd
, Num
, Arbitrary
, Default
, Generic
, Random
)
-- | The constraint on the type parameters of 'Ticks
type CanBeTicks (r :: Rate) w = (KnownRate r, Integral w)
-- | The highest resolution 'Ticks' possible, such that it can still be
-- converted to 'NominalDiffTime'
type PicoSeconds = Ticks OnePerPicoSecond Integer
-- ** Smart constructors for 'Ticks'
-- | Alias for 'Ticks' based on a 'Word32' tick count.
type Ticks32 r = Ticks r Word32
-- | Create a 'Ticks32' from a 'Rate' given via a proxy and the number of ticks.
mkTicks32
:: KnownRate r
=> proxy r -> Word32 -> Ticks32 r
mkTicks32 _ = MkTicks
-- | Alias for 'Ticks' based on a 'Word64' tick count.
type Ticks64 r = Ticks r Word64
-- | Create a 'Ticks64' from a 'Rate' given via a proxy and the number of ticks.
mkTicks64
:: KnownRate r
=> proxy r -> Word64 -> Ticks64 r
mkTicks64 _ = MkTicks
-- | Alias for 'Ticks' based on a 'Word32' tick count with a rate of 8000 ticks per second.
type Ticks32At8000 = Ticks32 (Hz 8000)
-- | Create a 'Ticks32At8000' from a tick count.
mkTicks32At8000 :: Word32 -> Ticks32At8000
mkTicks32At8000 = MkTicks
-- | Alias for 'Ticks' based on a 'Word32' tick count with a rate of 16000 ticks per second.
type Ticks32At16000 = Ticks32 (Hz 16000)
-- | Create a 'Ticks32At16000 from a tick count.
mkTicks32At16000 :: Word32 -> Ticks32At16000
mkTicks32At16000 = MkTicks
-- | Alias for 'Ticks' based on a 'Word32' tick count with a rate of 48000 ticks per second.
type Ticks32At48000 = Ticks32 (Hz 48000)
-- | Create a 'Ticks32At48000 from a tick count.
mkTicks32At48000 :: Word32 -> Ticks32At48000
mkTicks32At48000 = MkTicks
-- | Alias for 'Ticks' based on a 'Word64' tick count with a rate of 8000 ticks per second.
type Ticks64At8000 = Ticks64 (Hz 8000)
-- | Create a 'Ticks64At8000 from a tick count.
mkTicks64At8000 :: Word64 -> Ticks64At8000
mkTicks64At8000 = MkTicks
-- | Alias for 'Ticks' based on a 'Word64' tick count with a rate of 16000 ticks per second.
type Ticks64At16000 = Ticks64 (Hz 16000)
-- | Create a 'Ticks64At16000 from a tick count.
mkTicks64At16000 :: Word64 -> Ticks64At16000
mkTicks64At16000 = MkTicks
-- | Alias for 'Ticks' based on a 'Word64' tick count with a rate of 48000 ticks per second.
type Ticks64At48000 = Ticks64 (Hz 48000)
-- | Create a 'Ticks64At48000 from a tick count.
mkTicks64At48000 :: Word64 -> Ticks64At48000
mkTicks64At48000 = MkTicks
instance NFData w =>
NFData (Ticks rate w)
-- | Transform a 'Tick' value to another 'Tick' value.
convertTicks
:: (CanBeTicks r w, CanBeTicks r' w')
=> Ticks r w -> Ticks r' w'
convertTicks = view (from nominalDiffTime) . view nominalDiffTime
-- | A function (an 'Iso') that converts back-and-forth between 'Tick's and
-- 'NominalDiffTime's
nominalDiffTime
:: forall r w.
(CanBeTicks r w)
=> Iso' (Ticks r w) NominalDiffTime
nominalDiffTime = iso (toNDT . _ticks) (MkTicks . fromNDT)
where
toNDT = (/ rate) . fromIntegral
fromNDT = round . (* rate)
rate = fromInteger $ rateVal (Proxy :: Proxy r)
instance (CanBeTicks r w, Show w) =>
Show (Ticks r w) where
show tix@(MkTicks x) =
printf
"%10s (%10d @ %10d Hz)"
(show (view nominalDiffTime tix))
(toInteger x)
(rateVal (Proxy :: Proxy r))
instance (Eq w, LocalOrd w) =>
Ord (Ticks rate w) where
(<=) = flip succeeds
-- ** Compile-Time Known Time values
-- | Time unit for durations known at compile time.
data StaticTicks where
(:/:) :: Nat -> Rate -> StaticTicks
-- | Convenient wrapper around 'MkStaticTicks' and
-- 'MkRate' to create a promoted 'StaticTicks'.
type ticks :/ rate = ticks ':/: rate
-- | Return the 'Rate' value of a promoted 'StaticTicks'.
type family StaticTicksRate (s :: StaticTicks) :: Rate where
StaticTicksRate (t :/ r) = r
-- | Return the ticks value of a promoted 'StaticTicks'.
type family StaticTicksTicks (s :: StaticTicks) :: Nat where
StaticTicksTicks (t :/ r) = t
-- | Analog to 'KnownNat' this (kind-)class is for 'StaticTicks' with a runtime
-- 'Ticks' value.
class KnownStaticTicks (s :: StaticTicks) where
staticTicksVal
:: KnownRate r
=> proxy s -> Ticks r Integer
instance (KnownNat d, KnownRate r) =>
KnownStaticTicks (d :/ r) where
staticTicksVal _ =
convertTicks (MkTicks (natVal (Proxy :: Proxy d)) :: Ticks r Integer)
-- * Types with a duration
-- ** Runtime duration values
-- | Types with a duration (e.g. audio samples).
class HasDuration a where
getDuration :: a -> NominalDiffTime
getDuration !x = from nominalDiffTime # (getDurationTicks x :: PicoSeconds)
getDurationTicks
:: (CanBeTicks r i)
=> a -> Ticks r i
getDurationTicks !x = nominalDiffTime # getDuration x
instance HasDuration a =>
HasDuration (Maybe a) where
getDuration Nothing = 0
getDuration (Just !a) = getDuration a
getDurationTicks Nothing = 0
getDurationTicks (Just !a) = getDurationTicks a
-- | Types that contain a 'Timestamp'
class SetTimestamp t (GetTimestamp t) ~ t =>
HasTimestamp t where
type GetTimestamp t
type SetTimestamp t s
timestamp :: Lens t (SetTimestamp t s) (GetTimestamp t) s
timestamp' :: Lens' t (GetTimestamp t)
timestamp' = timestamp
-- ** Known at compile time durations
-- *** Static ticks TODO rename static -> known
-- | Types that have a duration known at compoile time.
class ( KnownStaticTicks (GetStaticDuration s)
, SetStaticDuration s (GetStaticDuration s) ~ s
) =>
HasStaticDuration (s :: k) where
type SetStaticDuration s (pt :: StaticTicks) :: k
type SetStaticDuration s (pt :: StaticTicks) = s
type GetStaticDuration s :: StaticTicks
instance (KnownRate r, KnownNat t) =>
HasStaticDuration (t :/ r) where
type SetStaticDuration (t :/ r) (t' :/ r') = t' :/ r'
type GetStaticDuration (t :/ r) = t :/ r
-- | Create a 'Proxy' for the 'StaticTicks' type associated with 's', this is
-- basically the analogon to the 'getDuration' method - just for types with a
-- duration known at compile time.
toStaticDurationProxy
:: (HasStaticDuration s)
=> proxy s -> Proxy (GetStaticDuration s)
toStaticDurationProxy _ = Proxy
-- | Convert the 'StaticDuration' that some type has to the number of seconds.
getStaticDuration
:: forall proxy s.
HasStaticDuration s
=> proxy s -> NominalDiffTime
getStaticDuration px =
from nominalDiffTime #
(staticTicksVal (toStaticDurationProxy px) :: PicoSeconds)
-- | Convert the 'StaticDuration' that some type has to any 'Ticks'.
getStaticDurationTicks
:: forall proxy s r t i.
( CanBeTicks r i
, KnownNat t
, HasStaticDuration s
, GetStaticDuration s ~ (t :/ r)
)
=> proxy s -> Ticks r i
getStaticDurationTicks px = ticksFromStaticDuration (toStaticDurationProxy px)
ticksFromStaticDuration
:: forall proxy rate ticks i.
(CanBeTicks rate i, KnownNat ticks)
=> proxy (ticks :/ rate) -> Ticks rate i
ticksFromStaticDuration _ =
MkTicks (fromIntegral (natVal (Proxy :: Proxy ticks)))
instance (HasTimestamp a, HasTimestamp b, GetTimestamp a ~ GetTimestamp b) =>
HasTimestamp (Series a b) where
type GetTimestamp (Series a b) = GetTimestamp a
type SetTimestamp (Series a b) t = Series (SetTimestamp a t) (SetTimestamp b t)
timestamp f (Start a) = Start <$> timestamp f a
timestamp f (Next b) = Next <$> timestamp f b