music-score-1.2: src/Music/Score/Score.hs
{-# LANGUAGE
TypeFamilies,
DeriveFunctor,
DeriveFoldable,
GeneralizedNewtypeDeriving #-}
-------------------------------------------------------------------------------------
-- |
-- Copyright : (c) Hans Hoglund 2012
--
-- License : BSD-style
--
-- Maintainer : hans@hanshoglund.se
-- Stability : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Provides a musical score represenation.
--
-------------------------------------------------------------------------------------
module Music.Score.Score (
Score(..),
rest,
note,
-- filterS,
perform,
performRelative
) where
import Prelude hiding (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.Maybe
import Data.Either
import Data.Function (on)
import Data.Ord (comparing)
import Data.Ratio
import Data.VectorSpace
import Data.AffineSpace
import qualified Data.Map as Map
import qualified Data.List as List
import Music.Pitch.Literal
import Music.Dynamics.Literal
import Music.Score.Time
import Music.Score.Duration
-------------------------------------------------------------------------------------
-- Score type
-------------------------------------------------------------------------------------
-- |
-- A score is a sorted list of absolute time notes and rests. A rest is a duration and
-- a note is a value and a duration.
--
-- 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 a 'Monad'. 'return' creates a score containing a single note of
-- duration one, and '>>=' transforms the values of a score, while allowing
-- transformations of time and duration. More intuitively, 'join' scales and
-- offsets each inner score to fit into an outer score, then removes the intermediate
-- structure.
--
-- > let s = Score [(0, 1, Just 0), (1, 2, Just 1)] :: Score Int
-- >
-- > s >>= \x -> Score [ (0, 1, Just $ toEnum $ x+65),
-- > (1, 3, Just $ toEnum $ x+97) ] :: Score Char
-- >
-- > ===> Score {getScore = [ (0 % 1, 1 % 1, Just 'A'),
-- > (1 % 1, 3 % 1, Just 'a'),
-- > (1 % 1, 2 % 1, Just 'B'),
-- > (3 % 1, 6 % 1, Just 'b') ]}
--
-- Score is an instance of 'VectorSpace' using sequential composition as addition,
-- and time scaling as scalar multiplication.
--
newtype Score a = Score { getScore :: [(Time, Duration, Maybe a)] }
deriving ({-Eq, Ord, -}Show, Functor, Foldable)
-- TODO invariant that the list is sorted
-- Performance equality needeed because of rests...
instance Eq a => Eq (Score a) where
a == b = perform a == perform b
instance Ord a => Ord (Score a) where
a `compare` b = perform a `compare` perform b
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)
fst3 (a,b,c) = a
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 Monad Score where
return = note
a >>= k = join' $ fmap k a
where
join' sc = mconcat $ toList $ mapWithTimeDur (\t d -> fmap (delay t . (d*^) )) $ sc
mapWithTimeDur :: (Duration -> Duration -> Maybe a -> Maybe b) -> Score a -> Score b
mapWithTimeDur f = Score . fmap (liftTimeDur f) . getScore
liftTimeDur :: (Duration -> Duration -> Maybe a -> Maybe b) -> (Time, Duration, Maybe a) -> (Time, Duration, Maybe b)
liftTimeDur f (t,d,x) = case f (t2d t) d x of
Nothing -> (t,d,Nothing)
Just y -> (t,d,Just y)
where
t2d = Duration . getTime
instance AdditiveGroup (Score a) where
zeroV = mempty
(^+^) = mappend
negateV = id
instance VectorSpace (Score a) where
type Scalar (Score a) = Duration
d *^ Score sc = Score . fmap (first3 (^* d2t d) . second3 (^* d)) $ sc
where
first3 f (a,b,c) = (f a,b,c)
second3 f (a,b,c) = (a,f b,c)
d2t = Time . getDuration
instance Delayable (Score a) where
d `delay` Score sc = Score . fmap (first3 (.+^ d)) $ sc
where
first3 f (a,b,c) = (f a,b,c)
instance HasOnset (Score a) where
onset (Score []) = 0
-- onset (Score xs) = minimum (fmap on xs) where on (t,d,x) = t
onset (Score xs) = on (head xs) where on (t,d,x) = t
offset (Score []) = 0
offset (Score xs) = maximum (fmap off xs) where off (t,d,x) = t + (Time . getDuration $ d)
-- Note: this version of onset is lazier, but depends on the invariant above
instance HasDuration (Score a) 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
-- |
-- Create a score of duration one with no values.
--
rest :: Score a
rest = Score [(0,1,Nothing)]
-- |
-- Create a score of duration one with the given value. Equivalent to 'pure' and 'return'.
--
note :: a -> Score a
note x = Score [(0,1,Just x)]
{-
-- Use mfilter instead of this
filterS :: (a -> Bool) -> Score a -> Score a
filterS f = Score . filter g . getScore
where
g (t,d,Nothing) = True
g (t,d,(Just x)) = f x
-}
perform :: Score a -> [(Time, Duration, a)]
perform = removeRests . getScore
where
removeRests = catMaybes . fmap propagateRest
propagateRest (t, d, Just x) = Just (t, d, x)
propagateRest (t, d, Nothing) = Nothing
performRelative :: Score a -> [(Time, Duration, a)]
performRelative = toRel . perform
where
toRel = snd . mapAccumL g 0
g now (t,d,x) = (t, (t-now,d,x))
list z f [] = z
list z f xs = f xs
first f (x,y) = (f x, y)
second f (x,y) = (x, f y)
mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]
mergeBy f as bs = List.sortBy f $ as <> bs