Musical Grammars Donya Quick Last modified: 13-Jan-2016 > module Kulitta.Grammars.MusicGrammars where > import Kulitta.PTGG > import System.Random > import Data.List ================================== TYPE SYNONYMS & CONSTANTS > type Dur = Rational > type AbsPitch = Int > wn = 1 :: Dur > hn = 1/2 :: Dur > qn = 1/4 :: Dur > en = 1/8 :: Dur > sn = 1/16 :: Dur > tn = 1/32 :: Dur ================================== ALPHABETS FOR BASE SYMBOLS Alphabet 1: Roman numerals for chords > data CType = I | II | III | IV | V | VI | VII > deriving (Eq, Show, Ord, Enum, Read) Alphabet 1: from Rohrmeier's paper "Towards a generative syntax of tonal harmony" > data RTerm = Piece | P | -- piece/phrase (or P=Plagal for Kulitta's reuse of P) > TR | DR | SR | -- regions > T | D | S | TP | TCP | SP | DP | -- chord functions > C CType -- Roman numerals > deriving (Eq, Read) Show function for alphabets with duration: > showDur :: (Show a) => (a,MP) -> String > showDur (a,MP d m k o sd) = show a ++ "(" ++ show d ++ ")" ================================== ALPHABETS FOR PARAMETERS Many finite base symbol alphabets can use the same potentially infinite alphabet of parameter symbols. Here we define a general "music parameter" or MP for many tonal applications. It will store the current duration of a symbol, and the symbol's tonal context as a mode and scale root. Finally, there is allowance for keeping track of the onset of the symbol as well as the total duration of the sentence to which it belongs. This allows for checking things like whether the symbols is the LAST in a sentence, at the midpoint, etc. > data MP = MP {dur :: Dur, mode :: Mode, key :: Int, onset :: Dur, sDur :: Dur} > deriving (Eq, Show) Modes include the seven usual derivatives of the C-major scale along with chromatic and custom options. Note that Major=Ionian and Minor=Aeoloean. > -- I II III IV V VI VII > data Mode = Major | Dorian | Phrygian | Lydian | Mixolydian | Minor | Locrian | > Chromatic | Custom [AbsPitch] > deriving (Eq, Show, Ord, Read) A partial Enum instance is supplied for the modes with seven-note scales. The enumFrom function is defined to loop around. For example: enumFrom Dorian ==> [Dorian, Phrygian, ..., Locrian, Major] > allEnumModes = [Major, Dorian, Phrygian, Lydian, Mixolydian, Minor, Locrian] > instance Enum Mode where > toEnum i = if i>=0 && i<=6 then allEnumModes !! i > else error "Only modes 0-6 are enumerable." > fromEnum Chromatic = error "Chromatic mode is not part of Enum instance." > fromEnum (Custom x) = error "Cannot enumerate a Custom mode." > fromEnum x = case findIndex (==x) allEnumModes of > Nothing -> error ("Cannot enumerate unknown mode: "++show x) > Just i -> i > enumFrom x = case findIndex (==x) allEnumModes of > Nothing -> error ("Cannot enumerate from unknown mode: "++show x) > Just i -> take 7 $ drop i (allEnumModes++allEnumModes) A default MP value is one measure long (in 4/4) in the key of C-major. > defMP = MP 1 Major 0 0 1 It is also useful to have tests for MP values and modifiers for them. > isMaj = (==Major) . mode > isMin = (==Minor) . mode Modifiers on duration can be used to succinctly write transformations. For example, to halve the duration of a parameter p::MP, one need only write (h p) rather than something like p{dur=(dur p)/2} > dFac x p = p{dur = dur p * x} > h = dFac 0.5 -- half of the original duration > q = dFac 0.25 -- a quarter of the original duration > e = dFac 0.125 Similarly, we have some shorthands for adjusting the onsets and durations at the same time. NOTE: offsets should be changed before the duration is changed. > q2 p = p{onset = onset p + (dur p / 4)} -- "beat" 2 (second quarter) > q3 p = p{onset = onset p + (dur p / 2)} -- "beat" 3 (third quarter) > q4 p = p{onset = onset p + 3*(dur p / 4)} -- "beat" 4 (fourth quarter) We can also do shorthands that do both things. > ho = h . q3 > qo2 = q . q2 > qo3 = q . q3 > qo4 = q . q4 The following alter Rules to do a duration test. Each has a "rejection condition" that will be the condition for an ID rule. The rejection condition in this case tests the left-hand-side symbol's duration. > toRelDur :: (Dur -> Bool) -> Rule a MP -> Rule a MP > toRelDur f ((l,d):-> rf) = > (l,d) :-> \p -> if f $ dur p then [NT (l,p)] else rf p This one is pickier - it will check whether applying the rule will produce symbols that satisfy the rejection condition. So, if there is ANY bad symbol on the right-hand-side, an ID rule will be applied instead. > toRelDur2 :: (Dur -> Bool) -> Rule a MP -> Rule a MP > toRelDur2 f ((l,d):-> rf) = (l, d) :-> \p -> > let xs = map (dur.snd) $ toPairs $ expand [] $ rf p > in if or (map f xs) then [NT (l,p)] else rf p Similarly, we can do this for ends of phrases. A symbol must be the last in its setence if the sentence duration minus the onset equals the symbol's duration. Note: this hinges on the fact that time is hangled with the Rational type in Haskell. Otherwise, we would have to compare with an error-tollerant threshold. > isLast :: MP -> Bool > isLast p = (sDur p - onset p) == dur p > toMinLast :: Dur -> Rule a MP -> Rule a MP > toMinLast minLastDur ((l,d):-> rf) = > (l, d) :-> \p -> if isLast p && dur p <= minLastDur then [NT (l,p)] else rf p For mode/key changes: > -- C D E F G A B > majModes = [Major, Minor, Minor, Major, Major, Minor, Minor] > minModes = [Minor, Minor, Major, Minor, Minor, Major, Major] > modalPats = enumFrom Major :: [Mode] > majScale = [0,2,4,5,7,9,11] > minScale = [0,2,3,5,7,8,10] > dorScale = [0, 2, 3, 5, 7, 9, 10] > phrScale = [0, 1, 3, 5, 7, 8, 10] > lydScale = [0, 2, 4, 6, 7, 9, 11] > mixScale = [0, 2, 4, 5, 7, 9, 10] > locScale = [0, 1, 3, 5, 6, 8, 10] > getScale :: Mode -> [AbsPitch] > getScale Major = majScale > getScale Minor = minScale > getScale Dorian = dorScale > getScale Phrygian = phrScale > getScale Lydian = lydScale > getScale Mixolydian = mixScale > getScale Locrian = locScale > getScale m = error ("(getScale) Scale not defined for mode" ++ show m) > modMajMin i p = let k0 = key p in > if mode p == Major then p{mode=majModes!!i, key=(k0+(majScale!!i)) `mod` 12} > else p{mode=minModes!!i, key=(k0+(minScale!!i)) `mod` 12} Basic modulations on scale degrees for Major and Minor systems > [m2, m3, m4, m5, m6, m7] = map modMajMin [1..6] [TO-DO: MODAL VERSION OF MODULATIONS] ================================== P = {piece, P} R = {TR, SR, DR} functional region symbols K = {Cmaj, Cmin, ...} key symbols F = {t, s, d, tp, sp, dp, tcp} functional term symbols S = {I, II, ...} scale degree chord representations O = {Cmaj, ...} surface chord symbols (e.g. I in K=Cmaj) > allRTerms = [Piece, P, TR, DR, SR, T, D, S, TP, TCP, SP, DP, > C I, C II, C III, C IV, C V, C VI, C VII] > instance Show RTerm where > show Piece = "Piece" > show P = "P" > show TR = "TR" > show DR = "DR" > show SR = "SR" > show T = "T" > show D = "D" > show S = "S" > show TP = "TP" > show TCP = "TCP" > show SP = "SP" > show DP = "DP" > show (C x) = show x > showRTerms :: [RTerm] -> String > showRTerms [] = "" > showRTerms [t] = show t > showRTerms (t1:ts) = show t1 ++ " " ++ showRTerms (ts) TSD Grammar Base > tsdRules :: [Rule RTerm MP] > tsdRules = [ > (T, 0.25) :-> \p -> [NT (T, p)], > (T, 0.25) :-> \p -> [NT (T, h p), NT (T, h p)], > (T, 0.25) :-> \p -> [NT (T, h p), NT (D, h p)], > (T, 0.25) :-> \p -> [NT (D, h p), NT (T, h p)], > (D, 0.33) :-> \p -> [NT (D, p)], > (D, 0.33) :-> \p -> [NT (D, h p), NT (D, h p)], > (D, 0.34) :-> \p -> [NT (S, h p), NT (D, h p)], > (S, 0.5) :-> \p -> [NT (S, p)], > (S, 0.5) :-> \p -> [NT (S, h p), NT (S, h p)] > ] ================================== Roman Numeral Grammar Base > [i, ii, iii, iv, v, vi, vii] = map fc $ enumFrom I where > fc ct p = NT (ct,p) Grammar from dissertation chapter 4, table 4.2 with optional let statements added. > rRules1 :: Dur -> Bool -> [Rule CType MP] > rRules1 minDur useLets = normalize $ map (toRelDur2 ( -- Rules for I -- > (I, 0.187) :-> \p -> [(if isMaj p then ii else iv) (q p), v (q p), i (h p)], > (I, 0.187) :-> \p -> map ($ q p) [i, iv, v, i], > (I, 0.187) :-> \p -> [v (h p), i (h p)], > (I, 0.187) :-> \p -> map ($ q p) $ [i, if isMaj p then ii else iv, v, i], > (I, 0.252) :-> \p -> [i p], > -- Rules for II -- > (II, 0.40) :-> \p -> if isMaj p then [ii p] else [iv p], > (II, 0.40) :-> \p -> if isMaj p then (if dur p > qn then [ii p] else [i (m2 p)]) else [ii p], > (II, 0.20) :-> \p -> map ($ h p) $ if isMaj p then [vi, ii] else [vi, iv], > -- Rules for III-- > (III, 0.90) :-> \p -> [iii p], > (III, 0.10) :-> \p -> [i $ m3 p], > -- Rules for IV -- > (IV, 0.90) :-> \p -> [iv p], > (IV, 0.10) :-> \p -> [i $ m4 p], > -- Rules for V -- > (V, 0.10) :-> \p -> [v p], > (V, 0.15) :-> \p -> [iv (h p), v (h p)], > (V, 0.10) :-> \p -> [iii (h p), vi (h p)], > (V, 0.10) :-> \p -> map ($ q p) [i, iii, vi, v], > (V, 0.10) :-> \p -> map ($ q p) [v, vi, vii, v], > (V, 0.10) :-> \p -> [v (h p), vi (h p)], > (V, 0.10) :-> \p -> [iii p], > (V, 0.05) :-> \p -> [v (h p), v (h p)], > (V, 0.10) :-> \p -> [vii (h p), v (h p)], > (V, 0.10) :-> \p -> [i $ m5 p], > -- Rules for VI -- > (VI, 0.70) :-> \p -> [vi p], > (VI, 0.30) :-> \p -> [i $ m6 p], > -- Rules for VII -- > (VII, 0.50) :-> \p -> if dur p > qn then [vii p] else [i $ m7 p], > (VII, 0.50) :-> \p -> [i (h p), iii (h p)] > ] ++ if useLets then letRules else []) where > letRules = concatMap (\ct -> [letRule1 ct, letRule2 ct]) (enumFrom I) > letRule1 ct = (ct, 0.1) :-> \p -> [Let "x" [NT(ct, h p)] [Var "x", Var "x"]] > letRule2 ct = (ct, 0.1) :-> \p -> [Let "x" [NT(ct, q p)] [Var "x", v (h p), Var "x"]]