music-score 1.6.2 → 1.7
raw patch · 85 files changed
+11672/−6178 lines, 85 filesdep +adjunctionsdep +colourdep +contravariantdep −monoid-extrasdep ~lensdep ~lilyponddep ~music-dynamics-literal
Dependencies added: adjunctions, colour, contravariant, distributive
Dependencies removed: monoid-extras
Dependency ranges changed: lens, lilypond, music-dynamics-literal, music-pitch-literal, musicxml2
Files
- music-score.cabal +61/−32
- src/Data/Clipped.hs +116/−0
- src/Data/Functor/Context.hs +17/−0
- src/Data/Functor/Couple.hs +92/−0
- src/Data/Functor/Rep/Lens.hs +30/−0
- src/Data/Semigroup/Instances.hs +78/−0
- src/Music/Score.hs +38/−35
- src/Music/Score/Articulation.hs +420/−94
- src/Music/Score/Chord.hs +0/−240
- src/Music/Score/Clef.hs +37/−20
- src/Music/Score/Color.hs +125/−0
- src/Music/Score/Combinators.hs +0/−457
- src/Music/Score/Convert.hs +45/−48
- src/Music/Score/Dynamics.hs +406/−102
- src/Music/Score/Export/Abc.hs +16/−0
- src/Music/Score/Export/ArticulationNotation.hs +131/−0
- src/Music/Score/Export/Backend.hs +238/−0
- src/Music/Score/Export/Common.hs +0/−105
- src/Music/Score/Export/DynamicNotation.hs +110/−0
- src/Music/Score/Export/Lilypond.hs +567/−313
- src/Music/Score/Export/Midi.hs +239/−176
- src/Music/Score/Export/MusicXml.hs +516/−260
- src/Music/Score/Export/NoteList.hs +162/−0
- src/Music/Score/Export/SuperCollider.hs +234/−0
- src/Music/Score/Harmonics.hs +124/−0
- src/Music/Score/Import/Abc.hs +15/−0
- src/Music/Score/Import/Lilypond.hs +15/−0
- src/Music/Score/Import/Midi.hs +23/−11
- src/Music/Score/Instances.hs +168/−759
- src/Music/Score/Internal/Export.hs +154/−0
- src/Music/Score/Internal/Util.hs +348/−0
- src/Music/Score/Meta.hs +120/−161
- src/Music/Score/Meta/Annotations.hs +8/−12
- src/Music/Score/Meta/Attribution.hs +18/−20
- src/Music/Score/Meta/Barline.hs +13/−13
- src/Music/Score/Meta/Clef.hs +13/−12
- src/Music/Score/Meta/Fermata.hs +9/−11
- src/Music/Score/Meta/Key.hs +9/−11
- src/Music/Score/Meta/RehearsalMark.hs +11/−11
- src/Music/Score/Meta/Tempo.hs +49/−42
- src/Music/Score/Meta/Time.hs +12/−17
- src/Music/Score/Meta/Title.hs +17/−19
- src/Music/Score/Note.hs +0/−99
- src/Music/Score/Ornaments.hs +0/−203
- src/Music/Score/Part.hs +259/−50
- src/Music/Score/Phrases.hs +243/−0
- src/Music/Score/Pitch.hs +407/−205
- src/Music/Score/Rhythm.hs +0/−406
- src/Music/Score/Score.hs +0/−296
- src/Music/Score/Slide.hs +155/−0
- src/Music/Score/Text.hs +120/−0
- src/Music/Score/Ties.hs +149/−63
- src/Music/Score/Track.hs +0/−168
- src/Music/Score/Tremolo.hs +106/−0
- src/Music/Score/Util.hs +0/−384
- src/Music/Score/Voice.hs +0/−183
- src/Music/Time.hs +45/−17
- src/Music/Time/Behavior.hs +317/−98
- src/Music/Time/Bound.hs +152/−0
- src/Music/Time/Chord.hs +136/−0
- src/Music/Time/Delayable.hs +0/−121
- src/Music/Time/Delayed.hs +143/−0
- src/Music/Time/Duration.hs +106/−0
- src/Music/Time/Internal/Quantize.hs +424/−0
- src/Music/Time/Internal/Transform.hs +384/−0
- src/Music/Time/Internal/Util.hs +373/−0
- src/Music/Time/Juxtapose.hs +108/−96
- src/Music/Time/Meta.hs +172/−0
- src/Music/Time/Note.hs +158/−0
- src/Music/Time/Onset.hs +0/−195
- src/Music/Time/Position.hs +218/−0
- src/Music/Time/Reactive.hs +212/−188
- src/Music/Time/Rest.hs +44/−0
- src/Music/Time/Reverse.hs +163/−12
- src/Music/Time/Score.hs +550/−0
- src/Music/Time/Segment.hs +371/−0
- src/Music/Time/Span.hs +0/−190
- src/Music/Time/Split.hs +120/−0
- src/Music/Time/Stretchable.hs +0/−110
- src/Music/Time/Stretched.hs +131/−0
- src/Music/Time/Time.hs +0/−113
- src/Music/Time/Track.hs +155/−0
- src/Music/Time/Transform.hs +66/−0
- src/Music/Time/Types.hs +421/−0
- src/Music/Time/Voice.hs +460/−0
music-score.cabal view
@@ -1,6 +1,6 @@ name: music-score-version: 1.6.2+version: 1.7 author: Hans Hoglund maintainer: Hans Hoglund license: BSD3@@ -21,53 +21,68 @@ location: git://github.com/music-suite/music-score.git library - build-depends: base >= 4 && < 5,- lens >= 4.0 && < 4.1,+ build-depends: base >= 4 && < 5,+ lens >= 4.1.2 && < 4.2, process, data-default, containers, comonad,- NumInstances, profunctors, transformers,+ distributive,+ adjunctions,+ NumInstances, monadplus, void, semigroups >= 0.13.0.1 && < 1,- monoid-extras,+ -- monoid-extras,+ contravariant >= 0.4.4 && < 1, nats, semigroupoids,+ colour >= 2.3.3 && < 3.0, HCodecs >= 0.3 && < 0.4, vector-space, vector-space-points == 0.1.3,- musicxml2 == 1.6.2,- lilypond == 1.6.2,- music-pitch-literal == 1.6.2,- music-dynamics-literal == 1.6.2,+ musicxml2 == 1.7,+ lilypond == 1.7,+ music-pitch-literal == 1.7,+ music-dynamics-literal == 1.7, prettify, parsec- exposed-modules: Data.PairMonad+ exposed-modules: Data.Clipped+ Data.PairMonad+ Data.Functor.Couple+ Data.Functor.Context+ Data.Functor.Rep.Lens+ Data.Semigroup.Instances Control.Monad.Compose Music.Time- Music.Time.Delayable- Music.Time.Stretchable- Music.Time.Onset- Music.Time.Juxtapose+ Music.Time.Types+ Music.Time.Transform+ Music.Time.Duration+ Music.Time.Position+ Music.Time.Split Music.Time.Reverse- Music.Time.Time- Music.Time.Span- Music.Time.Reactive+ Music.Time.Juxtapose+ Music.Time.Rest+ Music.Time.Stretched+ Music.Time.Delayed+ Music.Time.Note+ Music.Time.Track+ Music.Time.Voice+ Music.Time.Chord+ Music.Time.Score+ -- Music.Time.Linear ?+ -- Music.Time.Spline ?+ Music.Time.Segment Music.Time.Behavior- Music.Score.Rhythm+ Music.Time.Reactive+ Music.Time.Bound+ Music.Time.Meta Music.Score- Music.Score.Note- Music.Score.Track- Music.Score.Voice- Music.Score.Score- Music.Score.Combinators- Music.Score.Convert Music.Score.Ties Music.Score.Part- Music.Score.Chord+ Music.Score.Phrases Music.Score.Meta Music.Score.Meta.Annotations Music.Score.Meta.Attribution@@ -83,17 +98,31 @@ Music.Score.Pitch Music.Score.Articulation Music.Score.Dynamics- Music.Score.Ornaments+ Music.Score.Slide+ Music.Score.Tremolo+ Music.Score.Text+ Music.Score.Harmonics+ Music.Score.Color Music.Score.Export.Abc+ Music.Score.Export.Backend+ Music.Score.Export.NoteList+ Music.Score.Export.Midi+ Music.Score.Export.SuperCollider Music.Score.Export.Lilypond Music.Score.Export.MusicXml- Music.Score.Export.Midi+ Music.Score.Export.ArticulationNotation+ Music.Score.Export.DynamicNotation Music.Score.Import.Abc Music.Score.Import.Lilypond Music.Score.Import.Midi- -- Need to expose these to allow GHCI development- Music.Score.Util- Music.Score.Export.Common- other-modules: Music.Score.Instances- hs-source-dirs: src+ Music.Score.Convert+ Music.Score.Instances+ -- We expose these to allow GHCI development+ Music.Time.Internal.Util+ Music.Time.Internal.Transform+ Music.Time.Internal.Quantize+ Music.Score.Internal.Util+ Music.Score.Internal.Export+ hs-source-dirs: src default-language: Haskell2010+ --ghc-options: -fwarn-unused-imports
+ src/Data/Clipped.hs view
@@ -0,0 +1,116 @@++{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++module Data.Clipped (+ -- * Data.Clipped+ Clipped,+ clipped,+ fromClipped,+ -- ** Unsafe operations+ unsafeToClipped,+ -- unclipped, + ) where++-----+import Data.Fixed+import Data.Default+import Data.Ratio++import Control.Applicative+import Control.Arrow (first, second, (***), (&&&))+import qualified Control.Category+import Control.Comonad+import Control.Comonad.Env+import Control.Lens hiding (Indexable, Level, above,+ below, index, inside, parts,+ reversed, transform, (|>), (<|))+import Control.Monad+import Control.Monad.Plus+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Distributive+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import Data.Functor.Rep+import qualified Data.List+import Data.List.NonEmpty (NonEmpty)+import Data.Maybe+import Data.NumInstances+import Data.Semigroup hiding ()+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.Traversable (Traversable)+import qualified Data.Traversable as T+import Data.Typeable+import Data.VectorSpace hiding (Sum(..))+import Music.Dynamics.Literal+import Music.Pitch.Literal++import qualified Data.Ratio as Util_Ratio+import qualified Data.List as List+import qualified Data.Foldable as Foldable+import qualified Data.Ord as Ord+-----++-- | A value in the unit interval /(0,1)/.+newtype Clipped a = UnsafeClip { unsafeGetClipped :: a }+ deriving (Eq, Ord, Show)++instance Num a => Bounded (Clipped a) where+ minBound = UnsafeClip 0+ maxBound = UnsafeClip 1++instance (Num a, Ord a) => Num (Clipped a) where+ a + b = unsafeToClipped (fromClipped a + fromClipped b)+ a - b = unsafeToClipped (fromClipped a - fromClipped b)+ a * b = unsafeToClipped (fromClipped a * fromClipped b)+ abs = id+ signum 0 = 0+ signum _ = 1+ negate = error "negate: No instance for Clipped"+ fromInteger = unsafeToClipped . fromInteger++instance (Num a, Ord a, Fractional a) => Fractional (Clipped a) where+ a / b = unsafeToClipped (fromClipped a / fromClipped b)+ recip 1 = 1+ recip _ = error "Can not take reciprocal of a clipped value other than 1"+ fromRational = unsafeToClipped . fromRational++unsafeToClipped = fromMaybe (error "Outside 0-1") . (^? clipped)+fromClipped = (^. unclipped)++clipped :: (Num a, Ord a) => Prism' a (Clipped a)+clipped = prism unsafeGetClipped $+ \x -> if 0 <= x && x <= 1+ then Right (UnsafeClip x)+ else Left x++unclipped :: (Num a, Ord a) => Getter (Clipped a) a+unclipped = re clipped++zipClippedWith+ :: (Num a, Ord a,+ Num b, Ord b,+ Num c, Ord c)+ => (a -> b -> c)+ -> Clipped a -> Clipped b -> Maybe (Clipped c)+zipClippedWith f a b = ((a^.unclipped) `f` (b^.unclipped))^? clipped++addLim = zipClippedWith (+)
+ src/Data/Functor/Context.hs view
@@ -0,0 +1,17 @@++module Data.Functor.Context (+ Ctxt(..),+ mapCtxt,+ extractCtxt,+ ) where++-- TODO use newtype and derivice Functor, Comonad etc+type Ctxt a = (Maybe a, a, Maybe a)+++mapCtxt :: (a -> b) -> Ctxt a -> Ctxt b+mapCtxt f (a,b,c) = (fmap f a, f b, fmap f c)++extractCtxt :: Ctxt a -> a+extractCtxt (_,x,_) = x+
+ src/Data/Functor/Couple.hs view
@@ -0,0 +1,92 @@+++{-# LANGUAGE CPP,+ GeneralizedNewtypeDeriving,+ DeriveDataTypeable,+ DeriveFunctor,+ DeriveTraversable,+ DeriveFoldable,+ MultiParamTypeClasses,+ TypeFamilies+ #-}+module Data.Functor.Couple where++import Data.Functor.Product+import Data.Functor.Identity+import Data.Foldable+import Data.Traversable+import Data.Functor.Adjunction (unzipR)+import Data.Semigroup+import Data.Typeable+import Control.Applicative+import Control.Comonad+import Data.PairMonad ()+import Control.Arrow (first)+import Control.Lens (Wrapped(..), Rewrapped(..), iso)++-- |+-- A variant of pair/writer with lifted instances for the numeric classes, using 'Applicative'.+--+newtype Couple b a = Couple { getCouple :: (b, a) }+ deriving (Eq, Show, Functor, Foldable, Typeable, Applicative, Monad, Comonad)++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Couple b a) where+ type Unwrapped (Couple b a) = (b, a)+ _Wrapped' = iso getCouple Couple++instance Rewrapped (Couple c a) (Couple c b)++instance (Monoid b, Num a) => Num (Couple b a) where+ (+) = liftA2 (+)+ (*) = liftA2 (*)+ (-) = liftA2 (-)+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger++instance (Monoid b, Fractional a) => Fractional (Couple b a) where+ recip = fmap recip+ fromRational = pure . fromRational++instance (Monoid b, Floating a) => Floating (Couple b a) where+ pi = pure pi+ sqrt = fmap sqrt+ exp = fmap exp+ log = fmap log+ sin = fmap sin+ cos = fmap cos+ asin = fmap asin+ atan = fmap atan+ acos = fmap acos+ sinh = fmap sinh+ cosh = fmap cosh+ asinh = fmap asinh+ atanh = fmap atanh+ acosh = fmap acos++instance (Monoid b, Enum a) => Enum (Couple b a) where+ toEnum = pure . toEnum+ fromEnum = fromEnum . extract++instance (Monoid b, Bounded a) => Bounded (Couple b a) where+ minBound = pure minBound+ maxBound = pure maxBound++-- The following are more suspect, as they require Ord++instance (Monoid b, Ord b, Ord a) => Ord (Couple b a) where+ Couple (a,b) < Couple (a',b') = (b,a) < (b',a')++instance (Monoid b, Ord b, Real a, Enum a, Integral a) => Integral (Couple b a) where+ quot = liftA2 quot+ rem = liftA2 rem+ quotRem = fmap (fmap unzipR) (liftA2 quotRem)+ toInteger = toInteger . extract ++instance (Monoid b, Ord b, Real a) => Real (Couple b a) where+ toRational = toRational . extract++instance (Monoid b, Ord b, RealFrac a) => RealFrac (Couple b a) where+ properFraction = first extract . unzipR . fmap properFraction+
+ src/Data/Functor/Rep/Lens.hs view
@@ -0,0 +1,30 @@++module Data.Functor.Rep.Lens where++import Control.Lens hiding (index)+import Data.Functor.Rep++-- $dataFunctorRepLens+-- Provides access to the definition in "Data.Functor.Rep" in terms of "Control.Lens".++-- |+-- Index a representable functor.+--+-- This is an infix alias for 'index'.+--+(!) :: Representable f => f a -> Rep f -> a+(!) = index++infixl 6 !++-- |+-- The isomorpism between a representable functor and its representation.+--+-- @+-- 'tabulated' = 'iso' 'tabulate' 'index'+-- @+--+tabulated :: (Representable f, Representable g) => Iso (Rep f -> a) (Rep g -> b) (f a) (g b)+tabulated = iso tabulate index++
+ src/Data/Semigroup/Instances.hs view
@@ -0,0 +1,78 @@++{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Data.Semigroup.Instances () where++import Data.Semigroup+import Data.VectorSpace hiding (Sum)+import Data.AffineSpace+import Control.Applicative+import Music.Pitch.Literal+import Music.Dynamics.Literal++-- TODO move these to semigroups and music-pitch-literal++deriving instance Num a => Num (Sum a)+deriving instance Real a => Real (Sum a)+deriving instance Fractional a => Fractional (Sum a)+deriving instance AdditiveGroup a => AdditiveGroup (Sum a)++instance VectorSpace a => VectorSpace (Sum a) where+ type Scalar (Sum a) = Scalar a+ s *^ Sum v = Sum (s *^ v)++instance AffineSpace a => AffineSpace (Sum a) where+ type Diff (Sum a) = Sum (Diff a)+ Sum p .-. Sum q = Sum (p .-. q)+ Sum p .+^ Sum v = Sum (p .+^ v)+++deriving instance Num a => Num (Product a)+deriving instance Real a => Real (Product a)+deriving instance Fractional a => Fractional (Product a)+deriving instance AdditiveGroup a => AdditiveGroup (Product a)++instance VectorSpace a => VectorSpace (Product a) where+ type Scalar (Product a) = Scalar a+ x *^ Product y = Product (x *^ y)++instance AffineSpace a => AffineSpace (Product a) where+ type Diff (Product a) = Product (Diff a)+ Product p .-. Product q = Product (p .-. q)+ Product p .+^ Product v = Product (p .+^ v)+++deriving instance IsDynamics a => IsDynamics (Sum a)+deriving instance IsDynamics a => IsDynamics (Product a)+deriving instance IsPitch a => IsPitch (Sum a)+deriving instance IsPitch a => IsPitch (Product a)++deriving instance Functor Product+instance Applicative Product where+ pure = Product+ Product f <*> Product x = Product (f x)++deriving instance Functor Sum+instance Applicative Sum where+ pure = Sum+ Sum f <*> Sum x = Sum (f x)+++{-+deriving instance Floating a => Floating (Product a)+instance Num a => Num (Product a) where+ fromInteger = pure . fromInteger+ abs = fmap abs+ signum = fmap signum+ (+) = liftA2 (+)+ (-) = liftA2 (-)+ (*) = liftA2 (*)+instance Fractional a => Fractional (Product a) where+ fromRational = pure . fromRational+ (/) = liftA2 (/)+instance Real a => Real (Product a) where+ toRational (Product x) = toRational x+-}
src/Music/Score.hs view
@@ -1,7 +1,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -15,6 +15,8 @@ module Music.Score ( -- * Prerequisites+ module Control.Lens,+ module Control.Applicative, module Control.Monad, module Control.Monad.Plus, module Data.Semigroup,@@ -22,23 +24,25 @@ module Data.AffineSpace, module Data.AffineSpace.Point, - -- * Basic types module Music.Time,-- -- * Musical container types- module Music.Score.Note,- module Music.Score.Track,- module Music.Score.Voice,- module Music.Score.Score,- module Music.Score.Convert,+ -- module Music.Score.Combinators, - -- * Manipulation- -- ** Combinators- module Music.Score.Combinators,+ -- * Music representation+ -- ** Musical elements+ module Music.Score.Part,+ module Music.Score.Pitch,+ module Music.Score.Dynamics,+ module Music.Score.Articulation,+ module Music.Score.Slide,+ module Music.Score.Tremolo,+ module Music.Score.Text,+ module Music.Score.Harmonics,+ module Music.Score.Color, - -- ** Structure+ -- ** Miscellaneous module Music.Score.Ties,- module Music.Score.Chord,+ module Music.Score.Phrases,+ module Music.Score.Clef, -- ** Meta-information module Music.Score.Meta,@@ -52,28 +56,27 @@ module Music.Score.Meta.Time, module Music.Score.Meta.Tempo, module Music.Score.Meta.Annotations,- module Music.Score.Clef, - -- ** Musical elements- module Music.Score.Part,- module Music.Score.Pitch,- module Music.Score.Dynamics,- module Music.Score.Articulation,- module Music.Score.Ornaments,- -- * Import and export module Music.Score.Import.Abc, module Music.Score.Import.Lilypond, module Music.Score.Import.Midi, - module Music.Score.Export.Abc,+ -- module Music.Score.Export.Abc,+ module Music.Score.Export.Backend,+ module Music.Score.Export.NoteList, module Music.Score.Export.Midi,+ module Music.Score.Export.SuperCollider, module Music.Score.Export.Lilypond, module Music.Score.Export.MusicXml, ) where import Control.Applicative+import Control.Lens hiding (Level, above, below,+ inside, parts, reversed,+ rewrite, simple, transform,+ (<.>), (<|), (|>)) import Control.Monad hiding (mapM) import Control.Monad.Plus import Data.AffineSpace@@ -88,22 +91,25 @@ import Data.Typeable import Data.VectorSpace hiding (Sum, getSum) -import Music.Time+import Music.Time hiding (time) import Music.Score.Articulation-import Music.Score.Chord import Music.Score.Clef-import Music.Score.Combinators-import Music.Score.Convert+import Music.Score.Color import Music.Score.Dynamics+import Music.Score.Export.Backend+import Music.Score.Export.NoteList+import Music.Score.Export.Midi+import Music.Score.Export.SuperCollider import Music.Score.Export.Abc import Music.Score.Export.Lilypond import Music.Score.Export.Midi import Music.Score.Export.MusicXml+import Music.Score.Harmonics import Music.Score.Import.Abc import Music.Score.Import.Lilypond import Music.Score.Import.Midi-import Music.Score.Instances ()+import Music.Score.Instances import Music.Score.Meta import Music.Score.Meta.Annotations import Music.Score.Meta.Attribution@@ -115,13 +121,10 @@ import Music.Score.Meta.Tempo import Music.Score.Meta.Time import Music.Score.Meta.Title-import Music.Score.Note-import Music.Score.Ornaments import Music.Score.Part+import Music.Score.Phrases import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score+import Music.Score.Slide+import Music.Score.Text import Music.Score.Ties-import Music.Score.Track-import Music.Score.Voice-+import Music.Score.Tremolo
src/Music/Score/Articulation.hs view
@@ -1,19 +1,25 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -25,13 +31,24 @@ -- ------------------------------------------------------------------------------------- - module Music.Score.Articulation (- -- * Representation++ -- ** Articulation type functions+ Articulation,+ SetArticulation,+ Accentuation,+ Separation,+ Articulated(..),++ -- ** Accessing articulation+ HasArticulations(..), HasArticulation(..),- ArticulationT(..),+ HasArticulations',+ HasArticulation',+ articulation',+ articulations', - -- * Transformations+ -- * Manipulating articulation -- ** Accents accent, marcato,@@ -40,133 +57,442 @@ accentAll, marcatoAll, - -- ** Phrasing- tenuto,- separated,+ -- ** Phrasing and separation+ staccatissimo, staccato,+ separated, portato, legato,- spiccato,+ legatissimo, - -- ** Miscellaneous- resetArticulation,+ tenuto,+ spiccato, + -- * Articulation transformer+ ArticulationT(..), ) where import Control.Applicative+import Control.Comonad+import Control.Lens hiding (above, below, transform)+import Data.AffineSpace import Data.Foldable+import Data.Functor.Couple import Data.Semigroup import Data.Typeable+import Data.VectorSpace hiding (Sum) +import Music.Score.Part+import Music.Time+import Music.Time.Internal.Transform+ import Music.Dynamics.Literal import Music.Pitch.Literal-import Music.Score.Combinators+import Music.Score.Harmonics import Music.Score.Part-import Music.Score.Score+import Music.Score.Phrases+import Music.Score.Slide+import Music.Score.Text+import Music.Score.Ties+import Music.Score.Tremolo -class HasArticulation a where- setBeginSlur :: Bool -> a -> a- setContSlur :: Bool -> a -> a- setEndSlur :: Bool -> a -> a- setAccLevel :: Int -> a -> a- setStaccLevel :: Int -> a -> a -newtype ArticulationT a = ArticulationT { getArticulationT :: (((Any, Any, Any), (Sum Int, Sum Int)), a) }- deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad) --- instance Monad ArticulationT where- -- return = undefined- -- return x = ArticulationT (Any False,Any False,0,0,x,False)- -- (>>=) = error "No ArticulationT.(>>=)" -instance Semigroup a => Semigroup (ArticulationT a) where- ArticulationT (((es,us,bs),(al,sl)),a) <> ArticulationT (_,b) = ArticulationT (((es,us,bs),(al,sl)), a <> b)+-- |+-- Articulations type.+--+type family Articulation (s :: *) :: * -instance (Semigroup a, Monoid a) => Monoid (ArticulationT a) where- mempty = return mempty- mappend = (<>)+-- |+-- Articulation type.+--+type family SetArticulation (b :: *) (s :: *) :: * -instance IsPitch a => IsPitch (ArticulationT a) where- fromPitch l = return (fromPitch l)+type ArticulationLensLaws' s t a b = (+ Articulation (SetArticulation a s) ~ a,+ SetArticulation (Articulation t) s ~ t,+ SetArticulation a (SetArticulation b s) ~ SetArticulation a s+ ) -instance IsDynamics a => IsDynamics (ArticulationT a) where- fromDynamics l = return (fromDynamics l)+type ArticulationLensLaws s t = ArticulationLensLaws' s t (Articulation s) (Articulation t) -instance HasArticulation (ArticulationT a) where- setEndSlur (Any -> es) (ArticulationT (((_ ,us,bs),(al,sl)),a)) = ArticulationT (((es,us,bs),(al,sl)),a)- setContSlur (Any -> us) (ArticulationT (((es,_ ,bs),(al,sl)),a)) = ArticulationT (((es,us,bs),(al,sl)),a)- setBeginSlur (Any -> bs) (ArticulationT (((es,us,_ ),(al,sl)),a)) = ArticulationT (((es,us,bs),(al,sl)),a)- setAccLevel (Sum -> al) (ArticulationT (((es,us,bs),(_ ,sl)),a)) = ArticulationT (((es,us,bs),(al,sl)),a)- setStaccLevel (Sum -> sl) (ArticulationT (((es,us,bs),(al,_ )),a)) = ArticulationT (((es,us,bs),(al,sl)),a)+-- |+-- Class of types that provide a single articulation.+--+class (HasArticulations s t) => HasArticulation s t where -instance HasArticulation b => HasArticulation (a,b) where- setEndSlur n = fmap (setEndSlur n)- setContSlur n = fmap (setContSlur n)- setBeginSlur n = fmap (setBeginSlur n)- setAccLevel n = fmap (setAccLevel n)- setStaccLevel n = fmap (setStaccLevel n)+ -- | Articulation type.+ articulation :: Lens s t (Articulation s) (Articulation t) ------------------------------------------------------------------------------------ Articulation---------------------------------------------------------------------------------+-- |+-- Class of types that provide a articulation traversal.+--+class (Transformable (Articulation s),+ Transformable (Articulation t),+ ArticulationLensLaws s t) => HasArticulations s t where --- Accents+ -- | Articulation type.+ articulations :: Traversal s t (Articulation s) (Articulation t) --- | Add a normal accent at the beginning of each phrase in each part in the given score.-accent :: (HasPart' a, HasArticulation a) => Score a -> Score a-accent = mapPhrase (setAccLevel 1) id id+type HasArticulation' a = HasArticulation a a+type HasArticulations' a = HasArticulations a a --- | Add a marcato accent at the beginning of each phrase in each part in the given score.-marcato :: (HasPart' a, HasArticulation a) => Score a -> Score a-marcato = mapPhrase (setAccLevel 2) id id+-- |+-- Articulation type.+--+articulation' :: (HasArticulation s t, s ~ t) => Lens' s (Articulation s)+articulation' = articulation --- | Add a normal accent to all notes in the given score.-accentAll :: (HasPart' a, HasArticulation a) => Score a -> Score a-accentAll = mapPhrase (setAccLevel 1) (setAccLevel 1) (setAccLevel 1)+-- |+-- Articulation type.+--+articulations' :: (HasArticulations s t, s ~ t) => Traversal' s (Articulation s)+articulations' = articulations --- | Add a marcato accent to all notes in the given score.-marcatoAll :: (HasPart' a, HasArticulation a) => Score a -> Score a-marcatoAll = mapPhrase (setAccLevel 2) (setAccLevel 2) (setAccLevel 2)+#define PRIM_ARTICULATION_INSTANCE(TYPE) \+ \+type instance Articulation TYPE = TYPE; \+type instance SetArticulation a TYPE = a; \+ \+instance (Transformable a, a ~ Articulation a, SetArticulation TYPE a ~ TYPE) \+ => HasArticulation TYPE a where { \+ articulation = ($) } ; \+ \+instance (Transformable a, a ~ Articulation a, SetArticulation TYPE a ~ TYPE) \+ => HasArticulations TYPE a where { \+ articulations = ($) } ; \ --- | Add a normal accent at the end of each phrase in each part in the given score.-accentLast :: (HasPart' a, HasArticulation a) => Score a -> Score a-accentLast = mapPhrase id id (setAccLevel 1)+PRIM_ARTICULATION_INSTANCE(())+PRIM_ARTICULATION_INSTANCE(Bool)+PRIM_ARTICULATION_INSTANCE(Ordering)+PRIM_ARTICULATION_INSTANCE(Char)+PRIM_ARTICULATION_INSTANCE(Int)+PRIM_ARTICULATION_INSTANCE(Integer)+PRIM_ARTICULATION_INSTANCE(Float)+PRIM_ARTICULATION_INSTANCE(Double) --- | Add a marcato accent at the end of each phrase in each part in the given score.-marcatoLast :: (HasPart' a, HasArticulation a) => Score a -> Score a-marcatoLast = mapPhrase id id (setAccLevel 2) +type instance Articulation (c,a) = Articulation a+type instance SetArticulation b (c,a) = (c,SetArticulation b a)+type instance Articulation [a] = Articulation a+type instance SetArticulation b [a] = [SetArticulation b a] --- Phrasing+type instance Articulation (Maybe a) = Articulation a+type instance SetArticulation b (Maybe a) = Maybe (SetArticulation b a)+type instance Articulation (Either c a) = Articulation a+type instance SetArticulation b (Either c a) = Either c (SetArticulation b a) --- | Add tenuto marks to each phrase in each part in the given score.-tenuto :: (HasPart' a, HasArticulation a) => Score a -> Score a-tenuto = mapPhrase (setStaccLevel (-2)) (setStaccLevel (-2)) (setStaccLevel (-2))+type instance Articulation (Note a) = Articulation a+type instance SetArticulation g (Note a) = Note (SetArticulation g a)+type instance Articulation (Delayed a) = Articulation a+type instance SetArticulation g (Delayed a) = Delayed (SetArticulation g a)+type instance Articulation (Stretched a) = Articulation a+type instance SetArticulation g (Stretched a) = Stretched (SetArticulation g a) --- | Add combined staccato and tenuto marks to each phrase in each part in the given score.-separated :: (HasPart' a, HasArticulation a) => Score a -> Score a-separated = mapPhrase (setStaccLevel (-1)) (setStaccLevel (-1)) (setStaccLevel (-1))+type instance Articulation (Voice a) = Articulation a+type instance SetArticulation b (Voice a) = Voice (SetArticulation b a)+type instance Articulation (Chord a) = Articulation a+type instance SetArticulation b (Chord a) = Chord (SetArticulation b a)+type instance Articulation (Track a) = Articulation a+type instance SetArticulation b (Track a) = Track (SetArticulation b a)+type instance Articulation (Score a) = Articulation a+type instance SetArticulation b (Score a) = Score (SetArticulation b a) --- | Add staccato marks to each phrase in each part in the given score.-staccato :: (HasPart' a, HasArticulation a) => Score a -> Score a-staccato = mapPhrase (setStaccLevel 1) (setStaccLevel 1) (setStaccLevel 1) --- | Add portato marks to each phrase in each part in the given score.-portato :: (HasPart' a, HasArticulation a) => Score a -> Score a-portato = staccato . legato+instance HasArticulation a b => HasArticulation (c, a) (c, b) where+ articulation = _2 . articulation --- | Add legato marks to each phrase in each part in the given score.-legato :: (HasPart' a, HasArticulation a) => Score a -> Score a-legato = mapPhrase (setBeginSlur True) id (setEndSlur True)+instance HasArticulations a b => HasArticulations (c, a) (c, b) where+ articulations = traverse . articulations --- | Add spiccatto marks to the given score.-spiccato :: (HasPart' a, HasArticulation a) => Score a -> Score a-spiccato = mapPhrase (setStaccLevel 2) (setStaccLevel 2) (setStaccLevel 2)+instance HasArticulations a b => HasArticulations [a] [b] where+ articulations = traverse . articulations --- | Remove all articulation from the given note or notes.-resetArticulation :: HasArticulation c => c -> c-resetArticulation = setBeginSlur False . setContSlur False . setEndSlur False . setAccLevel 0 . setStaccLevel 0+instance HasArticulations a b => HasArticulations (Maybe a) (Maybe b) where+ articulations = traverse . articulations --- Safe for tuple-like types-get1 = head . toList+instance HasArticulations a b => HasArticulations (Either c a) (Either c b) where+ articulations = traverse . articulations++++instance (HasArticulations a b) => HasArticulations (Note a) (Note b) where+ articulations = _Wrapped . whilstL articulations++instance (HasArticulation a b) => HasArticulation (Note a) (Note b) where+ articulation = _Wrapped . whilstL articulation++instance (HasArticulations a b) => HasArticulations (Delayed a) (Delayed b) where+ articulations = _Wrapped . whilstLT articulations++instance (HasArticulation a b) => HasArticulation (Delayed a) (Delayed b) where+ articulation = _Wrapped . whilstLT articulation++instance (HasArticulations a b) => HasArticulations (Stretched a) (Stretched b) where+ articulations = _Wrapped . whilstLD articulations++instance (HasArticulation a b) => HasArticulation (Stretched a) (Stretched b) where+ articulation = _Wrapped . whilstLD articulation+++instance HasArticulations a b => HasArticulations (Voice a) (Voice b) where+ articulations = traverse . articulations++instance HasArticulations a b => HasArticulations (Chord a) (Chord b) where+ articulations = traverse . articulations++instance HasArticulations a b => HasArticulations (Track a) (Track b) where+ articulations = traverse . articulations++instance HasArticulations a b => HasArticulations (Score a) (Score b) where+ articulations =+ _Wrapped . _2 -- into NScore+ . _Wrapped+ . traverse+ . _Wrapped -- this needed?+ . whilstL articulations++type instance Articulation (Couple c a) = Articulation a+type instance SetArticulation g (Couple c a) = Couple c (SetArticulation g a)+type instance Articulation (TremoloT a) = Articulation a+type instance SetArticulation g (TremoloT a) = TremoloT (SetArticulation g a)+type instance Articulation (TextT a) = Articulation a+type instance SetArticulation g (TextT a) = TextT (SetArticulation g a)+type instance Articulation (HarmonicT a) = Articulation a+type instance SetArticulation g (HarmonicT a) = HarmonicT (SetArticulation g a)+type instance Articulation (TieT a) = Articulation a+type instance SetArticulation g (TieT a) = TieT (SetArticulation g a)+type instance Articulation (SlideT a) = Articulation a+type instance SetArticulation g (SlideT a) = SlideT (SetArticulation g a)++instance (HasArticulations a b) => HasArticulations (Couple c a) (Couple c b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (Couple c a) (Couple c b) where+ articulation = _Wrapped . articulation++instance (HasArticulations a b) => HasArticulations (TremoloT a) (TremoloT b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (TremoloT a) (TremoloT b) where+ articulation = _Wrapped . articulation++instance (HasArticulations a b) => HasArticulations (TextT a) (TextT b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (TextT a) (TextT b) where+ articulation = _Wrapped . articulation++instance (HasArticulations a b) => HasArticulations (HarmonicT a) (HarmonicT b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (HarmonicT a) (HarmonicT b) where+ articulation = _Wrapped . articulation++instance (HasArticulations a b) => HasArticulations (TieT a) (TieT b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (TieT a) (TieT b) where+ articulation = _Wrapped . articulation++instance (HasArticulations a b) => HasArticulations (SlideT a) (SlideT b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (SlideT a) (SlideT b) where+ articulation = _Wrapped . articulation+++type family Accentuation (a :: *) :: *+type family Separation (a :: *) :: *++type instance Accentuation () = ()+type instance Separation () = ()+type instance Accentuation (a, b) = a+type instance Separation (a, b) = b+++-- |+-- Class of types that can be transposed, inverted and so on.+--+class (+ Fractional (Accentuation a),+ Fractional (Separation a),+ AffineSpace (Accentuation a),+ AffineSpace (Separation a)+ ) => Articulated a where+ accentuation :: Lens' a (Accentuation a)+ separation :: Lens' a (Separation a)+++-- TODO move+instance Num () where+ _ + _ = ()+ _ - _ = ()+ _ * _ = ()+ signum _ = ()+ abs _ = ()+ fromInteger _ = ()++instance Fractional () where+ _ / _ = ()+ fromRational _ = ()++instance VectorSpace () where+ type Scalar () = ()+ _ *^ _ = ()++instance AffineSpace () where+ type Diff () = ()+ _ .-. _ = ()+ _ .+^ _ = ()++instance Articulated () where+ accentuation = id+ separation = id++instance (AffineSpace a, AffineSpace b, Fractional a, Fractional b) => Articulated (a, b) where+ accentuation = _1'+ separation = _2'++_1' :: Lens' (a, b) a+_1' = _1++_2' :: Lens' (a, b) b+_2' = _2+ +++accent :: (HasPhrases' s b, HasArticulations' b, Articulation b ~ a, Articulated a) => s -> s+accent = set (phrases . headV . articulations . accentuation) 1++marcato :: (HasPhrases' s b, HasArticulations' b, Articulation b ~ a, Articulated a) => s -> s+marcato = set (phrases . headV . articulations . accentuation) 2++accentLast :: (HasPhrases' s b, HasArticulations' b, Articulation b ~ a, Articulated a) => s -> s+accentLast = set (phrases . lastV . articulations . accentuation) 1++marcatoLast :: (HasPhrases' s b, HasArticulations' b, Articulation b ~ a, Articulated a) => s -> s+marcatoLast = set (phrases . lastV . articulations . accentuation) 2++accentAll :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+accentAll = set (articulations . accentuation) 1++marcatoAll :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+marcatoAll = set (articulations . accentuation) 2++++tenuto :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+tenuto = id++spiccato :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+spiccato = id++legatissimo :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+legatissimo = set (articulations . separation) (-2)++legato :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+legato = set (articulations . separation) (-1)++separated :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+separated = set (articulations . separation) 0++portato :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+portato = set (articulations . separation) 0.5++staccato :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+staccato = set (articulations . separation) 1++staccatissimo :: (HasArticulations' s, Articulation s ~ a, Articulated a) => s -> s+staccatissimo = set (articulations . separation) 2+++++newtype ArticulationT n a = ArticulationT { getArticulationT :: (n, a) }+ deriving (+ Eq, Ord, Show, Typeable, Functor, Applicative, Monad, + Comonad, Transformable, Monoid, Semigroup+ )++instance (Monoid n, Num a) => Num (ArticulationT n a) where+ (+) = liftA2 (+)+ (*) = liftA2 (*)+ (-) = liftA2 (-)+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger++instance (Monoid n, Fractional a) => Fractional (ArticulationT n a) where+ recip = fmap recip+ fromRational = pure . fromRational++instance (Monoid n, Floating a) => Floating (ArticulationT n a) where+ pi = pure pi+ sqrt = fmap sqrt+ exp = fmap exp+ log = fmap log+ sin = fmap sin+ cos = fmap cos+ asin = fmap asin+ atan = fmap atan+ acos = fmap acos+ sinh = fmap sinh+ cosh = fmap cosh+ asinh = fmap asinh+ atanh = fmap atanh+ acosh = fmap acos++instance (Monoid n, Enum a) => Enum (ArticulationT n a) where+ toEnum = pure . toEnum+ fromEnum = fromEnum . extract++instance (Monoid n, Bounded a) => Bounded (ArticulationT n a) where+ minBound = pure minBound+ maxBound = pure maxBound++-- instance (Monoid n, Num a, Ord a, Real a) => Real (ArticulationT n a) where+-- toRational = toRational . extract+--+-- instance (Monoid n, Real a, Enum a, Integral a) => Integral (ArticulationT n a) where+-- quot = liftA2 quot+-- rem = liftA2 rem+-- toInteger = toInteger . extract++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (ArticulationT p a) where+ type Unwrapped (ArticulationT p a) = (p, a)+ _Wrapped' = iso getArticulationT ArticulationT++instance Rewrapped (ArticulationT p a) (ArticulationT p' b)++type instance Articulation (ArticulationT p a) = p+type instance SetArticulation p' (ArticulationT p a) = ArticulationT p' a++instance (Transformable p, Transformable p') + => HasArticulation (ArticulationT p a) (ArticulationT p' a) where+ articulation = _Wrapped . _1++instance (Transformable p, Transformable p') + => HasArticulations (ArticulationT p a) (ArticulationT p' a) where+ articulations = _Wrapped . _1++deriving instance (IsPitch a, Monoid n) => IsPitch (ArticulationT n a)+deriving instance (IsInterval a, Monoid n) => IsInterval (ArticulationT n a)+deriving instance Reversible a => Reversible (ArticulationT p a)++instance (Tiable n, Tiable a) => Tiable (ArticulationT n a) where+ toTied (ArticulationT (d,a)) = (ArticulationT (d1,a1), ArticulationT (d2,a2))+ where+ (a1,a2) = toTied a+ (d1,d2) = toTied d+++headV :: Traversal' (Voice a) a+headV = (eventsV._head._2)++middleV :: Traversal' (Voice a) a+middleV = (eventsV._middle.traverse._2)++lastV :: Traversal' (Voice a) a+lastV = (eventsV._last._2)++-- Traverse writing to all elements *except* first and last+_middle :: (Snoc s s a a, Cons s s b b) => Traversal' s s+_middle = _tail._init
− src/Music/Score/Chord.hs
@@ -1,240 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE NoMonomorphismRestriction #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides a representation for chords.--------------------------------------------------------------------------------------------module Music.Score.Chord (- -- * Chord representation- HasChord(..),- ChordT(..),-- -- * Voice separation- separateVoices,- mergePossible,-- -- * Chord transformations- takeNoteInChord,- dropNoteInChord,- takeNotesInChord,- dropNotesInChord,- mapSimultaneous,- simultaneous,- simultaneous',- ) where--import Prelude hiding (any, mapM_)--import Control.Lens hiding (perform)-import Control.Monad.Plus hiding (mapM_)-import Data.Foldable-import qualified Data.List as List-import qualified Data.List.NonEmpty as NonEmpty-import Data.Ord-import Data.Semigroup-import Data.Typeable--import Music.Score.Combinators-import Music.Score.Convert-import Music.Score.Note-import Music.Score.Part-import Music.Score.Score-import Music.Score.Voice-import Music.Score.Meta-import Music.Time--class HasChord a where- type ChordNote a :: *- -- TODO use NonEmpty- getChord :: a -> [ChordNote a]--instance HasChord [a] where- type ChordNote [a] = a- getChord = id--instance HasChord (ChordT a) where- type ChordNote (ChordT a) = a- getChord (ChordT as) = as---- TODO Use NonEmpty-newtype ChordT a = ChordT { getChordT :: [a] }- deriving (Eq, Show, Ord, Monad, Functor, Monoid, Semigroup, Foldable, Typeable)--overlaps :: (HasOnset a, HasOffset a, HasOnset b, HasOffset b) => a -> b -> Bool-overlaps t u = not $ offset t <= onset u || offset u <= onset t--overlapsAny :: (Foldable t, HasOnset a, HasOffset a, HasOnset b, HasOffset b) => a -> t b -> Bool-overlapsAny x = any (overlaps x)--notOverlaps :: (HasOnset a, HasOnset b, HasOffset a, HasOffset b) => a -> b -> Bool-x `notOverlaps` y = not (x `overlaps` y)--hasOverlapping :: Score a -> Bool-hasOverlapping x = let ns = (^. notes) x in not $ null [(x,y) | x <- ns, y <- ns, x `overlaps` y, era x /= era y]---- | Heuristically merge voices if possible-mergePossible :: [Score a] -> [Score a]-mergePossible [] = []-mergePossible (x:xs) = let- pick = x- (res, rest) = List.foldr mergeMaybe' (x, []) xs- in res : mergePossible rest--mergeMaybe' x (y,rest) = if hasOverlapping (x <> y) then (x, y:rest) else (x <> y, rest)--mergeMaybe x y = if hasOverlapping (x <> y) then (x, Just y) else (x <> y, Nothing)----notOverlapsHead :: (HasOnset a, HasOnset b, HasOffset a, HasOffset b) => a -> [b] -> Bool-x `notOverlapsHead` [] = True-x `notOverlapsHead` xs = x `notOverlaps` head xs--class Null a where- isNull :: a -> Bool--- > isNull mempty-instance Null [a] where- isNull = null-nonNull = not . isNull-class Divisible a where- divide :: a -> (a, a)----data Tower a = Tower [a] a [a]- deriving (Functor, Eq, Show)--tower x = Tower (repeat mempty) x (repeat mempty)-moveUp (Tower (a:as) x (b:bs)) = Tower (x:a:as) b bs-moveDown (Tower (a:as) x (b:bs)) = Tower as a (x:b:bs)--top :: (Monoid a, Null a) => Tower a -> [a]-top (Tower as x sa) = List.takeWhile nonNull sa--middle :: Tower a -> a-middle (Tower as x sa) = x--bottom :: (Monoid a, Null a) => Tower a -> [a]-bottom (Tower as x sa) = reverse (List.takeWhile nonNull as)---- semantic function-floors :: (Monoid a, Null a) => Tower a -> ([a], a, [a])-floors t = (bottom t, middle t, top t)---compareHead x [] = EQ-compareHead x (y:_) = x `compare` y-comparingHead p x y = compareHead (p x) (fmap p y)-compareHeadVal = comparingHead getNoteValue--pushNote :: Ord a => Note a -> Tower [Note a] -> Tower [Note a]-pushNote n t = if n `notOverlapsHead` middle t then pushMiddle n t else- case n `compareHeadVal` middle t of- GT -> moveUp $ pushNote n (moveDown t)- _ -> moveDown $ pushNote n (moveUp t)--pushMiddle :: a -> Tower [a] -> Tower [a]-pushMiddle x (Tower as a sa) = Tower as (x:a) sa--separateVoices :: Ord a => Score a -> [Score ( a)]-separateVoices = fmap (^. from notes) . f . (^. notes)- where- f = (\(as,x,bs) -> as++[x]++bs) . floors . List.foldr pushNote (tower []) . List.sortBy (comparing getNoteSpan)---- Note:------ The HasChord instance (for other transformer types) takes care to transform strucuture *above* the chord representation--- In particular, getChord will extract the chord from below and transform each note (or only the first etc)--- as appropriate for the given type.--- The ChordT instances (of other transformer classes) transforms structure *below* the chord representation--- For example, it allow us to use functions such as up, down, legato etc on chords.---- TODO rewrite, generalize?--takeNotesInChord n = mapSimultaneous (fmap $ take n)-dropNotesInChord n = mapSimultaneous (fmap $ drop n)--takeNoteInChord n = mapSimultaneous $ (fmap $ take 1) . (fmap $ drop (n - 1))-dropNoteInChord n = mapSimultaneous $ (fmap $ drop1 n)--drop1 n xs = take (n - 1) xs <> drop n xs----- |--- Process all simultaneous events.------ Two events /a/ and /b/ are considered simultaneous if and only if they have the same--- era, that is if @`era` a == `era` b@----mapSimultaneous :: (Score [a] -> Score [b]) -> Score a -> Score b-mapSimultaneous f = mscatter . f . simultaneous'---- |--- Merge all simultaneous events using their 'Semigroup' instance.------ Two events /a/ and /b/ are considered simultaneous if and only if they have the same--- era, that is if @`era` a == `era` b@----simultaneous :: Semigroup a => Score a -> Score a-simultaneous = fmap (sconcat . NonEmpty.fromList) . simultaneous'---- |--- Group simultaneous events as lists.------ Two events /a/ and /b/ are considered simultaneous if and only if they have the same--- era, that is if @`era` a == `era` b@------ Note that 'simultaneous' is identical to 'simultaneous' @.@ 'fmap' 'return'----simultaneous' :: Score a -> Score [a]-simultaneous' sc = (meta .~) m $ (^. from events) vs- where- m = (view meta) sc- -- es :: [Era]- -- evs :: [[a]]- -- vs :: [(Time, Duration, [a])]- es = List.nub $ eras sc- evs = fmap (`chordEvents` sc) es- vs = zipWith (\(view delta -> (t,d)) a -> (t,d,a)) es evs----- TODO (re)move these--eras :: Score a -> [Span]-eras sc = fmap getSpan . (^. events) $ sc--chordEvents :: Span -> Score a -> [a]-chordEvents era sc = fmap getValue . filter (\ev -> getSpan ev == era) . (^. events) $ sc--getValue :: (Time, Duration, a) -> a-getValue (t,d,a) = a--getSpan :: (Time, Duration, a) -> Span-getSpan (t,d,a) = t >-> d-----
src/Music/Score/Clef.hs view
@@ -1,4 +1,5 @@ +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-}@@ -16,7 +17,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -26,6 +27,8 @@ -- -- Provides clefs. --+-- /Warning/ Experimental module.+-- ------------------------------------------------------------------------------------- @@ -35,6 +38,7 @@ ) where import Control.Arrow+import Control.Lens hiding (transform, parts) import Control.Monad.Plus import Data.Foldable (Foldable) import qualified Data.Foldable as F@@ -51,45 +55,58 @@ import Data.Typeable import Data.Void -import Music.Score.Combinators (mapFirst)-import Music.Score.Meta import Music.Score.Meta.Clef-import Music.Score.Note-import Music.Score.Ornaments (HasText, text) import Music.Score.Part-import Music.Score.Score import Music.Score.Ties-import Music.Score.Util+import Music.Score.Internal.Util import Music.Time-import Music.Time.Reactive -- Put the given clef in front of the note newtype ClefT a = ClefT { getClefT :: (Option (Last Clef), a) }- deriving (Functor, Semigroup, Monoid)+ deriving (Functor, Semigroup, Monoid) -type instance Part (ClefT a) = Part a-instance HasPart a => HasPart (ClefT a) where- getPart (ClefT (_,a)) = getPart a- modifyPart f (ClefT (a,b)) = ClefT (a, modifyPart f b) +-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (ClefT a) where+ type Unwrapped (ClefT a) = (Option (Last Clef), a)+ _Wrapped' = iso getClefT ClefT++instance Rewrapped (ClefT a) (ClefT b)++ instance Monad ClefT where- return x = ClefT (mempty, x)- (>>=) = error "No ClefT.(>>=)"+ return x = ClefT (mempty, x)+ (>>=) = error "No ClefT.(>>=)" +type instance Part (ClefT a) = Part a+type instance SetPart b (ClefT a) = ClefT (SetPart b a)+++instance (HasParts a b) => HasParts (ClefT a) (ClefT b) where+ parts = _Wrapped . parts++instance (HasPart a b) => HasPart (ClefT a) (ClefT b) where+ part = _Wrapped . part+++instance Transformable a => Transformable (ClefT a) where+ transform s = over (_Wrapped . _2) $ transform s+ instance Tiable a => Tiable (ClefT a) where- toTied (ClefT (clef,a)) = (ClefT (clef,b), ClefT (mempty,c)) where (b,c) = toTied a+ toTied (ClefT (clef,a)) = (ClefT (clef,b), ClefT (mempty,c)) where (b,c) = toTied a class HasClef a where- applyClef :: Clef -> a -> a+ applyClef :: Clef -> a -> a instance HasClef (ClefT a) where- applyClef c (ClefT (_,a)) = ClefT (Option $ Just $ Last c,a)+ applyClef c (ClefT (_,a)) = ClefT (Option $ Just $ Last c,a) instance HasClef a => HasClef (b,a) where- applyClef c = fmap (applyClef c)+ applyClef c = fmap (applyClef c) instance (HasPart' a, HasClef a) => HasClef (Score a) where- applyClef c = mapFirst (applyClef c) id+ applyClef c = id -- TODO+ -- applyClef c = mapFirst (applyClef c) id
+ src/Music/Score/Color.hs view
@@ -0,0 +1,125 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides colored note heads.+--+-------------------------------------------------------------------------------------++module Music.Score.Color (+ -- ** HasColor class+ HasColor(..),++ -- * Manipulating color+ color,++ -- * Representation+ ColorT(..),+ ) where++import Control.Applicative+import Control.Lens hiding (above, below, transform)+import Data.AffineSpace+import Data.Colour+import qualified Data.Colour.Names as C+import Data.Foldable+import Data.Functor.Couple+import Data.Semigroup+import Data.Typeable+import Control.Comonad++import Music.Score.Part+import Music.Time+import Music.Time.Internal.Transform++import Music.Dynamics.Literal+import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal+import Music.Score.Harmonics+import Music.Score.Part+import Music.Score.Phrases+import Music.Score.Slide+import Music.Score.Text+import Music.Score.Ties+import Music.Score.Tremolo++class HasColor a where+ setColor :: Colour Double -> a -> a++instance HasColor a => HasColor (b, a) where+ setColor s = fmap (setColor s)+instance HasColor a => HasColor [a] where+ setColor s = fmap (setColor s)+instance HasColor a => HasColor (Score a) where+ setColor s = fmap (setColor s)+instance HasColor a => HasColor (PartT n a) where+ setColor s = fmap (setColor s)+instance HasColor a => HasColor (TieT a) where+ setColor s = fmap (setColor s)++newtype ColorT a = ColorT { getColorT :: Couple (Option (Last (Colour Double))) a }+ deriving (Eq, {-Ord,-} Show, Functor, Foldable, {-Typeable,-} Applicative, Monad, Comonad)++-- Lifted instances+deriving instance Num a => Num (ColorT a)+deriving instance Fractional a => Fractional (ColorT a)+deriving instance Floating a => Floating (ColorT a)+deriving instance Enum a => Enum (ColorT a)+deriving instance Bounded a => Bounded (ColorT a)+-- deriving instance (Num a, Ord a, Real a) => Real (ColorT a)+-- deriving instance (Real a, Enum a, Integral a) => Integral (ColorT a)++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (ColorT a) where+ type Unwrapped (ColorT a) = Couple (Option (Last (Colour Double))) a+ _Wrapped' = iso getColorT ColorT++instance Rewrapped (ColorT a) (ColorT b)+++instance HasColor (ColorT a) where+ setColor s (ColorT (Couple (t,x))) = ColorT $ Couple (t <> wrap s,x)+ where+ wrap = Option . Just . Last++instance Semigroup a => Semigroup (ColorT a) where+ (<>) = liftA2 (<>)++instance Tiable a => Tiable (ColorT a) where+ toTied (ColorT (Couple (n,a))) = (ColorT $ Couple (n,b), ColorT $ Couple (n,c)) + where + (b,c) = toTied a+++-- |+-- Set the number of tremolo divisions for all notes in the score.+--+color :: HasColor a => Colour Double -> a -> a+color = setColor+
− src/Music/Score/Combinators.hs
@@ -1,457 +0,0 @@--{-# 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------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Combinators for manipulating scores and related structures.-------------------------------------------------------------------------------------------module Music.Score.Combinators (- -- * Basic- note,- rest,- noteRest,- removeRests,-- -- * Mapping over events- mapEvents,-- -- * Filtering events-- -- ** Editing- filterEvents,- mapFilterEvents,-- -- * Editing- before,- after,- split,- slice,- splice,-- -- * Meta-events- metaAt,- metaAtStart,- withMeta,- withGlobalMeta,- withMetaAtStart,- withGlobalMetaAtStart,----- -- ** Map over phrases- mapFirst,- mapLast,- mapPhrase,- mapPhraseSingle,-- -- * Parts- -- ** Extracting parts- filterPart,- extractParts,- extractParts',-- -- ** Map over parts- -- mapPart,- mapParts,- mapAllParts,- -- modifyParts,-- -- ** Part composition- (</>),- rcat,- -- moveParts,- -- moveToPart,-- -- * Zippers- -- apply,- -- snapshot,- -- snapshotWith,-- -- ** Single-part versions- applySingle,- -- snapshotSingle,- -- snapshotWithSingle,- ) where--import Control.Applicative-import Control.Arrow-import Control.Lens hiding (perform)-import Control.Monad-import Control.Monad.Plus-import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Foldable (Foldable (..))-import Data.Ord-import Data.Ratio-import Data.Semigroup-import Data.String-import Data.Traversable-import Data.VectorSpace--import Music.Score.Convert-import Music.Score.Meta-import Music.Score.Note-import Music.Score.Part-import Music.Score.Score-import Music.Score.Track-import Music.Score.Util-import Music.Score.Voice-import Music.Time-import Music.Time.Reactive--import qualified Data.Foldable as Foldable-import qualified Data.List as List---- | Create a score containing a note at time zero and duration one. This is an alias for 'return'.-note :: Monad m => a -> m a-note = return---- | Create a score containing a rest at time zero and duration one. This is an alias for @'return' 'Nothing'@.-rest :: MonadPlus m => m (Maybe a)-rest = return Nothing---- | Create a note or a rest at time zero and duration one. This is an alias for 'mfromMaybe'.-noteRest :: MonadPlus m => Maybe a -> m a-noteRest = mfromMaybe---- | Remove all rests from a score. This is an alias for 'mcatMaybes'.-removeRests :: MonadPlus m => m (Maybe a) -> m a-removeRests = mcatMaybes---- | Retain only the notes whose /offset/ does not fall after the given time.-before :: Time -> Score a -> Score a-before u = filterEvents (\t d _ -> t .+^ d <= u)---- | Retain only the notes whose /onset/ does not fall before the given time.-after :: Time -> Score a -> Score a-after u = filterEvents (\t d _ -> u <= t)---- | Returns notes whose /onset/ and /offset/ fall between the given times.-slice :: Time -> Time -> Score a -> Score a-slice u v = filterEvents (\t d _ -> u <= t && t .+^ d <= v)---- | Split a score into events whose onsets-split :: Time -> Score a -> (Score a, Score a)-split t a = (before t a, after t a)---- | Split a score into three parts-splice :: Time -> Duration -> Score a -> (Score a, Score a, Score a)-splice t d a = tripr (before t a, split (t .+^ d) a)-------- |--- 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, it is returned unchanged.----mapFirst :: HasPart' a => (a -> b) -> (a -> b) -> Score a -> Score 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, it is returned unchanged.----mapLast :: HasPart' a => (a -> b) -> (a -> b) -> Score a -> Score 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.----mapPhrase :: HasPart' a => (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b-mapPhrase f g h = mapAllParts (fmap $ mapPhraseSingle f g h)---- |--- Equivalent to 'mapPhrase' for single-part scores.------ Fails if the score contains overlapping events.------ > (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b----mapPhraseSingle :: (a -> b) -> (a -> b) -> (a -> b) -> Score a -> Score b-mapPhraseSingle f g h = mapAll (mapFTL (_3 %~ f) (_3 %~ g) (_3 %~ h))------ |--- Map over all events in a score.----mapAll :: ([(Time, Duration, a)] -> [(Time, Duration, b)]) -> Score a -> Score b-mapAll f = saveMeta $ over events f- where- saveMeta f x = (meta .~) ((view meta) x) $ f x--------------------------------------------------------------------------------------- Parts------------------------------------------------------------------------------------- |--- Filter a score to include only those events whose parts match a given predicate.----filterPart :: HasPart' a => (Part a -> Bool) -> Score a -> Score a-filterPart p = mfilter (p . getPart)--filterPartIs :: HasPart' a => Part a -> Score a -> Score a-filterPartIs = filterPart <$> (==)---- |--- Extract parts from the a score.------ The parts are returned in the order defined the associated 'Ord' instance part type.----extractParts :: HasPart' a => Score a -> [Score a]-extractParts x = filterPartIs <$> getParts x <*> return x---- |--- Extract parts from the a score and include the part name.------ The parts are returned in the order defined the associated 'Ord' instance part type.----extractParts' :: HasPart' a => Score a -> [(Part a, Score a)]-extractParts' x = getParts x `zip` extractParts x----- |--- Map over a specific part in the given score.----mapPart :: (Enum (Part a), HasPart' a) => Part a -> (Score a -> Score a) -> Score a -> Score a---- |--- Map over all parts in the given score.------ > (Score a -> Score a) -> Score a -> Score a----mapParts :: HasPart' a => (Score a -> Score b) -> Score a -> Score b---- |--- Map over all parts in the given score.------ > ([Score a] -> [Score a]) -> Score a -> Score a----mapAllParts :: HasPart' a => ([Score a] -> [Score b]) -> Score a -> Score b--{-# DEPRECATED mapParts "" #-}-{-# DEPRECATED mapAllParts "" #-}-{-# DEPRECATED filterPart "" #-}-{-# DEPRECATED extractParts "" #-}-{-# DEPRECATED extractParts' "" #-}---mapPart n f = mapAllParts (zipWith ($) (replicate (fromEnum n) id ++ [f] ++ repeat id))-mapParts f = mapAllParts (fmap f)-mapAllParts f = mconcat . f . extractParts---- |--- Modify all parts in the given score.------ > (Part -> Part) -> Score a -> Score a----modifyParts :: HasPart' a => (Part a -> Part a) -> Score a -> Score a-modifyParts n = fmap (modifyPart n)--------------------------------------------------------------------------------------- Part composition-----------------------------------------------------------------------------------infixr 6 </>---- |--- Similar to '<>', but increases parts in the second part to prevent collision.----(</>) :: (HasPart' 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 $ getParts a---- |--- Concatenate parts.----rcat :: (HasPart' a, Enum (Part a)) => [Score a] -> Score a-rcat = List.foldr (</>) mempty---- |--- Move down one voice (all parts).----moveParts :: (Integral b, HasPart' a, Enum (Part a)) => b -> Score a -> Score a-moveParts x = modifyParts (successor x)---- |--- Move top-part to the specific voice (other parts follow).----moveToPart :: (Enum b, HasPart' a, Enum (Part a)) => b -> Score a -> Score a-moveToPart v = moveParts (fromEnum v)-------------------------------------------------------------------------------------------- Zippers---- |--- Apply a time-varying function to all events in score.----apply :: HasPart' a => Voice (Score a -> Score b) -> Score a -> Score b-apply x = mapAllParts (fmap $ applySingle x)---- |--- Apply a time-varying function to all events in score.----applySingle :: Voice (Score a -> Score b) -> Score a -> Score b-applySingle fs = notJoin . fmap (uncurry ($)) . sample fs- where- notJoin = mconcat . Foldable.toList- sample fs = snapshotSingle (voiceToScore fs)-{-# DEPRECATED applySingle "" #-}---- |--- Get all notes that start during a given note.----snapshot :: HasPart' b => Score a -> Score b -> Score (a, Score b)-snapshot x = mapAllParts (fmap $ snapshotSingle x)--snapshotWith :: HasPart' b => (a -> Score b -> c) -> Score a -> Score b -> Score c-snapshotWith f x = mapAllParts (fmap $ snapshotWithSingle f x)---- |--- Get all notes that start during a given note.----snapshotSingle :: Score a -> Score b -> Score (a, Score b)-snapshotSingle = snapshotWithSingle (,)--snapshotWithSingle :: (a -> Score b -> c) -> Score a -> Score b -> Score c-snapshotWithSingle g as bs = mapEvents ( \t d a -> g a (onsetIn t d bs) ) as---- |--- Filter out events that has its onset in the given time interval (inclusive start).--- For example, onset in 1 2 filters events such that (1 <= onset x < 3)----onsetIn :: Time -> Duration -> Score a -> Score a-onsetIn a b = mapAll $ filterOnce (\(t,d,x) -> a <= t && t < a .+^ b)--- We could also have used mfilter. filterOnce is more lazy,--- but depends on the events being sorted--------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 = runMeta (Nothing :: Maybe a) . (view meta)--metaAt :: (HasPart' a, IsAttribute b) => Time -> Score a -> b-metaAt x = (? 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 = runMeta 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 (\(getNote -> (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' part f x = let- m = (view meta) x- in f (runMeta part m ? onset x) x------------------------------------------------------------------------------------------------ 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 (^. _3) . partial (uncurry3 f))--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)--minimum' :: (Ord a, Foldable t) => a -> t a -> a-minimum' z = option z getMin . foldMap (Option . Just . Min)-
src/Music/Score/Convert.hs view
@@ -12,7 +12,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -20,31 +20,21 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) ----- Provides conversion betwen temporal container types.+-- Provides conversion betwen temporal container. --+-- /Warning/ This module will be removed soon.+-- ------------------------------------------------------------------------------------- - module Music.Score.Convert (- -- * Conversion- noteToVoice,- noteToScore,- -- scoreToNotes,- -- notesToScore, voiceToScore,- voicesToScore,- trackToScore,- trackToScore',+ voiceToScore', scoreToVoice,- reactiveToVoice, reactiveToVoice',- noteToReactive,- splitReactive,- activate, ) where import Control.Applicative-import Control.Lens+import Control.Lens hiding (transform, time) import Control.Monad import Control.Monad.Plus import Data.AffineSpace@@ -57,11 +47,7 @@ import Data.Traversable import Data.VectorSpace -import Music.Score.Note import Music.Score.Part-import Music.Score.Score-import Music.Score.Track-import Music.Score.Voice import Music.Time import Music.Time.Reactive @@ -69,13 +55,17 @@ import qualified Data.List as List --- | Convert a note to an onset and a voice.+{-+-- | Convert a note to an _onset and a voice. noteToVoice :: Note a -> (Time, Voice a)-noteToVoice (getNote -> (s,x)) = (onset s, stretchTo (duration s) $ return x)+noteToVoice (view (from note) -> (s,x)) = (_onset s, stretchTo (_duration s) $ return x)+-} +{- -- | Convert a note to a score. noteToScore :: Note a -> Score a-noteToScore (getNote -> (s,x)) = s `sapp` return x+noteToScore (view (from note) -> (s,x)) = s `transform` return x+-} -- scoreToNotes :: Score a -> [Note a] -- scoreToNotes = Foldable.toList . reifyScore@@ -83,67 +73,74 @@ -- notesToScore :: [Note a] -> Score a -- notesToScore = pcat . fmap noteToScore +{- reactiveToVoice :: Duration -> Reactive a -> Voice a-reactiveToVoice d r = (^. voice) $ durs `zip` (fmap (r ?) times)+reactiveToVoice d r = (^. voice) $ fmap (^. stretched) $ durs `zip` (fmap (r `atTime`) times) where- times = origin : filter (\t -> origin < t && t < origin .+^ d) (occs r)- durs = toRelN' (origin .+^ d) times+ times = 0 : filter (\t -> 0 < t && t < 0 .+^ d) (occs r)+ durs = toRelN' (0 .+^ d) times+-} reactiveToVoice' :: Span -> Reactive a -> Voice a-reactiveToVoice' (view range -> (u,v)) r = (^. voice) $ durs `zip` (fmap (r ?) times)+reactiveToVoice' (view range -> (u,v)) r = (^. voice) $ fmap (^. stretched) $ durs `zip` (fmap (r `atTime`) times) where- times = origin : filter (\t -> u < t && t < v) (occs r)+ times = 0 : filter (\t -> u < t && t < v) (occs r) durs = toRelN' v times+{-# DEPRECATED reactiveToVoice' "" #-} -- | -- Convert a score to a voice. Fails if the score contain overlapping events. ---scoreToVoice :: Score a -> Voice (Maybe a)-scoreToVoice = (^. voice) . fmap throwTime . addRests . (^. events)+scoreToVoice :: Transformable a => Score a -> Voice (Maybe a)+scoreToVoice = (^. voice) . fmap (^. stretched) . fmap throwTime . addRests . (^. events) where throwTime (t,d,x) = (d,x)- addRests = concat . snd . mapAccumL g origin+ 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"-+ | otherwise = error "scoreToVoice: Strange prevTime"+{-# DEPRECATED scoreToVoice "" #-} -- | -- Convert a voice to a score. -- voiceToScore :: Voice a -> Score a-voiceToScore = scat . fmap g . (^. from voice)- where- g (d,x) = stretch d (return x)+voiceToScore = scat . fmap g . (^. stretcheds) where g = (^. stretchedValue) . fmap return+{-# DEPRECATED voiceToScore "" #-} +{- -- | Join voices in a given part into a score. voicesToScore :: HasPart a => [(Part a, Voice a)] -> Score a voicesToScore = pcat . fmap (voiceToScore . uncurry (\n -> fmap (setPart n)))+-} -- | -- Convert a voice which may contain rests to a score. -- voiceToScore' :: Voice (Maybe a) -> Score a voiceToScore' = mcatMaybes . voiceToScore+{-# DEPRECATED voiceToScore' "" #-} +{- -- | -- Convert a track to a score where each event is given a fixed duration. ---trackToScore :: Duration -> Track a -> Score a+trackToScore :: Transformable a => Duration -> Track a -> Score a trackToScore x = trackToScore' (const x) -- | -- Convert a track to a score, using durations determined by the values. ---trackToScore' :: (a -> Duration) -> Track a -> Score a-trackToScore' f = (^. from events) . fmap (\(t,x) -> (t,f x,x)) . (^. from track)+trackToScore' :: Transformable a => (a -> Duration) -> Track a -> Score a+trackToScore' f = (^. from events) . fmap (\(t,x) -> (t,f x,x)) . map (^. from delayed) . (^. delayeds)+-} -- Convert to delta (time to wait before this note) toRel :: [Time] -> [Duration]-toRel = snd . mapAccumL g origin where g prev t = (t, t .-. prev)+toRel = snd . mapAccumL g 0 where g prev t = (t, t .-. prev) -- Convert to delta (time to wait before next note) toRelN :: [Time] -> [Duration]@@ -159,23 +156,23 @@ -- Convert from delta (time to wait before this note) toAbs :: [Duration] -> [Time]-toAbs = snd . mapAccumL g origin where g now d = (now .+^ d, now .+^ d)+toAbs = snd . mapAccumL g 0 where g now d = (now .+^ d, now .+^ d) -- TODO rename during noteToReactive :: Monoid a => Note a -> Reactive a noteToReactive n = (pure <$> n) `activate` pure mempty --- | Split a reactive into notes, as well as the values before and after the first/last update+-- | Split a reactive into mkNotes, as well as the values before and after the first/last update splitReactive :: Reactive a -> Either a ((a, Time), [Note a], (Time, a)) splitReactive r = case updates r of [] -> Left (initial r) (t,x):[] -> Right ((initial r, t), [], (t, x))- (t,x):xs -> Right ((initial r, t), fmap note $ mrights (res $ (t,x):xs), head $ mlefts (res $ (t,x):xs))+ (t,x):xs -> Right ((initial r, t), fmap mkNote $ mrights (res $ (t,x):xs), head $ mlefts (res $ (t,x):xs)) where - note (t,u,x) = t <-> u =: x+ mkNote (t,u,x) = (t <-> u, x)^.note -- Always returns a 0 or more Right followed by one left res :: [(Time, a)] -> [Either (Time, a) (Time, Time, a)]@@ -194,8 +191,8 @@ go (x:y:rs) = (x, Just y) : withNext (y : rs) activate :: Note (Reactive a) -> Reactive a -> Reactive a-activate (getNote -> (view range -> (start,stop), x)) y = y `turnOn` (x `turnOff` y)- where- turnOn = switch start- turnOff = switch stop+activate (view (from note) -> (view range -> (start,stop), x)) y = y `turnOn` (x `turnOff` y)+ where+ turnOn = switchR start+ turnOff = switchR stop
src/Music/Score/Dynamics.hs view
@@ -1,18 +1,26 @@ +{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -24,162 +32,458 @@ -- ------------------------------------------------------------------------------------- - module Music.Score.Dynamics (- -- * Dynamics representation++ -- * Dynamic type functions+ Dynamic,+ SetDynamic,+ DynamicLensLaws',+ DynamicLensLaws,++ -- * Accessing dynamics+ HasDynamics(..), HasDynamic(..),+ HasDynamics',+ HasDynamic',+ dynamic',+ dynamics',++ -- * Manipulating dynamics+ Level,+ Attenuable,+ louder,+ softer,+ level,+ compressor,+ fadeIn,+ fadeOut,+ DynamicT(..),- dynamics,- dynamicVoice,- dynamicSingle, - -- * Dynamic transformations- -- ** Crescendo and diminuendo- Level(..),- cresc,- dim,+ -- * Context+ -- TODO move+ Ctxt,+ vdynamic,+ addDynCon, - -- ** Miscellaneous- resetDynamics, ) where import Control.Applicative import Control.Arrow-import Control.Lens hiding (Level)+import Control.Comonad+import Control.Lens hiding (Level, transform) import Control.Monad import Data.AffineSpace import Data.Foldable-import qualified Data.List as List+import Data.Functor.Couple+import Data.Functor.Context+import qualified Data.List as List import Data.Maybe import Data.Ratio import Data.Semigroup import Data.Typeable-import Data.VectorSpace hiding (Sum)+import Data.VectorSpace hiding (Sum) -import Music.Score.Combinators-import Music.Score.Convert+import Music.Dynamics.Literal+import Music.Pitch.Literal+import Music.Score.Harmonics import Music.Score.Part-import Music.Score.Score-import Music.Score.Voice+import Music.Score.Phrases+import Music.Score.Slide+import Music.Score.Text+import Music.Score.Ties+import Music.Score.Tremolo+import Music.Score.Internal.Util (through) import Music.Time+import Music.Time.Internal.Transform -import Music.Dynamics.Literal -class HasDynamic a where- setBeginCresc :: Bool -> a -> a- setEndCresc :: Bool -> a -> a- setBeginDim :: Bool -> a -> a- setEndDim :: Bool -> a -> a- setLevel :: Double -> a -> a+-- |+-- Dynamics type.+--+type family Dynamic (s :: *) :: * --- end cresc/dim, level, begin cresc/dim-newtype DynamicT a = DynamicT { getDynamicT :: (((Any, Any), Option (First Double), (Any, Any)), a) }- deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad)+-- |+-- Dynamic type.+--+type family SetDynamic (b :: *) (s :: *) :: * -instance HasDynamic (DynamicT a) where- setBeginCresc (Any -> bc) (DynamicT (((ec,ed),l,(_ ,bd)),a)) = DynamicT (((ec,ed),l,(bc,bd)),a)- setEndCresc (Any -> ec) (DynamicT (((_ ,ed),l,(bc,bd)),a)) = DynamicT (((ec,ed),l,(bc,bd)),a)- setBeginDim (Any -> bd) (DynamicT (((ec,ed),l,(bc,_ )),a)) = DynamicT (((ec,ed),l,(bc,bd)),a)- setEndDim (Any -> ed) (DynamicT (((ec,_ ),l,(bc,bd)),a)) = DynamicT (((ec,ed),l,(bc,bd)),a)- setLevel ((Option . Just . First) -> l ) (DynamicT (((ec,ed),_,(bc,bd)),a)) = DynamicT (((ec,ed),l,(bc,bd)),a)+-- |+-- Class of types that provide a single dynamic.+--+class (HasDynamics s t) => HasDynamic s t where -instance HasDynamic b => HasDynamic (a, b) where- setBeginCresc n = fmap (setBeginCresc n)- setEndCresc n = fmap (setEndCresc n)- setBeginDim n = fmap (setBeginDim n)- setEndDim n = fmap (setEndDim n)- setLevel n = fmap (setLevel n)+ -- | Access a single dynamic.+ dynamic :: Lens s t (Dynamic s) (Dynamic t) +type DynamicLensLaws' s t a b = (+ Dynamic (SetDynamic a s) ~ a,+ SetDynamic (Dynamic t) s ~ t,+ SetDynamic a (SetDynamic b s) ~ SetDynamic a s+ ) ------------------------------------------------------------------------------------ Dynamics---------------------------------------------------------------------------------+type DynamicLensLaws s t = DynamicLensLaws' s t (Dynamic s) (Dynamic t) -- |--- Represents dynamics over a duration.+-- Class of types that provide a dynamic traversal. ---data Level a- = Level a- | Change a a- deriving (Eq, Show)+class (+ Transformable (Dynamic s),+ Transformable (Dynamic t), + -- SetDynamic (Dynamic t) s ~ t+ DynamicLensLaws s t+ ) => HasDynamics s t where -instance Fractional a => IsDynamics (Level a) where- fromDynamics (DynamicsL (Just a, Nothing)) = Level (realToFrac a)- fromDynamics (DynamicsL (Just a, Just b)) = Change (realToFrac a) (realToFrac b)- fromDynamics x = error $ "fromDynamics: Invalid dynamics literal " ++ show x+ -- | Access all dynamics.+ dynamics :: Traversal s t (Dynamic s) (Dynamic t) +type HasDynamic' a = HasDynamic a a+type HasDynamics' a = HasDynamics a a +-- | Access a single dynamic.+dynamic' :: (HasDynamic s t, s ~ t) => Lens' s (Dynamic s)+dynamic' = dynamic++-- | Access all dynamics.+dynamics' :: (HasDynamics s t, s ~ t) => Traversal' s (Dynamic s)+dynamics' = dynamics++#define PRIM_DYNAMIC_INSTANCE(TYPE) \+ \+type instance Dynamic TYPE = TYPE; \+type instance SetDynamic a TYPE = a; \+ \+instance (Transformable a, a ~ Dynamic a, SetDynamic TYPE a ~ TYPE) \+ => HasDynamic TYPE a where { \+ dynamic = ($) } ; \+ \+instance (Transformable a, a ~ Dynamic a, SetDynamic TYPE a ~ TYPE) \+ => HasDynamics TYPE a where { \+ dynamics = ($) } ; \++PRIM_DYNAMIC_INSTANCE(())+PRIM_DYNAMIC_INSTANCE(Bool)+PRIM_DYNAMIC_INSTANCE(Ordering)+PRIM_DYNAMIC_INSTANCE(Char)+PRIM_DYNAMIC_INSTANCE(Int)+PRIM_DYNAMIC_INSTANCE(Integer)+PRIM_DYNAMIC_INSTANCE(Float)+PRIM_DYNAMIC_INSTANCE(Double)++type instance Dynamic (c,a) = Dynamic a+type instance SetDynamic b (c,a) = (c,SetDynamic b a)+type instance Dynamic [a] = Dynamic a+type instance SetDynamic b [a] = [SetDynamic b a]++type instance Dynamic (Maybe a) = Dynamic a+type instance SetDynamic b (Maybe a) = Maybe (SetDynamic b a)+type instance Dynamic (Either c a) = Dynamic a+type instance SetDynamic b (Either c a) = Either c (SetDynamic b a)++type instance Dynamic (Note a) = Dynamic a+type instance SetDynamic b (Note a) = Note (SetDynamic b a)+type instance Dynamic (Delayed a) = Dynamic a+type instance SetDynamic b (Delayed a) = Delayed (SetDynamic b a)+type instance Dynamic (Stretched a) = Dynamic a+type instance SetDynamic b (Stretched a) = Stretched (SetDynamic b a)++type instance Dynamic (Voice a) = Dynamic a+type instance SetDynamic b (Voice a) = Voice (SetDynamic b a)+type instance Dynamic (Chord a) = Dynamic a+type instance SetDynamic b (Chord a) = Chord (SetDynamic b a)+type instance Dynamic (Track a) = Dynamic a+type instance SetDynamic b (Track a) = Track (SetDynamic b a)+type instance Dynamic (Score a) = Dynamic a+type instance SetDynamic b (Score a) = Score (SetDynamic b a)++instance HasDynamic a b => HasDynamic (c, a) (c, b) where+ dynamic = _2 . dynamic++instance HasDynamics a b => HasDynamics (c, a) (c, b) where+ dynamics = traverse . dynamics++instance HasDynamics a b => HasDynamics [a] [b] where+ dynamics = traverse . dynamics++instance HasDynamics a b => HasDynamics (Maybe a) (Maybe b) where+ dynamics = traverse . dynamics++instance HasDynamics a b => HasDynamics (Either c a) (Either c b) where+ dynamics = traverse . dynamics++++instance (HasDynamics a b) => HasDynamics (Note a) (Note b) where+ dynamics = _Wrapped . whilstL dynamics++instance (HasDynamic a b) => HasDynamic (Note a) (Note b) where+ dynamic = _Wrapped . whilstL dynamic+++instance (HasDynamics a b) => HasDynamics (Delayed a) (Delayed b) where+ dynamics = _Wrapped . whilstLT dynamics++instance (HasDynamic a b) => HasDynamic (Delayed a) (Delayed b) where+ dynamic = _Wrapped . whilstLT dynamic+++instance (HasDynamics a b) => HasDynamics (Stretched a) (Stretched b) where+ dynamics = _Wrapped . whilstLD dynamics++instance (HasDynamic a b) => HasDynamic (Stretched a) (Stretched b) where+ dynamic = _Wrapped . whilstLD dynamic+++instance HasDynamics a b => HasDynamics (Voice a) (Voice b) where+ dynamics = traverse . dynamics++instance HasDynamics a b => HasDynamics (Track a) (Track b) where+ dynamics = traverse . dynamics++instance HasDynamics a b => HasDynamics (Chord a) (Chord b) where+ dynamics = traverse . dynamics++instance (HasDynamics a b) => HasDynamics (Score a) (Score b) where+ dynamics =+ _Wrapped . _2 -- into NScore+ . _Wrapped+ . traverse+ . _Wrapped -- this needed?+ . whilstL dynamics++type instance Dynamic (Behavior a) = Behavior a+type instance SetDynamic b (Behavior a) = b++instance (+ Transformable a, Transformable b, b ~ Dynamic b, SetDynamic (Behavior a) b ~ Behavior a+ ) => HasDynamics (Behavior a) b where+ dynamics = ($)++instance (+ Transformable a, Transformable b, b ~ Dynamic b, SetDynamic (Behavior a) b ~ Behavior a+ ) => HasDynamic (Behavior a) b where+ dynamic = ($)++type instance Dynamic (Couple c a) = Dynamic a+type instance SetDynamic g (Couple c a) = Couple c (SetDynamic g a)+type instance Dynamic (TremoloT a) = Dynamic a+type instance SetDynamic g (TremoloT a) = TremoloT (SetDynamic g a)+type instance Dynamic (TextT a) = Dynamic a+type instance SetDynamic g (TextT a) = TextT (SetDynamic g a)+type instance Dynamic (HarmonicT a) = Dynamic a+type instance SetDynamic g (HarmonicT a) = HarmonicT (SetDynamic g a)+type instance Dynamic (TieT a) = Dynamic a+type instance SetDynamic g (TieT a) = TieT (SetDynamic g a)+type instance Dynamic (SlideT a) = Dynamic a+type instance SetDynamic g (SlideT a) = SlideT (SetDynamic g a)+++instance (HasDynamics a b) => HasDynamics (Couple c a) (Couple c b) where+ dynamics = _Wrapped . dynamics++instance (HasDynamic a b) => HasDynamic (Couple c a) (Couple c b) where+ dynamic = _Wrapped . dynamic+++instance (HasDynamics a b) => HasDynamics (TremoloT a) (TremoloT b) where+ dynamics = _Wrapped . dynamics++instance (HasDynamic a b) => HasDynamic (TremoloT a) (TremoloT b) where+ dynamic = _Wrapped . dynamic+++instance (HasDynamics a b) => HasDynamics (TextT a) (TextT b) where+ dynamics = _Wrapped . dynamics++instance (HasDynamic a b) => HasDynamic (TextT a) (TextT b) where+ dynamic = _Wrapped . dynamic+++instance (HasDynamics a b) => HasDynamics (HarmonicT a) (HarmonicT b) where+ dynamics = _Wrapped . dynamics++instance (HasDynamic a b) => HasDynamic (HarmonicT a) (HarmonicT b) where+ dynamic = _Wrapped . dynamic+++instance (HasDynamics a b) => HasDynamics (TieT a) (TieT b) where+ dynamics = _Wrapped . dynamics++instance (HasDynamic a b) => HasDynamic (TieT a) (TieT b) where+ dynamic = _Wrapped . dynamic+++instance (HasDynamics a b) => HasDynamics (SlideT a) (SlideT b) where+ dynamics = _Wrapped . dynamics++instance (HasDynamic a b) => HasDynamic (SlideT a) (SlideT b) where+ dynamic = _Wrapped . dynamic+++ -- |--- Apply a dynamic level over the score.+-- Associated interval type. ---dynamics :: (HasDynamic a, HasPart' a) => Score (Level Double) -> Score a -> Score a-dynamics d a = (duration a `stretchTo` d) `dynamics'` a+type Level a = Diff (Dynamic a) -dynamicSingle :: HasDynamic a => Score (Level Double) -> Score a -> Score a-dynamicSingle d a = (duration a `stretchTo` d) `dynamicsSingle'` a+-- |+-- Class of types that can be transposed.+--+type Attenuable a+ = (HasDynamics a a,+ VectorSpace (Level a), AffineSpace (Dynamic a),+ {-IsLevel (Level a), -} IsDynamics (Dynamic a)) -- |--- Apply a dynamic level over a voice.+-- Transpose up. ---dynamicVoice :: HasDynamic a => Score (Level Double) -> Voice (Maybe a) -> Voice (Maybe a)-dynamicVoice d = scoreToVoice . dynamicSingle d . removeRests . voiceToScore+louder :: Attenuable a => Level a -> a -> a+louder a = dynamics %~ (.+^ a) +-- |+-- Transpose down.+--+softer :: Attenuable a => Level a -> a -> a+softer a = dynamics %~ (.-^ a) -dynamics' :: (HasDynamic a, HasPart' a) => Score (Level Double) -> Score a -> Score a-dynamics' ds = mapAllParts (fmap $ dynamicsSingle' ds)+-- |+-- Transpose down.+--+volume :: (Num (Dynamic t), HasDynamics s t, Dynamic s ~ Dynamic t) => Dynamic t -> s -> t+volume a = dynamics *~ a -dynamicsSingle' :: HasDynamic a => Score (Level Double) -> Score a -> Score a-dynamicsSingle' ds = applyDynSingle (fmap fromJust $ scoreToVoice ds)+-- |+-- Transpose down.+--+level :: Attenuable a => Dynamic a -> a -> a+level a = dynamics .~ a +compressor :: Attenuable a =>+ Dynamic a -- ^ Threshold+ -> Scalar (Level a) -- ^ Ratio+ -> a+ -> a+compressor = error "Not implemented: compressor" -applyDynSingle :: HasDynamic a => Voice (Level Double) -> Score a -> Score a-applyDynSingle ds = applySingle ds3- where- -- ds2 :: Voice (Dyn2 Double)- ds2 = mapValuesVoice dyn2 ds- -- ds3 :: Voice (Score a -> Score a)- ds3 = fmap g ds2+--+-- TODO non-linear fades etc+-- - g (ec,ed,l,bc,bd) = id- . (if ec then mapFirstSingle (setEndCresc True) else id)- . (if ed then mapFirstSingle (setEndDim True) else id)- . (if bc then mapFirstSingle (setBeginCresc True) else id)- . (if bd then mapFirstSingle (setBeginDim True) else id)- . maybe id (mapFirstSingle . setLevel) l- mapFirstSingle f = mapPhraseSingle f id id+-- |+-- Fade in.+--+fadeIn :: (+ HasPosition a, HasDynamics a a, + Dynamic a ~ Behavior c, Fractional c+ ) => Duration -> a -> a+fadeIn d x = x & dynamics *~ (_onset x >-> d `transform` unit) --- end cresc, end dim, level, begin cresc, begin dim-type LevelDiff a = (Bool, Bool, Maybe a, Bool, Bool)+-- |+-- Fade in.+--+fadeOut :: (+ HasPosition a, HasDynamics a a, + Dynamic a ~ Behavior c, Fractional c+ ) => Duration -> a -> a+fadeOut d x = x & dynamics *~ (d <-< _offset x `transform` rev unit) -dyn2 :: Ord a => [Level a] -> [LevelDiff a]-dyn2 = snd . List.mapAccumL g (Nothing, False, False) -- level, cresc, dim- where- g (Nothing, False, False) (Level b) = ((Just b, False, False), (False, False, Just b, False, False))- g (Nothing, False, False) (Change b c) = ((Just b, b < c, b > c), (False, False, Just b, b < c, b > c)) - g (Just a , cr, dm) (Level b)- | a == b = ((Just b, False, False), (cr, dm, Nothing, False, False))- | a /= b = ((Just b, False, False), (cr, dm, Just b, False, False))- g (Just a , cr, dm) (Change b c)- | a == b = ((Just b, b < c, b > c), (cr, dm, Nothing, b < c, b > c))- | a /= b = ((Just b, b < c, b > c), (cr, dm, Just b, b < c, b > c)) +newtype DynamicT n a = DynamicT { getDynamicT :: (n, a) }+ deriving (Eq, Ord, Show, Typeable, Functor,+ Applicative, Monad, Comonad, Transformable, Monoid, Semigroup) +instance (Monoid n, Num a) => Num (DynamicT n a) where+ (+) = liftA2 (+)+ (*) = liftA2 (*)+ (-) = liftA2 (-)+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger -mapValuesVoice :: ([a] -> [b]) -> Voice a -> Voice b-mapValuesVoice f = (^. voice) . uncurry zip . second f . unzip . (^. from voice)+instance (Monoid n, Fractional a) => Fractional (DynamicT n a) where+ recip = fmap recip+ fromRational = pure . fromRational +instance (Monoid n, Floating a) => Floating (DynamicT n a) where+ pi = pure pi+ sqrt = fmap sqrt+ exp = fmap exp+ log = fmap log+ sin = fmap sin+ cos = fmap cos+ asin = fmap asin+ atan = fmap atan+ acos = fmap acos+ sinh = fmap sinh+ cosh = fmap cosh+ asinh = fmap asinh+ atanh = fmap atanh+ acosh = fmap acos +instance (Monoid n, Enum a) => Enum (DynamicT n a) where+ toEnum = pure . toEnum+ fromEnum = fromEnum . extract +instance (Monoid n, Bounded a) => Bounded (DynamicT n a) where+ minBound = pure minBound+ maxBound = pure maxBound -cresc :: IsDynamics a => Double -> Double -> a-cresc a b = fromDynamics $ DynamicsL (Just a, Just b)+-- instance (Monoid n, Num a, Ord a, Real a) => Real (DynamicT n a) where+-- toRational = toRational . extract+--+-- instance (Monoid n, Real a, Enum a, Integral a) => Integral (DynamicT n a) where+-- quot = liftA2 quot+-- rem = liftA2 rem+-- toInteger = toInteger . extract -dim :: IsDynamics a => Double -> Double -> a-dim a b = fromDynamics $ DynamicsL (Just a, Just b)+-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (DynamicT p a) where+ type Unwrapped (DynamicT p a) = (p, a)+ _Wrapped' = iso getDynamicT DynamicT +instance Rewrapped (DynamicT p a) (DynamicT p' b) -resetDynamics :: HasDynamic c => c -> c-resetDynamics = setBeginCresc False . setEndCresc False . setBeginDim False . setEndDim False++type instance Dynamic (DynamicT p a) = p+type instance SetDynamic p' (DynamicT p a) = DynamicT p' a++instance (Transformable p, Transformable p') + => HasDynamic (DynamicT p a) (DynamicT p' a) where+ dynamic = _Wrapped . _1++instance (Transformable p, Transformable p') + => HasDynamics (DynamicT p a) (DynamicT p' a) where+ dynamics = _Wrapped . _1+++deriving instance (IsPitch a, Monoid n) => IsPitch (DynamicT n a)+deriving instance (IsInterval a, Monoid n) => IsInterval (DynamicT n a)++instance (IsDynamics n, Monoid a) => IsDynamics (DynamicT n a) where+ fromDynamics l = DynamicT (fromDynamics l, mempty)++deriving instance Reversible a => Reversible (DynamicT p a)++instance (Tiable n, Tiable a) => Tiable (DynamicT n a) where+ toTied (DynamicT (d,a)) = (DynamicT (d1,a1), DynamicT (d2,a2))+ where+ (a1,a2) = toTied a+ (d1,d2) = toTied d++-- |+-- View just the dynamices in a voice.+--+vdynamic :: ({-SetDynamic (Dynamic t) s ~ t,-} HasDynamic a a, HasDynamic a b) + => Lens (Voice a) (Voice b) (Voice (Dynamic a)) (Voice (Dynamic b))++vdynamic = lens (fmap $ view dynamic) (flip $ zipVoiceWithNoScale (set dynamic))+-- vdynamic = through dynamic dynamic++addDynCon :: (+ HasPhrases s t a b, HasDynamic a a, HasDynamic a b, + Dynamic a ~ d, Dynamic b ~ Ctxt d+ ) => s -> t+addDynCon = over (phrases.vdynamic) withContext
src/Music/Score/Export/Abc.hs view
@@ -1,2 +1,18 @@ +-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides ABC notation export.+--+-- /Warning/ Experimental module.+--+-------------------------------------------------------------------------------------+ module Music.Score.Export.Abc where
+ src/Music/Score/Export/ArticulationNotation.hs view
@@ -0,0 +1,131 @@++{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Score.Export.ArticulationNotation (+ Slur(..),+ Mark(..),+ ArticulationNotation(..),+ notateArticulation,+ ) where++import Data.Semigroup+import Data.Functor.Context+import Control.Lens -- ()+import Music.Score.Articulation+import Music.Score.Instances+import Music.Score.Ties+import Music.Time++-- TODO need NoSlur etc?++data Slur = NoSlur | BeginSlur | EndSlur+ deriving (Eq, Ord, Show)++-- data CrescDim = NoCrescDim | BeginCresc | EndCresc | BeginDim | EndDim+data Mark = NoMark | Staccato | MoltoStaccato | Marcato | Accent | Tenuto+ deriving (Eq, Ord, Show)++instance Monoid Slur where+ mempty = NoSlur+ mappend NoSlur a = a+ mappend a NoSlur = a+ mappend a _ = a++instance Monoid Mark where+ mempty = NoMark+ mappend NoMark a = a+ mappend a NoMark = a+ mappend a _ = a++newtype ArticulationNotation + = ArticulationNotation { getArticulationNotation :: ([Slur], [Mark]) }++instance Wrapped ArticulationNotation where+ type Unwrapped ArticulationNotation = ([Slur], [Mark])+ _Wrapped' = iso getArticulationNotation ArticulationNotation++instance Rewrapped ArticulationNotation ArticulationNotation++type instance Articulation ArticulationNotation = ArticulationNotation++instance Transformable ArticulationNotation where+ transform _ = id++instance Tiable ArticulationNotation where+ toTied (ArticulationNotation (beginEnd, marks)) + = (ArticulationNotation (beginEnd, marks), + ArticulationNotation (mempty, mempty))++instance Monoid ArticulationNotation where+ mempty = ArticulationNotation ([], [])+ ArticulationNotation ([], []) `mappend` y = y+ x `mappend` ArticulationNotation ([], []) = x+ x `mappend` y = x++-- TODO add slurs if separation is below some value...++getSeparationMarks :: Double -> [Mark]+getSeparationMarks = fst . getSeparationMarks'++hasSlur' :: Double -> Bool+hasSlur' = snd . getSeparationMarks'++getSeparationMarks' :: Double -> ([Mark], Bool)+getSeparationMarks' x+ | x <= (-1) = ([], True)+ | (-1) < x && x < 1 = ([], False)+ | 1 <= x && x < 2 = ([Staccato], False)+ | 2 <= x = ([MoltoStaccato], False)++getAccentMarks :: Double -> [Mark]+getAccentMarks x+ | x <= (-1) = []+ | (-1) < x && x < 1 = []+ | 1 <= x && x < 2 = [Accent]+ | 2 <= x = [Marcato]+ | otherwise = []++hasSlur y = hasSlur' (realToFrac $ view separation $ y)+allMarks y = getSeparationMarks (realToFrac $ view separation $ y) <> getAccentMarks (realToFrac $ view accentuation $ y)++notateArticulation :: (Ord a, a ~ (Sum Double, Sum Double)) => Ctxt a -> ArticulationNotation+notateArticulation (Nothing, y, Nothing) = ArticulationNotation ([], allMarks y)+notateArticulation (Just x, y, Nothing) = ArticulationNotation (if hasSlur x && hasSlur y then [EndSlur] else [], allMarks y)+notateArticulation (Nothing, y, Just z) = ArticulationNotation (if hasSlur y && hasSlur z then [BeginSlur] else [], allMarks y)+notateArticulation (Just x, y, Just z) = ArticulationNotation (slur3 x y z, allMarks y)+ where+ slur3 x y z = case (hasSlur x, hasSlur y, hasSlur z) of+ (True, True, True) -> [{-ContSlur-}]+ (False, True, True) -> [BeginSlur]+ (True, True, False) -> [EndSlur]+ _ -> []+
+ src/Music/Score/Export/Backend.hs view
@@ -0,0 +1,238 @@++{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Score.Export.Backend (+ HasOrdPart,+ HasDynamic3,+ HasDynamicNotation,++ HasBackend(..),+ HasBackendScore(..),+ HasBackendNote(..),+ export+ ) where++import Music.Dynamics.Literal+import Music.Pitch.Literal+import qualified Codec.Midi as Midi+import Control.Arrow ((***))+import Control.Comonad (Comonad (..), extract)+import Control.Applicative+import Data.Colour.Names as Color+import Data.Default+import Data.Foldable (Foldable)+import qualified Data.Foldable+import Data.Functor.Couple+import Data.Maybe+import Data.Ratio+import Data.Traversable (Traversable, sequenceA)+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as MusicXml+import Music.Score.Internal.Export hiding (MVoice)+import Music.Time.Internal.Transform (whilstLD)+import System.Process+import Music.Time.Internal.Quantize+import qualified Text.Pretty as Pretty+import qualified Data.List+import Music.Score.Convert (reactiveToVoice') -- TODO+import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates)+import Music.Score.Export.DynamicNotation+import Data.Semigroup.Instances+++import Music.Time+import Music.Score.Dynamics+import Music.Score.Part+++type HasDynamic3 a a' a'' = (+ HasDynamic' a,+ HasDynamic a a',+ HasDynamic a' a'',+ HasDynamic a a''+ )++-- -- TODO move+-- class (+-- HasDynamic' a,+-- HasDynamic a a',+-- HasDynamic a' a'',+-- HasDynamic a a''+-- ) => HasDynamic3 a a' a'' where+-- +-- instance (b ~ SetDynamic (Ctxt (Dynamic MyNote)) MyNote, c ~ SetDynamic DynamicNotation MyNote) +-- => HasDynamic3 MyNote b c++type HasDynamicNotation a b c = (+ HasDynamic3 a b c,+ Dynamic b ~ Ctxt (Dynamic a),+ Dynamic c ~ DynamicNotation,+ Real (Dynamic a),+ Part (SetDynamic (Dynamic a) a) ~ Part (SetDynamic DynamicNotation b)+ )+type HasOrdPart a = (HasPart' a, Ord (Part a))++++{-+-- TODO move+mapWithDur :: (Duration -> a -> b) -> Rhythm a -> Rhythm b+mapWithDur f = go+ where+ go (Beat d x) = Beat d (f d x)+ go (Dotted n (Beat d x)) = Dotted n $ Beat d (f (dotMod n * d) x)+ go (Group rs) = Group $ fmap (mapWithDur f) rs+ go (Tuplet m r) = Tuplet m (mapWithDur f r) ++extractTimeSignatures :: Score a -> ([Maybe TimeSignature], [Duration])+extractTimeSignatures score = (barTimeSignatures, barDurations)+ where + defaultTimeSignature = time 4 4+ timeSignatures = fmap swap + $ view eventsV . fuse . reactiveToVoice' (0 <-> (score^.offset)) + $ getTimeSignatures defaultTimeSignature score++ -- Despite the fuse above we need retainUpdates here to prevent redundant repetition of time signatures+ barTimeSignatures = retainUpdates $ getBarTimeSignatures timeSignatures+ barDurations = getBarDurations timeSignatures+++-}+++++-- |+-- This class defines types and functions for exporting music. It provides the+-- primitive types and methods used to implement 'export'.+--+-- The backend type @b@ is just a type level tag to identify a specific backend.+-- It is typically defined as an empty data declaration.+--+-- The actual conversion is handled by the subclasses 'HasBackendScore' and+-- 'HasBackendNote', which converts the time structure, and the contained music+-- respectively. Thus structure and content are handled separately.+--+-- It is often necessary to alter the events based on their surrounding context: for+-- examples the beginning and end of spanners and beams depend on surrounding notes.+-- The 'BackendContext' type allow 'HasBackendScore' instances to provide context for+-- 'HasBackendNote' instances.+--+-- @+-- data NoteList+--+-- instance HasBackend NoteList where+-- type BackendScore NoteList = []+-- type BackendContext NoteList = Identity+-- type BackendNote NoteList = [(Sum Int, Int)]+-- type BackendMusic NoteList = [(Sum Int, Int)]+-- finalizeExport _ = concat+--+-- instance HasBackendScore NoteList [a] a where+-- exportScore _ = fmap Identity+--+-- instance HasBackendNote NoteList a => HasBackendNote NoteList [a] where+-- exportNote b ps = mconcat $ map (exportNote b) $ sequenceA ps+--+-- instance HasBackendNote NoteList Int where+-- exportNote _ (Identity p) = [(mempty ,p)]+--+-- instance HasBackendNote NoteList a => HasBackendNote NoteList (DynamicT (Sum Int) a) where+-- exportNote b (Identity (DynamicT (d,ps))) = set (mapped._1) d $ exportNote b (Identity ps)+-- -- @+--+--+class Functor (BackendScore b) => HasBackend b where+ -- | External music representation+ type BackendMusic b :: *++ -- | Notes, chords and rests, with output handled by 'HasBackendNote'+ type BackendNote b :: *++ -- | Score, voice and time structure, with output handled by 'HasBackendScore'+ type BackendScore b :: * -> *++ -- | This type may be used to pass context from 'exportScore' to 'exportNote'.+ -- If the note export is not context-sensitive, 'Identity' can be used.+ type BackendContext b :: * -> *++ finalizeExport :: b -> BackendScore b (BackendNote b) -> BackendMusic b++-- |+-- A class for musical container types with an external representation.+--+-- The first type parameter is simply a token representing the external format,+-- and the second parameter is the type being represented. In a sense, the first+-- parameter saves us from defining a separate class for each external representation,+-- so rather than having `HasMidiScore`, `HasMusicXmlScore` and so on, we have +-- 'HasBackendScore' 'Midi', 'HasBackendScore' 'MusicXml' and so on.+--+class (HasBackend b) => HasBackendScore b s where++ -- | Type of events in this score type.+ -- This is generally just the parameterized type in a container, so we have+ -- + -- @+ -- BackendScoreEvent (Score a) ~ a+ -- BackendScoreEvent (Voice a) ~ a+ -- @+ --+ -- and so on.+ --+ -- It is defined as a type function so that instances can put constraints on the+ -- saturated type, rather than being parametric over all note types.+ --+ type BackendScoreEvent b s :: *+ exportScore :: b -> s -> BackendScore b (BackendContext b (BackendScoreEvent b s))++-- |+-- A class for musical event types with an external representation.+--+-- The first type parameter is simply a token representing the external format,+-- and the second parameter is the type being represented. In a sense, the first+-- parameter saves us from defining a separate class for each external representation,+-- so rather than having `HasMidiNote`, `HasMusicXmlNote` and so on, we have +-- 'HasBackendNote' 'Midi', 'HasBackendNote' 'MusicXml' and so on.+--+class (HasBackend b) => HasBackendNote b a where+ exportNote :: b -> BackendContext b a -> BackendNote b+ exportChord :: b -> BackendContext b [a] -> BackendNote b+ exportChord = error "Not implemented: exportChord"++-- |+-- This is the primitive music export function.+--+-- Backend developers are encouraged to provide wrappers on the form 'toX', 'writeX' etc.+--+export :: (HasBackendScore b s, HasBackendNote b (BackendScoreEvent b s)) => b -> s -> BackendMusic b+export b = finalizeExport b . export'+ where+ export' = fmap (exportNote b) . exportScore b+
− src/Music/Score/Export/Common.hs
@@ -1,105 +0,0 @@--{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)-------------------------------------------------------------------------------------------module Music.Score.Export.Common (- voiceToBars',- -- separateBars,- spellPitch,- toRelative,- ) where--import Prelude hiding (concat, concatMap, foldl,- foldr, mapM, maximum, minimum, sum)--import Control.Applicative-import Control.Lens-import Control.Monad hiding (mapM)-import Control.Monad.Plus-import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Basis-import Data.Either-import Data.Foldable-import Data.Function (on)-import Data.Maybe-import Data.Ord (comparing)-import Data.Ratio-import Data.Semigroup-import Data.String-import Data.Traversable-import Data.Typeable-import Data.VectorSpace--import Music.Score.Articulation-import Music.Score.Combinators-import Music.Score.Convert-import Music.Score.Dynamics-import Music.Score.Ornaments-import Music.Score.Part-import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score-import Music.Score.Ties-import Music.Score.Track-import Music.Score.Voice-import Music.Time--import qualified Codec.Midi as Midi-import qualified Data.List as List-import qualified Data.Map as Map-import qualified Music.Lilypond as Lilypond-import qualified Music.MusicXml.Simple as Xml-import qualified Text.Pretty as Pretty--import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Util-import System.IO.Unsafe-import System.Process---- | Convert a voice to a list of bars using the given bar durations.-voiceToBars' :: Tiable a => [Duration] -> Voice (Maybe a) -> [[(Duration, Maybe a)]]-voiceToBars' barDurs = fmap (^. from voice) . splitTiesVoiceAt barDurs--- TODO remove prime from name---- | Convert absolute to relative durations.-toRelative :: [(Time, Duration, b)] -> [(Time, Duration, b)]-toRelative = snd . mapAccumL g origin- where- g now (t,d,x) = (t, (origin .+^ (t .-. now),d,x))---- | Basic spelling for integral types.-spellPitch :: Integral a => a -> (a, a, a)-spellPitch p = (- pitchClass,- alteration,- octave- )- where- octave = (p `div` 12) - 1- semitone = p `mod` 12- pitchClass = fromStep major semitone- alteration = semitone - step major pitchClass-- step xs p = xs !! (fromIntegral p `mod` length xs)- fromStep xs p = fromIntegral $ fromMaybe (length xs - 1) $ List.findIndex (>= p) xs- scaleFromSteps = snd . List.mapAccumL add 0- where- add a x = (a + x, a + x)- major = scaleFromSteps [0,2,2,1,2,2,2,1]-
+ src/Music/Score/Export/DynamicNotation.hs view
@@ -0,0 +1,110 @@++{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Score.Export.DynamicNotation (+ CrescDim(..),+ DynamicNotation(..),+ notateDynamic,+ ) where++import Data.Semigroup+import Data.Functor.Context+import Control.Lens -- ()+import Music.Score.Dynamics+import Music.Score.Ties+import Music.Time+++data CrescDim = NoCrescDim | BeginCresc | EndCresc | BeginDim | EndDim+ deriving (Eq, Ord, Show)++instance Monoid CrescDim where+ mempty = NoCrescDim+ mappend NoCrescDim a = a+ mappend a NoCrescDim = a+ mappend a _ = a++newtype DynamicNotation + = DynamicNotation { getDynamicNotation :: ([CrescDim], Maybe Double) }++instance Wrapped DynamicNotation where+ type Unwrapped DynamicNotation = ([CrescDim], Maybe Double)+ _Wrapped' = iso getDynamicNotation DynamicNotation++instance Rewrapped DynamicNotation DynamicNotation++type instance Dynamic DynamicNotation = DynamicNotation++instance Transformable DynamicNotation where+ transform _ = id++instance Tiable DynamicNotation where+ toTied (DynamicNotation (beginEnd, marks)) + = (DynamicNotation (beginEnd, marks), + DynamicNotation (mempty, Nothing))++instance Monoid DynamicNotation where+ mempty = DynamicNotation ([], Nothing)+ DynamicNotation ([], Nothing) `mappend` y = y+ x `mappend` DynamicNotation ([], Nothing) = x+ x `mappend` y = x++-- Given a dynamic value and its context, decide:+--+-- 1) Whether we should begin or end a crescendo or diminuendo+-- 2) Whether we should display the current dynamic value+--+notateDynamic :: (Ord a, Real a) => Ctxt a -> DynamicNotation+notateDynamic x = DynamicNotation $ over _2 (\t -> if t then Just (realToFrac $ extractCtxt x) else Nothing) $ case x of+ (Nothing, y, Nothing) -> ([], True)+ (Nothing, y, Just z ) -> case (y `compare` z) of+ LT -> ([BeginCresc], True)+ EQ -> ([], True)+ GT -> ([BeginDim], True)+ (Just x, y, Just z ) -> case (x `compare` y, y `compare` z) of+ (LT,LT) -> ([NoCrescDim], False)+ (LT,EQ) -> ([EndCresc], True)+ (EQ,LT) -> ([BeginCresc], False{-True-})++ (GT,GT) -> ([NoCrescDim], False)+ (GT,EQ) -> ([EndDim], True)+ (EQ,GT) -> ([BeginDim], False{-True-})++ (EQ,EQ) -> ([], False)+ (LT,GT) -> ([EndCresc, BeginDim], True)+ (GT,LT) -> ([EndDim, BeginCresc], True)+++ (Just x, y, Nothing) -> case (x `compare` y) of+ LT -> ([EndCresc], True)+ EQ -> ([], False)+ GT -> ([EndDim], True)
src/Music/Score/Export/Lilypond.hs view
@@ -1,19 +1,24 @@ +{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -24,415 +29,664 @@ ------------------------------------------------------------------------------------- module Music.Score.Export.Lilypond (- Lilypond,- HasLilypond(..),-- toLilypond,- toLilypondString,-- showLilypond,- openLilypond,- writeLilypond,+ -- * Lilypond backend+ HasLilypondInstrument(..),+ Lilypond,+ LyContext(..),+ HasLilypond,+ + -- ** Converting to Lilypond+ toLilypond,+ toLilypondString, + + -- ** Lilypond I/O+ showLilypond,+ openLilypond,+ writeLilypond, - -- * Options- LilypondOptions(..),- writeLilypond',- openLilypond',+ -- ** Customize Lilypond backend+ LilypondOptions(..),+ openLilypond',+ writeLilypond', ) where -import Prelude hiding (concat, concatMap, foldl,- foldr, mapM, maximum, minimum,- sum)-+import Music.Dynamics.Literal+import Music.Pitch.Literal+import qualified Codec.Midi as Midi+import Control.Arrow ((***))+import Control.Comonad (Comonad (..), extract) import Control.Applicative-import Control.Arrow-import Control.Lens hiding (rewrite)-import Control.Monad hiding (mapM)-import Data.AffineSpace+import Data.Colour.Names as Color import Data.Default-import Data.Foldable-import Data.Function (on)-import Data.Maybe (fromMaybe)-import Data.Ord (comparing)+import Data.Foldable (Foldable)+import qualified Data.Foldable+import Data.Functor.Couple+import Data.Maybe import Data.Ratio-import Data.Semigroup-import Data.String-import Data.Traversable-import Data.Typeable-import Data.VectorSpace hiding (Sum)+import Data.Traversable (Traversable, sequenceA)+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as MusicXml+import Music.Score.Internal.Export hiding (MVoice)+import Music.Time.Internal.Transform (whilstLD) import System.Process+import Music.Time.Internal.Quantize+import qualified Text.Pretty as Pretty+import qualified Data.List+import Music.Score.Convert (reactiveToVoice', voiceToScore) -- TODO+import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates)+import Music.Score.Export.DynamicNotation+import Music.Score.Export.ArticulationNotation+import Data.Semigroup.Instances -import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Articulation-import Music.Score.Chord-import Music.Score.Clef-import Music.Score.Combinators-import Music.Score.Convert-import Music.Score.Dynamics-import Music.Score.Export.Common-import Music.Score.Instances-import Music.Score.Meta-import Music.Score.Meta.Attribution-import Music.Score.Meta.Clef-import Music.Score.Meta.Time-import Music.Score.Meta.Title-import Music.Score.Ornaments-import Music.Score.Part-import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score-import Music.Score.Ties-import Music.Score.Track-import Music.Score.Util-import Music.Score.Voice-import Music.Time-import Music.Time.Reactive (initial)+import Music.Score.Export.Backend -import qualified Codec.Midi as Midi-import qualified Data.List as List-import qualified Data.Map as Map-import qualified Music.Lilypond as Lilypond-import qualified Music.MusicXml.Simple as Xml-import qualified Text.Pretty as Pretty+import Data.Functor.Identity+import Data.Semigroup+import Control.Monad+import Data.VectorSpace hiding (Sum(..))+import Data.AffineSpace+import Control.Lens hiding (rewrite)+-- import Control.Lens.Operators hiding ((|>)) +import Music.Time+import Music.Score.Meta+import Music.Score.Dynamics+import Music.Score.Articulation+import Music.Score.Part+import Music.Score.Tremolo+import Music.Score.Text+import Music.Score.Harmonics+import Music.Score.Slide+import Music.Score.Color+import Music.Score.Ties+import Music.Score.Export.Backend+import Music.Score.Meta.Time+import Music.Score.Phrases -type Lilypond = Lilypond.Music --- |--- Class of types that can be converted to Lilypond.+++++++-- | +-- Extract instrument info as per "Music.Part"+-- This is really crude, needs rethinking! ---class Tiable a => HasLilypond a where+class HasLilypondInstrument a where+ getLilypondClef :: a -> Int - -- |- -- Convert a value to a Lilypond music expression.- --- getLilypond :: Duration -> a -> Lilypond - getLilypondChord :: Duration -> [a] -> Lilypond- getLilypondChord d = pcatLilypond . fmap (getLilypond d)+{-+ TODO rewrite so that:+ - Each bar may include voices/layers (a la Sibelius)+ - Each part may generate more than one staff (for piano etc)+-} - getLilypondWithPrefix :: Duration -> a -> (Lilypond -> Lilypond,Lilypond)- getLilypondWithPrefix d x = (id, getLilypond d x) - getLilypondChordWithPrefix :: Duration -> [a] -> (Lilypond -> Lilypond,Lilypond)- getLilypondChordWithPrefix d x = (id, getLilypondChord d x)+-- | A token to represent the Lilypond backend.+data Lilypond -instance HasLilypond Int where getLilypond d = getLilypond d . toInteger-instance HasLilypond Float where getLilypond d = getLilypond d . toInteger . round-instance HasLilypond Double where getLilypond d = getLilypond d . toInteger . round-instance Integral a => HasLilypond (Ratio a) where getLilypond d = getLilypond d . toInteger . round+data ScoreInfo = ScoreInfo+ deriving (Eq, Show) -instance HasLilypond Integer where- getLilypond d = (^*realToFrac (d*4)) . Lilypond.note . spellLilypond- getLilypondChord d = (^*realToFrac (d*4)) . Lilypond.chord . fmap spellLilypond+data StaffInfo = StaffInfo { staffName :: String, + staffClef :: Lilypond.Clef } + deriving (Eq, Show) -instance HasLilypond a => HasLilypond (ChordT a) where- getLilypond d = getLilypondChord d . getChordT+data BarInfo = BarInfo { barTimeSignature :: Maybe TimeSignature } + deriving (Eq, Show) -instance HasLilypond a => HasLilypond (PartT n a) where- getLilypond d (PartT (_,x)) = getLilypond d x+-- | Hierachical representation of a Lilypond score.+-- A score is a parallel composition of staves.+data LyScore a = LyScore { getLyScore :: (ScoreInfo, [LyStaff a]) }+ deriving (Functor, Eq, Show) -instance HasLilypond a => HasLilypond (TieT a) where- getLilypond d (TieT ((Any ta, Any tb),x)) = addTies $ getLilypond d x- where- addTies | ta && tb = id . Lilypond.beginTie- | tb = Lilypond.beginTie- | ta = id- | otherwise = id+-- | A staff is a sequential composition of bars.+data LyStaff a = LyStaff { getLyStaff :: (StaffInfo, [LyBar a]) }+ deriving (Functor, Eq, Show) -instance HasLilypond a => HasLilypond (DynamicT a) where- getLilypond d (DynamicT (((Any ec,Any ed),Option l,(Any bc,Any bd)), a)) = notate $ getLilypond d a- where- notate x = nec . ned . nl . nbc . nbd $ x- nec = if ec then Lilypond.endCresc else id- ned = if ed then Lilypond.endDim else id- nbc = if bc then Lilypond.beginCresc else id- nbd = if bd then Lilypond.beginDim else id- nl = case l of- Nothing -> id- Just (First lvl) -> Lilypond.addDynamics (fromDynamics (DynamicsL (Just lvl, Nothing)))+-- | A bar is a sequential composition of chords/notes/rests.+data LyBar a = LyBar { getLyBar :: (BarInfo, Rhythm a) } + deriving (Functor, Eq, Show) -instance HasLilypond a => HasLilypond (ArticulationT a) where- getLilypond d (ArticulationT (((Any es, Any us, Any bs), (Sum al, Sum sl)), a)) = notate $ getLilypond d a- where- notate = nes . nal . nsl . nbs- nes = if es then Lilypond.endSlur else id- nal = case al of- 0 -> id- 1 -> Lilypond.addAccent- 2 -> Lilypond.addMarcato- nsl = case sl of- (-2) -> Lilypond.addTenuto- (-1) -> Lilypond.addPortato- 0 -> id- 1 -> Lilypond.addStaccato- 2 -> Lilypond.addStaccatissimo- nbs = if bs then Lilypond.beginSlur else id+-- | Context passed to the note export.+-- Includes duration and note/rest distinction.+data LyContext a = LyContext Duration (Maybe a)+ deriving (Functor, Foldable, Traversable, Eq, Show) -instance HasLilypond a => HasLilypond (TremoloT a) where- getLilypond d (TremoloT (Sum 0, x)) = getLilypond d x- getLilypond d (TremoloT (Sum n, x)) = notate $ getLilypond newDur x+{-+TODO move+instance Monoid Lilypond.Music where+ mempty = pcatLy []+ mappend x y = pcatLy [x,y]+-}++type LyMusic = Lilypond.Music++instance HasBackend Lilypond where+ type BackendScore Lilypond = LyScore+ type BackendContext Lilypond = LyContext+ type BackendNote Lilypond = LyMusic+ type BackendMusic Lilypond = LyMusic++ finalizeExport _ = finalizeScore+ where+ finalizeScore :: LyScore LyMusic -> Lilypond.Music+ finalizeScore (LyScore (info, x)) + = pcatLy + . map finalizeStaff $ x where- scale = 2^n- newDur = (d `min` (1/4)) / scale- repeats = d / newDur- notate = Lilypond.Tremolo (round repeats)+ extra = id -instance HasLilypond a => HasLilypond (TextT a) where- getLilypond d (TextT (s,x)) = notate s $ getLilypond d x+ -- TODO finalizeStaffGroup++ finalizeStaff :: LyStaff LyMusic -> LyMusic+ finalizeStaff (LyStaff (info, x)) + = addStaff + . addPartName (staffName info) + . addClef (staffClef info)+ . scatLy + . map finalizeBar $ x where- notate ts = foldr (.) id (fmap Lilypond.addText ts)+ addStaff = Lilypond.New "Staff" Nothing+ addClef c x = scatLy [Lilypond.Clef c, x]+ addPartName partName xs = scatLy [longName, shortName, xs]+ where+ longName = Lilypond.Set "Staff.instrumentName" (Lilypond.toValue partName)+ shortName = Lilypond.Set "Staff.shortInstrumentName" (Lilypond.toValue partName) -instance HasLilypond a => HasLilypond (HarmonicT a) where- getLilypond d (HarmonicT ((view _Wrapped' -> isNat, view _Wrapped' -> n),x)) = notate isNat n $ getLilypond d x+ finalizeBar :: LyBar LyMusic -> LyMusic+ finalizeBar (LyBar (BarInfo timeSignature, x))+ = maybe id setBarTimeSignature timeSignature + . renderBarMusic $ x where- notate _ 0 = id- notate True n = notateNatural n- notate False n = notateArtificial n+ -- TODO key signatures+ -- TODO rehearsal marks+ -- TODO bar number change+ -- TODO compound time signatures+ setBarTimeSignature (getTimeSignature -> (ms, n)) x = scatLy [Lilypond.Time (sum ms) n, x]+ + renderBarMusic :: Rhythm LyMusic -> LyMusic+ renderBarMusic = go+ where+ go (Beat d x) = Lilypond.removeSingleChords x+ go (Dotted n (Beat d x)) = Lilypond.removeSingleChords x+ go (Group rs) = scatLy $ map renderBarMusic rs+ go (Tuplet m r) = Lilypond.Times (realToFrac m) (renderBarMusic r)+ where+ (a,b) = fromIntegral *** fromIntegral $ unRatio $ realToFrac m - notateNatural n = Lilypond.addFlageolet -- addOpen?+-- TODO move+second f (x, y) = (x, f y) - notateArtificial n = id -- TODO+type HasArticulation3 c d e = (+ HasArticulation' c,+ HasArticulation c d,+ HasArticulation d e,+ HasArticulation c e+ ) -instance HasLilypond a => HasLilypond (SlideT a) where- getLilypond d (SlideT (((eg,es),(bg,bs)),a)) = notate $ getLilypond d a- where- notate = if view _Wrapped' bg || view _Wrapped' bs then Lilypond.beginGlissando else id+type HasArticulationNotation a b c = (+ HasArticulation3 a b c,+ Articulation b ~ Ctxt (Articulation a),+ Articulation c ~ ArticulationNotation,+ -- TODO generalize+ Articulation a ~ (Sum Double, Sum Double)+ ) -instance HasLilypond a => HasLilypond (ClefT a) where- -- TODO consolidate- getLilypondWithPrefix d (ClefT (c, a)) = (notate c, getLilypond d a)++instance ( + HasDynamicNotation a b c,+ HasArticulationNotation c d e,+ Part e ~ Part c,+ HasOrdPart a, + Transformable a, + Semigroup a,+ Tiable e,+ HasOrdPart c, Show (Part c), HasLilypondInstrument (Part c)+ )+ => HasBackendScore Lilypond (Score a) where+ type BackendScoreEvent Lilypond (Score a) = SetArticulation ArticulationNotation (SetDynamic DynamicNotation a)+ exportScore b score = LyScore + . (ScoreInfo,)+ . map (uncurry $ exportPart timeSignatureMarks barDurations)+ . map (second (over articulations notateArticulation)) + . map (second (preserveMeta addArtCon))+ . map (second (over dynamics notateDynamic)) + . map (second (preserveMeta addDynCon))+ . map (second (preserveMeta simultaneous)) + . extractParts'+ $ score+ where+ (timeSignatureMarks, barDurations) = extractTimeSignatures score +++ -- | Export a score as a single part. Overlapping notes will cause an error.+ exportPart :: (+ Show (Part a),+ HasLilypondInstrument (Part a),+ Tiable a+ ) + => [Maybe TimeSignature] + -> [Duration] + -> Part a + -> Score a + -> LyStaff (LyContext a)++ exportStaff :: Tiable a + => [Maybe TimeSignature] + -> [Duration] + -> String -- ^ name+ -> Int -- ^ clef, as per Music.Parts+ -> MVoice a + -> LyStaff (LyContext a)++ exportBar :: Tiable a + => Maybe TimeSignature + -> MVoice a + -> LyBar (LyContext a)++ quantizeBar :: Tiable a + => MVoice a + -> Rhythm (LyContext a)++ exportPart timeSignatureMarks barDurations part+ = exportStaff timeSignatureMarks barDurations (show part) (getLilypondClef part)+ . view singleMVoice++ exportStaff timeSignatures barDurations name clefId + = LyStaff + . addStaffInfo+ . zipWith exportBar timeSignatures + . splitIntoBars barDurations where- notate c = case fmap getLast $ getOption c of- Nothing -> id- Just c -> \x -> Lilypond.Sequential [addClef c, x]- getLilypond d (ClefT (c, a)) = notate c $ getLilypond d a+ clef = case clefId of+ 0 -> Lilypond.Treble+ 1 -> Lilypond.Alto+ 2 -> Lilypond.Bass+ addStaffInfo = (,) $ StaffInfo { staffName = name, staffClef = clef }+ splitIntoBars = splitTiesVoiceAt++ exportBar timeSignature+ = LyBar + . addBarInfo + . quantizeBar+ where+ addBarInfo = (,) $ BarInfo timeSignature++ quantizeBar = mapWithDur LyContext . rewrite . handleErrors . quantize . view eventsV where- notate c = case fmap getLast $ getOption c of- Nothing -> id- Just c -> \x -> Lilypond.Sequential [addClef c, x]+ -- FIXME propagate quantization errors+ handleErrors (Left e) = error $ "Quantization failed: " ++ e+ handleErrors (Right x) = x -instance HasLilypond a => HasLilypond (Behavior a) where- getLilypond d = getLilypond d . (? 0)+-- TODO move+addArtCon :: (+ HasPhrases s t a b, HasArticulation a a, HasArticulation a b, + Articulation a ~ d, Articulation b ~ Ctxt d+ ) => s -> t+addArtCon = over (phrases.varticulation) withContext+varticulation = lens (fmap $ view articulation) (flip $ zipVoiceWithNoScale (set articulation)) --- TODO-addClef GClef = Lilypond.Clef Lilypond.Treble-addClef CClef = Lilypond.Clef Lilypond.Alto-addClef FClef = Lilypond.Clef Lilypond.Bass +{-+-- TODO customize and remove extraction-related constraints+instance (+ HasDynamicNotation a b c,+ HasOrdPart a, Transformable a, Semigroup a,+ HasOrdPart c, Show (Part c), HasLilypondInstrument (Part c), Tiable c+ )+ => HasBackendScore Lilypond (Voice a) where+ type BackendScoreEvent Lilypond (Voice a) = SetDynamic DynamicNotation a+ exportScore b = exportScore b . voiceToScore+-} -pcatLilypond :: [Lilypond] -> Lilypond-pcatLilypond = pcatLilypond' False+-------------------------------------------------------------------------------- -pcatLilypond' :: Bool -> [Lilypond] -> Lilypond-pcatLilypond' p = foldr Lilypond.simultaneous e+{-+ Note:+ We want all note transformers to be applicative morphisms, i.e.+ + notate (pure x) = pure (notate x)++ Specifically+ notate (mempty,x) = id . notate x++ Note:+ We use these idioms:+ exportNote b = exportNote b . fmap extract+ exportNote b = uncurry notate . fmap (exportNote b) . getTieT . sequenceA++ The latter looks a lot like cotraverse. Generalization?+ + +-}++instance HasBackendNote Lilypond a => HasBackendNote Lilypond [a] where+ exportNote = exportChord++instance HasBackendNote Lilypond Integer where+ -- TODO can we get rid of exportChord alltogether and just use LyContext?+ exportNote _ (LyContext d Nothing) = (^*realToFrac (4*d)) Lilypond.rest+ exportNote _ (LyContext d (Just x)) = (^*realToFrac (4*d)) $ Lilypond.note $ spellLy x++ exportChord _ (LyContext d Nothing) = (^*realToFrac (4*d)) Lilypond.rest+ exportChord _ (LyContext d (Just xs)) = (^*realToFrac (4*d)) $ Lilypond.chord $ fmap spellLy xs++instance HasBackendNote Lilypond Int where+ exportNote b = exportNote b . fmap toInteger+ exportChord b = exportChord b . fmap (fmap toInteger)++instance HasBackendNote Lilypond Float where+ exportNote b = exportNote b . fmap (toInteger . round)+ exportChord b = exportChord b . fmap (fmap (toInteger . round))++instance HasBackendNote Lilypond Double where+ exportNote b = exportNote b . fmap (toInteger . round)+ exportChord b = exportChord b . fmap (fmap (toInteger . round))++instance Integral a => HasBackendNote Lilypond (Ratio a) where+ exportNote b = exportNote b . fmap (toInteger . round)++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (Behavior a) where+ exportNote b = exportNote b . fmap (! 0)+ exportChord b = exportChord b . fmap (fmap (! 0))++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (Sum a) where+ exportNote b = exportNote b . fmap getSum++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (Product a) where+ exportNote b = exportNote b . fmap getProduct++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (PartT n a) where+ -- Part structure is handled by HasMidiBackendScore instances, so this is just an identity+ exportNote b = exportNote b . fmap extract+ exportChord b = exportChord b . fmap (fmap extract)++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (DynamicT DynamicNotation a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getDynamicT . sequenceA where- e = Lilypond.Simultaneous p []+ notate :: DynamicNotation -> LyMusic -> LyMusic+ notate (DynamicNotation (crescDims, level)) + = rcomposed (fmap notateCrescDim crescDims) + . notateLevel level -scatLilypond :: [Lilypond] -> Lilypond-scatLilypond = foldr Lilypond.sequential e+ notateCrescDim crescDims = case crescDims of+ NoCrescDim -> id+ BeginCresc -> Lilypond.beginCresc+ EndCresc -> Lilypond.endCresc+ BeginDim -> Lilypond.beginDim+ EndDim -> Lilypond.endDim++ -- TODO these literals are not so nice...+ notateLevel showLevel = case showLevel of+ Nothing -> id+ Just lvl -> Lilypond.addDynamics (fromDynamics (DynamicsL (Just (fixLevel . realToFrac $ lvl), Nothing)))+ + fixLevel :: Double -> Double+ fixLevel x = fromIntegral (round (x - 0.5)) + 0.5++ -- Use rcomposed as notateDynamic returns "mark" order, not application order+ rcomposed = composed . reverse++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (ArticulationT ArticulationNotation a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getArticulationT . sequenceA where- e = Lilypond.Sequential []+ notate :: ArticulationNotation -> LyMusic -> LyMusic+ notate (ArticulationNotation (slurs, marks))+ = rcomposed (fmap notateMark marks) + . rcomposed (fmap notateSlur slurs) + + notateMark mark = case mark of+ NoMark -> id+ Staccato -> Lilypond.addStaccato+ MoltoStaccato -> Lilypond.addStaccatissimo+ Marcato -> Lilypond.addMarcato+ Accent -> Lilypond.addAccent+ Tenuto -> Lilypond.addTenuto + notateSlur slurs = case slurs of+ NoSlur -> id+ BeginSlur -> Lilypond.beginSlur+ EndSlur -> Lilypond.endSlur + -- Use rcomposed as notateDynamic returns "mark" order, not application order+ rcomposed = composed . reverse+ +instance HasBackendNote Lilypond a => HasBackendNote Lilypond (ColorT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getColorT . sequenceA+ where+ -- TODO This syntax will change in future Lilypond versions+ -- TODO handle any color+ notate (Option Nothing) = id+ notate (Option (Just (Last color))) = \x -> Lilypond.Sequential [+ Lilypond.Override "NoteHead#' color" + (Lilypond.toLiteralValue $ "#" ++ colorName color),+ x,+ Lilypond.Revert "NoteHead#' color"+ ]++ colorName c+ | c == Color.black = "black"+ | c == Color.red = "red"+ | c == Color.blue = "blue"+ | otherwise = error "Lilypond backend: Unkown color"++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (TremoloT a) where+ -- TODO can this instance use the new shorter idiom?+ exportNote b (LyContext d x) =+ fst (notate x d) $ exportNote b $ LyContext (snd $ notate x d) (fmap extract x)+ where+ notate Nothing d = (id, d)+ notate (Just (TremoloT (Couple (Max 0, _)))) d = (id, d)+ notate (Just (TremoloT (Couple (Max n, _)))) d = let+ scale = 2^n+ newDur = (d `min` (1/4)) / scale+ repeats = d / newDur+ in (Lilypond.Tremolo (round repeats), newDur) ++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (TextT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getTextT . sequenceA+ where+ notate texts = composed (fmap Lilypond.addText texts)++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (HarmonicT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getHarmonicT . sequenceA+ where+ notate (Any isNat, Sum n) = case (isNat, n) of+ (_, 0) -> id+ (True, n) -> notateNatural n+ (False, n) -> notateArtificial n+ notateNatural n = Lilypond.addFlageolet -- addOpen?+ notateArtificial n = id -- TODO++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (SlideT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getSlideT . sequenceA+ where+ notate ((Any eg, Any es),(Any bg, Any bs))+ | bg = Lilypond.beginGlissando+ | bs = Lilypond.beginGlissando+ | otherwise = id++instance HasBackendNote Lilypond a => HasBackendNote Lilypond (TieT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getTieT . sequenceA+ where+ notate (Any ta, Any tb)+ | ta && tb = Lilypond.beginTie+ | tb = Lilypond.beginTie+ | ta = id+ | otherwise = id+ -- |+-- Constraint for types that has a Lilypond representation.+--+type HasLilypond a = (HasBackendNote Lilypond (BackendScoreEvent Lilypond a), HasBackendScore Lilypond a)++-- |+-- Convert a score to a Lilypond string.+--+toLilypondString :: HasLilypond a => a -> String+toLilypondString = show . Pretty.pretty . toLilypond++-- |+-- Convert a score to a Lilypond representation.+--+toLilypond :: HasLilypond a => a -> Lilypond.Music+toLilypond = export (undefined::Lilypond)+++-- | -- Convert a score to a Lilypond representaiton and print it on the standard output. ---showLilypond :: (HasLilypond a, HasPart' a, Semigroup a) => Score a -> IO ()+showLilypond :: HasLilypond a => a -> IO () showLilypond = putStrLn . toLilypondString -- | -- Convert a score to a Lilypond representation and write to a file. ---writeLilypond :: (HasLilypond a, HasPart' a, Semigroup a) => FilePath -> Score a -> IO ()+writeLilypond :: HasLilypond a => FilePath -> a -> IO () writeLilypond = writeLilypond' def data LilypondOptions- = Inline- | Score+ = LyInlineFormat+ | LyScoreFormat+ instance Default LilypondOptions where- def = Inline+ def = LyInlineFormat -- | -- Convert a score to a Lilypond representation and write to a file. ---writeLilypond' :: (HasLilypond a, HasPart' a, Semigroup a) => LilypondOptions -> FilePath -> Score a -> IO ()+writeLilypond' :: HasLilypond a => LilypondOptions -> FilePath -> a -> IO () writeLilypond' options path sc = writeFile path $ (lyFilePrefix ++) $ toLilypondString sc- where- title = fromMaybe "" $ flip getTitleAt 0 $ metaAtStart sc- composer = fromMaybe "" $ flip getAttribution "composer" $ metaAtStart sc+ where+ -- title = fromMaybe "" $ flip getTitleAt 0 $ metaAtStart sc+ -- composer = fromMaybe "" $ flip getAttribution "composer" $ metaAtStart sc+ title = ""+ composer = ""+ -- TODO generalize metaAtStart! - lyFilePrefix = case options of- Inline -> lyInlinePrefix- Score -> lyScorePrefix+ lyFilePrefix = case options of+ LyInlineFormat -> lyInlinePrefix+ LyScoreFormat -> lyScorePrefix - lyInlinePrefix = mempty ++- "%%% Generated by music-score %%%\n" ++- "\\include \"lilypond-book-preamble.ly\"\n" ++- "\\paper {\n" ++- " #(define dump-extents #t)\n" ++- "\n" ++- " indent = 0\\mm\n" ++- " line-width = 210\\mm - 2.0 * 0.4\\in\n" ++- " ragged-right = ##t\n" ++- " force-assignment = #\"\"\n" ++- " line-width = #(- line-width (* mm 3.000000))\n" ++- "}\n" ++- "\\header {\n" ++- " title = \"" ++ title ++ "\"\n" ++- " composer = \"" ++ composer ++ "\"\n" ++- "}\n" ++- "\\layout {\n" ++- "}" ++- "\n\n"+ lyInlinePrefix = mempty +++ "%%% Generated by music-score %%%\n" +++ "\\include \"lilypond-book-preamble.ly\"\n" +++ "\\paper {\n" +++ " #(define dump-extents #t)\n" +++ "\n" +++ " indent = 0\\mm\n" +++ " line-width = 210\\mm - 2.0 * 0.4\\in\n" +++ " ragged-right = ##t\n" +++ " force-assignment = #\"\"\n" +++ " line-width = #(- line-width (* mm 3.000000))\n" +++ "}\n" +++ "\\header {\n" +++ " title = \"" ++ title ++ "\"\n" +++ " composer = \"" ++ composer ++ "\"\n" +++ "}\n" +++ "\\layout {\n" +++ "}" +++ "\n\n" - lyScorePrefix = mempty ++- "\\paper {" ++- " indent = 0\\mm" ++- " line-width = 210\\mm - 2.0 * 0.4\\in" ++- "}" ++- "\\header {\n" ++- " title = \"" ++ title ++ "\"\n" ++- " composer = \"" ++ composer ++ "\"\n" ++- "}\n" ++- "\\layout {" ++- "}" ++- "\n\n"+ lyScorePrefix = mempty +++ "\\paper {" +++ " indent = 0\\mm" +++ " line-width = 210\\mm - 2.0 * 0.4\\in" +++ "}" +++ "\\header {\n" +++ " title = \"" ++ title ++ "\"\n" +++ " composer = \"" ++ composer ++ "\"\n" +++ "}\n" +++ "\\layout {" +++ "}" +++ "\n\n" -- | -- Typeset a score using Lilypond and open it. ---openLilypond :: (HasLilypond a, HasPart' a, Semigroup a) => Score a -> IO ()+-- (This is simple wrapper around 'writeLilypond' that may not work well on all platforms.)+--+openLilypond :: HasLilypond a => a -> IO () openLilypond = openLilypond' def -openLilypond' :: (HasLilypond a, HasPart' a, Semigroup a) => LilypondOptions -> Score a -> IO ()+-- |+-- Typeset a score using Lilypond and open it.+--+-- (This is simple wrapper around 'writeLilypond' that may not work well on all platforms.)+--+openLilypond' :: HasLilypond a => LilypondOptions -> a -> IO () openLilypond' options sc = do- writeLilypond' options "test.ly" sc- runLilypond- cleanLilypond- openLilypond''+ writeLilypond' options "test.ly" sc+ runLilypond >> cleanLilypond >> runOpen+ where + runLilypond = void $ runCommand + "lilypond -f pdf test.ly" >>= waitForProcess+ cleanLilypond = void $ runCommand + "rm -f test-*.tex test-*.texi test-*.count test-*.eps test-*.pdf test.eps"+ runOpen = void $ runCommand + $ openCommand ++ " test.pdf" -runLilypond = void $ runCommand "lilypond -f pdf test.ly" >>= waitForProcess-cleanLilypond = void $ runCommand "rm -f test-*.tex test-*.texi test-*.count test-*.eps test-*.pdf test.eps"-openLilypond'' = void $ runCommand "open test.pdf" --- |--- Convert a score to a Lilypond string.----toLilypondString :: (HasLilypond a, HasPart' a, Semigroup a) => Score a -> String-toLilypondString = show . Pretty.pretty . toLilypond --- |--- Convert a score to a Lilypond representation.----toLilypond :: (HasLilypond a, HasPart' a, Semigroup a) => Score a -> Lilypond-toLilypond sc =- -- Score structure- pcatLilypond . fmap (- addStaff . scatLilypond . uncurry addPartName - -- Main notation pipeline- . second (voiceToLilypond barTimeSigs barDurations . scoreToVoice . simultaneous) - -- Meta-event expansion- . uncurry addClefs- ) - . extractParts' $ sc - where- addClefT :: a -> ClefT a- addClefT = return - addClefs p = (,) p . setClef . fmap addClefT- setClef = withClef def $ \c x -> applyClef c x where def = GClef -- TODO use part default - timeSigs = getTimeSignatures (time 4 4) sc -- 4/4 is default- timeSigsV = fmap swap $ (^. from voice) $ mergeEqual $ reactiveToVoice' (start <-> offset sc) timeSigs - -- Despite mergeEqual above we need retainUpdates here to prevent redundant repetition of time signatures- barTimeSigs = retainUpdates $ getBarTimeSignatures $ timeSigsV- barDurations = getBarDurations $ timeSigsV - -- getTimeSignatures def = fmap (fromMaybe def . unOptionFirst) . runMeta (Nothing::Maybe Int) . getScoreMeta- -- getTimeSignatureChanges def = updates . fmap (fromMaybe def . unOptionFirst) . runMeta (Nothing::Maybe Int) . getScoreMeta - addStaff = Lilypond.New "Staff" Nothing- addPartName partName x = Lilypond.Set "Staff.instrumentName" (Lilypond.toValue $ show partName)- : Lilypond.Set "Staff.shortInstrumentName" (Lilypond.toValue $ show partName)- : x -mergeBars :: [Lilypond] -> Lilypond-mergeBars [x] = x-mergeBars _ = error "mergeBars: Not supported" --- |--- Convert a voice score to a list of bars.----voiceToLilypond :: HasLilypond a => [Maybe TimeSignature] -> [Duration] -> Voice (Maybe a) -> [Lilypond]-voiceToLilypond barTimeSigs barDurations = zipWith setBarTimeSig barTimeSigs . fmap barToLilypond . voiceToBars' barDurations------ This is where notation of a single voice takes place--- * voiceToBars is generic for most notations outputs: it handles bar splitting and ties--- * barToLilypond is specific: it handles quantization and notation---- where- -- FIXME compounds- setBarTimeSig Nothing x = x- setBarTimeSig (Just (getTimeSignature -> (m:_, n))) x = scatLilypond [Lilypond.Time m n, x] -barToLilypond :: HasLilypond a => [(Duration, Maybe a)] -> Lilypond-barToLilypond bar = case (fmap rewrite . quantize) bar of- Left e -> error $ "barToLilypond: Could not quantize this bar: " ++ show e- Right rh -> rhythmToLilypond rh -rhythmToLilypond = uncurry ($) . rhythmToLilypond2 --- rhythmToLilypond :: HasLilypond a => Rhythm (Maybe a) -> Lilypond--- rhythmToLilypond (Beat d x) = noteRestToLilypond d x--- rhythmToLilypond (Dotted n (Beat d x)) = noteRestToLilypond (dotMod n * d) x--- rhythmToLilypond (Group rs) = scatLilypond $ map rhythmToLilypond rs--- rhythmToLilypond (Tuplet m r) = Lilypond.Times (realToFrac m) (rhythmToLilypond r)--- where (a,b) = fromIntegral *** fromIntegral $ unRatio $ realToFrac m------ noteRestToLilypond :: HasLilypond a => Duration -> Maybe a -> Lilypond--- noteRestToLilypond d Nothing = Lilypond.rest^*(realToFrac d*4)--- noteRestToLilypond d (Just p) = Lilypond.removeSingleChords $ getLilypond d p -rhythmToLilypond2 :: HasLilypond a => Rhythm (Maybe a) -> (Lilypond -> Lilypond, Lilypond)-rhythmToLilypond2 (Beat d x) = noteRestToLilypond2 d x-rhythmToLilypond2 (Dotted n (Beat d x)) = noteRestToLilypond2 (dotMod n * d) x --- TODO propagate-rhythmToLilypond2 (Group rs) = first (maybe id id) $ second scatLilypond $ extract1 $ map rhythmToLilypond2 $ rs -rhythmToLilypond2 (Tuplet m r) = second (Lilypond.Times (realToFrac m)) $ (rhythmToLilypond2 r)- where (a,b) = fromIntegral *** fromIntegral $ unRatio $ realToFrac m -noteRestToLilypond2 :: HasLilypond a => Duration -> Maybe a -> (Lilypond -> Lilypond, Lilypond)-noteRestToLilypond2 d Nothing = ( id, Lilypond.rest^*(realToFrac d*4) )-noteRestToLilypond2 d (Just p) = second Lilypond.removeSingleChords $ getLilypondWithPrefix d p --- extract first value of type b-extract1 :: [(b, a)] -> (Maybe b, [a])-extract1 [] = (Nothing, [])-extract1 ((p,x):xs) = (Just p, x : fmap snd xs) -spellLilypond :: Integer -> Lilypond.Note-spellLilypond a = Lilypond.NotePitch (spellLilypond' a) Nothing -spellLilypond' :: Integer -> Lilypond.Pitch-spellLilypond' p = Lilypond.Pitch (- toEnum $ fromIntegral pc,- fromIntegral alt,- fromIntegral oct- )- where (pc,alt,oct) = spellPitch (p + 72) +++++-- Internal stuff+-- TODO This function is a workaround+-- Whenever it is used, we should make the original function preserve meta instead+preserveMeta :: (HasMeta a, HasMeta b) => (a -> b) -> a -> b+preserveMeta f x = let m = view meta x in set meta m (f x)++pcatLy :: [Lilypond.Music] -> Lilypond.Music+pcatLy = pcatLy' False++pcatLy' :: Bool -> [Lilypond.Music] -> Lilypond.Music+pcatLy' p = foldr Lilypond.simultaneous (Lilypond.Simultaneous p [])++scatLy :: [Lilypond.Music] -> Lilypond.Music+scatLy = foldr Lilypond.sequential (Lilypond.Sequential [])++spellLy :: Integer -> Lilypond.Note+spellLy a = Lilypond.NotePitch (spellLy' a) Nothing++spellLy' :: Integer -> Lilypond.Pitch+spellLy' p = Lilypond.Pitch (+ toEnum $ fromIntegral pc,+ fromIntegral alt,+ fromIntegral oct+ )+ where (pc,alt,oct) = spellPitch (p + 72)+-- End internal
src/Music/Score/Export/Midi.hs view
@@ -1,20 +1,23 @@-+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -25,221 +28,281 @@ ------------------------------------------------------------------------------------- module Music.Score.Export.Midi (- HasMidi(..),- HasMidiPart,- HasMidiProgram(..),- toMidi,- toMidiTrack,- writeMidi,- -- playMidi,- playMidiIO,+ -- * Midi backend+ HasMidiProgram(..),+ Midi,+ HasMidi,+ toMidi,+ writeMidi,+ showMidi,+ openMidi, ) where -import Prelude hiding (concat, concatMap, foldl,- foldr, mapM, maximum, minimum, sum)-+import Music.Dynamics.Literal+import Music.Pitch.Literal+import qualified Codec.Midi as Midi+import Control.Arrow ((***))+import Control.Comonad (Comonad (..), extract) import Control.Applicative-import Control.Arrow-import Control.Lens hiding ((|>))-import Control.Monad hiding (mapM)-import Control.Monad.Plus-import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Basis-import Data.Either-import Data.Foldable-import Data.Function (on)+import Data.Colour.Names as Color+import Data.Default+import Data.Foldable (Foldable)+import qualified Data.Foldable+import Data.Functor.Couple import Data.Maybe-import Data.Ord (comparing) import Data.Ratio-import Data.Semigroup-import Data.String-import Data.Traversable-import Data.Typeable-import Data.VectorSpace+import Data.Traversable (Traversable, sequenceA)+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as MusicXml+import Music.Score.Internal.Export hiding (MVoice)+import Music.Time.Internal.Transform (whilstLD)+import System.Process+import Music.Time.Internal.Quantize+import qualified Text.Pretty as Pretty+import qualified Data.List+import Music.Score.Convert (reactiveToVoice') -- TODO+import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates)+import Music.Score.Export.DynamicNotation+import Data.Semigroup.Instances -import Codec.Midi hiding (Track)--- import Control.Reactive hiding (Event)--- import qualified Control.Reactive as R--- import Control.Reactive.Midi+import Data.Functor.Identity+import Data.Semigroup+import Control.Monad+import Data.VectorSpace hiding (Sum(..))+import Data.AffineSpace+import Control.Lens (over)+import Control.Lens.Operators hiding ((|>)) -import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Articulation-import Music.Score.Chord-import Music.Score.Combinators-import Music.Score.Dynamics-import Music.Score.Export.Common-import Music.Score.Ornaments-import Music.Score.Part-import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score-import Music.Score.Ties-import Music.Score.Track-import Music.Score.Voice-import Music.Time+import Music.Time+import Music.Score.Dynamics+import Music.Score.Articulation+import Music.Score.Part+import Music.Score.Tremolo+import Music.Score.Text+import Music.Score.Harmonics+import Music.Score.Slide+import Music.Score.Color+import Music.Score.Ties+import Music.Score.Meta+import Music.Score.Meta.Tempo+import Music.Score.Export.Backend -import qualified Codec.Midi as Midi-import qualified Data.List as List-import qualified Data.Map as Map-import qualified Music.Lilypond as Lilypond-import qualified Music.MusicXml.Simple as Xml-import qualified Text.Pretty as Pretty --- | Class of types with MIDI-compatible parts.-type HasMidiPart a = (HasPart' a, HasMidiProgram (Part a)) + -- | Class of part types with an associated MIDI program number. class HasMidiProgram a where- getMidiChannel :: a -> Midi.Channel- getMidiProgram :: a -> Midi.Preset- getMidiChannel _ = 0+ getMidiChannel :: a -> Midi.Channel+ getMidiProgram :: a -> Midi.Preset+ getMidiChannel _ = 0 instance HasMidiProgram () where- getMidiProgram _ = 0+ getMidiProgram _ = 0+ instance HasMidiProgram Double where- getMidiProgram = fromIntegral . floor+ getMidiProgram = fromIntegral . floor+ instance HasMidiProgram Float where- getMidiProgram = fromIntegral . floor+ getMidiProgram = fromIntegral . floor+ instance HasMidiProgram Int where- getMidiProgram = id+ getMidiProgram = id+ instance HasMidiProgram Integer where- getMidiProgram = fromIntegral+ getMidiProgram = fromIntegral+ instance (Integral a, HasMidiProgram a) => HasMidiProgram (Ratio a) where- getMidiProgram = fromIntegral . floor+ getMidiProgram = fromIntegral . floor --- |--- Class of types that can be converted to MIDI.------ Numeric types are interpreted as notes with a default velocity, pairs are--- interpreted as @(pitch, velocity)@ pairs.++-- | A token to represent the Midi backend.+data Midi++-- | We do not need to pass any context to the note export.+type MidiContext = Identity++-- | Every note may give rise to a number of messages. We represent this as a score of messages.+type MidiEvent = Score Midi.Message++-- | The MIDI channel allocation is somewhat simplistic.+-- We use a dedicated channel and program number for each instrument (there *will* be colissions).+type MidiInstr = (Midi.Channel, Midi.Preset)++-- | A Midi file consist of a number of tracks.+-- Channel and preset info is passed on from exportScore to finalizeExport using this type. ----- Minimal definition: 'getMidi'. Given 'getMidiScore', 'getMidi' can be implemented--- as @getMidiScore . return@.+-- TODO also pass meta-info etc. ---class HasMidi a where+data MidiScore a = MidiScore [(MidiInstr, Score a)]+ deriving Functor - -- | Convert a value to a MIDI score.- -- Typically, generates an /on/ event using 'note' followed by an optional /off/ event.- getMidi :: a -> Score Midi.Message+instance HasBackend Midi where+ type BackendScore Midi = MidiScore+ type BackendContext Midi = MidiContext+ type BackendNote Midi = MidiEvent+ type BackendMusic Midi = Midi.Midi - -- | Convert a score to a MIDI score.- -- The default definition can be overriden for efficiency.- getMidiScore :: Score a -> Score Midi.Message- getMidiScore = (>>= getMidi)+ finalizeExport _ (MidiScore trs) = let+ controlTrack = [(0, Midi.TempoChange 1000000), (endDelta, Midi.TrackEnd)]+ mainTracks = fmap (uncurry translMidiTrack . fmap join) trs+ in+ Midi.Midi fileType (Midi.TicksPerBeat divisions) (controlTrack : mainTracks) + where+ translMidiTrack :: MidiInstr -> Score Midi.Message -> [(Int, Midi.Message)]+ translMidiTrack (ch, p) = addTrackEnd+ . setProgramChannel ch p+ . scoreToMidiTrack -instance HasMidi (Integer, Integer) where- getMidi (p,v) = mempty- |> return (Midi.NoteOn 0 (fromIntegral $ p + 60) (fromIntegral v))- |> return (Midi.NoteOff 0 (fromIntegral $ p + 60) (fromIntegral v))+ -- Each track needs TrackEnd+ -- We place it a long time after last event just in case (necessary?)+ addTrackEnd :: [(Int, Midi.Message)] -> [(Int, Midi.Message)]+ addTrackEnd = (<> [(endDelta, Midi.TrackEnd)]) -instance HasMidi Midi.Message where getMidi = return-instance HasMidi Int where getMidi = getMidi . toInteger-instance HasMidi Float where getMidi = getMidi . toInteger . round-instance HasMidi Double where getMidi = getMidi . toInteger . round-instance Integral a => HasMidi (Ratio a) where getMidi = getMidi . toInteger . round-instance HasMidi a => HasMidi (Maybe a) where getMidi = getMidiScore . mfromMaybe-instance HasMidi Integer where getMidi x = getMidi (x,100::Integer)+ setProgramChannel :: Midi.Channel -> Midi.Preset -> Midi.Track Midi.Ticks -> Midi.Track Midi.Ticks+ setProgramChannel ch prg = ([(0, Midi.ProgramChange ch prg)] <>) . fmap (fmap $ setC ch) -instance HasMidi a => HasMidi (PartT n a) where- getMidi (PartT (_,a)) = getMidi a-instance HasMidi a => HasMidi (ChordT a) where- getMidi = pcat . fmap getMidi . getChordT-instance HasMidi a => HasMidi (TieT a) where- getMidi (TieT (_,a)) = getMidi a-instance HasMidi a => HasMidi (DynamicT a) where- getMidi (DynamicT (_,a)) = getMidi a-instance HasMidi a => HasMidi (ArticulationT a) where- getMidi (ArticulationT (_,a)) = getMidi a-instance HasMidi a => HasMidi (TremoloT a) where- getMidi (TremoloT (_,a)) = getMidi a-instance HasMidi a => HasMidi (TextT a) where- getMidi (TextT (_,a)) = getMidi a-instance HasMidi a => HasMidi (HarmonicT a) where- getMidi (HarmonicT (_,a)) = getMidi a-instance HasMidi a => HasMidi (SlideT a) where- getMidi (SlideT (_,a)) = getMidi a+ scoreToMidiTrack :: Score Midi.Message -> Midi.Track Midi.Ticks+ scoreToMidiTrack = fmap (\(t,_,x) -> (round ((t .-. 0) ^* divisions), x)) . toRelative . (^. events) -instance HasMidi a => HasMidi (Behavior a) where- getMidi = getMidi . (? 0)+ -- Hardcoded values for Midi export+ -- We always generate MultiTrack (type 1) files with division 1024+ fileType = Midi.MultiTrack+ divisions = 1024+ endDelta = 10000 --- |--- Convert a score to a MIDI file representation.----toMidi :: forall a . (HasMidiPart a, HasMidi a) => Score a -> Midi.Midi-toMidi score = Midi.Midi fileType divisions' (controlTrack : eventTracks)+instance (HasPart' a, HasMidiProgram (Part a)) => HasBackendScore Midi (Voice a) where+ type BackendScoreEvent Midi (Voice a) = a+ -- exportScore _ xs = MidiScore [((getMidiChannel (xs^?!parts), getMidiProgram (xs^?!parts)), fmap <$> voiceToScore xs)]+ exportScore _ xs = MidiScore [((getMidiChannel (xs^?!parts), getMidiProgram (xs^?!parts)), fmap Identity $ voiceToScore xs)] where- -- Each track needs TrackEnd- -- We place it long after last event just in case (necessary?)- endDelta = 10000- fileType = Midi.MultiTrack- divisions = 1024- divisions' = Midi.TicksPerBeat divisions- controlTrack = [(0, Midi.TempoChange 1000000), (endDelta, Midi.TrackEnd)]- eventTracks = fmap ((<> [(endDelta, Midi.TrackEnd)]) . uncurry setProgramChannel . second scoreToMTrack)- $ extractParts' score+ voiceToScore :: Voice a -> Score a+ voiceToScore = error "FIXME" - setProgramChannel :: Part a -> Midi.Track Midi.Ticks -> Midi.Track Midi.Ticks- setProgramChannel p = ([(0, Midi.ProgramChange ch prg)] <>) . fmap (fmap (setChannel ch))- where- ch = getMidiChannel p- prg = getMidiProgram p+instance (HasPart' a, Ord (Part a), HasMidiProgram (Part a)) => HasBackendScore Midi (Score a) where+ type BackendScoreEvent Midi (Score a) = a+ exportScore _ xs = MidiScore (map (\(p,sc) -> ((getMidiChannel p, getMidiProgram p), fmap Identity sc)) $ extractParts' $ fixTempo xs)+ where+ -- We actually want to extract *all* tempo changes and transform the score appropriately+ -- For the time being, we assume the whole score has the same tempo+ fixTempo = stretch (tempoToDuration (metaAtStart xs)) - scoreToMTrack :: Score a -> Midi.Track Midi.Ticks- scoreToMTrack = fmap (\(t,_,x) -> (round ((t.-. origin) ^* divisions), x)) . toRelative . (^. events) . getMidiScore+instance HasBackendNote Midi a => HasBackendNote Midi [a] where+ exportNote b ps = mconcat $ map (exportNote b) $ sequenceA ps - -- TODO render voices separately+instance HasBackendNote Midi Int where+ exportNote _ (Identity pv) = mkMidiNote pv --- |--- Convert a score to a track of MIDI messages.----toMidiTrack :: HasMidi a => Score a -> Track Message-toMidiTrack = (^. track) . fmap (\(t,_,m) -> (t, m)) . (^. events) . getMidiScore+instance HasBackendNote Midi Integer where+ exportNote _ (Identity pv) = mkMidiNote (fromIntegral pv) --- |--- Convert a score MIDI and write to a file.----writeMidi :: (HasMidiPart a, HasMidi a) => FilePath -> Score a -> IO ()-writeMidi path sc = Midi.exportFile path (toMidi sc)+instance HasBackendNote Midi Float where+ exportNote b = exportNote b . fmap (toInteger . round)+ exportChord b = exportChord b . fmap (fmap (toInteger . round)) -playMidiIO :: HasMidi a => String -> Score a -> IO ()-playMidiIO = error "playMidiIO: Not implemented"+instance HasBackendNote Midi Double where+ exportNote b = exportNote b . fmap (toInteger . round)+ exportChord b = exportChord b . fmap (fmap (toInteger . round)) -{---- |--- Convert a score to a MIDI event.----playMidi :: HasMidi a => String -> Score a -> R.Event MidiMessage-playMidi dest x = midiOut midiDest $ playback trig (pure $ toTrack $ startAt 0.2 x)- where- -- trig = accumR 0 ((+ 0.005) <$ pulse 0.005)- trig = time- toTrack = fmap (\(t,_,m) -> (t .-. origin, m)) . (^. events) . getMidiScore- midiDest = fromJust $ unsafeGetReactive (findDestination $ pure dest)+instance HasBackendNote Midi a => HasBackendNote Midi (Behavior a) where+ exportNote b = exportNote b . fmap (! 0)+ exportChord b = exportChord b . fmap (fmap (! 0)) ++instance HasBackendNote Midi a => HasBackendNote Midi (DynamicT (Sum Double) a) where+ -- TODO+ -- We have not standarized dynamic levels+ -- Assume for now -6.5 to 6.5 where (-3.5 is ppp, -0.5 is mp, 0.5 is mf, 3.5 is fff etc)+ exportNote b (Identity (DynamicT (Sum d, x))) = setV (dynLevel d) <$> exportNote b (Identity x)+++dynLevel :: Double -> Midi.Velocity+dynLevel x = round $ (\x -> x * 58.5 + 64) $ f $ inRange (-1,1) (x/3.5)+ where+ f = id+ -- f x = (x^3)+ inRange (m,n) x = (m `max` x) `min` n+ ++-- instance (Transformable d, HasBackendNote Midi (DynamicT d a)) => HasBackendNote Midi (DynamicT (Product d) a) where+-- exportNote b = exportNote b . fmap (over dynamic getProduct)+-- +-- instance (Transformable d, HasBackendNote Midi (DynamicT d a)) => HasBackendNote Midi (DynamicT (Sum d) a) where+-- exportNote b = exportNote b . fmap (over dynamic getSum)+++instance HasBackendNote Midi a => HasBackendNote Midi (ArticulationT b a) where+ exportNote b (Identity (ArticulationT (_, x))) = exportNote b (Identity x)++instance HasBackendNote Midi a => HasBackendNote Midi (PartT n a) where+ -- Part structure is handled by HasMidiBackendScore instances, so this is just an identity+ exportNote b = exportNote b . fmap extract++instance HasBackendNote Midi a => HasBackendNote Midi (TremoloT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote Midi a => HasBackendNote Midi (TextT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote Midi a => HasBackendNote Midi (HarmonicT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote Midi a => HasBackendNote Midi (SlideT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote Midi a => HasBackendNote Midi (TieT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote Midi a => HasBackendNote Midi (ColorT a) where+ exportNote b = exportNote b . fmap extract++mkMidiNote :: Int -> Score Midi.Message+mkMidiNote p = mempty+ |> pure (Midi.NoteOn 0 (fromIntegral $ p + 60) 64)+ |> pure (Midi.NoteOff 0 (fromIntegral $ p + 60) 64)++setV :: Midi.Velocity -> Midi.Message -> Midi.Message+setV v = go+ where+ go (Midi.NoteOff c k _) = Midi.NoteOff c k v+ go (Midi.NoteOn c k _) = Midi.NoteOn c k v+ go (Midi.KeyPressure c k _) = Midi.KeyPressure c k v+ go (Midi.ControlChange c n v) = Midi.ControlChange c n v+ go (Midi.ProgramChange c p) = Midi.ProgramChange c p+ go (Midi.ChannelPressure c p) = Midi.ChannelPressure c p+ go (Midi.PitchWheel c w) = Midi.PitchWheel c w+ go (Midi.ChannelPrefix c) = Midi.ChannelPrefix c++setC :: Midi.Channel -> Midi.Message -> Midi.Message+setC c = go+ where+ go (Midi.NoteOff _ k v) = Midi.NoteOff c k v+ go (Midi.NoteOn _ k v) = Midi.NoteOn c k v+ go (Midi.KeyPressure _ k v) = Midi.KeyPressure c k v+ go (Midi.ControlChange _ n v) = Midi.ControlChange c n v+ go (Midi.ProgramChange _ p) = Midi.ProgramChange c p+ go (Midi.ChannelPressure _ p) = Midi.ChannelPressure c p+ go (Midi.PitchWheel _ w) = Midi.PitchWheel c w+ go (Midi.ChannelPrefix _) = Midi.ChannelPrefix c+ -- |--- Convert a score to a MIDI event and run it.+-- Constraint for types that has a MIDI representation. ---playMidiIO :: HasMidi a => String -> Score a -> IO ()-playMidiIO dest = runLoop . playMidi dest--}+type HasMidi a = (HasBackendNote Midi (BackendScoreEvent Midi a), HasBackendScore Midi a) +toMidi :: HasMidi a => a -> Midi.Midi+toMidi = export (undefined::Midi) +writeMidi :: HasMidi a => FilePath -> a -> IO ()+writeMidi path sc = Midi.exportFile path (toMidi sc) -setChannel :: Midi.Channel -> Midi.Message -> Midi.Message-setChannel c = go- where- go (NoteOff _ k v) = NoteOff c k v- go (NoteOn _ k v) = NoteOn c k v- go (KeyPressure _ k v) = KeyPressure c k v- go (ControlChange _ n v) = ControlChange c n v- go (ProgramChange _ p) = ProgramChange c p- go (ChannelPressure _ p) = ChannelPressure c p- go (PitchWheel _ w) = PitchWheel c w- go (ChannelPrefix _) = ChannelPrefix c+showMidi :: HasMidi a => a -> IO ()+showMidi = print . toMidi +openMidi :: HasMidi a => a -> IO ()+openMidi score = do+ writeMidi "test.mid" score+ void $ runCommand "timidity test.mid" >>= waitForProcess
src/Music/Score/Export/MusicXml.hs view
@@ -1,19 +1,24 @@ +{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -24,325 +29,576 @@ ------------------------------------------------------------------------------------- module Music.Score.Export.MusicXml (- XmlScore,- XmlMusic,- HasMusicXml(..),-- toMusicXml,- toMusicXmlString,-- showMusicXml,- openMusicXml,- writeMusicXml,-) where--import Prelude hiding (concat, concatMap, foldl,- foldr, mapM, maximum, minimum,- sum)+ -- * MusicXml backend+ HasMusicXmlInstrument(..),+ MusicXml,+ XmlContext(..),+ HasMusicXml,+ + -- ** Converting to MusicXml+ toMusicXml,+ toMusicXmlString, + + -- ** MusicXml I/O+ showMusicXml,+ openMusicXml,+ writeMusicXml,+ ) where +import Music.Dynamics.Literal+import Music.Pitch.Literal+import qualified Codec.Midi as Midi+import Control.Arrow ((***))+import Control.Comonad (Comonad (..), extract) import Control.Applicative-import Control.Arrow-import Control.Lens hiding (rewrite)-import Control.Monad hiding (mapM)-import Data.Function (on)+import Data.Colour.Names as Color+import Data.Default+import Data.Foldable (Foldable)+import qualified Data.Foldable+import Data.Functor.Couple import Data.Maybe-import Data.Monoid.WithSemigroup-import Data.Ord (comparing) import Data.Ratio-import Data.Semigroup-import Data.String+import Data.Traversable (Traversable, sequenceA)+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as MusicXml+import Music.Score.Internal.Export hiding (MVoice)+import Music.Time.Internal.Transform (whilstLD) import System.Process+import Music.Time.Internal.Quantize+import qualified Text.Pretty as Pretty+import qualified Data.List+import Music.Score.Convert (reactiveToVoice') -- TODO+import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates)+import Music.Score.Export.DynamicNotation+import Music.Score.Export.ArticulationNotation+import Data.Semigroup.Instances -import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Articulation-import Music.Score.Chord-import Music.Score.Clef-import Music.Score.Combinators-import Music.Score.Convert-import Music.Score.Convert-import Music.Score.Dynamics-import Music.Score.Export.Common-import Music.Score.Instances-import Music.Score.Meta-import Music.Score.Meta.Attribution-import Music.Score.Meta.Clef-import Music.Score.Meta.Time-import Music.Score.Meta.Title-import Music.Score.Ornaments-import Music.Score.Part-import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score-import Music.Score.Ties-import Music.Score.Track-import Music.Score.Util-import Music.Score.Voice-import Music.Time-import Music.Time.Reactive (Reactive, initial)+import Music.Score.Export.Backend -import qualified Codec.Midi as Midi-import qualified Data.List as List-import qualified Data.Map as Map-import qualified Music.Lilypond as Lilypond-import qualified Music.MusicXml.Simple as Xml-import qualified Text.Pretty as Pretty+import Data.Functor.Identity+import Data.Semigroup+import Control.Monad+import Data.VectorSpace hiding (Sum(..))+import Data.AffineSpace+import Control.Lens hiding (rewrite)+-- import Control.Lens.Operators hiding ((|>)) +import Music.Time+import Music.Score.Meta+import Music.Score.Meta.Title+import Music.Score.Meta.Attribution+import Music.Score.Dynamics+import Music.Score.Articulation+import Music.Score.Part+import Music.Score.Tremolo+import Music.Score.Text+import Music.Score.Harmonics+import Music.Score.Slide+import Music.Score.Color+import Music.Score.Ties+import Music.Score.Export.Backend+import Music.Score.Meta.Time+import Music.Score.Meta.Tempo+import Music.Score.Phrases -type XmlScore = Xml.Score-type XmlMusic = Xml.Music --- |--- Class of types that can be converted to MusicXML.+-- | +-- Extract instrument info as per "Music.Part"+-- This is really crude, needs rethinking! ---class Tiable a => HasMusicXml a where- -- |- -- Convert a value to MusicXML.- --- -- Typically, generates a 'XmlMusic' value using 'Xml.note' or 'Xml.chord', and transforms it- -- to add beams, slurs, dynamics, articulation etc.- --- getMusicXml :: Duration -> a -> XmlMusic+class HasMusicXmlInstrument a where+ getMusicXmlClef :: a -> Int - getMusicXmlChord :: Duration -> [a] -> XmlMusic- getMusicXmlChord d = error "getMusicXmlChord: Not implemented" -instance HasMusicXml Int where getMusicXml d = getMusicXml d . toInteger-instance HasMusicXml Float where getMusicXml d = getMusicXml d . toInteger . round-instance HasMusicXml Double where getMusicXml d = getMusicXml d . toInteger . round-instance Integral a => HasMusicXml (Ratio a) where getMusicXml d = getMusicXml d . toInteger . round -instance HasMusicXml Integer where- getMusicXml d = (`Xml.note` realToFrac d) . spellMusicXml . fromIntegral- getMusicXmlChord d = (`Xml.chord` realToFrac d) . fmap (spellMusicXml . fromIntegral) -instance HasMusicXml a => HasMusicXml (ChordT a) where- getMusicXml d = getMusicXmlChord d . getChordT+-- TODO move +deriving instance Show MusicXml.Line+deriving instance Show MusicXml.ClefSign+-- deriving instance Eq MusicXml.PartList+-- deriving instance Show MusicXml.PartList+-- deriving instance Eq MusicXml.PartListElem+-- deriving instance Show MusicXml.PartListElem+-- deriving instance Show MusicXml.GroupBarLines+-- FIXME bogus+instance Eq MusicXml.PartList where+instance Show MusicXml.PartList where -instance HasMusicXml a => HasMusicXml (PartT n a) where- getMusicXml d (PartT (_,x)) = getMusicXml d x -instance HasMusicXml a => HasMusicXml (TieT a) where- getMusicXml d (TieT ((Any ta,Any tb),x)) = addTies $ getMusicXml d x- where- addTies | ta && tb = Xml.endTie . Xml.beginTie- | tb = Xml.beginTie- | ta = Xml.endTie- | otherwise = id -instance HasMusicXml a => HasMusicXml (DynamicT a) where- getMusicXml d (DynamicT (((Any ec,Any ed),Option l,(Any bc,Any bd)), a)) = notate $ getMusicXml d a- where- notate x = nec <> ned <> nl <> nbc <> nbd <> x- nec = if ec then Xml.endCresc else mempty- ned = if ed then Xml.endDim else mempty- nbc = if bc then Xml.beginCresc else mempty- nbd = if bd then Xml.beginDim else mempty- nl = case l of- Nothing -> mempty- Just (First lvl) -> Xml.dynamic (fromDynamics (DynamicsL (Just lvl, Nothing)))+-- | A token to represent the MusicXml backend.+data MusicXml -instance HasMusicXml a => HasMusicXml (ArticulationT a) where- getMusicXml d (ArticulationT (((Any es, Any us, Any bs), (Sum al, Sum sl)), a)) = notate $ getMusicXml d a- where- notate = nes . nal . nsl . nbs- nes = if es then Xml.endSlur else id- nal = case al of- 0 -> id- 1 -> Xml.accent- 2 -> Xml.strongAccent- nsl = case sl of- (-2) -> Xml.tenuto- (-1) -> Xml.tenuto . Xml.staccato- 0 -> id- 1 -> Xml.staccato- 2 -> Xml.staccatissimo- nbs = if bs then Xml.beginSlur else id+data ScoreInfo = ScoreInfo { scoreTitle :: String,+ scoreComposer :: String,+ scorePartList :: MusicXml.PartList,+ scoreTempo :: Tempo+ }+ deriving (Eq, Show) -instance HasMusicXml a => HasMusicXml (TremoloT a) where- getMusicXml d (TremoloT (Sum n,x)) = notate $ getMusicXml d x- where- notate = case n of- 0 -> id- _ -> Xml.tremolo n -instance HasMusicXml a => HasMusicXml (TextT a) where- getMusicXml d (TextT (s,x)) = notate s $ getMusicXml d x- where- notate ts a = mconcat (fmap Xml.text ts) <> a+data StaffInfo = StaffInfo { staffClef :: (MusicXml.ClefSign, MusicXml.Line) } + deriving (Eq, Show) -instance HasMusicXml a => HasMusicXml (HarmonicT a) where- getMusicXml d (HarmonicT ((view _Wrapped' -> isNat, view _Wrapped' -> n),x)) = notate isNat n $ getMusicXml d x- where- notate _ 0 = id- notate True n = notateNatural n- notate False n = notateArtificial n+data BarInfo = BarInfo { x_barTimeSignature :: Maybe TimeSignature } + deriving (Eq, Show) - -- notateNatural n = Xml.harmonic -- openString?- notateNatural n = Xml.setNoteHead Xml.DiamondNoteHead- -- Most programs do not recognize the harmonic tag- -- We set a single diamond notehead instead, which can be manually replaced+-- | Hierachical representation of a MusicXml score.+-- A score is a parallel composition of staves.+data XmlScore a = XmlScore { getXmlScore :: (ScoreInfo, [XmlStaff a]) }+ deriving (Functor, Eq, Show) - notateArtificial n = id -- TODO+-- | A staff is a sequential composition of bars.+data XmlStaff a = XmlStaff { getXmlStaff :: (StaffInfo, [XmlBar a]) }+ deriving (Functor, Eq, Show) +-- | A bar is a sequential composition of chords/notes/rests.+data XmlBar a = XmlBar { getXmlBar :: (BarInfo, Rhythm a) } + deriving (Functor, Eq, Show) - -- notate | n /= 0 = Xml.setNoteHead Xml.DiamondNoteHead- -- TODO adjust pitch etc+-- | Context passed to the note export.+-- Includes duration and note/rest distinction.+data XmlContext a = XmlContext Duration (Maybe a)+ deriving (Functor, Foldable, Traversable, Eq, Show) -instance HasMusicXml a => HasMusicXml (SlideT a) where- getMusicXml d (SlideT (((eg,es),(bg,bs)),a)) = notate $ getMusicXml d a- where- notate = neg . nes . nbg . nbs- neg = if view _Wrapped' eg then Xml.endGliss else id- nes = if view _Wrapped' es then Xml.endSlide else id- nbg = if view _Wrapped' bg then Xml.beginGliss else id- nbs = if view _Wrapped' bs then Xml.beginSlide else id+-- instance Monoid Lilypond.Music where+ -- mempty = pcatXml []+ -- mappend x y = pcatXml [x,y] -instance HasMusicXml a => HasMusicXml (ClefT a) where- getMusicXml d (ClefT (c, a)) = notate $ getMusicXml d a+instance HasBackend MusicXml where+ type BackendScore MusicXml = XmlScore+ type BackendContext MusicXml = XmlContext+ type BackendNote MusicXml = MusicXml.Music+ type BackendMusic MusicXml = MusicXml.Score++ finalizeExport _ = finalizeScore+ where++finalizeScore :: XmlScore MusicXml.Music -> MusicXml.Score+finalizeScore (XmlScore (info, x)) + = MusicXml.fromParts title composer partList + . map (finalizeStaff tempo) $ x+ where+ title = scoreTitle info+ composer = scoreComposer info+ partList = scorePartList info+ tempo = scoreTempo info++-- TODO finalizeStaffGroup++finalizeStaff :: Tempo -> XmlStaff MusicXml.Music -> [MusicXml.Music]+finalizeStaff tempo (XmlStaff (info, x)) + = id + -- Staff name is not added here as MusicXML uses a separate part list+ . addStartInfo+ . addClef (staffClef info)+ . map finalizeBar $ x+ where+ -- TODO name+ -- TODO clef++ -- Both of these stick to the first bar+ -- TODO clean+ addClef :: (MusicXml.ClefSign, MusicXml.Line) -> [MusicXml.Music] -> [MusicXml.Music]+ addClef _ [] = []+ addClef (clefSign, line) (x:xs) = (MusicXml.clef clefSign line <> x):xs++ addStartInfo :: [MusicXml.Music] -> [MusicXml.Music]+ addStartInfo [] = []+ addStartInfo (x:xs) = (startInfo <> x):xs++ startInfo :: MusicXml.Music+ startInfo = mempty+ <> MusicXml.defaultDivisions+ <> MusicXml.defaultKey+ <> MusicXml.metronome (realToFrac nv) (realToFrac bpm)+ -- <> Xml.commonTime+ (nv, bpm) = getTempo tempo+++finalizeBar :: XmlBar MusicXml.Music -> MusicXml.Music+finalizeBar (XmlBar (BarInfo timeSignature, x))+ = maybe id setBarTimeSignature timeSignature + . renderBarMusic $ x+ where+ -- TODO key signatures+ -- TODO rehearsal marks+ -- TODO bar number change+ -- TODO compound time signatures+ setBarTimeSignature (getTimeSignature -> (ms, n)) x = mconcat [MusicXml.time (fromIntegral $ sum ms) (fromIntegral n), x] + +renderBarMusic :: Rhythm MusicXml.Music -> MusicXml.Music+renderBarMusic = go+ where+ go (Beat d x) = setDefaultVoice x+ go (Dotted n (Beat d x)) = setDefaultVoice x+ go (Group rs) = mconcat $ map renderBarMusic rs+ go (Tuplet m r) = MusicXml.tuplet b a (renderBarMusic r)+ where+ (a,b) = fromIntegral *** fromIntegral $ unRatio $ realToFrac m++setDefaultVoice :: MusicXml.Music -> MusicXml.Music+setDefaultVoice = MusicXml.setVoice 1++-- TODO move+second f (x, y) = (x, f y)++instance (+ HasDynamicNotation a b c,+ HasOrdPart a, Transformable a, Semigroup a,+ HasOrdPart c, Tiable c, Show (Part a), HasMusicXmlInstrument (Part a)+ )+ => HasBackendScore MusicXml (Score a) where+ type BackendScoreEvent MusicXml (Score a) = SetDynamic DynamicNotation a+ exportScore b score = XmlScore + . (ScoreInfo title composer partList tempo,)+ . map (uncurry $ exportPart timeSignatureMarks barDurations)+ . map (second (over dynamics notateDynamic)) + . map (second (preserveMeta addDynCon))+ . map (second (preserveMeta simultaneous)) + . extractParts'+ $ score+ where+ title = fromMaybe "" $ flip getTitleAt 0 $ metaAtStart score+ composer = fromMaybe "" $ flip getAttribution "composer" $ metaAtStart score+ partList = MusicXml.partList (fmap show $ allParts score)+ tempo = (metaAtStart score :: Tempo)+ (timeSignatureMarks, barDurations) = extractTimeSignatures score +++ -- | Export a score as a single part. Overlapping notes will cause an error.+ exportPart :: (+ Tiable a,+ HasMusicXmlInstrument (Part a)+ ) + => [Maybe TimeSignature] + -> [Duration] + -> Part a + -> Score a + -> XmlStaff (XmlContext a)++ exportStaff :: Tiable a + => [Maybe TimeSignature] + -> [Duration] + -> Int -- ^ clef, as per Music.Parts+ -> MVoice a + -> XmlStaff (XmlContext a)++ exportBar :: Tiable a + => Maybe TimeSignature + -> MVoice a + -> XmlBar (XmlContext a)++ quantizeBar :: Tiable a + => MVoice a + -> Rhythm (XmlContext a)++ exportPart timeSignatureMarks barDurations part+ = exportStaff timeSignatureMarks barDurations (getMusicXmlClef part)+ . view singleMVoice++ exportStaff timeSignatures barDurations clefId + = XmlStaff + . addStaffInfo+ . zipWith exportBar timeSignatures + . splitIntoBars barDurations+ where + clef = case clefId of+ 0 -> (MusicXml.GClef, 2)+ 1 -> (MusicXml.CClef, 3)+ 2 -> (MusicXml.FClef, 4)+ addStaffInfo = (,) $ StaffInfo { staffClef = clef }+ splitIntoBars = splitTiesVoiceAt++ exportBar timeSignature+ = XmlBar + . addBarInfo + . quantizeBar+ where+ addBarInfo = (,) $ BarInfo timeSignature++ quantizeBar = mapWithDur XmlContext . rewrite . handleErrors . quantize . view eventsV where- notate = case fmap getLast $ getOption c of- Nothing -> id- Just GClef -> (Xml.trebleClef <>)- Just CClef -> (Xml.altoClef <>)- Just FClef -> (Xml.bassClef <>)+ -- FIXME propagate quantization errors+ handleErrors (Left e) = error $ "Quantization failed: " ++ e+ handleErrors (Right x) = x -instance HasMusicXml a => HasMusicXml (Behavior a) where- getMusicXml d = getMusicXml d . (? 0)+-------------------------------------------------------------------------------- +{-+ Note:+ We want all note transformers to be applicative morphisms, i.e.+ + notate (pure x) = pure (notate x) --- |--- Convert a score to MusicXML and write to a file.----writeMusicXml :: (HasMusicXml a, HasPart' a, Semigroup a) => FilePath -> Score a -> IO ()-writeMusicXml path sc = writeFile path (Xml.showXml $ toMusicXml sc)+ Specifically+ notate (mempty,x) = id . notate x --- |--- Convert a score to MusicXML and open it.----openMusicXml :: (HasMusicXml a, HasPart' a, Semigroup a) => Score a -> IO ()-openMusicXml sc = do- writeMusicXml "test.xml" sc- -- FIXME find out which program to use...- void $ rawSystem "open" ["-a", "Sibelius 7", "test.xml"]+ Note:+ We use these idioms:+ exportNote b = exportNote b . fmap extract+ exportNote b = uncurry notate . fmap (exportNote b) . getTieT . sequenceA --- -- |--- -- Convert a score to MusicXML and write to a file.--- ----- writeXmlSingle :: HasMusicXml a => FilePath -> Score a -> IO ()--- writeXmlSingle path sc = writeFile path (Xml.showXml $ toXmlSingle sc)+ The latter looks a lot like cotraverse. Generalization?+ + +-} --- -- |--- -- Convert a score to MusicXML and open it.--- ----- openXmlSingle :: HasMusicXml a => Score a -> IO ()--- openXmlSingle sc = do--- writeXmlSingle "test.xml" sc--- void $ rawSystem "open" ["-a", "/Applications/Sibelius 6.app/Contents/MacOS/Sibelius 6", "test.xml"]+instance HasBackendNote MusicXml a => HasBackendNote MusicXml [a] where+ exportNote = exportChord --- |--- Convert a score to MusicXML and print it on the standard output.----showMusicXml :: (HasMusicXml a, HasPart' a, Semigroup a) => Score a -> IO ()-showMusicXml = putStrLn . toMusicXmlString+instance HasBackendNote MusicXml Integer where+ -- TODO can we get rid of exportChord alltogether and just use XmlContext?+ exportNote _ (XmlContext d Nothing) = MusicXml.rest (realToFrac d)+ exportNote _ (XmlContext d (Just x)) = (`MusicXml.note` realToFrac d) . spellMusicXml . fromIntegral $ x --- |--- Convert a score to a MusicXML string.----toMusicXmlString :: (HasMusicXml a, HasPart' a, Semigroup a) => Score a -> String-toMusicXmlString = Xml.showXml . toMusicXml+ exportChord _ (XmlContext d Nothing) = MusicXml.rest (realToFrac d)+ exportChord _ (XmlContext d (Just xs)) = (`MusicXml.chord` realToFrac d) . fmap (spellMusicXml . fromIntegral) $ xs+ -- getMusicXml d = (`Xml.note` realToFrac d) . spellMusicXml . fromIntegral+ -- getMusicXmlChord d = (`Xml.chord` realToFrac d) . fmap (spellMusicXml . fromIntegral) --- |--- Convert a score to a MusicXML representation.----toMusicXml :: (HasMusicXml a, HasPart' a, Semigroup a) => Score a -> XmlScore-toMusicXml sc =- -- Score structure- Xml.fromParts title composer pl - -- Main notation pipeline- . fmap (voiceToMusicXml' barTimeSigs barDurations . scoreToVoice . simultaneous+instance HasBackendNote MusicXml Int where+ exportNote b = exportNote b . fmap toInteger+ exportChord b = exportChord b . fmap (fmap toInteger) - -- Meta-event expansion- . addClefs- )+instance HasBackendNote MusicXml Float where+ exportNote b = exportNote b . fmap (toInteger . round)+ exportChord b = exportChord b . fmap (fmap (toInteger . round)) - . extractParts $ sc+instance HasBackendNote MusicXml Double where+ exportNote b = exportNote b . fmap (toInteger . round)+ exportChord b = exportChord b . fmap (fmap (toInteger . round)) +instance Integral a => HasBackendNote MusicXml (Ratio a) where+ exportNote b = exportNote b . fmap (toInteger . round)++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (Behavior a) where+ exportNote b = exportNote b . fmap (! 0)+ exportChord b = exportChord b . fmap (fmap (! 0))++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (Sum a) where+ exportNote b = exportNote b . fmap getSum++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (Product a) where+ exportNote b = exportNote b . fmap getProduct++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (PartT n a) where+ -- Part structure is handled by HasMidiBackendScore instances, so this is just an identity+ exportNote b = exportNote b . fmap extract+ exportChord b = exportChord b . fmap (fmap extract)++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (DynamicT DynamicNotation a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getDynamicT . sequenceA where- addClefT :: a -> ClefT a- addClefT = return+ notate (DynamicNotation (crescDims, level)) + = composed (fmap notateCrescDim crescDims) + . notateLevel level - addClefs = setClef . fmap addClefT- setClef = withClef def $ \c x -> applyClef c x where def = GClef -- TODO use part default+ notateCrescDim crescDims = case crescDims of+ NoCrescDim -> id+ BeginCresc -> (<>) MusicXml.beginCresc+ EndCresc -> (<>) MusicXml.endCresc+ BeginDim -> (<>) MusicXml.beginDim+ EndDim -> (<>) MusicXml.endDim - timeSigs = getTimeSignatures (time 4 4) sc -- 4/4 is default- timeSigsV = fmap swap $ (^. from voice) $ mergeEqual $ reactiveToVoice' (start <-> offset sc) timeSigs+ -- TODO these literals are not so nice...+ notateLevel showLevel = case showLevel of+ Nothing -> id+ Just lvl -> (<>) $ MusicXml.dynamic (fromDynamics (DynamicsL (Just (fixLevel . realToFrac $ lvl), Nothing)))+ + fixLevel :: Double -> Double+ fixLevel x = fromIntegral (round (x - 0.5)) + 0.5 - -- Despite mergeEqual above we need retainUpdates here to prevent redundant repetition of time signatures- barTimeSigs = retainUpdates $ getBarTimeSignatures $ timeSigsV- barDurations = getBarDurations $ timeSigsV+ -- DO NOT use rcomposed as notateDynamic returns "mark" order, not application order+ -- rcomposed = composed . reverse - title = fromMaybe "" $ flip getTitleAt 0 $ metaAtStart sc- composer = fromMaybe "" $ flip getAttribution "composer" $ metaAtStart sc+instance HasBackendNote MusicXml a => HasBackendNote MusicXml (ArticulationT b{-ArticulationNotation-} a) where+ exportNote b = exportNote b . fmap extract+ -- exportNote b = uncurry notate . fmap (exportNote b) . getArticulationT . sequenceA+ -- where+ -- notate (ArticulationNotation (slurs, marks))+ -- = composed (fmap notateMark marks)+ -- . composed (fmap notateSlur slurs)+ -- + -- notateMark mark = case mark of+ -- NoMark -> id+ -- Staccato -> MusicXml.staccato+ -- MoltoStaccato -> MusicXml.staccatissimo+ -- Marcato -> MusicXml.strongAccent+ -- Accent -> MusicXml.accent+ -- Tenuto -> MusicXml.tenuto+ -- + -- notateSlur slurs = case slurs of+ -- NoSlur -> id+ -- BeginSlur -> MusicXml.beginSlur+ -- EndSlur -> MusicXml.endSlur - pl = Xml.partList (fmap show $ getParts sc)+instance HasBackendNote MusicXml a => HasBackendNote MusicXml (ColorT a) where+ exportNote b = exportNote b . fmap extract+{-+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getColorT . sequenceA+ where+ -- TODO This syntax will change in future MusicXml versions+ -- TODO handle any color+ notate (Option Nothing) = id+ notate (Option (Just (Last color))) = \x -> MusicXml.Sequential [+ MusicXml.Override "NoteHead#' color" + (MusicXml.toLiteralValue $ "#" ++ colorName color),+ x,+ MusicXml.Revert "NoteHead#' color"+ ] -mergeBars :: [XmlMusic] -> XmlMusic-mergeBars [x] = x-mergeBars _ = error "mergeBars: Not supported"+ colorName c+ | c == Color.black = "black"+ | c == Color.red = "red"+ | c == Color.blue = "blue"+ | otherwise = error "MusicXml backend: Unkown color"+-}+instance HasBackendNote MusicXml a => HasBackendNote MusicXml (TremoloT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getTremoloT . sequenceA+ where+ notate (Max n) = case n of+ 0 -> id+ n -> MusicXml.tremolo (fromIntegral n)+ +instance HasBackendNote MusicXml a => HasBackendNote MusicXml (TextT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getCouple . getTextT . sequenceA+ where+ notate texts a = mconcat (fmap MusicXml.text texts) <> a +instance HasBackendNote MusicXml a => HasBackendNote MusicXml (HarmonicT a) where+ exportNote b = uncurry notateX . fmap (exportNote b) . getCouple . getHarmonicT . sequenceA+ where+ notateX (Any isNat, Sum n) = notate isNat n++ notate _ 0 = id+ notate True n = notateNatural n+ notate False n = notateArtificial n++ -- notateNatural n = Xml.harmonic -- openString?+ notateNatural n = MusicXml.setNoteHead MusicXml.DiamondNoteHead+ -- Most programs do not recognize the harmonic tag+ -- We set a single diamond notehead instead, which can be manually replaced+ notateArtificial n = id -- TODO+ ++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (SlideT a) where+ exportNote b = uncurry notateX . fmap (exportNote b) . getCouple . getSlideT . sequenceA+ where+ notateX ((eg,es),(bg,bs)) = notate+ where+ notate = neg . nes . nbg . nbs+ neg = if view _Wrapped' eg then MusicXml.endGliss else id+ nes = if view _Wrapped' es then MusicXml.endSlide else id+ nbg = if view _Wrapped' bg then MusicXml.beginGliss else id+ nbs = if view _Wrapped' bs then MusicXml.beginSlide else id++instance HasBackendNote MusicXml a => HasBackendNote MusicXml (TieT a) where+ exportNote b = uncurry notate . fmap (exportNote b) . getTieT . sequenceA+ where+ notate (Any ta, Any tb)+ | ta && tb = MusicXml.beginTie . MusicXml.endTie -- TODO flip order?+ | tb = MusicXml.beginTie+ | ta = MusicXml.endTie+ | otherwise = id+ -- |--- Convert a voice score to a list of bars.+-- Constraint for types that has a MusicXML representation. ---voiceToMusicXml' :: HasMusicXml a => [Maybe TimeSignature] -> [Duration] -> Voice (Maybe a) -> [XmlMusic]-voiceToMusicXml' barTimeSigs barDurations = addStartInfo . zipWith setBarTimeSig barTimeSigs . fmap barToMusicXml . voiceToBars' barDurations--- TODO attach key signatures in each bar (basically zip)+type HasMusicXml a = (HasBackendNote MusicXml (BackendScoreEvent MusicXml a), HasBackendScore MusicXml a) +-- |+-- Convert a score to a MusicXML score. ----- This is where notation of a single voice takes place--- * voiceToBars is generic for most notations outputs: it handles bar splitting and ties--- * barToMusicXml is specific: it handles quantization and notation+toMusicXml :: HasMusicXml a => a -> MusicXml.Score+toMusicXml = export (undefined::MusicXml)++-- |+-- Convert a score to a MusicXML string. --- where- -- FIXME compounds- setBarTimeSig Nothing x = x- setBarTimeSig (Just (getTimeSignature -> (m:_, n))) x = Xml.time (fromInteger m) (fromInteger n) <> x+toMusicXmlString :: HasMusicXml a => a -> String+toMusicXmlString = MusicXml.showXml . toMusicXml - addStartInfo [] = []- addStartInfo (x:xs) = (startInfo <> x):xs- startInfo = mempty- <> Xml.defaultKey- <> Xml.defaultDivisions- <> Xml.metronome (1/4) 60- -- <> Xml.commonTime- -- TODO explicit time sig+-- |+-- Convert a score to MusicXML string and print it on the standard output.+--+showMusicXml :: HasMusicXml a => a -> IO ()+showMusicXml = putStrLn . toMusicXmlString +-- |+-- Convert a score to MusicXML and write to a file.+--+writeMusicXml :: HasMusicXml a => FilePath -> a -> IO ()+writeMusicXml path = writeFile path . toMusicXmlString -barToMusicXml :: HasMusicXml a => [(Duration, Maybe a)] -> XmlMusic-barToMusicXml bar = case (fmap rewrite . quantize) bar of- Left e -> error $ "barToMusicXml: Could not quantize this bar: " ++ show e- Right rh -> rhythmToMusicXml rh+-- |+-- Typeset a score using MusicXML and open it.+--+-- (This is simple wrapper around 'writeMusicXml' that may not work well on all platforms.)+--+openMusicXml :: HasMusicXml a => a -> IO ()+openMusicXml sc = do+ writeMusicXml "test.xml" sc+ -- TODO find out which program to use etc...+ void $ rawSystem "open" ["-a", "Sibelius 7", "test.xml"] -rhythmToMusicXml :: HasMusicXml a => Rhythm (Maybe a) -> XmlMusic-rhythmToMusicXml (Beat d x) = noteRestToMusicXml d x-rhythmToMusicXml (Group rs) = mconcat $ map rhythmToMusicXml rs-rhythmToMusicXml (Dotted n (Beat d x)) = noteRestToMusicXml (dotMod n * d) x-rhythmToMusicXml (Tuplet m r) = Xml.tuplet b a (rhythmToMusicXml r)- where (a,b) = fromIntegral *** fromIntegral $ unRatio $ realToFrac m -noteRestToMusicXml :: HasMusicXml a => Duration -> Maybe a -> XmlMusic-noteRestToMusicXml d Nothing = setDefaultVoice $ Xml.rest $ realToFrac d-noteRestToMusicXml d (Just p) = setDefaultVoice $ getMusicXml d p--setDefaultVoice :: XmlMusic -> XmlMusic-setDefaultVoice = Xml.setVoice 1--spellMusicXml :: Integer -> Xml.Pitch+-- Internal+spellMusicXml :: Integer -> MusicXml.Pitch spellMusicXml p = ( toEnum $ fromIntegral pc, if alt == 0 then Nothing else Just (fromIntegral alt), fromIntegral oct ) where (pc,alt,oct) = spellPitch (p + 60)++-- TODO This function is a workaround+-- Whenever it is used, we should make the original function preserve meta instead+preserveMeta :: (HasMeta a, HasMeta b) => (a -> b) -> a -> b+preserveMeta f x = let m = view meta x in set meta m (f x)+-- End internal+++-- -- main = putStrLn $ show $ view notes $ simultaneous+-- main = do+-- openLilypond music+-- openMusicXml music+-- music =+-- -- over pitches' (+ 2) $+-- -- text "Hello" $+-- compress 1 $ sj </> sj^*2 </> sj^*4+-- where+-- sj = timesPadding 2 1 $ harmonic 1 (scat [+-- color Color.blue $ level _f $ c <> d,+-- cs,+-- level _f ds,+-- level ff fs,+-- level _f a_,+-- text "pizz" $ level pp gs_,+-- tremolo 2 d,+-- tremolo 3 e+-- ::Score MyNote])^*(1+3/5) +-- +-- timesPadding n d x = mcatMaybes $ times n (fmap Just x |> rest^*d)+-- +-- type MyNote = +-- (PartT Int +-- (TieT +-- (ColorT +-- (TextT +-- (TremoloT +-- (HarmonicT +-- (SlideT +-- (ArticulationT () +-- (DynamicT +-- (Sum (Double)) +-- [Behavior Double])))))))))+-- +-- +-- open :: Score MyNote -> IO ()+-- open = do+-- -- showLilypond+-- openLilypond+--
+ src/Music/Score/Export/NoteList.hs view
@@ -0,0 +1,162 @@++{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- A simple backend that supports rendering scores to lists of pitch and velocity.+--+-- This exists as a sanity check for the 'Backend' classes, and as an example.+--+module Music.Score.Export.NoteList (+ -- * Note list backend+ NoteList,+ toNoteList,+ ) where++import Music.Dynamics.Literal+import Music.Pitch.Literal+import qualified Codec.Midi as Midi+import Control.Arrow ((***))+import Control.Comonad (Comonad (..), extract)+import Control.Applicative+import Data.Colour.Names as Color+import Data.Default+import Data.Foldable (Foldable)+import qualified Data.Foldable+import Data.Functor.Couple+import Data.Maybe+import Data.Ratio+import Data.Traversable (Traversable, sequenceA)+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as MusicXml+import Music.Score.Internal.Export hiding (MVoice)+import Music.Time.Internal.Transform (whilstLD)+import System.Process+import Music.Time.Internal.Quantize+import qualified Text.Pretty as Pretty+import qualified Data.List+import Music.Score.Convert (reactiveToVoice') -- TODO+import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates)+import Music.Score.Export.DynamicNotation+import Data.Semigroup.Instances++import Music.Score.Export.Backend+import Music.Time+import Music.Score.Dynamics+import Music.Score.Part++import Music.Score.Export.Backend++import Data.Functor.Identity+import Data.Semigroup+import Control.Monad+import Data.VectorSpace hiding (Sum(..))+import Data.AffineSpace+import Control.Lens hiding (rewrite)+-- import Control.Lens.Operators hiding ((|>))++import Music.Time+import Music.Score.Meta+import Music.Score.Meta.Title+import Music.Score.Meta.Attribution+import Music.Score.Dynamics+import Music.Score.Articulation+import Music.Score.Part+import Music.Score.Tremolo+import Music.Score.Text+import Music.Score.Harmonics+import Music.Score.Slide+import Music.Score.Color+import Music.Score.Ties+import Music.Score.Export.Backend+import Music.Score.Meta.Time+import Music.Score.Phrases+++-- |+-- A token to represent the note list backend.+--+data NoteList++instance HasBackend NoteList where+ type BackendScore NoteList = []+ type BackendContext NoteList = Identity+ type BackendNote NoteList = [(Sum Int, Int)]+ type BackendMusic NoteList = [(Sum Int, Int)]+ finalizeExport _ = concat++instance HasBackendScore NoteList [a] where+ type BackendScoreEvent NoteList [a] = a+ exportScore _ = fmap Identity++instance HasBackendScore NoteList (Score a) where+ type BackendScoreEvent NoteList (Score a) = a+ exportScore _ = fmap Identity . toListOf traverse++instance HasBackendNote NoteList a => HasBackendNote NoteList [a] where+ exportNote b ps = mconcat $ map (exportNote b) $ sequenceA ps++instance HasBackendNote NoteList Int where+ exportNote _ (Identity p) = [(mempty, p)]++instance HasBackendNote NoteList Double where+ exportNote _ (Identity p) = [(mempty, round p)]++-- TODO prettier+instance HasBackendNote NoteList a => HasBackendNote NoteList (DynamicT b a) where+ exportNote b = exportNote b . fmap extract ++instance HasBackendNote NoteList a => HasBackendNote NoteList (ArticulationT b a) where+ exportNote b = exportNote b . fmap extract ++instance HasBackendNote NoteList a => HasBackendNote NoteList (PartT n a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote NoteList a => HasBackendNote NoteList (TremoloT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote NoteList a => HasBackendNote NoteList (TextT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote NoteList a => HasBackendNote NoteList (HarmonicT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote NoteList a => HasBackendNote NoteList (SlideT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote NoteList a => HasBackendNote NoteList (TieT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote NoteList a => HasBackendNote NoteList (ColorT a) where+ exportNote b = exportNote b . fmap extract ++-- |+-- Export music as a note list.+-- +toNoteList :: (HasBackendNote NoteList (BackendScoreEvent NoteList s), HasBackendScore NoteList s) => s -> [(Int, Int)]+toNoteList = over (mapped._1) getSum . export (undefined::NoteList)++
+ src/Music/Score/Export/SuperCollider.hs view
@@ -0,0 +1,234 @@++{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- A backend to generate SuperCollider code.+--+-- This is a very simple thing that generates /patterns/ (which are basically lazy event +-- lists) in the SuperCollider language.+--+-- It would of course also be nice to have a backend based the Haskell bindings (see+-- <http://hackage.haskell.org/package/hsc3>). In that case we could bypass the+-- SuperCollider language and just use /scsynth/.+--+module Music.Score.Export.SuperCollider (+ -- * SuperCollider patterns backend+ SuperCollider,+ HasSuperCollider,+ toSuperCollider,+ writeSuperCollider,+ openSuperCollider,+ ) where++import Music.Dynamics.Literal+import Music.Pitch.Literal+import qualified Codec.Midi as Midi+import Control.Arrow ((***))+import Control.Comonad (Comonad (..), extract)+import Control.Applicative+import Data.Colour.Names as Color+import Data.Default+import Data.Foldable (Foldable)+import qualified Data.Foldable+import Data.Functor.Couple+import Data.Maybe+import Data.Ratio+import Data.Traversable (Traversable, sequenceA)+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as MusicXml+import Music.Score.Internal.Export hiding (MVoice)+import Music.Time.Internal.Transform (whilstLD)+import System.Process+import Music.Time.Internal.Quantize+import qualified Text.Pretty as Pretty+import qualified Data.List+import Music.Score.Convert (reactiveToVoice') -- TODO+import Music.Score.Internal.Util (composed, unRatio, swap, retainUpdates)+import Music.Score.Export.DynamicNotation+import Data.Semigroup.Instances++import Music.Score.Export.Backend++import Data.Functor.Identity+import Data.Semigroup+import Control.Monad+import Data.VectorSpace hiding (Sum(..))+import Data.AffineSpace+import Control.Lens hiding (rewrite)++import Music.Time+import Music.Score.Meta+import Music.Score.Meta.Title+import Music.Score.Meta.Attribution+import Music.Score.Dynamics+import Music.Score.Articulation+import Music.Score.Part+import Music.Score.Tremolo+import Music.Score.Text+import Music.Score.Harmonics+import Music.Score.Slide+import Music.Score.Color+import Music.Score.Ties+import Music.Score.Export.Backend+import Music.Score.Meta.Time+import Music.Score.Phrases+++-- | A token to represent the SuperCollider backend.+data SuperCollider++-- | Pass duration to the note export.+type ScContext = Identity--ScContext Duration a deriving (Functor, Foldable, Traversable)++-- | Just \dur, \midinote, \db for now+type ScEvent = (Double, Double)++-- | Score is just a list of parallel voices.+data ScScore a = ScScore [[(Duration, Maybe a)]]+ deriving (Functor)++instance Monoid (ScScore a) where+ mempty = ScScore mempty+ ScScore a `mappend` ScScore b = ScScore (a `mappend` b)++instance HasBackend SuperCollider where+ type BackendContext SuperCollider = ScContext+ type BackendScore SuperCollider = ScScore+ type BackendNote SuperCollider = ScEvent+ type BackendMusic SuperCollider = String++ finalizeExport _ (ScScore trs) = composeTracksInParallel $ map exportTrack trs+ where + composeTracksInParallel :: [String] -> String+ composeTracksInParallel = (\x -> "Ppar([" ++ x ++ "])") . Data.List.intercalate ", "+ + exportTrack :: [(Duration, Maybe ScEvent)] -> String+ exportTrack events = "Pbind("+ ++ "\\dur, Pseq(" ++ show durs ++ ")"+ ++ ", "+ ++ "\\midinote, Pseq(" ++ showRestList pitches ++ ")"+ ++ ")"+ where+ showRestList = (\x -> "[" ++ x ++ "]") + . Data.List.intercalate ", " + . map (maybe "\\rest" show) + + -- events :: ScEvent+ durs :: [Double]+ pitches :: [Maybe Double]+ ampls :: [Maybe Double]+ durs = map (realToFrac . fst) events+ pitches = map (fmap fst . snd) events+ ampls = map (fmap snd . snd) events+ ++instance () => HasBackendScore SuperCollider (Voice (Maybe a)) where+ type BackendScoreEvent SuperCollider (Voice (Maybe a)) = a+ exportScore _ xs = Identity <$> ScScore [view eventsV xs]++instance (HasPart' a, Ord (Part a)) => HasBackendScore SuperCollider (Score a) where+ type BackendScoreEvent SuperCollider (Score a) = a+ exportScore b = mconcat+ . map (exportScore b . view singleMVoice)+ . extractParts+ +instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider [a] where+ exportNote b ps = head $ map (exportNote b) $ sequenceA ps++instance HasBackendNote SuperCollider Double where+ exportNote _ (Identity x) = (x + 60, 1)++instance HasBackendNote SuperCollider Int where+ exportNote _ (Identity x) = (fromIntegral x + 60, 1)++instance HasBackendNote SuperCollider Integer where+ exportNote _ (Identity x) = (fromIntegral x + 60, 1)++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (Behavior a) where+ exportNote b = exportNote b . fmap (! 0)+ exportChord b = exportChord b . fmap (fmap (! 0))++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (DynamicT b a) where+ exportNote b = exportNote b . fmap extract+ -- exportNote b (Identity (DynamicT (Sum v, x))) = fmap (setV v) $ exportNote b (Identity x)++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (ArticulationT b a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (PartT n a) where+ -- Part structure is handled by HasSuperColliderBackendScore instances, so this is just an identity+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (TremoloT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (TextT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (HarmonicT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (SlideT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (TieT a) where+ exportNote b = exportNote b . fmap extract++instance HasBackendNote SuperCollider a => HasBackendNote SuperCollider (ColorT a) where+ exportNote b = exportNote b . fmap extract+++-- |+-- Constraint for types that has a SuperCollider representation.+--+type HasSuperCollider a = (HasBackendNote SuperCollider (BackendScoreEvent SuperCollider a), HasBackendScore SuperCollider a)++-- |+-- Convert music to a SuperCollider code string.+--+toSuperCollider :: HasSuperCollider a => a -> String+toSuperCollider = export (undefined::SuperCollider)++-- |+-- Write music as a SuperCollider code string to the given path.+--+-- @+-- writeSuperCollider \"test.sc\" $ scat [c,d,e]+-- @+--+writeSuperCollider :: HasSuperCollider a => FilePath -> a -> IO ()+writeSuperCollider path score =+ writeFile path ("(" ++ toSuperCollider score ++ ").play")++-- |+-- Write music as a SuperCollider code string and open it.+--+-- (This is simple wrapper around 'writeSuperCollider' that may not work well on all platforms.)+--+openSuperCollider :: HasSuperCollider a => a -> IO ()+openSuperCollider = writeSuperCollider "test.sc"+
+ src/Music/Score/Harmonics.hs view
@@ -0,0 +1,124 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides a representation of harmonics. +--+-- /Warning/ This module is experimental.+--+-------------------------------------------------------------------------------------++module Music.Score.Harmonics (+ -- * Harmonics+ HasHarmonic(..),+ HarmonicT(..),+ harmonic,+ artificial,+ ) where++import Control.Applicative+import Control.Comonad+import Control.Lens hiding (transform)+import Data.Foldable+import Data.Foldable+import Data.Functor.Couple+import Data.Ratio+import Data.Semigroup+import Data.Typeable+import Data.Word++import Music.Dynamics.Literal+import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal+import Music.Score.Part+import Music.Score.Phrases+import Music.Time+++-- 0 for none, positive for natural, negative for artificial+class HasHarmonic a where+ setNatural :: Bool -> a -> a+ setHarmonic :: Int -> a -> a++-- (isNatural, overtone series index where 0 is fundamental)+newtype HarmonicT a = HarmonicT { getHarmonicT :: Couple (Any, Sum Int) a }+ deriving (+ Eq, Show, Ord, Functor, Foldable, Typeable, + Applicative, Monad, Comonad+ )++instance HasHarmonic a => HasHarmonic (b, a) where+ setNatural b = fmap (setNatural b)+ setHarmonic n = fmap (setHarmonic n)++instance HasHarmonic a => HasHarmonic (Couple b a) where+ setNatural b = fmap (setNatural b)+ setHarmonic n = fmap (setHarmonic n)++instance HasHarmonic a => HasHarmonic [a] where+ setNatural b = fmap (setNatural b)+ setHarmonic n = fmap (setHarmonic n)++instance HasHarmonic a => HasHarmonic (Score a) where+ setNatural b = fmap (setNatural b)+ setHarmonic n = fmap (setHarmonic n)+++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (HarmonicT a) where+ type Unwrapped (HarmonicT a) = Couple (Any, Sum Int) a+ _Wrapped' = iso getHarmonicT HarmonicT++instance Rewrapped (HarmonicT a) (HarmonicT b)++instance HasHarmonic (HarmonicT a) where+ setNatural b = over (_Wrapped'._Wrapped') $ \((_,n),x) -> ((Any b,n),x)+ setHarmonic n = over (_Wrapped'._Wrapped') $ \((nat,_),x) -> ((nat,Sum n),x)++-- Lifted instances+deriving instance Num a => Num (HarmonicT a)+deriving instance Fractional a => Fractional (HarmonicT a)+deriving instance Floating a => Floating (HarmonicT a)+deriving instance Enum a => Enum (HarmonicT a)+deriving instance Bounded a => Bounded (HarmonicT a)+deriving instance (Num a, Ord a, Real a) => Real (HarmonicT a)+deriving instance (Real a, Enum a, Integral a) => Integral (HarmonicT a)++-- |+-- Make all notes natural harmonics on the given overtone (1 for octave, 2 for fifth etc).+-- Sounding pitch is unaffected, but notated output is transposed automatically.+--+harmonic :: HasHarmonic a => Int -> a -> a+harmonic n = setNatural True . setHarmonic n+-- TODO verify this can actually be played++-- |+-- Make all notes natural harmonics on the given overtone (1 for octave, 2 for fifth etc).+-- Sounding pitch is unaffected, but notated output is transposed automatically.+--+artificial :: HasHarmonic a => a -> a+artificial = setNatural False . setHarmonic 3+++
src/Music/Score/Import/Abc.hs view
@@ -1,3 +1,18 @@ +-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides ABC notation import.+--+-- /Warning/ Experimental module.+--+------------------------------------------------------------------------------------- module Music.Score.Import.Abc where
src/Music/Score/Import/Lilypond.hs view
@@ -1,2 +1,17 @@ +-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides Lilypond import.+--+-- /Warning/ Experimental module.+--+------------------------------------------------------------------------------------- module Music.Score.Import.Lilypond where
src/Music/Score/Import/Midi.hs view
@@ -5,6 +5,21 @@ {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-} +-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides MIDI import.+--+-- /Warning/ Experimental module.+--+------------------------------------------------------------------------------------- module Music.Score.Import.Midi ( IsMidi(..), fromMidi,@@ -14,11 +29,10 @@ ) where import Music.Pitch.Literal (IsPitch)-import Music.Score.Score +import Codec.Midi hiding (Track) import Control.Applicative import Control.Lens-import Codec.Midi hiding (Track) -- import Control.Reactive hiding (Event) -- import qualified Control.Reactive as R -- import Control.Reactive.Midi@@ -26,18 +40,15 @@ import Music.Dynamics.Literal import Music.Pitch.Literal import Music.Score.Articulation-import Music.Score.Chord-import Music.Score.Combinators import Music.Score.Dynamics-import Music.Score.Export.Common-import Music.Score.Ornaments+import Music.Score.Internal.Export+import Music.Score.Harmonics import Music.Score.Part import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score+import Music.Score.Slide+import Music.Score.Text import Music.Score.Ties-import Music.Score.Track-import Music.Score.Voice+import Music.Score.Tremolo import Music.Time import qualified Codec.Midi as Midi@@ -57,11 +68,12 @@ -- TODO IsPitch a, HasPart' a,+ Ord (Part a), Enum (Part a), -- HasPitch a, Num (Pitch a), HasTremolo a,- HasArticulation a,+ HasArticulation a a, Tiable a )
src/Music/Score/Instances.hs view
@@ -13,7 +13,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -30,131 +30,48 @@ import Control.Applicative import Control.Comonad+import Control.Lens hiding (part, transform) import Control.Monad import Data.AffineSpace import Data.Default import Data.Foldable+import Data.Functor.Adjunction (unzipR)+import Data.Functor.Couple import qualified Data.List as List import Data.Maybe import Data.Ratio import Data.Semigroup import Data.Typeable-import Data.VectorSpace+import Data.VectorSpace hiding (Sum)+import Data.Semigroup.Instances import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Pitch.Augmentable import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal import Music.Score.Articulation-import Music.Score.Chord-import Music.Score.Combinators+import Music.Score.Color import Music.Score.Dynamics-import Music.Score.Ornaments+import Music.Score.Harmonics+import Music.Score.Meta import Music.Score.Part import Music.Score.Pitch-import Music.Score.Rhythm-import Music.Score.Score+import Music.Score.Slide+import Music.Score.Text import Music.Score.Ties-import Music.Score.Track-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Tremolo import Music.Time ----------------------------------------------------------------------------------------{-- Rewrite pairs:-- CANNOT DO- ChordT- TODO- PartT-- DONE- DynamicT- TieT- ArticulationT- TremoloT- HarmonicT- TextT- SlideT----}---instance (IsPitch a, Enum n) => IsPitch (PartT n a) where- fromPitch l = PartT (toEnum 0, fromPitch l)-instance (IsDynamics a, Enum n) => IsDynamics (PartT n a) where- fromDynamics l = PartT (toEnum 0, fromDynamics l)--instance IsPitch a => IsPitch (ChordT a) where- fromPitch = return . fromPitch-instance IsDynamics a => IsDynamics (ChordT a) where- fromDynamics = return . fromDynamics--instance IsPitch a => IsPitch (TieT a) where- fromPitch = pure . fromPitch-instance IsDynamics a => IsDynamics (TieT a) where- fromDynamics = return . fromDynamics--instance IsPitch a => IsPitch (DynamicT a) where- fromPitch = pure . fromPitch-instance IsDynamics a => IsDynamics (DynamicT a) where- fromDynamics = return . fromDynamics--instance IsPitch a => IsPitch (TremoloT a) where- fromPitch = pure . fromPitch-instance IsDynamics a => IsDynamics (TremoloT a) where- fromDynamics = pure . fromDynamics--instance IsPitch a => IsPitch (TextT a) where- fromPitch = pure . fromPitch-instance IsDynamics a => IsDynamics (TextT a) where- fromDynamics = pure . fromDynamics--instance IsPitch a => IsPitch (HarmonicT a) where- fromPitch = pure . fromPitch-instance IsDynamics a => IsDynamics (HarmonicT a) where- fromDynamics = pure . fromDynamics--instance IsPitch a => IsPitch (SlideT a) where- fromPitch = pure . fromPitch-instance IsDynamics a => IsDynamics (SlideT a) where- fromDynamics = pure . fromDynamics-----------------------------------------------------------------------------------------instance Reversible (ChordT a) where- rev = id-instance Reversible a => Reversible (DynamicT a) where- rev = fmap rev-instance Reversible a => Reversible (SlideT a) where- rev = fmap rev-instance Reversible a => Reversible (TieT a) where- rev = fmap rev-instance Reversible a => Reversible (HarmonicT a) where- rev = fmap rev-instance Reversible a => Reversible (ArticulationT a) where- rev = fmap rev-instance Reversible a => Reversible (TextT a) where- rev = fmap rev-instance Reversible a => Reversible (TremoloT a) where- rev = fmap rev-instance Reversible a => Reversible (PartT p a) where- rev = fmap rev------------------------------------------------------------------------------------------instance Semigroup a => Semigroup (DynamicT a) where- DynamicT (d1, x1) <> DynamicT (d2, x2) = DynamicT (d1 <> d2, x1 <> x2)+-- -------------------------------------------------------------------------------------+--+-- instance Semigroup a => Semigroup (DynamicT a) where+-- DynamicT (d1, x1) <> DynamicT (d2, x2) = DynamicT (d1 <> d2, x1 <> x2) instance Semigroup a => Semigroup (SlideT a) where (<>) = liftA2 (<>) instance Semigroup a => Semigroup (TieT a) where TieT (t1, x1) <> TieT (t2, x2) = TieT (t1 <> t2, x1 <> x2) -- This instance is suspect: in general chord notes are not required to share ties,- -- so this instance may be removed (provided that TieT is moved inside ChordT for+ -- so this instance may be removed (provided that TieT is moved inside Chord for -- all Preludes). See #134 instance Semigroup a => Semigroup (HarmonicT a) where (<>) = liftA2 (<>)@@ -166,196 +83,105 @@ PartT (v1,x1) <> PartT (v2,x2) = PartT (v1, x1 <> x2) --------------------------------------------------------------------------------------+-- ------------------------------------------------------------------------------------- --- Maybe+--+-- Aspect instaces (Pitch, Dynamics and Articulation) for PartT needs to go here,+-- as the other aspects depends on partwise traversals etc+-- --- TODO this instance may be problematic with mapPhrase-instance HasArticulation a => HasArticulation (Maybe a) where- setEndSlur n = fmap (setEndSlur n)- setContSlur n = fmap (setContSlur n)- setBeginSlur n = fmap (setBeginSlur n)- setAccLevel n = fmap (setAccLevel n)- setStaccLevel n = fmap (setStaccLevel n)-type instance Part (Maybe a) = Part a-instance HasPart a => HasPart (Maybe a) where- getPart Nothing = error "Nothing: no part"- getPart (Just a) = getPart a- modifyPart f = fmap (modifyPart f)-type instance Pitch (Maybe a) = Pitch a-instance HasSetPitch a b => HasSetPitch (Maybe a) (Maybe b) where- type SetPitch g (Maybe a) = Maybe (SetPitch g a)- __mapPitch f (Nothing) = Nothing- __mapPitch f (Just a) = Just (__mapPitch f a)+type instance Pitch (PartT p a) = Pitch a+type instance SetPitch b (PartT p a) = PartT p (SetPitch b a) +instance HasPitch a b => HasPitch (PartT p a) (PartT p b) where+ pitch = _Wrapped . _2 . pitch+instance HasPitches a b => HasPitches (PartT p a) (PartT p b) where+ pitches = _Wrapped . _2 . pitches --- PartT+type instance Pitch (DynamicT p a) = Pitch a+type instance SetPitch b (DynamicT p a) = DynamicT p (SetPitch b a) -instance HasChord a => HasChord (PartT n a) where- type ChordNote (PartT n a) = PartT n (ChordNote a)- getChord (PartT (v,x)) = fmap (\x -> PartT (v,x)) (getChord x)+instance HasPitch a b => HasPitch (DynamicT p a) (DynamicT p b) where+ pitch = _Wrapped . _2 . pitch+instance HasPitches a b => HasPitches (DynamicT p a) (DynamicT p b) where+ pitches = _Wrapped . _2 . pitches -type instance Pitch (PartT n a) = Pitch a-instance HasGetPitch a => HasGetPitch (PartT n a) where- __getPitch = __getPitch . extract-instance HasSetPitch a b => HasSetPitch (PartT n a) (PartT n b) where- type SetPitch g (PartT n a) = PartT n (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)+type instance Pitch (ArticulationT p a) = Pitch a+type instance SetPitch b (ArticulationT p a) = ArticulationT p (SetPitch b a) -instance Tiable a => Tiable (PartT n a) where- toTied (PartT (v,a)) = (PartT (v,b), PartT (v,c)) where (b,c) = toTied a-deriving instance HasDynamic a => HasDynamic (PartT n a)-deriving instance HasArticulation a => HasArticulation (PartT n a)-deriving instance HasTremolo a => HasTremolo (PartT n a)-deriving instance HasHarmonic a => HasHarmonic (PartT n a)-deriving instance HasSlide a => HasSlide (PartT n a)-deriving instance HasText a => HasText (PartT n a)+instance HasPitch a b => HasPitch (ArticulationT p a) (ArticulationT p b) where+ pitch = _Wrapped . _2 . pitch+instance HasPitches a b => HasPitches (ArticulationT p a) (ArticulationT p b) where+ pitches = _Wrapped . _2 . pitches --- ChordT -instance Tiable a => Tiable (ChordT a) where- toTied (ChordT as) = (ChordT bs, ChordT cs) where (bs,cs) = (unzip . fmap toTied) as--- There is no (HasPart ChordT) instance, so PartT must be outside ChordT in the stack--- This restriction assures all chord notes are in the same part+type instance Dynamic (PartT p a) = Dynamic a+type instance SetDynamic b (PartT p a) = PartT p (SetDynamic b a) -type instance Pitch (ChordT a) = Pitch a+instance HasDynamic a b => HasDynamic (PartT p a) (PartT p b) where+ dynamic = _Wrapped . _2 . dynamic+instance HasDynamics a b => HasDynamics (PartT p a) (PartT p b) where+ dynamics = _Wrapped . _2 . dynamics -instance HasSetPitch a b => HasSetPitch (ChordT a) (ChordT b) where- type SetPitch g (ChordT a) = ChordT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)+type instance Dynamic (ArticulationT p a) = Dynamic a+type instance SetDynamic b (ArticulationT p a) = ArticulationT p (SetDynamic b a) -instance HasDynamic a => HasDynamic (ChordT a) where- setEndDim n = fmap (setEndDim n)- setLevel n = fmap (setLevel n)-instance HasArticulation a => HasArticulation (ChordT a) where- setEndSlur n = fmap (setEndSlur n)- setContSlur n = fmap (setContSlur n)- setBeginSlur n = fmap (setBeginSlur n)- setAccLevel n = fmap (setAccLevel n)- setStaccLevel n = fmap (setStaccLevel n)-instance HasTremolo a => HasTremolo (ChordT a) where- setTrem n = fmap (setTrem n)-instance HasHarmonic a => HasHarmonic (ChordT a) where- setNatural n = fmap (setNatural n)- setHarmonic n = fmap (setHarmonic n)-instance HasSlide a => HasSlide (ChordT a) where- setBeginGliss n = fmap (setBeginGliss n)- setBeginSlide n = fmap (setBeginSlide n)- setEndGliss n = fmap (setEndGliss n)- setEndSlide n = fmap (setEndSlide n)-instance HasText a => HasText (ChordT a) where- addText s (ChordT as) = ChordT (mapF (addText s) as)+instance HasDynamic a b => HasDynamic (ArticulationT p a) (ArticulationT p b) where+ dynamic = _Wrapped . _2 . dynamic+instance HasDynamics a b => HasDynamics (ArticulationT p a) (ArticulationT p b) where+ dynamics = _Wrapped . _2 . dynamics --- TieT -type instance Part (TieT a) = Part a-instance HasPart a => HasPart (TieT a) where- getPart (TieT (_,x)) = getPart x- modifyPart f = fmap (modifyPart f)-instance HasChord a => HasChord (TieT a) where- type ChordNote (TieT a) = TieT (ChordNote a)- getChord (TieT (t,x)) = fmap (\x -> TieT (t,x)) (getChord x)--type instance Pitch (TieT a) = Pitch a-instance HasGetPitch a => HasGetPitch (TieT a) where- __getPitch = __getPitch . get1-instance HasSetPitch a b => HasSetPitch (TieT a) (TieT b) where- type SetPitch g (TieT a) = TieT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)---deriving instance HasDynamic a => HasDynamic (TieT a)-deriving instance HasArticulation a => HasArticulation (TieT a)-deriving instance HasTremolo a => HasTremolo (TieT a)-deriving instance HasHarmonic a => HasHarmonic (TieT a)-deriving instance HasSlide a => HasSlide (TieT a)-deriving instance HasText a => HasText (TieT a)----- DynamicT---- end cresc/dim, level, begin cresc/dim--- newtype DynamicT a = DynamicT { getDynamicT :: (Bool, Bool, Maybe Double, a, Bool, Bool) }--instance Tiable a => Tiable (DynamicT a) where- toTied (DynamicT (l, a)) = (DynamicT (l, b), DynamicT (mempty, c)) where (b,c) = toTied a+type instance Articulation (PartT p a) = Articulation a+type instance SetArticulation b (PartT p a) = PartT p (SetArticulation b a) -type instance Part (DynamicT a) = Part a-instance HasPart a => HasPart (DynamicT a) where- getPart (DynamicT (_,x)) = getPart x- modifyPart f = fmap (modifyPart f)-instance HasChord a => HasChord (DynamicT a) where- type ChordNote (DynamicT a) = DynamicT (ChordNote a)- getChord (DynamicT (d,as)) = fmap (\x -> DynamicT (d,x)) (getChord as)+instance HasArticulation a b => HasArticulation (PartT p a) (PartT p b) where+ articulation = _Wrapped . _2 . articulation+instance HasArticulations a b => HasArticulations (PartT p a) (PartT p b) where+ articulations = _Wrapped . _2 . articulations -type instance Pitch (DynamicT a) = Pitch a-instance HasGetPitch a => HasGetPitch (DynamicT a) where- __getPitch (DynamicT (_,x)) = __getPitch x-instance HasSetPitch a b => HasSetPitch (DynamicT a) (DynamicT b) where- type SetPitch g (DynamicT a) = DynamicT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)+-- TODO move up?+type instance Pitch (ColorT a) = Pitch a+type instance SetPitch g (ColorT a) = ColorT (SetPitch g a)+instance (HasPitches a b) => HasPitches (ColorT a) (ColorT b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (ColorT a) (ColorT b) where+ pitch = _Wrapped . pitch +type instance Dynamic (ColorT a) = Dynamic a+type instance SetDynamic g (ColorT a) = ColorT (SetDynamic g a)+instance (HasDynamics a b) => HasDynamics (ColorT a) (ColorT b) where+ dynamics = _Wrapped . dynamics+instance (HasDynamic a b) => HasDynamic (ColorT a) (ColorT b) where+ dynamic = _Wrapped . dynamic -deriving instance HasArticulation a => HasArticulation (DynamicT a)-deriving instance HasTremolo a => HasTremolo (DynamicT a)-deriving instance HasHarmonic a => HasHarmonic (DynamicT a)-deriving instance HasSlide a => HasSlide (DynamicT a)-deriving instance HasText a => HasText (DynamicT a)+type instance Articulation (ColorT a) = Articulation a+type instance SetArticulation g (ColorT a) = ColorT (SetArticulation g a)+instance (HasArticulations a b) => HasArticulations (ColorT a) (ColorT b) where+ articulations = _Wrapped . articulations+instance (HasArticulation a b) => HasArticulation (ColorT a) (ColorT b) where+ articulation = _Wrapped . articulation --- ArticulationT+-- ------------------------------------------------------------------------------------- --- end slur, cont slur, acc level, stacc level, begin slur--- newtype ArticulationT a = ArticulationT { getArticulationT :: (Bool, Bool, Int, Int, a, Bool) }+deriving instance HasTremolo a => HasTremolo (PartT n a)+deriving instance HasHarmonic a => HasHarmonic (PartT n a)+deriving instance HasSlide a => HasSlide (PartT n a)+deriving instance HasText a => HasText (PartT n a) -instance Tiable a => Tiable (ArticulationT a) where- toTied (ArticulationT (v,a)) = (ArticulationT (v,b), ArticulationT (v,c)) where (b,c) = toTied a--type instance Part (ArticulationT a) = Part a-instance HasPart a => HasPart (ArticulationT a) where- getPart = getPart . get1- modifyPart f = fmap (modifyPart f)-instance HasChord a => HasChord (ArticulationT a) where- type ChordNote (ArticulationT a) = ArticulationT (ChordNote a)- getChord (ArticulationT (v,x)) = fmap (\x -> ArticulationT (v,x)) (getChord x)--type instance Pitch (ArticulationT a) = Pitch a-instance HasGetPitch a => HasGetPitch (ArticulationT a) where- __getPitch (ArticulationT (_,a)) = __getPitch a-instance HasSetPitch a b => HasSetPitch (ArticulationT a) (ArticulationT b) where- type SetPitch g (ArticulationT a) = ArticulationT (SetPitch g a)- __mapPitch f (ArticulationT (v,x)) = (ArticulationT (v,__mapPitch f x))--deriving instance HasDynamic a => HasDynamic (ArticulationT a)-deriving instance HasTremolo a => HasTremolo (ArticulationT a)-deriving instance HasHarmonic a => HasHarmonic (ArticulationT a)-deriving instance HasSlide a => HasSlide (ArticulationT a)-deriving instance HasText a => HasText (ArticulationT a)---- TremoloT+deriving instance HasTremolo a => HasTremolo (TieT a)+deriving instance HasHarmonic a => HasHarmonic (TieT a)+deriving instance HasSlide a => HasSlide (TieT a)+deriving instance HasText a => HasText (TieT a) instance Tiable a => Tiable (TremoloT a) where- toTied (TremoloT (n,a)) = (TremoloT (n,b), TremoloT (n,c)) where (b,c) = toTied a-type instance Part (TremoloT a) = Part a-instance HasPart a => HasPart (TremoloT a) where- getPart (TremoloT (_,a)) = getPart a- modifyPart f (TremoloT (n,x)) = TremoloT (n, modifyPart f x)-instance HasChord a => HasChord (TremoloT a) where- type ChordNote (TremoloT a) = TremoloT (ChordNote a)- getChord (TremoloT (n,x)) = fmap (\x -> TremoloT (n,x)) (getChord x)--type instance Pitch (TremoloT a) = Pitch a-instance HasGetPitch a => HasGetPitch (TremoloT a) where- __getPitch = __getPitch . get1-instance HasSetPitch a b => HasSetPitch (TremoloT a) (TremoloT b) where- type SetPitch g (TremoloT a) = TremoloT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)+ toTied = unzipR . fmap toTied -deriving instance HasDynamic a => HasDynamic (TremoloT a)-deriving instance HasArticulation a => HasArticulation (TremoloT a)+type instance Part (TremoloT a) = Part a deriving instance HasHarmonic a => HasHarmonic (TremoloT a) deriving instance HasSlide a => HasSlide (TremoloT a) deriving instance HasText a => HasText (TremoloT a)@@ -364,24 +190,7 @@ -- TextT instance Tiable a => Tiable (TextT a) where- toTied (TextT (n,a)) = (TextT (n,b), TextT (mempty,c)) where (b,c) = toTied a-type instance Part (TextT a) = Part a-instance HasPart a => HasPart (TextT a) where- getPart (TextT (_,a)) = getPart a- modifyPart f = fmap (modifyPart f)-instance HasChord a => HasChord (TextT a) where- type ChordNote (TextT a) = TextT (ChordNote a)- getChord (TextT (n,x)) = fmap (\x -> TextT (n,x)) (getChord x)--type instance Pitch (TextT a) = Pitch a-instance HasGetPitch a => HasGetPitch (TextT a) where- __getPitch = __getPitch . get1-instance HasSetPitch a b => HasSetPitch (TextT a) (TextT b) where- type SetPitch g (TextT a) = TextT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)--deriving instance HasDynamic a => HasDynamic (TextT a)-deriving instance HasArticulation a => HasArticulation (TextT a)+ toTied (TextT (Couple (n,a))) = (TextT (Couple (n,b)), TextT (Couple (mempty,c))) where (b,c) = toTied a deriving instance HasTremolo a => HasTremolo (TextT a) deriving instance HasHarmonic a => HasHarmonic (TextT a) deriving instance HasSlide a => HasSlide (TextT a)@@ -390,24 +199,8 @@ -- HarmonicT instance Tiable a => Tiable (HarmonicT a) where- toTied (HarmonicT (n,a)) = (HarmonicT (n,b), HarmonicT (n,c)) where (b,c) = toTied a-type instance Part (HarmonicT a) = Part a-instance HasPart a => HasPart (HarmonicT a) where- getPart (HarmonicT (_,a)) = getPart a- modifyPart f (HarmonicT (n,x)) = HarmonicT (n, modifyPart f x)-instance HasChord a => HasChord (HarmonicT a) where- type ChordNote (HarmonicT a) = HarmonicT (ChordNote a)- getChord (HarmonicT (n,x)) = fmap (\x -> HarmonicT (n,x)) (getChord x)--type instance Pitch (HarmonicT a) = Pitch a-instance HasGetPitch a => HasGetPitch (HarmonicT a) where- __getPitch = __getPitch . get1-instance HasSetPitch a b => HasSetPitch (HarmonicT a) (HarmonicT b) where- type SetPitch g (HarmonicT a) = HarmonicT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)--deriving instance HasDynamic a => HasDynamic (HarmonicT a)-deriving instance HasArticulation a => HasArticulation (HarmonicT a)+ -- toTied = unzipR . fmap toTied+ toTied (HarmonicT (Couple (n,a))) = (HarmonicT (Couple (n,b)), HarmonicT (Couple (mempty,c))) where (b,c) = toTied a deriving instance HasTremolo a => HasTremolo (HarmonicT a) deriving instance HasSlide a => HasSlide (HarmonicT a) deriving instance HasText a => HasText (HarmonicT a)@@ -417,121 +210,88 @@ instance Tiable a => Tiable (SlideT a) where- toTied (SlideT (v,x)) = (SlideT (v,a), SlideT (v,b)) where (a,b) = toTied x--type instance Part (SlideT a) = Part a-instance HasPart a => HasPart (SlideT a) where- getPart = getPart . get1- modifyPart f = fmap (modifyPart f)-instance HasChord a => HasChord (SlideT a) where- type ChordNote (SlideT a) = SlideT (ChordNote a)- getChord (SlideT (x,as)) = fmap (\a -> SlideT (x,a)) (getChord as)--type instance Pitch (SlideT a) = Pitch a-instance HasGetPitch a => HasGetPitch (SlideT a) where- __getPitch = __getPitch . get1-instance HasSetPitch a b => HasSetPitch (SlideT a) (SlideT b) where- type SetPitch g (SlideT a) = SlideT (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)--deriving instance HasDynamic a => HasDynamic (SlideT a)-deriving instance HasArticulation a => HasArticulation (SlideT a)+ toTied = unzipR . fmap toTied deriving instance HasTremolo a => HasTremolo (SlideT a) deriving instance HasHarmonic a => HasHarmonic (SlideT a) deriving instance HasText a => HasText (SlideT a) ----------------------------------------------------------------------------------------- Literal instances--------------------------------------------------------------------------------------+deriving instance (Monoid b, IsPitch a) => IsPitch (Couple b a)+deriving instance (Monoid b, IsDynamics a) => IsDynamics (Couple b a)+deriving instance (Monoid b, Transformable a) => Transformable (Couple b a)+deriving instance (Monoid b, Reversible a) => Reversible (Couple b a)+deriving instance (Monoid b, Alterable a) => Alterable (Couple b a)+deriving instance (Monoid b, Augmentable a) => Augmentable (Couple b a) -instance Alterable a => Alterable (Score a) where- sharpen = fmap sharpen- flatten = fmap flatten+deriving instance IsPitch a => IsPitch (TremoloT a)+deriving instance IsDynamics a => IsDynamics (TremoloT a)+deriving instance IsPitch a => IsPitch (TextT a)+deriving instance IsDynamics a => IsDynamics (TextT a)+deriving instance IsPitch a => IsPitch (HarmonicT a)+deriving instance IsDynamics a => IsDynamics (HarmonicT a)+deriving instance IsPitch a => IsPitch (SlideT a)+deriving instance IsDynamics a => IsDynamics (SlideT a) -instance Alterable a => Alterable (ChordT a) where- sharpen = fmap sharpen- flatten = fmap flatten+deriving instance IsPitch a => IsPitch (ColorT a)+deriving instance IsDynamics a => IsDynamics (ColorT a)+deriving instance Transformable a => Transformable (ColorT a)+deriving instance Reversible a => Reversible (ColorT a)+deriving instance Alterable a => Alterable (ColorT a)+deriving instance Augmentable a => Augmentable (ColorT a)+deriving instance HasTremolo a => HasTremolo (ColorT a)+deriving instance HasHarmonic a => HasHarmonic (ColorT a)+deriving instance HasSlide a => HasSlide (ColorT a)+deriving instance HasText a => HasText (ColorT a) -instance Alterable a => Alterable (DynamicT a) where- sharpen = fmap sharpen- flatten = fmap flatten+deriving instance Transformable a => Transformable (SlideT a)+deriving instance Transformable a => Transformable (HarmonicT a)+deriving instance Transformable a => Transformable (TextT a)+deriving instance Transformable a => Transformable (TremoloT a) -instance Alterable a => Alterable (SlideT a) where- sharpen = fmap sharpen- flatten = fmap flatten+deriving instance Reversible a => Reversible (SlideT a)+deriving instance Reversible a => Reversible (HarmonicT a)+deriving instance Reversible a => Reversible (TextT a)+deriving instance Reversible a => Reversible (TremoloT a) -instance Alterable a => Alterable (TieT a) where- sharpen = fmap sharpen- flatten = fmap flatten+deriving instance Alterable a => Alterable (SlideT a)+deriving instance Alterable a => Alterable (HarmonicT a)+deriving instance Alterable a => Alterable (TextT a)+deriving instance Alterable a => Alterable (TremoloT a) -instance Alterable a => Alterable (HarmonicT a) where- sharpen = fmap sharpen- flatten = fmap flatten+deriving instance Augmentable a => Augmentable (SlideT a)+deriving instance Augmentable a => Augmentable (HarmonicT a)+deriving instance Augmentable a => Augmentable (TextT a)+deriving instance Augmentable a => Augmentable (TremoloT a) -instance Alterable a => Alterable (ArticulationT a) where- sharpen = fmap sharpen- flatten = fmap flatten -instance Alterable a => Alterable (TextT a) where- sharpen = fmap sharpen- flatten = fmap flatten -instance Alterable a => Alterable (TremoloT a) where- sharpen = fmap sharpen- flatten = fmap flatten+-------------------------------------------------------------------------------------+-- Literal instances+------------------------------------------------------------------------------------- -instance Alterable a => Alterable (PartT n a) where++instance Alterable a => Alterable (Score a) where sharpen = fmap sharpen flatten = fmap flatten+deriving instance Alterable a => Alterable (TieT a)+deriving instance Alterable a => Alterable (PartT n a)+deriving instance Alterable a => Alterable (DynamicT n a)+deriving instance Alterable a => Alterable (ArticulationT n a) instance Augmentable a => Augmentable (Score a) where augment = fmap augment diminish = fmap diminish--instance Augmentable a => Augmentable (ChordT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (DynamicT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (SlideT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (TieT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (HarmonicT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (ArticulationT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (TextT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (TremoloT a) where- augment = fmap augment- diminish = fmap diminish--instance Augmentable a => Augmentable (PartT n a) where- augment = fmap augment- diminish = fmap diminish-+deriving instance Augmentable a => Augmentable (TieT a)+deriving instance Augmentable a => Augmentable (PartT n a)+deriving instance Augmentable a => Augmentable (DynamicT n a)+deriving instance Augmentable a => Augmentable (ArticulationT n a) ----------------------------------------------------------------------------------------- Num, Integral, Enum and Bounded---------------------------------------------------------------------------------------+-- -------------------------------------------------------------------------------------+-- -- Num, Integral, Enum and Bounded+-- -------------------------------------------------------------------------------------+-- -- PartT instance (Enum v, Eq v, Num a) => Num (PartT v a) where@@ -557,376 +317,25 @@ PartT (v,a) `quotRem` PartT (_,b) = (PartT (v,q), PartT (v,r)) where (q,r) = a `quotRem` b toInteger (PartT (v,a)) = toInteger a ---- ChordT---- instance Num a => Num (ChordT a) where--- ChordT [a] + ChordT [b] = ChordT [a+b]--- ChordT [a] * ChordT [b] = ChordT [a*b]--- ChordT [a] - ChordT [b] = ChordT [a-b]--- abs (ChordT [a]) = ChordT [abs a]--- signum (ChordT [a]) = ChordT [signum a]--- fromInteger a = ChordT [fromInteger a]--instance Enum a => Enum (ChordT a) where- toEnum a = ChordT [toEnum a]- fromEnum (ChordT [a]) = fromEnum a--instance Bounded a => Bounded (ChordT a) where- minBound = ChordT [minBound]- maxBound = ChordT [maxBound]---- instance (Num a, Ord a, Real a) => Real (ChordT a) where--- toRational (ChordT [a]) = toRational a---- instance (Real a, Enum a, Integral a) => Integral (ChordT a) where--- ChordT [a] `quotRem` ChordT [b] = (ChordT [q], ChordT [r]) where (q,r) = a `quotRem` b--- toInteger (ChordT [a]) = toInteger a----- TieT--instance Num a => Num (TieT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (TieT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (TieT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (TieT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (TieT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (TieT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (TieT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1----- DynamicT--instance Num a => Num (DynamicT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (DynamicT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (DynamicT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (DynamicT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (DynamicT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (DynamicT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (DynamicT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1----- ArticulationT--instance Num a => Num (ArticulationT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (ArticulationT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (ArticulationT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (ArticulationT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (ArticulationT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (ArticulationT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (ArticulationT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1---- TremoloT--instance Num a => Num (TremoloT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (TremoloT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (TremoloT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (TremoloT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (TremoloT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (TremoloT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (TremoloT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1---- TextT--instance Num a => Num (TextT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (TextT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (TextT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (TextT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (TextT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (TextT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (TextT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1----- HarmonicT--instance Num a => Num (HarmonicT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (HarmonicT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (HarmonicT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (HarmonicT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (HarmonicT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (HarmonicT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (HarmonicT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1---- SlideT--instance Num a => Num (SlideT a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (SlideT a) where- recip = fmap recip- fromRational = pure . fromRational--instance Floating a => Floating (SlideT a) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acos--instance Enum a => Enum (SlideT a) where- toEnum = pure . toEnum- fromEnum = fromEnum . get1--instance Bounded a => Bounded (SlideT a) where- minBound = pure minBound- maxBound = pure maxBound--instance (Num a, Ord a, Real a) => Real (SlideT a) where- toRational = toRational . get1--instance (Real a, Enum a, Integral a) => Integral (SlideT a) where- quot = liftA2 quot- rem = liftA2 rem- toInteger = toInteger . get1---type instance Pitch (Behavior a) = Behavior (Pitch a)---- TODO undecidable-instance (HasGetPitch a, HasSetPitch a b) => HasSetPitch (Behavior a) (Behavior b) where- type SetPitch (Behavior p) (Behavior a) = Behavior (SetPitch p a)- __mapPitch f a = liftA2 (__setPitch) (f $ (__getPitch) <$> a) a--instance Tiable a => Tiable (Behavior a) where toTied x = (x,x)+--+-- TODO suspect instances+-- We should remove both these after replacing [] by Chord in Preludes+--+instance Enum a => Enum [a] where+ toEnum a = [toEnum a]+ fromEnum ([a]) = fromEnum a +instance Bounded a => Bounded [a] where+ minBound = [minBound]+ maxBound = [maxBound] --- Safe for tuple-like types-get1 = head . toList--- TODO replace with extract+-- TODO use wrapper type and replace withContext+type instance Dynamic (a,b,c) = (a,b,c)+type instance SetDynamic g (a,b,c) = g -fmaps :: Functor f => (a -> (b, c)) -> f a -> (f b, f c)-fmaps f x = ((fst . f) <$> x, (snd . f) <$> x)+instance Transformable a => Transformable (Maybe a) where+ transform s = fmap (transform s)+instance (Transformable a, Transformable b, Transformable c) => Transformable (a,b,c) where+ transform s (a,b,c) = (transform s a,transform s b,transform s c) -liftsA2 :: Applicative f => (a -> b -> (c, d)) -> f a -> f b -> (f a, f b)-liftsA2 f x y = (fst <$> ((,) <$> x <*> y), snd <$> ((,) <$> x <*> y))
+ src/Music/Score/Internal/Export.hs view
@@ -0,0 +1,154 @@++{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE TypeFamilies #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Score.Internal.Export (+ extractTimeSignatures,+ voiceToBars',+ -- separateBars,+ spellPitch,+ toRelative,+ MVoice,+ toMVoice,+ unvoice,+ openCommand+ ) where++import Prelude hiding (concat, concatMap, foldl,+ foldr, mapM, maximum, minimum, sum)++import Control.Applicative+import Control.Lens+import Control.Monad hiding (mapM)+import Control.Monad.Plus+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Basis+import Data.Either+import Data.Foldable+import Data.Function (on)+import Data.Maybe+import Data.Ord (comparing)+import Data.Ratio+import Data.Semigroup+import Data.String+import Data.Traversable+import Data.Typeable+import Data.VectorSpace++import Music.Score.Articulation+import Music.Score.Convert (reactiveToVoice', scoreToVoice)+import Music.Score.Dynamics+import Music.Score.Part+import Music.Score.Pitch+import Music.Time.Internal.Quantize+import Music.Score.Ties+import Music.Score.Meta.Time+import Music.Time++import qualified Codec.Midi as Midi+import qualified Data.List as List+import qualified Data.Map as Map+import qualified Music.Lilypond as Lilypond+import qualified Music.MusicXml.Simple as Xml+import qualified System.Info as Info+import qualified Text.Pretty as Pretty++import Control.Exception+import Music.Dynamics.Literal+import Music.Pitch.Literal+import Music.Score.Internal.Util+import System.IO.Unsafe+import System.Process++extractTimeSignatures :: Score a -> ([Maybe TimeSignature], [Duration])+extractTimeSignatures score = (barTimeSignatures, barDurations)+ where + defaultTimeSignature = time 4 4+ timeSignatures = fmap swap + $ view eventsV . fuse . reactiveToVoice' (0 <-> (score^.offset)) + $ getTimeSignatures defaultTimeSignature score++ -- Despite the fuse above we need retainUpdates here to prevent redundant repetition of time signatures+ barTimeSignatures = retainUpdates $ getBarTimeSignatures timeSignatures+ barDurations = getBarDurations timeSignatures++-- | Convert a voice to a list of bars using the given bar durations.+voiceToBars' :: Tiable a => [Duration] -> Voice (Maybe a) -> [[(Duration, Maybe a)]]+voiceToBars' barDurs = fmap (map (^. from stretched) . (^. stretcheds)) . splitTiesVoiceAt barDurs+-- TODO remove prime from name++-- | Convert absolute to relative durations.+toRelative :: [(Time, Duration, b)] -> [(Time, Duration, b)]+toRelative = snd . mapAccumL g 0+ where+ g now (t,d,x) = (t, (0 .+^ (t .-. now),d,x))++-- | Basic spelling for integral types.+spellPitch :: Integral a => a -> (a, a, a)+spellPitch p = (+ pitchClass,+ alteration,+ octave+ )+ where+ octave = (p `div` 12) - 1+ semitone = p `mod` 12+ pitchClass = fromStep major semitone+ alteration = semitone - step major pitchClass++ step xs p = xs !! (fromIntegral p `mod` length xs)+ fromStep xs p = fromIntegral $ fromMaybe (length xs - 1) $ List.findIndex (>= p) xs+ scaleFromSteps = snd . List.mapAccumL add 0+ where+ add a x = (a + x, a + x)+ major = scaleFromSteps [0,2,2,1,2,2,2,1]+++type MVoice a = Voice (Maybe a)++toMVoice :: (Semigroup a, Transformable a) => Score a -> MVoice a+toMVoice = scoreToVoice . simultaneous++unvoice :: Voice b -> [(Duration, b)]+unvoice = toListOf (stretcheds . traverse . from stretched)+-- unvoice = fmap (^. from stretched) . (^. stretcheds)+{-# DEPRECATED unvoice "Use 'unsafeEventsV'" #-}+++openCommand :: String+openCommand = case Info.os of+ "darwin" -> "open"+ "linux" -> "xdg-open"++{-+-- TODO any version and/or OS+hasMuseScore = do+ result <- try (readProcess "ls" ["/Applications/MuseScore.app"] "")+ return $ case result of+ Left e -> (e::SomeException) `assumed` False+ Right _ -> True++hasSibelius = do+ result <- try (readProcess "ls" ["/Applications/Sibelius 7.app"] "")+ return $ case result of+ Left e -> (e::SomeException) `assumed` False+ Right _ -> True+++assumed = flip const+-}
+ src/Music/Score/Internal/Util.hs view
@@ -0,0 +1,348 @@+++{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ViewPatterns #-}++module Music.Score.Internal.Util where++{-+ Rules:+ + * Functions may depend on any module in the lastest Haskell Platform release+ * All functions but those in Prelude must be referred to with their full,+ qualified names (i.e. Data.List.unfoldr).+ * Each function must have a unique name (so the whole file is a loadable module).+ * Each function should have a synopisis, like:++ -- | Ordinary Haddock commentary ...+ -- > category: Categories (please use the common Hackage names)+ -- > depends : base (all packages in HP that the function depends on)+ +-}++import Control.Lens+import Control.Monad.Plus+import Control.Applicative+import qualified Data.Char+import qualified Data.Monoid+import qualified Data.List+import qualified Data.Ratio+++-- | Divide a list into parts of maximum length n.+-- > category : List+-- > depends : base+divideList :: Int -> [a] -> [[a]]+divideList n xs+ | length xs <= n = [xs]+ | otherwise = [take n xs] ++ (divideList n $ drop n xs)++-- | Group a list into sublists whereever a predicate holds. The matched element+-- is the first in the sublist.+--+-- > splitWhile isSpace "foo bar baz"+-- > ===> ["foo"," bar"," baz"]+-- >+-- > splitWhile (> 3) [1,5,4,7,0,1,2]+-- > ===> [[1],[5],[4],[7,0,1,2]]+--+-- > category : List+-- > depends : base+splitWhile :: (a -> Bool) -> [a] -> [[a]]+splitWhile p xs = case splitWhile' p xs of+ []:xss -> xss+ xss -> xss+ where+ splitWhile' p [] = [[]]+ splitWhile' p (x:xs) = case splitWhile' p xs of+ (xs:xss) -> if p x then []:(x:xs):xss else (x:xs):xss+++-- | Break up a list into parts of maximum length n, inserting the given list as separator.+-- Useful for breaking up strings, as in @breakList 80 "\n" str@.+--+-- > category : List+-- > depends : base+breakList :: Int -> [a] -> [a] -> [a]+breakList n z = Data.Monoid.mconcat . Data.List.intersperse z . divideList n++-- | Map over the indices and elements of list.+-- > category : List+-- > depends : base+mapIndexed :: (Int -> a -> b) -> [a] -> [b]+mapIndexed f as = map (uncurry f) (zip is as)+ where+ n = length as - 1+ is = [0..n]+ +-- test++-- | Duplicate an element.+-- > category: Combinator, Tuple+-- > depends: base+dup :: a -> (a,a)+dup x = (x,x)++-- | Unfold a partial function. This is a simpler version of 'Data.List.unfoldr'. +-- > category: Function, List+-- > depends: base+unf :: (a -> Maybe a) -> a -> [a]+unf f = Data.List.unfoldr (fmap dup . f)++-- |+-- Map over first elements of a list.+-- Biased on first element for shorter lists.+-- > category: List+-- > depends: base+mapF f = mapFTL f id id++-- |+-- Map over all but the first and last elements of a list.+-- Biased on middle elements for shorter lists.+-- > category: List+-- > depends: base+mapT f = mapFTL id f id++-- |+-- Map over last elements of a list.+-- Biased on last element for shorter lists.+-- > category: List+-- > depends: base+mapL f = mapFTL id id f++-- |+-- Map over first, middle and last elements of list.+-- Biased on first, then on first and last for short lists.+--+-- > category: List+-- > depends: base+mapFTL :: (a -> b) -> (a -> b) -> (a -> b) -> [a] -> [b]+mapFTL 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]++-- |+-- Extract the first consecutive sublist for which the predicate returns true, or+-- the empty list if no such sublist exists.+-- > category: List+-- > depends: base+filterOnce :: (a -> Bool) -> [a] -> [a]+filterOnce p = Data.List.takeWhile p . Data.List.dropWhile (not . p)+++-- | Returns all rotations of the given list. Given an infinite list, returns an infinite+-- list of rotated infinite lists.+-- > category: List+-- > depends: base+rots :: [a] -> [[a]]+rots xs = init (zipWith (++) (Data.List.tails xs) (Data.List.inits xs))++-- |+-- > category: List+-- > depends: base+rotl :: [a] -> [a]+rotl [] = []+rotl (x:xs) = xs ++ [x]++-- |+-- > category: List+-- > depends: base+rotr :: [a] -> [a]+rotr [] = []+rotr xs = last xs : init xs++-- |+-- > category: List+-- > depends: base+rotated :: Int -> [a] -> [a]+rotated = go+ where+ go n as + | n >= 0 = iterate rotr as !! n+ | n < 0 = iterate rotl as !! abs n+++curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d+curry3 = curry . curry . (. tripl)++uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d+uncurry3 = (. untripl) . uncurry . uncurry++untripl :: (a,b,c) -> ((a,b),c)+untripl (a,b,c) = ((a,b),c)++tripl :: ((a,b),c) -> (a,b,c)+tripl ((a,b),c) = (a,b,c)++tripr :: (a,(b,c)) -> (a,b,c)+tripr (a,(b,c)) = (a,b,c)+++-- 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))++-- | Case matching on lists.+-- > category: List+-- > depends: base+list :: r -> ([a] -> r) -> [a] -> r+list z f [] = z+list z f xs = f xs++-- | Merge lists.+-- > category: List+-- > depends: base+merge :: Ord a => [a] -> [a] -> [a]+merge = mergeBy compare++-- | Merge lists.+-- > category: List+-- > depends: base+mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]+mergeBy f = mergeBy' $ (fmap.fmap) orderingToBool f+ where+ orderingToBool LT = True+ orderingToBool EQ = True+ orderingToBool GT = False++mergeBy' :: (a -> a -> Bool) -> [a] -> [a] -> [a]+mergeBy' pred xs [] = xs+mergeBy' pred [] ys = ys+mergeBy' pred (x:xs) (y:ys) =+ case pred x y of+ True -> x: mergeBy' pred xs (y:ys)+ False -> y: mergeBy' pred (x:xs) ys++-- | Compose all functions.+-- > category: Function+-- > depends: base+composed :: [b -> b] -> b -> b+composed = Prelude.foldr (.) id++-- | Separate a ratio.+-- > category: Math+-- > depends: base+unRatio :: Integral a => Data.Ratio.Ratio a -> (a, a)+unRatio x = (Data.Ratio.numerator x, Data.Ratio.denominator x)++-- | Nicer printing of ratio as ordinary fractions.+-- > category: Math+-- > depends: base+showRatio :: (Integral a, Show a) => Data.Ratio.Ratio a -> String+showRatio (realToFrac -> (unRatio -> (x, 1))) = show x+showRatio (realToFrac -> (unRatio -> (x, y))) = "(" ++ show x ++ "/" ++ show y ++ ")"+++-- Replace all contigous ranges of equal values with [Just x, Nothing, Nothing ...]+-- > category: List+-- > depends: base+retainUpdates :: Eq a => [a] -> [Maybe a]+retainUpdates = snd . Data.List.mapAccumL g Nothing where+ g Nothing x = (Just x, Just x)+ g (Just p) x = (Just x, if p == x then Nothing else Just x)+++-- Generic version of 'replicate'.+-- > category: List+-- > depends: base+replic :: Integral a => a -> b -> [b]+replic n = replicate (fromIntegral n)++-- Swap components.+-- > category: Tuple+-- > depends: base+swap :: (a, b) -> (b, a)+swap (x, y) = (y, x)++-- Interleave a list with the next consecutive element.+--+-- For any xs+--+-- > lenght xs == length (withNext xs)+--+-- If @xs@ is a finite list+--+-- > isNothing $ snd $ last $ withNext xs == True+-- > all isJust $ snd $ init $ withNext xs == True+--+-- If @xs@ is an infinite list+--+-- > all isJust $ snd $ withNext xs == True+--+-- > category: List+-- > depends: base+withNext :: [a] -> [(a, Maybe a)]+withNext = fmap (\(p,c,n) -> (c,n)) . withPrevNext++withPrev :: [a] -> [(Maybe a, a)]+withPrev = fmap (\(p,c,n) -> (p,c)) . withPrevNext++-- withNext = go+-- where+-- go [] = []+-- go [x] = [(x, Nothing)]+-- go (x:y:rs) = (x, Just y) : withNext (y : rs)++withPrevNext :: [a] -> [(Maybe a, a, Maybe a)]+withPrevNext xs = zip3 (pure Nothing ++ fmap Just xs) xs (fmap Just (tail xs) ++ repeat Nothing)++-- Map over a list with the next consecutive element.+--+-- > category: List+-- > depends: base+mapWithNext :: (a -> Maybe a -> b) -> [a] -> [b]+mapWithNext f = map (uncurry f) . withNext++mapWithPrev :: (Maybe a -> a -> b) -> [a] -> [b]+mapWithPrev f = map (uncurry f) . withPrev++mapWithPrevNext :: (Maybe a -> a -> Maybe a -> b) -> [a] -> [b]+mapWithPrevNext f = map (uncurry3 f) . withPrevNext+++++--------++++++++toDouble :: Real a => a -> Double+toDouble = realToFrac++through :: Applicative f => + Lens' s a + -> Lens s t a b + -> Lens (f s) (f t) (f a) (f b)+through lens1 lens2 = lens getter (flip setter)+ where+ getter = fmap (view lens1)+ setter = liftA2 (over lens2 . const)+{-# INLINE through #-}++single :: Prism' [a] a+single = prism' return $ \xs -> case xs of+ [x] -> Just x+ _ -> Nothing+{-# INLINE single #-}+++floor' :: RealFrac a => a -> a+floor' = fromIntegral . floor++-- Like Data.Ord.comparing+-- (Are both variants of contramap?)+inspecting :: Eq a => (b -> a) -> b -> b -> Bool+inspecting p x y = p x == p y+
src/Music/Score/Meta.hs view
@@ -6,7 +6,6 @@ {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-}@@ -15,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -23,221 +22,181 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) ----- Provides meta-information.------ Each score supports an unlimited number of 'Reactive' meta-values.+-- Combinators for manipulating scores and related structures. ----- This is more or less based on Diagrams styles, which is in turn based--- on XMonad.+-- /Warning/ This module is experimental. -- ------------------------------------------------------------------------------------- module Music.Score.Meta (- -- * Attributes- IsAttribute,- Attribute,- wrapAttr,- unwrapAttr,+ module Music.Time.Meta, - -- * Meta-values- Meta,- -- addMeta,+ -- TODO move+ (</>),+ rcat,++ -- * Meta-events addMetaNote, addGlobalMetaNote,- runMeta,- HasMeta(..),- ) where+ fromMetaReactive, + metaAt,+ metaAtStart,+ withMeta,+ withGlobalMeta,+ withMetaAtStart,+ withGlobalMetaAtStart,+ ) where+ import Control.Applicative import Control.Arrow-import Control.Lens+import Control.Lens hiding (parts, perform)+import Control.Monad import Control.Monad.Plus-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.AffineSpace+import Data.AffineSpace.Point+import Data.Foldable (Foldable (..)) import Data.Maybe-import Data.Monoid.WithSemigroup+import Data.Ord+import Data.Ratio import Data.Semigroup-import Data.Set (Set)-import qualified Data.Set as Set import Data.String-import Data.Traversable (Traversable)-import qualified Data.Traversable as T-import Data.Typeable-import Data.Void+import Data.Traversable+import Data.VectorSpace -import Music.Pitch.Literal-import Music.Score.Note import Music.Score.Part-import Music.Score.Pitch-import Music.Score.Util-import Music.Score.Voice+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 -type IsAttribute a = (Typeable a, Monoid' a)+infixr 6 </> --- | An existential wrapper type to hold attributes.-data Attribute :: * where- Attribute :: IsAttribute a => a -> Attribute- -- TAttribute :: (Transformable a, IsAttribute a) => a -> Attribute --- | Wrap up an attribute.-wrapAttr :: IsAttribute a => a -> Attribute-wrapAttr = Attribute+-- |+-- Concatenate parts.+--+rcat :: (HasParts' a, Enum (Part a)) => [Score a] -> Score a+rcat = List.foldr (</>) mempty -unwrapAttr :: IsAttribute a => Attribute -> Maybe a-unwrapAttr (Attribute a) = cast a+-- |+-- 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 -instance Semigroup Attribute where- (Attribute a1) <> a2 = case unwrapAttr a2 of- Nothing -> a2- Just a2' -> Attribute (a1 <> a2')+ -- |+ -- Move down one voice (all parts).+ --+ moveParts :: (Integral b, HasParts' a, Enum (Part a)) => b -> Score a -> Score a+ moveParts x = parts %~ (successor x) -instance Delayable Attribute where- delay _ (Attribute a) = Attribute a-instance Stretchable Attribute where- stretch _ (Attribute a) = Attribute a+ -- |+ -- 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) --- TODO is Transformable right w.r.t. join?-newtype Meta = Meta (Map String (Reactive Attribute))- deriving (Delayable, Stretchable)+ 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) --- instance HasPart Meta where+ successor :: (Integral b, Enum a) => b -> a -> a+ successor n = iterating pred succ (fromIntegral n) -inMeta :: (Map String (Reactive Attribute) -> Map String (Reactive Attribute)) -> Meta -> Meta-inMeta f (Meta s) = Meta (f s)+ maximum' :: (Ord a, Foldable t) => a -> t a -> a+ maximum' z = option z getMax . foldMap (Option . Just . Max) -addGlobalMetaNote :: forall a b . (IsAttribute a, HasMeta b) => Note a -> b -> b-addGlobalMetaNote x = applyMeta $ addMeta' (Nothing::Maybe Int) $ noteToReactive x --- XXX-addMetaNote :: forall a b . (IsAttribute a, HasMeta b, HasPart' b) => Note a -> b -> b-addMetaNote x y = (applyMeta $ addMeta' (Just y) $ noteToReactive x) y---- Switch at time t to the given value (switch is valid until the end of the music).--- TODO might not work as we think-addMetaChange :: forall a b . (IsAttribute a, HasMeta b, HasPart' b) => Time -> a -> b -> b-addMetaChange t x y = (applyMeta $ addMeta' (Just y) $ switch t mempty (pure x)) y---runMeta :: forall a b . (HasPart' a, IsAttribute b) => Maybe a -> Meta -> Reactive b-runMeta part = fromMaybe mempty . runMeta' part--addMeta' :: forall a b . (HasPart' a, IsAttribute b) => Maybe a -> Reactive b -> Meta-addMeta' part a = Meta $ Map.singleton key $ fmap wrapAttr a- where- key = ty ++ pt- pt = show $ fmap getPart part- ty = show $ typeOf (undefined :: b)---- runMeta' :: forall a . IsAttribute a => Meta -> Maybe (Reactive a)-runMeta' :: forall a b . (HasPart' a, IsAttribute b) => Maybe a -> Meta -> Maybe (Reactive b)-runMeta' part (Meta s) = fmap (fmap (fromMaybe (error "runMeta'") . unwrapAttr)) $ Map.lookup key s--- Note: unwrapAttr should never fail- where- key = ty ++ pt- pt = show $ fmap getPart part- ty = show . typeOf $ (undefined :: b)--instance Semigroup Meta where- Meta s1 <> Meta s2 = Meta $ Map.unionWith (<>) s1 s2---- | The empty meta contains no attributes; composition of metas is--- a union of attributes; if the two metas have attributes of the--- same type they are combined according to their semigroup--- structure.-instance Monoid Meta where- mempty = Meta Map.empty- mappend = (<>)---- | Type class for things which have meta-information.-class HasMeta a where- -- | Apply meta-information by combining it (on the left) with the- -- existing meta-information.- meta :: Lens' a Meta--instance HasMeta Meta where- meta = ($)- -applyMeta :: HasMeta a => Meta -> a -> a-applyMeta m = (meta <>~ m)+addMetaNote :: forall a b . (IsAttribute a, HasMeta b{-, HasPart' b-}) => Note a -> b -> b+addMetaNote x y = (applyMeta $ toMeta (Just y) $ noteToReactive x) y --- instance (HasMeta a, HasMeta b) => HasMeta (a,b) where--- applyMeta s = applyMeta s *** applyMeta s--- --- instance HasMeta a => HasMeta [a] where--- applyMeta = fmap . applyMeta--- --- instance HasMeta b => HasMeta (a -> b) where--- applyMeta = fmap . applyMeta--- --- instance HasMeta a => HasMeta (Map k a) where--- applyMeta = fmap . applyMeta--- --- instance (HasMeta a, Ord a) => HasMeta (Set a) where--- applyMeta = Set.map . applyMeta--- +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 -newtype RehearsalMark = RehearsalMark ()- deriving (Typeable, Monoid, Semigroup)+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 rename during+-- TODO move noteToReactive :: Monoid a => Note a -> Reactive a noteToReactive n = (pure <$> n) `activate` pure mempty --- | Split a reactive into notes, as well as the values before and after the first/last update-splitReactive :: Reactive a -> Either a ((a, Time), [Note a], (Time, a))-splitReactive r = case updates r of- [] -> Left (initial r)- (t,x):[] -> Right ((initial r, t), [], (t, x))- (t,x):xs -> Right ((initial r, t), fmap note $ mrights (res $ (t,x):xs), head $ mlefts (res $ (t,x):xs))-- where-- note (t,u,x) = t <-> u =: x-- -- Always returns a 0 or more Right followed by one left- res :: [(Time, a)] -> [Either (Time, a) (Time, Time, a)]- res rs = let (ts,xs) = unzip rs in- flip fmap (withNext ts `zip` xs) $- \ ((t, mu), x) -> case mu of- Nothing -> Left (t, x)- Just u -> Right (t, u, x)-- -- lenght xs == length (withNext xs)- withNext :: [a] -> [(a, Maybe a)]- withNext = go- where- go [] = []- go [x] = [(x, Nothing)]- go (x:y:rs) = (x, Just y) : withNext (y : rs)- activate :: Note (Reactive a) -> Reactive a -> Reactive a-activate (getNote -> (view range -> (start,stop), x)) y = y `turnOn` (x `turnOff` y)+activate (view (from note) -> (view range -> (start,stop), x)) y = y `turnOn` (x `turnOff` y) where- turnOn = switch start- turnOff = switch stop+ turnOn = switchR start+ turnOff = switchR stop+
src/Music/Score/Meta/Annotations.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,11 +22,10 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) ----- Provides annotations+-- Provides a basic way annotating scores. -- ------------------------------------------------------------------------------------- - module Music.Score.Meta.Annotations ( Annotation, annotate,@@ -44,12 +43,9 @@ import Data.Typeable import Data.Void -import Music.Score.Combinators (withGlobalMeta) import Music.Score.Meta-import Music.Score.Note-import Music.Score.Ornaments (HasText, text) import Music.Score.Part-import Music.Score.Score+import Music.Score.Text (HasText, text) import Music.Time import Music.Time.Reactive @@ -66,21 +62,21 @@ -- | Annotate the whole score. annotate :: String -> Score a -> Score a-annotate str x = annotateSpan (start >-> duration x) str x+annotate str x = annotateSpan (0 >-> _duration x) str x -- | Annotate a part of the score. annotateSpan :: Span -> String -> Score a -> Score a-annotateSpan span str x = addGlobalMetaNote (sapp span $ return $ Annotation [str]) x+annotateSpan span str x = addGlobalMetaNote (transform span $ return $ Annotation [str]) x -- | Show all annotations in the score.-showAnnotations :: (HasPart' a, HasText a) => Score a -> Score a+showAnnotations :: (HasPart' a, Ord (Part a), HasText a) => Score a -> Score a showAnnotations = showAnnotations' ":" -- | Show all annotations in the score using the given prefix.-showAnnotations' :: (HasPart' a, HasText a) => String -> Score a -> Score a+showAnnotations' :: (HasPart' a, Ord (Part a), HasText a) => String -> Score a -> Score a showAnnotations' prefix = withAnnotations (flip $ \s -> foldr (text . (prefix ++ )) s) -- | Handle the annotations in a score.-withAnnotations :: (HasPart' a, HasText a) => ([String] -> Score a -> Score a) -> Score a -> Score a+withAnnotations :: (HasParts' a, HasText a) => ([String] -> Score a -> Score a) -> Score a -> Score a withAnnotations f = withGlobalMeta (f . getAnnotation)
src/Music/Score/Meta/Attribution.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,6 +22,8 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides meta-data attribution for composer, lyricist etc.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Attribution (@@ -48,6 +50,7 @@ ) where import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Foldable (Foldable) import qualified Data.Foldable as F@@ -55,7 +58,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -66,14 +68,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -120,35 +118,35 @@ -- | Set the given attribution in the given score.-attribute :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Attribution -> a -> a-attribute a x = attributeDuring (era x) a x+attribute :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Attribution -> a -> a+attribute a x = attributeDuring (_getEra x) a x -- | Set the given attribution in the given part of a score.-attributeDuring :: (HasMeta a, HasPart' a) => Span -> Attribution -> a -> a-attributeDuring s a = addGlobalMetaNote (s =: a)+attributeDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Attribution -> a -> a+attributeDuring s a = addGlobalMetaNote (view note (s, a)) -- | Set composer of the given score.-composer :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => String -> a -> a-composer t x = composerDuring (era x) t x+composer :: (HasMeta a, {-HasPart' a, -}HasPosition a) => String -> a -> a+composer t x = composerDuring (_getEra x) t x -- | Set composer of the given part of a score.-composerDuring :: (HasMeta a, HasPart' a) => Span -> String -> a -> a+composerDuring :: (HasMeta a{-, HasPart' a-}) => Span -> String -> a -> a composerDuring s x = attributeDuring s ("composer" `attribution` x) -- | Set lyricist of the given score.-lyricist :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => String -> a -> a-lyricist t x = lyricistDuring (era x) t x+lyricist :: (HasMeta a, {-HasPart' a, -}HasPosition a) => String -> a -> a+lyricist t x = lyricistDuring (_getEra x) t x -- | Set lyricist of the given part of a score.-lyricistDuring :: (HasMeta a, HasPart' a) => Span -> String -> a -> a+lyricistDuring :: (HasMeta a{-, HasPart' a-}) => Span -> String -> a -> a lyricistDuring s x = attributeDuring s ("lyricist" `attribution` x) -- | Set arranger of the given score.-arranger :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => String -> a -> a-arranger t x = arrangerDuring (era x) t x+arranger :: (HasMeta a, {-HasPart' a, -}HasPosition a) => String -> a -> a+arranger t x = arrangerDuring (_getEra x) t x -- | Set arranger of the given part of a score.-arrangerDuring :: (HasMeta a, HasPart' a) => Span -> String -> a -> a+arrangerDuring :: (HasMeta a{-, HasPart' a-}) => Span -> String -> a -> a arrangerDuring s x = attributeDuring s ("arranger" `attribution` x) -- | Extract attribution values of the given category from a score.
src/Music/Score/Meta/Barline.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,6 +22,10 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides special barlines as meta-data.+--+-- (Ordinary barlines are generated automatically, see also "Music.Score.Meta.Time").+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Barline (@@ -41,6 +45,7 @@ import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Default import Data.Foldable (Foldable)@@ -49,7 +54,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -60,14 +64,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -81,20 +81,20 @@ deriving (Eq, Ord, Show, Typeable) -- | Add a barline over the whole score.-barline :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Barline -> a -> a-barline c x = barlineDuring (era x) c x+barline :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Barline -> a -> a+barline c x = barlineDuring (_getEra x) c x -- | Add a barline over the whole score.-doubleBarline :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Barline -> a -> a+doubleBarline :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Barline -> a -> a doubleBarline = undefined -- | Add a barline over the whole score.-finalBarline :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Barline -> a -> a+finalBarline :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Barline -> a -> a finalBarline = undefined -- | Add a barline to the given score.-barlineDuring :: (HasMeta a, HasPart' a) => Span -> Barline -> a -> a-barlineDuring s c = addMetaNote (s =: (Option $ Just $ Last c))+barlineDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Barline -> a -> a+barlineDuring s c = addMetaNote $ view note (s, (Option $ Just $ Last c)) -- | Extract barlines in from the given score, using the given default barline. withBarline :: (Barline -> Score a -> Score a) -> Score a -> Score a
src/Music/Score/Meta/Clef.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,6 +22,10 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides clefs as meta-data.+--+-- /Warning/ Experimental module.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Clef (@@ -37,6 +41,7 @@ ) where import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Foldable (Foldable) import qualified Data.Foldable as F@@ -44,7 +49,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -55,14 +59,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -70,13 +70,14 @@ deriving (Eq, Ord, Show, Typeable) -- | Set clef of the given score.-clef :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Clef -> a -> a-clef c x = clefDuring (era x) c x+clef :: (HasMeta a, {-HasPart' a,-} HasPosition a) => Clef -> a -> a+clef c x = clefDuring (_getEra x) c x -- | Set clef of the given part of a score.-clefDuring :: (HasMeta a, HasPart' a) => Span -> Clef -> a -> a-clefDuring s c = addMetaNote (s =: (Option $ Just $ Last c))+clefDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Clef -> a -> a+clefDuring s c = addMetaNote $ view note (s, (Option $ Just $ Last c)) -- | Extract the clef in from the given score, using the given default clef.-withClef :: HasPart' a => Clef -> (Clef -> Score a -> Score a) -> Score a -> Score a+withClef :: {-HasPart' a =>-} Clef -> (Clef -> Score a -> Score a) -> Score a -> Score a withClef def f = withMeta (f . fromMaybe def . fmap getLast . getOption)+
src/Music/Score/Meta/Fermata.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,6 +22,8 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides fermatas.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Fermata (@@ -39,6 +41,7 @@ import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Default import Data.Foldable (Foldable)@@ -47,7 +50,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -58,14 +60,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -79,12 +77,12 @@ deriving (Eq, Ord, Show, Typeable) -- | Add a fermata over the whole score.-fermata :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Fermata -> a -> a-fermata c x = fermataDuring (era x) c x+fermata :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Fermata -> a -> a+fermata c x = fermataDuring (_getEra x) c x -- | Add a fermata to the given score.-fermataDuring :: (HasMeta a, HasPart' a) => Span -> Fermata -> a -> a-fermataDuring s c = addMetaNote (s =: (Option $ Just $ Last c))+fermataDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Fermata -> a -> a+fermataDuring s c = addMetaNote $ view note (s, (Option $ Just $ Last c)) -- | Extract fermatas in from the given score, using the given default fermata. withFermata :: (Fermata -> Score a -> Score a) -> Score a -> Score a
src/Music/Score/Meta/Key.hs view
@@ -15,7 +15,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -23,6 +23,8 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides key signature meta-data.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Key (@@ -42,6 +44,7 @@ ) where import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Foldable (Foldable) import qualified Data.Foldable as F@@ -49,7 +52,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -60,14 +62,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -121,12 +119,12 @@ isMinorKey = not . isMajorKey -- | Set the key signature of the given score.-keySignature :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => KeySignature -> a -> a-keySignature c x = keySignatureDuring (era x) c x+keySignature :: (HasMeta a, {-HasPart' a, -}HasPosition a) => KeySignature -> a -> a+keySignature c x = keySignatureDuring (_getEra x) c x -- | Set the key signature of the given part of a score.-keySignatureDuring :: (HasMeta a, HasPart' a) => Span -> KeySignature -> a -> a-keySignatureDuring s c = addGlobalMetaNote (s =: (Option $ Just $ Last c))+keySignatureDuring :: (HasMeta a{-, HasPart' a-}) => Span -> KeySignature -> a -> a+keySignatureDuring s c = addGlobalMetaNote $ view note (s, (Option $ Just $ Last c)) -- | Extract all key signatures from the given score, using the given default key signature. withKeySignature :: KeySignature -> (KeySignature -> Score a -> Score a) -> Score a -> Score a
src/Music/Score/Meta/RehearsalMark.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,6 +22,10 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides tempo meta-data.+--+-- /Warning/ This is not supported by any backends yet.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.RehearsalMark (@@ -38,6 +42,7 @@ import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Default import Data.Foldable (Foldable)@@ -46,7 +51,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -57,14 +61,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -92,11 +92,11 @@ -- metronome :: Duration -> Bpm -> Tempo -- metronome noteVal bpm = Tempo Nothing (Just noteVal) $ 60 / (bpm * noteVal) -rehearsalMark :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => RehearsalMark -> a -> a-rehearsalMark c x = rehearsalMarkDuring (era x) c x+rehearsalMark :: (HasMeta a, {-HasPart' a, -}HasPosition a) => RehearsalMark -> a -> a+rehearsalMark c x = rehearsalMarkDuring (_getEra x) c x -rehearsalMarkDuring :: (HasMeta a, HasPart' a) => Span -> RehearsalMark -> a -> a-rehearsalMarkDuring s x = addGlobalMetaNote (s =: x)+rehearsalMarkDuring :: (HasMeta a{-, HasPart' a-}) => Span -> RehearsalMark -> a -> a+rehearsalMarkDuring s x = addGlobalMetaNote $ view note (s, x) withRehearsalMark :: (RehearsalMark -> Score a -> Score a) -> Score a -> Score a withRehearsalMark = withGlobalMeta
src/Music/Score/Meta/Tempo.hs view
@@ -15,7 +15,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -23,6 +23,8 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides tempo meta-data.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Tempo (@@ -56,7 +58,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -68,17 +69,11 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators-import Music.Score.Convert import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice-import Music.Time-import Music.Time.Reactive+import Music.Score.Internal.Util+import Music.Time hiding (time) type Bpm = Duration type NoteValue = Duration@@ -110,8 +105,18 @@ showR (realToFrac -> (unRatio -> (x, y))) = "(" ++ show x ++ "/" ++ show y ++ ")" instance Default Tempo where- def = metronome (1/1) 60+ def = mempty +instance Semigroup Tempo where+ (<>) = mappend++instance Monoid Tempo where+ mempty = metronome (1/4) 120+ a `mappend` b+ | a == mempty = b+ | b == mempty = a+ | otherwise = a+ -- | Create a tempo from a duration and a number of beats per minute. -- -- For example @metronome (1/2) 48@ means 48 half notes per minute.@@ -161,12 +166,12 @@ tempoToDuration (Tempo _ _ x) = x -- | Set the tempo of the given score.-tempo :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Tempo -> a -> a-tempo c x = tempoDuring (era x) c x+tempo :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Tempo -> a -> a+tempo c x = tempoDuring (_getEra x) c x -- | Set the tempo of the given part of a score.-tempoDuring :: (HasMeta a, HasPart' a) => Span -> Tempo -> a -> a-tempoDuring s c = addGlobalMetaNote (s =: (optionFirst c))+tempoDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Tempo -> a -> a+tempoDuring s c = addGlobalMetaNote $ view note (s, c) -- TODO move@@ -175,40 +180,44 @@ inSpan' (view range -> (t,u)) x = t <= x && x < u +mkNote s x = view note (s, x)++++-- | Extract all tempi from the given score, using the given default tempo.+-- withTempo :: (Tempo -> Score a -> Score a) -> Score a -> Score a+-- withTempo f = withGlobalMeta (f . fromMaybe def . fmap getFirst . getOption)++renderTempo :: Score a -> Score a+renderTempo = error "renderTempo: Not implemented"+{-+ -- | Split a reactive into notes, as well as the values before and after the first/last update -- TODO fails if not positive--- TODO same as reactiveToVoice+-- TODO consolidate reactiveIn :: Span -> Reactive a -> [Note a] reactiveIn s r- | duration s <= 0 = error "reactiveIn: Needs positive duration"- | otherwise = let r2 = trim s (fmap optionFirst r)+ | _duration s <= 0 = error "reactiveIn: Needs positive duration"+ | otherwise = let r2 = trimR s (fmap optionFirst r) in fmap (fmap $ fromJust . unOptionFirst) $ case updates r2 of -- We have at least 2 value because of trim- (frl -> ((t,x),[],(u,_))) -> [t <-> u =: x] -- one note+ (frl -> ((t,x),[],(u,_))) -> [view note (t <-> u, x)] -- one note (frl -> ((t0,x0), unzip -> (tn,xn), (tl,_))) -> let times = [t0] ++ tn spans = mapWithNext (\t mu -> t <-> fromMaybe tl mu) times values = [x0] ++ xn- in zipWith (=:) spans values------- | Extract all tempi from the given score, using the given default tempo.--- withTempo :: (Tempo -> Score a -> Score a) -> Score a -> Score a--- withTempo f = withGlobalMeta (f . fromMaybe def . fmap getFirst . getOption)+ in zipWith mkNote spans values -renderTempo :: Score a -> Score a renderTempo sc = flip composed sc $ fmap renderTempoScore- $ tempoRegions (era sc)- $ tempoRegions0 (era sc)+ $ tempoRegions (_getEra sc)+ $ tempoRegions0 (_getEra sc) $ getTempoChanges defTempo sc renderTempoTest :: Score a -> [TempoRegion] renderTempoTest sc = id- $ tempoRegions (era sc)- $ tempoRegions0 (era sc)+ $ tempoRegions (_getEra sc)+ $ tempoRegions0 (_getEra sc) $ getTempoChanges defTempo sc @@ -219,17 +228,17 @@ defTempo = metronome (1/1) 60 getTempoChanges :: Tempo -> Score a -> Reactive Tempo-getTempoChanges def = fmap (fromMaybe def . unOptionFirst) . runMeta (Nothing::Maybe Int) . (view meta)+getTempoChanges def = fmap (fromMaybe def . unOptionFirst) . fromMetaReactive (Nothing::Maybe Int) . (view meta) -- | Get all tempo regions for the given span. tempoRegions0 :: Span -> Reactive Tempo -> [TempoRegion0] tempoRegions0 s r = fmap f $ s `reactiveIn` r where- f (getNote -> (view delta -> (t,u),x)) = TempoRegion0 t u (tempoToDuration x)+ f (view (from note) -> (view delta -> (t,u),x)) = TempoRegion0 t u (tempoToDuration x) tempoRegions :: Span -> [TempoRegion0] -> [TempoRegion]-tempoRegions s = snd . List.mapAccumL f (onset s, onset s) -- XXX offset?+tempoRegions s = snd . List.mapAccumL f (_onset s, _onset s) -- XXX offset? where f (nt,st) (TempoRegion0 _ d x) = ((nt .+^ d, st .+^ (d*x)), TempoRegion nt (nt .+^ d) st x@@ -256,7 +265,7 @@ -- TODO use lens renderTempoScore :: TempoRegion -> Score a -> Score a-renderTempoScore tr = over notes $ fmap $ over (note_ . _1) $ renderTempoSpan tr+renderTempoScore tr = over notes $ fmap $ over (from note . _1) $ renderTempoSpan tr data TempoRegion0 =@@ -277,10 +286,7 @@ deriving (Eq, Ord, Show) tempoRegionNotated (TempoRegion t u _ _) = t <-> u---- TODO add to Music.Score.Note-note_ :: Iso (Note a) (Note b) (Span, a) (Span, b)-note_ = iso getNote (uncurry (=:))+-} -- span :: Iso (Note a) (Note b) Span Span @@ -306,10 +312,11 @@ -- TODO consolidate-optionFirst = Option . Just . First-unOptionFirst = fmap getFirst . getOption+-- optionFirst = Option . Just . First+-- unOptionFirst = fmap getFirst . getOption -- TODO move frl [] = error "frl: No value" frl [x] = error "frl: Just one value" frl xs = (head xs, (tail.init) xs, last xs)+
src/Music/Score/Meta/Time.hs view
@@ -15,7 +15,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -23,6 +23,8 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides time signatures and related meta-data.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Time (@@ -52,15 +54,14 @@ ) where import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus-import Control.Lens (view) 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.Maybe-import Data.Monoid.WithSemigroup import Data.Ratio ((%)) import Data.Semigroup import Data.Set (Set)@@ -72,16 +73,11 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice-import Music.Time-import Music.Time.Reactive+import Music.Score.Internal.Util+import Music.Time hiding (time) -- | -- A time signature is a sequence of beat numbers and a note value (i.e. an expression on the@@ -167,18 +163,18 @@ getTimeSignature (TimeSignature x) = x -- | Set the time signature of the given score.-timeSignature :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => TimeSignature -> a -> a-timeSignature c x = timeSignatureDuring (start <-> offset x) c x+timeSignature :: (HasMeta a, {-HasPart' a,-} HasPosition a) => TimeSignature -> a -> a+timeSignature c x = timeSignatureDuring (0 <-> _offset x) c x --- use (onset x <-> offset x) instead of (start <-> offset x)+-- use (_onset x <-> _offset x) instead of (0 <-> _offset x) -- timeSignature' c x = timeSignatureDuring (era x) c x -- | Set the time signature of the given part of a score.-timeSignatureDuring :: (HasMeta a, HasPart' a) => Span -> TimeSignature -> a -> a-timeSignatureDuring s c = addGlobalMetaNote (s =: optionFirst c)+timeSignatureDuring :: (HasMeta a{-, HasPart' a-}) => Span -> TimeSignature -> a -> a+timeSignatureDuring s c = addGlobalMetaNote $ view note (s, optionFirst c) getTimeSignatures :: TimeSignature -> Score a -> Reactive TimeSignature-getTimeSignatures def = fmap (fromMaybe def . unOptionFirst) . runMeta (Nothing::Maybe Int) . (view meta)+getTimeSignatures def = fmap (fromMaybe def . unOptionFirst) . fromMetaReactive (Nothing::Maybe Int) . (view meta) getTimeSignatureChanges :: TimeSignature -> Score a -> [(Time, TimeSignature)] getTimeSignatureChanges def = updates . getTimeSignatures def@@ -245,6 +241,5 @@ -- TODO consolidate optionFirst = Option . Just . First unOptionFirst = fmap getFirst . getOption-
src/Music/Score/Meta/Title.hs view
@@ -14,7 +14,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -22,6 +22,8 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-- Provides various forms of title, subtitle etc. and related meta-data.+-- ------------------------------------------------------------------------------------- module Music.Score.Meta.Title (@@ -48,6 +50,7 @@ ) where import Control.Arrow+import Control.Lens (view) import Control.Monad.Plus import Data.Foldable (Foldable) import qualified Data.Foldable as F@@ -55,7 +58,6 @@ import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe-import Data.Monoid.WithSemigroup import Data.Semigroup import Data.Set (Set) import qualified Data.Set as Set@@ -66,14 +68,10 @@ import Data.Void import Music.Pitch.Literal-import Music.Score.Combinators import Music.Score.Meta-import Music.Score.Note import Music.Score.Part import Music.Score.Pitch-import Music.Score.Score-import Music.Score.Util-import Music.Score.Voice+import Music.Score.Internal.Util import Music.Time import Music.Time.Reactive @@ -122,28 +120,28 @@ getTitleAt (Title t) n = fmap getLast . getOption . t $ n -- | Set title of the given score.-title :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Title -> a -> a-title t x = titleDuring (era x) t x+title :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Title -> a -> a+title t x = titleDuring (_getEra x) t x -- | Set title of the given part of a score.-titleDuring :: (HasMeta a, HasPart' a) => Span -> Title -> a -> a-titleDuring s t = addGlobalMetaNote (s =: t)+titleDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Title -> a -> a+titleDuring s t = addGlobalMetaNote $ view note (s, t) -- | Set subtitle of the given score.-subtitle :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Title -> a -> a-subtitle t x = subtitleDuring (era x) t x+subtitle :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Title -> a -> a+subtitle t x = subtitleDuring (_getEra x) t x -- | Set subtitle of the given part of a score.-subtitleDuring :: (HasMeta a, HasPart' a) => Span -> Title -> a -> a-subtitleDuring s t = addGlobalMetaNote (s =: denoteTitle t)+subtitleDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Title -> a -> a+subtitleDuring s t = addGlobalMetaNote $ view note (s, denoteTitle t) -- | Set subsubtitle of the given score.-subsubtitle :: (HasMeta a, HasPart' a, HasOnset a, HasOffset a) => Title -> a -> a-subsubtitle t x = subsubtitleDuring (era x) t x+subsubtitle :: (HasMeta a, {-HasPart' a, -}HasPosition a) => Title -> a -> a+subsubtitle t x = subsubtitleDuring (_getEra x) t x -- | Set subsubtitle of the given part of a score.-subsubtitleDuring :: (HasMeta a, HasPart' a) => Span -> Title -> a -> a-subsubtitleDuring s t = addGlobalMetaNote (s =: denoteTitle (denoteTitle t))+subsubtitleDuring :: (HasMeta a{-, HasPart' a-}) => Span -> Title -> a -> a+subsubtitleDuring s t = addGlobalMetaNote $ view note (s, denoteTitle (denoteTitle t)) -- | Extract the title in from the given score. withTitle :: (Title -> Score a -> Score a) -> Score a -> Score a
− src/Music/Score/Note.hs
@@ -1,99 +0,0 @@--{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}--module Music.Score.Note (- Note,- getNote,- getNoteSpan,- getNoteValue,- (=:),- ) where--import Control.Applicative-import Control.Comonad-import Control.Comonad.Env-import Control.Monad--import Data.Foldable (Foldable)-import qualified Data.Foldable as F-import Data.PairMonad ()-import Data.Traversable (Traversable)-import qualified Data.Traversable as T--import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Pitch-import Music.Time--newtype Note a = Note { getNote_ :: (Span, a) }- deriving (Eq, Ord, Show, {-Read, -}Functor, Applicative, Monad, Comonad, Foldable, Traversable)---- |--- Deconstruct a note.------ Typically used with the @ViewPatterns@ extension, as in------ > foo (getNote -> (s,x)) = ...----getNote :: Note a -> (Span, a)-getNote (Note x) = x---- | Get the span of the note. Same as 'era' and 'ask'.-getNoteSpan :: Note a -> Span-getNoteSpan = fst . getNote---- | Get the value of the note. Same as 'extract'.-getNoteValue :: Note a -> a-getNoteValue = snd . getNote---- Note that--- extract = getNoteValue--- ask = getNoteSpan--instance ComonadEnv Span Note where- ask = getNoteSpan--instance Delayable (Note a) where- delay n (Note (s,x)) = Note (delay n s, x)--instance Stretchable (Note a) where- stretch n (Note (s,x)) = Note (stretch n s, x)--instance HasOnset (Note a) where- onset (Note (s,x)) = onset s--instance HasOffset (Note a) where- offset (Note (s,x)) = offset s--instance IsPitch a => IsPitch (Note a) where- fromPitch = pure . fromPitch--instance IsDynamics a => IsDynamics (Note a) where- fromDynamics = pure . fromDynamics--instance IsInterval a => IsInterval (Note a) where- fromInterval = pure . fromInterval--type instance Pitch (Note a) = Pitch a-instance HasGetPitch a => HasGetPitch (Note a) where- __getPitch = __getPitch . getNoteValue-instance HasSetPitch a b => HasSetPitch (Note a) (Note b) where- type SetPitch g (Note a) = Note (SetPitch g a)- __mapPitch f = fmap (__mapPitch f)----- | Construct a note from a span and value.------ Typically used with the span constructors as in:------ > 0 <-> 2 =: c--- > 0 >-> 1 =: d----(=:) :: Span -> a -> Note a-s =: x = Note (s,x)
− src/Music/Score/Ornaments.hs
@@ -1,203 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides functions for manipulating ornaments (and miscellaneous stuff to be--- given its own module soon...).--------------------------------------------------------------------------------------------module Music.Score.Ornaments (- -- * Tremolo- HasTremolo(..),- TremoloT(..),- tremolo,-- -- * Text- HasText(..),- TextT(..),- text,-- -- * Harmonics- HasHarmonic(..),- HarmonicT(..),- harmonic,- artificial,-- -- * Slides and glissando- HasSlide(..),- SlideT(..),- slide,- glissando,- ) where--import Control.Applicative-import Control.Lens-import Data.Foldable-import Data.Foldable-import Data.Ratio-import Data.Semigroup-import Data.Typeable--import Music.Score.Combinators-import Music.Score.Part-import Music.Score.Score-import Music.Score.Voice-import Music.Time--class HasTremolo a where- setTrem :: Int -> a -> a--newtype TremoloT a = TremoloT { getTremoloT :: (Sum Int, a) }- deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad)--instance HasTremolo (TremoloT a) where- setTrem n (TremoloT (_,x)) = TremoloT (Sum n,x)--instance HasTremolo b => HasTremolo (a, b) where- setTrem n = fmap (setTrem n)--instance HasTremolo a => HasTremolo (Score a) where- setTrem n = fmap (setTrem n)------class HasText a where- addText :: String -> a -> a--newtype TextT a = TextT { getTextT :: ([String], a) }- deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad)--instance HasText (TextT a) where- addText s (TextT (t,x)) = TextT (t ++ [s],x)--instance HasText a => HasText (b, a) where- addText s = fmap (addText s)--instance HasText a => HasText (Score a) where- addText s = fmap (addText s)------- 0 for none, positive for natural, negative for artificial-class HasHarmonic a where- setNatural :: Bool -> a -> a- setHarmonic :: Int -> a -> a---- (isNatural, overtone series index where 0 is fundamental)-newtype HarmonicT a = HarmonicT { getHarmonicT :: ((Any, Sum Int), a) }- deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad)--instance HasHarmonic (HarmonicT a) where- setNatural b (HarmonicT ((_,n),x)) = HarmonicT ((Any b,n),x)- setHarmonic n (HarmonicT ((nat,_),x)) = HarmonicT ((nat,Sum n),x)--instance HasHarmonic a => HasHarmonic (b, a) where- setNatural b = fmap (setNatural b)- setHarmonic n = fmap (setHarmonic n)--instance HasHarmonic a => HasHarmonic (Score a) where- setNatural b = fmap (setNatural b)- setHarmonic n = fmap (setHarmonic n)----class HasSlide a where- setBeginGliss :: Bool -> a -> a- setBeginSlide :: Bool -> a -> a- setEndGliss :: Bool -> a -> a- setEndSlide :: Bool -> a -> a---- (eg,es,a,bg,bs)-newtype SlideT a = SlideT { getSlideT :: (((Any, Any), (Any, Any)), a) }- deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad)--instance Wrapped (SlideT a) where- type Unwrapped (SlideT a) = (((Any, Any), (Any, Any)), a)- _Wrapped' = iso getSlideT SlideT --bg, bs, eg, es :: Lens' (SlideT a) Any-bg = _Wrapped' . _1 . _2 . _1-bs = _Wrapped' . _1 . _2 . _2-eg = _Wrapped' . _1 . _1 . _1-es = _Wrapped' . _1 . _1 . _2--instance HasSlide (SlideT a) where- setBeginGliss x = bg .~ Any x- setBeginSlide x = bs .~ Any x- setEndGliss x = eg .~ Any x- setEndSlide x = es .~ Any x--instance HasSlide a => HasSlide (b, a) where- setBeginGliss n = fmap (setBeginGliss n)- setBeginSlide n = fmap (setBeginSlide n)- setEndGliss n = fmap (setEndGliss n)- setEndSlide n = fmap (setEndSlide n)--instance HasSlide a => HasSlide (Score a) where- setBeginGliss n = fmap (setBeginGliss n)- setBeginSlide n = fmap (setBeginSlide n)- setEndGliss n = fmap (setEndGliss n)- setEndSlide n = fmap (setEndSlide n)---- |--- Set the number of tremolo divisions for all notes in the score.----tremolo :: HasTremolo a => Int -> a -> a-tremolo = setTrem---- |--- Attach the given text to the first note in the score.----text :: (HasPart' a, HasText a) => String -> Score a -> Score a-text s = mapPhrase (addText s) id id---- |--- Add a slide between the first and the last note.----slide :: (HasPart' a, HasSlide a) => Score a -> Score a-slide = mapPhrase (setBeginSlide True) id (setEndSlide True)---- |--- Add a glissando between the first and the last note.----glissando :: (HasPart' a, HasSlide a) => Score a -> Score a-glissando = mapPhrase (setBeginGliss True) id (setEndGliss True)---- |--- Make all notes natural harmonics on the given overtone (1 for octave, 2 for fifth etc).--- Sounding pitch is unaffected, but notated output is transposed automatically.----harmonic :: HasHarmonic a => Int -> a -> a-harmonic n = setNatural True . setHarmonic n--- TODO verify this can actually be played---- |--- Make all notes natural harmonics on the given overtone (1 for octave, 2 for fifth etc).--- Sounding pitch is unaffected, but notated output is transposed automatically.----artificial :: HasHarmonic a => a -> a-artificial = setNatural False . setHarmonic 3-
src/Music/Score/Part.hs view
@@ -3,16 +3,24 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -24,94 +32,295 @@ -- ------------------------------------------------------------------------------------- - module Music.Score.Part (- -- * Part representation+ -- ** Articulation type functions Part,+ SetPart,+ -- ** Accessing parts+ HasParts(..), HasPart(..), HasPart',+ HasParts',+ part',+ parts',++ -- * Listing parts+ allParts,++ -- * Extracting parts+ extracted,+ extracted',+ extractPart,+ extractParts,+ extractParts',++ -- * Manipulating parts++ -- * Part representation PartT(..),- getParts,++ -- Part,+ -- HasPart(..),+ -- HasPart',+ -- PartT(..),+ -- getParts, ) where import Control.Applicative import Control.Comonad+import Control.Lens hiding (parts, transform) import Control.Monad.Plus import Data.Default import Data.Foldable-import qualified Data.List as List-import Data.Ord (comparing)+import Data.Functor.Couple+import qualified Data.List as List+import qualified Data.List as List+import Data.Ord (comparing) import Data.PairMonad import Data.Ratio import Data.Semigroup import Data.Traversable import Data.Typeable +import Music.Dynamics.Literal+import Music.Pitch.Literal+import Music.Score.Ties+import Music.Score.Internal.Util (through) import Music.Time+import Music.Time.Internal.Transform --- | Associated part type. Should normally implement 'Ord' and 'Show'.-type family Part a :: * +-- |+-- Parts type.+--+type family Part (s :: *) :: * -- Part s = a -- |--- Class of types with an associated part.+-- Part type. ----- The part type can be any ordered type. A 'Show' instance is also--- required from printing the name of the part in output.+type family SetPart (b :: *) (s :: *) :: * -- Part b s = t++-- |+-- Class of types that provide a single part. ---class HasPart a where- -- | Get the voice of the given note.- getPart :: a -> Part a+class (HasParts s t) => HasPart s t where - -- | Set the voice of the given note.- setPart :: Part a -> a -> a+ -- | Part type.+ part :: Lens s t (Part s) (Part t) - -- | Modify the voice of the given note.- modifyPart :: (Part a -> Part a) -> a -> a+-- |+-- Class of types that provide a part traversal.+--+class (Transformable (Part s),+ Transformable (Part t)+ -- , SetPart (Part t) s ~ t+ ) => HasParts s t where - setPart n = modifyPart (const n)- modifyPart f x = x+ -- | Part type.+ parts :: Traversal s t (Part s) (Part t) -newtype PartT n a = PartT { getPartT :: (n, a) }- deriving (Eq, Ord, Show, Functor, Applicative, Comonad, Monad, Typeable)+type HasPart' a = HasPart a a+type HasParts' a = HasParts a a -type instance Part () = Integer-type instance Part Double = Integer-type instance Part Float = Integer-type instance Part Int = Integer+-- |+-- Part type.+--+part' :: (HasPart s t, s ~ t) => Lens' s (Part s)+part' = part++-- |+-- Part type.+--+parts' :: (HasParts s t, s ~ t) => Traversal' s (Part s)+parts' = parts++type instance Part Bool = Bool+type instance SetPart a Bool = a+instance (b ~ Part b, Transformable b) => HasPart Bool b where+ part = ($)+instance (b ~ Part b, Transformable b) => HasParts Bool b where+ parts = ($)++type instance Part Ordering = Ordering+type instance SetPart a Ordering = a+instance (b ~ Part b, Transformable b) => HasPart Ordering b where+ part = ($)+instance (b ~ Part b, Transformable b) => HasParts Ordering b where+ parts = ($)++type instance Part () = ()+type instance SetPart a () = a+instance (b ~ Part b, Transformable b) => HasPart () b where+ part = ($)+instance (b ~ Part b, Transformable b) => HasParts () b where+ parts = ($)++type instance Part Int = Int+type instance SetPart a Int = a+instance HasPart Int Int where+ part = ($)+instance HasParts Int Int where+ parts = ($)+ type instance Part Integer = Integer-type instance Part (Ratio a) = Integer+type instance SetPart a Integer = a+instance HasPart Integer Integer where+ part = ($)+instance HasParts Integer Integer where+ parts = ($) -instance HasPart () where { getPart _ = def }-instance HasPart Double where { getPart _ = def }-instance HasPart Float where { getPart _ = def }-instance HasPart Int where { getPart _ = def }-instance HasPart Integer where { getPart _ = def }-instance Integral a => HasPart (Ratio a) where { getPart _ = def }+type instance Part Float = Float+type instance SetPart a Float = a+instance HasPart Float Float where+ part = ($)+instance HasParts Float Float where+ parts = ($) -type instance Part (PartT n a) = n-instance HasPart (PartT n a) where- getPart (PartT (v,_)) = v- modifyPart f (PartT (v,x)) = PartT (f v, x)+type instance Part (c,a) = Part a+type instance SetPart b (c,a) = (c,SetPart b a)+type instance Part [a] = Part a+type instance SetPart b [a] = [SetPart b a]+type instance Part (Maybe a) = Part a+type instance SetPart b (Maybe a) = Maybe (SetPart b a)+type instance Part (Either c a) = Part a+type instance SetPart b (Either c a) = Either c (SetPart b a) -type instance Part (a,b) = Part b-instance HasPart b => HasPart (a,b) where- getPart (a,b) = getPart b- modifyPart f (a,b) = (a, modifyPart f b)+instance HasPart a b => HasPart (c, a) (c, b) where+ part = _2 . part +instance HasParts a b => HasParts (c, a) (c, b) where+ parts = traverse . parts++instance HasParts a b => HasParts [a] [b] where+ parts = traverse . parts++instance HasParts a b => HasParts (Maybe a) (Maybe b) where+ parts = traverse . parts++instance HasParts a b => HasParts (Either c a) (Either c b) where+ parts = traverse . parts+++type instance Part (Note a) = Part a+type instance SetPart g (Note a) = Note (SetPart g a)++instance (HasPart a b) => HasPart (Note a) (Note b) where+ part = _Wrapped . whilstL part++instance (HasParts a b) => HasParts (Note a) (Note b) where+ parts = _Wrapped . whilstL parts+ -- |--- Like 'HasPart', but enforces the part to be ordered.--- This is usually required for part separation and traversal.+-- List all the parts ---type HasPart' a = (Show (Part a), Ord (Part a), Default (Part a), HasPart a)---- TODO unify with class above as in Pitch?+allParts :: (Ord (Part a), HasParts' a) => a -> [Part a]+allParts = List.nub . List.sort . toListOf parts -- |--- Get all parts in the given score. Returns a list of parts.+-- List all the parts ----- > Score a -> [Part]+extractPart :: (Eq (Part a), HasPart' a) => Part a -> Score a -> Score a+extractPart = extractPartG++extractPartG :: (Eq (Part a), MonadPlus f, HasPart' a) => Part a -> f a -> f a+extractPartG p x = head $ (\p s -> filterPart (== p) s) <$> [p] <*> return x++-- |+-- List all the parts ---getParts :: (Foldable t, HasPart' a) => t a -> [Part a]-getParts = List.sort . List.nub . fmap getPart . toList+extractParts :: (Ord (Part a), HasPart' a) => Score a -> [Score a]+extractParts = extractPartsG++extractPartsG :: (+ MonadPlus f, HasParts' (f a), HasPart' a, + Part (f a) ~ Part a, Ord (Part a)+ ) => f a -> [f a]+extractPartsG x = (\p s -> filterPart (== p) s) <$> allParts x <*> return x++filterPart :: (MonadPlus f, HasPart a a) => (Part a -> Bool) -> f a -> f a+filterPart p = mfilter (\x -> p (x ^. part))++extractParts' :: (Ord (Part a), HasPart' a) => Score a -> [(Part a, Score a)]+extractParts' x = zip (allParts x) (extractParts x)++extracted :: (Ord (Part a), HasPart' a{-, HasPart a b-}) => Iso (Score a) (Score b) [Score a] [Score b]+extracted = iso extractParts mconcat++extracted' :: (Ord (Part a), Ord (Part b), HasPart' a, HasPart' b) => Iso (Score a) (Score b) [(Part a, Score a)] [(Part b, Score b)]+extracted' = iso extractParts' $ mconcat . fmap (uncurry $ set parts')++++newtype PartT n a = PartT { getPartT :: (n, a) }+ deriving (Eq, Ord, Show, Typeable, Functor,+ Applicative, Comonad, Monad, Transformable)++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (PartT p a) where+ type Unwrapped (PartT p a) = (p, a)+ _Wrapped' = iso getPartT PartT++instance Rewrapped (PartT p a) (PartT p' b)+++type instance Part (PartT p a) = p+type instance SetPart p' (PartT p a) = PartT p' a++instance (Transformable p, Transformable p') => HasPart (PartT p a) (PartT p' a) where+ part = _Wrapped . _1++instance (Transformable p, Transformable p') => HasParts (PartT p a) (PartT p' a) where+ parts = _Wrapped . _1+++instance (IsPitch a, Enum n) => IsPitch (PartT n a) where+ fromPitch l = PartT (toEnum 0, fromPitch l)++instance (IsDynamics a, Enum n) => IsDynamics (PartT n a) where+ fromDynamics l = PartT (toEnum 0, fromDynamics l)++instance Reversible a => Reversible (PartT p a) where+ rev = fmap rev++instance Tiable a => Tiable (PartT n a) where+ toTied (PartT (v,a)) = (PartT (v,b), PartT (v,c)) where (b,c) = toTied a+++type instance Part (Segment a) = Segment (Part a)+type instance SetPart (Segment g) (Segment a) = Segment (SetPart g a)++instance (HasPart a a, HasPart a b) => HasParts (Segment a) (Segment b) where+ parts = through part part+instance (HasPart a a, HasPart a b) => HasPart (Segment a) (Segment b) where+ part = through part part++type instance Part (Behavior a) = Behavior (Part a)+type instance SetPart (Behavior g) (Behavior a) = Behavior (SetPart g a)++instance (HasPart a a, HasPart a b) => HasParts (Behavior a) (Behavior b) where+ parts = through part part+instance (HasPart a a, HasPart a b) => HasPart (Behavior a) (Behavior b) where+ part = through part part++type instance Part (Score a) = Part a+type instance SetPart g (Score a) = Score (SetPart g a)++instance (HasParts a b) => HasParts (Score a) (Score b) where+ parts =+ _Wrapped . _2 -- into NScore+ . _Wrapped+ . traverse+ . _Wrapped -- this needed?+ . whilstL parts++type instance Part (Voice a) = Part a+type instance SetPart g (Voice a) = Voice (SetPart g a)++instance (HasParts a b) => HasParts (Voice a) (Voice b) where+ parts =+ _Wrapped+ . traverse+ . _Wrapped -- this needed?+ . whilstLD parts+
+ src/Music/Score/Phrases.hs view
@@ -0,0 +1,243 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- This module provides phrase-wise traversal.+--+-- /Warning/ This module is experimental.+--+-------------------------------------------------------------------------------------++module Music.Score.Phrases (+ -- * HasPhrases class+ HasPhrases(..),+ HasPhrases',+ phrases,+ phrases',++ -- * Phrase types etc+ Phrase,+ MVoice,+ PVoice,++ -- ** Utility+ mvoicePVoice,+ unsafeMvoicePVoice,+ singleMVoice,+ ) where++import Control.Applicative+import Control.Exception (assert)+import Control.Lens+import Control.Monad.Plus+import Data.AffineSpace+import qualified Data.List as List+import Data.Maybe+import Data.Ord+import Data.Semigroup++import Music.Score.Part+import Music.Score.Convert+import Music.Time+++-- |+-- For a phrase, we simply use a voice without rests.+--+-- To represent a sequence of phrases we provide two equivalent representations:+--+-- * 'MVoice' is a sequence of notes/chords or rests. All consecutive non-rests consitute a phrase.+--+-- * 'PVoice' is a sequence of phrases or durations.+--+type Phrase a = Voice a++-- |+-- A sequence of phrases or rests, represented as notes or rests.+--+-- Each consecutive sequence of non-rest elements is considered to be a phrase.+-- For an explicit representation of the phrase structure, see 'mvoicePVoice'.+--+type MVoice a = Voice (Maybe a)++-- |+-- A sequence of phrases or rests, represented with explicit phrase structure.+--+type PVoice a = [Either Duration (Phrase a)]+++{-+class HasPhrases a b | a -> b where+ mvoices :: Traversal' a (MVoice b)++instance HasPhrases (MVoice a) a where+ mvoices = id++instance HasPhrases (PVoice a) a where+ -- Note: This is actually OK in 'phrases', as that just becomes (id . each . _Right)+ mvoices = from unsafeMvoicePVoice++instance (HasPart' a, Transformable a, Ord (Part a)) => HasPhrases (Score a) a where+ mvoices = extracted . each . singleMVoice+-}++-- |+-- Classes that provide a phrase traversal.+--+class HasPhrases s t a b | s -> a, t -> b, s b -> t, t a -> s where+ mvoices :: Traversal s t (MVoice a) (MVoice b)++-- | Traverses all phrases in a voice.+instance HasPhrases (MVoice a) (MVoice b) a b where+ mvoices = id++ -- | Traverses all phrases in a voice.+instance HasPhrases (PVoice a) (PVoice b) a b where+ -- Note: This is actually OK in 'phr', as that just becomes (id . each . _Right)+ mvoices = from unsafeMvoicePVoice++-- | Traverses all phrases in each voice, using 'extracted'.+instance (HasPart' a, {-HasPart a b, -}{-Transformable a,-} Ord (Part a)) =>+ HasPhrases (Score a) (Score b) a b where+ mvoices = extracted . each . singleMVoice++type HasPhrases' s a = HasPhrases s s a a++{-+Phrase traversal for score:++phrasesS :: (Ord (Part a), HasPart' a, Transformable a) => Traversal' (Score a) (Phrase a)+phrasesS = extracted . each . singleMVoice . mvoicePVoice . each . _Right++More generally:+-}++-- |+-- A simple generic phrase-traversal.+--+phrases' :: HasPhrases' s a => Traversal' s (Phrase a)+phrases' = phrases++-- |+-- A generic phrase-traversal.+--+phrases :: HasPhrases s t a b => Traversal s t (Phrase a) (Phrase b)+phrases = mvoices . mvoicePVoice . each . _Right++-- |+-- View an 'MVoice' as a 'PVoice'.+--+mvoicePVoice :: Lens (MVoice a) (MVoice b) (PVoice a) (PVoice b)+mvoicePVoice = unsafeMvoicePVoice+-- TODO meta++-- |+-- View an 'MVoice' as a 'PVoice' and vice versa.+--+-- This a valid 'Iso' up to meta-data equivalence.+--+unsafeMvoicePVoice :: Iso (MVoice a) (MVoice b) (PVoice a) (PVoice b)+unsafeMvoicePVoice = iso mvoiceToPVoice pVoiceToMVoice+ where+ mvoiceToPVoice :: MVoice a -> PVoice a+ mvoiceToPVoice =+ map ( bimap voiceToRest voiceToPhrase+ . bimap (^.from unsafeEventsV) (^.from unsafeEventsV) )+ . groupDiff' (isJust . snd)+ . view eventsV++ voiceToRest :: MVoice a -> Duration+ voiceToRest = sumOf (eventsV.each._1) . fmap (\x -> assert (isNothing x) x)+ -- TODO just _duration++ voiceToPhrase :: MVoice a -> Phrase a+ voiceToPhrase = fmap fromJust++ pVoiceToMVoice :: (PVoice a) -> MVoice a+ pVoiceToMVoice = mconcat . fmap (either restToVoice phraseToVoice)++ restToVoice :: Duration -> MVoice a+ restToVoice d = stretch d $ pure Nothing++ phraseToVoice :: Phrase a -> MVoice a+ phraseToVoice = fmap Just++++++-- TODO failure+-- TODO why Transformable?+singleMVoice :: {-Transformable a =>-} Prism (Score a) (Score b) (MVoice a) (MVoice b)+singleMVoice = iso scoreToVoice voiceToScore'+ where+ scoreToVoice :: {-Transformable a =>-} Score a -> MVoice a+ scoreToVoice = (^. voice) . fmap (^. stretched) . fmap throwTime . addRests .+ -- TODO+ List.sortBy (comparing (^._1))+ -- end TODO+ . (^. events)+ where+ throwTime (t,d,x) = (d,x)+ addRests = concat . snd . List.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 "singleMVoice: Strange prevTime"++ voiceToScore :: Voice a -> Score a+ voiceToScore = scat . fmap g . (^. stretcheds) where g = (^. stretchedValue) . fmap return++ voiceToScore' :: MVoice a -> Score a+ voiceToScore' = mcatMaybes . voiceToScore+++instance (Transformable a, Transformable b) => Cons (Phrase a) (Phrase b) a b where+ _Cons = undefined+-- instance (Transformable a, Transformable b) => Snoc (Phrase a) (Phrase b) a b where+ -- _Snoc = prism' pure (preview lastV)+++-- TODO move++-- |+-- Group contigous sequences matching/not-matching the predicate.+--+-- >>> groupDiff (== 0) [0,1,2,3,5,0,0,6,7]+-- [[0],[1,2,3,5],[0,0],[6,7]]+--+groupDiff :: (a -> Bool) -> [a] -> [[a]]+groupDiff p [] = []+groupDiff p (x:xs)+ | p x = (x : List.takeWhile p xs) : groupDiff p (List.dropWhile p xs)+ | not (p x) = (x : List.takeWhile (not . p) xs) : groupDiff p (List.dropWhile (not . p) xs)++groupDiff' :: (a -> Bool) -> [a] -> [Either [a] [a]]+groupDiff' p [] = []+groupDiff' p (x:xs)+ | not (p x) = Left (x : List.takeWhile (not . p) xs) : groupDiff' p (List.dropWhile (not . p) xs)+ | p x = Right (x : List.takeWhile p xs) : groupDiff' p (List.dropWhile p xs)+++
src/Music/Score/Pitch.hs view
@@ -1,25 +1,26 @@ -{-# LANGUAGE- CPP,- DeriveFunctor,- DefaultSignatures,- DeriveFoldable,- DeriveDataTypeable,- FlexibleInstances,- FlexibleContexts,- ConstraintKinds,- TypeFamilies,- TypeOperators,- MultiParamTypeClasses,- NoMonomorphismRestriction,- UndecidableInstances,- RankNTypes,- ScopedTypeVariables,- GeneralizedNewtypeDeriving #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} + ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -32,294 +33,495 @@ ------------------------------------------------------------------------------------- -module Music.Score.Pitch ( - -- * Accessors+module Music.Score.Pitch (+ + -- * Pitch type functions+ Pitch,+ SetPitch,+ Interval,+ + -- * Accessing pitch+ HasPitches(..),+ HasPitch(..),+ -- ** Simple versions+ HasPitches',+ HasPitch', pitch',- pitch,- -- pitch_,- -- pitches',- -- pitches,-- -- * Transformations- -- ** Transformations- inv,-- -- ** Transformations+ pitches',+ + -- * Converting pitch to container+ fromPitch',+ + -- * Manipulating pitch+ Transposable, up, down,- fifthsUp,- fifthsDown,+ above,+ below,+ inv,+ invertPitches, octavesUp, octavesDown,-- -- ** Transformations- -- above,- -- below,- fifthsAbove,- fifthsBelow, octavesAbove, octavesBelow,+ fifthsUp,+ fifthsDown,+ fifthsAbove,+ fifthsBelow, - -- * Pitch representation- Pitch,- Interval,- HasGetPitch(..),- HasSetPitch(..),- HasPitch'(..),- HasPitch(..),- HasSetPitch'(..),- Transposable,- Transposable1,+ -- * Inspecting pitch+ highestPitch,+ lowestPitch,+ meanPitch, - ) where+ -- * Intervals+ augmentIntervals, -import Control.Monad (ap, mfilter, join, liftM, MonadPlus(..))-import Control.Applicative-import Control.Lens-import Data.Semigroup-import Data.String-import Data.Typeable-import Data.Foldable (Foldable)-import Data.Traversable (Traversable)-import Data.Ratio-import Data.VectorSpace-import Data.AffineSpace-import Data.AffineSpace.Point-import qualified Data.List as List+ -- TODO pitchIs, to write filter pitchIs ... etc+ -- TODO gliss etc -import Music.Time-import Music.Pitch.Literal+ ) where --- This is outside HasGetPitch etc because HasSetPitch needs it--- (and it allow us to derive more instances, see #95)-type family Pitch a+import Control.Applicative+import Control.Lens hiding (above, below, transform)+import Control.Monad (MonadPlus (..), ap, join, liftM,+ mfilter)+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Foldable (Foldable)+import Data.Functor.Couple+import qualified Data.List as List+import Data.Ratio+import Data.Semigroup+import Data.String+import Data.Traversable (Traversable)+import Data.Typeable+import Data.VectorSpace hiding (Sum) -type Interval a = Diff (Pitch a)+import Music.Pitch.Literal+import Music.Score.Harmonics+import Music.Score.Part+import Music.Score.Slide+import Music.Score.Text+import Music.Score.Ties+import Music.Score.Tremolo+import Music.Score.Phrases+import Music.Time+import Music.Time.Internal.Transform -- |--- Class of types with readable pitch.+-- This type fuction is used to retrive the /pitch type/ for a given concrete type. ---class HasGetPitch s where- __getPitch :: (a ~ Pitch s) => s -> a+-- For types representing pitch, it is generally 'Identity', i.e+--+-- @+-- Pitch Integer ~ Integer+-- Pitch Double ~ Double+-- @+--+-- and so on.+--+-- For containers, 'Pitch' provides a morphism:+--+-- @+-- 'Pitch' (c,a) ~ 'Pitch' a+-- 'Pitch' [a] ~ 'Pitch' a+-- 'Pitch' ('Note' a) ~ 'Pitch' a+-- 'Pitch' ('Delayed' a) ~ 'Pitch' a+-- 'Pitch' ('Stretched' a) ~ 'Pitch' a+-- 'Pitch' ('Voice' a) ~ 'Pitch' a+-- 'Pitch' ('Chord' a) ~ 'Pitch' a+-- 'Pitch' ('Track' a) ~ 'Pitch' a+-- 'Pitch' ('Score' a) ~ 'Pitch' a+-- @+--+type family Pitch (s :: *) :: * -- |--- Class of types with mutable pitch.+-- This type fuction is used to retrive the /pitch type/ for a given concrete type. ----- Either 'setPitch' or 'mapPitch' can be implemented. If both are implemented,--- the following laws should be satisfied:+-- For types representing pitch, it is generally 'Constant', i.e ----- > setPitch x = mapPitch (const x)--- > mapPitch f x = setPitch p x where p = f (__getPitch x)+-- @+-- SetPitch a Double ~ a+-- SetPitch a Integer ~ a+-- @ ----- For types that are 'Functors', the following instance can be used+-- For containers, 'Pitch' provides a morphism: ----- > type instance Pitch (T a) = Pitch a--- > instance HasSetPitch a b => HasSetPitch (T a) (T b) where--- > type SetPitch g (T a) = T (SetPitch g a)--- > mapPitch = fmap . mapPitch+-- @+-- 'SetPitch' b (c,a) ~ (c, 'SetPitch' b a)+-- 'SetPitch' b [a] ~ ['SetPitch' b a]+-- 'SetPitch' g ('Note' a) ~ Note ('SetPitch' g a)+-- 'SetPitch' g ('Delayed' a) ~ Delayed ('SetPitch' g a)+-- 'SetPitch' g ('Stretched' a) ~ Stretched ('SetPitch' g a)+-- 'SetPitch' g ('Voice' a) ~ 'Voice' ('SetPitch' g a)+-- 'SetPitch' g ('Chord' a) ~ 'Chord' ('SetPitch' g a)+-- 'SetPitch' g ('Track' a) ~ 'Track' ('SetPitch' g a)+-- 'SetPitch' g ('Score' a) ~ 'Score' ('SetPitch' g a)+-- @ ---class (SetPitch (Pitch t) s ~ t) => HasSetPitch (s :: *) (t :: *) where- type SetPitch (b :: *) (s :: *) :: *+type family SetPitch (b :: *) (s :: *) :: * - __setPitch :: Pitch t -> s -> t- __setPitch x = __mapPitch (const x)- - __mapPitch :: (Pitch s -> Pitch t) -> s -> t- default __mapPitch :: HasGetPitch s => (Pitch s -> Pitch t) -> s -> t- __mapPitch f x = __setPitch p x where p = f (__getPitch x)- -type HasPitch s t = (HasGetPitch s, HasSetPitch s t)+-- |+-- Class of types that provide a single pitch.+--+class HasPitches s t => HasPitch s t where + -- | Access the pitch.+ --+ -- As this is a 'Traversal', you can use all combinators from the lens package,+ -- for example:+ --+ -- @+ -- 'view' 'pitch' :: HasPitch a a+ -- @+ --+ -- @+ -- 'over' 'pitch' :: HasPitch' a => a -> Pitch a+ -- 'pitch' %~ 'succ' :: HasPitch' a => a -> a+ -- 'pitch' +~ 2 :: (HasPitch' a, Num (Pitch a)) => a -> a+ -- 'pitch' .~ c :: (HasPitch' a, IsPitch a) => a -> a+ -- @+ --+ pitch :: Lens s t (Pitch s) (Pitch t)++-- |+-- Class of types that provide a pitch traversal.+--+class (Transformable (Pitch s),+ Transformable (Pitch t),+ SetPitch (Pitch t) s ~ t) => HasPitches s t where++ -- | Access all pitches.+ --+ -- As this is a 'Traversal', you can use all combinators from the lens package,+ -- for example:+ --+ -- @+ -- 'lengthOf' 'pitches' :: HasPitches a a => a -> Int+ -- @+ --+ -- @+ -- 'toListOf' 'pitches' :: HasPitches' a => a -> Pitch a+ -- @+ --+ -- @+ -- 'over' 'pitches' :: HasPitches a b => a -> b+ -- @+ --+ pitches :: Traversal s t (Pitch s) (Pitch t)+ type HasPitch' a = HasPitch a a-type HasSetPitch' a = HasSetPitch a a --- | A lens to the pitch in a note, score or other structure. +type HasPitches' a = HasPitches a a+++-- |+-- Pitch type. ---pitch' :: HasPitch' a => Lens' a (Pitch a)+pitch' :: (HasPitch s t, s ~ t) => Lens' s (Pitch s) pitch' = pitch+{-# INLINE pitch' #-} --- | A lens to the pitch in a note, score or other structure. +-- |+-- Pitch type. ---pitch :: HasPitch a b => Lens a b (Pitch a) (Pitch b)-pitch = lens __getPitch (flip __setPitch)+pitches' :: (HasPitches s t, s ~ t) => Traversal' s (Pitch s)+pitches' = pitches+{-# INLINE pitches' #-} --- | A setter to the pitch in a note, score or other structure. ----pitch_ :: HasSetPitch a b => Setter a b (Pitch a) (Pitch b)-pitch_ = sets __mapPitch --- | Traverses all pitches in structure. ----pitches' :: (Traversable t, HasPitch' a) => Traversal' (t a) (Pitch a) -pitches' = traverse . pitch'+-- TODO flip name of this and Literal.fromPitch (or call that fromPitchL)+fromPitch' :: (HasPitches' a, IsPitch a) => Pitch a -> a+fromPitch' x = c & pitches' .~ x+{-# INLINE fromPitch' #-} --- | Traverses all pitches in structure. ----pitches :: (Traversable t, HasPitch a b) => Traversal (t a) (t b) (Pitch a) (Pitch b) -pitches = traverse . pitch +#define PRIM_PITCH_INSTANCE(TYPE) \+ \+type instance Pitch TYPE = TYPE; \+type instance SetPitch a TYPE = a; \+ \+instance (Transformable a, a ~ Pitch a) \+ => HasPitch TYPE a where { \+ pitch = ($) } ; \+ \+instance (Transformable a, a ~ Pitch a) \+ => HasPitches TYPE a where { \+ pitches = ($) } ; \ -type HasPitchConstr a = (- HasPitch' a, - VectorSpace (Interval a), Integer ~ Scalar (Interval a),- AffineSpace (Pitch a)- ) -newtype PitchT p a = PitchT { __getPitchT :: (p, a) }- deriving (Eq, Ord, Show, Functor, Foldable, Traversable)+PRIM_PITCH_INSTANCE(())+PRIM_PITCH_INSTANCE(Bool)+PRIM_PITCH_INSTANCE(Ordering)+PRIM_PITCH_INSTANCE(Char)+PRIM_PITCH_INSTANCE(Int)+PRIM_PITCH_INSTANCE(Integer)+PRIM_PITCH_INSTANCE(Float)+PRIM_PITCH_INSTANCE(Double) -instance (Semigroup p, Monoid p) => Applicative (PitchT p) where- pure x = PitchT (mempty,x)- PitchT (pf,vf) <*> PitchT (px,vx) = PitchT (pf <> px, vf $ vx) --- TODO move these-instance Stretchable ()-instance Stretchable Double-instance Stretchable Float-instance Stretchable Int-instance Stretchable Integer-instance Integral a => Stretchable (Ratio a)+type instance Pitch (c,a) = Pitch a+type instance SetPitch b (c,a) = (c,SetPitch b a)+type instance Pitch [a] = Pitch a+type instance SetPitch b [a] = [SetPitch b a] -instance Delayable ()-instance Delayable Double-instance Delayable Float-instance Delayable Int-instance Delayable Integer-instance Integral a => Delayable (Ratio a)+type instance Pitch (Maybe a) = Pitch a+type instance SetPitch b (Maybe a) = Maybe (SetPitch b a)+type instance Pitch (Either c a) = Pitch a+type instance SetPitch b (Either c a) = Either c (SetPitch b a) -type instance Pitch (c,a) = Pitch a-instance HasGetPitch a => HasGetPitch (c,a) where- __getPitch (c,a) = __getPitch a+type instance Pitch (Note a) = Pitch a+type instance SetPitch b (Note a) = Note (SetPitch b a)+type instance Pitch (Delayed a) = Pitch a+type instance SetPitch b (Delayed a) = Delayed (SetPitch b a)+type instance Pitch (Stretched a) = Pitch a+type instance SetPitch b (Stretched a) = Stretched (SetPitch b a) --- Undecidable ??-instance (HasGetPitch a, HasSetPitch a b) => HasSetPitch (c,a) (c,b) where- type SetPitch b (c,a) = (c,SetPitch b a)- __setPitch b = fmap (__setPitch b)+type instance Pitch (Voice a) = Pitch a+type instance SetPitch b (Voice a) = Voice (SetPitch b a)+type instance Pitch (Chord a) = Pitch a+type instance SetPitch b (Chord a) = Chord (SetPitch b a)+type instance Pitch (Track a) = Pitch a+type instance SetPitch b (Track a) = Track (SetPitch b a)+type instance Pitch (Score a) = Pitch a+type instance SetPitch b (Score a) = Score (SetPitch b a) -#define HAS_PITCH_PRIM(T) \-type instance Pitch T = T; \-instance HasGetPitch T where { \- __getPitch = id }- -#define HAS_SET_PITCH_PRIM(T) \-instance (a ~ Pitch a) => HasSetPitch T a where { \- type SetPitch a T = a; \- __mapPitch = id }+instance HasPitch a b => HasPitch (c, a) (c, b) where+ pitch = _2 . pitch+instance HasPitches a b => HasPitches (c, a) (c, b) where+ pitches = traverse . pitches -HAS_PITCH_PRIM(())-HAS_PITCH_PRIM(Bool)-HAS_PITCH_PRIM(Double)-HAS_PITCH_PRIM(Float)-HAS_PITCH_PRIM(Int)-HAS_PITCH_PRIM(Integer)+instance (HasPitches a b) => HasPitches (Note a) (Note b) where+ pitches = _Wrapped . whilstL pitches+instance (HasPitch a b) => HasPitch (Note a) (Note b) where+ pitch = _Wrapped . whilstL pitch -HAS_SET_PITCH_PRIM(())-HAS_SET_PITCH_PRIM(Bool)-HAS_SET_PITCH_PRIM(Double)-HAS_SET_PITCH_PRIM(Float)-HAS_SET_PITCH_PRIM(Int)-HAS_SET_PITCH_PRIM(Integer)+instance (HasPitches a b) => HasPitches (Delayed a) (Delayed b) where+ pitches = _Wrapped . whilstLT pitches+instance (HasPitch a b) => HasPitch (Delayed a) (Delayed b) where+ pitch = _Wrapped . whilstLT pitch --- type Transposable p i = (Diff p ~ i, AffineSpace p, VectorSpace i, IsPitch p, IsInterval i)-type Transposable a = - (- HasSetPitch' a, - Transposable1 a- )-type Transposable1 a =- (- Diff (Pitch a) ~ Interval a,- AffineSpace (Pitch a), - VectorSpace (Interval a),- IsPitch (Pitch a), - IsInterval (Interval a)- )- +instance (HasPitches a b) => HasPitches (Stretched a) (Stretched b) where+ pitches = _Wrapped . whilstLD pitches+instance (HasPitch a b) => HasPitch (Stretched a) (Stretched b) where+ pitch = _Wrapped . whilstLD pitch +instance HasPitches a b => HasPitches (Maybe a) (Maybe b) where+ pitches = traverse . pitches++instance HasPitches a b => HasPitches (Either c a) (Either c b) where+ pitches = traverse . pitches++instance HasPitches a b => HasPitches [a] [b] where+ pitches = traverse . pitches++instance HasPitches a b => HasPitches (Voice a) (Voice b) where+ pitches = traverse . pitches++instance HasPitches a b => HasPitches (Track a) (Track b) where+ pitches = traverse . pitches++instance HasPitches a b => HasPitches (Chord a) (Chord b) where+ pitches = traverse . pitches++instance (HasPitches a b) => HasPitches (Score a) (Score b) where+ pitches =+ _Wrapped . _2 -- into NScore+ . _Wrapped+ . traverse+ . _Wrapped -- this needed?+ . whilstL pitches+++type instance Pitch (Sum a) = Pitch a+type instance SetPitch b (Sum a) = Sum (SetPitch b a)++instance HasPitches a b => HasPitches (Sum a) (Sum b) where+ pitches = _Wrapped . pitches++type instance Pitch (Behavior a) = Behavior a+type instance SetPitch b (Behavior a) = b++instance (Transformable a, Transformable b, b ~ Pitch b) => HasPitches (Behavior a) b where+ pitches = ($)+instance (Transformable a, Transformable b, b ~ Pitch b) => HasPitch (Behavior a) b where+ pitch = ($)+++type instance Pitch (Couple c a) = Pitch a+type instance SetPitch g (Couple c a) = Couple c (SetPitch g a)+type instance Pitch (TremoloT a) = Pitch a+type instance SetPitch g (TremoloT a) = TremoloT (SetPitch g a)+type instance Pitch (TextT a) = Pitch a+type instance SetPitch g (TextT a) = TextT (SetPitch g a)+type instance Pitch (HarmonicT a) = Pitch a+type instance SetPitch g (HarmonicT a) = HarmonicT (SetPitch g a)+type instance Pitch (TieT a) = Pitch a+type instance SetPitch g (TieT a) = TieT (SetPitch g a)+type instance Pitch (SlideT a) = Pitch a+type instance SetPitch g (SlideT a) = SlideT (SetPitch g a)++instance (HasPitches a b) => HasPitches (Couple c a) (Couple c b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (Couple c a) (Couple c b) where+ pitch = _Wrapped . pitch++instance (HasPitches a b) => HasPitches (TremoloT a) (TremoloT b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (TremoloT a) (TremoloT b) where+ pitch = _Wrapped . pitch++instance (HasPitches a b) => HasPitches (TextT a) (TextT b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (TextT a) (TextT b) where+ pitch = _Wrapped . pitch++instance (HasPitches a b) => HasPitches (HarmonicT a) (HarmonicT b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (HarmonicT a) (HarmonicT b) where+ pitch = _Wrapped . pitch++instance (HasPitches a b) => HasPitches (TieT a) (TieT b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (TieT a) (TieT b) where+ pitch = _Wrapped . pitch++instance (HasPitches a b) => HasPitches (SlideT a) (SlideT b) where+ pitches = _Wrapped . pitches+instance (HasPitch a b) => HasPitch (SlideT a) (SlideT b) where+ pitch = _Wrapped . pitch++ -- |+-- Associated interval type.+--+type Interval a = Diff (Pitch a)++-- |+-- Class of types that can be transposed, inverted and so on.+--+type Transposable a+ = (HasPitches a a,+ VectorSpace (Interval a), AffineSpace (Pitch a),+ IsInterval (Interval a), IsPitch (Pitch a))++-- | -- Transpose up. -- up :: Transposable a => Interval a -> a -> a-up a = pitch_ %~ (.+^ a)+up v = pitches %~ (.+^ v) -- | -- Transpose down. -- down :: Transposable a => Interval a -> a -> a-down a = pitch_ %~ (.-^ a)+down v = pitches %~ (.-^ v) -- | -- Add the given interval above. -- above :: (Semigroup a, Transposable a) => Interval a -> a -> a-above a x = x <> up a x+above v x = x <> up v x -- | -- Add the given interval below. -- below :: (Semigroup a, Transposable a) => Interval a -> a -> a-below a x = x <> down a x+below v x = x <> down v x +inv :: Transposable a => Pitch a -> a -> a+inv = invertPitches+{-# DEPRECATED inv "Use 'invertPitches'" #-}+ -- | -- Invert pitches. ---inv :: Transposable a => Pitch a -> a -> a-inv p = pitch_ %~ (reflectThrough p)+invertPitches :: Transposable a => Pitch a -> a -> a+invertPitches p = pitches %~ reflectThrough p -- | -- Transpose up by the given number of octaves. -- octavesUp :: Transposable a => Scalar (Interval a) -> a -> a-octavesUp a = up (_P8^*a)+octavesUp n = up (_P8^*n) -- | -- Transpose down by the given number of octaves. -- octavesDown :: Transposable a => Scalar (Interval a) -> a -> a-octavesDown a = down (_P8^*a)+octavesDown n = down (_P8^*n) -- |--- Add the given interval below.+-- Add the given octave above. --+octavesAbove :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a+octavesAbove n = above (_P8^*n)++-- |+-- Add the given octave below.+--+octavesBelow :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a+octavesBelow n = below (_P8^*n)++-- |+-- Transpose up by the given number of fifths.+-- fifthsUp :: Transposable a => Scalar (Interval a) -> a -> a-fifthsUp a = up (_P8^*a)+fifthsUp n = up (_P8^*n) -- |--- Add the given interval below.+-- Transpose down by the given number of fifths. -- fifthsDown :: Transposable a => Scalar (Interval a) -> a -> a-fifthsDown a = down (_P8^*a)+fifthsDown n = down (_P8^*n) +-- |+-- Add the given octave above.+--+fifthsAbove :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a+fifthsAbove n = above (_P8^*n) -- |--- Add the given interval below.+-- Add the given octave below. ---octavesAbove :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a-octavesAbove n x = x <> octavesUp n x+fifthsBelow :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a+fifthsBelow n = below (_P8^*n) +++ -- |--- Add the given interval below.+-- Return the highest pitch in the given music. ---octavesBelow :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a-octavesBelow n x = x <> octavesUp n x+highestPitch :: (HasPitches' a, Ord (Pitch a)) => a -> Pitch a+highestPitch = maximum . toListOf pitches' -- |--- Add the given interval below.+-- Return the lowest pitch in the given music. ---fifthsAbove :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a-fifthsAbove n x = x <> fifthsUp n x+lowestPitch :: (HasPitches' a, Ord (Pitch a)) => a -> Pitch a+lowestPitch = maximum . toListOf pitches' -- |--- Add the given interval below.+-- Return the mean pitch in the given music. ---fifthsBelow :: (Semigroup a, Transposable a) => Scalar (Interval a) -> a -> a-fifthsBelow n x = x <> fifthsUp n x+meanPitch :: (HasPitches' a, Fractional (Pitch a)) => a -> Pitch a+meanPitch = mean . toListOf pitches'+ where+ mean x = fst $ foldl (\(m, n) x -> (m+(x-m)/(n+1),n+1)) (0,0) x -{- -highestPitch = maximum . __getPitches-lowestPitch = maximum . __getPitches-meanPitch = mean . __getPitches-mean x = fst $ foldl (\(m, n) x -> (m+(x-m)/(n+1),n+1)) (0,0) x +augmentIntervals :: (HasPhrases' s a, Transposable a) => Interval a -> s -> s+augmentIntervals x = over phrases (augmentIntervals' x) --}+augmentIntervals' :: Transposable a => Interval a -> Voice a -> Voice a+augmentIntervals' = error "Not implemented: augmentIntervals"+-- TODO generalize to any type where we can traverse phrases of something that has pitch+++-- TODO augment/diminish intervals (requires phrase traversal)+-- TODO rotatePitch (requires phrase traversal)+-- TODO invert diatonically+
− src/Music/Score/Rhythm.hs
@@ -1,406 +0,0 @@--{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ViewPatterns #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)-------------------------------------------------------------------------------------------module Music.Score.Rhythm (- -- * Rhythm type- Rhythm(..),-- -- * Quantization- quantize,- rewrite,- dotMod,- ) where--import Prelude hiding (concat, concatMap, foldl, foldr,- mapM, maximum, minimum, sum)--import Control.Applicative-import Control.Arrow hiding (left)-import Control.Monad (MonadPlus (..), ap, join)-import Data.Either-import Data.Foldable-import Data.Function (on)-import qualified Data.List as List-import Data.Maybe-import Data.Ord (comparing)-import Data.Ratio-import Data.Semigroup-import Data.Traversable-import Data.Tree-import Data.VectorSpace--import Text.Parsec hiding ((<|>))-import Text.Parsec.Pos--import Music.Score.Ties-import Music.Score.Util-import Music.Score.Voice-import Music.Time--data Rhythm a- = Beat Duration a -- d is divisible by 2- | Group [Rhythm a] --- | Dotted Int (Rhythm a) -- n > 0.- | Tuplet Duration (Rhythm a) -- d is an emelent of 'konstTuplets'.- deriving (Eq, Show, Functor, Foldable)- -- RInvTuplet Duration (Rhythm a)--getBeatValue :: Rhythm a -> a-getBeatValue (Beat d a) = a-getBeatValue _ = error "getBeatValue: Not a beat"--getBeatDuration :: Rhythm a -> Duration-getBeatDuration (Beat d a) = d-getBeatDuration _ = error "getBeatValue: Not a beat"--realize :: Rhythm a -> [(Duration, a)]-realize (Beat d a) = [(d, a)]-realize (Group rs) = rs >>= realize-realize (Dotted n r) = dotMod n `stretch` realize r-realize (Tuplet n r) = n `stretch` realize r---- rhythmToTree :: Rhythm a -> Tree (String, Maybe a)--- rhythmToTree = go--- where--- go (Beat d a) = Node ("beat " ++ showD d, Just a) []--- go (Group rs) = Node ("group", Nothing) (fmap rhythmToTree rs)--- go (Dotted n r) = Node ("dotted " ++ show n, Nothing) [rhythmToTree r]--- go (Tuplet n r) = Node ("tuplet " ++ showD n, Nothing) [rhythmToTree r]--- showD = show . toRational------ drawRhythm :: Show a => Rhythm a -> String--- drawRhythm = drawTree . fmap (uncurry (++) <<< (++ " ") *** show) . rhythmToTree--rhythmToTree :: Rhythm a -> Tree String-rhythmToTree = go- where- go (Beat d a) = Node ("" ++ showD d) []- go (Group rs) = Node ("") (fmap rhythmToTree rs)- go (Dotted n r) = Node (replicate n '.') [rhythmToTree r]- go (Tuplet n r) = Node ("*^ " ++ showD n) [rhythmToTree r]- showD = (\x -> show (numerator x) ++ "/" ++ show (denominator x)) . toRational--drawRhythm :: Show a => Rhythm a -> String-drawRhythm = drawTree . rhythmToTree--instance Semigroup (Rhythm a) where- (<>) = mappend---- Catenates using 'Group'-instance Monoid (Rhythm a) where- mempty = Group []- Group as `mappend` Group bs = Group (as <> bs)- r `mappend` Group bs = Group ([r] <> bs)- Group as `mappend` r = Group (as <> [r])- a `mappend` b = Group [a, b]--instance HasDuration (Rhythm a) where- duration (Beat d _) = d- duration (Dotted n a) = duration a * dotMod n- duration (Tuplet c a) = duration a * c- duration (Group as) = sum (fmap duration as)--instance AdditiveGroup (Rhythm a) where- zeroV = error "No zeroV for (Rhythm a)"- (^+^) = error "No ^+^ for (Rhythm a)"- negateV = error "No negateV for (Rhythm a)"--instance VectorSpace (Rhythm a) where- type Scalar (Rhythm a) = Duration- a *^ Beat d x = Beat (a*d) x- -- FIXME how does this preserve the invariant?--Beat d x `subDur` d' = Beat (d-d') x---{-- Rhythm rewrite laws (all up to realization equality)-- Note: Just sketching, needs more formal treatment.--- Group [Group xs ...] = Group [xs ...]- [JoinGroup]-- Tuplet m (Tuplet n x) = Tuplet (m * n) x- [NestTuplet]-- Tuplet m (Group [a,b ...]) = Group [Tuplet m a, Tuplet m b ...]- [DistributeTuplet]- This is only OK in certain contexts! Which?---}--rewrite :: Rhythm a -> Rhythm a--rewrite = rewriteR . rewrite1--rewriteR = go where- go (Beat d a) = Beat d a- go (Group rs) = Group (fmap (rewriteR . rewrite1) rs)- go (Dotted n r) = Dotted n ((rewriteR . rewrite1) r)- go (Tuplet n r) = Tuplet n ((rewriteR . rewrite1) r)--rewrite1 = splitTuplet . tupletDot . singleGroup---singleGroup :: Rhythm a -> Rhythm a-singleGroup orig@(Group [x]) = x-singleGroup orig = orig---- | Removes dotted notes in 2/3 tuplets.-tupletDot :: Rhythm a -> Rhythm a-tupletDot orig@(Tuplet ((unRatio.realToFrac) -> (2,3)) (Dotted 1 x)) = x-tupletDot orig = orig---- | Splits a tuplet iff it contans a group which can be split into two halves of exactly the same size.-splitTuplet :: Rhythm a -> Rhythm a-splitTuplet orig@(Tuplet n (Group xs)) = case trySplit xs of- Nothing -> orig- Just (as, bs) -> Tuplet n (Group as) <> Tuplet n (Group bs)-splitTuplet orig = orig---- TODO bad instance-instance HasDuration a => HasDuration [a] where- duration = sum . fmap duration--trySplit :: [Rhythm a] -> Maybe ([Rhythm a], [Rhythm a])-trySplit = firstJust . fmap g . splits- where- g (part1, part2)- | duration part1 == duration part2 = Just (part1, part2)- | otherwise = Nothing- splits xs = List.inits xs `zip` List.tails xs- firstJust = listToMaybe . fmap fromJust . List.dropWhile isNothing---quantize :: Tiable a => [(Duration, a)] -> Either String (Rhythm a)-quantize = quantize' (atEnd rhythm)--testQuantize :: [Duration] -> IO ()-testQuantize x = case fmap rewrite $ quantize' (atEnd rhythm) $ fmap (\x -> (x,())) $ x of- Left e -> error e- Right x -> putStrLn $ drawRhythm x---konstNumDotsAllowed :: [Int]-konstNumDotsAllowed = [1..2]--konstBounds :: [Duration]-konstBounds = [ 1/2, 1/4, 1/8 ]--konstTuplets :: [Duration]-konstTuplets = [ 2/3, 4/5, 4/7, 8/9, 8/11, 8/13, 8/15, 16/17, 16/18, 16/19, 16/21, 16/23 ]--konstMaxTupletNest :: Int-konstMaxTupletNest = 1---data RhythmContext = RhythmContext {-- -- Time scaling of the current note (from dots and tuplets).- timeMod :: Duration,-- -- Time subtracted from the current rhythm (from ties).- timeSub :: Duration,-- -- Number of tuplets above the current note (default 0).- tupleDepth :: Int- }--instance Monoid RhythmContext where- mempty = RhythmContext { timeMod = 1, timeSub = 0, tupleDepth = 0 }- a `mappend` _ = a--modifyTimeMod :: (Duration -> Duration) -> RhythmContext -> RhythmContext-modifyTimeMod f (RhythmContext tm ts td) = RhythmContext (f tm) ts td--modifyTimeSub :: (Duration -> Duration) -> RhythmContext -> RhythmContext-modifyTimeSub f (RhythmContext tm ts td) = RhythmContext tm (f ts) td--modifyTupleDepth :: (Int -> Int) -> RhythmContext -> RhythmContext-modifyTupleDepth f (RhythmContext tm ts td) = RhythmContext tm ts (f td)--------- |--- A @RhytmParser a b@ converts (Voice a) to b.-type RhythmParser a b = Parsec [(Duration, a)] RhythmContext b--quantize' :: Tiable a => RhythmParser a b -> [(Duration, a)] -> Either String b-quantize' p = left show . runParser p mempty ""-------rhythm :: Tiable a => RhythmParser a (Rhythm a)-rhythm = Group <$> many1 (rhythm' <|> bound)--rhythmNoBound :: Tiable a => RhythmParser a (Rhythm a)-rhythmNoBound = Group <$> many1 rhythm'--rhythm' :: Tiable a => RhythmParser a (Rhythm a)-rhythm' = mzero- <|> beat- <|> dotted- <|> tuplet---- Matches a beat divisible by 2 (notated)--- beat :: Tiable a => RhythmParser a (Rhythm a)--- beat = do--- RhythmContext tm ts _ <- getState--- (\d -> (d^/tm) `subDur` ts) <$> match (\d _ ->--- d - ts > 0 && isDivisibleBy 2 (d / tm - ts))--beat :: Tiable a => RhythmParser a (Rhythm a)-beat = do- RhythmContext tm ts _ <- getState- match' $ \d x ->- let d2 = d / tm - ts- in (d2, x) `assuming` (d - ts > 0 && isDivisibleBy 2 d2)----- | Matches a dotted rhythm-dotted :: Tiable a => RhythmParser a (Rhythm a)-dotted = msum . fmap dotted' $ konstNumDotsAllowed---- | Matches a bound rhythm-bound :: Tiable a => RhythmParser a (Rhythm a)-bound = msum . fmap bound' $ konstBounds---- | Matches a tuplet-tuplet :: Tiable a => RhythmParser a (Rhythm a)-tuplet = msum . fmap tuplet' $ konstTuplets------dotted' :: Tiable a => Int -> RhythmParser a (Rhythm a)-dotted' n = do- modifyState $ modifyTimeMod (* dotMod n)- a <- beat- modifyState $ modifyTimeMod (/ dotMod n)- return (Dotted n a)---- | Return the scaling applied to a note with the given number of dots (i.e. 3/2, 7/4 etc).-dotMod :: Int -> Duration-dotMod n = dotMods !! (n-1)---- [3/2, 7/4, 15/8, 31/16 ..]-dotMods :: [Duration]-dotMods = zipWith (/) (fmap pred $ drop 2 times2) (drop 1 times2)- where- times2 = iterate (*2) 1--bound' :: Tiable a => Duration -> RhythmParser a (Rhythm a)-bound' d = do- modifyState $ modifyTimeSub (+ d)- a <- beat- modifyState $ modifyTimeSub (subtract d)- let (b,c) = toTied $ getBeatValue a-- -- FIXME doesn't know order- return $ Group [Beat (getBeatDuration a) b, Beat d c]---- tuplet' 2/3 for triplet, 4/5 for quintuplet etc-tuplet' :: Tiable a => Duration -> RhythmParser a (Rhythm a)-tuplet' d = do- RhythmContext _ _ depth <- getState- onlyIf (depth < konstMaxTupletNest) $ do- modifyState $ modifyTimeMod (* d)- . modifyTupleDepth succ- a <- rhythmNoBound- modifyState $ modifyTimeMod (/ d)- . modifyTupleDepth pred- return (Tuplet d a)-------------------------------------------------------------------------------------------- | Similar to 'many1', but tries longer sequences before trying one.--- many1long :: Stream s m t => ParsecT s u m a -> ParsecT s u m [a]--- many1long p = try (many2 p) <|> fmap return p---- | Similar to 'many1', but applies the parser 2 or more times.--- many2 :: Stream s m t => ParsecT s u m a -> ParsecT s u m [a]--- many2 p = do { x <- p; xs <- many1 p; return (x : xs) }---- Matches a (duration, value) pair iff the predicate matches, returns beat-match :: Tiable a => (Duration -> a -> Bool) -> RhythmParser a (Rhythm a)-match p = tokenPrim show next test- where- show x = ""- next pos _ _ = updatePosChar pos 'x'- test (d,x) = if p d x then Just (Beat d x) else Nothing---- Matches a (duration, value) pair iff the predicate matches, returns beat-match' :: Tiable a => (Duration -> a -> Maybe (Duration, b)) -> RhythmParser a (Rhythm b)-match' f = tokenPrim show next test- where- show x = ""- next pos _ _ = updatePosChar pos 'x'- test (d,x) = case f d x of- Nothing -> Nothing- Just (d,x) -> Just $ Beat d x---- |--- Succeed only if the entire input is consumed.----atEnd :: RhythmParser a b -> RhythmParser a b-atEnd p = do- x <- p- notFollowedBy' anyToken' <?> "end of input"- return x- where- notFollowedBy' p = try $ (try p >> unexpected "") <|> return ()- anyToken' = tokenPrim (const "") (\pos _ _ -> pos) Just--onlyIf :: MonadPlus m => Bool -> m b -> m b-onlyIf b p = if b then p else mzero---- | Just x or Nothing-assuming :: a -> Bool -> Maybe a-assuming x b = if b then Just x else Nothing--logBaseR :: forall a . (RealFloat a, Floating a) => Rational -> Rational -> a-logBaseR k n- | isInfinite (fromRational n :: a) = logBaseR k (n/k) + 1-logBaseR k n- | isDenormalized (fromRational n :: a) = logBaseR k (n*k) - 1-logBaseR k n = logBase (fromRational k) (fromRational n)---divides = isDivisibleBy-divisibleBy = flip isDivisibleBy---- As it sounds, do NOT use infix--- Only works for simple n such as 2 or 3, TODO determine-isDivisibleBy :: Duration -> Duration -> Bool-isDivisibleBy n = (== 0.0) . snd . properFraction . logBaseR (toRational n) . toRational---left f (Left x) = Left (f x)-left f (Right y) = Right y
− src/Music/Score/Score.hs
@@ -1,296 +0,0 @@--{-# 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))
+ src/Music/Score/Slide.hs view
@@ -0,0 +1,155 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides a representation of musical /slides/, commonly known as /glissando/ or+-- /portamento/. +--+-- /Warning/ This module is experimental.+--+-------------------------------------------------------------------------------------+++module Music.Score.Slide (++ -- * Slides and glissando+ HasSlide(..),+ SlideT(..),+ slide,+ glissando,++ ) where++import Control.Applicative+import Control.Comonad+import Control.Lens hiding (transform)+import Data.Foldable+import Data.Foldable+import Data.Functor.Couple+import Data.Ratio+import Data.Semigroup+import Data.Typeable+import Data.Word++import Music.Dynamics.Literal+import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal+import Music.Score.Part+import Music.Score.Phrases+import Music.Time+++class HasSlide a where+ setBeginGliss :: Bool -> a -> a+ setBeginSlide :: Bool -> a -> a+ setEndGliss :: Bool -> a -> a+ setEndSlide :: Bool -> a -> a++instance HasSlide a => HasSlide (b, a) where+ setBeginGliss n = fmap (setBeginGliss n)+ setBeginSlide n = fmap (setBeginSlide n)+ setEndGliss n = fmap (setEndGliss n)+ setEndSlide n = fmap (setEndSlide n)++instance HasSlide a => HasSlide (Couple b a) where+ setBeginGliss n = fmap (setBeginGliss n)+ setBeginSlide n = fmap (setBeginSlide n)+ setEndGliss n = fmap (setEndGliss n)+ setEndSlide n = fmap (setEndSlide n)++instance HasSlide a => HasSlide [a] where+ setBeginGliss n = fmap (setBeginGliss n)+ setBeginSlide n = fmap (setBeginSlide n)+ setEndGliss n = fmap (setEndGliss n)+ setEndSlide n = fmap (setEndSlide n)++instance HasSlide a => HasSlide (Score a) where+ setBeginGliss n = fmap (setBeginGliss n)+ setBeginSlide n = fmap (setBeginSlide n)+ setEndGliss n = fmap (setEndGliss n)+ setEndSlide n = fmap (setEndSlide n)+++-- (eg,es,a,bg,bs)+newtype SlideT a = SlideT { getSlideT :: Couple ((Any, Any), (Any, Any)) a }+ deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad, Comonad)++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (SlideT a) where+ type Unwrapped (SlideT a) = Couple ((Any, Any), (Any, Any)) a+ _Wrapped' = iso getSlideT SlideT++instance Rewrapped (SlideT a) (SlideT b)++_bg, _bs, _eg, _es :: Lens' (SlideT a) Any+_bg = (_Wrapped'._Wrapped') . _1 . _2 . _1+_bs = (_Wrapped'._Wrapped') . _1 . _2 . _2+_eg = (_Wrapped'._Wrapped') . _1 . _1 . _1+_es = (_Wrapped'._Wrapped') . _1 . _1 . _2++instance HasSlide (SlideT a) where+ setBeginGliss x = _bg .~ Any x+ setBeginSlide x = _bs .~ Any x+ setEndGliss x = _eg .~ Any x+ setEndSlide x = _es .~ Any x++-- Lifted instances+deriving instance Num a => Num (SlideT a)+deriving instance Fractional a => Fractional (SlideT a)+deriving instance Floating a => Floating (SlideT a)+deriving instance Enum a => Enum (SlideT a)+deriving instance Bounded a => Bounded (SlideT a)+deriving instance (Num a, Ord a, Real a) => Real (SlideT a)+deriving instance (Real a, Enum a, Integral a) => Integral (SlideT a)++-- |+-- Add a slide between the first and the last note.+--+slide :: (HasPhrases' s a, HasSlide a) => s -> s+slide = mapPhraseWise3 (setBeginSlide True) id (setEndSlide True)+ where+ mapPhraseWise3 f g h = over phrases' (over headV f . over middleV g . over lastV h)++-- |+-- Add a glissando between the first and the last note.+--+glissando :: (HasPhrases' s a, HasSlide a) => s -> s+glissando = mapPhraseWise3 (setBeginGliss True) id (setEndGliss True)+ where+ mapPhraseWise3 f g h = over phrases' (over headV f . over middleV g . over lastV h)+++headV :: Traversal' (Voice a) a+headV = (eventsV._head._2)++middleV :: Traversal' (Voice a) a+middleV = (eventsV._middle.traverse._2)++lastV :: Traversal' (Voice a) a+lastV = (eventsV._last._2)++-- Traverse writing to all elements *except* first and last+_middle :: (Snoc s s a a, Cons s s b b) => Traversal' s s+_middle = _tail._init+
+ src/Music/Score/Text.hs view
@@ -0,0 +1,120 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides a way of adding text to notes.+--+-------------------------------------------------------------------------------------+++module Music.Score.Text (++ -- * Text+ HasText(..),+ TextT(..),+ text,++ ) where++import Control.Applicative+import Control.Comonad+import Control.Lens hiding (transform)+import Data.Foldable+import Data.Foldable+import Data.Functor.Couple+import Data.Ratio+import Data.Semigroup+import Data.Typeable+import Data.Word++import Music.Dynamics.Literal+import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal+import Music.Score.Part+import Music.Score.Phrases+import Music.Time++++class HasText a where+ addText :: String -> a -> a++newtype TextT a = TextT { getTextT :: Couple [String] a }+ deriving (+ Eq, Show, Ord, Functor, Foldable, Typeable, + Applicative, Monad, Comonad+ )++instance HasText a => HasText (b, a) where+ addText s = fmap (addText s)++instance HasText a => HasText (Couple b a) where+ addText s = fmap (addText s)++instance HasText a => HasText [a] where+ addText s = fmap (addText s)++instance HasText a => HasText (Score a) where+ addText s = fmap (addText s)+++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (TextT a) where+ type Unwrapped (TextT a) = Couple [String] a+ _Wrapped' = iso getTextT TextT++instance Rewrapped (TextT a) (TextT b)++instance HasText (TextT a) where+ addText s (TextT (Couple (t,x))) = TextT (Couple (t ++ [s],x))++-- Lifted instances+deriving instance Num a => Num (TextT a)+deriving instance Fractional a => Fractional (TextT a)+deriving instance Floating a => Floating (TextT a)+deriving instance Enum a => Enum (TextT a)+deriving instance Bounded a => Bounded (TextT a)+deriving instance (Num a, Ord a, Real a) => Real (TextT a)+deriving instance (Real a, Enum a, Integral a) => Integral (TextT a)++-- |+-- Attach the given text to the first note in the score.+--+text :: (HasPhrases' s a, HasText a) => String -> s -> s+text s = over (phrases'.headV) (addText s)+++headV :: Traversal' (Voice a) a+headV = (eventsV._head._2)++middleV :: Traversal' (Voice a) a+middleV = (eventsV._middle.traverse._2)++lastV :: Traversal' (Voice a) a+lastV = (eventsV._last._2)++-- Traverse writing to all elements *except* first and last+_middle :: (Snoc s s a a, Cons s s b b) => Traversal' s s+_middle = _tail._init+
src/Music/Score/Ties.hs view
@@ -6,12 +6,14 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -19,12 +21,13 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) ----- Provides a representation for tied notes, and a way to split a single note+-- Provides a representation for tied notes, and a class to split a single note -- into a pair of tied notes. -- ------------------------------------------------------------------------------------- module Music.Score.Ties (+ -- * Tiable class Tiable(..), TieT(..),@@ -32,28 +35,28 @@ -- * Splitting tied notes in scores splitTiesVoice, splitTiesVoiceAt,+ ) where import Control.Applicative import Control.Arrow-import Control.Lens+import Control.Comonad+import Control.Lens hiding (transform) import Control.Monad import Control.Monad.Plus import Data.AffineSpace import Data.Default import Data.Foldable hiding (concat)+import Data.Functor.Adjunction (unzipR) import qualified Data.List as List import Data.Maybe import Data.Ratio import Data.Semigroup import Data.Typeable-import Data.VectorSpace+import Data.VectorSpace hiding (Sum) -import Music.Score.Combinators-import Music.Score.Convert-import Music.Score.Part-import Music.Score.Score-import Music.Score.Voice+import Music.Dynamics.Literal+import Music.Pitch.Literal import Music.Time -- |@@ -64,59 +67,145 @@ -- Minimal definition: 'toTied', or both 'beginTie' and 'endTie'. -- class Tiable a where- -- |- -- Modify a note to be the first note in a tied note pair.- --- beginTie :: a -> a- beginTie = fst . toTied+ -- |+ -- Modify a note to be the first note in a tied note pair.+ --+ beginTie :: a -> a+ beginTie = fst . toTied - -- |- -- Modify a note to be the second note in a tied note pair.- --- endTie :: a -> a- endTie = snd . toTied+ -- |+ -- Modify a note to be the second note in a tied note pair.+ --+ endTie :: a -> a+ endTie = snd . toTied - -- |- -- Split a single note into a pair of tied notes.- --- -- The first returned element should have the original 'onset' and the second- -- element should have the original 'offset'. Formally- --- -- > (onset . fst . toTied) a = onset a- -- > (offset . snd . toTied) a = offset a- --- toTied :: a -> (a, a)- toTied a = (beginTie a, endTie a)+ -- |+ -- Split a single note into a pair of tied notes.+ --+ -- The first returned element should have the original 'onset' and the second+ -- element should have the original 'offset'. Formally+ --+ -- > (onset . fst . toTied) a = onset a+ -- > (offset . snd . toTied) a = offset a+ --+ toTied :: a -> (a, a)+ toTied a = (beginTie a, endTie a) newtype TieT a = TieT { getTieT :: ((Any, Any), a) }- deriving (Eq, Ord, Show, Functor, Foldable, Typeable, Applicative, Monad)+ deriving (Eq, Ord, Show, Functor, Foldable, Typeable, Applicative, Monad, Comonad) +-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (TieT a) where+ type Unwrapped (TieT a) = ((Any, Any), a)+ _Wrapped' = iso getTieT TieT++instance Rewrapped (TieT a) (TieT b)+ instance Tiable Double where { beginTie = id ; endTie = id } instance Tiable Float where { beginTie = id ; endTie = id }+instance Tiable Char where { beginTie = id ; endTie = id } instance Tiable Int where { beginTie = id ; endTie = id } instance Tiable Integer where { beginTie = id ; endTie = id } instance Tiable () where { beginTie = id ; endTie = id } instance Tiable (Ratio a) where { beginTie = id ; endTie = id } +instance Tiable a => Tiable (TieT a) where+ toTied (TieT ((prevTie, nextTie), a)) = (TieT ((prevTie, Any True), b), TieT ((Any True, nextTie), c))+ where (b,c) = toTied a++instance Tiable a => Tiable [a] where+ toTied = unzip . fmap toTied++instance Tiable a => Tiable (Behavior a) where+ toTied = unzipR . fmap toTied++--+-- There is no (HasPart ChordT) instance, so PartT must be outside ChordT in the stack+-- This restriction assures all chord notes are in the same part+--+instance Tiable a => Tiable (c, a) where+ toTied = unzipR . fmap toTied+ instance Tiable a => Tiable (Maybe a) where- beginTie = fmap beginTie- endTie = fmap endTie+ toTied = unzipR . fmap toTied -instance Tiable a => Tiable (TieT a) where- toTied (TieT ((prevTie, nextTie), a)) = (TieT ((prevTie, Any True), b), TieT ((Any True, nextTie), c))- where (b,c) = toTied a+instance Tiable a => Tiable (Sum a) where+ toTied = unzipR . fmap toTied +instance Tiable a => Tiable (Product a) where+ toTied = unzipR . fmap toTied++-- Lifted instances++instance IsPitch a => IsPitch (TieT a) where+ fromPitch = pure . fromPitch++instance IsDynamics a => IsDynamics (TieT a) where+ fromDynamics = return . fromDynamics++instance Transformable a => Transformable (TieT a) where+ transform s = fmap (transform s)++instance Reversible a => Reversible (TieT a) where+ rev = fmap rev++instance Num a => Num (TieT a) where+ (+) = liftA2 (+)+ (*) = liftA2 (*)+ (-) = liftA2 (-)+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger++instance Fractional a => Fractional (TieT a) where+ recip = fmap recip+ fromRational = pure . fromRational++instance Floating a => Floating (TieT a) where+ pi = pure pi+ sqrt = fmap sqrt+ exp = fmap exp+ log = fmap log+ sin = fmap sin+ cos = fmap cos+ asin = fmap asin+ atan = fmap atan+ acos = fmap acos+ sinh = fmap sinh+ cosh = fmap cosh+ asinh = fmap asinh+ atanh = fmap atanh+ acosh = fmap acos++instance Enum a => Enum (TieT a) where+ toEnum = pure . toEnum+ fromEnum = fromEnum . extract++instance Bounded a => Bounded (TieT a) where+ minBound = pure minBound+ maxBound = pure maxBound++instance (Num a, Ord a, Real a) => Real (TieT a) where+ toRational = toRational . extract++instance (Real a, Enum a, Integral a) => Integral (TieT a) where+ quot = liftA2 quot+ rem = liftA2 rem+ toInteger = toInteger . extract+ -- | -- Split all notes that cross a barlines into a pair of tied notes. -- splitTiesVoice :: Tiable a => Voice a -> Voice a-splitTiesVoice = (^. voice) . concat . snd . List.mapAccumL g 0 . (^. from voice)- where- g t (d, x) = (t + d, occs)- where- (_, barTime) = properFraction t- remBarTime = 1 - barTime- occs = splitDurThen remBarTime 1 (d,x)+splitTiesVoice = (^. voice) . map (^. stretched)+ . concat . snd . List.mapAccumL g 0+ . map (^. from stretched) . (^. stretcheds)+ where+ g t (d, x) = (t + d, occs)+ where+ (_, barTime) = properFraction t+ remBarTime = 1 - barTime+ occs = splitDurThen remBarTime 1 (d,x) -- | -- Split all voice into bars, using the given bar durations. Music that does not@@ -125,17 +214,17 @@ -- Notes that cross a barlines are split into tied notes. -- splitTiesVoiceAt :: Tiable a => [Duration] -> Voice a -> [Voice a]-splitTiesVoiceAt barDurs x = fmap (^. voice) $ splitTiesVoiceAt' barDurs ((^. from voice) x)+splitTiesVoiceAt barDurs x = fmap ((^. voice) . map (^. stretched)) $ splitTiesVoiceAt' barDurs ((map (^. from stretched) . (^. stretcheds)) x) splitTiesVoiceAt' :: Tiable a => [Duration] -> [(Duration, a)] -> [[(Duration, a)]] splitTiesVoiceAt' [] _ = [] splitTiesVoiceAt' _ [] = [] splitTiesVoiceAt' (barDur : rbarDur) occs = case splitDurFor barDur occs of- (barOccs, []) -> barOccs : []- (barOccs, restOccs) -> barOccs : splitTiesVoiceAt' rbarDur restOccs+ (barOccs, []) -> barOccs : []+ (barOccs, restOccs) -> barOccs : splitTiesVoiceAt' rbarDur restOccs tsplitTiesVoiceAt :: [Duration] -> [Duration] -> [[(Duration, Char)]]-tsplitTiesVoiceAt barDurs = fmap (^. from voice) . splitTiesVoiceAt barDurs . (^. voice) . fmap (\x -> (x,'_'))+tsplitTiesVoiceAt barDurs = fmap (map (^. from stretched) . (^. stretcheds)) . splitTiesVoiceAt barDurs . ((^. voice) . map (^. stretched)) . fmap (\x -> (x,'_')) -- | -- Split an event into one chunk of the duration @s@, followed parts shorter than duration @t@.@@ -146,8 +235,8 @@ -- splitDurThen :: Tiable a => Duration -> Duration -> (Duration, a) -> [(Duration, a)] splitDurThen s t x = case splitDur s x of- (a, Nothing) -> [a]- (a, Just b) -> a : splitDurThen t t b+ (a, Nothing) -> [a]+ (a, Just b) -> a : splitDurThen t t b -- |@@ -165,17 +254,17 @@ splitDurFor :: Tiable a => Duration -> [(Duration, a)] -> ([(Duration, a)], [(Duration, a)]) splitDurFor remDur [] = ([], []) splitDurFor remDur (x : xs) = case splitDur remDur x of- (x@(d,_), Nothing) ->- if d < remDur then- first (x:) $ splitDurFor (remDur - d) xs- else -- d == remDur- ([x], xs)- (x@(d,_), Just rest) -> ([x], rest : xs)+ (x@(d,_), Nothing) ->+ if d < remDur then+ first (x:) $ splitDurFor (remDur - d) xs+ else -- d == remDur+ ([x], xs)+ (x@(d,_), Just rest) -> ([x], rest : xs) tsplitDurFor :: Duration -> [Duration] -> ([(Duration,Char)], [(Duration,Char)]) tsplitDurFor maxDur xs = splitDurFor maxDur $ fmap (\x -> (x,'_')) xs-instance Tiable Char where- toTied _ = ('(',')')+-- instance Tiable Char where+ -- toTied _ = ('(',')') -- | -- Split a note if it is longer than the given duration. Returns the first part of the@@ -185,10 +274,7 @@ -- splitDur :: Tiable a => Duration -> (Duration, a) -> ((Duration, a), Maybe (Duration, a)) splitDur maxDur (d,a)- | maxDur <= 0 = error "splitDur: maxDur must be > 0"- | d <= maxDur = ((d, a), Nothing)- | d > maxDur = ((maxDur, b), Just (d - maxDur, c)) where (b,c) = toTied a---+ | maxDur <= 0 = error "splitDur: maxDur must be > 0"+ | d <= maxDur = ((d, a), Nothing)+ | d > maxDur = ((maxDur, b), Just (d - maxDur, c)) where (b,c) = toTied a
− src/Music/Score/Track.hs
@@ -1,168 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides the 'Track' type.-------------------------------------------------------------------------------------------module Music.Score.Track (- -- * Track type- Track,- track',- track,- -- mkTrack,- -- getTrack,- ) where--import Control.Applicative-import Control.Arrow-import Control.Lens-import Control.Monad-import Control.Monad.Compose-import Data.AffineSpace.Point-import Data.Foldable (Foldable (..), foldMap)-import qualified Data.Foldable as F-import qualified Data.List as List-import Data.PairMonad ()-import Data.Semigroup-import Data.Traversable (Traversable (..))-import qualified Data.Traversable as T-import Data.Typeable-import Data.VectorSpace hiding (Sum)--- import Test.QuickCheck (Arbitrary (..), Gen (..))--import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Pitch-import Music.Score.Util-import Music.Time---- |--- A track is a list of events with explicit onset.------ Track is a 'Monoid' under parallel composition. 'mempty' is the empty track--- and 'mappend' interleaves values.------ Track is a 'Monad'. 'return' creates a track containing a single value at time--- zero, and '>>=' transforms the values of a track, allowing the addition and--- removal of values relative to the time of the value. Perhaps more intuitively,--- 'join' delays each inner track to start at the offset of an outer track, then--- removes the intermediate structure.------ > let t = Track [(0, 65),(1, 66)]--- >--- > t >>= \x -> Track [(0, 'a'), (10, toEnum x)]--- >--- > ==> Track {getTrack = [ (0.0, 'a'),--- > (1.0, 'a'),--- > (10.0, 'A'),--- > (11.0, 'B') ]}------ Track is an instance of 'VectorSpace' using parallel composition as addition,--- and time scaling as scalar multiplication.----newtype Track a = Track { getTrack' :: [Occ a] }- deriving (Eq, Ord, Show, Functor, Foldable, Typeable, Traversable, Monoid, Semigroup, Delayable, Stretchable)--{--instance Semigroup (Track a) where- (<>) = mappend---- Equivalent to the derived Monoid, except for the sorted invariant.-instance Monoid (Track a) where- mempty = Track []- Track as `mappend` Track bs = Track (as `m` bs)- where- m = mergeBy (comparing fst)--}--instance Wrapped (Track a) where- type Unwrapped (Track a) = [Occ a]- _Wrapped' = iso getTrack' Track--instance Applicative Track where- pure = return- (<*>) = ap--instance Monad Track where- return = (^. _Unwrapped') . return . return- xs >>= f = (^. _Unwrapped') $ mbind ((^. _Wrapped') . f) ((^. _Wrapped') xs)--instance Alternative Track where- empty = mempty- (<|>) = mappend--instance MonadPlus Track where- mzero = mempty- mplus = mappend--instance HasOnset (Track a) where- onset (Track a) = list origin (onset . head) a--instance IsPitch a => IsPitch (Track a) where- fromPitch = pure . fromPitch--instance IsDynamics a => IsDynamics (Track a) where- fromDynamics = pure . fromDynamics--instance IsInterval a => IsInterval (Track a) where- fromInterval = pure . fromInterval--type instance Pitch (Track a) = Pitch a-instance (HasSetPitch a b, Transformable (Pitch (Track a)), Transformable (Pitch (Track b))) => HasSetPitch (Track a) (Track b) where- type SetPitch g (Track a) = Track (SetPitch g a)- -- FIXME this is wrong, need to behave like __mapPitch'- __mapPitch f = fmap (__mapPitch f)----- |--- Create a voice from a list of occurences.----track' :: Iso' [(Time, a)] (Track a)-track' = track---- |--- Create a voice from a list of occurences.----track :: Iso [(Time, a)] [(Time, b)] (Track a) (Track b)-track = iso mkTrack getTrack- where- mkTrack = Track . fmap (uncurry occ . first (fmap realToFrac))- getTrack = fmap (first (fmap realToFrac) . getOcc) . getTrack'----newtype Occ a = Occ (Sum Time, a)- deriving (Eq, Ord, Show, {-Read, -}Functor, Applicative, Monad, Foldable, Traversable)--occ t x = Occ (Sum t, x)-getOcc (Occ (Sum t, x)) = (t, x)--instance Delayable (Occ a) where- delay n (Occ (s,x)) = Occ (delay n s, x)-instance Stretchable (Occ a) where- stretch n (Occ (s,x)) = Occ (stretch n s, x)-instance HasOnset (Occ a) where- onset (Occ (s,x)) = onset s-
+ src/Music/Score/Tremolo.hs view
@@ -0,0 +1,106 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides a representation for tremolo, i.e. rapid iterations of a note.+--+-------------------------------------------------------------------------------------+++module Music.Score.Tremolo (++ -- * Tremolo+ HasTremolo(..),+ TremoloT(..),+ tremolo,++ ) where++import Control.Applicative+import Control.Comonad+import Control.Lens hiding (transform)+import Data.Foldable+import Data.Foldable+import Data.Functor.Couple+import Data.Ratio+import Data.Semigroup+import Data.Typeable+import Data.Word++import Music.Dynamics.Literal+import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal+import Music.Score.Part+import Music.Score.Phrases+import Music.Time++class HasTremolo a where+ setTrem :: Int -> a -> a++instance HasTremolo a => HasTremolo (b, a) where+ setTrem n = fmap (setTrem n)++instance HasTremolo a => HasTremolo (Couple b a) where+ setTrem n = fmap (setTrem n)++instance HasTremolo a => HasTremolo [a] where+ setTrem n = fmap (setTrem n)++instance HasTremolo a => HasTremolo (Score a) where+ setTrem n = fmap (setTrem n)++++newtype TremoloT a = TremoloT { getTremoloT :: Couple (Max Word) a }+ deriving (Eq, Show, Ord, Functor, Foldable, Typeable, Applicative, Monad, Comonad)+--+-- We use Word instead of Int to get (mempty = Max 0), as (Max.mempty = Max minBound)+-- Preferably we would use Natural but unfortunately this is not an instance of Bounded+--++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (TremoloT a) where+ type Unwrapped (TremoloT a) = Couple (Max Word) a+ _Wrapped' = iso getTremoloT TremoloT++instance Rewrapped (TremoloT a) (TremoloT b)++instance HasTremolo (TremoloT a) where+ setTrem n = set (_Wrapped . _Wrapped . _1) (Max $ fromIntegral n)++-- Lifted instances+deriving instance Num a => Num (TremoloT a)+deriving instance Fractional a => Fractional (TremoloT a)+deriving instance Floating a => Floating (TremoloT a)+deriving instance Enum a => Enum (TremoloT a)+deriving instance Bounded a => Bounded (TremoloT a)+deriving instance (Num a, Ord a, Real a) => Real (TremoloT a)+deriving instance (Real a, Enum a, Integral a) => Integral (TremoloT a)++-- |+-- Set the number of tremolo divisions for all notes in the score.+--+tremolo :: HasTremolo a => Int -> a -> a+tremolo = setTrem+
− src/Music/Score/Util.hs
@@ -1,384 +0,0 @@---{-# LANGUAGE ViewPatterns #-}--module Music.Score.Util where------------------------------------------------------------------------------ File generated by hackette. Do not edit!------ Fetched by hans on Sön 9 Mar 2014 14:52:14 CET------------------------------------------------------------------------------{-# LANGUAGE ViewPatterns #-}--{-- Rules:- - * Functions may depend on any module in the lastest Haskell Platform release- * All functions but those in Prelude must be referred to with their full,- qualified names (i.e. Data.List.unfoldr).- * Each function must have a unique name (so the whole file is a loadable module).- * Each function should have a synopisis, like:-- -- | Ordinary Haddock commentary ...- -- > category: Categories (please use the common Hackage names)- -- > depends : base (all packages in HP that the function depends on)- --}--import qualified Data.Char-import qualified Data.Monoid-import qualified Data.List-import qualified Data.Ratio---- | Synonym for 'Data.Char.toUpper'--- > category : String--- > depends : base-toUpperChar :: Char -> Char-toUpperChar = Data.Char.toUpper---- | Synonym for 'Data.Char.toLower'--- > category : String--- > depends : base-toLowerChar :: Char -> Char-toLowerChar = Data.Char.toLower---- | Synonym for 'fmap Data.Char.toUpper'--- > category : String--- > depends : base-toUpperString :: String -> String-toUpperString = fmap Data.Char.toUpper---- | Synonym for 'fmap Data.Char.toLower'--- > category : String--- > depends : base-toLowerString :: String -> String-toLowerString = fmap Data.Char.toLower---- | Convert a string to use upper case for the leading letter and lower case for remaining letters.--- > category : String--- > depends : base-toCapitalString :: String -> String-toCapitalString [] = []-toCapitalString (x:xs) = toUpperChar x : toLowerString xs---- | Synonym for '(++)'--- > category : List--- > depends : base-withPrefix :: [a] -> [a] -> [a]-withPrefix x = (x ++)---- | Synonym for 'flip (++)'--- > category : List--- > depends : base-withSuffix :: [a] -> [a] -> [a]-withSuffix x = (++ x)---- | Separate a list by the given element. Equivalent to 'Data.List.intersperse'.--- > category : List--- > depends : base-sep :: a -> [a] -> [a]-sep = Data.List.intersperse---- | Initiate and separate a list by the given element.--- > category : List--- > depends : base-pre :: a -> [a] -> [a]-pre x = (x :) . sep x---- | Separate and terminate a list by the given element.--- > category : List--- > depends : base-post :: a -> [a] -> [a]-post x = withSuffix [x] . sep x---- | Separate and terminate a list by the given element.--- > category : List--- > depends : base-wrap :: a -> a -> [a] -> [a]-wrap x y = (x :) . withSuffix [y] . sep x---- | Combination of 'concat' and 'sep'. Equivalent to 'Data.List.intercalate'.--- > category : List--- > depends : base-concatSep :: [a] -> [[a]] -> [a]-concatSep x = concat . sep x---- | Combination of 'concat' and 'pre'.--- > category : List--- > depends : base-concatPre :: [a] -> [[a]] -> [a]-concatPre x = concat . pre x---- | Combination of 'concat' and 'post'.--- > category : List--- > depends : base-concatPost :: [a] -> [[a]] -> [a]-concatPost x = concat . post x---- | Combination of 'concat' and 'wrap'.--- > category : List--- > depends : base-concatWrap :: [a] -> [a] -> [[a]] -> [a]-concatWrap x y = concat . wrap x y---- | Divide a list into parts of maximum length n.--- > category : List--- > depends : base-divideList :: Int -> [a] -> [[a]]-divideList n xs- | length xs <= n = [xs]- | otherwise = [take n xs] ++ (divideList n $ drop n xs)---- | Group a list into sublists whereever a predicate holds. The matched element--- is the first in the sublist.------ > splitWhile isSpace "foo bar baz"--- > ===> ["foo"," bar"," baz"]--- >--- > splitWhile (> 3) [1,5,4,7,0,1,2]--- > ===> [[1],[5],[4],[7,0,1,2]]------ > category : List--- > depends : base-splitWhile :: (a -> Bool) -> [a] -> [[a]]-splitWhile p xs = case splitWhile' p xs of- []:xss -> xss- xss -> xss- where- splitWhile' p [] = [[]]- splitWhile' p (x:xs) = case splitWhile' p xs of- (xs:xss) -> if p x then []:(x:xs):xss else (x:xs):xss----- | Break up a list into parts of maximum length n, inserting the given list as separator.--- Useful for breaking up strings, as in @breakList 80 "\n" str@.------ > category : List--- > depends : base-breakList :: Int -> [a] -> [a] -> [a]-breakList n z = Data.Monoid.mconcat . Data.List.intersperse z . divideList n---- | Map over the indices and elements of list.--- > category : List--- > depends : base-mapIndexed :: (Int -> a -> b) -> [a] -> [b]-mapIndexed f as = map (uncurry f) (zip is as)- where- n = length as - 1- is = [0..n]- --- test---- | Duplicate an element.--- > category: Combinator, Tuple--- > depends: base-dup :: a -> (a,a)-dup x = (x,x)---- | Unfold a partial function. This is a simpler version of 'Data.List.unfoldr'. --- > category: Function, List--- > depends: base-unf :: (a -> Maybe a) -> a -> [a]-unf f = Data.List.unfoldr (fmap dup . f)---- |--- Map over first elements of a list.--- Biased on first element for shorter lists.--- > category: List--- > depends: base-mapF f = mapFTL f id id---- |--- Map over all but the first and last elements of a list.--- Biased on middle elements for shorter lists.--- > category: List--- > depends: base-mapT f = mapFTL id f id---- |--- Map over last elements of a list.--- Biased on last element for shorter lists.--- > category: List--- > depends: base-mapL f = mapFTL id id f---- |--- Map over first, middle and last elements of list.--- Biased on first, then on first and last for short lists.------ > category: List--- > depends: base-mapFTL :: (a -> b) -> (a -> b) -> (a -> b) -> [a] -> [b]-mapFTL 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]---- |--- Extract the first consecutive sublist for which the predicate returns true, or--- the empty list if no such sublist exists.--- > category: List--- > depends: base-filterOnce :: (a -> Bool) -> [a] -> [a]-filterOnce p = Data.List.takeWhile p . Data.List.dropWhile (not . p)----- | Returns all rotations of the given list. Given an infinite list, returns an infinite--- list of rotated infinite lists.--- > category: List--- > depends: base-rots :: [a] -> [[a]]-rots xs = init (zipWith (++) (Data.List.tails xs) (Data.List.inits xs))---- |--- > category: List--- > depends: base-rotl :: [a] -> [a]-rotl [] = []-rotl (x:xs) = xs ++ [x]---- |--- > category: List--- > depends: base-rotr :: [a] -> [a]-rotr [] = []-rotr xs = last xs : init xs---- |--- > category: List--- > depends: base-rotated :: Int -> [a] -> [a]-rotated = go- where- go n as - | n >= 0 = iterate rotr as !! n- | n < 0 = iterate rotl as !! abs n---curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d-curry3 = curry . curry . (. tripl)--uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d-uncurry3 = (. untripl) . uncurry . uncurry--untripl :: (a,b,c) -> ((a,b),c)-untripl (a,b,c) = ((a,b),c)--tripl :: ((a,b),c) -> (a,b,c)-tripl ((a,b),c) = (a,b,c)--tripr :: (a,(b,c)) -> (a,b,c)-tripr (a,(b,c)) = (a,b,c)---- | Case matching on lists.--- > category: List--- > depends: base-list :: r -> ([a] -> r) -> [a] -> r-list z f [] = z-list z f xs = f xs---- | Merge lists.--- > category: List--- > depends: base-merge :: Ord a => [a] -> [a] -> [a]-merge = mergeBy compare---- | Merge lists.--- > category: List--- > depends: base-mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]-mergeBy f = mergeBy' $ (fmap.fmap) orderingToBool f- where- orderingToBool LT = True- orderingToBool EQ = True- orderingToBool GT = False--mergeBy' :: (a -> a -> Bool) -> [a] -> [a] -> [a]-mergeBy' pred xs [] = xs-mergeBy' pred [] ys = ys-mergeBy' pred (x:xs) (y:ys) =- case pred x y of- True -> x: mergeBy' pred xs (y:ys)- False -> y: mergeBy' pred (x:xs) ys---- | Compose all functions.--- > category: Function--- > depends: base-composed :: [b -> b] -> b -> b-composed = Prelude.foldr (.) id---- | Separate a ratio.--- > category: Math--- > depends: base-unRatio :: Integral a => Data.Ratio.Ratio a -> (a, a)-unRatio x = (Data.Ratio.numerator x, Data.Ratio.denominator x)---- | Nicer printing of ratio as ordinary fractions.--- > category: Math--- > depends: base-showRatio :: (Integral a, Show a) => Data.Ratio.Ratio a -> String-showRatio (realToFrac -> (unRatio -> (x, 1))) = show x-showRatio (realToFrac -> (unRatio -> (x, y))) = "(" ++ show x ++ "/" ++ show y ++ ")"----- Replace all contigous ranges of equal values with [Just x, Nothing, Nothing ...]--- > category: List--- > depends: base-retainUpdates :: Eq a => [a] -> [Maybe a]-retainUpdates = snd . Data.List.mapAccumL g Nothing where- g Nothing x = (Just x, Just x)- g (Just p) x = (Just x, if p == x then Nothing else Just x)----- Generic version of 'replicate'.--- > category: List--- > depends: base-replic :: Integral a => a -> b -> [b]-replic n = replicate (fromIntegral n)---- Swap components.--- > category: Tuple--- > depends: base-swap :: (a, b) -> (b, a)-swap (x, y) = (y, x)---- Interleave a list with the next consecutive element.------ For any xs------ > lenght xs == length (withNext xs)------ If @xs@ is a finite list------ > isNothing $ snd $ last $ withNext xs == True--- > all isJust $ snd $ init $ withNext xs == True------ If @xs@ is an infinite list------ > all isJust $ snd $ withNext xs == True------ > category: List--- > depends: base-withNext :: [a] -> [(a, Maybe a)]-withNext = go- where- go [] = []- go [x] = [(x, Nothing)]- go (x:y:rs) = (x, Just y) : withNext (y : rs)---- Map over a list with the next consecutive element.------ > category: List--- > depends: base-mapWithNext :: (a -> Maybe a -> b) -> [a] -> [b]-mapWithNext f = fmap (uncurry f) . withNext
− src/Music/Score/Voice.hs
@@ -1,183 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-{-# LANGUAGE UndecidableInstances #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides the 'Voice' type.-------------------------------------------------------------------------------------------module Music.Score.Voice (- -- * Voice type- Voice,- voice',- voice,-- zipVoice,- zipVoiceWith,- dzipVoiceWith,- mergeEqual,- ) where--import Control.Applicative-import Control.Arrow-import Control.Lens-import Control.Monad-import Control.Monad.Compose-import Data.Semigroup--import Data.Foldable (Foldable (..), foldMap)-import qualified Data.Foldable as F-import qualified Data.List as List-import Data.PairMonad ()-import Data.Traversable (Traversable (..))-import qualified Data.Traversable as T-import Data.Typeable-import Data.VectorSpace hiding (Sum)--import Music.Dynamics.Literal-import Music.Pitch.Literal-import Music.Score.Pitch-import Music.Score.Util-import Music.Time----- |--- A voice is a list of events with explicit duration. Events can not overlap.------ Voice is a 'Monoid' under sequential composition. 'mempty' is the empty part and 'mappend'--- appends parts.------ Voice is a 'Monad'. 'return' creates a part containing a single value of duration--- one, and '>>=' transforms the values of a part, allowing the addition and--- removal of values under relative duration. Perhaps more intuitively, 'join' scales--- each inner part to the duration of the outer part, then removes the--- intermediate structure.------ > let p = Voice [(1, Just 0), (2, Just 1)] :: Voice Int--- >--- > p >>= \x -> Voice [ (1, Just $ toEnum $ x+65),--- > (3, Just $ toEnum $ x+97) ] :: Voice Char--- >--- > ===> Voice {getVoice = [ (1 % 1,Just 'A'),--- > (3 % 1,Just 'a'),--- > (2 % 1,Just 'B'),--- > (6 % 1,Just 'b') ]}------ Voice is a 'VectorSpace' using sequential composition as addition, and time scaling--- as scalar multiplication.----newtype Voice a = Voice { getVoice' :: [Ev a] }- deriving (Eq, Ord, Show, Functor, Foldable, Monoid, Semigroup, Typeable, Traversable, Stretchable)---instance Wrapped (Voice a) where- type Unwrapped (Voice a) = [Ev a]- _Wrapped' = iso getVoice' Voice--instance Applicative Voice where- pure = return- (<*>) = ap--instance Monad Voice where- return = (^. _Unwrapped') . return . return- xs >>= f = (^. _Unwrapped') $ ((^. _Wrapped') . f) `mbind` ((^. _Wrapped') xs)--instance HasDuration (Voice a) where- duration = sum . fmap duration . getVoice'--instance IsPitch a => IsPitch (Voice a) where- fromPitch = pure . fromPitch--instance IsDynamics a => IsDynamics (Voice a) where- fromDynamics = pure . fromDynamics--instance IsInterval a => IsInterval (Voice a) where- fromInterval = pure . fromInterval---- TODO-instance Num a => Num (Voice a) where- fromInteger = pure . fromInteger--type instance Pitch (Voice a) = Pitch a-instance (HasSetPitch a b, Transformable (Pitch a), Transformable (Pitch b)) => HasSetPitch (Voice a) (Voice b) where- type SetPitch g (Voice a) = Voice (SetPitch g a)- -- FIXME this is wrong, need to behave like __mapPitch'- __mapPitch f = fmap (__mapPitch f)---- |--- Create a voice from a list of events.----voice' :: Iso' [(Duration, a)] (Voice a)-voice' = voice---- |--- Create a voice from a list of events.----voice :: Iso [(Duration, a)] [(Duration, b)] (Voice a) (Voice b)-voice = iso mkVoice getVoice- where- mkVoice = Voice . fmap (uncurry ev . first realToFrac)- getVoice = fmap (first realToFrac . getEv) . getVoice'---- |--- Join the given voices by multiplying durations and pairing values.----zipVoice :: Voice a -> Voice b -> Voice (a, b)-zipVoice = zipVoiceWith (,)---- |--- Join the given voices by multiplying durations and combining values using the given function.----zipVoiceWith :: (a -> b -> c) -> Voice a -> Voice b -> Voice c-zipVoiceWith f (Voice a) (Voice b) = Voice $ zipWith (\(Ev (dx,vx)) (Ev (dy,vy)) -> Ev (dx <> dy, f vx vy)) a b---- |--- Join the given voices by combining durations and values using the given function.----dzipVoiceWith :: (Duration -> Duration -> a -> b -> (Duration, c)) -> Voice a -> Voice b -> Voice c-dzipVoiceWith f (Voice a) (Voice b) = Voice $ zipWith (\(Ev (Product dx,vx)) (Ev (Product dy,vy)) -> Ev (first Product $ f dx dy vx vy)) a b---- |--- Merge consecutive equal note.----mergeEqual :: Eq a => Voice a -> Voice a-mergeEqual = over (from voice) $ fmap f . List.groupBy (inspecting snd)- where- f dsAs = let (ds,as) = unzip dsAs in (sum ds, head as)--inspecting :: Eq a => (b -> a) -> b -> b -> Bool-inspecting p x y = p x == p y--newtype Ev a = Ev (Product Duration, a)- deriving (Eq, Ord, Show, {-Read, -}Functor, Applicative, Monad, Foldable, Traversable)--ev t x = Ev (Product t, x)-getEv (Ev (Product t, x)) = (t, x)--instance Stretchable (Ev a) where- stretch n (Ev (s,x)) = Ev (stretch n s, x)--instance HasDuration (Ev a) where- duration (Ev (s,x)) = duration s--
src/Music/Time.hs view
@@ -1,7 +1,7 @@ ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -14,27 +14,55 @@ ------------------------------------------------------------------------------------- module Music.Time (- module Music.Time.Time,- module Music.Time.Span,+ -- * Prerequisites+ module Data.Functor.Rep.Lens, - module Music.Time.Delayable,- module Music.Time.Stretchable,- module Music.Time.Onset,+ -- * Basic types+ module Music.Time.Types, - module Music.Time.Juxtapose,- module Music.Time.Reverse,- module Music.Time.Reactive,- module Music.Time.Behavior+ -- * Time transformations+ module Music.Time.Transform,+ module Music.Time.Duration,+ module Music.Time.Position,+ module Music.Time.Split,+ module Music.Time.Reverse,+ module Music.Time.Juxtapose,+ module Music.Time.Rest,++ -- * Time types+ -- ** Discrete time+ module Music.Time.Stretched,+ module Music.Time.Delayed,+ module Music.Time.Note,+ module Music.Time.Voice,+ module Music.Time.Chord,+ module Music.Time.Track,+ module Music.Time.Score,+ -- ** Continous time+ module Music.Time.Segment,+ module Music.Time.Behavior,+ module Music.Time.Reactive, ) where -import Music.Time.Span-import Music.Time.Time+import Data.Functor.Rep+import Data.Functor.Rep.Lens -import Music.Time.Delayable-import Music.Time.Onset-import Music.Time.Stretchable+import Music.Time.Duration+import Music.Time.Juxtapose+import Music.Time.Position+import Music.Time.Rest+import Music.Time.Reverse+import Music.Time.Split+import Music.Time.Transform+import Music.Time.Types import Music.Time.Behavior-import Music.Time.Juxtapose+import Music.Time.Chord+import Music.Time.Delayed+import Music.Time.Note import Music.Time.Reactive-import Music.Time.Reverse+import Music.Time.Score+import Music.Time.Segment+import Music.Time.Stretched+import Music.Time.Track+import Music.Time.Voice
src/Music/Time/Behavior.hs view
@@ -1,21 +1,22 @@ {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -25,136 +26,354 @@ -- -- Behaviors, or time-varying values. ----- TODO integrate better in the library--- ------------------------------------------------------------------------------------- module Music.Time.Behavior (- Behavior,- -- constant,- -- behavior,- -- varying,- -- varyingIn,- -- time,- -- switchB,- -- trimBeforeB,- ) where -import Prelude hiding (trimAfter)+ -- * Behavior type+ Behavior, -import Control.Applicative-import Control.Arrow-import Control.Lens-import Control.Monad-import Control.Monad.Compose-import Control.Monad.Plus+ -- ** Examples+ behavior,++ -- * Combinators+ switch,+ switch',+ -- splice,+ -- trim,+ trimBefore,+ trimAfter,+ -- concatB,++ -- * Common behaviors+ -- ** Oscillators+ line,+ sawtooth,+ sine,+ cosine,+ + -- ** Impulse functions+ unit,+ impulse,+ turnOn,+ turnOff,++ ) where+ import Data.AffineSpace import Data.AffineSpace.Point-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.NumInstances ()-import Data.Profunctor+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio import Data.Semigroup-import Data.Set (Set)-import qualified Data.Set as Set-import Data.Traversable (Traversable)-import qualified Data.Traversable as T-import Data.Typeable+import Data.Set (Set)+import qualified Data.Set as Set import Data.VectorSpace+import Prelude hiding (trimAfter) +import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Lens hiding (Indexable, Level, above,+ below, index, inside, parts,+ reversed, transform, (<|), (|>))++import Data.Distributive+import Data.Functor.Rep+import Data.Functor.Rep.Lens+import Data.Typeable+import Music.Time.Bound+import Music.Time.Internal.Transform+import Music.Time.Note+import Music.Time.Reverse+import Music.Time.Score+import Music.Time.Split+ import Music.Dynamics.Literal+import Music.Pitch.Alterable+import Music.Pitch.Augmentable import Music.Pitch.Literal-import Music.Time.Delayable-import Music.Time.Reactive-import Music.Time.Span-import Music.Time.Stretchable-import Music.Time.Time-import Music.Time.Time --- Inner TFun focuses on [0..1]-newtype Behavior a = Behavior { getBehavior :: Reactive (Time -> a) }- deriving (Functor, Semigroup, Monoid) -instance Delayable (Behavior a) where- delay n (Behavior x) = Behavior (fmap (delay n) $ delay n x) -instance Stretchable (Behavior a) where- stretch n (Behavior x) = Behavior (fmap (stretch n) $ stretch n x)+-- Behavior is 'Representable':+--+-- > ask = realToFrac <$> time+-- > localRep (- t) = delay t+-- > localRep (/ t) = stretch t -instance Wrapped (Behavior a) where- type Unwrapped (Behavior a) = (Reactive (Time -> a))- _Wrapped' = iso getBehavior Behavior+-- |+-- A 'Behavior' is a value varying over time.+--+-- Use 'focusing' to view a particular 'Segment'.+--+-- The semantics are given by+--+-- @+-- type Behavior a = 'Time' -> a+-- @+--+newtype Behavior a = Behavior { getBehavior :: Time -> a }+ deriving (Functor, Applicative, Monad, Typeable) -instance Applicative Behavior where- pure = (^. _Unwrapped') . pure . pure- ((^. _Wrapped') -> f) <*> ((^. _Wrapped') -> x) = (^. _Unwrapped') $ liftA2 (<*>) f x+--+-- $musicTimeBehaviorExamples+--+-- 'behavior' let us convert any function to a behavior using '^.' or 'view'.+--+-- We can unwrap a behavior using @'from' 'behavior'@ or '!^'.+--+-- A sine function+--+-- @+-- ('sin' . (* 'tau') . 'realToFrac')^.'behavior'+-- @+--+-- A behavior that switches from (-1) to 1 at time 0+--+-- @+-- (\\t -> if t < 0 then (-1) else 1)^.'behavior'+-- @+--+-- A time-varying function applied to a value+--+-- @+-- ('+')^.'behavior' '<*>' 10+-- @+--+instance Show (Behavior a) where+ show _ = "<<Behavior>>" -instance HasBehavior Behavior where- (?) = behAt+deriving instance Distributive Behavior +instance Transformable (Behavior a) where+ transform s (Behavior a) = Behavior (a `whilst` s)+ where+ f `whilst` s = f . transform (negateV s)++instance Reversible (Behavior a) where+ rev = stretch (-1)+ -- TODO alternative+ -- rev = (stretch (-1) `whilst` undelaying 0.5)+ -- (i.e. revDefault pretending that Behaviors have era (0 <-> 1))++instance Representable Behavior where+ type Rep Behavior = Time+ tabulate = Behavior+ index (Behavior x) = x++deriving instance Semigroup a => Semigroup (Behavior a)+deriving instance Monoid a => Monoid (Behavior a) deriving instance Num a => Num (Behavior a) deriving instance Fractional a => Fractional (Behavior a) deriving instance Floating a => Floating (Behavior a) +-- TODO bad instance+instance Real a => Real (Behavior a) where+ toRational = toRational . (! 0)++-- #ifdef INCLUDE_LIFTED+deriving instance AdditiveGroup a => AdditiveGroup (Behavior a)+ instance IsPitch a => IsPitch (Behavior a) where- fromPitch = pure . fromPitch+ fromPitch = pure . fromPitch+ instance IsInterval a => IsInterval (Behavior a) where- fromInterval = pure . fromInterval+ fromInterval = pure . fromInterval+ instance IsDynamics a => IsDynamics (Behavior a) where- fromDynamics = pure . fromDynamics-instance AdditiveGroup a => AdditiveGroup (Behavior a) where- zeroV = pure zeroV- negateV = fmap negateV- (^+^) = liftA2 (^+^)+ fromDynamics = pure . fromDynamics++instance Alterable a => Alterable (Behavior a) where+ sharpen = fmap sharpen+ flatten = fmap flatten++instance Augmentable a => Augmentable (Behavior a) where+ augment = fmap augment+ diminish = fmap diminish++instance Eq a => Eq (Behavior a) where+ (==) = error "No fun"++instance Ord a => Ord (Behavior a) where+ (<) = error "No fun"+ max = liftA2 max+ min = liftA2 min++instance Enum a => Enum (Behavior a) where+ toEnum = pure . toEnum+ fromEnum = fromEnum . (! 0)++instance VectorSpace a => VectorSpace (Behavior a) where+ type Scalar (Behavior a) = Behavior (Scalar a)+ (*^) = liftA2 (*^)+ instance AffineSpace a => AffineSpace (Behavior a) where- type Diff (Behavior a) = Behavior (Diff a)- (.+^) = liftA2 (.+^)- (.-.) = liftA2 (.-.)+ type Diff (Behavior a) = Behavior (Diff a)+ (.-.) = liftA2 (.-.)+ (.+^) = liftA2 (.+^)+-- #endif +-- |+-- View a behavior as a time function and vice versa.+--+-- Note that this is just an alias defined to make the documentation nicer:+--+--+-- @+-- 'behavior' = 'tabulated'+-- @+--+behavior :: Iso (Time -> a) (Time -> b) (Behavior a) (Behavior b)+behavior = tabulated +-- |+-- View a time function as a behavior.+--+-- @+-- unbehavior = from behavior+-- x^.unbehavior = (x !)+-- @+--+unbehavior :: Iso (Behavior a) (Behavior b) (Time -> a) (Time -> b)+unbehavior = from behavior +--+-- @+-- ('const' x)^.'behavior' ! t = x forall t+-- @+--+-- --- | A constant (non-varying) behavior.++-- |+-- A behavior that ----- Identical to @behavior . const@ but creates more efficient behaviors.-constant :: a -> Behavior a-constant = (^. _Unwrapped') . pure . pure+line' :: Behavior Time+line' = id ^. tabulated --- | Create a behavior from function of (absolute) time.+-- |+-- A behavior that gives the current time, i.e. the identity function ----- You should pass a function defined for the whole range, including negative time.-behavior :: (Time -> a) -> Behavior a-behavior = (^. _Unwrapped') . pure+-- Should really have the type 'Behavior' 'Time', but is provided in a more general form+-- for convenience.+--+line :: Fractional a => Behavior a+line = realToFrac ^. tabulated+--+-- > f t = t+-- --- | Create a behaviour from a function of (relative) duration focused on 'sunit'.+-- |+-- A behavior that varies from 0 to 1 during the same time interval and is 0 before and 1 after+-- that interval. ----- You should pass a function defined for the whole range, including negative durations.-varying :: (Duration -> a) -> Behavior a-varying = varyingIn sunit+unit :: Fractional a => Behavior a+unit = switch 0 0 (switch 1 line 1)+-- > f t | t < 0 = 0+-- > | t > 1 = 1+-- > | otherwise = t+-- --- | Create a behaviour from a function of (relative) duration focused on the given span.+-- |+-- A behavior that ----- You should pass a function defined for the whole range, including negative durations.-varyingIn :: Span -> (Duration -> a) -> Behavior a-varyingIn s f = behavior $ sapp (sinvert s) (lmap (.-. start) f)+interval :: (Fractional a, Transformable a) => Time -> Time -> Note (Behavior a)+interval t u = (t <-> u, line) ^. note --- | @b ?? t@ returns the value of the behavior at time @t@.--- Semantic function.-behAt :: Behavior a -> Time -> a-b `behAt` t = ((^. _Wrapped') b ? t) t+-- |+-- A behavior that+--+sine :: Floating a => Behavior a+sine = sin (line*tau) -time :: Behavior Time-time = behavior id--- --- trimBeforeB :: Monoid a => Time -> Behavior a -> Behavior a--- trimBeforeB t = _Wrapping' Behavior %~ trimBefore t--- --- switchB :: Time -> Behavior a -> Behavior a -> Behavior a--- switchB t ((^. _Wrapped') -> x) ((^. _Wrapped') -> y) = (^. _Unwrapped') $ switch t x y--- --- --- --- +-- |+-- A behavior that+--+cosine :: Floating a => Behavior a+cosine = cos (line*tau)++-- |+-- A behavior that goes from 0 to 1 repeatedly with a period of 1.+--+sawtooth :: RealFrac a => Behavior a+sawtooth = line - fmap floor' line++-- |+-- A behavior that is 1 at time 0, and 0 at all other times.+--+impulse :: Num a => Behavior a+impulse = switch' 0 0 1 0+-- > f t | t == 0 = 1+-- > | otherwise = 0+--++-- |+-- A behavior that goes from 0 to 1 at time 0.+--+turnOn = switch 0 0 1++-- |+-- A behavior that goes from 1 to 0 at time 0.+--+turnOff = switch 0 1 0++--+-- TODO+--+-- Because the 'Time' type is fixed and unbounded in the current version, we can not+-- define a generix isomorphism from behaviors to segments. If we change the library to+-- provide multiple time representations (using TFs or similar), we should provide+-- these combinators:+--+-- > focusOnFullRange :: Bounded Time => Behavior a -> Segment a+-- > focusingOnFullRange :: Bounded Time => Iso' (Behavior a) (Segment a)+--++{-+-- |+-- View part of a 'Behavior' as a 'Segment'.+--+-- This can be used to modify a behavior in a specific range, as in+--+-- @+-- 'line' & 'focusing' ``on`` (2 '<->' 3) '+a~' 1+-- 'line' & 'focusingOn' (2 '<->' 3) '+~' 1+-- @+--+focusingOn :: Span -> Lens' (Behavior a) (Segment a)+focusingOn s = flip whilstM (negateV s) . focusing+-- or focusing . flip whilstM s+-}++-- focusing `on` s == focusingOn s+f `on` s = flip whilstM (negateV s) . f++-- |+-- Instantly switch from one behavior to another.+--+switch :: Time -> Behavior a -> Behavior a -> Behavior a+switch t rx ry = switch' t rx ry ry++-- | Replace everthing before the given time by `mempty`.+trimBefore :: Monoid a => Time -> Behavior a -> Behavior a+trimBefore start = switch start mempty++-- | Replace everthing after the given time by `mempty`.+trimAfter :: Monoid a => Time -> Behavior a -> Behavior a+trimAfter stop x = switch stop x mempty++-- |+-- Instantly switch from one behavior to another with an optinal intermediate value.+--+switch' :: Time -> Behavior a -> Behavior a -> Behavior a -> Behavior a+switch' t rx ry rz = tabulate $ \u -> case u `compare` t of+ LT -> rx ! u+ EQ -> ry ! u+ GT -> rz ! u++-- TODO move+tau = 2 * pi++floor' :: RealFrac a => a -> a+floor' = fromIntegral . floor+
+ src/Music/Time/Bound.hs view
@@ -0,0 +1,152 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- /Warning/ This module is experimental.+--+-------------------------------------------------------------------------------------++module Music.Time.Bound (+ -- * Bound type+ Bound(..),+ + -- * Construction+ bounds,+ bounding,+ -- trim,+ -- splice,+ -- bounded',+ -- bounded,+ ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Reverse+import Music.Time.Split++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.Typeable++-- |+-- 'Bound' restricts the start and stop time of a value, and prevents access to values+-- outside the bounds.+--+-- 'Bound' is especially useful to restrict the range of a 'Behavior'. If we have a+-- value with can only be reasonably defined for a particular time range, we can+-- represent it as 'Bound' 'Behavior'. This is isomorphic to a 'Note' 'Segment', and+-- 'bounded' whitnesses the isomorphism.+--+-- 'Bound' is not 'Foldable' or 'Traversable', as that would allow us to access values+-- outside the bounds. However, we can still access values of a 'Bound' 'Behavior' in a+-- safe manner using 'trim' or 'splice'.+--+-- The semantics are given by+--+-- @+-- type Bound a = (Time, Time, a)+-- @+--+newtype Bound a = Bound { getBound :: (Span, a) }+ deriving (Functor, Semigroup, Typeable, Eq, Show)++--+-- These are both unsafe, as they allow us to define 'unBound'+--+-- instance Foldable Bound where+-- foldr f z (Bound (_,x)) = f x z+--+-- instance Traversable Bound where+-- traverse f (Bound (s,x)) = (Bound . (s,)) <$> f x+--++--+-- TODO define Applicative/Monad+--+--+-- This is a Writer-style instance with interval arithmetic style union/empty as the Monoid+-- A possible problem with this is that there are multiple representations of the empty+-- set (namely [(t, t)^.from range | t <- {Time} ]).+--++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Bound a) where+ type Unwrapped (Bound a) = (Span, a)+ _Wrapped' = iso getBound Bound++instance Rewrapped (Bound a) (Bound b)++instance Reversible a => Reversible (Bound a) where+ -- rev = over (_Wrapped . each) rev+ rev = over _Wrapped $ (rev *** rev)++instance (HasPosition a, Splittable a) => Splittable (Bound a) where+ -- TODO++-- |+-- 'Bound' transform by transforming the bounded value as well as+-- the bounds.+--+instance Transformable a => Transformable (Bound a) where+ transform t = over _Wrapped (transform t *** transform t)++instance (HasPosition a, HasDuration a) => HasDuration (Bound a) where+ _duration x = _offset x .-. _onset x++instance HasPosition a => HasPosition (Bound a) where+ -- TODO lawless+ -- _position (Bound (view range -> (t, u), x)) d = truncating t u (_position x d)+ _position (Bound (view range -> (t, u), x)) = alerp t u++truncating :: Ord a => a -> a -> a -> a+truncating t u x = (x `max` t) `min` u++-- |+-- Add bounds.+--+bounds :: Time -> Time -> a -> Bound a+bounds t u x = Bound (t <-> u, x)++-- |+-- Add bounds.+--+-- @+-- (s,x)^.note = (bounding s . transform s) x+-- @+--+bounding :: Span -> a -> Bound a+bounding (view range -> (t, u)) = bounds t u+
+ src/Music/Time/Chord.hs view
@@ -0,0 +1,136 @@++{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- /Warning/ This module is experimental.+--+-------------------------------------------------------------------------------------++module Music.Time.Chord (++ -- * Chord type+ Chord,+ -- * Construction+ chord,+ unsafeChord,++ ) where+++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Delayed+import Music.Time.Reverse+import Music.Time.Split++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Control.Monad+import Control.Monad.Compose+import Control.Monad.Plus+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import Data.Traversable (Traversable)+import qualified Data.Traversable as T+import Data.Typeable++import Music.Dynamics.Literal+import Music.Pitch.Literal+++-- |+-- A 'Chord' is a parallel composition of values.+--+-- @+-- type Chord a = [Delayed a]+-- @+--+newtype Chord a = Chord { getChord :: ChordList (ChordEv a) }+ deriving (Functor, Foldable, Traversable, Semigroup, Monoid, Typeable, Show, Eq)++-- Can use [] or Seq here+type ChordList = []++-- Can use any type as long as chordEv provides an Iso+type ChordEv a = Delayed a++chordEv :: Iso (Delayed a) (Delayed b) (ChordEv a) (ChordEv b)+chordEv = id++instance Applicative Chord where+ pure = return+ (<*>) = ap++instance Monad Chord where+ return = view _Unwrapped . return . return+ xs >>= f = view _Unwrapped $ (view _Wrapped . f) `mbind` view _Wrapped xs++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Chord a) where+ type Unwrapped (Chord a) = (ChordList (ChordEv a))+ _Wrapped' = iso getChord Chord++instance Rewrapped (Chord a) (Chord b)++instance Transformable (Chord a) where+ transform s = over _Wrapped' (transform s)++instance HasDuration (Chord a) where+ _duration = Foldable.sum . fmap _duration . view _Wrapped'++instance Splittable a => Splittable (Chord a) where+ -- TODO++instance Reversible a => Reversible (Chord a) where+ rev = over _Wrapped' (fmap rev) -- TODO OK?++-- TODO+-- instance HasMeta (Chord a) where+ -- meta = error "Not implemented: meta"++chord :: Lens (Chord a) (Chord b) [Delayed a] [Delayed b]+chord = _Wrapped++-- TODO names are not consistent with other types+unsafeChord :: Iso (Chord a) (Chord b) [Delayed a] [Delayed b]+unsafeChord = _Wrapped++deriving instance IsPitch a => IsPitch (Chord a) +deriving instance IsInterval a => IsInterval (Chord a) +deriving instance IsDynamics a => IsDynamics (Chord a)+
− src/Music/Time/Delayable.hs
@@ -1,121 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides delayable values.-------------------------------------------------------------------------------------------module Music.Time.Delayable (- -- * Delayable class- Delayable(..),- undelay,- delaying,- move,- moveBack,-- -- ** Utility- delayTime,- NoDelay(..),- ) where--import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Map (Map)-import qualified Data.Map as Map-import Data.Semigroup-import Data.Set (Set)-import qualified Data.Set as Set-import Data.VectorSpace hiding (Sum)--import Music.Time.Time---- |--- Delayable values.----class Delayable a where-- -- |- -- Delay a value.- --- delay :: Duration -> a -> a- delay _ = id--instance Delayable Time where- delay n = (.+^ n)--instance Delayable (Time -> a) where- delay n = (. delay (negateV n))--instance Delayable a => Delayable [a] where- delay n = fmap (delay n)--instance (Ord a, Delayable a) => Delayable (Set a) where- delay n = Set.map (delay n)--instance Delayable a => Delayable (Map k a) where- delay n = fmap (delay n)--instance Delayable (Time, a) where- delay n (t, a) = (n `delay` t, a)--instance Delayable (Time, Duration, a) where- delay n (t, d, a) = (n `delay` t, d, a)--instance Delayable a => Delayable (Sum a) where- delay n (Sum x) = Sum (delay n x)--instance Delayable a => Delayable (Product a) where- delay n (Product x) = Product (delay n x)------ |--- Move a score forward in time. Equivalent to 'delay'.----move :: Delayable a => Duration -> a -> a---- |--- Move a score backward in time. Negated verison of 'delay'.----moveBack :: Delayable a => Duration -> a -> a---- |--- Delay relative to 'origin'. Provided for situations when you have a value that--- should forward based on the distance between some time @t@ and the origin, but--- it does not necessarily have a start time.----delayTime :: Delayable a => Time -> a -> a--undelay t = delay (negateV t)-move = delay-moveBack t = delay (negateV t)-delayTime t = delay (t .-. origin)----- | Apply a function under delay.--- See also 'sunder'.-delaying :: (Delayable a, Delayable b) => Duration -> (a -> b) -> a -> b-delaying t f = undelay t . f . delay t--newtype NoDelay a = NoDelay { getNoDelay :: a }- deriving (Eq, Ord, Enum, Show, Semigroup, Monoid- {-Delayable, HasOnset, HasOffset, HasDuration-})--instance Delayable (NoDelay a) where- delay _ = id
+ src/Music/Time/Delayed.hs view
@@ -0,0 +1,143 @@++{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Delayed (+ -- * Delayed type+ Delayed,++ -- * Construction+ delayed,++ -- ** Inspecting delayed values+ delayedValue,+ ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Reverse+import Music.Time.Split++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Comonad+import Control.Comonad.Env+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import Data.PairMonad+import Data.Typeable++import Music.Dynamics.Literal+import Music.Pitch.Literal+++-- |+-- 'Delayed' represents a value with an offset in time.+--+-- A delayed value has a known 'position', but no 'duration'.+--+-- Placing a value inside 'Delayed' does not make it invariant under 'stretch', as the+-- offset of a delayed value may be stretched with respect to the origin. However, in+-- contrast to a note the /duration/ is not stretched.+--+-- The semantics are given by+--+-- @+-- type Delayed a = (Time, a)+-- @+--+newtype Delayed a = Delayed { _delayedValue :: (Time, a) }+ deriving (Eq, {-Ord, -}{-Show, -}+ Applicative, Monad, {-Comonad, -}+ Functor, Foldable, Traversable, Typeable)++deriving instance Show a => Show (Delayed a)++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Delayed a) where+ type Unwrapped (Delayed a) = (Time, a)+ _Wrapped' = iso _delayedValue Delayed++instance Rewrapped (Delayed a) (Delayed b)++instance Transformable (Delayed a) where+ transform t = over _Wrapped $ first (transform t)++instance HasDuration (Delayed a) where+ _duration x = _offset x .-. _onset x++instance HasPosition (Delayed a) where+ x `_position` p = ask (view _Wrapped x) `_position` p++instance Reversible (Delayed a) where+ rev = revDefault++instance Splittable a => Splittable (Delayed a) where+ -- FIXME++-- |+-- View a delayed value as a pair of a the original value and a delay time.+--+delayed :: Iso (Time, a) (Time, b) (Delayed a) (Delayed b)+delayed = _Unwrapped+++-- |+-- View a delayed value as a pair of the original value and the transformation (and vice versa).+--+delayedValue :: (Transformable a, Transformable b)+ => Lens+ (Delayed a) (Delayed b)+ a b+delayedValue = lens runDelayed (flip $ _delayed . const)+ where+ _delayed f (Delayed (t,x)) = Delayed (t, f `whilst` delaying (t .-. 0) $ x)+{-# INLINE delayedValue #-}+++runDelayed :: Transformable a => Delayed a -> a+runDelayed = uncurry delayTime . view _Wrapped+++deriving instance IsPitch a => IsPitch (Delayed a) +deriving instance IsInterval a => IsInterval (Delayed a) +deriving instance IsDynamics a => IsDynamics (Delayed a)+
+ src/Music/Time/Duration.hs view
@@ -0,0 +1,106 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Duration (+ module Music.Time.Transform,++ -- * The HasDuration class+ HasDuration(..),++ -- * Absolute duration+ duration,+ stretchTo,+ ) where++import Music.Time.Transform++import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.NumInstances ()+import Data.Semigroup hiding ()+import Data.VectorSpace hiding (Sum (..))++-- |+-- Class of values that have a duration.+--+-- Law Duration+--+-- @+-- '_duration' x = ('offset' x '.-.' 'onset' x)+-- @+--+class HasDuration a where+ _duration :: a -> Duration++instance HasDuration Time where+ _duration = 0++instance HasDuration Duration where+ _duration = id++instance HasDuration Span where+ _duration = snd . view delta++--+-- By convention, we treat pairs and triplets as having the form+-- (t,x), (d,x) and (t,d,x) where t has a position and d has a+-- duration. This makes it convenient to represent simple event+-- lists as [(Time, Duration, a)] without needing any special+-- structure.+--++instance HasDuration a => HasDuration (a, b) where+ _duration (d,_) = _duration d++instance HasDuration b => HasDuration (a, b, c) where+ _duration (_,d,_) = _duration d++instance HasDuration a => HasDuration (Product a) where+ _duration (Product x) = _duration x++instance HasDuration a => HasDuration (Sum a) where+ _duration (Sum x) = _duration x++instance HasDuration a => HasDuration (Min a) where+ _duration (Min x) = _duration x++instance HasDuration a => HasDuration (Max a) where+ _duration (Max x) = _duration x+++-- |+-- Access the duration.+--+duration :: (Transformable a, HasDuration a) => Lens' a Duration+duration = lens _duration (flip stretchTo)+{-# INLINE duration #-}++-- |+-- Stretch a value to have the given duration.+--+stretchTo :: (Transformable a, HasDuration a) => Duration -> a -> a+stretchTo d x = (d ^/ _duration x) `stretch` x+{-# INLINE stretchTo #-}+
+ src/Music/Time/Internal/Quantize.hs view
@@ -0,0 +1,424 @@++{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# 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)+--+-- Provides rhythmical quantization.+--+-- You generally need not worry about this module, as quantization is handled+-- automatically by 'HasBackendScore' instances.+--+-- /Warning/ This modules is poorly named and will probably be split or moved+-- soon.+--+-------------------------------------------------------------------------------------++module Music.Time.Internal.Quantize (+ -- * Rhythm type+ Rhythm(..),+ mapWithDur,++ -- * Quantization+ quantize,+ rewrite,+ dotMod,+ + -- * Utility+ drawRhythm+ ) where++import Prelude hiding (concat, concatMap, foldl, foldr,+ mapM, maximum, minimum, sum)++import Control.Applicative+import Control.Arrow hiding (left)+import Control.Lens (over, (^.), _Left)+import Control.Monad (MonadPlus (..), ap, join)+import Data.Either+import Data.Foldable+import Data.Function (on)+import qualified Data.List as List+import Data.Maybe+import Data.Ord (comparing)+import Data.Ratio+import Data.Semigroup+import Data.Traversable+import Data.Tree+import Data.VectorSpace++import Text.Parsec hiding ((<|>))+import Text.Parsec.Pos++import Music.Score.Ties+import Music.Score.Internal.Util+import Music.Time++data Rhythm a+ = Beat Duration a -- d is divisible by 2+ | Group [Rhythm a] --+ | Dotted Int (Rhythm a) -- n > 0.+ | Tuplet Duration (Rhythm a) -- d is an emelent of 'konstTuplets'.+ deriving (Eq, Show, Functor, Foldable)+ -- RInvTuplet Duration (Rhythm a)++getBeatValue :: Rhythm a -> a+getBeatValue (Beat d a) = a+getBeatValue _ = error "getBeatValue: Not a beat"++getBeatDuration :: Rhythm a -> Duration+getBeatDuration (Beat d a) = d+getBeatDuration _ = error "getBeatValue: Not a beat"++-- TODO return voice+realize :: Rhythm a -> [Stretched a]+realize (Beat d a) = [(d, a)^.stretched]+realize (Group rs) = rs >>= realize+realize (Dotted n r) = dotMod n `stretch` realize r+realize (Tuplet n r) = n `stretch` realize r++-- rhythmToTree :: Rhythm a -> Tree (String, Maybe a)+-- rhythmToTree = go+-- where+-- go (Beat d a) = Node ("beat " ++ showD d, Just a) []+-- go (Group rs) = Node ("group", Nothing) (fmap rhythmToTree rs)+-- go (Dotted n r) = Node ("dotted " ++ show n, Nothing) [rhythmToTree r]+-- go (Tuplet n r) = Node ("tuplet " ++ showD n, Nothing) [rhythmToTree r]+-- showD = show . toRational+--+-- drawRhythm :: Show a => Rhythm a -> String+-- drawRhythm = drawTree . fmap (uncurry (++) <<< (++ " ") *** show) . rhythmToTree++rhythmToTree :: Rhythm a -> Tree String+rhythmToTree = go+ where+ go (Beat d a) = Node ("" ++ showD d) []+ go (Group rs) = Node ("") (fmap rhythmToTree rs)+ go (Dotted n r) = Node (replicate n '.') [rhythmToTree r]+ go (Tuplet n r) = Node ("*^ " ++ showD n) [rhythmToTree r]+ showD = (\x -> show (numerator x) ++ "/" ++ show (denominator x)) . toRational++drawRhythm :: Show a => Rhythm a -> String+drawRhythm = drawTree . rhythmToTree++mapWithDur :: (Duration -> a -> b) -> Rhythm a -> Rhythm b+mapWithDur f = go+ where+ go (Beat d x) = Beat d (f d x)+ go (Dotted n (Beat d x)) = Dotted n $ Beat d (f (dotMod n * d) x)+ go (Group rs) = Group $ fmap (mapWithDur f) rs+ go (Tuplet m r) = Tuplet m (mapWithDur f r) ++instance Semigroup (Rhythm a) where+ (<>) = mappend++-- Catenates using 'Group'+instance Monoid (Rhythm a) where+ mempty = Group []+ Group as `mappend` Group bs = Group (as <> bs)+ r `mappend` Group bs = Group ([r] <> bs)+ Group as `mappend` r = Group (as <> [r])+ a `mappend` b = Group [a, b]++instance HasDuration (Rhythm a) where+ _duration (Beat d _) = d+ _duration (Dotted n a) = _duration a * dotMod n+ _duration (Tuplet c a) = _duration a * c+ _duration (Group as) = sum (fmap _duration as)++instance AdditiveGroup (Rhythm a) where+ zeroV = error "No zeroV for (Rhythm a)"+ (^+^) = error "No ^+^ for (Rhythm a)"+ negateV = error "No negateV for (Rhythm a)"++instance VectorSpace (Rhythm a) where+ type Scalar (Rhythm a) = Duration+ a *^ Beat d x = Beat (a*d) x+ -- TODO how does this preserve the invariant?++Beat d x `subDur` d' = Beat (d-d') x+++{-+ Rhythm rewrite laws (all up to realization equality)++ Note: Just sketching, needs more formal treatment.+++ Group [Group xs ...] = Group [xs ...]+ [JoinGroup]++ Tuplet m (Tuplet n x) = Tuplet (m * n) x+ [NestTuplet]++ Tuplet m (Group [a,b ...]) = Group [Tuplet m a, Tuplet m b ...]+ [DistributeTuplet]+ This is only OK in certain contexts! Which?++-}++rewrite :: Rhythm a -> Rhythm a+rewrite = rewriteR . rewrite1++rewriteR = go where+ go (Beat d a) = Beat d a+ go (Group rs) = Group (fmap (rewriteR . rewrite1) rs)+ go (Dotted n r) = Dotted n ((rewriteR . rewrite1) r)+ go (Tuplet n r) = Tuplet n ((rewriteR . rewrite1) r)++rewrite1 = tupletDot . splitTuplet . singleGroup+++singleGroup :: Rhythm a -> Rhythm a+singleGroup orig@(Group [x]) = x+singleGroup orig = orig++-- | Removes dotted notes in 2/3 tuplets.+tupletDot :: Rhythm a -> Rhythm a+tupletDot orig@(Tuplet ((unRatio.realToFrac) -> (2,3)) (Dotted 1 x)) = x+tupletDot orig = orig++-- | Splits a tuplet iff it contans a group which can be split into two halves of exactly the same size.+-- TODO this should only happen if the tuplet lenght is longer than a beat+splitTuplet :: Rhythm a -> Rhythm a+splitTuplet orig@(Tuplet n (Group xs)) = case trySplit xs of+ Nothing -> orig+ Just (as, bs) -> Tuplet n (Group as) <> Tuplet n (Group bs)+splitTuplet orig = orig++{-+-- TODO bad instance+instance HasDuration a => HasDuration [a] where+ _duration = sum . fmap _duration+-}++trySplit :: [Rhythm a] -> Maybe ([Rhythm a], [Rhythm a])+trySplit = firstJust . fmap g . splits+ where+ g (part1, part2)+ | (sum . fmap _duration) part1 == (sum . fmap _duration) part2 = Just (part1, part2)+ | otherwise = Nothing+ splits xs = List.inits xs `zip` List.tails xs+ firstJust = listToMaybe . fmap fromJust . List.dropWhile isNothing+++quantize :: Tiable a => [(Duration, a)] -> Either String (Rhythm a)+quantize = quantize' (atEnd rhythm)++testQuantize :: [Duration] -> IO ()+testQuantize x = case fmap rewrite $ quantize' (atEnd rhythm) $ fmap (\x -> (x,())) $ x of+ Left e -> error e+ Right x -> putStrLn $ drawRhythm x+++konstNumDotsAllowed :: [Int]+konstNumDotsAllowed = [1..4]++konstBounds :: [Duration]+konstBounds = [ 1/2, 1/4, 1/8, 1/16 ]++konstTuplets :: [Duration]+konstTuplets = [ 2/3, 4/5, 4/7, 8/9, 8/11, 8/13, 8/15, 16/17, 16/18, 16/19, 16/21, 16/23 ]++konstMaxTupletNest :: Int+konstMaxTupletNest = 1+++data RhythmContext = RhythmContext {++ -- Time scaling of the current note (from dots and tuplets).+ timeMod :: Duration,++ -- Time subtracted from the current rhythm (from ties).+ timeSub :: Duration,++ -- Number of tuplets above the current note (default 0).+ tupleDepth :: Int+ }++instance Monoid RhythmContext where+ mempty = RhythmContext { timeMod = 1, timeSub = 0, tupleDepth = 0 }+ a `mappend` _ = a++modifyTimeMod :: (Duration -> Duration) -> RhythmContext -> RhythmContext+modifyTimeMod f (RhythmContext tm ts td) = RhythmContext (f tm) ts td++modifyTimeSub :: (Duration -> Duration) -> RhythmContext -> RhythmContext+modifyTimeSub f (RhythmContext tm ts td) = RhythmContext tm (f ts) td++modifyTupleDepth :: (Int -> Int) -> RhythmContext -> RhythmContext+modifyTupleDepth f (RhythmContext tm ts td) = RhythmContext tm ts (f td)+++++++-- |+-- A @RhytmParser a b@ converts (Voice a) to b.+type RhythmParser a b = Parsec [(Duration, a)] RhythmContext b++quantize' :: Tiable a => RhythmParser a b -> [(Duration, a)] -> Either String b+quantize' p = over _Left show . runParser p mempty ""+++++++rhythm :: Tiable a => RhythmParser a (Rhythm a)+rhythm = Group <$> many1 (rhythm' <|> bound)++rhythmNoBound :: Tiable a => RhythmParser a (Rhythm a)+rhythmNoBound = Group <$> many1 rhythm'++rhythm' :: Tiable a => RhythmParser a (Rhythm a)+rhythm' = mzero+ <|> beat+ <|> dotted+ <|> tuplet++-- Matches a beat divisible by 2 (notated)+-- beat :: Tiable a => RhythmParser a (Rhythm a)+-- beat = do+-- RhythmContext tm ts _ <- getState+-- (\d -> (d^/tm) `subDur` ts) <$> match (\d _ ->+-- d - ts > 0 && isDivisibleBy 2 (d / tm - ts))++beat :: Tiable a => RhythmParser a (Rhythm a)+beat = do+ RhythmContext tm ts _ <- getState+ match' $ \d x ->+ let d2 = d / tm - ts+ in (d2, x) `assuming` (d - ts > 0 && isDivisibleBy 2 d2)+++-- | Matches a dotted rhythm+dotted :: Tiable a => RhythmParser a (Rhythm a)+dotted = msum . fmap dotted' $ konstNumDotsAllowed++-- | Matches a bound rhythm+bound :: Tiable a => RhythmParser a (Rhythm a)+bound = msum . fmap bound' $ konstBounds++-- | Matches a tuplet+tuplet :: Tiable a => RhythmParser a (Rhythm a)+tuplet = msum . fmap tuplet' $ konstTuplets++++++dotted' :: Tiable a => Int -> RhythmParser a (Rhythm a)+dotted' n = do+ modifyState $ modifyTimeMod (* dotMod n)+ a <- beat+ modifyState $ modifyTimeMod (/ dotMod n)+ return (Dotted n a)++-- | Return the scaling applied to a note with the given number of dots (i.e. 3/2, 7/4 etc).+dotMod :: Int -> Duration+dotMod n = dotMods !! (n-1)++-- [3/2, 7/4, 15/8, 31/16 ..]+dotMods :: [Duration]+dotMods = zipWith (/) (fmap pred $ drop 2 times2) (drop 1 times2)+ where+ times2 = iterate (*2) 1++bound' :: Tiable a => Duration -> RhythmParser a (Rhythm a)+bound' d = do+ modifyState $ modifyTimeSub (+ d)+ a <- beat+ modifyState $ modifyTimeSub (subtract d)+ let (b,c) = toTied $ getBeatValue a++ -- TODO doesn't know order+ return $ Group [Beat (getBeatDuration a) b, Beat d c]++-- tuplet' 2/3 for triplet, 4/5 for quintuplet etc+tuplet' :: Tiable a => Duration -> RhythmParser a (Rhythm a)+tuplet' d = do+ RhythmContext _ _ depth <- getState+ onlyIf (depth < konstMaxTupletNest) $ do+ modifyState $ modifyTimeMod (* d)+ . modifyTupleDepth succ+ a <- rhythmNoBound+ modifyState $ modifyTimeMod (/ d)+ . modifyTupleDepth pred+ return (Tuplet d a)+++-------------------------------------------------------------------------------------++-- | Similar to 'many1', but tries longer sequences before trying one.+-- many1long :: Stream s m t => ParsecT s u m a -> ParsecT s u m [a]+-- many1long p = try (many2 p) <|> fmap return p++-- | Similar to 'many1', but applies the parser 2 or more times.+-- many2 :: Stream s m t => ParsecT s u m a -> ParsecT s u m [a]+-- many2 p = do { x <- p; xs <- many1 p; return (x : xs) }++-- Matches a (_duration, value) pair iff the predicate matches, returns beat+match :: Tiable a => (Duration -> a -> Bool) -> RhythmParser a (Rhythm a)+match p = tokenPrim show next test+ where+ show x = ""+ next pos _ _ = updatePosChar pos 'x'+ test (d,x) = if p d x then Just (Beat d x) else Nothing++-- Matches a (_duration, value) pair iff the predicate matches, returns beat+match' :: Tiable a => (Duration -> a -> Maybe (Duration, b)) -> RhythmParser a (Rhythm b)+match' f = tokenPrim show next test+ where+ show x = ""+ next pos _ _ = updatePosChar pos 'x'+ test (d,x) = case f d x of+ Nothing -> Nothing+ Just (d,x) -> Just $ Beat d x++-- |+-- Succeed only if the entire input is consumed.+--+atEnd :: RhythmParser a b -> RhythmParser a b+atEnd p = do+ x <- p+ notFollowedBy' anyToken' <?> "end of input"+ return x+ where+ notFollowedBy' p = try $ (try p >> unexpected "") <|> return ()+ anyToken' = tokenPrim (const "") (\pos _ _ -> pos) Just++onlyIf :: MonadPlus m => Bool -> m b -> m b+onlyIf b p = if b then p else mzero++-- | Just x or Nothing+assuming :: a -> Bool -> Maybe a+assuming x b = if b then Just x else Nothing++logBaseR :: forall a . (RealFloat a, Floating a) => Rational -> Rational -> a+logBaseR k n | isInfinite (fromRational n :: a) = logBaseR k (n/k) + 1+logBaseR k n | isDenormalized (fromRational n :: a) = logBaseR k (n*k) - 1+logBaseR k n | otherwise = logBase (fromRational k) (fromRational n)+++divides = isDivisibleBy+divisibleBy = flip isDivisibleBy++-- As it sounds, do NOT use infix+-- Only works for simple n such as 2 or 3, TODO determine+isDivisibleBy :: Duration -> Duration -> Bool+isDivisibleBy n = (== 0.0) . snd . properFraction . logBaseR (toRational n) . toRational+
+ src/Music/Time/Internal/Transform.hs view
@@ -0,0 +1,384 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Internal.Transform (++ -- * The Transformable class+ Transformable(..),+ itransform,+ transformed,++ -- * Apply under a transformation+ whilst,+ whilstM,+ whilstL,+ whilstLT,+ whilstLD,+ whilstStretch,+ whilstDelay,+ spanned,+ onSpan,+ conjugateS,++ -- * Specific transformations+ delay,+ undelay,+ stretch,+ compress,+ -- ** Applied transformations+ delaying,+ undelaying,+ stretching,+ compressing,++ -- ** Utility+ delayTime, ++ ) where++import Music.Time.Types++import Data.Ratio++import Control.Applicative+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Semigroup+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.VectorSpace hiding (Sum (..))++-- |+-- Class of values that can be transformed (i.e. scaled and moved) in time.+--+-- Law+--+-- @+-- transform mempty = id+-- transform (s \<> t) = transform s . transform t+-- transform (s \<> negateV t) = id+-- @+--+-- Law+--+-- @+-- onset (delay n a) = n ^+. onset a+-- offset (delay n a) = n ^+. offset a+-- duration (stretch n a) = n * duration a+-- duration (compress n a) = duration a / n+-- @+--+-- @+-- delay n b ! t = b ! (t .-^ n)+-- undelay n b ! t = b ! (t .+^ n)+-- @+--+-- Lemma+--+-- @+-- duration a = duration (delay n a)+-- @+--+class Transformable a where+ transform :: Span -> a -> a++instance Transformable () where+ transform _ = id++instance Transformable Bool where+ transform _ = id++instance Transformable Ordering where+ transform _ = id++instance Transformable Char where+ transform _ = id++instance Transformable Int where+ transform _ = id++instance Transformable Integer where+ transform _ = id++instance Transformable a => Transformable (Ratio a) where+ transform _ = id++instance Transformable Float where+ transform _ = id++instance Transformable Double where+ transform _ = id++instance Transformable Duration where+ (view delta -> (_, d1)) `transform` d2 = d1 * d2++instance Transformable Time where+ (view delta -> (t1, d1)) `transform` t2 = t1 ^+^ d1 *^ t2++instance Transformable Span where+ transform = (<>)++instance Transformable a => Transformable (Option a) where+ transform s = fmap (transform s)++instance Transformable a => Transformable (Last a) where+ transform s = fmap (transform s)++instance Transformable a => Transformable (Sum a) where+ transform s = fmap (transform s)++instance Transformable a => Transformable (Product a) where+ transform s = fmap (transform s)++-- |+-- Apply the inverse of the given transformation.+--+-- @+-- 'itransform' s = 'transform' ('negateV' s)+-- @+--+itransform :: Transformable a => Span -> a -> a+itransform s = transform (negateV s)++-- |+-- View the given value in the context of the given transformation.+--+-- @+-- 'over' ('transformed' s) = (``whilst`` s)+-- @+--+transformed :: Transformable a => Span -> Iso' a a+transformed s = iso (transform s) (itransform s)++--+-- TODO+--+-- Should really transform the /second/ element, but this is incompatible with Note/SCcore+--+-- 1) Change this to transform both components+-- Then Note could be defined as type Note a = (Span, TransfInv a)+--+-- 2) Redefine note as type Note a = (a, Span)+--+instance Transformable a => Transformable (b, a) where+ transform t (s,a) = (s, transform t a)++-- |+-- Lists transform by transforming each element.+--+instance Transformable a => Transformable [a] where+ transform t = map (transform t)++instance Transformable a => Transformable (Seq a) where+ transform t = fmap (transform t)++instance (Ord k, Transformable a) => Transformable (Map k a) where+ transform t = Map.map (transform t)++-- |+-- Functions transform by conjugation, i.e. we reverse-transform the argument+-- and transform the result.+--+instance (Transformable a, Transformable b) => Transformable (a -> b) where+ transform t = (`whilst` negateV t)++-- |+-- A transformation that moves a value forward in time.+--+delaying :: Duration -> Span+delaying x = (0 .+^ x) >-> 1++-- |+-- A transformation that stretches (augments) a value by the given factor.+--+stretching :: Duration -> Span+stretching x = 0 >-> x++-- |+-- A transformation that moves a value backward in time.+--+undelaying :: Duration -> Span+undelaying x = delaying (negate x)++-- |+-- A transformation that compresses (diminishes) a value by the given factor.+--+compressing :: Duration -> Span+compressing x = stretching (recip x)++-- |+-- Moves a value forward in time.+--+delay :: Transformable a => Duration -> a -> a+delay = transform . delaying++-- |+-- Moves a value backward in time. Equnitvalent to @'stretch' . 'negate'@.+--+undelay :: Transformable a => Duration -> a -> a+undelay = transform . undelaying++-- |+-- Stretches (augments) a value by the given factor.+--+stretch :: Transformable a => Duration -> a -> a+stretch = transform . stretching++-- |+-- Compresses (diminishes) a score. Equnitvalent to @'stretch' . 'recip'@.+--+compress :: Transformable a => Duration -> a -> a+compress = transform . compressing+++-- |+-- Delay relative to 'origin'.+--+-- Provided for situations when we really want to use 'startAt', but the+-- type does not have an instance for 'HasPosition' and we can assume that+-- the value is starting at time zero.+--+delayTime :: Transformable a => Time -> a -> a+delayTime t = delay (t .-. 0)++++--+-- $musicTimeSpanConstruct+--+-- - To convert a span to a pair, use @s^.'delta'@.+-- - To construct a span from a pair, use @(t, d)^.'from' 'delta'@.+--++--+-- $musicTimeSpanLaws+--+-- > forall s . id `whilst` s = id+-- > forall s . return `whilstM` s = return+-- > forall s . extract `whilstW` s = extract+++-- We really must flip all these functions. To do:+--+-- 1) Come up with some other name for the infix version+-- 2) Acknowledge that this is a valid Lens (when flipped)+--+-- Perhaps we should call the inline version `whilst`, as in @f `whilst` delaying 2@?+++-- |+-- Apply a function under transformation.+--+-- Designed to be used infix, as in+--+-- @+-- 'stretch' 2 ``whilst`` 'delaying' 2+-- @+--+whilst :: (Transformable a, Transformable b) => (a -> b) -> Span -> a -> b+f `whilst` t = transform (negateV t) . f . transform t++-- |+-- Apply a morphism under transformation (monadic version).+--++whilstM :: (Functor f, Transformable a, Transformable b) => (a -> f b) -> Span -> a -> f b+f `whilstM` t = fmap (transform (negateV t)) . f . transform t++{-+-- |+-- Apply a morphism under transformation (co-monadic version).+--+whilstW :: (Functor f, Transformable a, Transformable b) => (f a -> b) -> Span -> f a -> b+f `whilstW` t = transform (negateV t) . f . fmap (transform t)+-}+whilstL :: (Functor f, Transformable a, Transformable b)+ => LensLike f s t a b+ -> LensLike f (Span,s) (Span,t) a b+whilstL l f (s,a) = (s,) <$> (l $ f `whilstM` s) a++whilstLT :: (Functor f, Transformable a, Transformable b)+ => LensLike f s t a b+ -> LensLike f (Time,s) (Time,t) a b+whilstLT l f (t,a) = (t,) <$> (l $ f `whilstM` (t >-> 1)) a++whilstLD :: (Functor f, Transformable a, Transformable b)+ => LensLike f s t a b+ -> LensLike f (Duration,s) (Duration,t) a b+whilstLD l f (d,a) = (d,) <$> (l $ f `whilstM` (0 >-> d)) a+++-- |+-- Apply a function under transformation.+--+whilstDelay :: (Transformable a, Transformable b) => (a -> b) -> Time -> a -> b+whilstDelay = flip (flip whilst . delaying . (.-. 0))++-- |+-- Apply a function under transformation.+--+whilstStretch :: (Transformable a, Transformable b) => (a -> b) -> Duration -> a -> b+whilstStretch = flip (flip whilst . stretching)++-- |+-- The conjugate of two spans.+--+conjugateS :: Span -> Span -> Span+conjugateS t1 t2 = negateV t1 <> t2 <> t1+++-- |+-- Transforms a lens of to a 'Transformable' type to act inside a transformation.+--+spanned :: (Transformable a, Transformable b) => Span -> Lens a b a b+spanned s = flip whilstM (negateV s)++-- |+-- Transforms a lens of to a 'Transformable' type to act inside a transformation.+--+-- Designed to be used infix, as in+--+-- @+-- l ``onSpan`` (2 \<-> 3)+-- @+--+onSpan :: (Transformable a, Functor f) => LensLike' f a b -> Span -> LensLike' f a b+f `onSpan` s = spanned s . f+-- TODO name++-- TODO move!+deriving instance Functor Sum+deriving instance Functor Product+
+ src/Music/Time/Internal/Util.hs view
@@ -0,0 +1,373 @@++{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Internal.Util where++{-+ Rules:+ + * Functions may depend on any module in the lastest Haskell Platform release+ * All functions but those in Prelude must be referred to with their full,+ qualified names (i.e. Data.List.unfoldr).+ * Each function must have a unique name (so the whole file is a loadable module).+ * Each function should have a synopisis, like:++ -- | Ordinary Haddock commentary ...+ -- > category: Categories (please use the common Hackage names)+ -- > depends : base (all packages in HP that the function depends on)+ +-}++import Control.Lens+import Control.Monad.Plus+import Control.Applicative+import qualified Data.Char+import qualified Data.Monoid+import qualified Data.List+import qualified Data.Ratio+import Data.Functor.Contravariant++-- | Divide a list into parts of maximum length n.+-- > category : List+-- > depends : base+divideList :: Int -> [a] -> [[a]]+divideList n xs+ | length xs <= n = [xs]+ | otherwise = [take n xs] ++ (divideList n $ drop n xs)++-- | Group a list into sublists whereever a predicate holds. The matched element+-- is the first in the sublist.+--+-- > splitWhile isSpace "foo bar baz"+-- > ===> ["foo"," bar"," baz"]+-- >+-- > splitWhile (> 3) [1,5,4,7,0,1,2]+-- > ===> [[1],[5],[4],[7,0,1,2]]+--+-- > category : List+-- > depends : base+splitWhile :: (a -> Bool) -> [a] -> [[a]]+splitWhile p xs = case splitWhile' p xs of+ []:xss -> xss+ xss -> xss+ where+ splitWhile' p [] = [[]]+ splitWhile' p (x:xs) = case splitWhile' p xs of+ (xs:xss) -> if p x then []:(x:xs):xss else (x:xs):xss+++-- | Break up a list into parts of maximum length n, inserting the given list as separator.+-- Useful for breaking up strings, as in @breakList 80 "\n" str@.+--+-- > category : List+-- > depends : base+breakList :: Int -> [a] -> [a] -> [a]+breakList n z = Data.Monoid.mconcat . Data.List.intersperse z . divideList n++-- | Map over the indices and elements of list.+-- > category : List+-- > depends : base+mapIndexed :: (Int -> a -> b) -> [a] -> [b]+mapIndexed f as = map (uncurry f) (zip is as)+ where+ n = length as - 1+ is = [0..n]+ +-- test++-- | Duplicate an element.+-- > category: Combinator, Tuple+-- > depends: base+dup :: a -> (a,a)+dup x = (x,x)++-- | Unfold a partial function. This is a simpler version of 'Data.List.unfoldr'. +-- > category: Function, List+-- > depends: base+unf :: (a -> Maybe a) -> a -> [a]+unf f = Data.List.unfoldr (fmap dup . f)++-- |+-- Map over first elements of a list.+-- Biased on first element for shorter lists.+-- > category: List+-- > depends: base+mapF f = mapFTL f id id++-- |+-- Map over all but the first and last elements of a list.+-- Biased on middle elements for shorter lists.+-- > category: List+-- > depends: base+mapT f = mapFTL id f id++-- |+-- Map over last elements of a list.+-- Biased on last element for shorter lists.+-- > category: List+-- > depends: base+mapL f = mapFTL id id f++-- |+-- Map over first, middle and last elements of list.+-- Biased on first, then on first and last for short lists.+--+-- > category: List+-- > depends: base+mapFTL :: (a -> b) -> (a -> b) -> (a -> b) -> [a] -> [b]+mapFTL 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]++-- |+-- Extract the first consecutive sublist for which the predicate returns true, or+-- the empty list if no such sublist exists.+-- > category: List+-- > depends: base+filterOnce :: (a -> Bool) -> [a] -> [a]+filterOnce p = Data.List.takeWhile p . Data.List.dropWhile (not . p)+++-- | Returns all rotations of the given list. Given an infinite list, returns an infinite+-- list of rotated infinite lists.+-- > category: List+-- > depends: base+rots :: [a] -> [[a]]+rots xs = init (zipWith (++) (Data.List.tails xs) (Data.List.inits xs))++-- |+-- > category: List+-- > depends: base+rotl :: [a] -> [a]+rotl [] = []+rotl (x:xs) = xs ++ [x]++-- |+-- > category: List+-- > depends: base+rotr :: [a] -> [a]+rotr [] = []+rotr xs = last xs : init xs++-- |+-- > category: List+-- > depends: base+rotated :: Int -> [a] -> [a]+rotated = go+ where+ go n as + | n >= 0 = iterate rotr as !! n+ | n < 0 = iterate rotl as !! abs n+++curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d+curry3 = curry . curry . (. tripl)++uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d+uncurry3 = (. untripl) . uncurry . uncurry++untripl :: (a,b,c) -> ((a,b),c)+untripl (a,b,c) = ((a,b),c)++tripl :: ((a,b),c) -> (a,b,c)+tripl ((a,b),c) = (a,b,c)++tripr :: (a,(b,c)) -> (a,b,c)+tripr (a,(b,c)) = (a,b,c)+++-- 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))++-- | Case matching on lists.+-- > category: List+-- > depends: base+list :: r -> ([a] -> r) -> [a] -> r+list z f [] = z+list z f xs = f xs++-- | Merge lists.+-- > category: List+-- > depends: base+merge :: Ord a => [a] -> [a] -> [a]+merge = mergeBy compare++-- | Merge lists.+-- > category: List+-- > depends: base+mergeBy :: (a -> a -> Ordering) -> [a] -> [a] -> [a]+mergeBy f = mergeBy' $ (fmap.fmap) orderingToBool f+ where+ orderingToBool LT = True+ orderingToBool EQ = True+ orderingToBool GT = False++mergeBy' :: (a -> a -> Bool) -> [a] -> [a] -> [a]+mergeBy' pred xs [] = xs+mergeBy' pred [] ys = ys+mergeBy' pred (x:xs) (y:ys) =+ case pred x y of+ True -> x: mergeBy' pred xs (y:ys)+ False -> y: mergeBy' pred (x:xs) ys++-- | Compose all functions.+-- > category: Function+-- > depends: base+composed :: [b -> b] -> b -> b+composed = Prelude.foldr (.) id++-- | Separate a ratio.+-- > category: Math+-- > depends: base+unRatio :: Integral a => Data.Ratio.Ratio a -> (a, a)+unRatio x = (Data.Ratio.numerator x, Data.Ratio.denominator x)++-- | Nicer printing of ratio as ordinary fractions.+-- > category: Math+-- > depends: base+showRatio :: (Integral a, Show a) => Data.Ratio.Ratio a -> String+showRatio (realToFrac -> (unRatio -> (x, 1))) = show x+showRatio (realToFrac -> (unRatio -> (x, y))) = "(" ++ show x ++ "/" ++ show y ++ ")"+++-- Replace all contigous ranges of equal values with [Just x, Nothing, Nothing ...]+-- > category: List+-- > depends: base+retainUpdates :: Eq a => [a] -> [Maybe a]+retainUpdates = snd . Data.List.mapAccumL g Nothing where+ g Nothing x = (Just x, Just x)+ g (Just p) x = (Just x, if p == x then Nothing else Just x)+++-- Generic version of 'replicate'.+-- > category: List+-- > depends: base+replic :: Integral a => a -> b -> [b]+replic n = replicate (fromIntegral n)++-- Swap components.+-- > category: Tuple+-- > depends: base+swap :: (a, b) -> (b, a)+swap (x, y) = (y, x)++-- Interleave a list with the next consecutive element.+--+-- For any xs+--+-- > lenght xs == length (withNext xs)+--+-- If @xs@ is a finite list+--+-- > isNothing $ snd $ last $ withNext xs == True+-- > all isJust $ snd $ init $ withNext xs == True+--+-- If @xs@ is an infinite list+--+-- > all isJust $ snd $ withNext xs == True+--+-- > category: List+-- > depends: base+withNext :: [a] -> [(a, Maybe a)]+withNext = fmap (\(p,c,n) -> (c,n)) . withPrevNext++withPrev :: [a] -> [(Maybe a, a)]+withPrev = fmap (\(p,c,n) -> (p,c)) . withPrevNext++-- withNext = go+-- where+-- go [] = []+-- go [x] = [(x, Nothing)]+-- go (x:y:rs) = (x, Just y) : withNext (y : rs)++withPrevNext :: [a] -> [(Maybe a, a, Maybe a)]+withPrevNext xs = zip3 (pure Nothing ++ fmap Just xs) xs (fmap Just (tail xs) ++ repeat Nothing)++-- Map over a list with the next consecutive element.+--+-- > category: List+-- > depends: base+mapWithNext :: (a -> Maybe a -> b) -> [a] -> [b]+mapWithNext f = map (uncurry f) . withNext++mapWithPrev :: (Maybe a -> a -> b) -> [a] -> [b]+mapWithPrev f = map (uncurry f) . withPrev++mapWithPrevNext :: (Maybe a -> a -> Maybe a -> b) -> [a] -> [b]+mapWithPrevNext f = map (uncurry3 f) . withPrevNext++-- |+-- Rotate a list.+--+-- > rotate n xs == id iff (n `mod` length xs) == 0+-- > rotate (-n) . rotate n == id+rotate :: Int -> [a] -> [a]+rotate n xs = drop n' xs ++ take n' xs+ where+ n' = negate n `mod` length xs++--------++++++++toDouble :: Real a => a -> Double+toDouble = realToFrac++through :: Applicative f => + Lens' s a + -> Lens s t a b + -> Lens (f s) (f t) (f a) (f b)+through lens1 lens2 = lens getter (flip setter)+ where+ getter = fmap (view lens1)+ setter = liftA2 (over lens2 . const)+{-# INLINE through #-}++single :: Prism' [a] a+single = prism' return $ \xs -> case xs of+ [x] -> Just x+ _ -> Nothing+{-# INLINE single #-}++tripped :: Iso ((a, b), c) ((a', b'), c') (a, b, c) (a', b', c')+tripped = iso tripl untripl+{-# INLINE tripped #-}++floor' :: RealFrac a => a -> a+floor' = fromIntegral . floor++-- Like Data.Ord.comparing+-- (Are both variants of contramap?)+inspecting :: Eq a => (b -> a) -> b -> b -> Bool+inspecting f x y = f x == f y++inspectingBy :: (b -> a) -> (a -> a -> Bool) -> (b -> b -> Bool)+inspectingBy f e = getEquivalence $ contramap f $ Equivalence e+-- inspectingBy f p x y = f x `p` f y+
src/Music/Time/Juxtapose.hs view
@@ -1,14 +1,18 @@ -{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -19,131 +23,151 @@ ------------------------------------------------------------------------------------- module Music.Time.Juxtapose (- -- * Prerequisites- Transformable(..),+ -- * Align without composition+ lead,+ follow, - -- ** Juxtaposing values- following,- preceding,+ -- * Standard composition+ after,+ before, during,-- -- * Composing values (|>),- (>|), (<|),- scat,- pcat, - -- ** Special composition+ -- ** More exotic sustain,- anticipate,+ palindrome, - -- ** Repetition+ -- * Catenation+ scat,+ pcat,++ -- * Repetition times,- repeated,- group, ) where -+import Control.Lens hiding ((|>), (<|)) import Data.AffineSpace import Data.AffineSpace.Point-import Data.Monoid.WithSemigroup+-- import Data.Monoid.WithSemigroup import Data.Semigroup import Data.VectorSpace -import Music.Time.Delayable-import Music.Time.Onset-import Music.Time.Stretchable-import Music.Time.Time+import Music.Time.Reverse+import Music.Time.Split --- |--- This pseudo-class gathers the restrictions needed to implement position a value at--- any point and duration in time.----type Transformable a = (Stretchable a, Delayable a) +--+-- TODO names+-- Especially 'after' is counter-intuitive+-- ----------------------------------------------------------------------------------------- Juxtaposition--------------------------------------------------------------------------------------+-- |+-- Move a value so that+--+-- @+-- '_offset' (a ``lead`` b) = '_onset' b+-- @+--+--+lead :: (HasPosition a, HasPosition b, Transformable a) => a -> b -> a+a `lead` b = placeAt 1 (b `_position` 0) a -- |--- @a \`following\` b@ moves score /b/ so that its onset is at the offset of score--- /a/ and returns the moved score.+-- Move a value so that ---following :: (HasOffset a, Delayable b, HasOnset b) => a -> b -> b-a `following` b = startAt (offset a) b+-- @+-- '_offset' a = '_onset' (a ``follow`` b)+-- @+--+follow :: (HasPosition a, HasPosition b, Transformable b) => a -> b -> b+a `follow` b = placeAt 0 (a `_position` 1) b -- |--- @a \`preceding\` b@ moves score /a/ so that its offset is at the onset of score--- /b/ and returns the moved score.+-- Move a value so that ---preceding :: (Delayable a, HasOffset a, HasOnset b) => a -> b -> a-a `preceding` b = stopAt (onset b) a+after :: (Semigroup a, Transformable a, HasPosition a) => a -> a -> a+a `after` b = a <> (a `follow` b) --- | @a \`during\` b@ places /a/ at the same era as /b/ and returns the moved score.-during :: (Delayable a, Stretchable a, HasOnset a, HasDuration a, HasOnset b, HasDuration b) => a -> b -> a-a `during` b = startAt (onset b) $ stretchTo (duration b) a+-- |+-- Move a value so that+--+before :: (Semigroup a, Transformable a, HasPosition a) => a -> a -> a+a `before` b = (a `lead` b) <> b ----------------------------------------------------------------------------------------- Composition--------------------------------------------------------------------------------------+-- |+-- A value followed by its reverse (retrograde).+--+palindrome :: (Semigroup a, Reversible a, HasPosition a) => a -> a+palindrome a = a `after` rev a infixr 6 |>-infixr 6 >| infixr 6 <| -- |--- Compose in sequence.------ @a |> b@ moves score /b/ as per 'following' and then composes the resulting scores with '<>'.------ > Score a -> Score a -> Score a+-- An infix alias for 'after'. ---(|>) :: (Semigroup a, HasOnset a, HasOffset a, Delayable a) =>- a -> a -> a+(|>) :: (Semigroup a, HasPosition a, Transformable a) => a -> a -> a+(|>) = after+ -- |--- Compose in sequence.+-- An infix alias for 'before'. ----- @a >| b@ moves score /a/ as per 'preceding' and then composes the resulting scores with '<>'.+(<|) :: (Semigroup a, HasPosition a, Transformable a) => a -> a -> a+(<|) = before++-- infixr 6 >|+-- infixr 6 |<++-- |+-- Compose a list of sequential objects, with onset and offset tangent to one another. ----- > Score a -> Score a -> Score a+-- For non-positioned types, this is the often same as 'mconcat'+-- For positioned types, this is the same as 'afterAnother' ---(>|) :: (Semigroup a, HasOnset a, HasOffset a, Delayable a) =>- a -> a -> a+scat :: (Semigroup a, Monoid a, HasPosition a, Transformable a) => [a] -> a+scat = Prelude.foldr (|>) mempty -- |--- Compose in reverse sequence.------ To compose in parallel, use '<>'.+-- Compose a list of parallel objects, so that their local origins align. ----- > Score a -> Score a -> Score a+-- This not possible for non-positioned types, as they have no notion of an origin.+-- For positioned types this is the same as 'mconcat'. ---(<|) :: (Semigroup a, HasOnset a, HasOffset a, Delayable a) =>- a -> a -> a--a |> b = a <> (a `following` b)-a >| b = (a `preceding` b) <> b-a <| b = b |> a+pcat :: (Semigroup a, Monoid a) => [a] -> a+pcat = Prelude.foldr (<>) mempty -- |--- Sequential catenation.+-- Move a value so that its era is equal to the era of another value. ----- > [Score a] -> Score a+-- @+-- 'Score' a -> 'Score' a -> 'Score' a+-- @ ---scat :: (Monoid' a, HasOnset a, HasOffset a, Delayable a) =>- [a] -> a+during :: (HasPosition a, HasPosition b, Transformable a, Transformable b) => a -> b -> a+y `during` x = set era (view era x) y+ -- |--- Parallel catenation.+-- Like '<>', but scaling the second agument to the duration of the first. ----- > [Score a] -> Score a+-- @+-- 'Score' a -> 'Score' a -> 'Score' a+-- @ ---pcat :: Monoid' a =>- [a] -> a+sustain :: (Semigroup a, HasPosition a, Transformable a) => a -> a -> a+x `sustain` y = x <> y `during` x -scat = Prelude.foldr (|>) mempty-pcat = Prelude.foldr (<>) mempty+-- |+-- Repeat exact amount of times.+--+-- @+-- 'Int' -> 'Score' a -> 'Score' a+-- @+--+times :: (Semigroup a, Monoid a, HasPosition a, Transformable a) => Int -> a -> a+times n = scat . replicate n +{- -- | -- Like '<>', but scaling the second agument to the duration of the first.@@ -167,20 +191,7 @@ -- Like '<>', but truncating the second agument to the duration of the first. -- prolong x y = x <> before (duration x) y ---- ----------------------------------------------------------------------------------- -- Structure--- --------------------------------------------------------------------------------- -- |--- Repeat exact amount of times.------ > Duration -> Score Note -> Score Note----times :: (Monoid' a, Transformable a, HasOnset a, HasOffset a) =>- Int -> a -> a---- | -- Repeat once for each element in the list. -- -- Example:@@ -205,4 +216,5 @@ times n = scat . replicate n repeated = flip (\f -> scat . fmap f) group n = times n . (fromIntegral n `compress`)+-}
+ src/Music/Time/Meta.hs view
@@ -0,0 +1,172 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Provides meta-information.+--+-- Each score supports an unlimited number of 'Reactive' meta-values.+--+-- This is more or less based on Diagrams styles, which is in turn based+-- on XMonad.+--+-------------------------------------------------------------------------------------++module Music.Time.Meta (+ -- * Attributes+ IsAttribute,+ Attribute,+ + -- ** Creating attributes+ wrapAttr,+ wrapTAttr,+ unwrapAttr,+ unwrapTAttr,++ -- * Meta-data+ Meta,++ -- ** Creating meta-data+ toMeta,+ fromMeta,++ -- ** The HasMeta class+ HasMeta(..),+ applyMeta,+ ) where++-- import Control.Applicative+-- import Control.Arrow+import Control.Lens hiding (transform)+import Control.Monad.Plus+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.Maybe+-- import Data.Monoid.WithSemigroup+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.String+-- import Data.Traversable (Traversable)+-- import qualified Data.Traversable as T+import Data.Typeable+import Data.Void++-- import Music.Score.Part+import Music.Time.Internal.Util+import Music.Time.Reverse+import Music.Time.Split+import Music.Time.Transform++type IsAttribute a = (Typeable a, Monoid a, Semigroup a)++-- | An existential wrapper type to hold attributes.+data Attribute :: * where+ Attribute :: IsAttribute a => a -> Attribute+ TAttribute :: (Transformable a, IsAttribute a) => a -> Attribute++wrapAttr :: IsAttribute a => a -> Attribute+wrapAttr = Attribute++unwrapAttr :: IsAttribute a => Attribute -> Maybe a+unwrapAttr (Attribute a) = cast a++wrapTAttr :: (Transformable a, IsAttribute a) => a -> Attribute+wrapTAttr = TAttribute++unwrapTAttr :: (Transformable a, IsAttribute a) => Attribute -> Maybe a+unwrapTAttr (TAttribute a) = cast a++instance Semigroup Attribute where+ (Attribute a1) <> a2 = case unwrapAttr a2 of+ -- Nothing -> a2+ Nothing -> error "Attribute.(<>) mismatch"+ Just a2' -> Attribute (a1 <> a2')+ (TAttribute a1) <> a2 = case unwrapTAttr a2 of+ -- Nothing -> a2+ Nothing -> error "Attribute.(<>) mismatch"+ Just a2' -> TAttribute (a1 <> a2')++instance Transformable Attribute where+ transform _ (Attribute a) = Attribute a+ transform s (TAttribute a) = TAttribute (transform s a)++instance Splittable Attribute where+ split _ x = (x,x)++instance Reversible Attribute where+ rev = id++-- Meta is Transformable because the contents of the map is transformable+newtype Meta = Meta (Map String Attribute)+ deriving (Transformable, Reversible, Splittable)++instance Semigroup Meta where+ Meta s1 <> Meta s2 = Meta $ Map.unionWith (<>) s1 s2++-- | The empty meta contains no attributes; composition of metas is+-- a union of attributes; if the two metas have attributes of the+-- same type they are combined according to their semigroup+-- structure.+instance Monoid Meta where+ mempty = Meta Map.empty+ mappend = (<>)++instance HasMeta Meta where+ meta = ($)++--+-- TODO+-- Temporarily disabling part specific meta-events+-- The API still works, but all parts are merged together+--++toMeta :: forall a b . ({-HasPart' a, -}IsAttribute b, Transformable b) => Maybe a -> b -> Meta+toMeta partId a = Meta $ Map.singleton key $ wrapTAttr a+ where+ key = ty ++ pt+ pt = ""+ -- pt = show $ fmap getPart partId+ ty = show $ typeOf (undefined :: b)++fromMeta :: forall a b . ({-HasPart' a, -}IsAttribute b, Transformable b) => Maybe a -> Meta -> Maybe b+fromMeta partId (Meta s) = (unwrapTAttr =<<) $ Map.lookup key s+-- Note: unwrapAttr should never fail+ where+ key = ty ++ pt+ pt = ""+ -- pt = show $ fmap getPart partId+ ty = show . typeOf $ (undefined :: b)+++-- | Type class for things which have meta-information.+class HasMeta a where+ -- | Apply meta-information by combining it (on the left) with the+ -- existing meta-information.+ meta :: Lens' a Meta++applyMeta :: HasMeta a => Meta -> a -> a+applyMeta m = (meta <>~ m)+
+ src/Music/Time/Note.hs view
@@ -0,0 +1,158 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Note (+ -- * Note type+ Note,++ -- * Construction+ note,+ event,+ noteValue, + ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Reverse+import Music.Time.Split++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Comonad+import Control.Comonad.Env+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import Data.PairMonad+import Data.Typeable+import Music.Time.Internal.Util (through, tripped)++-- |+-- A 'Note' is a value with an 'onset' and and 'offset' in time. It is an instance+-- of 'Transformable'.+--+-- You can use 'value' to apply a function in the context of the transformation,+-- i.e.+--+-- @+-- over value (* line) (delay 2 $ return line)+-- @+--+-- @+-- ('view' 'value') . 'transform' s = 'transform' s . ('view' 'value')+-- @+--+-- The semantics are given by+--+-- @+-- type Note a = (Span, a)+-- @+--+newtype Note a = Note { _noteValue :: (Span, a) }+ deriving (Typeable)++deriving instance Eq a => Eq (Note a)+deriving instance Functor Note+deriving instance Foldable Note+deriving instance Traversable Note+deriving instance Comonad Note++instance (Show a, Transformable a) => Show (Note a) where+ show x = show (x^.from note) ++ "^.note"++-- |+-- Note is a 'Monad' and 'Applicative' in the style of pair, with 'return' placing a value+-- at the default span 'mempty' and 'join' composing time transformations.+deriving instance Monad Note+deriving instance Applicative Note++-- instance ComonadEnv Span Note where+ -- ask = noteValueSpan++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Note a) where+ type Unwrapped (Note a) = (Span, a)+ _Wrapped' = iso _noteValue Note++instance Rewrapped (Note a) (Note b)++instance Transformable (Note a) where+ transform t = over _Wrapped $ first (transform t)++instance HasDuration (Note a) where+ _duration = _duration . ask . view _Wrapped++instance HasPosition (Note a) where+ x `_position` p = ask (view _Wrapped x) `_position` p++instance Splittable a => Splittable (Note a) where+ -- beginning d = over _Wrapped $ \(s, v) -> (beginning d s, beginning (transform (negateV s) d) v)+ beginning d = over _Wrapped $ \(s, v) -> (beginning d s, beginning (d / _duration s) v)+ ending d = over _Wrapped $ \(s, v) -> (ending d s, ending (1 - (d / _duration s)) v)++instance Reversible (Note a) where+ rev = revDefault++-- |+-- View a note as a pair of the original value and the transformation (and vice versa).+--+note :: ({-Transformable a, Transformable b-}) => Iso (Span, a) (Span, b) (Note a) (Note b)+note = _Unwrapped++-- |+-- View the value in the note.+--+noteValue :: (Transformable a, Transformable b) => Lens (Note a) (Note b) a b+noteValue = lens runNote (flip $ mapNote . const)+ where+ runNote = uncurry transform . view _Wrapped+ -- setNote f (view (from note) -> (s,x)) = view note (s, itransform s x)+ mapNote f (view (from note) -> (s,x)) = view note (s, f `whilst` negateV s $ x)+{-# INLINE noteValue #-}++-- |+-- View a note as an events, i.e. a time-duration-value triplet.+--+event :: Iso (Note a) (Note b) (Time, Duration, a) (Time, Duration, b)+event = from note . bimapping delta id . tripped+++
− src/Music/Time/Onset.hs
@@ -1,195 +0,0 @@--{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ This misleadingly named module provide a way to query a value for its--- 'duration', 'onset' and 'offset'.-------------------------------------------------------------------------------------------module Music.Time.Onset (- -- * Duration class- HasDuration(..),- stretchTo,-- -- * Onset and offset class- HasOnset(..),- HasOffset(..),- startAt,- stopAt,- withSameOnset,- withSameOffset,-- -- * Utility- -- ** Default implementations- durationDefault,- onsetDefault,- offsetDefault,- ) where---import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Map (Map)-import qualified Data.Map as Map-import Data.Semigroup-import Data.Set (Set)-import qualified Data.Set as Set-import Data.VectorSpace hiding (Sum)--import Music.Score.Util-import Music.Time.Delayable-import Music.Time.Stretchable-import Music.Time.Time---- |--- Class of types with a duration.------ If a type has an instance for both 'HasOnset' and 'HasDuration', the following laws--- should hold:------ > duration a = offset a .-. onset a----class HasDuration a where- duration :: a -> Duration--instance HasDuration Duration where- duration = id--instance HasDuration (Duration, a) where- duration = fst--instance HasDuration (Time, Duration, a) where- duration (t,d,x) = d--instance HasDuration a => HasDuration (Product a) where- duration (Product x) = duration x---- Works for monophonic containers but not in general--- instance HasDuration a => HasDuration [a] where- -- duration = getSum . F.foldMap (Sum . duration)---- |--- Stretch a score to fit into the given duration.------ > Duration -> Score a -> Score a----stretchTo :: (Stretchable a, HasDuration a) => Duration -> a -> a---- |--- Class of types with a position in time.------ Onset and offset are logical start and stop time, i.e. the preferred beginning and end--- of the sound, not o the the time of the attack and damp actions on an instrument,------ If a type has an instance for both 'HasOnset' and 'HasDuration', the following laws--- should hold:------ > duration a = offset a .-- onset a----class HasOnset a where- -- |- -- Get the onset of the given value.- --- onset :: a -> Time--class HasOffset a where- -- |- -- Get the offset of the given value.- --- offset :: a -> Time--instance HasOnset Time where- onset = id--instance HasOnset (Time, a) where- onset = fst--instance HasOnset (Time, Duration, a) where- onset (t,d,x) = t--instance HasOffset (Time, Duration, a) where- offset (t,d,x) = t .+^ d--instance HasOnset a => HasOnset [a] where- onset = list origin (minimum . fmap onset)--instance HasOffset a => HasOffset [a] where- offset = list origin (maximum . fmap offset)--instance HasOnset a => HasOnset (Set a) where- onset = list origin (onset . head) . Set.toAscList--instance HasOffset a => HasOffset (Set a) where- offset = list origin (offset . last) . Set.toAscList--instance HasOnset k => HasOnset (Map k a) where- onset = list origin (onset . head) . Map.keys--instance HasOffset k => HasOffset (Map k a) where- offset = list origin (offset . last) . Map.keys--instance HasOnset a => HasOnset (Sum a) where- onset (Sum x) = onset x---- |--- Move a score so that its onset is at the specific time.------ > Time -> Score a -> Score a----startAt :: (HasOnset a, Delayable a) => Time -> a -> a---- |--- Move a score so that its offset is at the specific time.------ > Time -> Score a -> Score a----stopAt :: (HasOffset a, Delayable a) => Time -> a -> a--t `stretchTo` x = (t / duration x) `stretch` x-t `startAt` x = (t .-. onset x) `delay` x-t `stopAt` x = (t .-. offset x) `delay` x---- |--- Transform a score without affecting its onset.------ > Time -> Score a -> Score a----withSameOnset :: (Delayable a, HasOnset a, HasOnset b) => (b -> a) -> b -> a---- |--- Transform a score without affecting its offset.------ > Time -> Score a -> Score a----withSameOffset :: (Delayable a, HasOffset a, HasOffset b) => (b -> a) -> b -> a--withSameOnset f a = startAt (onset a) $ f a-withSameOffset f a = stopAt (offset a) $ f a---- | Given 'HasOnset' and 'HasOffset' instances, this function implements 'duration'.-durationDefault :: (HasOffset a, HasOnset a) => a -> Duration-durationDefault x = offset x .-. onset x---- | Given 'HasDuration' and 'HasOffset' instances, this function implements 'onset'.-onsetDefault :: (HasOffset a, HasDuration a) => a -> Time-onsetDefault x = offset x .-^ duration x---- | Given 'HasOnset' and 'HasOnset' instances, this function implements 'offset'.-offsetDefault :: (HasOnset a, HasDuration a) => a -> Time-offsetDefault x = onset x .+^ duration x-
+ src/Music/Time/Position.hs view
@@ -0,0 +1,218 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- This misleadingly named module provide a way to query a value for its+-- 'duration', 'onset' and 'offset'.+--+-------------------------------------------------------------------------------------++module Music.Time.Position (+ module Music.Time.Duration,++ -- * The HasPosition class+ HasPosition(..),+ -- * Inspecting position+ era,+ position,++ -- * Specific positions+ onset,+ offset,+ preOnset,+ postOnset,+ postOffset,++ -- * Moving to absolute positions+ startAt,+ stopAt,+ placeAt,++ -- * Internal+ -- TODO hide...+ _setEra,+ _getEra,+ ) where+++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace hiding (Sum)++import Music.Time.Duration+import Music.Time.Internal.Util++import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))++-- |+-- Class of values that have a position in time.+--+-- Many values such as notes, envelopes etc can in fact have many positions such as onset,+-- attack point, offset, decay point time etc. Rather than having separate methods for a+-- discrete set of cases, this class provides an interpolation from a /local/ position to+-- a /global/ position. While the local position goes from 0 to 1, the global position+-- goes from the 'onset' to the 'offset' of the value.+--+-- For instantaneous values, a suitable instance is:+--+-- @+-- '_position' x = 'const' t+-- @+--+-- For values with an onset and offset we can use 'alerp':+--+-- @+-- '_position' x = 'alerp' ('_onset' x) ('_offset' x)+-- @+--+class HasDuration a => HasPosition a where+ -- |+ -- Return the onset of the given value, or the value between the attack and decay phases.+ --+ _position :: a -> Duration -> Time+ _position x = alerp (_onset x) (_offset x)++ -- |+ -- Return the onset of the given value, or the value between the attack and decay phases.+ --+ _onset, _offset :: a -> Time+ _onset = (`_position` 0)+ _offset = (`_position` 1.0)++instance HasPosition Time where+ _position = const++instance HasPosition Span where+ -- Override as an optimization:+ _onset (view range -> (t1, t2)) = t1+ _offset (view range -> (t1, t2)) = t2+ _position (view range -> (t1, t2)) = alerp t1 t2++instance (HasPosition a, HasDuration a) => HasDuration [a] where+ _duration x = _offset x .-. _onset x++instance (HasPosition a, HasDuration a) => HasPosition [a] where+ _onset = foldr min 0 . fmap _onset+ _offset = foldr max 0 . fmap _offset++_getEra :: HasPosition a => a -> Span+_getEra x = _onset x <-> _offset x+{-# INLINE _getEra #-}++-- |+-- Position of the given value.+--+position :: (HasPosition a, Transformable a) => Duration -> Lens' a Time+position d = lens (`_position` d) (flip $ placeAt d)+{-# INLINE position #-}++-- |+-- Onset of the given value.+--+onset :: (HasPosition a, Transformable a) => Lens' a Time+onset = position 0+{-# INLINE onset #-}++-- |+-- Onset of the given value.+--+offset :: (HasPosition a, Transformable a) => Lens' a Time+offset = position 1+{-# INLINE offset #-}++-- |+-- Pre-onset of the given value, or the value right before the attack phase.+--+preOnset :: (HasPosition a, Transformable a) => Lens' a Time+preOnset = position (-0.5)+{-# INLINE preOnset #-}++-- |+-- Post-onset of the given value, or the value between the decay and sustain phases.+--+postOnset :: (HasPosition a, Transformable a) => Lens' a Time+postOnset = position 0.5+{-# INLINE postOnset #-}++-- |+-- Post-offset of the given value, or the value right after the release phase.+--+postOffset :: (HasPosition a, Transformable a) => Lens' a Time+postOffset = position 1.5+{-# INLINE postOffset #-}++++-- |+-- Move a value forward in time.+--+startAt :: (Transformable a, HasPosition a) => Time -> a -> a+startAt t x = (t .-. _onset x) `delay` x++-- |+-- Move a value forward in time.+--+stopAt :: (Transformable a, HasPosition a) => Time -> a -> a+stopAt t x = (t .-. _offset x) `delay` x++-- |+-- Align a value to a given position.+--+-- @placeAt p t@ places the given thing so that its position p is at time t+--+-- @+-- 'placeAt' 0 = 'startAt'+-- 'placeAt' 1 = 'stopAt'+-- @+--+placeAt :: (Transformable a, HasPosition a) => Duration -> Time -> a -> a+placeAt p t x = (t .-. x `_position` p) `delay` x++-- |+-- Place a value over the given span.+--+-- @placeAt s t@ places the given thing so that @x^.place = s@+--+_setEra :: (HasPosition a, Transformable a) => Span -> a -> a+_setEra s x = transform (s ^-^ view era x) x++-- |+-- A lens to the position+--+era :: (HasPosition a, Transformable a) => Lens' a Span+era = lens _getEra (flip _setEra)+{-# INLINE era #-}++
src/Music/Time/Reactive.hs view
@@ -6,17 +6,17 @@ {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE ViewPatterns #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -24,6 +24,35 @@ -- Stability : experimental -- Portability : non-portable (TF,GNTD) --+-------------------------------------------------------------------------------------++module Music.Time.Reactive (+ -- * Reactive type+ Reactive,+ + -- * Construction+ initial,+ final,+ intermediate,+ discrete,+ updates,+ occs,+ atTime,+ splitReactive,+ + -- * Combinators+ switchR,+ trimR,++ -- * Sampling and discretization+ continous,+ continousWith,+ sample,+ -- TODO+ -- window,+ -- windowed, + ) where+ -- Reactive values, or piecewise functions of time. -- -- Similar to Conal's definition in <http://conal.net/blog/posts/reactive-normal-form>,@@ -33,175 +62,94 @@ -- -- TODO integrate better in the library ---------------------------------------------------------------------------------------- -module Music.Time.Reactive (- Reactive,+import Music.Time.Behavior+import Music.Time.Bound+import Music.Time.Note+import Music.Time.Reverse+import Music.Time.Segment+import Music.Time.Split - -- * Create, isos etc (TODO)- initial,- updates,- occs,- -- final,- -- Future,- -- Past,- -- resets,+import Music.Pitch.Alterable+import Music.Pitch.Augmentable+import Music.Pitch.Literal - -- * Predicates- isConstant,- isVariable, - -- * Combinators- -- step,-- switch,- trim,- -- trimBefore,- -- trimAfter,-- -- * Semantics- -- renderR,- -- renderR',- -- printR,- ) where- import Control.Applicative-import Control.Arrow-import Control.Lens+import Control.Arrow (first, second, (&&&), (***))+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>)) import Control.Monad-import Control.Monad.Compose import Control.Monad.Plus-import Data.AffineSpace-import Data.AffineSpace.Point-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.Semigroup-import Data.Set (Set)-import qualified Data.Set as Set-import Data.String-import Data.Traversable (Traversable)-import qualified Data.Traversable as T+import Data.Distributive+import Data.Functor.Rep+import Data.Functor.Rep.Lens+import qualified Data.List as List+import Data.Semigroup hiding () import Data.Typeable-import Data.VectorSpace--import Music.Time.Delayable-import Music.Time.Span-import Music.Time.Stretchable-import Music.Time.Time-import Music.Time.Time--- import Music.Score.Note--- import Music.Score.Track--- import Music.Score.Pitch--- import Music.Score.Util--- import Music.Pitch.Literal--- import Music.Dynamics.Literal--newtype Reactive a = Reactive { getReactive :: ([Time], Time -> a) }- deriving (Functor, Semigroup, Monoid)+import Music.Dynamics.Literal+import Music.Pitch.Literal -instance Delayable (Reactive a) where- delay n (Reactive (t,r)) = Reactive (delay n t, delay n r)+-- |+-- Forms an applicative as per 'Behavior', but only switches at discrete points.+--+-- The semantics are given by+--+-- @+-- type Reactive a = (a, Time, Voice a)+-- @+--+newtype Reactive a = Reactive { getReactive :: ([Time], Behavior a) }+ deriving (Functor, Semigroup, Monoid, Typeable)+--+-- TODO Define a more compact representation and reimplement Behavior as (Reactive Segment).+--+-- Possible approach:+--+-- * Implement PosReactive (no negative values) and define Reactive = Delayed (PosReactive)+--+-- * Implement liftA2 for PosReactive (preferably with a single traversal)+-- -instance Stretchable (Reactive a) where- stretch n (Reactive (t,r)) = Reactive (stretch n t, stretch n r)+instance Transformable (Reactive a) where+ transform s (Reactive (t,r)) = Reactive (transform s t, transform s r) instance Wrapped (Reactive a) where- type Unwrapped (Reactive a) = ([Time], Time -> a)+ type Unwrapped (Reactive a) = ([Time], Behavior a) _Wrapped' = iso getReactive Reactive +instance Rewrapped (Reactive a) (Reactive b) instance Applicative Reactive where- pure = (^. _Unwrapped') . pure . pure- ((^. _Wrapped') -> (tf, rf)) <*> ((^. _Wrapped') -> (tx, rx)) = (^. _Unwrapped') (tf <> tx, rf <*> rx)--instance HasBehavior Reactive where- (?) = atTime--instance IsString a => IsString (Reactive a) where- fromString = pure . fromString--instance Eq (Reactive b) where- (==) = error "(==)"- (/=) = error "(/=)"--instance Ord b => Ord (Reactive b) where- min = liftA2 min- max = liftA2 max--instance Enum a => Enum (Reactive a) where- succ = fmap succ- pred = fmap pred- toEnum = pure . toEnum- fromEnum = error "fromEnum"- enumFrom = error "enumFrom"- enumFromThen = error "enumFromThen"- enumFromTo = error "enumFromTo"- enumFromThenTo = error "enumFromThenTo"--instance Num a => Num (Reactive a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance (Num a, Ord a) => Real (Reactive a) where- toRational = error "toRational"+ pure = pureDefault+ (<*>) = apDefault -instance Integral a => Integral (Reactive a) where- quot = liftA2 quot- rem = liftA2 rem- div = liftA2 div- mod = liftA2 mod- quotRem = (fmap.fmap) unzip' (liftA2 quotRem)- divMod = (fmap.fmap) unzip' (liftA2 divMod)- toInteger = error "toInteger"+instance IsPitch a => IsPitch (Reactive a) where+ fromPitch = pure . fromPitch -instance Fractional b => Fractional (Reactive b) where- recip = fmap recip- fromRational = pure . fromRational+instance IsInterval a => IsInterval (Reactive a) where+ fromInterval = pure . fromInterval -instance Floating b => Floating (Reactive b) where- pi = pure pi- sqrt = fmap sqrt- exp = fmap exp- log = fmap log- sin = fmap sin- cos = fmap cos- asin = fmap asin- atan = fmap atan- acos = fmap acos- sinh = fmap sinh- cosh = fmap cosh- asinh = fmap asinh- atanh = fmap atanh- acosh = fmap acosh+instance IsDynamics a => IsDynamics (Reactive a) where+ fromDynamics = pure . fromDynamics -instance AdditiveGroup v => AdditiveGroup (Reactive v) where- zeroV = pure zeroV- (^+^) = liftA2 (^+^)- negateV = liftA negateV+instance Alterable a => Alterable (Reactive a) where+ sharpen = fmap sharpen+ flatten = fmap flatten -instance VectorSpace v => VectorSpace (Reactive v) where- type Scalar (Reactive v) = Scalar v- (*^) s = fmap (s *^)+instance Augmentable a => Augmentable (Reactive a) where+ augment = fmap augment+ diminish = fmap diminish --- | Get the time of all updatese.-occs :: Reactive a -> [Time]-occs = fst . (^. _Wrapped')---- | @b ?? t@ returns the value of the reactive at time @t@.--- Semantic function.-atTime :: Reactive a -> Time -> a-atTime = snd . (^. _Wrapped')+(view _Wrapped -> (tf, rf)) `apDefault` (view _Wrapped -> (tx, rx)) = view _Unwrapped (tf <> tx, rf <*> rx)+pureDefault = view _Unwrapped . pure . pure --- | Get the initial value.+-- |+-- Get the initial value.+-- initial :: Reactive a -> a-initial r = r ? minB (occs r)+initial r = r `atTime` minB (occs r) where -- If there are no updates, just use value at time 0 -- Otherwise pick an arbitrary time /before/ the first value@@ -210,64 +158,140 @@ minB (x:_) = x - 1 -- | Get the time of all updates and the value switched to at this point.-updates :: Reactive a -> [Future a]-updates r = (\t -> (t, r ? t)) <$> (List.sort . List.nub) (occs r)+updates :: Reactive a -> [(Time, a)]+updates r = (\t -> (t, r `atTime` t)) <$> (List.sort . List.nub) (occs r) --- | Get the final value.+renderR :: Reactive a -> (a, [(Time, a)])+renderR = initial &&& updates++occs :: Reactive a -> [Time]+occs = fst . (^. _Wrapped')++-- | Split a reactive into notes, as well as the values before and after the first/last update+splitReactive :: Reactive a -> Either a ((a, Time), [Note a], (Time, a))+splitReactive r = case updates r of+ [] -> Left (initial r)+ (t,x):[] -> Right ((initial r, t), [], (t, x))+ (t,x):xs -> Right ((initial r, t), fmap mkNote $ mrights (res $ (t,x):xs), head $ mlefts (res $ (t,x):xs))++ where++ mkNote (t,u,x) = (t <-> u, x)^.note++ -- Always returns a 0 or more Right followed by one left+ res :: [(Time, a)] -> [Either (Time, a) (Time, Time, a)]+ res rs = let (ts,xs) = unzip rs in+ flip fmap (withNext ts `zip` xs) $+ \ ((t, mu), x) -> case mu of+ Nothing -> Left (t, x)+ Just u -> Right (t, u, x)++ -- lenght xs == length (withNext xs)+ withNext :: [a] -> [(a, Maybe a)]+ withNext = go+ where+ go [] = []+ go [x] = [(x, Nothing)]+ go (x:y:rs) = (x, Just y) : withNext (y : rs)++{-# DEPRECATED updates "" #-}+{-# DEPRECATED occs "" #-}+{-# DEPRECATED splitReactive "" #-}+{-# DEPRECATED atTime "" #-}++atTime :: Reactive a -> Time -> a+atTime = (!) . snd . (^. _Wrapped')++-- |+-- Get the final value.+-- final :: Reactive a -> a final (renderR -> (i,[])) = i final (renderR -> (i,xs)) = snd $ last xs -- | @switch t a b@ behaves as @a@ before time @t@, then as @b@.-switch :: Time -> Reactive a -> Reactive a -> Reactive a-switch t (Reactive (tx, rx)) (Reactive (ty, ry)) = Reactive $ (,)- (filter (< t) tx <> [t] <> filter (> t) ty)- (\u -> if u < t then rx u else ry u)+switchR :: Time -> Reactive a -> Reactive a -> Reactive a+switchR t (Reactive (tx, bx)) (Reactive (ty, by)) = Reactive $ (,)+ (filter (< t) tx <> [t] <> filter (> t) ty) (switch t bx by) +trimR :: Monoid a => Span -> Reactive a -> Reactive a+trimR (view range -> (t, u)) x = switchR t mempty (switchR u x mempty) +-- |+-- Get all intermediate values.+--+intermediate :: Transformable a => Reactive a -> [Note a]+intermediate (updates -> []) = []+intermediate (updates -> xs) = fmap (\((t1, x), (t2, _)) -> (t1 <-> t2, x)^.note) $ withNext $ xs+ where+ withNext xs = zip xs (tail xs) --- TODO separate these with newtype-type Future a = (Time, a)-type Past a = (Time, a)+-- |+-- Realize a 'Reactive' value as a discretely changing behavior.+--+discrete :: Reactive a -> Behavior a+discrete = continous . fmap pure -isConstant :: Reactive a -> Bool-isConstant = null . occs+-- |+-- Realize a 'Reactive' value as an continous behavior.+--+-- See also 'concatSegment' and 'concatB'.+--+continous :: Reactive (Segment a) -> Behavior a -isVariable :: Reactive a -> Bool-isVariable = not . isConstant+-- |+-- Realize a 'Reactive' value as an continous behavior.+--+-- See also 'concatSegment' and 'concatB'.+--+continousWith :: Segment (a -> b) -> Reactive a -> Behavior b+continousWith f x = continous $ liftA2 (<*>) (pure f) (fmap pure x) -resets :: Reactive a -> [Past a]-resets = error "resets: Not impl"+-- |+-- Sample a 'Behavior' into a reactive.+--+sample :: [Time] -> Behavior a -> Reactive a --- | The unit step function, which goes from 0 to 1 at 'start'.-step :: (AdditiveGroup a, Fractional a) => Reactive a-step = switch start zeroV 1.0 -- TODO some overloaded unit+-- TODO linear approximation+(continous, sample) = error "Not implemented: (continous, sample)" --- | Replace everthing outside the given span by `mempty`.-trim :: Monoid a => Span -> Reactive a -> Reactive a-trim (view range -> (t,u)) = trimBefore t . trimAfter u --- | Replace everthing before the given time by `mempty`.-trimBefore :: Monoid a => Time -> Reactive a -> Reactive a-trimBefore start x = switch start mempty x+window :: [Time] -> Behavior a -> Reactive (Segment a)+windowed :: Iso (Behavior a) (Behavior b) (Reactive (Segment a)) (Reactive (Segment b))+(window, windowed) = error "Not implemented: (window, windowed)" --- | Replace everthing after the given time by `mempty`.-trimAfter :: Monoid a => Time -> Reactive a -> Reactive a-trimAfter stop x = switch stop x mempty+{- --- | Semantic function.-renderR :: Reactive a -> (a, [(Time, a)])-renderR = initial &&& updates+-- Fre monad of ?+{-+data Score s a+ = SOne a+ | SPlus s [Score a]+-}+newtype Trans s a = Trans (s, [a]) deriving (Functor)+instance Monoid s => Monad (Trans s) where+ return = Trans . return . return+ -- TODO the usual >>= --- | Semantic function.-renderR' :: Reactive a -> ([Time], Time -> a)-renderR' = occs &&& (?)+type Score s a = Free (Trans s) a +viewScore :: Monoid s => Score s a -> [(s, a)]+viewScore x = case retract x of+ Trans (s,as) -> zip (repeat s) as -printR :: Show a => Reactive a -> IO ()-printR r = let (x, xs) = renderR r in do- print x- mapM_ print xs -unzip' :: Functor f => f (a, b) -> (f a, f b)-unzip' r = (fst <$> r, snd <$> r)+-- Free monad of (a,a)+{-+data Tree a+ = One a+ | Plus (Tree a) (Tree a)+-}+data Pair a = Pair a a deriving (Functor)+newtype MaybePair a = MaybePair (Maybe (Pair a)) deriving (Functor) -- Use compose+type Tree a = Free MaybePair a++-- CPS-version of Tree+newtype Search a = Search { getSearch :: forall r . (a -> Tree r) -> Tree r }+ -}++
+ src/Music/Time/Rest.hs view
@@ -0,0 +1,44 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-- Many time structures such as 'Score' allows for rests between notes. Generally rests+-- are simply treated as blank space, and thus have no duration. Sometimes it is useful+-- to represent rests explicitly, so this module provides an alias for 'pure' 'Nothing' that+-- can be used to that end.+--+-- To remove rests from a score, use 'mcatMaybes', for example:+--+-- > open $ mcatMaybes $ scat [c,d,rest^*2,e]^/8+--+-------------------------------------------------------------------------------------++module Music.Time.Rest (+ -- * Rests+ rest,+ ) where++import Control.Applicative+import Music.Time.Reverse+import Music.Time.Split++rest :: Applicative f => f (Maybe a)+rest = pure Nothing
src/Music/Time/Reverse.hs view
@@ -1,9 +1,24 @@ +{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-} ------------------------------------------------------------------------------------- -- |--- Copyright : (c) Hans Hoglund 2012+-- Copyright : (c) Hans Hoglund 2012-2014 -- -- License : BSD-style --@@ -16,14 +31,17 @@ ------------------------------------------------------------------------------------- module Music.Time.Reverse (- -- * Reversible class+ module Music.Time.Position,++ -- * The Reversible class Reversible(..), - -- ** Utility- NoRev(..),- WithRev(..),- withRev,- fromWithRev,+ -- * Reversed values+ reversed,+ revDefault,++ -- * Utility+ NoReverse(..), ) where import Data.AffineSpace@@ -36,13 +54,144 @@ import qualified Data.Set as Set import Data.VectorSpace -import Music.Time.Delayable-import Music.Time.Juxtapose-import Music.Time.Onset-import Music.Time.Stretchable-import Music.Time.Time+import Music.Time.Position +import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Semigroup hiding ()+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.Typeable+import Data.VectorSpace hiding (Sum (..))+ -- |+-- Class of values that can be reversed (retrograded).+--+-- For positioned values succh as 'Note', the value is reversed relative to its middle point, i.e.+-- the onset value becomes the offset value and vice versa.+--+-- For non-positioned values such as 'Stretched', the value is reversed in-place.+--+-- FIXME Second law is incompatible with 'revDefault' (and the 'Span' definition below)+--+-- Law+--+-- @+-- 'rev' ('rev' a) = a+-- @+--+-- @+-- 'abs' ('_duration' x) = _duration ('rev' x)+-- @+--+-- @+-- 'rev' s ``transform`` a = 'rev' (s ``transform`` a)+-- @+--+-- or equivalently,+--+-- @+-- 'transform' . 'rev' = 'fmap' 'rev' . 'transform'+-- @+--+-- For 'Span'+--+-- @+-- 'rev' = 'over' 'range' 'swap'+-- @+--+class Transformable a => Reversible a where++ -- | Reverse (retrograde) the given value.+ rev :: a -> a++--+-- XXX Counter-intuitive Behavior instances (just Behavior should reverse around origin,+-- while Bound (Behavior a) should reverse around the middle, like a note)+--++--+-- XXX Alternate formulation of second Reversiblee law+--+-- rev s `transform` a = rev (s `transform` a)+-- ==> (rev s `transform`) = rev . (s `transform`)+-- ==> transform (rev s) = rev . (transform s)+-- ==> (transform . rev) s = (rev .) (transform s)+-- ==> (transform . rev) s = fmap rev (transform s)+-- ==> transform . rev = fmap rev . transform+--++instance Reversible () where+ rev = id++instance Reversible Int where+ rev = id++instance Reversible Double where+ rev = id++instance Reversible Integer where+ rev = id++instance Reversible a => Reversible [a] where+ rev = reverse . map rev++instance Reversible a => Reversible (Seq a) where+ rev = Seq.reverse . fmap rev++instance (Ord k, Reversible a) => Reversible (Map k a) where+ rev = Map.map rev++instance Reversible Duration where+ rev = stretch (-1)++--+-- There is no instance for Reversible Time+-- as we can not satisfy the second Reversible law+--++instance Reversible Span where+ rev = revDefault++instance Reversible a => Reversible (b, a) where+ rev (s,a) = (s, rev a)++-- |+-- A default implementation of 'rev'+--+revDefault :: (HasPosition a, Transformable a) => a -> a+-- revDefault x = (stretch (-1) `whilst` undelaying (_position x 0.5 .-. 0)) x+revDefault x = stretch (-1) x++newtype NoReverse a = NoReverse { getNoReverse :: a }+ deriving (Typeable, Eq, Ord, Show)++instance Transformable (NoReverse a) where+ transform _ = id++instance Reversible (NoReverse a) where+ rev = id++-- |+-- View the reverse of a value.+--+-- >>> [1,2,3] & reversed %~ sort+-- > [3,2,1]+--+reversed :: Reversible a => Iso' a a+reversed = iso rev rev++++++{-+-- | -- Reversible values. -- -- For instances of 'Reversible' and 'HasOnset', the following laws should hold:@@ -63,6 +212,7 @@ -- > rev = withSameOnset (stretch (-1)) -- --+ class Reversible a where -- |@@ -115,5 +265,6 @@ instance Reversible a => Reversible (WithRev a) where rev (WithRev (r,x)) = WithRev (x,r) + -}
+ src/Music/Time/Score.hs view
@@ -0,0 +1,550 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Score (+ -- * Score type+ Score,++ -- * Construction+ score,+ + -- ** Extracting values+ notes,+ events,+ + -- ** Pattern matching+ singleNote,++ -- ** Unsafe versions+ unsafeNotes,+ unsafeEvents,+++ -- * Simultaneous values+ -- TODO check for overlapping values etc+ simult,+ simultaneous,++ -- * Traversing+ mapWithSpan,+ filterWithSpan,+ mapFilterWithSpan,+ mapEvents,+ filterEvents,+ mapFilterEvents,++ ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import qualified Data.List.NonEmpty as NonEmpty+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace+import Data.Functor.Adjunction (unzipR)++import Music.Time.Juxtapose (scat)+import Music.Time.Meta+import Music.Time.Note+import Music.Time.Reverse+import Music.Time.Split+import Music.Time.Stretched+import Music.Time.Voice++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Comonad+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Control.Monad+import Control.Monad.Compose+import Control.Monad.Plus+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import qualified Data.List as List+import qualified Data.Ord as Ord+import Data.Semigroup hiding ()+import Data.Traversable (Traversable)+import qualified Data.Traversable as T+import Data.Typeable+import Data.VectorSpace hiding (Sum (..))+import Music.Dynamics.Literal+import Music.Pitch.Literal+import Music.Time.Internal.Util++++type ScoreNote a = Note a++-- * 'empty' creates an empty score+--+-- * 'pure' creates a score containing a single note in the span @0 '<->' 1@+--+-- * '<|>' composes scores in parallel+--+-- * '|>' composes scores as a forward sequence+--+-- * '<|' composes scores as a backward sequence+--+-- You can also use '<>' and 'mempty' of course.+--++-- |+-- A 'Score' is a sequential or parallel composition of values, and allows overlapping events+--+-- You typically create a 'Score' using 'score', 'notes', 'voices', and 'phrases', or the 'Alternative' interface.+--+-- Score is an instance of 'Transformable', so you can use 'delay' and 'stretch'.+--+-- Score is an instance of 'HasPosition', so you can use 'duration', 'onset', 'offset', 'era'.+--+-- To inspect or deconstruct a score, see 'notes', 'voices', and 'phrases', as+-- well as 'singleNote', 'singleVoice', and 'singlePhrase'+--+-- The semantics are given by+--+-- @+-- type Score a = [Note a]+-- @+--++newtype Score a = Score { getScore' :: (Meta, NScore a) }+ deriving (Functor, Semigroup, Monoid, Foldable, Traversable, Typeable{-, Show, Eq, Ord-})++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 FunctorWithIndex Span Score where+ imap f = over (_Wrapped._2) $ imap f++instance FoldableWithIndex Span Score where+ ifoldMap f (Score (m,x)) = ifoldMap f x++instance TraversableWithIndex Span Score where+ itraverse f (Score (m,x)) = fmap (\x -> Score (m,x)) $ itraverse f x++instance Transformable (Score a) where+ transform t (Score (m,x)) = Score (transform t m, transform t x)++instance Reversible a => Reversible (Score a) where+ rev (Score (m,x)) = Score (rev m, rev x)++instance Splittable a => Splittable (Score a) where+ split t (Score (m,x)) = (Score (m1,x1), Score (m2,x2))+ where+ (m1, m2) = split t m+ (x1, x2) = split t x++instance HasPosition (Score a) where+ _onset (Score (_,x)) = _onset x+ _offset (Score (_,x)) = _offset x+ _position (Score (_,x)) = _position x++instance HasDuration (Score a) where+ _duration (Score (_,x)) = _duration x++++-- Lifted instances++instance IsPitch a => IsPitch (Score a) where+ fromPitch = pure . fromPitch++instance IsInterval a => IsInterval (Score a) where+ fromInterval = pure . fromInterval++instance IsDynamics a => IsDynamics (Score a) where+ fromDynamics = pure . fromDynamics++-- Bogus instance, so we can use [c..g] expressions+instance Enum a => Enum (Score a) where+ toEnum = return . toEnum+ fromEnum = list 0 (fromEnum . head) . Foldable.toList++-- Bogus instance, so we can use numeric literals+instance Num a => Num (Score a) where+ fromInteger = return . fromInteger+ abs = fmap abs+ signum = fmap signum+ (+) = error "Not implemented"+ (-) = error "Not implemented"+ (*) = error "Not implemented"++-- Bogus instances, so we can use c^*2 etc.+instance AdditiveGroup (Score a) where+ zeroV = error "Not implemented"+ (^+^) = error "Not implemented"+ negateV = error "Not implemented"++instance VectorSpace (Score a) where+ type Scalar (Score a) = Duration+ d *^ s = d `stretch` s++instance HasMeta (Score a) where+ meta = _Wrapped . _1++++++++newtype NScore a = NScore { getNScore :: [ScoreNote a] }+ deriving ({-Eq, -}{-Ord, -}{-Show, -}Functor, Foldable, Traversable, Semigroup, Monoid, Typeable, Show, Eq)++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (NScore a) where+ type Unwrapped (NScore a) = [ScoreNote a]+ _Wrapped' = iso getNScore NScore++instance Rewrapped (NScore a) (NScore b)++instance Applicative NScore where+ pure = return+ (<*>) = ap++instance Monad NScore where+ return = (^. _Unwrapped) . pure . pure+ xs >>= f = (^. _Unwrapped) $ mbind ((^. _Wrapped') . f) ((^. _Wrapped') xs)++instance Alternative NScore where+ empty = mempty+ (<|>) = mappend++instance MonadPlus NScore where+ mzero = mempty+ mplus = mappend++instance FunctorWithIndex Span NScore where+ imap = undefined+ -- TODO++instance FoldableWithIndex Span NScore where+ ifoldMap = undefined+ -- TODO++instance TraversableWithIndex Span NScore where+ itraverse = undefined+ -- TODO++instance Transformable (NScore a) where+ transform t (NScore xs) = NScore (fmap (transform t) xs)++instance Reversible a => Reversible (NScore a) where+ rev (NScore xs) = NScore (fmap rev xs)++instance HasPosition (NScore a) where+ _onset = safeMinimum . fmap _onset . view _Wrapped'+ _offset = safeMaximum . fmap _offset . view _Wrapped'++-- TODO move+safeMinimum xs = if null xs then 0 else minimum xs+safeMaximum xs = if null xs then 0 else maximum xs++instance HasDuration (NScore a) where+ _duration x = _offset x .-. _onset x++instance Splittable a => Splittable (NScore a) where+ split t (NScore notes) = over both (NScore . mfilter (not . isEmptyNote)) $ unzip $ map (\x -> splitAbs (0 .+^ t) x) notes+ where+ -- TODO move+ isEmptyNote :: Note a -> Bool+ isEmptyNote = isEmptySpan . view era+ + isEmptySpan :: Span -> Bool+ isEmptySpan (view range -> (t, u)) = t == u+++-- |+-- Create a score from a list of notes.+--+-- This is a getter (rather than a function) for consistency:+--+-- @+-- [ (0 '<->' 1, 10)^.'note',+-- (1 '<->' 2, 20)^.'note',+-- (3 '<->' 4, 30)^.'note' ]^.'score'+-- @+--+-- @+-- 'view' 'score' $ 'map' ('view' 'note') [(0 '<->' 1, 1)]+-- @+--+-- Se also 'notes'.+--+score :: Getter [Note a] (Score a)+score = from unsafeNotes+{-# INLINE score #-}++-- |+-- View a 'Score' as a list of 'Note' values.+--+-- @+-- 'view' 'notes' :: 'Score' a -> ['Note' a]+-- 'set' 'notes' :: ['Note' a] -> 'Score' a -> 'Score' a+-- 'over' 'notes' :: (['Note' a] -> ['Note' b]) -> 'Score' a -> 'Score' b+-- @+--+-- @+-- 'preview' ('notes' . 'each') :: 'Score' a -> 'Maybe' ('Note' a)+-- 'preview' ('notes' . 'element' 1) :: 'Score' a -> 'Maybe' ('Note' a)+-- 'preview' ('notes' . 'elements' odd) :: 'Score' a -> 'Maybe' ('Note' a)+-- @+--+-- @+-- 'set' ('notes' . 'each') :: 'Note' a -> 'Score' a -> 'Score' a+-- 'set' ('notes' . 'element' 1) :: 'Note' a -> 'Score' a -> 'Score' a+-- 'set' ('notes' . 'elements' odd) :: 'Note' a -> 'Score' a -> 'Score' a+-- @+--+-- @+-- 'over' ('notes' . 'each') :: ('Note' a -> 'Note' b) -> 'Score' a -> 'Score' b+-- 'over' ('notes' . 'element' 1) :: ('Note' a -> 'Note' a) -> 'Score' a -> 'Score' a+-- 'over' ('notes' . 'elements' odd) :: ('Note' a -> 'Note' a) -> 'Score' a -> 'Score' a+-- @+--+-- @+-- 'toListOf' ('notes' . 'each') :: 'Score' a -> ['Note' a]+-- 'toListOf' ('notes' . 'elements' odd) :: 'Score' a -> ['Note' a]+-- 'toListOf' ('notes' . 'each' . 'filtered'+-- (\\x -> '_duration' x \< 2)) :: 'Score' a -> ['Note' a]+-- @+--+-- This is not an 'Iso', as the note list representation does not contain meta-data.+-- To construct a score from a note list, use 'score' or @'flip' ('set' 'notes') 'empty'@.+--+notes :: Lens (Score a) (Score b) [Note a] [Note b]+notes = _Wrapped . _2 . _Wrapped . sorted+ where+ sorted = iso (List.sortBy (Ord.comparing _onset)) (List.sortBy (Ord.comparing _onset))+-- notes = unsafeNotes+{-# INLINE notes #-}++-- -- |+-- -- View a score as a list of voices.+-- --+-- -- @+-- -- 'view' 'voices' :: 'Score' a -> ['Voice' a]+-- -- 'set' 'voices' :: ['Voice' a] -> 'Score' a -> 'Score' a+-- -- 'over' 'voices' :: (['Voice' a] -> ['Voice' b]) -> 'Score' a -> 'Score' b+-- -- @+-- --+-- -- @+-- -- 'preview' ('voices' . 'each') :: 'Score' a -> 'Maybe' ('Voice' a)+-- -- 'preview' ('voices' . 'element' 1) :: 'Score' a -> 'Maybe' ('Voice' a)+-- -- 'preview' ('voices' . 'elements' odd) :: 'Score' a -> 'Maybe' ('Voice' a)+-- -- @+-- --+-- -- @+-- -- 'set' ('voices' . 'each') :: 'Voice' a -> 'Score' a -> 'Score' a+-- -- 'set' ('voices' . 'element' 1) :: 'Voice' a -> 'Score' a -> 'Score' a+-- -- 'set' ('voices' . 'elements' odd) :: 'Voice' a -> 'Score' a -> 'Score' a+-- -- @+-- --+-- -- @+-- -- 'over' ('voices' . 'each') :: ('Voice' a -> 'Voice' b) -> 'Score' a -> 'Score' b+-- -- 'over' ('voices' . 'element' 1) :: ('Voice' a -> 'Voice' a) -> 'Score' a -> 'Score' a+-- -- 'over' ('voices' . 'elements' odd) :: ('Voice' a -> 'Voice' a) -> 'Score' a -> 'Score' a+-- -- @+-- --+-- -- @+-- -- 'toListOf' ('voices' . 'each') :: 'Score' a -> ['Voice' a]+-- -- 'toListOf' ('voices' . 'elements' odd) :: 'Score' a -> ['Voice' a]+-- -- 'toListOf' ('voices' . 'each' . 'filtered' (\\x -> '_duration' x \< 2)) :: 'Score' a -> ['Voice' a]+-- -- @+-- --+-- -- This is not an 'Iso', as the voice list representation does not contain meta-data.+-- -- To construct a score from a voice list, use 'score' or @'flip' ('set' 'voices') 'empty'@.+-- --+-- voices :: Lens (Score a) (Score b) [Voice a] [Voice b]+-- voices = unsafeVoices+-- {-# INLINE voices #-}++-- |+-- View a score as a list of notes.+--+-- This only an isomorphism up to meta-data. See also the safe (but more restricted)+-- 'notes' and 'score'.+--+unsafeNotes :: Iso (Score a) (Score b) [Note a] [Note b]+unsafeNotes = _Wrapped . noMeta . _Wrapped . sorted+ where+ sorted = iso (List.sortBy (Ord.comparing _onset)) (List.sortBy (Ord.comparing _onset))+ noMeta = iso extract return+ -- noMeta = iso (\(_,x) -> x) (\x -> (mempty,x))++{-# INLINE unsafeNotes #-}++-- |+-- View a score as a list of events.+--+-- This only an isomorphism up to meta-data. See also the safe (but more restricted)+-- 'notes' and 'score'.+--+unsafeEvents :: Iso (Score a) (Score b) [(Time, Duration, a)] [(Time, Duration, b)]+unsafeEvents = iso _getScore _score++-- |+-- View a score as a single note.+--+singleNote :: Prism' (Score a) (Note a)+singleNote = unsafeNotes . single+{-# INLINE singleNote #-}+-- TODO make prism fail if score contains meta-data+-- (or else second prism law is not satisfied)+++-- | Map with the associated time span.+mapScore :: (Note a -> b) -> Score a -> Score b+mapScore f = over (_Wrapped._2) (mapNScore f)+ where+ mapNScore f = over (_Wrapped.traverse) (extend f)++reifyScore :: Score a -> Score (Note a)+reifyScore = over (_Wrapped . _2 . _Wrapped) $ fmap duplicate++-- |+-- View a score as a list of events, i.e. time-duration-value triplets.+--+-- This is a convenient combination of 'notes' and 'event'.+--+-- @+-- 'events' = 'notes' . 'through' 'event' 'event'+-- @+--+events :: {-Transformable a => -}Lens (Score a) (Score b) [(Time, Duration, a)] [(Time, Duration, b)]+events = notes . through event event++_score :: [(Time, Duration, a)] -> Score a+_score = mconcat . fmap (uncurry3 event)+ where+ event t d x = (delay (t .-. 0) . stretch d) (return x)++_getScore :: {-Transformable a => -}Score a -> [(Time, Duration, a)]+_getScore =+ fmap (\(view delta -> (t,d),x) -> (t,d,x)) .+ List.sortBy (Ord.comparing fst) .+ Foldable.toList .+ fmap (view $ from note) .+ reifyScore+++-- | Map over the values in a score.+mapWithSpan :: (Span -> a -> b) -> Score a -> Score b+mapWithSpan f = mapScore (uncurry f . view (from note))++-- | Filter the values in a score.+filterWithSpan :: (Span -> a -> Bool) -> Score a -> Score a+filterWithSpan f = mapFilterWithSpan (partial2 f)++-- | Combination of 'mapEvents' and 'filterEvents'.+mapFilterWithSpan :: (Span -> a -> Maybe b) -> Score a -> Score b+mapFilterWithSpan f = mcatMaybes . mapWithSpan f++-- | Map over the values in a score.+mapEvents :: (Time -> Duration -> a -> b) -> Score a -> Score b+mapEvents f = mapWithSpan (uncurry f . view delta)++-- | Filter the values 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+++++eras :: Transformable a => Score a -> [Span]+eras sc = fmap getSpan . (^. events) $ sc++chordEvents :: Transformable a => Span -> Score a -> [a]+chordEvents era sc = fmap getValue . filter (\ev -> getSpan ev == era) . (^. events) $ sc++getValue :: (Time, Duration, a) -> a+getValue (t,d,a) = a++getSpan :: (Time, Duration, a) -> Span+getSpan (t,d,a) = t >-> d+++simultaneous' :: Transformable a => Score a -> Score [a]+simultaneous' sc = (^. from unsafeEvents) vs+ where+ -- es :: [Era]+ -- evs :: [[a]]+ -- vs :: [(Time, Duration, [a])]+ es = List.nub $ eras sc+ evs = fmap (`chordEvents` sc) es+ vs = zipWith (\(view delta -> (t,d)) a -> (t,d,a)) es evs++-- overSimult :: Transformable a => (Score [a] -> Score [b]) -> Score a -> Score b+-- overSimult f = mscatter . f . simultaneous'++-- |+-- Merge all simultaneous events using their 'Semigroup' instance.+--+-- Two events /a/ and /b/ are considered simultaneous if and only if they have the same+-- era, that is if @`era` a == `era` b@+--+simultaneous :: (Transformable a, Semigroup a) => Score a -> Score a+simultaneous = fmap (sconcat . NonEmpty.fromList) . simultaneous'++simult :: Transformable a => Lens (Score a) (Score b) (Score [a]) (Score [b])+simult = iso simultaneous' mscatter+-- TODO identical to: lens simultaneous' (flip $ mapSimultaneous . const)+-- wrap in something to preserve meta+
+ src/Music/Time/Segment.hs view
@@ -0,0 +1,371 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Segment (++ -- * Behavior type+ Behavior,+ -- ** Examples+ -- $musicTimeBehaviorExamples+ -- (!^),+ -- behavior',+ behavior,++ -- ** Combinators+ switch,+ switch',+ splice,+ trim,+ trimBefore,+ trimAfter,+ concatB,++ -- * Common behaviors+ line,+ unit,+ impulse,+ turnOn,+ turnOff,+ sawtooth,+ sine,+ cosine,++ -- * Segment type+ Segment,+ -- ** Examples+ -- $XXmusicTimeSegmentExamples+ segment,++ -- ** Combinators+ focusing,+ apSegments',+ apSegments,+ -- concatS,++ Bound,+ bounds,+ bounding,+ trim,+ splice,+ bounded',+ bounded, ++ ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Clipped+import Data.Functor.Rep.Lens+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Behavior+import Music.Time.Bound+import Music.Time.Note+import Music.Time.Reverse+import Music.Time.Score+import Music.Time.Split+import Music.Time.Stretched+import Music.Time.Voice++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.Distributive+import Data.Functor.Rep+import Data.Functor.Rep.Lens+import Data.Maybe+import Data.Typeable+import Music.Dynamics.Literal+import Music.Pitch.Literal+++-- TODO Compare Diagram's Trail and Located (and see the conal blog post)++-- |+--+-- A 'Segment' is a value varying over some unknown time span.+--+-- To give a segment an explicit duration, use 'Stretched' 'Segment'.+--+-- To place a segment in a particular time span, use 'Note' 'Segment'.+--+-- The semantics are given by+--+-- @+-- type Segment a = 'Duration' -> a+-- @+--+newtype Segment a = Segment { getSegment :: Clipped Duration -> a }+ deriving (Functor, Applicative, Monad{-, Comonad-})++--+-- TODO constant-optimize a la Conal+--++-- $musicTimeSegmentExamples+--+-- > foldr1 apSegments' $ map (view stretched) $ [(0.5,0::Segment Float), (1, timeS), (2,rev timeS), (3,-1)]+--+-- > openG $ draw $ (1, timeS :: Segment Float)^.stretched+--++instance Show (Segment a) where+ show _ = "<<Segment>>"++deriving instance Typeable1 Segment+deriving instance Distributive Segment++instance Representable Segment where+ type Rep Segment = Duration+ tabulate f = Segment (f . fromClipped)+ index (Segment f) = f . unsafeToClipped++-- |+-- Segments are /invariant/ under transformation. To transform a timve varying value, use+-- 'fromSegment'.+--+instance Transformable (Segment a) where+ transform _ = id++instance Reversible (Segment a) where+ -- TODO in terms of Representable+ rev (Segment f) = Segment (f . unsafeToClipped . r . fromClipped)+ where+ r x = (x * (-1)) + 1++-- TODO+-- type instance Pitch (Segment a) = Segment (Pitch a)+-- type instance SetPitch (Segment g) (Segment a) = Segment (SetPitch g a)+--+-- instance (HasPitch a a, HasPitch a b) => HasPitches (Segment a) (Segment b) where+-- pitches = through pitch pitch+-- instance (HasPitch a a, HasPitch a b) => HasPitch (Segment a) (Segment b) where+-- pitch = through pitch pitch+--+-- type instance Dynamic (Segment a) = Segment (Dynamic a)+-- type instance SetDynamic (Segment g) (Segment a) = Segment (SetDynamic g a)+--+-- instance (HasDynamic a a, HasDynamic a b) => HasDynamics (Segment a) (Segment b) where+-- dynamics = through dynamic dynamic+-- instance (HasDynamic a a, HasDynamic a b) => HasDynamic (Segment a) (Segment b) where+-- dynamic = through dynamic dynamic+--+--+-- type instance Articulation (Segment a) = Segment (Articulation a)+-- type instance SetArticulation (Segment g) (Segment a) = Segment (SetArticulation g a)+--+-- instance (HasArticulation a a, HasArticulation a b) => HasArticulations (Segment a) (Segment b) where+-- articulations = through articulation articulation+-- instance (HasArticulation a a, HasArticulation a b) => HasArticulation (Segment a) (Segment b) where+-- articulation = through articulation articulation+--+--+-- type instance Part (Segment a) = Segment (Part a)+-- type instance SetPart (Segment g) (Segment a) = Segment (SetPart g a)+--+-- instance (HasPart a a, HasPart a b) => HasParts (Segment a) (Segment b) where+-- parts = through part part+-- instance (HasPart a a, HasPart a b) => HasPart (Segment a) (Segment b) where+-- part = through part part++-- #ifdef INCLUDE_LIFTED+-- deriving instance Semigroup a => Semigroup (Segment a)+-- deriving instance Monoid a => Monoid (Segment a)+-- deriving instance Num a => Num (Segment a)+-- deriving instance Fractional a => Fractional (Segment a)+-- deriving instance Floating a => Floating (Segment a)+--+-- instance IsPitch a => IsPitch (Segment a) where+-- fromPitch = pure . fromPitch+--+-- instance IsInterval a => IsInterval (Segment a) where+-- fromInterval = pure . fromInterval+--+-- instance Alterable a => Alterable (Segment a) where+-- sharpen = fmap sharpen+-- flatten = fmap flatten+--+-- instance Augmentable a => Augmentable (Segment a) where+-- augment = fmap augment+-- diminish = fmap diminish+--+-- instance Eq a => Eq (Segment a) where+-- (==) = error "No fun"+--+-- instance Ord a => Ord (Segment a) where+-- (<) = error "No fun"+-- max = liftA2 max+-- min = liftA2 min+-- #endif++-- |+-- View a segment as a time function and vice versa.+--+segment :: Iso (Duration -> a) (Duration -> b) (Segment a) (Segment b)+segment = tabulated++apSegments' :: Stretched (Segment a) -> Stretched (Segment a) -> Stretched (Segment a)+apSegments' (view (from stretched) -> (d1,s1)) (view (from stretched) -> (d2,s2))+ = view stretched (d1+d2, slerp (d1/(d1+d2)) s1 s2)++-- |+-- Append a voice of segments to a single stretched segment.+--+apSegments :: Voice (Segment a) -> Stretched (Segment a)+apSegments = foldr1 apSegments' . toListOf (stretcheds . each)++-- t < i && 0 <= t <= 1 ==> 0 < (t/i) < 1+-- i is the fraction of the slerped segment spent in a+-- (1-i) is the fraction of the slerped segment spent in b+slerp :: Duration -> Segment a -> Segment a -> Segment a+slerp i a b+ | i < 0 || i >= 1 = error "slerp: Bad value"+ | otherwise = tabulate $ \t -> if t < i then a ! (t/i) else b ! ((t-i)/(1-i))++slerp2 :: (a -> a -> a) -> Duration -> Segment a -> Segment a -> Segment a+slerp2 f i a b+ | i < 0 || i >= 1 = error "slerp: Bad value"+ | otherwise = tabulate $ \t -> case t `compare` i of+ LT -> a ! (t/i)+ EQ -> (a ! 1) `f` (b ! 1)+ GT -> b ! ((t-i)/(1-i))+++-- |+-- View a 'Note' 'Segment' as a 'Bound' 'Behavior' and vice versa.+--+-- This can be used to safely turn a behavior into a segment and vice+-- versa. Often 'focusing' is more convenient to use.+--+bounded' :: Iso' (Note (Segment a)) (Bound (Behavior a))+bounded' = bounded++-- |+-- View a 'Note' 'Segment' as a 'Bound' 'Behavior' and vice versa.+--+-- This can be used to safely turn a behavior into a segment and vice+-- versa. Often 'focusing' is more convenient to use.+--+bounded :: Iso (Note (Segment a)) (Note (Segment b)) (Bound (Behavior a)) (Bound (Behavior b))+bounded = iso ns2bb bb2ns+ where+ bb2ns (Bound (s, x)) = view note (s, b2s $ transform (negateV s) $ x)+ ns2bb (view (from note) -> (s, x)) = Bound (s, transform s $ s2b $ x)+ s2b = under tabulated (. realToFrac)+ b2s = under tabulated (. realToFrac)++--+-- Note that the isomorhism only works because of 'Bound' being abstract.+-- A function @unBound :: Bound a -> a@ could break the isomorphism+-- as follows:+--+-- > (unBound . view (from bounded . bounded) . bounds 0 1) b ! 2+-- *** Exception: Outside 0-1+--++-- |+-- Extract a bounded behavior, replacing all values outside the bound with 'mempty'.+--+-- @+-- 'trim' = 'splice' 'mempty'+-- 'trim' x = 'trimBefore' '_onset' x . 'trimAfter' '_offset' x+-- @+--+trim :: Monoid b => Bound (Behavior b) -> Behavior b+trim = trimG+ where+ trimG :: (Monoid b, Representable f, Rep f ~ Time) => Bound (f b) -> f b+ trimG (Bound (s, x)) = tabulate (trimOutside s) `apRep` x++trimOutside :: Monoid a => Span -> Time -> a -> a+trimOutside s t x = if t `inside` s then x else mempty++-- |+-- Inserts a bounded behavior on top of another behavior.+--+-- @+-- 'trim' = 'splice' 'mempty'+-- @+--+-- (Named after the analogous tape-editing technique.)+--+splice :: Behavior a -> Bound (Behavior a) -> Behavior a+splice constant insert = fmap fromLast $ fmap toLast constant <> trim (fmap (fmap toLast) insert)+ where+ toLast = Option . Just . Last+ fromLast = getLast . fromJust . getOption+ -- fromJust is safe here, as toLast is used to create the Maybe wrapper+++concatSegment :: Monoid a => Note (Segment a) -> Behavior a+concatSegment = trim . view bounded++-- |+-- Concatenate a score of (possibly overlapping) segments.+--+-- See also 'concatB' and 'continous'.+--+concatS :: Monoid a => Score (Segment a) -> Behavior a+concatS = mconcat . map concatSegment . view notes+-- Or: mconcat.fmap trim.toListOf (notes.each.bounded)++-- |+-- Concatenate a score of (possibly overlapping) segments.+--+-- See also 'concatSegment' and 'continous'.+--+concatB :: Monoid a => Score (Behavior a) -> Behavior a+concatB = concatS . fmap (view focusing)+-- Or (more generally): mconcat.toListOf (notes.each.noteValue)+++-- |+-- View part of a 'Behavior' as a 'Segment'.+--+-- @+-- 'line' & 'focusing' ``onSpan`` (2 '<->' 3) '*~' 0+-- @+--+focusing :: Lens' (Behavior a) (Segment a)+focusing = lens get set+ where+ get = view (from bounded . noteValue) . {-pure-}bounding mempty+ set x = splice x . (view bounded) . pure++
− src/Music/Time/Span.hs
@@ -1,190 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE ViewPatterns #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides time spans.-------------------------------------------------------------------------------------------module Music.Time.Span (- Span,- -- inSpan,-- -- ** Constructing spans- (<->),- (>->),- era,-- -- ** Deconstructing spans- -- _range,- -- _delta,- range,- delta,- -- mapRange,- -- mapDelta,-- -- ** Span as transformation- sunit,- sapp,- sunder,- sinvert,- ) where--import Control.Arrow-import Control.Lens-import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Semigroup-import Data.VectorSpace--import Music.Time.Delayable-import Music.Time.Onset-import Music.Time.Reverse-import Music.Time.Stretchable-import Music.Time.Time---- |--- A 'Span' represents two points in time @u@ and @v@ where @t <= u@ or, equivalently,--- a time @t@ and a duration @d@ where @d >= 0@.------ A third way of looking at 'Span' is that it represents a time transformation where--- onset is translation and duration is scaling.----newtype Span = Span (Time, Duration)- deriving (Eq, Ord, Show)---- normalizeSpan :: Span -> Span--- normalizeSpan (Span (t,d))- -- | d >= 0 = t <-> (t .+^ d)- -- | d < 0 = (t .+^ d) <-> t--{-- "Applying a transformation t to a Located a results in the transformation being- applied to the location, and the linear portion of t being applied to the value of- type a (i.e. it is not translated)."-- -- Diagrams Haddocks--}-instance Delayable Span where- delay n = delta %~ first (delay n)--instance Stretchable Span where- stretch n = delta %~ (stretch n *** stretch n)---instance HasOnset Span where- onset = fst . _range--instance HasOffset Span where- offset = snd . _range--instance HasDuration Span where- duration = snd . _delta--instance Semigroup Span where- -- Span (t1, d1) <> Span (t2, d2) = Span (t1 `delayTime` (d1 `stretch` t2), d1 `stretch` d2)- (<>) = sapp---instance Monoid Span where- mempty = start <-> stop- mappend = (<>)--inSpan f = Span . f . _delta---- |--- The default span, i.e. 'start' '<->' 'stop'.----sunit :: Span-sunit = mempty---- | @t \<-\> u@ represents the span between @t@ and @u@.-(<->) :: Time -> Time -> Span-(<->) t u = t >-> (u .-. t)---- | @t >-> d@ represents the span between @t@ and @t .+^ d@.-(>->) :: Time -> Duration -> Span-t >-> d = Span (t,d)- -- | d > 0 = Span (t,d)- -- | otherwise = error "Invalid span"---- | Get the era (onset to offset) of a given value.-era :: (HasOnset a, HasOffset a) => a -> Span-era x = onset x <-> offset x--_delta :: Span -> (Time, Duration)-_delta (Span x) = x--_range :: Span -> (Time, Time)-_range x = let (t, d) = _delta x- in (t, t .+^ d)---- |--- View a span as onset and offset.------ Typically used with the @ViewPatterns@ extension, as in------ > foo (view range -> (u,v)) = ...----range :: Iso' Span (Time, Time)-range = iso _range $ uncurry (<->)---- |--- View a span as a time and duration.------ Typically used with the @ViewPatterns@ extension, as in------ > foo (view delta -> (t,d)) = ...----delta :: Iso' Span (Time, Duration)-delta = iso _delta $ uncurry (>->)---- | Apply a span transformation.-sapp :: (Delayable a, Stretchable a) => Span -> a -> a-sapp (view delta -> (t,d)) = delayTime t . stretch d---- | Apply a function under a span transformation.-sunder :: (Delayable a, Stretchable a, Delayable b, Stretchable b) => Span -> (a -> b) -> a -> b--- sunder s f = sapp (sinvert s) . f . sapp s--sunder s f = sappInv s . f . sapp s-sappInv (view delta -> (t,d)) = stretch (recip d) . delayTime (mirror t)---- | The inversion of a span.------ > sinvert (sinvert s) = s--- > sapp (sinvert s) . sapp s = id----sinvert :: Span -> Span-sinvert (Span (t,d)) = Span (mirror t, recip d)---- TODO add "individual scaling" component, i.e. scale just duration not both time and duration--- Useful for implementing separation in articulation etc--deriving instance Delayable a => Delayable (NoStretch a)-deriving instance HasOnset a => HasOnset (NoStretch a)-deriving instance HasOffset a => HasOffset (NoStretch a)-deriving instance HasDuration a => HasDuration (NoStretch a)--deriving instance Stretchable a => Stretchable (NoDelay a)-deriving instance HasOnset a => HasOnset (NoDelay a)-deriving instance HasOffset a => HasOffset (NoDelay a)-deriving instance HasDuration a => HasDuration (NoDelay a)--
+ src/Music/Time/Split.hs view
@@ -0,0 +1,120 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Split (++ module Music.Time.Position,++ -- * The Splittable class+ Splittable(..),+ splitAbs,+ + chunks,++ ) where++import Music.Time.Position+import Music.Time.Internal.Util++import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Functor.Adjunction (unzipR)+import Data.Functor.Rep+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Semigroup hiding ()+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.VectorSpace hiding (Sum (..))++-- |+-- Class of values that can be split.+--+-- For non-positioned values such as 'Stretched', split cuts a value into pieces+-- of the given duration and the rest.+--+-- For positioned values succh as 'Note', split cuts a value relative to its onset.+-- To split at an absolute position, see 'splitAbs'.+--+--+-- Law+--+-- @+-- '_duration' ('beginning' t x) + '_duration' ('ending' t x) = '_duration' x+-- '_duration' ('beginning' t x) = t ``min`` '_duration' x iff t >= 0+-- '_duration' ('ending' t x) = '_duration' x - (t ``min`` '_duration' x) iff t >= 0+-- @+--+-- (Note that any of these three laws can be derived from the other two, so it is+-- sufficient to prove two!).+--+class Splittable a where+ split :: Duration -> a -> (a, a)+ beginning :: Duration -> a -> a+ ending :: Duration -> a -> a+ split d x = (beginning d x, ending d x)+ beginning d = fst . split d+ ending d = snd . split d+-- TODO rename beginning/ending to fstSplit/sndSplit or similar++instance Splittable Duration where+ -- Directly from the laws+ -- Guard against t < 0+ split t x = (t' `min` x, x ^-^ (t' `min` x))+ where t' = t `max` 0++instance Splittable Span where+ -- Splitting a span splits the duration+ split pos (view delta -> (t, d)) = (t >-> d1, (t .+^ d1) >-> d2)+ where (d1, d2) = split pos d++instance (Ord k, Splittable a) => Splittable (Map k a) where+ split d = unzipR . Map.map (split d)+++-- takeMWhile :: (Monoid a, HasDuration a, Splittable a) => Duration -> (a -> Bool) -> a -> a+-- takeMWhile d p xs = if _duration xs <= 0 then mempty else takeMWhile' d p xs+-- where+-- takeMWhile' d p (split d -> (x, xs)) = if p x then x `mappend` takeMWhile d p xs else mempty++chunks :: (Splittable a, HasDuration a) => Duration -> a -> [a]+chunks d xs = if _duration xs <= 0 then [] else chunks' d xs+ where+ chunks' d (split d -> (x, xs)) = [x] ++ chunks d xs+++splitAbs :: (HasPosition a, Splittable a) => Time -> a -> (a, a)+splitAbs t x = split (t .-. _onset x) x++
− src/Music/Time/Stretchable.hs
@@ -1,110 +0,0 @@--{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DeriveFoldable #-}-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)------ Provides stretchable values.-------------------------------------------------------------------------------------------module Music.Time.Stretchable (- -- * Stretchable class- Stretchable(..),- compress,- stretching,-- -- ** Utility- NoStretch(..),- ) where--import Control.Arrow--import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Map (Map)-import qualified Data.Map as Map-import Data.Semigroup-import Data.Set (Set)-import qualified Data.Set as Set-import Data.VectorSpace hiding (Sum)--import Music.Time.Time---- |--- Stretchable values.----class Stretchable a where-- -- |- -- Stretch (augment) a value by the given factor.- --- stretch :: Duration -> a -> a- stretch _ = id--instance Stretchable Time where- stretch n = (n*.)--instance Stretchable Duration where- stretch n = (n*^)--instance Stretchable (Time, a) where- stretch n (t, a) = (n `stretch` t, a)--instance Stretchable (Duration, a) where- stretch n (d, a) = (n `stretch` d, a)--instance Stretchable (Time, Duration, a) where- stretch n (t, d, a) = (n `stretch` t, n `stretch` d, a)--instance Stretchable (Time -> a) where- stretch n = (. relative origin (^/ n))--instance Stretchable (Duration -> a) where- stretch n = (. (^/ n))--instance Stretchable a => Stretchable [a] where- stretch n = fmap (stretch n)--instance Stretchable a => Stretchable (Map k a) where- stretch n = fmap (stretch n)--instance Stretchable a => Stretchable (Product a) where- stretch n (Product x) = Product (stretch n x)--instance Stretchable a => Stretchable (Sum a) where- stretch n (Sum x) = Sum (stretch n x)----- |--- Compress (diminish) a score. Flipped version of 'stretch'.----compress :: Stretchable a => Duration -> a -> a-compress x = stretch (recip x)---- | Apply a function under stretch.--- See also 'sunder'.-stretching :: (Stretchable a, Stretchable b) => Duration -> (a -> b) -> a -> b-stretching t f = compress t . f . stretch t---newtype NoStretch a = NoStretch { getNoStretch :: a }- deriving (Eq, Ord, Enum, Show, Semigroup, Monoid- {-Delayable, HasOnset, HasOffset, HasDuration-})--instance Stretchable (NoStretch a) where- stretch _ = id
+ src/Music/Time/Stretched.hs view
@@ -0,0 +1,131 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Stretched (++ -- * Stretched values+ Stretched,++ -- * Construction+ stretched,+ stretchedValue, ++ ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Reverse+import Music.Time.Split++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Comonad+import Control.Comonad.Env+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import Data.PairMonad+import Data.Typeable++-- |+-- A 'Stretched' value has a known 'duration', but no 'position'.+--+-- Placing a value inside 'Stretched' makes it invariante under 'delay'.+--+-- The semantics are given by+--+-- @+-- type Stretched = (Duration, a)+-- @+--+newtype Stretched a = Stretched { _stretchedValue :: (Duration, a) }+ deriving (Eq, {-Ord, -}{-Show, -}+ Applicative, Monad, {-Comonad, -}+ Functor, Foldable, Traversable)++-- >>> stretch 2 $ (5,1)^.stretched+-- (10,1)^.stretched+--+-- >>> delay 2 $ (5,1)^.stretched+-- (5,1)^.stretched+--++deriving instance Typeable1 Stretched++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Stretched a) where+ type Unwrapped (Stretched a) = (Duration, a)+ _Wrapped' = iso _stretchedValue Stretched++instance Rewrapped (Stretched a) (Stretched b)++instance Transformable (Stretched a) where+ transform t = over _Wrapped $ first (transform t)++instance HasDuration (Stretched a) where+ _duration = _duration . ask . view _Wrapped++instance Reversible (Stretched a) where+ rev = stretch (-1)++instance Splittable a => Splittable (Stretched a) where+ beginning d = over _Wrapped $ \(s, v) -> (beginning d s, beginning d v)+ ending d = over _Wrapped $ \(s, v) -> (ending d s, ending d v)++deriving instance Show a => Show (Stretched a)++-- |+-- View a stretched value as a pair of the original value and a stretch factor.+--+stretched :: Iso (Duration, a) (Duration, b) (Stretched a) (Stretched b)+stretched = _Unwrapped+++-- |+-- View a stretched value as a pair of the original value and the transformation (and vice versa).+--+stretchedValue :: (Transformable a, Transformable b) => Lens (Stretched a) (Stretched b) a b+stretchedValue = lens runStretched (flip $ _stretched . const)+ where+ _stretched f (Stretched (d,x)) = Stretched (d, f `whilst` stretching d $ x)+{-# INLINE stretchedValue #-}++runStretched :: Transformable a => Stretched a -> a+runStretched = uncurry stretch . view _Wrapped
− src/Music/Time/Time.hs
@@ -1,113 +0,0 @@--{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE TypeFamilies #-}------------------------------------------------------------------------------------------ |--- Copyright : (c) Hans Hoglund 2012------ License : BSD-style------ Maintainer : hans@hanshoglund.se--- Stability : experimental--- Portability : non-portable (TF,GNTD)-------------------------------------------------------------------------------------------module Music.Time.Time (- -- $convert-- -- * Time type- Time(..),-- -- * Duration type- Duration,-- -- * Behavior class- HasBehavior(..),-- -- ** Identities- start,- stop,- unit,-- -- ** Combinators- -- TODO- ) where--import Data.AffineSpace-import Data.AffineSpace.Point-import Data.Semigroup-import Data.VectorSpace--import Music.Score.Util (showRatio)---- $convert------ Note that you should use '.-.' and '.+^' to convert between time and--- duration. To refer to time zero (the beginning of the music), use--- 'origin'.------- |--- This type represents relative time in seconds.----newtype Duration = Duration { getDuration :: Rational }- deriving (Eq, Ord, Num, Enum, Fractional, Real, RealFrac, AdditiveGroup)--instance Show Duration where- show = showRatio . getDuration--instance VectorSpace Duration where- type Scalar Duration = Duration- (Duration x) *^ (Duration y) = Duration (x *^ y)---- |--- The unit duration.----unit :: Duration-unit = 1 -- TODO some overloaded unit value---- |--- This type represents absolute time in seconds since 'start'. Note that time can be--- negative, representing events occuring before the start time.------ Time forms an affine space with durations as the underlying vector space,--- that is, we can add a time to a duration to get a new time using '.+^',--- take the difference of two times to get a duration using '.-.'.----type Time = Point Duration--instance Num Time where- (+) = relative2 origin (+)- (*) = relative2 origin (*)- negate = mirror- abs = relative origin abs- signum = relative origin signum- fromInteger = (origin .+^) . fromInteger-instance Fractional Time where- recip = relative origin recip- fromRational = (origin .+^) . fromRational---- |--- The global start time, which usually means the the beginning of the musical performance.------ This is a synonym for 'origin'.----start :: Time-start = origin---- |--- The global end time, defined as @start .+^ unit@.----stop :: Time-stop = origin .+^ unit---class HasBehavior f where- (?) :: f a -> Time -> a--instance HasBehavior ((->) Time) where- (?) = id-
+ src/Music/Time/Track.hs view
@@ -0,0 +1,155 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Track (++ -- * Track type++ Track,++ -- * Construction+ track,+ delayeds,+ singleDelayed, ++ ) where+++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace++import Music.Time.Delayed+import Music.Time.Reverse+import Music.Time.Split++import Control.Applicative+import Control.Arrow (first, second, (&&&), (***))+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Control.Monad+import Control.Monad.Compose+import Control.Monad.Plus+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import Data.Traversable (Traversable)+import qualified Data.Traversable as T+import Data.Typeable+import Music.Time.Internal.Util++-- |+-- A 'Track' is a parallel composition of values.+--+-- @+-- type Track a = [Delayed a]+-- @+--+newtype Track a = Track { getTrack :: TrackList (TrackEv a) }+ deriving (Functor, Foldable, Traversable, Semigroup, Monoid, Typeable, Show, Eq)++-- A track is a list of events with explicit onset.+--+-- Track is a 'Monoid' under parallel composition. 'mempty' is the empty track+-- and 'mappend' interleaves values.+--+-- Track is a 'Monad'. 'return' creates a track containing a single value at time+-- zero, and '>>=' transforms the values of a track, allowing the addition and+-- removal of values relative to the time of the value. Perhaps more intuitively,+-- 'join' delays each inner track to start at the offset of an outer track, then+-- removes the intermediate structure.++-- Can use [] or Seq here+type TrackList = []++-- Can use any type as long as trackEv provides an Iso+type TrackEv a = Delayed a++trackEv :: Iso (Delayed a) (Delayed b) (TrackEv a) (TrackEv b)+trackEv = id++instance Applicative Track where+ pure = return+ (<*>) = ap++instance Alternative Track where+ (<|>) = (<>)+ empty = mempty++instance Monad Track where+ return = view _Unwrapped . return . return+ xs >>= f = view _Unwrapped $ (view _Wrapped . f) `mbind` view _Wrapped xs++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Track a) where+ type Unwrapped (Track a) = (TrackList (TrackEv a))+ _Wrapped' = iso getTrack Track++instance Rewrapped (Track a) (Track b)++instance Transformable (Track a) where+ transform s = over _Wrapped' (transform s)++instance HasDuration (Track a) where+ _duration = Foldable.sum . fmap _duration . view _Wrapped'++instance Splittable a => Splittable (Track a) where+ -- TODO++instance Reversible a => Reversible (Track a) where+ rev = over _Wrapped' (fmap rev) -- TODO OK?+++-- |+-- Create a track from a list of notes.+--+-- Se also 'delayeds'.+--+track :: Getter [Delayed a] (Track a)+track = from unsafeTrack+{-# INLINE track #-}++delayeds :: Lens (Track a) (Track b) [Delayed a] [Delayed b]+delayeds = unsafeTrack++singleDelayed :: Prism' (Track a) (Delayed a)+singleDelayed = unsafeTrack . single+{-# INLINE singleDelayed #-}++unsafeTrack :: Iso (Track a) (Track b) [Delayed a] [Delayed b]+unsafeTrack = _Wrapped+{-# INLINE unsafeTrack #-}+
+ src/Music/Time/Transform.hs view
@@ -0,0 +1,66 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Transform (++ module Music.Time.Types,++ -- * The Transformable class+ Transformable(..),+ itransform,+ transformed,++ -- * Apply under a transformation+ whilst,+ spanned,+ onSpan,+ -- conjugateS,++ -- * Specific transformations+ -- ** Transformations+ delay,+ undelay,+ stretch,+ compress,++ -- ** Transforming values+ delaying,+ undelaying,+ stretching,+ compressing,++ -- * Utility+ delayTime,++ ) where++import Music.Time.Internal.Transform+import Music.Time.Types+
+ src/Music/Time/Types.hs view
@@ -0,0 +1,421 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Types (++ -- * Duration+ Duration,+ toDuration,+ fromDuration,++ -- * Time points+ Time,+ toTime,+ fromTime,++ -- $convert++ -- * Time spans+ Span,+ -- *** Creating spans+ (<->),+ (>->),+ (<-<),+ -- *** Accessing spans+ range,+ delta,+ showRange,+ showDelta,++ -- ** Points in spans+ -- TODO move+ isProper,+ isBefore,+ inside,+ encloses,+ overlaps,+ -- union+ -- intersection (alt name 'overlap')+ -- difference (would actually become a split)++ -- TODO Abjad terminology: contains/curtails/delays/intersects/isCongruentTo++ -- ** Properties+ delayOnly,+ stretchOnly,+ -- Proper spans are always bounded and closed+ ) where++import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Semigroup+import Data.Typeable+import Data.VectorSpace++import Music.Time.Internal.Util (showRatio)+++-- $convert+--+-- Note that you should use '.-.' and '.+^' to convert between time and+-- duration. To refer to time zero (the beginning of the music), use+-- 'origin'.+--++-- |+-- Internal time representation. Can be anything with instances+-- for 'Fractional' and 'RealFrac'.+--+type TimeBase = Rational+-- type TimeBase = Fixed E12++-- instance HasResolution a => AdditiveGroup (Fixed a) where+-- zeroV = 0+-- negateV = negate+-- (^+^) = (+)++-- Can be enabled for experimental time representation+-- deriving instance Floating Time+-- deriving instance Floating Duration+++-- |+-- Duration, corresponding to note values in standard notation.+-- The standard names can be used: @1\/2@ for half note @1\/4@ for a quarter note and so on.+--+-- Duration is a one-dimensional 'VectorSpace', and is the associated vector space of time points.+-- It is a also an 'AdditiveGroup' (and hence also 'Monoid' and 'Semigroup') under addition.+--+-- 'Duration' is invariant under translation so 'delay' has no effect on it.+--+-- The semantics are given by+--+-- @+-- type Duration = R+-- @+--+newtype Duration = Duration { getDuration :: TimeBase }+ deriving (Eq, Ord, Num, Enum, Fractional, Real, RealFrac, Typeable)++instance Show Duration where+ show = showRatio . realToFrac++instance InnerSpace Duration where+ (<.>) = (*)++instance AdditiveGroup Duration where+ zeroV = 0+ (^+^) = (+)+ negateV = negate++instance VectorSpace Duration where+ type Scalar Duration = Duration+ (*^) = (*)++instance Semigroup Duration where+ (<>) = (*^)++instance Monoid Duration where+ mempty = 1+ mappend = (*^)+ -- TODO use some notion of norm rather than 1++-- |+-- Convert a value to a duration.+--+toDuration :: Real a => a -> Duration+toDuration = realToFrac++-- |+-- Convert a value to a duration.+--+fromDuration :: Fractional a => Duration -> a+fromDuration = realToFrac+++-- |+-- Time points, representing duration since some known reference time, typically the start+-- of the music. Note that time can be negative, representing values occuring before the+-- reference time.+--+-- Time forms an affine space with durations as the underlying vector space, that is, we+-- can add a time to a duration to get a new time using '.+^', take the difference of two+-- times to get a duration using '.-.'. 'Time' forms an 'AffineSpace' with 'Duration' as+-- difference space.+--+-- The semantics are given by+--+-- @+-- type Time = R+-- @+--+newtype Time = Time { getTime :: TimeBase }+ deriving (Eq, Ord, Num, Enum, Fractional, Real, RealFrac, Typeable)++instance Show Time where+ show = showRatio . realToFrac++deriving instance AdditiveGroup Time++instance VectorSpace Time where+ type Scalar Time = Duration+ Duration x *^ Time y = Time (x * y)++instance AffineSpace Time where+ type Diff Time = Duration+ Time x .-. Time y = Duration (x - y)+ Time x .+^ Duration y = Time (x + y)++instance Semigroup Time where+ (<>) = (^+^)++instance Monoid Time where+ mempty = zeroV+ mappend = (^+^)+ mconcat = sumV++-- |+-- Convert a value to a duration.+--+toTime :: Real a => a -> Time+toTime = realToFrac++-- |+-- Convert a value to a duration.+--+fromTime :: Fractional a => Time -> a+fromTime = realToFrac+++++-- |+-- A 'Span' represents an onset and offset in time (or equivalently: an onset and a+-- duration, /or/ a duration and an offset, /or/ a duration and a middle point).+--+-- Pattern matching over span is possible (with @ViewPatterns@):+--+-- @+-- foo ('view' 'range' -> (t1, t2)) = ...+-- foo ('view' 'delta' -> (t, d)) = ...+-- @+--+-- Another way of looking at 'Span' is that it represents a time transformation where+-- onset is translation and duration is scaling. TODO see 'transform' and 'whilst'+--+-- The semantics are given by+--+-- @+-- type Span = Time x Time+-- @+--+newtype Span = Delta { _delta :: (Time, Duration) }+ deriving (Eq, Ord, Typeable)++-- You can create a span using the '<->' and '>->' constructors. Note that:+--+-- > t <-> u = t >-> (u .-. t)+-- > t >-> d = t <-> t .+^ d+--+-- To create and destruct a span (in any of its incarnations), use the provided isomorphisms:+--+-- 'Span' is a 'Semigroup', 'Monoid' and 'AdditiveGroup':+--+-- - To convert a span to a pair, use @s^.'range'@.+--+-- - To construct a span from a pair, use @(t, u)^.'from' 'range'@.+--++--+-- $musicTimeSpanIsos+--+-- >>> (2 <-> 3)^.range+-- > (2, 3)+-- >+-- >>> hs> (2 <-> 3)^.delta+-- > (2, 1)+-- >+-- >>> hs> (10 >-> 5)^.range+-- > (10, 15)+-- >+-- >>> hs> (10 >-> 5)^.delta+-- > (10, 5)+--++instance Show Span where+ -- show = showDelta+ show = showRange+ -- Which form should we use?++-- |+-- 'zeroV' or 'mempty' represents the /unit interval/ @0 \<-\> 1@, which also happens to+-- be the identity transformation.+--+instance Semigroup Span where+ (<>) = (^+^)++-- |+-- '<>' or '^+^' composes transformations, i.e. both time and duration is stretched,+-- and then time is added.+--+instance Monoid Span where+ mempty = zeroV+ mappend = (^+^)++-- |+-- 'negateV' returns the inverse of a given transformation.+--+instance AdditiveGroup Span where+ zeroV = 0 <-> 1+ Delta (t1, d1) ^+^ Delta (t2, d2) = Delta (t1 ^+^ d1 *^ t2, d1*d2)+ negateV (Delta (t, d)) = Delta (-t ^/ d, recip d)++--+-- a >-> b = a <-> (a .+^ b)+-- a <-< b = (b .-^ a) <-> b+-- a <-> b = a >-> (b .-. a)+-- (b .-^ a) <-> b = a <-< b+--++-- |+-- @t \<-\> u@ represents the span between @t@ and @u@.+--+(<->) :: Time -> Time -> Span+t <-> u = t >-> (u .-. t)++-- |+-- @t >-> d@ represents the span between @t@ and @t .+^ d@.+--+(>->) :: Time -> Duration -> Span+(>->) = curry Delta++-- |+-- @d \<-\> t@ represents the span between @t .-^ d@ and @t@.+--+(<-<) :: Duration -> Time -> Span+a <-< b = (b .-^ a) <-> b+++-- > (<->) = curry $ view $ from range+-- > (>->) = curry $ view $ from delta++-- |+-- View a span as pair of onset and offset.+--+range :: Iso' Span (Time, Time)+range = iso _range $ uncurry (<->)+ where+ _range x = let (t, d) = _delta x in (t, t .+^ d)++-- |+-- View a span as a pair of onset and duration.+--+delta :: Iso' Span (Time, Duration)+delta = iso _delta Delta++-- |+-- Show a span in range notation, i.e. @t1 \<-\> t2@.+--+showRange :: Span -> String+showRange (view range -> (t,u)) = show t ++ " <-> " ++ show u++-- |+-- Show a span in range notation, i.e. @t >-> d@.+--+showDelta :: Span -> String+showDelta (view delta -> (t,d)) = show t ++ " >-> " ++ show d++-- |+-- A prism to the subset of 'Span' that performs a delay but no stretch.+--+-- To access the delay component in any span, use @'view' ('delta' . e'_1')@+--+delayOnly :: Prism' Span Time+delayOnly = prism' (\t -> view (from delta) (t, 1)) $ \x -> case view delta x of+ (t, 1) -> Just t+ _ -> Nothing+++-- |+-- A prism to the subset of 'Span' that performs a stretch but no delay.+--+-- To access the stretch component in any span, use @'view' ('delta' . '_2')@+--+stretchOnly :: Prism' Span Duration+stretchOnly = prism' (\d -> view (from delta) (0, d)) $ \x -> case view delta x of+ (0, d) -> Just d+ _ -> Nothing+++-- Same as (onset, offset), defined here for bootstrapping reasons+startTime (view range -> (t1, t2)) = t1+stopTime (view range -> (t1, t2)) = t2+++-- |+-- Whether this is a proper span, i.e. whether @'_onset' x '<' 'stopTime' x@.+--+isProper :: Span -> Bool+isProper (view range -> (t, u)) = t < u++-- |+-- Whether the given point falls inside the given span (inclusively).+--+-- Designed to be used infix, for example+--+-- @+-- 0.5 ``inside`` (1 '<->' 2)+-- @+--+inside :: Time -> Span -> Bool+inside x (view range -> (t, u)) = t <= x && x <= u++-- |+-- Whether the first given span encloses the second span.+--+encloses :: Span -> Span -> Bool+a `encloses` b = startTime b `inside` a && stopTime b `inside` a++-- |+-- Whether the given span overlaps.+--+overlaps :: Span -> Span -> Bool+a `overlaps` b = not (a `isBefore` b) && not (b `isBefore` a)++-- |+-- Whether the first given span occurs before the second span.+--+isBefore :: Span -> Span -> Bool+a `isBefore` b = (startTime a `max` stopTime a) <= (startTime b `min` stopTime b)+
+ src/Music/Time/Voice.hs view
@@ -0,0 +1,460 @@++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ViewPatterns #-}++-------------------------------------------------------------------------------------+-- |+-- Copyright : (c) Hans Hoglund 2012-2014+--+-- License : BSD-style+--+-- Maintainer : hans@hanshoglund.se+-- Stability : experimental+-- Portability : non-portable (TF,GNTD)+--+-------------------------------------------------------------------------------------++module Music.Time.Voice (++ -- * Voice type+ Voice,++ -- * Construction+ voice,++ -- ** Extracting values+ stretcheds,+ eventsV,++ -- ** Pattern matching+ singleStretched,++ -- *** Unsafe versions+ unsafeStretcheds,+ unsafeEventsV,+++ -- * Fusion+ fuse,+ fuseBy,++ -- * Traversing+ -- ** Separating rhythms and values+ withValues,+ withDurations,+ rotateValues,+ rotateDurations,+ reverseValues,+ reverseDurations,++ -- ** Zips+ unzipVoice,+ zipVoice,+ zipVoiceWith,+ zipVoiceWith',+ zipVoiceWithNoScale,++ -- * Context+ withContext,++ -- headV,+ -- middleV,+ -- lastV, ++ ) where++import Data.AffineSpace+import Data.AffineSpace.Point+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Ratio+import Data.Semigroup+import Data.Set (Set)+import qualified Data.Set as Set+import Data.VectorSpace+import Data.Functor.Adjunction (unzipR)++import Music.Time.Reverse+import Music.Time.Split+import Music.Time.Stretched++import Control.Applicative+import Control.Lens hiding (Indexable, Level, above, below,+ index, inside, parts, reversed,+ transform, (<|), (|>))+import Control.Monad+import Control.Monad.Compose+import Control.Monad.Plus+import Data.Foldable (Foldable)+import qualified Data.Foldable as Foldable+import qualified Data.List+import Data.List.NonEmpty (NonEmpty)+import Data.Traversable (Traversable)+import qualified Data.Traversable as T+import Data.Typeable+import Music.Dynamics.Literal+import Music.Pitch.Literal+import Music.Time.Internal.Util++-- |+-- A 'Voice' is a sequential composition of values. Events may not overlap.+--+-- @+-- type Voice a = [Stretched a]+-- @+--+newtype Voice a = Voice { getVoice :: VoiceList (VoiceEv a) }+ deriving (Functor, Foldable, Traversable, Semigroup, Monoid, Typeable, Show, Eq)++-- A voice is a list of events with explicit duration. Events can not overlap.+--+-- Voice is a 'Monoid' under sequential composition. 'mempty' is the empty part and 'mappend'+-- appends parts.+--+-- Voice is a 'Monad'. 'return' creates a part containing a single value of duration+-- one, and '>>=' transforms the values of a part, allowing the addition and+-- removal of values under relative duration. Perhaps more intuitively, 'join' scales+-- each inner part to the duration of the outer part, then removes the+-- intermediate structure.++-- Can use [] or Seq here+type VoiceList = []++-- Can use any type as long as voiceEv provides an Iso+type VoiceEv a = Stretched a++voiceEv :: Iso (Stretched a) (Stretched b) (VoiceEv a) (VoiceEv b)+voiceEv = id++instance Applicative Voice where+ pure = return+ (<*>) = ap++instance Alternative Voice where+ (<|>) = (<>)+ empty = mempty++instance Monad Voice where+ return = view _Unwrapped . return . return+ xs >>= f = view _Unwrapped $ (view _Wrapped . f) `mbind` view _Wrapped xs++-- | Unsafe: Do not use 'Wrapped' instances+instance Wrapped (Voice a) where+ type Unwrapped (Voice a) = (VoiceList (VoiceEv a))+ _Wrapped' = iso getVoice Voice++instance Rewrapped (Voice a) (Voice b)++instance Transformable (Voice a) where+ transform s = over _Wrapped' (transform s)++instance HasDuration (Voice a) where+ _duration = Foldable.sum . fmap _duration . view _Wrapped'++-- TODO move+instance Splittable () where+ split _ x = (x,x)++instance Splittable a => Splittable (Voice a) where+ split t x+ | t <= 0 = (mempty, x)+ | t >= _duration x = (x, mempty)+ | otherwise = let (a,b) = split' t {-split-} (x^._Wrapped) in (a^._Unwrapped, b^._Unwrapped)+ where+ split' = error "TODO"++ -- split' :: Time -> (Duration -> a -> (a,a)) -> [Stretched a] -> ([Stretched a], [Stretched a])+ -- split' t f = over both mcatMaybes . unzipR . snd . Data.List.mapAccumL go (0::Time)+ -- where+ -- go :: Time -> Stretched a -> (Time, (Maybe (Stretched a), Maybe (Stretched a)))+ -- go currentOnset (view (from stretched) -> (d,x)) + -- = let currentOffset = currentOnset .+^ d+ -- theSplit = if t <= currentOnset then (Nothing, Just (view stretched (d,x))) else+ -- if currentOnset < t && t < currentOffset then + -- let x1 = x+ -- x2 = x in+ -- (Just (view stretched (t .-^ currentOnset,x1)), Just (view stretched (d ^-^ (t .-^ currentOnset),x2))) else+ -- if currentOffset <= t then (Just (view stretched (d,x)), Nothing) else error "Impossible"+ -- in (currentOnset .+^ d, theSplit) ++instance Reversible a => Reversible (Voice a) where+ rev = over _Wrapped' (fmap rev) -- TODO OK?+++-- Lifted instances++instance IsPitch a => IsPitch (Voice a) where+ fromPitch = pure . fromPitch++instance IsInterval a => IsInterval (Voice a) where+ fromInterval = pure . fromInterval++instance IsDynamics a => IsDynamics (Voice a) where+ fromDynamics = pure . fromDynamics++-- Bogus instance, so we can use [c..g] expressions+instance Enum a => Enum (Voice a) where+ toEnum = return . toEnum+ fromEnum = list 0 (fromEnum . head) . Foldable.toList++-- Bogus instance, so we can use numeric literals+instance Num a => Num (Voice a) where+ fromInteger = return . fromInteger+ abs = fmap abs+ signum = fmap signum+ (+) = error "Not implemented"+ (-) = error "Not implemented"+ (*) = error "Not implemented"++-- Bogus instances, so we can use c^*2 etc.+instance AdditiveGroup (Voice a) where+ zeroV = error "Not implemented"+ (^+^) = error "Not implemented"+ negateV = error "Not implemented"++instance VectorSpace (Voice a) where+ type Scalar (Voice a) = Duration+ d *^ s = d `stretch` s+++-- |+-- Create a 'Voice' from a list of 'Stretched' values.+--+-- This is a 'Getter' (rather than a function) for consistency:+--+-- @+-- [ (0 '<->' 1, 10)^.'stretched',+-- (1 '<->' 2, 20)^.'stretched',+-- (3 '<->' 4, 30)^.'stretched' ]^.'voice'+-- @+--+-- @+-- 'view' 'voice' $ 'map' ('view' 'stretched') [(0 '<->' 1, 1)]+-- @+--+-- Se also 'stretcheds'.+--+voice :: Getter [Stretched a] (Voice a)+voice = from unsafeStretcheds+-- voice = to $ flip (set stretcheds) empty+{-# INLINE voice #-}++-- |+-- View a 'Voice' as a list of 'Stretched' values.+--+-- @+-- 'view' 'stretcheds' :: 'Voice' a -> ['Stretched' a]+-- 'set' 'stretcheds' :: ['Stretched' a] -> 'Voice' a -> 'Voice' a+-- 'over' 'stretcheds' :: (['Stretched' a] -> ['Stretched' b]) -> 'Voice' a -> 'Voice' b+-- @+--+-- @+-- 'preview' ('stretcheds' . 'each') :: 'Voice' a -> 'Maybe' ('Stretched' a)+-- 'preview' ('stretcheds' . 'element' 1) :: 'Voice' a -> 'Maybe' ('Stretched' a)+-- 'preview' ('stretcheds' . 'elements' odd) :: 'Voice' a -> 'Maybe' ('Stretched' a)+-- @+--+-- @+-- 'set' ('stretcheds' . 'each') :: 'Stretched' a -> 'Voice' a -> 'Voice' a+-- 'set' ('stretcheds' . 'element' 1) :: 'Stretched' a -> 'Voice' a -> 'Voice' a+-- 'set' ('stretcheds' . 'elements' odd) :: 'Stretched' a -> 'Voice' a -> 'Voice' a+-- @+--+-- @+-- 'over' ('stretcheds' . 'each') :: ('Stretched' a -> 'Stretched' b) -> 'Voice' a -> 'Voice' b+-- 'over' ('stretcheds' . 'element' 1) :: ('Stretched' a -> 'Stretched' a) -> 'Voice' a -> 'Voice' a+-- 'over' ('stretcheds' . 'elements' odd) :: ('Stretched' a -> 'Stretched' a) -> 'Voice' a -> 'Voice' a+-- @+--+-- @+-- 'toListOf' ('stretcheds' . 'each') :: 'Voice' a -> ['Stretched' a]+-- 'toListOf' ('stretcheds' . 'elements' odd) :: 'Voice' a -> ['Stretched' a]+-- 'toListOf' ('stretcheds' . 'each' . 'filtered'+-- (\\x -> '_duration' x \< 2)) :: 'Voice' a -> ['Stretched' a]+-- @+--+-- This is not an 'Iso', as the note list representation does not contain meta-data.+-- To construct a score from a note list, use 'score' or @'flip' ('set' 'stretcheds') 'empty'@.+--+stretcheds :: Lens (Voice a) (Voice b) [Stretched a] [Stretched b]+stretcheds = unsafeStretcheds+{-# INLINE stretcheds #-}++eventsV :: Lens (Voice a) (Voice b) [(Duration, a)] [(Duration, b)]+eventsV = unsafeEventsV+{-# INLINE eventsV #-}++unsafeEventsV :: Iso (Voice a) (Voice b) [(Duration, a)] [(Duration, b)]+unsafeEventsV = iso (map (^.from stretched) . (^.stretcheds)) ((^.voice) . map (^.stretched))+{-# INLINE unsafeEventsV #-}++unsafeStretcheds :: Iso (Voice a) (Voice b) [Stretched a] [Stretched b]+unsafeStretcheds = _Wrapped+{-# INLINE unsafeStretcheds #-}++singleStretched :: Prism' (Voice a) (Stretched a)+singleStretched = unsafeStretcheds . single+{-# INLINE singleStretched #-}+++-- |+-- Unzip the given voice. This is specialization of 'unzipR'. +--+unzipVoice :: Voice (a, b) -> (Voice a, Voice b)+unzipVoice = unzipR++-- |+-- Join the given voices by multiplying durations and pairing values.+--+zipVoice :: Voice a -> Voice b -> Voice (a, b)+zipVoice = zipVoiceWith (,)++-- |+-- Join the given voices by multiplying durations and combining values using the given function.+--+zipVoiceWith :: (a -> b -> c) -> Voice a -> Voice b -> Voice c+zipVoiceWith = zipVoiceWith' (*)++-- |+-- Join the given voices without combining durations.+--+zipVoiceWithNoScale :: (a -> b -> c) -> Voice a -> Voice b -> Voice c+zipVoiceWithNoScale f a b = zipVoiceWith' (\x y -> x) f a b++-- |+-- Join the given voices by combining durations and values using the given function.+--+zipVoiceWith' :: (Duration -> Duration -> Duration) -> (a -> b -> c) -> Voice a -> Voice b -> Voice c+zipVoiceWith' f g+ ((unzip.view eventsV) -> (ad, as))+ ((unzip.view eventsV) -> (bd, bs))+ = let cd = zipWith f ad bd+ cs = zipWith g as bs+ in view (from unsafeEventsV) (zip cd cs)++-- |+-- Merge consecutive equal notes.+--+fuse :: Eq a => Voice a -> Voice a+fuse = fuseBy (==)++-- |+-- Merge consecutive equal notes using the given function.+--+fuseBy :: (a -> a -> Bool) -> Voice a -> Voice a+fuseBy p = fuseBy' p head++-- |+-- Merge consecutive equal notes using the given equality predicate and merge function.+--+fuseBy' :: (a -> a -> Bool) -> ([a] -> a) -> Voice a -> Voice a+fuseBy' p g = over unsafeEventsV $ fmap foldNotes . Data.List.groupBy (inspectingBy snd p)+ where+ -- Add up durations and use a custom function to combine notes+ -- Typically, the combination function us just 'head', as we know that group returns+ -- non-empty lists of equal elements.+ foldNotes (unzip -> (ds, as)) = (sum ds, g as)+++withContext :: Voice a -> Voice (Maybe a, a, Maybe a)+withContext = over valuesV withPrevNext++--+-- TODO more elegant definition of durationsV and valuesV using indexed traversal or similar?+--++durationsV :: Lens' (Voice a) [Duration]+durationsV = lens getDurs (flip setDurs)+ where+ getDurs :: Voice a -> [Duration]+ getDurs = map fst . view eventsV++ setDurs :: [Duration] -> Voice a -> Voice a+ setDurs ds as = zipVoiceWith' (\a b -> a) (\a b -> b) (mconcat $ map durToVoice ds) as++ durToVoice d = stretch d $ pure ()++valuesV :: Lens (Voice a) (Voice b) [a] [b]+valuesV = lens getValues (flip setValues)+ where+ getValues :: Voice a -> [a]+ getValues = map snd . view eventsV++ setValues :: [a] -> Voice b -> Voice a+ setValues as bs = zipVoiceWith' (\a b -> b) (\a b -> a) (listToVoice as) bs++ listToVoice = mconcat . map pure++-- |+-- Transform the durations, leaving values intact.+withDurations :: ([Duration] -> [Duration]) -> Voice a -> Voice a+withDurations = over durationsV++-- |+-- Transform the values, leaving durations intact.+withValues :: ([a] -> [b]) -> Voice a -> Voice b+withValues = over valuesV++-- |+-- Rotate durations by the given number of steps, leaving values intact.+--+rotateDurations :: Int -> Voice a -> Voice a+rotateDurations n = over durationsV (rotate n)++-- |+-- Rotate values by the given number of steps, leaving durations intact.+--+rotateValues :: Int -> Voice a -> Voice a+rotateValues n = over valuesV (rotate n)++-- |+-- Reverse durations, leaving values intact.+--+reverseDurations :: Voice a -> Voice a+reverseDurations = over durationsV reverse++-- |+-- Reverse values, leaving durations intact.+--+reverseValues :: Voice a -> Voice a+reverseValues = over valuesV reverse+++--+-- TODO+-- Implement meta-data+--++--+-- TODO make Voice/Voice an instance of Cons/Snoc and remove these+--++headV :: Traversal' (Voice a) a+headV = (eventsV._head._2)++middleV :: Traversal' (Voice a) a+middleV = (eventsV._middle.traverse._2)++lastV :: Traversal' (Voice a) a+lastV = (eventsV._last._2)++-- Traverse writing to all elements *except* first and last+_middle :: (Snoc s s a a, Cons s s b b) => Traversal' s s+_middle = _tail._init++