hmt-0.20: Music/Theory/Time/KeyKit.hs
{- | A sequence structure, courtesy <https://github.com/nosuchtim/keykit>.
A /note/ has a time, a duration and a value.
A /phrase/ is a time-ascending sequence of notes and a /length/.
The length of a phrase is independent of the contents.
The sequence operator, /phrase_append/, sums phrase lengths.
The parallel operator, /phrase_merge/, selects the longer length.
Operations are ordinarily on phrases, notes are operated on indirectly.
The phrase indexing operation, /phrase_at/ returns a phrase of degree one.
-}
module Music.Theory.Time.KeyKit where
import Data.List {- base -}
import qualified Data.List.Ordered as O {- data-ordlist -}
import qualified Music.Theory.Time.Seq as Seq {- hmt -}
-- * Time
type Time = Rational
type Duration = Time
type Length = Time
-- * Note
data Note t =
Note { note_start_time :: Time, note_duration :: Duration, note_value :: t }
deriving (Eq, Ord, Show)
note_end_time :: Note t -> Time
note_end_time n = note_start_time n + note_duration n
note_region :: Note t -> (Time, Time)
note_region n = (note_start_time n, note_end_time n)
note_shift_time :: Time -> Note t -> Note t
note_shift_time k (Note t d e) = Note (t + k) d e
note_scale_duration :: Time -> Note t -> Note t
note_scale_duration m (Note t d e) = Note t (d * m) e
note_scale_duration_and_time :: Time -> Note t -> Note t
note_scale_duration_and_time m (Note t d e) = Note (t * m) (d * m) e
note_is_start_in_region :: (Time, Time) -> Note t -> Bool
note_is_start_in_region (t1, t2) (Note t _ _) = t >= t1 && t < t2
note_is_entirely_in_region :: (Time, Time) -> Note t -> Bool
note_is_entirely_in_region (t1, t2) (Note t d _) = t >= t1 && (t + d) < t2
-- * Phrase
-- | It is an un-checked invariant that the note list is in ascending order.
data Phrase t =
Phrase { phrase_notes :: [Note t], phrase_length :: Length }
deriving (Eq, Ord, Show)
phrase_values :: Phrase t -> [t]
phrase_values = map note_value . phrase_notes
phrase_set_length :: Phrase t -> Length -> Phrase t
phrase_set_length (Phrase n _) l = Phrase n l
phrase_degree :: Phrase t -> Int
phrase_degree (Phrase n _) = length n
phrase_start_time :: Phrase t -> Time
phrase_start_time (Phrase n _) =
case n of
[] -> 0
n1 : _ -> note_start_time n1
phrase_end_time :: Phrase t -> Time
phrase_end_time (Phrase n _) =
case n of
[] -> 0
_ -> note_start_time (last n)
phrase_duration :: Phrase t -> Duration
phrase_duration p = phrase_end_time p - phrase_start_time p
phrase_maximum :: Ord t => Phrase t -> Note t
phrase_maximum (Phrase n _) = maximum n
phrase_minimum :: Ord t => Phrase t -> Note t
phrase_minimum (Phrase n _) = minimum n
-- | Keykit sets the length to the duration, i.e. ('c,e,g'%2).length is 192.
phrase_at :: Phrase t -> Int -> Phrase t
phrase_at (Phrase n _) k =
let nt = n !! (k - 1)
in Phrase [nt] (note_start_time nt + note_duration nt)
phrase_time_at :: Phrase t -> Int -> Time
phrase_time_at (Phrase n _) k = note_start_time (n !! (k - 1))
phrase_clear_at :: Phrase t -> Int -> Phrase t
phrase_clear_at (Phrase n l) k =
let remove_ix ix list = let (p,q) = splitAt ix list in p ++ tail q
in Phrase (remove_ix (k - 1) n) l
phrase_at_put :: Ord t => Phrase t -> Int -> Phrase t -> Phrase t
phrase_at_put (Phrase n1 l1) k (Phrase n2 _) =
let nt = n1 !! (k - 1)
remove_ix ix list = let (p,q) = splitAt ix list in p ++ tail q
in Phrase (O.merge (remove_ix (k - 1) n1) (map (note_shift_time (note_start_time nt)) n2)) l1
phrase_is_empty :: Phrase t -> Bool
phrase_is_empty (Phrase n _) = null n
-- | KeyKits p+q
phrase_append :: Ord t => Phrase t -> Phrase t -> Phrase t
phrase_append (Phrase n1 l1) (Phrase n2 l2) = Phrase (O.merge n1 (map (note_shift_time l1) n2)) (l1 + l2)
phrase_append_list :: Ord t => [Phrase t] -> Phrase t
phrase_append_list = foldl1' phrase_append
-- | KeyKits p|q
phrase_merge :: Ord t => Phrase t -> Phrase t -> Phrase t
phrase_merge (Phrase n1 l1) (Phrase n2 l2) = Phrase (O.merge n1 n2) (max l1 l2)
phrase_merge_list :: Ord t => [Phrase t] -> Phrase t
phrase_merge_list p =
let l = maximum (map phrase_length p)
n = sort (concatMap phrase_notes p)
in Phrase n l
phrase_select :: Phrase t -> (Note t -> Bool) -> Phrase t
phrase_select (Phrase n l) f = Phrase (filter f n) l
phrase_partition :: Phrase t -> (Note t -> Bool) -> (Phrase t, Phrase t)
phrase_partition (Phrase n l) f =
let (n1, n2) = partition f n
in (Phrase n1 l, Phrase n2 l)
phrase_select_region :: Phrase t -> (Time, Time) -> Phrase t
phrase_select_region p r = phrase_select p (note_is_start_in_region r)
phrase_clear_region :: Phrase t -> (Time, Time) -> Phrase t
phrase_clear_region p r = phrase_select p (not . note_is_start_in_region r)
phrase_select_indices :: Phrase t -> (Int, Int) -> Phrase t
phrase_select_indices (Phrase n l) (i, j) = Phrase (take (j - i + 1) (drop (i - 1) n)) l
phrase_clear_indices :: Phrase t -> (Int, Int) -> Phrase t
phrase_clear_indices (Phrase n l) (i, j) = Phrase (take (i - 1) n ++ drop j n) l
phrase_extract_region :: Phrase t -> (Time, Time) -> Phrase t
phrase_extract_region p (t1, t2) =
let p' = phrase_select_region p (t1, t2)
in phrase_set_length (phrase_shift p' (0 - t1)) (t2 - t1)
phrase_delete_region :: Ord t => Phrase t -> (Time, Time) -> Phrase t
phrase_delete_region p (t1, t2) =
phrase_append
(phrase_extract_region p (0, t1))
(phrase_extract_region p (t2, phrase_length p))
phrase_separate :: Phrase t -> Time -> (Phrase t, Phrase t)
phrase_separate p t =
let (p1, p2) = phrase_partition p (note_is_start_in_region (0, t))
p1' = phrase_set_length p1 t
p2' = phrase_set_length (phrase_shift p2 (0 - t)) (phrase_length p - t)
in (p1', p2')
phrase_reverse :: Phrase t -> Phrase t
phrase_reverse (Phrase n l) =
let f (Note t d e) = Note (l - t - d) d e
in Phrase (reverse (map f n)) l
phrase_reorder :: Phrase t -> [Int] -> Phrase t
phrase_reorder (Phrase n l) p =
let f (Note t d _) i = Note t d (note_value (n !! (i - 1)))
in Phrase (zipWith f n p) l
phrase_truncate :: Phrase t -> Phrase t
phrase_truncate p = phrase_set_length p (phrase_end_time p)
phrase_trim :: Phrase t -> Phrase t
phrase_trim p =
let t = phrase_start_time p
in phrase_truncate (phrase_shift p (0 - t))
-- * Functor
note_map :: (t -> u) -> Note t -> Note u
note_map f (Note t d e) = Note t d (f e)
phrase_value_map :: (t -> u) -> Phrase t -> Phrase u
phrase_value_map f (Phrase n l) = Phrase (map (note_map f) n) l
phrase_note_map :: (Note t -> Note u) -> Phrase t -> Phrase u
phrase_note_map f (Phrase n l) = Phrase (map f n) l
phrase_phrase_map :: Ord u => (Phrase t -> Phrase u) -> Phrase t -> Phrase u
phrase_phrase_map f (Phrase n l) =
let g (Note t d e) = f (Phrase [Note t d e] (t + d))
in Phrase (sort (concatMap phrase_notes (map g n))) l
phrase_map :: Ord u => (Note t -> Phrase u) -> Phrase t -> Phrase u
phrase_map f (Phrase n l) = Phrase (sort (concatMap phrase_notes (map f n))) l
phrase_shift :: Phrase t -> Time -> Phrase t
phrase_shift p t = phrase_note_map (note_shift_time t) p
phrase_scale_duration :: Phrase t -> Time -> Phrase t
phrase_scale_duration p m = phrase_note_map (note_scale_duration m) p
phrase_scale_duration_and_time :: Phrase t -> Time -> Phrase t
phrase_scale_duration_and_time p m = phrase_note_map (note_scale_duration_and_time m) p
phrase_scale_to_duration :: Phrase t -> Duration -> Phrase t
phrase_scale_to_duration p d = phrase_scale_duration_and_time p (d / phrase_length p)
phrase_scale_to_region :: Phrase t -> (Time, Duration) -> Phrase t
phrase_scale_to_region p (t1, t2) = phrase_shift (phrase_scale_to_duration p (t2 - t1)) t1
-- * Seq
phrase_to_wseq :: Phrase t -> Seq.Wseq Time t
phrase_to_wseq (Phrase n _) =
let f (Note tm dur e) = ((tm, dur), e)
in map f n
useq_to_phrase :: Seq.Useq Time t -> Phrase t
useq_to_phrase = dseq_to_phrase . Seq.useq_to_dseq
dseq_to_phrase :: Seq.Dseq Time t -> Phrase t
dseq_to_phrase = wseq_to_phrase . Seq.dseq_to_wseq 0
wseq_to_phrase :: Seq.Wseq Time t -> Phrase t
wseq_to_phrase sq =
let f ((t, d), e) = Note t d e
in Phrase (map f sq) (Seq.wseq_dur sq)