music-score-1.7: src/Music/Score/Meta.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
-------------------------------------------------------------------------------------
-- |
-- Copyright : (c) Hans Hoglund 2012-2014
--
-- License : BSD-style
--
-- Maintainer : hans@hanshoglund.se
-- Stability : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Combinators for manipulating scores and related structures.
--
-- /Warning/ This module is experimental.
--
-------------------------------------------------------------------------------------
module Music.Score.Meta (
module Music.Time.Meta,
-- TODO move
(</>),
rcat,
-- * Meta-events
addMetaNote,
addGlobalMetaNote,
fromMetaReactive,
metaAt,
metaAtStart,
withMeta,
withGlobalMeta,
withMetaAtStart,
withGlobalMetaAtStart,
) where
import Control.Applicative
import Control.Arrow
import Control.Lens hiding (parts, perform)
import Control.Monad
import Control.Monad.Plus
import Data.AffineSpace
import Data.AffineSpace.Point
import Data.Foldable (Foldable (..))
import Data.Maybe
import Data.Ord
import Data.Ratio
import Data.Semigroup
import Data.String
import Data.Traversable
import Data.VectorSpace
import Music.Score.Part
import Music.Score.Internal.Util
import Music.Time
import Music.Time.Meta
import Music.Time.Reactive
import qualified Data.Foldable as Foldable
import qualified Data.List as List
infixr 6 </>
-- |
-- Concatenate parts.
--
rcat :: (HasParts' a, Enum (Part a)) => [Score a] -> Score a
rcat = List.foldr (</>) mempty
-- |
-- Similar to '<>', but increases parts in the second part to prevent collision.
--
(</>) :: (HasParts' a, Enum (Part a)) => Score a -> Score a -> Score a
a </> b = a <> moveParts offset b
where
-- max voice in a + 1
offset = succ $ maximum' 0 $ fmap fromEnum $ toListOf parts a
-- |
-- Move down one voice (all parts).
--
moveParts :: (Integral b, HasParts' a, Enum (Part a)) => b -> Score a -> Score a
moveParts x = parts %~ (successor x)
-- |
-- Move top-part to the specific voice (other parts follow).
--
moveToPart :: (Enum b, HasParts' a, Enum (Part a)) => b -> Score a -> Score a
moveToPart v = moveParts (fromEnum v)
iterating :: (a -> a) -> (a -> a) -> Int -> a -> a
iterating f g n
| n < 0 = f . iterating f g (n + 1)
| n == 0 = id
| n > 0 = g . iterating f g (n - 1)
successor :: (Integral b, Enum a) => b -> a -> a
successor n = iterating pred succ (fromIntegral n)
maximum' :: (Ord a, Foldable t) => a -> t a -> a
maximum' z = option z getMax . foldMap (Option . Just . Max)
addMetaNote :: forall a b . (IsAttribute a, HasMeta b{-, HasPart' b-}) => Note a -> b -> b
addMetaNote x y = (applyMeta $ toMeta (Just y) $ noteToReactive x) y
addGlobalMetaNote :: forall a b . (IsAttribute a, HasMeta b) => Note a -> b -> b
addGlobalMetaNote x = applyMeta $ toMeta (Nothing::Maybe Int) $ noteToReactive x
fromMetaReactive :: forall a b . ({-HasPart' a, -}IsAttribute b) => Maybe a -> Meta -> Reactive b
fromMetaReactive part = fromMaybe mempty . fromMeta part
withSpan :: Score a -> Score (Span, a)
withSpan = mapEvents (\t d x -> (t >-> d,x))
withTime = mapEvents (\t d x -> (t, x))
inSpan t' (view range -> (t,u)) = t <= t' && t' < u
-- TODO clean
mapBefore :: Time -> (Score a -> Score a) -> Score a -> Score a
mapDuring :: Span -> (Score a -> Score a) -> Score a -> Score a
mapAfter :: Time -> (Score a -> Score a) -> Score a -> Score a
mapBefore t f x = let (y,n) = (fmap snd *** fmap snd) $ mpartition (\(t2,x) -> t2 < t) (withTime x) in (f y <> n)
mapDuring s f x = let (y,n) = (fmap snd *** fmap snd) $ mpartition (\(t,x) -> t `inSpan` s) (withTime x) in (f y <> n)
mapAfter t f x = let (y,n) = (fmap snd *** fmap snd) $ mpartition (\(t2,x) -> t2 >= t) (withTime x) in (f y <> n)
-- Transform the score with the current value of some meta-information
-- Each "update chunk" of the meta-info is processed separately
runScoreMeta :: forall a b . ({-HasPart' a, -}IsAttribute b) => Score a -> Reactive b
runScoreMeta = fromMetaReactive (Nothing :: Maybe a) . (view meta)
metaAt :: ({-HasPart' a, -}IsAttribute b) => Time -> Score a -> b
metaAt x = (`atTime` x) . runScoreMeta
metaAtStart :: ({-HasPart' a, -}IsAttribute b) => Score a -> b
metaAtStart x = _onset x `metaAt` x
withGlobalMeta :: IsAttribute a => (a -> Score b -> Score b) -> Score b -> Score b
withGlobalMeta = withMeta' (Nothing :: Maybe Int)
withMeta :: (IsAttribute a{-, HasPart' b-}) => (a -> Score b -> Score b) -> Score b -> Score b
withMeta f x = withMeta' (Just x) f x
withMeta' :: ({-HasPart' c, -}IsAttribute a) => Maybe c -> (a -> Score b -> Score b) -> Score b -> Score b
withMeta' part f x = let
m = (view meta) x
r = fromMetaReactive part m
in case splitReactive r of
Left a -> f a x
Right ((a, t), bs, (u, c)) ->
(meta .~) m
$ mapBefore t (f a)
$ (composed $ fmap (\(view (from note) -> (s, a)) -> mapDuring s $ f a) $ bs)
$ mapAfter u (f c)
$ x
withGlobalMetaAtStart :: IsAttribute a => (a -> Score b -> Score b) -> Score b -> Score b
withGlobalMetaAtStart = withMetaAtStart' (Nothing :: Maybe Int)
withMetaAtStart :: (IsAttribute a{-, HasPart' b-}) => (a -> Score b -> Score b) -> Score b -> Score b
withMetaAtStart f x = withMetaAtStart' (Just x) f x
withMetaAtStart' :: (IsAttribute b{-, HasPart' p-}) =>
Maybe p -> (b -> Score a -> Score a) -> Score a -> Score a
withMetaAtStart' partId f x = let
m = (view meta) x
in f (fromMetaReactive partId m `atTime` _onset x) x
-- TODO move
noteToReactive :: Monoid a => Note a -> Reactive a
noteToReactive n = (pure <$> n) `activate` pure mempty
activate :: Note (Reactive a) -> Reactive a -> Reactive a
activate (view (from note) -> (view range -> (start,stop), x)) y = y `turnOn` (x `turnOff` y)
where
turnOn = switchR start
turnOff = switchR stop