{-# LANGUAGE CPP, TypeFamilies, DeriveFunctor, DeriveFoldable, FlexibleInstances, FlexibleContexts, ConstraintKinds, OverloadedStrings, MultiParamTypeClasses, NoMonomorphismRestriction, GeneralizedNewtypeDeriving #-} ------------------------------------------------------------------------------------- -- | -- Copyright : (c) Hans Hoglund 2012 -- -- License : BSD-style -- -- Maintainer : hans@hanshoglund.se -- Stability : experimental -- Portability : non-portable (TF,GNTD) -- -- Combinators for manipulating scores. -- ------------------------------------------------------------------------------------- module Music.Score.Combinators ( -- ** Preliminaries Monoid', Transformable1, Transformable, Composable, HasEvents, -- ** Composing scores (|>), (<|), scat, pcat, -- *** Special composition sustain, anticipate, -- ** Transforming scores -- *** Moving in time move, moveBack, startAt, stopAt, -- *** Stretching in time stretch, compress, stretchTo, -- *** Rests rest, removeRests, -- *** Repetition times, repeated, group, -- triplet, -- quadruplet, -- quintuplet, -- *** Transformations perform, compose, retrograde, mapEvents, filterEvents, mapFilterEvents, mapAllEvents, mapEventsSingle, mapFirst, mapLast, mapPhrase, mapPhraseSingle, -- ** Conversion scoreToVoice, voiceToScore, voiceToScore', eventToScore, ) where import Control.Monad import Control.Monad.Plus import Data.Semigroup import Data.String import Data.Foldable (Foldable) import Data.Traversable import Data.VectorSpace import Data.AffineSpace import Data.Ratio import Data.Pointed import Data.Ord import Music.Score.Track import Music.Score.Voice import Music.Score.Score import Music.Score.Part import Music.Time import qualified Data.List as List -- | -- This pseudo-class can be used in place of 'Monoid' whenever an additional 'Semigroup' -- constraint is needed. -- -- Ideally, 'Monoid' should be changed to extend 'Semigroup' instead. -- type Monoid' a = (Monoid a, Semigroup a) -- TODO names? type Scalable t d a = ( Stretchable a, Delayable a, AdditiveGroup t, AffineSpace t, Diff t ~ d, Time a ~ t, Duration a ~ d ) -- | -- This class includes time-based structures that can be scaled and moved in time. -- class ( Stretchable s, Delayable s, AdditiveGroup (Time s), AffineSpace (Time s) ) => Transformable1 s where instance Transformable1 Score instance Transformable1 Track instance (Transformable1 a, t ~ Time a) => Transformable1 (AddOffset t a) -- | -- This class includes time-based structures with a known position in time. -- class ( HasOnset s, HasOffset s, Transformable1 s ) => Transformable s where {- type Transformable t d a = ( Stretchable a, Delayable a, AdditiveGroup t, AffineSpace t, HasOnset a, HasOffset a, Time a ~ t, Duration a ~ d ) -} instance Transformable Score instance (Transformable a, t ~ Time a) => Transformable (AddOffset t a) -- | -- This class includes time-based structures that can be transcribed. -- class ( MonadPlus s, Transformable s ) => Composable s where instance Composable Score -- instance Composable Track -- | -- This class includes time-based structures that can be perfomed /and/ transcribed. -- -- The combined power of 'perform' and 'compose' give us the power to traverse and -- the entire event structure, as per 'mapEvents'. -- class ( Performable s, Composable s ) => HasEvents s where {- type HasEvents t d s a = ( Performable s, MonadPlus s, Transformable t d (s a) ) -} instance HasEvents Score ------------------------------------------------------------------------------------- -- Constructors ------------------------------------------------------------------------------------- -- | -- Create a score containing a single event. -- -- This function uses the unit position (0, 1). -- -- > a -> Score a -- note :: Monad s => a -> s a note = return -- | -- Create a score containing a rest at time zero of duration one. -- -- This function uses the unit position (0, 1). -- -- > Score (Maybe a) -- rest :: MonadPlus s => s (Maybe a) rest = note Nothing -- | -- Create a note or a rest. This is an alias for 'mfromMaybe' with a nicer reading. -- -- This function uses the unit position (0, 1). -- -- > a -> Score a -- noteRest :: MonadPlus s => Maybe a -> s a noteRest = mfromMaybe -- | Creates a score containing a chord. -- -- This function uses the unit position (0, 1). -- -- > [a] -> Score a -- -- chord :: (Pointed s, Monoid (s a)) => [a] -> s a chord :: (MonadPlus s, Monoid' (s a)) => [a] -> s a chord = pcat . map note -- | Creates a score containing the given elements, composed in sequence. -- -- > [a] -> Score a -- melody :: (MonadPlus s, Monoid' (s a), Transformable s) => [a] -> s a melody = scat . map note -- | Like 'melody', but stretching each note by the given factors. -- -- > [(Duration, a)] -> Score a -- melodyStretch :: (MonadPlus s, Monoid' (s a), Transformable s, d ~ Duration s) => [(d, a)] -> s a melodyStretch = scat . map ( \(d, x) -> stretch d $ note x ) -- | Like 'chord', but delays each note the given amounts. -- -- > [(Time, a)] -> Score a -- chordDelay :: (MonadPlus s, Monoid (s a), Transformable s, t ~ Time s) => [(t, a)] -> s a chordDelay = pcat . map (\(t, x) -> delay' t $ note x) -- | Like 'chord', but delays and stretches each note the given amounts. -- -- > [(Time, Duration, a)] -> Score a -- chordDelayStretch :: (MonadPlus s, Monoid (s a), Transformable s, d ~ Duration s, t ~ Time s) => [(t, d, a)] -> s a chordDelayStretch = pcat . map (\(t, d, x) -> delay' t . stretch d $ note x) ------------------------------------------------------------------------------------- -- Transformations ------------------------------------------------------------------------------------- -- | -- Move a score forward in time. Equivalent to 'delay'. -- -- > Duration -> Score a -> Score a -- move :: (Delayable s, d ~ Duration s) => d -> s a -> s a move = delay -- | -- Move a score backward in time. Negated verison of 'delay' -- -- > Duration -> Score a -> Score a -- moveBack :: (Delayable s, AdditiveGroup d, d ~ Duration s) => d -> s a -> s a moveBack t = delay (negateV t) -- | -- Move a score so that its onset is at the specific time. -- -- > Duration -> Score a -> Score a -- startAt :: (HasOnset s, Delayable s, AffineSpace t, t ~ Time s) => t -> s a -> s a t `startAt` x = (t .-. onset x) `delay` x -- | -- Move a score so that its offset is at the specific time. -- -- > Duration -> Score a -> Score a -- stopAt :: (HasOffset s, Delayable s, AffineSpace t, t ~ Time s) => t -> s a -> s a t `stopAt` x = (t .-. offset x) `delay` x -- | -- Compress (diminish) a score. Flipped version of '^/'. -- -- > Duration -> Score a -> Score a -- compress :: (Stretchable s, Fractional d, d ~ Duration s) => d -> s a -> s a compress x = stretch (recip x) -- | -- Stretch a score to fit into the given duration. -- -- > Duration -> Score a -> Score a -- stretchTo :: (Stretchable s, HasDuration s, Fractional d, d ~ Duration s) => d -> s a -> s a t `stretchTo` x = (t / duration x) `stretch` x ------------------------------------------------------------------------------------- -- Composition ------------------------------------------------------------------------------------- infixr 6 |> infixr 6 <| -- | -- Compose in sequence. -- -- To compose in parallel, use '<>'. -- -- > Score a -> Score a -> Score a (|>) :: (Semigroup (s a), AffineSpace (Time s), HasOnset s, HasOffset s, Delayable s) => s a -> s a -> s a a |> b = a <> startAt (offset a) b -- | -- Compose in reverse sequence. -- -- To compose in parallel, use '<>'. -- -- > Score a -> Score a -> Score a (<|) :: (Semigroup (s a), AffineSpace (Time s), HasOnset s, HasOffset s, Delayable s) => s a -> s a -> s a a <| b = b |> a -- | -- Sequential concatentation. -- -- > [Score t] -> Score t scat :: (Monoid' (s a), AffineSpace (Time s), HasOnset s, HasOffset s, Delayable s) => [s a] -> s a scat = foldr (|>) mempty -- | -- Parallel concatentation. A synonym for 'mconcat'. -- -- > [Score t] -> Score t pcat :: Monoid a => [a] -> a pcat = mconcat -- | -- Like '<>', but scaling the second agument to the duration of the first. -- -- > Score a -> Score a -> Score a -- sustain :: (Fractional (Duration s), Semigroup (s a), Stretchable s, HasDuration s) => s a -> s a -> s a x `sustain` y = x <> duration x `stretchTo` y -- Like '<>', but truncating the second agument to the duration of the first. -- prolong x y = x <> before (duration x) y -- | -- Like '|>' but with a negative delay on the second element. -- -- > Duration -> Score a -> Score a -> Score a -- anticipate :: (Semigroup (s a), Transformable s, d ~ Duration s, Ord d) => d -> s a -> s a -> s a anticipate t a b = a <> startAt (offset a .-^ t) b -------------------------------------------------------------------------------- -- Structure -------------------------------------------------------------------------------- -- | -- Repeat exact amount of times. -- -- > Duration -> Score Note -> Score Note -- times :: (Monoid' (s a), Transformable s) => Int -> s a -> s a times n a = replicate (0 `max` n) () `repeated` const a -- | -- Repeat once for each element in the list. -- -- Example: -- -- > repeated [1,2,1] (c^*) -- -- Simple type: -- -- > [a] -> (a -> Score Note) -> Score Note -- repeated :: (Monoid' (s b), Transformable s) => [a] -> (a -> s b) -> s b repeated = flip (\f -> scat . fmap f) {- repeatedIndex n = repeated [0..n-1] repeatedTime n = repeated $ fmap (/ n) [0..(n - 1)] -} -- | -- Remove rests from a score. -- -- This is just an alias for 'mcatMaybes' which reads better in certain contexts. -- -- > Score (Maybe a) -> Score a -- removeRests :: MonadPlus m => m (Maybe a) -> m a removeRests = mcatMaybes -- -- | -- -- Repeat three times and scale down by three. -- -- -- -- > Score a -> Score a -- -- -- triplet :: (Monoid' (s a), Transformable t d s, Time s ~ TimeT) => s a -> s a -- triplet = group 3 -- -- -- | -- -- Repeat three times and scale down by three. -- -- -- -- > Score a -> Score a -- -- -- quadruplet :: (Monoid' (s a), Transformable t d s, Time s ~ TimeT) => s a -> s a -- quadruplet = group 4 -- -- -- | -- -- Repeat three times and scale down by three. -- -- -- -- > Score a -> Score a -- -- -- quintuplet :: (Monoid' (s a), Transformable t d s, Time s ~ TimeT) => s a -> s a -- quintuplet = group 5 -- | -- Repeat a number of times and scale down by the same amount. -- -- > Duration -> Score a -> Score a -- group :: (Monoid' (s a), Transformable s, Time s ~ TimeT) => Int -> s a -> s a group n a = times n (toDurationT n `compress` a) -- | -- Reverse a score around its middle point (TODO not correct documentation w.r.t to start). -- -- > onset a = onset (retrograde a) -- > duration a = duration (retrograde a) -- > offset a = offset (retrograde a) -- -- > Score a -> Score a retrograde :: (HasEvents s, t ~ Time s, Num t, Ord t) => s a -> s a retrograde = startAt 0 . (mapAllEvents $ List.sortBy (comparing fst3) . fmap g) where g (t,d,x) = (-(t.+^d),d,x) -------------------------------------------------------------------------------- -- Mapping and recomposition -------------------------------------------------------------------------------- #define MAP_CONSTRAINT \ HasPart' a, \ HasEvents s -- | Recompose a score. -- -- This is the inverse of 'perform' -- -- > [(Time, Duration, a)] -> Score a -- compose :: (Composable s, d ~ Duration s, t ~ Time s) => [(t, d, a)] -> s a compose = msum . liftM eventToScore -- retrograde :: (HasEvents s, t ~ Time s, Num t, Ord t) => s a -> s a mapAllEvents :: (HasEvents s, d ~ Duration s, t ~ Time s) => ([(t, d, a)] -> [(t, d, b)]) -> s a -> s b mapAllEvents f = compose . f . perform {- mapFilterAllEvents :: (HasEvents s, d ~ Duration s, t ~ Time s) => ([(t, d, a)] -> [(t, d, Maybe b)]) -> s a -> s b mapFilterAllEvents f = mcatMaybes . mapAllEvents f -} -- | -- Map over the events in a score. -- -- > (Time -> Duration -> a -> b) -> Score a -> Score b -- filterEvents :: (MAP_CONSTRAINT, t ~ Time s, d ~ Duration s) => (t -> d -> a -> Bool) -> s a -> s a filterEvents f = mapFilterEvents (partial3 f) -- TODO Maybe this could be optimized by using mapEventsSingle? -- | -- Map over the events in a score. -- -- > (Time -> Duration -> a -> b) -> Score a -> Score b -- mapFilterEvents :: (MAP_CONSTRAINT, t ~ Time s, d ~ Duration s) => (t -> d -> a -> Maybe b) -> s a -> s b mapFilterEvents f = mcatMaybes . mapAllParts (liftM $ mapEventsSingle f) -- | -- Map over the events in a score. -- -- > (Time -> Duration -> a -> b) -> Score a -> Score b -- mapEvents :: (MAP_CONSTRAINT, t ~ Time s, d ~ Duration s) => (t -> d -> a -> b) -> s a -> s b mapEvents f = mapAllParts (liftM $ mapEventsSingle f) -- | -- Equivalent to 'mapEvents' for single-voice scores. -- Fails if the score contains overlapping events. -- -- > (Time -> Duration -> a -> b) -> Score a -> Score b -- mapEventsSingle :: (HasEvents s, t ~ Time s, d ~ Duration s) => (t -> d -> a -> b) -> s a -> s b mapEventsSingle f sc = compose . fmap (third' f) . perform $ sc -- | -- Equivalent to 'mapEvents' for single-voice scores. -- Fails if the score contains overlapping events. -- -- > ([(Time,Duration,a)] -> [b]) -> Score a -> Score b -- -- mapAllEventsSingle :: (HasEvents s, t ~ Time s, d ~ Duration s) => ([(t,d,a)] -> b) -> s a -> s b -- mapAllEventsSingle f sc = compose . fmap trd3 . f . perform $ sc -- mapAllEventsSingle' :: (HasEvents s, t ~ Time s, d ~ Duration s) => ([(t,d,a)] -> [b]) -> s a -> s b -- mapAllEventsSingle' f = compose . fmap trd3 . f . perform trd3 (a,b,c) = c mapAllEventsSingle' :: (HasEvents s, t ~ Time s, d ~ Duration s) => ([(t,d,a)] -> [(t,d,b)]) -> s a -> s b mapAllEventsSingle' f = compose . f . perform -- | -- Map over the first, and remaining notes in each part. -- -- If a part has only one notes, the first function is applied. -- If a part has no notes, the given score is returned unchanged. -- -- > (a -> b) -> (a -> b) -> Score a -> Score b -- mapFirst :: (MAP_CONSTRAINT) => (a -> b) -> (a -> b) -> s a -> s b mapFirst f g = mapPhrase f g g -- | -- Map over the last, and preceding notes in each part. -- -- If a part has only one notes, the first function is applied. -- If a part has no notes, the given score is returned unchanged. -- -- > (a -> b) -> (a -> b) -> Score a -> Score b -- mapLast :: (MAP_CONSTRAINT) => (a -> b) -> (a -> b) -> s a -> s b mapLast f g = mapPhrase g g f -- | -- Map over the first, middle and last note in each part. -- -- If a part has fewer than three notes the first takes precedence over the last, -- and last takes precedence over the middle. -- -- > (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b -- mapPhrase :: (MAP_CONSTRAINT) => (a -> b) -> (a -> b) -> (a -> b) -> s a -> s b mapPhrase f g h = mapAllParts (liftM $ mapPhraseSingle f g h) -- | -- Equivalent to 'mapPhrase' for single-voice scores. -- Fails if the score contains overlapping events. -- -- > (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b -- mapPhraseSingle :: HasEvents s => (a -> b) -> (a -> b) -> (a -> b) -> s a -> s b mapPhraseSingle f g h sc = compose . mapFirstMiddleLast (third f) (third g) (third h) . perform $ sc -- eventToScore :: Scalable t d a => (t, d, a) -> m a eventToScore :: (Monad s, Transformable1 s, Time s ~ t, Duration s ~ d ) => (t, d, a) -> s a eventToScore (t,d,x) = delay' t . stretch d $ return x -------------------------------------------------------------------------------- -- Conversion -------------------------------------------------------------------------------- -- | -- Convert a score into a voice. -- -- This function fails if the score contain overlapping events. -- scoreToVoice :: Score a -> Voice (Maybe a) scoreToVoice = Voice . fmap throwTime . addRests' . perform where throwTime (t,d,x) = (d,x) -- | -- Convert a voice into a score. -- voiceToScore :: Voice a -> Score a voiceToScore = scat . fmap g . getVoice where g (d,x) = stretch d (note x) -- | -- Convert a voice which may contain rests into a score. -- voiceToScore' :: Voice (Maybe a) -> Score a voiceToScore' = mcatMaybes . voiceToScore -- TODO move this instance instance Performable Voice where perform = perform . voiceToScore -------------------------------------------------------------------------------- addRests' :: [(TimeT, DurationT, a)] -> [(TimeT, DurationT, Maybe a)] addRests' = concat . snd . mapAccumL g 0 where g u (t, d, x) | u == t = (t .+^ d, [(t, d, Just x)]) | u < t = (t .+^ d, [(u, t .-. u, Nothing), (t, d, Just x)]) | otherwise = error "addRests: Strange prevTime" -- | -- Map over first, middle and last elements of list. -- Biased on first, then on first and last for short lists. -- mapFirstMiddleLast :: (a -> b) -> (a -> b) -> (a -> b) -> [a] -> [b] mapFirstMiddleLast f g h = go where go [] = [] go [a] = [f a] go [a,b] = [f a, h b] go xs = [f $ head xs] ++ map g (tail $ init xs) ++ [h $ last xs] delay' t = delay (t .-. zeroV) fst3 (t, d, x) = t third f (a,b,c) = (a,b,f c) third' f (a,b,c) = (a,b,f a b c) rotl [] = [] rotl (x:xs) = xs ++ [x] rotr [] = [] rotr xs = last xs : init xs curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d curry3 = curry . curry . (. trip) uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d uncurry3 = (. untrip) . uncurry . uncurry untrip (a,b,c) = ((a,b),c) trip ((a,b),c) = (a,b,c) {- partial :: (a -> Bool) -> a -> Maybe a -} partial2 :: (a -> b -> Bool) -> a -> b -> Maybe b partial3 :: (a -> b -> c -> Bool) -> a -> b -> c -> Maybe c partial2 f = curry (fmap snd . partial (uncurry f)) partial3 f = curry3 (fmap trd3 . partial (uncurry3 f)) rotated :: Int -> [a] -> [a] rotated = go where go n as | n >= 0 = iterate rotr as !! n | n < 0 = iterate rotl as !! abs n