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