{-|
Module : Data.FlickDuration
Copyright : (c) 2018 Christopher C Lord, Pliosoft
License : BSD Style
Stability : experimental
Maintainer : christopher@pliosoft.com
Portability : GHC
Use a to contain some number of flicks, and provide standard numerical operations.
Based on idea from <https:www.facebook.com/christopher.horvath.395/posts/1157292757692660 ChristopherHorvath>
-}
module Data.Duration
(
-- * The 'Duration' type
Duration
-- * Constant Ratios
, flicksPerMillisecond
, flicksPerSecond
, flicksPerMinute
, flickRatio
, durationToRationalFlicks
, durationToRationalSeconds
-- * 'Duration' to various human units
, durationAsHours
, durationAsNanoseconds
, durationAsMicroseconds
, durationAsMilliseconds
, durationAsMinutes
, durationAsSeconds
, durationAsHms
-- * Creating 'Duration' from various human units
, durationFromFlicks
, durationFromHours
, durationFromNanoseconds
, durationFromMicroseconds
, durationFromMilliseconds
, durationFromMinutes
, durationFromSeconds
-- * 'Duration' from wavelength at some frequency, and vice versa
, durationOfOneBeatAtBPM
, durationOfOneCycleAtHz
, frequencyInHzForWavePeriod
-- * 'Duration' manipulations
, durationOfRepeatedDuration
, durationFromDividingDuration
, durationIntoDuration
, negateDuration
, compareDurationsWithEpsilon
-- * Thread delay
, delayThreadByDuration
-- * Measuring delay with the system clock
, StartOfDuration
, startMeasuring
, durationSince
, measurementEpsilon
)
where
import Prelude
import Data.Ratio
import Data.Semigroup
import Data.Maybe
import System.Clock
import Control.Concurrent(threadDelay)
import Control.Monad ((>>), when)
-- | Flicks are a small unit of time that are very evenly divisible by common file format time durations; i.e., for common durations they will have no rounding.
-- Even if there is rounding, it is a very short amount of time -- 1.417 nanoseconds, so rounding is typically not a concern.
flicksPerMillisecond :: Num a => a
flicksPerMillisecond = 705600
-- | How many flicks are in a second
flicksPerSecond :: Num a => a
flicksPerSecond = 705600000
-- | How many flicks are in a minute
flicksPerMinute :: Num a => a
flicksPerMinute = 60 * 705600000
-- | 'Ratio' of seconds to flicks. for example, 100 seconds of flicks is @(100 * flickRatio)@
flickRatio :: Fractional a => a
flickRatio = recip flicksPerSecond
-- | Represents a span of time using some number of flicks. To create a 'Duration' use:
--
-- * 'durationFromFlicks'
-- * 'durationFromHours'
-- * 'durationFromMicroseconds'
-- * 'durationFromMilliseconds'
-- * 'durationFromMinutes'
-- * 'durationFromSeconds'
--
-- 'Duration' can be concatinated with 'mappend' or the '(<>)' operator.
newtype Duration = Duration { fromDuration :: Integer }
deriving (Eq, Ord, Show)
instance Semigroup Duration where
a <> b = Duration $ fromDuration a + fromDuration b
instance Monoid Duration where
mempty = Duration 0
mappend = (<>)
{-
Can't have Num instance since it makes little sense to multiply durations Durations are not a ring. But we can have monoid (and group?) instances.
instance Num Duration where
a + b = Duration $ fromDuration a + fromDuration b
a * b = Duration $ fromDuration a * fromDuration b
abs a = Duration $ abs (fromDuration a)
signum a = Duration $ signum (fromDuration a)
fromInteger a = Duration (fromInteger a)
negate a = Duration (negate (fromDuration a))
-}
-- | Convert a 'Duration' into flicks, stored in a 'Rational'
durationToRationalFlicks :: Duration -> Rational
durationToRationalFlicks d = toRational (fromDuration d)
-- | Convert a 'Duration' into seconds, stored in a 'Rational'
durationToRationalSeconds :: Duration -> Rational
durationToRationalSeconds d = flickRatio * durationToRationalFlicks d
-- | given some 'Duration' d1, how many times can we iterate that duration into another duration
durationIntoDuration :: Duration -> Duration -> Maybe Rational
durationIntoDuration _ d2 | fromDuration d2 == 0 = Nothing
durationIntoDuration d1 d2 = Just $ fromDuration d1 % fromDuration d2
-- | Repeat a 'Duration' some rational number of times to produce the total duration
durationOfRepeatedDuration :: Duration -> Rational -> Duration
durationOfRepeatedDuration d r = Duration $ truncate (durationToRationalFlicks d * r)
-- | take a 'Duration' and divide it into equal pieces of some length (the result)
durationFromDividingDuration :: Duration -> Rational -> Maybe Duration
durationFromDividingDuration d r = if r == 0 then Nothing else Just $ durationOfRepeatedDuration d (recip r)
-- | Given a number of flicks, produce a 'Duration'
durationFromFlicks :: Integral a => a -> Duration
durationFromFlicks i = Duration (fromIntegral i)
-- | Given a number of nanoseconds, produce a 'Duration'.
-- Note that flicks are larger than nanoseconds, so rounding will occur
durationFromNanoseconds :: Rational -> Duration
durationFromNanoseconds i = durationFromMicroseconds (i / 1000)
-- | Given a number of microseconds, produce a 'Duration'
durationFromMicroseconds :: Rational -> Duration
durationFromMicroseconds i = durationFromMilliseconds (i / 1000)
-- | Given a number of milliseconds, produce a 'Duration'
durationFromMilliseconds :: Rational -> Duration
durationFromMilliseconds i = Duration (truncate (i * flicksPerMillisecond))
-- | Given a number of seconds, produce a 'Duration'
durationFromSeconds :: Rational -> Duration
durationFromSeconds i = Duration (truncate (i * flicksPerSecond))
-- | Given a number of minutes, produce a 'Duration'
durationFromMinutes :: Rational -> Duration
durationFromMinutes i = Duration (truncate (i * flicksPerMinute))
-- | Given a number of hours, produce a 'Duration'
durationFromHours :: Rational -> Duration
durationFromHours i = durationFromMinutes (i * 60)
-- | Given an epsilon e and two durations, will determine their 'Ord' with respect to the epsilon
compareDurationsWithEpsilon :: Duration -> Duration -> Duration -> Ordering
compareDurationsWithEpsilon e a b =
let diff = (fromDuration a) - (fromDuration b)
in if abs diff <= fromDuration e
then EQ
else if diff < 0
then LT
else GT
-- | given some frequency in cycles per second, produce the 'Duration' of a single wavelength's period
durationOfOneCycleAtHz :: Rational -> Maybe Duration
durationOfOneCycleAtHz hz =
if hz <= 0
then Nothing
else durationFromDividingDuration (durationFromSeconds 1) hz
-- | given some frequency in beats per minute, produce the 'Duration' between beats
durationOfOneBeatAtBPM :: Rational -> Maybe Duration
durationOfOneBeatAtBPM bpm =
if bpm <= 0
then Nothing
else durationFromDividingDuration (durationFromMinutes 1) bpm
-- | If 'Duration' d is the time for one period of a wave, produce the corresponding frequency in hz.
frequencyInHzForWavePeriod :: Duration -> Maybe Rational
frequencyInHzForWavePeriod = durationIntoDuration (durationFromSeconds 1)
-- | given some 'Duration', represent it as a count of seconds
durationAsSeconds :: Duration -> Rational
durationAsSeconds d = durationAsMilliseconds d / 1000
-- | given some 'Duration', represent it as a count of minutes
durationAsMinutes :: Duration -> Rational
durationAsMinutes d = durationAsSeconds d / 60
-- | given some 'Duration', represent it as a count of hours
durationAsHours :: Duration -> Rational
durationAsHours d = durationAsMinutes d / 60
-- | given some 'Duration', represent it as a count of milliseconds
durationAsMilliseconds :: Duration -> Rational
durationAsMilliseconds d = fromDuration d % flicksPerMillisecond
-- | given some 'Duration', represent it as a count of microseconds
durationAsMicroseconds :: Duration -> Rational
durationAsMicroseconds d = fromRational 1000 * durationAsMilliseconds d
-- | given some 'Duration', represent it as a count of nanoseconds
durationAsNanoseconds :: Duration -> Rational
durationAsNanoseconds d = fromRational 1000 * durationAsMicroseconds d
-- | given some 'Duration' produce a negative version
negateDuration :: Duration -> Duration
negateDuration d = Duration (negate (fromDuration d))
-- | Given two durations, produce the larger of the two
maximumOfDurations :: Duration -> Duration -> Duration
maximumOfDurations d1 d2 =
if d1 > d2 then d1 else d2
-- | Given two durations, produce the smaller of the two
minimumOfDurations :: Duration -> Duration -> Duration
minimumOfDurations d1 d2 =
if d1 < d2 then d1 else d2
-- | present a 'Duration' as a count of hours, minutes, seconds, ms, us
durationAsHms :: Duration -> (Integer, Integer, Integer, Integer, Integer)
durationAsHms d =
let (h,hr) = fromDuration d `divMod` fromDuration (durationFromHours 1)
(m,mr) = hr `divMod` fromDuration (durationFromMinutes 1)
(s,sr) = mr `divMod` fromDuration (durationFromSeconds 1)
(ms,msr) = sr `divMod` fromDuration (durationFromMilliseconds 1)
(us,usr) = msr `divMod` fromDuration (durationFromMicroseconds 1)
in (h,m,s,ms,us)
-- | Suspend currently running thread for a 'Duration' of time.
--
-- Provides no exact guarantee on when the thread will be resumed after the delay.
delayThreadByDuration :: Duration -> IO ()
delayThreadByDuration d = do
let maxMicroseconds = durationFromMicroseconds (fromIntegral (maxBound :: Int))
let w = min d maxMicroseconds
threadDelay (truncate (durationAsMicroseconds (min d maxMicroseconds)))
when (w /= d) (delayThreadByDuration (d <> negateDuration w))
-- | Holds implementation-defined start of time measurment for 'startMeasuring'
newtype StartOfDuration = StartOfDuration { toDurationFromOrigin :: Duration }
-- | Helper for internal math
timespecToDuration :: TimeSpec -> Duration
timespecToDuration ts = durationFromSeconds (fromIntegral (sec ts)) <> durationFromNanoseconds (fromIntegral (nsec ts))
-- | start measuring a duration
startMeasuring :: IO StartOfDuration
startMeasuring = do
now <- getTime Monotonic
let d1 = timespecToDuration now
return (StartOfDuration d1)
-- | How long since the start of measuring has it mean, plus or minus 'measurementEpsilon'
durationSince :: StartOfDuration -> IO Duration
durationSince s = do
now <- getTime Monotonic
let d1 = toDurationFromOrigin s
let d2 = timespecToDuration now
return (Duration ((fromDuration d2) - (fromDuration d1)))
-- | About how accurate is are the methods 'durationSince' and 'startMeasuring'
measurementEpsilon :: IO Duration
measurementEpsilon = return (durationFromMilliseconds 100)
{- ugg, this does not seem to give a reasonable e
do
res <- getRes Monotonic
let rres = timespecToDuration res
putStrLn (show (durationToRationalSeconds rres))
return $ rres
-}