{-# 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))