-- | Notation of a sequence of 'RQ' values as annotated 'Duration' values. module Music.Theory.Duration.Sequence.Notate (Duration_A ,Simplify,simplify ,notate,notate' ,ascribe ,group_boundary_lenient,group_boundary_strict) where import Data.Maybe import Data.Ratio import Music.Theory.Duration import Music.Theory.Duration.RQ {- import Debug.Trace debug :: (Show a) => a -> x -> x debug = traceShow -} debug :: (Show a) => a -> x -> x debug _ x = x -- | Tuple of /start-time/, /duration/, /tied-left/ and /tied-right/. type D = (RQ,RQ,Bool,Bool) -- | Annotated 'Duration' type Duration_A = (Duration,[D_Annotation]) -- | Duration of 'D'. d_duration :: D -> RQ d_duration (_,x,_,_) = x -- | Is 'Duration_A' tied to the the right? da_tied_right :: Duration_A -> Bool da_tied_right = elem Tie_Right . snd -- | dx -> d -- -- > integrate [1,2,3,4] == [1,3,6,10] integrate :: (Num a) => [a] -> [a] integrate = scanl1 (+) -- | Given /boundaries/ and /duration/ calculate step. -- -- > step_dur [2,1,3] 5 == ([2,1,2],[1]) -- > step_dur [3%2,3%2,3%2] 2 == ([3%2,1%2],[1%1,3%2]) step_dur :: (Ord a, Num a) => [a] -> a -> ([a], [a]) step_dur l d = case d of 0 -> error "step_dur: zero duration" _ -> case l of [] -> error "step_dur: no boundaries" x:xs -> let jn a (a',b) = (a:a',b) in case compare d x of EQ -> ([d],xs) LT -> ([d],(x-d):xs) GT -> jn x (step_dur xs (d - x)) -- | xs = boundaries, d(s) = duration(s) -- -- > boundaries (repeat 3) [1..5] == [[1],[2],[3],[3,1],[2,3]] -- > boundaries (repeat (3%2)) [1%2,1..5] boundaries :: (Num a, Ord a) => [a] -> [a] -> [[a]] boundaries = let go [] _ = [] go _ [] = [] go xs (d:ds) = let (d',xs') = step_dur xs d in d' : go xs' ds in go -- | Given an initial start time and a list of durations make -- /start-time/ and /duration/ pairs. -- -- > with_start_times 0 [4,3,5,2,1] == [(0,4),(4,3),(7,5),(12,2),(14,1)] with_start_times :: (Num a) => a -> [a] -> [(a,a)] with_start_times i xs = let is = map (+i) (0 : integrate xs) in zip is xs -- | Variant starting at zero and processing sets of durations. -- -- > with_start_times' [[4,3],[2,1]] == [[(0,4),(4,3)],[(7,2),(9,1)]] -- > last (with_start_times' [[4,3,5],[2,1],[6,3]]) == [(15,6),(21,3)] with_start_times' :: (Num a) => [[a]] -> [[(a, a)]] with_start_times' xs = let is = 0 : integrate (map sum xs) in zipWith with_start_times is xs {- with_start_times' (boundaries [3,3,3,3,3] [4,3,5,2,1]) let xs = [3%4,2%1,5%4,9%4,1%4,3%2,1%2,7%4,1%1,5%2,11%4,3%2] with_start_times 0 xs with_start_times' (boundaries (repeat (3%2)) xs) -} -- | Split /xs/ into first, possibly empty 'middle', and last parts. -- /xs/ must have at least two elements. -- -- > start_middle_end [] == undefined -- > start_middle_end [1,2] == (1,[],2) -- > start_middle_end [1..6] == (1,[2..5],6) start_middle_end :: [x] -> (x,[x],x) start_middle_end xs = case xs of _:_:_ -> let n = length xs x0 = xs !! 0 xn = xs !! (n - 1) in (x0,take (n - 2) (drop 1 xs),xn) _ -> error "start_middle_end: list must have at least two elements" -- xs = [(start-time,duration)] tied_r_to_d :: [(RQ,RQ)] -> [D] tied_r_to_d xs = case xs of [] -> [] [(s,d)] -> [(s,d,False,False)] _ -> let ((s0,d0),xs',(sn,dn)) = start_middle_end xs f (s,d) = (s,d,True,True) in (s0,d0,False,True) : map f xs' ++ [(sn,dn,True,False)] boundaries_d :: [RQ] -> [RQ] -> [D] boundaries_d xs ds = let bs = boundaries xs ds in concatMap tied_r_to_d (with_start_times' bs) {- boundaries_d [3,3,3,3,3,3,3,3] [4,3,5,2,1,7,2] -} -- | Rational modulo -- -- > map (r_mod (5/2)) [3/2,3/4] == [1,1/4] r_mod :: RQ -> RQ -> RQ r_mod i j | i == j = 0 | i < 0 = r_mod (i + j) j | i > j = r_mod (i - j) j | otherwise = i {- -- n = boundary -- i = phase sep_at :: RQ -> RQ -> R -> [D] sep_at = let go l n i x = let i' = n - (i `r_mod` n) in if x > i' then let d = (i,i',l,True) in d : go True n (i + i') (x - i') else [(i,x,l,False)] in go False sep_at 1 (1%2) 1 sep_at 1 (1%3) (6%3) -} -- | Given /phase/ separate a /RQ/ duration if un-representable by a -- single /CMN/ duration (ie. requires tie). -- -- > sep_unrep 0 5 == Just (4,1) sep_unrep :: RQ -> RQ -> Maybe (RQ,RQ) sep_unrep i x = let i' = denominator i == 1 j = case numerator x of 5 -> Just (1,4) 7 -> Just (3,4) _ -> Nothing f (n,m) = (n%denominator x,m%denominator x) swap (a,b) = (b,a) in case j of Nothing -> Nothing Just j' -> Just (f (if i' then swap j' else j')) sep_unrep_d :: D -> [D] sep_unrep_d d = let (i,x,l,r) = d in case sep_unrep i x of Nothing -> [d] Just (x0,x1) -> [(i,x0,l,True),(i+x0,x1,True,r)] {- zipWith sep_unrep [1,3%8,1] [5%4,5%8,4] == [Just (1%1,1%4),Just (1%8,1%2),Nothing] zipWith (\i x -> sep_unrep_d (i,x,False,False)) [1,3%8,1] [5%4,5%8,4] -} separate :: [RQ] -> [RQ] -> [D] separate ns = concatMap sep_unrep_d . boundaries_d ns {- let xs = [3%4,2%1,5%4,9%4,1%4,3%2,1%2,7%4,1%1,5%2,11%4,3%2] separate (repeat (1%2)) xs -} -- | group to n, or to multiple of -- -- > group_boundary_lenient id [1,1,1] [2,1%2,1%2] == [[2%1],[1%2,1%2]] -- > group_boundary_lenient id [3,3,3] (cycle [1,2,3]) == [[1,2],[3],[1,2]] group_boundary_lenient :: (a -> RQ) -> [RQ] -> [a] -> [[a]] group_boundary_lenient dur_f = let go _ [] [] _ = [] go _ _ [] _ = error "group_boundary_lenient: no boundaries?" go _ js _ [] = [reverse js] go _ js _ [x] = [reverse (x:js)] go c js (n:ns) (x:xs) = let c' = c + dur_f x in case compare c' n of EQ -> reverse (x:js) : go 0 [] ns xs LT -> go c' (x:js) (n:ns) xs GT -> let c'' = c' - n in if c'' `divisible_by` n then reverse (x:js) : go 0 [] ns xs else go c'' (x:js) ns xs in go 0 [] group_boundary_lenient_d :: [RQ] -> [D] -> [[D]] group_boundary_lenient_d = group_boundary_lenient d_duration {- let i = [1,1%2,2,1%3,5%3,1%8,1%2,7%8] in group_boundary_lenient_d (repeat 1) (separate (repeat 1) i) -} with_sum :: (Num i) => (a -> i) -> [a] -> [(i,a)] with_sum f = let go _ [] = [] go i (x:xs) = (i,x) : go (i + f x) xs in go 0 to_boundary :: (Num i,Ord i) => (a->i) -> i -> [(i,a)] -> ([(i,a)],[(i,a)]) to_boundary f b = span (\(i,j) -> i + f j <= b) -- | Keeps the /zero/ duration chord element in the same measure. group_boundary_strict' :: (Ord i,Num i) => (a->i) -> [i] -> [a] -> [[(i,a)]] group_boundary_strict' f bs is = let is' = with_sum f is bs' = integrate bs go [] _ = [] go (j:js) zs = let (x,y) = to_boundary f j zs in x : go js y in go bs' is' -- | Variant on 'group_boundary_lenient'. -- -- > let g = group_boundary_strict id -- > in g [3,2,3] [1,0,1,1,0,2,0,1,1,1] == [[1,0,1,1,0],[2,0],[1,1,1]] group_boundary_strict :: (Ord a, Num a) => (b -> a) -> [a] -> [b] -> [[b]] group_boundary_strict f bs = map (map snd) . group_boundary_strict' f bs derive_tuplet :: [D] -> Maybe (Integer,Integer) derive_tuplet xs = let xs' = map d_duration xs i = maximum (map denominator xs') smpl n = if even n then smpl (n `div` 2) else n i' = smpl i j = case i' of 3 -> (3,2) 5 -> (5,4) 7 -> (7,4) 9 -> (9,8) _ -> error ("derive_tuplet: " ++ show (i,i')) in if i' == 1 then Nothing else Just j {- let i = [1,1%2,2,1%3,5%3,1%8,1%2,7%8] in map derive_tuplet (group_boundary_lenient_d 1 (separate 1 i)) -} -- | Remove tuplet multiplier from value, ie. to give notated -- duration. This seems odd but is neccessary to avoid ambiguity. -- Ie. is 1 a quarter note or a 3:2 tuplet dotted-quarter-note etc. un_tuplet :: (Integer,Integer) -> RQ -> RQ un_tuplet (i,j) x = x * (i%j) d_join_aligned :: D -> D -> Maybe D d_join_aligned (s1,x1,l1,r1) (_,x2,_,r2) | (x1 == (1%4) && r1 && x2 `elem` [1%4,1%2,3%4]) || (x1 == (1%2) && r1 && x2 `elem` [1%4,1%2,1,3%2]) || (x1 == 1 && r1 && x2 `elem` [1%2,1,2]) || (x1 == (3%2) && r1 && x2 `elem` [1%2,3%2]) || (x1 == 2 && r1 && x2 `elem` [1,2]) = debug ("aligned-join",s1,x1,x2) (Just (s1,x1+x2,l1,r2)) | otherwise = debug ("aligned-no-join",s1,x1,r1,x2) Nothing divisible_by :: RQ -> RQ -> Bool divisible_by i j = denominator (i / j) == 1 -- | partial/incomplete/inaccurate -- -- > d_join 1 (7%4,1%4,False,True) (2%1,1%4,True,False) == Nothing d_join :: RQ -> D -> D -> Maybe D d_join a (s1,x1,l1,r1) (s2,x2,l2,r2) | s1 `divisible_by` a = d_join_aligned (s1,x1,l1,r1) (s2,x2,l2,r2) | denominator (s1 `r_mod` 1) == 4 && x1 == 1%4 && r1 && x2 == 1%4 && not (s2 `divisible_by` a) = debug ("non-aligned-join",a,s1,x1) (Just (s1,x1+x2,l1,r2)) | s1 `r_mod` 1 == 2%3 && x1 == 1%3 && r1 && x2 == 1%3 = debug ("non-aligned-join",a,s1,x1) (Just (s1,x1+x2,l1,r2)) | otherwise = debug ("non-aligned-no-join",a,s1,x1) Nothing {- -- error checking variant d_join' :: RQ -> D -> D -> Maybe D d_join' a d1 d2 = case d_join a d1 d2 of Nothing -> Nothing Just x -> let (_,y,_,_) = x in case rq_to_duration y of Nothing -> error ("d_join' :" ++ show (a,d1,d2,x)) Just _ -> Just x -} coalesce :: (a -> a -> Maybe a) -> [a] -> [a] coalesce f xs = case xs of (x1:x2:xs') -> case f x1 x2 of Nothing -> x1 : coalesce f (x2:xs') Just x' -> coalesce f (x':xs') _ -> xs -- | Type of function used by 'notate' to simplify duration sequence. -- Arguments specify /alignment/ and /boundaries/. type Simplify = (RQ -> [RQ] -> [D] -> [D]) -- | Simple minded two pass 'Simplify' function. The two pass -- structure is so that @[2,1%2,1%2]@ becomes @[2,1]@ becomes @[3]@. simplify :: Simplify simplify a ns xs = let xs' = group_boundary_lenient_d ns xs pass :: [[D]] -> [[D]] pass = map (coalesce (d_join a)) in concat ((pass . pass) xs') -- | Variant of 'rq_to_duration' with error message. to_duration :: Show a => a -> RQ -> Duration to_duration msg n = let err = error ("to_duration:" ++ show (msg,n)) in fromMaybe err (rq_to_duration n) tuplet :: (Integer,Integer) -> [Duration] -> [Duration_A] tuplet (d,n) xs = let fn x = x { multiplier = n%d } xn = length xs ty = to_duration "tuplet" (sum (map duration_to_rq xs) / (d%1)) t0 = [Begin_Tuplet (d,n,ty)] ts = [t0] ++ replicate (xn - 2) [] ++ [[End_Tuplet]] in zip (map fn xs) ts -- | The @d0:dN@ distinction is to catch, for instance, dotted @1\/4@ -- and tuplet @1\/16@. It'd be better to not simplify to that, -- however the simplifier does not look ahead. notate_sec :: [D] -> [Duration_A] notate_sec xs = let ds = map d_duration xs add_ties_from (_,_,l,r) (d,fs) = let l' = if l then [Tie_Left] else [] r' = if r then [Tie_Right] else [] in (d,l' ++ r' ++ fs) xs' = case derive_tuplet xs of Nothing -> let f = to_duration ("notate-sec:no-tuplet",ds) in map (\d -> (f d,[])) ds Just t -> let f = to_duration ("notate-sec:tuplet",t,ds) (d0:dN) = ds in if denominator d0 == 2 then (f d0,[]) : tuplet t (map (f . un_tuplet t) dN) else tuplet t (map (f . un_tuplet t) ds) in zipWith add_ties_from xs xs' -- | Notate sequence of rational quarter note durations given a -- 'Simplify' function, a list of /unit divisions/ which must not -- conflict with a list of /boundaries/ (ie. measures). -- -- IMPORTANT NOTE: alignments are not handled correctly -- -- > let n = notate (Just simplify) (repeat 1) (repeat 4) -- > in n [3,3] == [(dotted_half_note,[]),(quarter_note,[Tie_Right]),(half_note,[Tie_Left])] notate :: Maybe Simplify -> [RQ] -> [RQ] -> [RQ] -> [Duration_A] notate smp is ns xs = let xs' = case smp of Nothing -> separate is xs Just f -> f (head is) ns (separate is xs) in concatMap notate_sec (group_boundary_lenient_d is xs') -- | Variant with default 'simplify' function and constant unit -- division of @1@. -- -- > map (duration_to_rq . fst) (notate' [4,4] [3,3,2]) == [3,1,2,2] notate' :: [RQ] -> [RQ] -> [Duration_A] notate' = notate (Just simplify) (repeat 1) {- let xs = [2%3,2%3,2%3,3%2,3%2,2%3,2%3,2%3,1%2,1%2,5%2,3%2] let xs = map (%4) [1,6,2,3] let xs = [2%1, 3%5, 2%5] let is = repeat (1%1) let ns = repeat (3%1) map (\(x,y) -> (duration_to_lilypond_type x,y)) (notate is ns xs) separate is xs let xs' = simplify (head is) ns (separate is xs) group_boundary_lenient_d is xs' let is = [1,1,1,1%2,1%2,1,1] let ns = [2%5,1%5,1%5,1%5+1%2,1%2,1,1%10,1%10,1%10,1%10,1%10,1%6,1%6,1%6+1%7,2%7,4%7,1] notate (Just simplify) is [1,5] ns == notate Nothing is [1,5] ns -} ascribe_fn :: (x -> Bool) -> [x] -> [a] -> [(x,a)] ascribe_fn fn = let go [] _ = [] go _ [] = error "ascribe_fn" go (x:xs) (i:is) = let is' = if fn x then i:is else is in (x,i) : go xs is' in go -- | Zip a list of 'Duration_A' elements duplicating elements of the -- right hand sequence for tied durations. -- -- > map snd (ascribe (notate' [4,4] [3,3,2]) "xyz") == "xyyz" ascribe :: [Duration_A] -> [x] -> [(Duration_A,x)] ascribe = ascribe_fn da_tied_right