packages feed

music-score-1.2: src/Music/Score/Ties.hs

                              
{-# LANGUAGE
    TypeFamilies,
    DeriveFunctor,
    DeriveFoldable,
    FlexibleInstances,
    GeneralizedNewtypeDeriving #-} 

-------------------------------------------------------------------------------------
-- |
-- Copyright   : (c) Hans Hoglund 2012
--
-- License     : BSD-style
--
-- Maintainer  : hans@hanshoglund.se
-- Stability   : experimental
-- Portability : non-portable (TF,GNTD)
--
-- Provides a musical score represenation.
--
-------------------------------------------------------------------------------------


module Music.Score.Ties (
        Tiable(..),
        TieT(..),
        splitTies,
        splitTiesSingle,
        splitTiesPart,
  ) where

import Data.Ratio
import qualified Data.List as List
import Data.VectorSpace
import Data.AffineSpace


import Music.Score.Part
import Music.Score.Score
import Music.Score.Duration
import Music.Score.Time



-- |
-- Class of types that can be tied.
--
class Tiable a where
    -- | Split elements into beginning and end and add tie.
    --   Begin properties goes to the first tied note, and end properties to the latter.

    --   The first returned element will have the original onset.
    --   
    toTied    :: a -> (a, a)

newtype TieT a = TieT { getTieT :: (Bool, a, Bool) }    
    deriving (Eq, Ord, Show, Functor)

-- These are note really tiable..., but Tiable a => (Bool,a,Bool) would be
instance Tiable Double      where toTied x = (x,x)
instance Tiable Float       where toTied x = (x,x)
instance Tiable Int         where toTied x = (x,x)
instance Tiable Integer     where toTied x = (x,x)
instance Tiable ()          where toTied x = (x,x)
instance Tiable (Ratio a)   where toTied x = (x,x)

instance Tiable a => Tiable (Maybe a) where
    toTied Nothing  = (Nothing, Nothing)
    toTied (Just a) = (Just b, Just c) where (b,c) = toTied a
    
instance Tiable a => Tiable (TieT a) where
    toTied (TieT (prevTie, a, _)) = (TieT (prevTie, b, True), TieT (True, c, False))
         where (b,c) = toTied a

-- | 
-- /Not implemented/
-- Split all notes that cross a barlines into a pair of tied notes.
-- 
splitTies :: Tiable a => Score a -> Score a
splitTies = error "splitTies: Not implemented"

-- | 
-- Split all notes that cross a barlines into a pair of tied notes.
-- Note: only works for single-part scores (with no overlapping events).
-- 
splitTiesSingle :: Tiable a => Score a -> Score a
splitTiesSingle = partToSingleScore . splitTiesPart . singleScoreToPart

partToSingleScore :: Part (Maybe a) -> Score a
partToSingleScore  = Score . accumTime . getPart
    where
        accumTime = snd . List.mapAccumL g 0
            where
                g t (d, x) = (t .+^ d, (t, d, x))

singleScoreToPart :: Score a -> Part (Maybe a)
singleScoreToPart sc = Part . movePart . throwTime . getScore $ sc
    where
        throwTime = fmap g where g (t,d,x) = (d,x)
        d = onset sc .-. 0
        movePart = if (d == 0) then id else ([(d, Nothing)] ++)

-- | 
-- Split all notes that cross a barlines into a pair of tied notes.
-- 
splitTiesPart :: Tiable a => Part a -> Part a
splitTiesPart = Part . concat . snd . List.mapAccumL g 0 . getPart
    where
        g t (d, x) = (t + d, occs)        
            where
                (_, barTime) = properFraction t
                remBarTime   = 1 - barTime
                occs = splitDur remBarTime (d,x)

-- |
-- Split an event into a part the given duration, and parts shorter than or equal to one.
-- The returned list is always non-empty.
--
-- > sum $ fmap fst $ splitDur s (x,a) = x
--         
splitDur :: Tiable a => Duration -> (Duration, a) -> [(Duration, a)]
splitDur s x = case splitDur' s x of
    (a, Nothing) -> a : []
    (a, Just b)  -> a : splitDur 1 b

-- FIXME completely assumes bar dur 1, see #36

-- |
-- Extract the the first part of a given duration. If the note is shorter than the given duration,
-- return it and @Nothing@. Otherwise return the extracted part, and the rest.
--
-- > splitDur s (d,a)
--         
splitDur' :: Tiable a => Duration -> (Duration, a) -> ((Duration, a), Maybe (Duration, a))
splitDur' s (d,a) | d <= s     =  ((d,a), Nothing)
                  | otherwise  =  ((s,b), Just (d-s, c)) where (b,c) = toTied a