music-score-1.3: src/Music/Score/Score.hs
{-# LANGUAGE
TypeFamilies,
DeriveFunctor,
DeriveFoldable,
DeriveDataTypeable,
DeriveTraversable,
GeneralizedNewtypeDeriving #-}
-------------------------------------------------------------------------------------
-- |
-- Copyright : (c) Hans Hoglund 2012
--
-- License : BSD-style
--
-- Maintainer : hans@hanshoglund.se
-- Stability : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Provides the 'Score' type.
--
-------------------------------------------------------------------------------------
module Music.Score.Score (
Score,
-- mapTime,
) where
import Prelude hiding (null, length, repeat, foldr, concat, foldl, mapM, concatMap, maximum, sum, minimum)
import Data.Semigroup
import Control.Applicative
import Control.Monad (ap, join, MonadPlus(..))
import Data.Foldable
import Data.Traversable
import Data.Typeable
import Data.Maybe
import Data.Either
import Data.Pointed
import Data.Function (on)
import Data.Ord (comparing)
import Data.Ratio
import Data.VectorSpace
import Data.AffineSpace
import Test.QuickCheck (Arbitrary(..),Gen(..))
import qualified Data.Map as Map
import qualified Data.List as List
import Music.Pitch.Literal
import Music.Dynamics.Literal
import Music.Time
import Music.Score.Voice
import Music.Score.Track
-------------------------------------------------------------------------------------
-- Score type
-------------------------------------------------------------------------------------
-- |
-- A score is a list of events, i.e. time-duration-value triplets. Semantically
--
-- > type Score a = [(Time, Duration, a)]
--
-- There is no explicit representation for rests. However you can use `Score (Maybe a)` to
-- represent a score with rests. Such rests are only useful when composing scores. They
-- may be removed with 'removeRests'.
--
-- Score is a 'Monoid' under parallel composition. 'mempty' is a score of no parts.
-- For sequential composition of scores, use '|>'.
--
-- Score has an 'Applicative' instance derived from the 'Monad' instance. Not sure it is useful.
--
-- Score is an instance of 'VectorSpace' using sequential composition as addition,
-- and time scaling as scalar multiplication.
--
newtype Score a = Score { getScore :: [(TimeT, DurationT, a)] }
deriving (Eq, Ord, Show, Functor, Foldable, Typeable, Traversable)
type instance Time Score = TimeT
instance Semigroup (Score a) where
(<>) = mappend
-- Equivalent to the derived Monoid, except for the sorted invariant.
instance Monoid (Score a) where
mempty = Score []
Score as `mappend` Score bs = Score (as `m` bs)
where
m = mergeBy (comparing fst3)
-- |
-- This instance is somewhat similar to the list instance.
--
-- * 'return' creates a score containing a single note at /(0, 1)/.
--
-- * @s@ '>>=' @k@ maps each note to a new score, which is then scaled and delayed by the onset and
-- duration of the original note. That is, @k@ returns a score @t@ such that /0 < onset t < offset t < 1/,
-- the resulting events will not cross the boundaries of the original note.
--
-- * 'join' scales and offsets each inner score to fit into the note containing it, then
-- removes the intermediate structure.
--
-- > let s = compose [(0,1,0), (1,2,1)]
-- >
-- > s >>= \x -> compose [ (0,1,toEnum $ x+65),
-- > (1,3,toEnum $ x+97) ] :: Score Char
-- >
-- > ===> compose [ (1, 1, 'A'),
-- > (1, 3, 'a'),
-- > (1, 2, 'B'),
-- > (3, 6, 'b') ]}
--
instance Monad Score where
return x = Score [(0, 1, x)]
a >>= k = join' $ fmap k a
where
join' sc = {-mconcat $ toList-}fold $ mapTime (\t d -> delay' t . stretch d) sc
instance Pointed Score where
point = return
instance Applicative Score where
pure = return
(<*>) = ap
instance Alternative Score where
empty = mempty
(<|>) = mappend
-- Satisfies left distribution
instance MonadPlus Score where
mzero = mempty
mplus = mappend
instance Performable Score where
perform = getScore
instance Stretchable (Score) where
d `stretch` Score sc = Score $ fmap (first3 (^* fromDurationT d) . second3 (^* d)) $ sc
instance Delayable (Score) where
d `delay` Score sc = Score . fmap (first3 (.+^ d)) $ sc
instance HasOnset (Score) where
-- onset (Score []) = 0
-- onset (Score xs) = minimum (fmap on xs) where on (t,d,x) = t
-- Note: this version of onset is lazier, but depends on the invariant that the list is sorted
onset (Score []) = 0
onset (Score xs) = on (head xs) where on (t,d,x) = t
instance HasOffset (Score) where
offset (Score []) = 0
offset (Score xs) = maximum (fmap off xs) where off (t,d,x) = t + (fromDurationT $ d)
instance HasDuration (Score) where
duration x = offset x .-. onset x
instance IsPitch a => IsPitch (Score a) where
fromPitch = pure . fromPitch
instance IsDynamics a => IsDynamics (Score a) where
fromDynamics = pure . fromDynamics
-- Utility
instance AdditiveGroup (Score a) where
zeroV = error "Not impl"
(^+^) = error "Not impl"
negateV = error "Not impl"
instance VectorSpace (Score a) where
type Scalar (Score a) = DurationT
d *^ s = d `stretch` s
instance Arbitrary a => Arbitrary (Score a) where
arbitrary = do
x <- arbitrary
t <- fmap toDurationT $ (arbitrary::Gen Double)
d <- fmap toDurationT $ (arbitrary::Gen Double)
return $ delay t $ stretch d $ (note x)
-- |
-- Create a score of duration one with the given value (same as 'return').
--
note :: a -> Score a
note = return
-- |
-- Create a score of duration one with no values.
--
rest :: Score (Maybe a)
rest = return Nothing
-- |
-- Repeat a score indefinately.
--
repeat :: Score a -> Score a
repeat a = a `plus` delay (duration a) (repeat a)
where
Score as `plus` Score bs = Score (as <> bs)
-- |
-- Map over all events in a score.
--
mapTime :: (TimeT -> DurationT -> a -> b) -> Score a -> Score b
mapTime f = Score . fmap (mapEvent f) . getScore
mapEvent :: (TimeT -> DurationT -> a -> b) -> (TimeT, DurationT, a) -> (TimeT, DurationT, b)
mapEvent f (t, d, x) = (t, d, f t d x)
-------------------------------------------------------------------------------------
delay' t = delay (fromTimeT t)
fst3 (a,b,c) = a
list z f [] = z
list z f xs = f xs
first f (x,y) = (f x, y)
second f (x,y) = (x, f y)
first3 f (a,b,c) = (f a,b,c)
second3 f (a,b,c) = (a,f b,c)
mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]
mergeBy f [] ys = ys
mergeBy f xs [] = xs
mergeBy f xs'@(x:xs) ys'@(y:ys)
| x `f` y == LT = x : mergeBy f xs ys'
| x `f` y /= LT = y : mergeBy f xs' ys