music-score-1.6: src/Music/Time/Onset.hs
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}
-------------------------------------------------------------------------------------
-- |
-- Copyright : (c) Hans Hoglund 2012
--
-- License : BSD-style
--
-- Maintainer : hans@hanshoglund.se
-- Stability : experimental
-- Portability : non-portable (TF,GNTD)
--
-- This misleadingly named module provide a way to query a value for its
-- 'duration', 'onset' and 'offset'.
--
-------------------------------------------------------------------------------------
module Music.Time.Onset (
-- * Duration class
HasDuration(..),
stretchTo,
-- * Onset and offset class
HasOnset(..),
HasOffset(..),
startAt,
stopAt,
withSameOnset,
withSameOffset,
-- * Utility
-- ** Default implementations
durationDefault,
onsetDefault,
offsetDefault,
) where
import Data.AffineSpace
import Data.AffineSpace.Point
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Semigroup
import Data.Set (Set)
import qualified Data.Set as Set
import Data.VectorSpace hiding (Sum)
import Music.Score.Util
import Music.Time.Delayable
import Music.Time.Stretchable
import Music.Time.Time
-- |
-- Class of types with a duration.
--
-- If a type has an instance for both 'HasOnset' and 'HasDuration', the following laws
-- should hold:
--
-- > duration a = offset a .-. onset a
--
class HasDuration a where
duration :: a -> Duration
instance HasDuration Duration where
duration = id
instance HasDuration (Duration, a) where
duration = fst
instance HasDuration (Time, Duration, a) where
duration (t,d,x) = d
instance HasDuration a => HasDuration (Product a) where
duration (Product x) = duration x
-- Works for monophonic containers but not in general
-- instance HasDuration a => HasDuration [a] where
-- duration = getSum . F.foldMap (Sum . duration)
-- |
-- Stretch a score to fit into the given duration.
--
-- > Duration -> Score a -> Score a
--
stretchTo :: (Stretchable a, HasDuration a) => Duration -> a -> a
-- |
-- Class of types with a position in time.
--
-- Onset and offset are logical start and stop time, i.e. the preferred beginning and end
-- of the sound, not o the the time of the attack and damp actions on an instrument,
--
-- If a type has an instance for both 'HasOnset' and 'HasDuration', the following laws
-- should hold:
--
-- > duration a = offset a .-- onset a
--
class HasOnset a where
-- |
-- Get the onset of the given value.
--
onset :: a -> Time
class HasOffset a where
-- |
-- Get the offset of the given value.
--
offset :: a -> Time
instance HasOnset Time where
onset = id
instance HasOnset (Time, a) where
onset = fst
instance HasOnset (Time, Duration, a) where
onset (t,d,x) = t
instance HasOffset (Time, Duration, a) where
offset (t,d,x) = t .+^ d
instance HasOnset a => HasOnset [a] where
onset = list origin (minimum . fmap onset)
instance HasOffset a => HasOffset [a] where
offset = list origin (maximum . fmap offset)
instance HasOnset a => HasOnset (Set a) where
onset = list origin (onset . head) . Set.toAscList
instance HasOffset a => HasOffset (Set a) where
offset = list origin (offset . last) . Set.toAscList
instance HasOnset k => HasOnset (Map k a) where
onset = list origin (onset . head) . Map.keys
instance HasOffset k => HasOffset (Map k a) where
offset = list origin (offset . last) . Map.keys
instance HasOnset a => HasOnset (Sum a) where
onset (Sum x) = onset x
-- |
-- Move a score so that its onset is at the specific time.
--
-- > Time -> Score a -> Score a
--
startAt :: (HasOnset a, Delayable a) => Time -> a -> a
-- |
-- Move a score so that its offset is at the specific time.
--
-- > Time -> Score a -> Score a
--
stopAt :: (HasOffset a, Delayable a) => Time -> a -> a
t `stretchTo` x = (t / duration x) `stretch` x
t `startAt` x = (t .-. onset x) `delay` x
t `stopAt` x = (t .-. offset x) `delay` x
-- |
-- Transform a score without affecting its onset.
--
-- > Time -> Score a -> Score a
--
withSameOnset :: (Delayable a, HasOnset a, HasOnset b) => (b -> a) -> b -> a
-- |
-- Transform a score without affecting its offset.
--
-- > Time -> Score a -> Score a
--
withSameOffset :: (Delayable a, HasOffset a, HasOffset b) => (b -> a) -> b -> a
withSameOnset f a = startAt (onset a) $ f a
withSameOffset f a = stopAt (offset a) $ f a
-- | Given 'HasOnset' and 'HasOffset' instances, this function implements 'duration'.
durationDefault :: (HasOffset a, HasOnset a) => a -> Duration
durationDefault x = offset x .-. onset x
-- | Given 'HasDuration' and 'HasOffset' instances, this function implements 'onset'.
onsetDefault :: (HasOffset a, HasDuration a) => a -> Time
onsetDefault x = offset x .-^ duration x
-- | Given 'HasOnset' and 'HasOnset' instances, this function implements 'offset'.
offsetDefault :: (HasOnset a, HasDuration a) => a -> Time
offsetDefault x = onset x .+^ duration x