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 (<minDur)) ([
>     -- 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"]]