music-score-1.6: src/Music/Score/Score.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
-------------------------------------------------------------------------------------
-- |
-- 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 type
Score,
notes,
events,
-- mkScore,
-- getScore,
mapScore,
reifyScore,
mapWithSpan,
filterWithSpan,
mapFilterWithSpan,
mapEvents,
filterEvents,
mapFilterEvents,
) where
import Control.Applicative
import Control.Arrow
import Control.Comonad
import Control.Lens
import Control.Monad
import Control.Monad.Compose
import Control.Monad.Plus
import Data.Dynamic
import Data.Foldable (foldMap)
import Data.Maybe
import Data.Ord
import Data.Semigroup
import Data.AffineSpace
import Data.AffineSpace.Point
import Data.VectorSpace
import Test.QuickCheck (Arbitrary (..), Gen (..))
import Data.Default
import Data.Foldable (Foldable)
import qualified Data.Foldable as F
import qualified Data.List as List
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Traversable (Traversable)
import qualified Data.Traversable as T
import Data.Typeable
import Music.Dynamics.Literal
import Music.Pitch.Literal
import Music.Score.Meta
import Music.Score.Note
import Music.Score.Part
import Music.Score.Pitch
import Music.Score.Util
import Music.Time
import Music.Time.Reactive
newtype Score a = Score { getScore' :: (Meta, NScore a) }
deriving (Functor, Semigroup, Monoid, Foldable, Traversable, Typeable)
-- | TODO not a real iso, must be lens (meta)
notes :: Iso (Score a) (Score b) [Note a] [Note b]
notes = iso (getNScore . snd . getScore') (Score . return . NScore)
-- | TODO not a real iso, must be lens (meta)
events :: Iso (Score a) (Score b) [(Time, Duration, a)] [(Time, Duration, b)]
events = iso getScore mkScore
inScore f = Score . f . getScore'
mkScore :: [(Time, Duration, a)] -> Score a
mkScore = mconcat . fmap (uncurry3 event)
where
event t d x = (delay (t .-. origin) . stretch d) (return x)
getScore :: Score a -> [(Time, Duration, a)]
getScore =
fmap (\(view delta -> (t,d),x) -> (t,d,x)) .
List.sortBy (comparing fst) .
F.toList .
fmap getNote .
reifyScore
-- | Map with the associated time span.
mapScore :: (Note a -> b) -> Score a -> Score b
mapScore f = over _Wrapped (second $ mapNScore f)
-- | Group each occurence with its associated time span.
--
-- Note: This may or may not be what you expect. Each note is /not/ repositioned
-- to start at 'sunit', so this holds
--
-- > fmap extract . reifyScore = id
--
-- while
--
-- > join . fmap (noteToScore) . reifyScore /= id
--
reifyScore :: Score a -> Score (Note a)
reifyScore = over _Wrapped (second reifyNScore)
-- | Map over the events in a score.
mapWithSpan :: (Span -> a -> b) -> Score a -> Score b
mapWithSpan f = mapScore (uncurry f . getNote)
-- | Filter the events in a score.
filterWithSpan :: (Span -> a -> Bool) -> Score a -> Score a
filterWithSpan f = mapFilterWithSpan (partial2 f)
-- | Efficient combination of 'mapEvents' and 'filterEvents'.
mapFilterWithSpan :: (Span -> a -> Maybe b) -> Score a -> Score b
mapFilterWithSpan f = mcatMaybes . mapWithSpan f
-- | Map over the events in a score.
mapEvents :: (Time -> Duration -> a -> b) -> Score a -> Score b
mapEvents f = mapWithSpan (uncurry f . view delta)
-- | Filter the events in a score.
filterEvents :: (Time -> Duration -> a -> Bool) -> Score a -> Score a
filterEvents f = mapFilterEvents (partial3 f)
-- | Efficient combination of 'mapEvents' and 'filterEvents'.
mapFilterEvents :: (Time -> Duration -> a -> Maybe b) -> Score a -> Score b
mapFilterEvents f = mcatMaybes . mapEvents f
instance Wrapped (Score a) where
type Unwrapped (Score a) = (Meta, NScore a)
_Wrapped' = iso getScore' Score
instance Rewrapped (Score a) (Score b) where
instance Applicative Score where
pure = return
(<*>) = ap
instance Monad Score where
return = (^. _Unwrapped') . return . return
xs >>= f = (^. _Unwrapped') $ mbind ((^. _Wrapped') . f) ((^. _Wrapped') xs)
instance Alternative Score where
empty = mempty
(<|>) = mappend
instance MonadPlus Score where
mzero = mempty
mplus = mappend
instance HasOnset (Score a) where
onset (Score (m,x)) = onset x
instance HasOffset (Score a) where
offset (Score (m,x)) = offset x
instance Delayable (Score a) where
delay n (Score (m,x)) = Score (delay n m, delay n x)
instance Stretchable (Score a) where
stretch n (Score (m,x)) = Score (stretch n m, stretch n x)
instance HasDuration (Score a) where
duration = durationDefault
instance Reversible a => Reversible (Score a) where
rev = fmap rev . withSameOnset (stretch (-1))
instance HasMeta (Score a) where
meta = _Wrapped' . _1
-- |
-- Score without meta-events.
--
-- Semantics: a list of 'Note'. The semantics of each instances follow the instances of
-- the semantics.
--
newtype NScore a = NScore { getNScore :: [Note a] } -- sorted
deriving (Functor, Foldable, Semigroup, Monoid, Traversable, Delayable, Stretchable, HasOnset, HasOffset)
inNScore f = NScore . f . getNScore
-- | Map with the associated span.
mapNScore :: (Note a -> b) -> NScore a -> NScore b
mapNScore f = inNScore (fmap $ extend f)
-- | Reify the associated span. Use with 'Traversable' to get a fold.
reifyNScore :: NScore a -> NScore (Note a)
reifyNScore = inNScore $ fmap duplicate
instance Wrapped (NScore a) where
type Unwrapped (NScore a) = [Note a]
_Wrapped' = iso getNScore NScore
instance Applicative NScore where
pure = return
(<*>) = ap
instance Monad NScore where
return = (^. _Unwrapped') . return . return
xs >>= f = (^. _Unwrapped') $ mbind ((^. _Wrapped') . f) ((^. _Wrapped') xs)
instance MonadPlus NScore where
mzero = mempty
mplus = mappend
instance HasDuration (Note a) where
duration = durationDefault
-- The following instances allow us to write expressions like [c..g]
instance IsPitch a => IsPitch (Score a) where
fromPitch = pure . fromPitch
instance IsDynamics a => IsDynamics (Score a) where
fromDynamics = pure . fromDynamics
instance IsInterval a => IsInterval (Score a) where
fromInterval = pure . fromInterval
instance Enum a => Enum (Score a) where
toEnum = return . toEnum
fromEnum = list 0 (fromEnum . head) . F.toList
-- TODO
instance Num a => Num (Score a) where
fromInteger = return . fromInteger
-- Bogus VectorSpace instance, so we can use c^*2 etc.
-- If you hate this instance, please open an issue.
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) = Duration
d *^ s = d `stretch` s
type instance Pitch (Score a) = Pitch a
instance (HasSetPitch a b,
Transformable (Pitch a),
Transformable (Pitch b)) =>
HasSetPitch (Score a) (Score b) where
type SetPitch g (Score a) = Score (SetPitch g a)
-- TODO really similar to indexed maps
-- compare lens, category-extras
__mapPitch f = mapWithSpan (__mapPitch . (`sunder` f))
type instance Part (Score a) = Part a
instance HasPart a => HasPart (Score a) where
getPart = error "No Score.getPart"
modifyPart f = fmap (modifyPart f)
-- TODO mo
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 (view _3) . partial (uncurry3 f))