{-# Language BangPatterns, TypeFamilies, DeriveFunctor, DeriveFoldable, DeriveTraversable, CPP #-} -- | A library for creating lists of constant time events related in time. module Temporal.Media( -- * Introduction -- | "Temporal.Media" is a library for creating lists of constant time -- events related in time. Constant time event is value -- that starts at some fixed time and lasts for some -- fixed time. Library provides functions to build lists -- of such events with time-relations like sequent, -- parallel or delayed. -- -- Core type of library is 'Track'. It provides interface -- to compose list of events. -- * Types Event(..), Track, within, eventEnd, -- * Composition module Temporal.Class, temp, fromEvent, singleEvent, reflect, (=:/), harT, sustain, sustainT, -- ** Common patterns melTemp, harTemp, harTMap, -- * Filtering slice, takeT, dropT, filterEvents, -- * Mappings mapEvents, traverseEvents, tmap, tmapRel, -- * Rendering render, alignByZero, sortEvents, -- * Monoid synonyms -- -- | This package heavily relies on 'Monoid's, so there are shorcuts -- for 'Monoid' methods. nil, module Data.Monoid, -- * Miscellaneous linfun, linfunRel ) where import Data.Monoid import Data.Boolean import Data.Foldable(Foldable(foldMap)) import Data.Traversable import Control.Applicative hiding ((<*)) import Data.List(sortBy) import Data.Ord(comparing) import Temporal.Class -- TODO : optimise loops -- reflect ??? -- Monoid shortcuts -- | Synonym for method 'mempty'. nil :: Monoid a => a nil = mempty ---------------------------------------------- -- Track -- | 'Track' is a set of 'Event' s. There is total duration -- of the track, but Events can go beyond the scope of total duration -- (as a result of 'mapEvents' function). Total duration is used in sequent -- composition of tracks. data Track t a = Track t (TList t a) deriving (Show, Eq, Functor, Foldable, Traversable) #if MIN_VERSION_base(4,11,0) instance (Num t, IfB t, OrdB t) => Semigroup (Track t a) where x <> y = x `mappendTrack` y instance (Num t, IfB t, OrdB t) => Monoid (Track t a) where mempty = Track 0 mempty #else instance (Num t, IfB t, OrdB t) => Monoid (Track t a) where mempty = Track 0 mempty mappend = mappendTrack #endif mappendTrack :: (Num t, IfB t, OrdB t) => Track t a -> Track t a -> Track t a mappendTrack (Track d es) (Track d' es') = Track (maxB d d') $ mappend es es' type instance DurOf (Track t a) = t instance Duration (Track t a) where dur (Track d _) = d -- | Stretches track in time domain. instance Num t => Stretch (Track t a) where str k (Track d es) = Track (k*d) $ stretchTList k es -- | Delays all events by given duration. instance Num t => Delay (Track t a) where del k (Track d es) = Track (k+d) $ delayTList k es -- | Turncating parallel composition. Total duration -- equals to minimum of the two tracks. All events -- that goes beyond the limit are dropped. (=:/) :: (Real t, IfB t, OrdB t) => Track t a -> Track t a -> Track t a a =:/ b = slice 0 (dur a `minB` dur b) $ a <> b instance (Num t, IfB t, OrdB t) => Melody (Track t a) where mel = foldr (+:+) nil a +:+ b = a <> del (dur a) b instance (Num t, IfB t, OrdB t) => Harmony (Track t a) where har = mconcat a =:= b = a <> b instance (Num t, IfB t, OrdB t) => Compose (Track t a) where -- | Turncating parallel composition on list of tracks. harT :: (Real t, IfB t, OrdB t) => [Track t a] -> Track t a harT xs = slice 0 (minimum $ dur <$> xs) $ har xs -- | Analog of 'replicate' function for tracks. Replicated -- tracks are played sequentially. -- | A melody of events. Each of them lasts for one second. melTemp :: (Num t, IfB t, OrdB t) => [a] -> Track t a melTemp = melMap temp -- | A chord of events. Each of them lasts for one second. harTemp :: (Num t, IfB t, OrdB t) => [a] -> Track t a harTemp = harMap temp -- | Transforms a sequence and then applies a harT. harTMap :: (Real t, IfB t, OrdB t) => (a -> Track t b) -> [a] -> Track t b harTMap f xs = harT $ fmap f xs -- | Reversing the tracks reflect :: (Num t, IfB t, OrdB t) => Track t a -> Track t a reflect a = mapEvents (\e -> e{ eventStart = d - (eventStart e + eventDur e) }) a where d = dur a -- | Empty track that lasts for some time. instance (Num t, IfB t, OrdB t) => Rest (Track t a) where rest = flip del nil -- | 'slice' cuts piece of value within given time interval. -- for @('slice' t0 t1 m)@, if @t1 < t0@ result is reversed. -- If @t0@ is negative or @t1@ goes beyond @'dur' m@ blocks of -- nothing inserted so that duration of result equals to -- @'abs' (t0 - t1)@. slice :: (Real t) => t -> t -> Track t a -> Track t a slice t0 t1 = (slice' t0 t1) slice' :: (Real t) => t -> t -> Track t a -> Track t a slice' t0 t1 = sliceDur . del (-t0) . filterEvents (within t0 t1) where sliceDur (Track _ a) = Track (t1 - t0) a -- | @('takeT' t)@ is equivalent to @('slice' 0 t)@. takeT :: (Real t) => t -> Track t a -> Track t a takeT t1 = slice 0 t1 -- | @('dropT' t m)@ is equivalent to @('slice' t (dur a) a)@. dropT :: Real t => t -> Track t a -> Track t a dropT t0 a = slice t0 (dur a) a -- | 'temp' constructs just an event. -- Value of type a lasts for one time unit and starts at zero. temp :: (Num t) => a -> Track t a temp = Track 1 . Single -- | Constructs a track that contains a single event. fromEvent :: Num t => Event t a -> Track t a fromEvent (Event start duration content) = singleEvent start duration content -- | Constructs a track that contains a single event. -- -- > singleEvent start duration content singleEvent :: Num t => t -> t -> a -> Track t a singleEvent start duration content = del start $ str duration $ temp content -- | Get all events on recordered on the track. render :: (Num t) => Track t a -> [Event t a] render (Track d es) = renderTList es ----------------------------------------------- -- Event -- | Constant time events. Value @a@ starts at some time -- and lasts for some time. data Event t a = Event { eventStart :: t, eventDur :: t, eventContent :: a } deriving (Show, Eq) -- | End point of event (start time plus duration). eventEnd :: Num t => Event t a -> t eventEnd e = eventStart e + eventDur e instance Functor (Event t) where fmap f e = e{ eventContent = f (eventContent e) } durEvent = eventDur delayEvent d e = e{ eventStart = eventStart e + d } stretchEvent d e = e{ eventStart = eventStart e * d, eventDur = eventDur e * d } -- | Tests if given 'Event' happens between two time stamps. within :: (Real t) => t -> t -> Event t a -> Bool within t0 t1 e = within' t0 t1 (eventStart e) && within' t0 t1 (eventEnd e) where within' a b x = x >= a && x <= b -- | General mapping. Maps not only values but events. mapEvents :: Num t => (Event t a -> Event t b) -> Track t a -> Track t b mapEvents = onEvents . fmap traverseEvents :: (Num t1, Applicative f) => (t1 -> f t2) -> (Event t1 a -> f (Event t2 b)) -> Track t1 a -> f (Track t2 b) traverseEvents df f t = Track <$> (df $ dur t) <*> (fmap fromEventList $ traverse f $ render t) -- | Filter track. filterEvents :: Real t => (Event t a -> Bool) -> Track t a -> Track t a filterEvents = onEvents . filter onEvents :: Num t => ([Event t a] -> [Event t b]) -> Track t a -> Track t b onEvents phi t@(Track d es) = Track d $ fromEventList $ phi $ render t -- | Maps values and time stamps. tmap :: Real t => (Event t a -> b) -> Track t a -> Track t b tmap f = mapEvents $ \e -> e{ eventContent = f e } -- | Relative tmap. Time values are normalized by argument's duration. tmapRel :: (RealFrac t) => (Event t a -> b) -> Track t a -> Track t b tmapRel f x = tmap (f . stretchEvent (1 / dur x)) x -- | After this transformation events last longer -- by some constant amount of time. sustain :: Num t => t -> Track t a -> Track t a sustain a = mapEvents $ \e -> e{ eventDur = a + eventDur e } -- | Prolongated events can not exceed total track duration. -- All event are sustained but those that are close to -- end of the track are sliced. It resembles sustain on piano, -- when track ends you release the pedal. sustainT :: (Ord t, Num t) => t -> Track t a -> Track t a sustainT a x = mapEvents (\e -> truncate $ e{ eventDur = a + eventDur e }) x where truncate e | eventEnd e > d = e{ eventDur = max 0 $ d - eventStart e } | otherwise = e d = dur x -- | Shifts all events so that minimal start time -- equals to zero if first event has negative start time. alignByZero :: (Real t) => [Event t a] -> [Event t a] alignByZero es | minT < 0 = alignEvent <$> es | otherwise = es where minT = minimum $ eventStart <$> es alignEvent e = e{ eventStart = eventStart e - minT } -- | Sorts all events by start time. sortEvents :: Ord t => [Event t a] -> [Event t a] sortEvents = sortBy (comparing eventStart) ----------------------------------------------- ----------------------------------------------- -- Temporal List data TList t a = Empty | Single a | Append (TList t a) (TList t a) | TFun (Tfm t) (TList t a) deriving (Show, Eq, Functor, Foldable, Traversable) foldT :: b -> (a -> b) -> (b -> b -> b) -> (Tfm t -> b -> b) -> TList t a -> b foldT empty single append tfun x = case x of Empty -> empty Single a -> single a Append a b -> append (f a) (f b) TFun t a -> tfun t (f a) where f = foldT empty single append tfun #if MIN_VERSION_base(4,11,0) instance Semigroup (TList t a) where (<>) = mappendTList instance Monoid (TList t a) where mempty = Empty #else instance Monoid (TList t a) where mempty = Empty mappend = mappendTList #endif mappendTList :: TList t a -> TList t a -> TList t a mappendTList Empty a = a mappendTList a Empty = a mappendTList a b = Append a b durTList = maximum . fmap totalEventDur . renderTList where totalEventDur = (+) <$> eventStart <*> eventDur stretchTList k x = case x of TFun t a -> TFun (stretchTfm k t) a Empty -> Empty a -> TFun (Tfm k 0) a delayTList k x = case x of TFun t a -> TFun (delayTfm k t) a Empty -> Empty a -> TFun (Tfm 1 k) a renderTList :: Num t => TList t a -> [Event t a] renderTList = ($[]) . foldMap (:) . eventList eventList :: Num t => TList t a -> TList t (Event t a) eventList = iter unit where iter !tfm x = case x of Empty -> Empty Single a -> Single (eventFromTfm tfm a) TFun t a -> iter (tfm `composeTfm` t) a Append a b -> Append (iter tfm a) (iter tfm b) fromEventList :: [Event t a] -> TList t a fromEventList = foldr (mappend . phi) mempty where phi e = TFun (tfmFromEvent e) (Single $ eventContent e) -- transformation -- it's a pair of (stretch factor, delay offset) data Tfm t = Tfm !t !t deriving (Show, Eq) unit :: Num t => Tfm t unit = Tfm 1 0 durTfm (Tfm str del) = str + del stretchTfm k (Tfm str del) = Tfm (k*str) (k*del) delayTfm k (Tfm str del) = Tfm str (k+del) eventFromTfm :: Tfm t -> a -> Event t a eventFromTfm (Tfm str del) = Event del str tfmFromEvent :: Event t a -> Tfm t tfmFromEvent = Tfm <$> eventDur <*> eventStart -- composition on transformations: -- s2 `composeTfm` s1 composeTfm :: Num t => Tfm t -> Tfm t -> Tfm t composeTfm (Tfm s2 d2) (Tfm s1 d1) = Tfm (s1*s2) (d1*s2 + d2) --------------------------------------------------------------- -- Misc -- | Linear interpolation. Can be useful with 'mapEvents' for -- envelope changes. -- -- > linfun [a, da, b, db, c, ... ] -- -- @a, b, c ...@ - values -- -- @da, db, ...@ - duration of segments linfun :: (Ord t, Fractional t) => [t] -> t -> t linfun xs t = case xs of (a:dur:b:[]) -> seg a dur b t (a:dur:b:(x:xs')) -> if t < dur then seg a dur b t else linfun (b:x:xs') (t - dur) where seg a dur b t | t < 0 = a | t >= dur = b | otherwise = a + (b - a)*(t/dur) -- | With 'linfunRel' you can make linear interpolation -- function that has equal distance between points. -- First argument gives total length of the interpolation function -- and second argument gives list of values. So call -- -- > linfunRel dur [a1, a2, a3, ..., aN] -- -- is equivalent to: -- -- > linfun [a1, dur/N, a2, dur/N, a3, ..., dur/N, aN] linfunRel :: (Ord t, Fractional t) => t -> [t] -> t -> t linfunRel dur xs = linfun $ init $ f =<< xs where dt = dur / (fromIntegral $ length xs) f x = [x, dt]