Simple Jazz Foreground Algorithms
Donya Quick

Last Modified: 15-Oct-2015

Last updates:
- added support for more modes

> module Kulitta.Foregrounds.JazzFG where
> import Kulitta.PTGG
> import Kulitta.Grammars.MusicGrammars
> import System.Random
> import Kulitta.EuterpeaSpecial
> import Kulitta.ChordSpaces
> import Kulitta.PostProc
> import Data.List
> import Control.Parallel.Strategies
> import Kulitta.Foregrounds.ClassicalFG
> import Kulitta.Search
> import Kulitta.Constraints



First, we need to find the modes for Roman numerals interpreted
in a particular key/mode. The type JTriple is actually a synonym
for TChord, but it is used for clarity to indicate that the pitch
information represents a mode rather than a chord.

> rotateModes i = drop i allModes ++ (take i allModes)

> majorModes = allModes
> ionianModes = majorModes -- synonym

> dorianModes = rotateModes 1
> phrygianModes = rotateModes 2
> lydianModes = rotateModes 3
> mixolydianModes = rotateModes 4
> minorModes = rotateModes 5
> aoleanModes = minorModes -- synonym

> locrianModes = rotateModes 6

> modeLookup Major = majorModes
> modeLookup Dorian = dorianModes
> modeLookup Phrygian = phrygianModes
> modeLookup Lydian = lydianModes
> modeLookup Mixolydian = mixolydianModes
> modeLookup Minor = minorModes
> modeLookup Locrian = locrianModes

> chordMode :: CType -> Key -> AbsMode
> chordMode ct (k,m) =
>     let pModes = modeLookup m -- what pattern of modes for each scale degree?

>         ctMode = pModes !! fromEnum ct -- pick the mode for the Roman numeral

>         ck = pModes !! 0 !! fromEnum ct -- pick the pitch offset for the Roman numeral

>     in  t (k+ck) ctMode -- transposition op and convert numeral to modal pitches


> toJTriple :: (Key, Dur, CType) -> (Key, Dur, AbsMode)
> toJTriple (km,d,c) = (km, d, chordMode c km)

============================

> jazzChords :: StdGen -> [(Key, Dur, CType)] -> Constraints -> (StdGen, [(Key, Dur, AbsChord)])
> jazzChords g chords consts =
>     let [gJ, gOPC, g'] = take 3 $ splitN g
>         jts = map toJTriple chords
>         ms = map (\(a,b,c) -> ([],c)) jts -- get just modes as JChords

>         qJ = modeSpace' alg1Temps -- subset of ModeSpace desired

>         chordsJ = greedyLet (const True) nearFallJ consts (map (eqClass qJ modeEq) ms) gJ -- random walk through qj

>         qOPC = makeRange' alg1Rans // opcEq  -- subset of OPC-space desired

>         es = map (convOPC qOPC bassRoot) chordsJ -- OPC equivalence classes for chords

>         chordsOPC = greedyProg' (const True) nearFall gOPC es -- random walk through OPC-space

>         chordsOPC' = zipWith newP jts chordsOPC -- tag with dur & mode

>     in  (g', chordsOPC')

============================

Algorithm 1: chords and a stochastic bass. Let instantiation only takes
place at the level of Roman numerals.

> --            r t f 7    r 2 t f 7	r=root, r=third, f=fifth

> alg1Temps = [[0,2,4,6], [0,1,2,4,6]]

> --          bass       chords

> alg1Rans = (34,45) : take 4 (repeat (50,64))

> bassRoot (chrd, m) = (minimum chrd `mod` 12) == head (normO m)

> jazzFG1 :: StdGen -> [(Key, Dur, CType)] -> Constraints -> (StdGen, Music Pitch)
> jazzFG1 g chords consts =
>     let [gJ, gR, gOPC, gB] = take 4 $ splitN g
>         jts = map toJTriple chords
>         ms = map (\(a,b,c) -> ([],c)) jts -- get just modes as JChords

>         qJ = modeSpace' alg1Temps -- subset of ModeSpace desired

>         chordsJ = greedyLet (const True) nearFallJ consts (map (eqClass qJ modeEq) ms) gJ
>                -- greedyProg qJ modeEq (const True) nearFallJ gJ ms -- random walk through qj

>         qOPC = makeRange' alg1Rans // opcEq  -- subset of OPC-space desired

>         es = map (convOPC qOPC bassRoot) chordsJ -- OPC equivalence classes for chords

>         chordsOPC = greedyProg' (const True) nearFall gOPC es -- random walk through OPC-space

>         chordsOPC' = zipWith newP jts chordsOPC -- tag with dur & mode

>         voices = toVoices chordsOPC' -- place in voice format

>         (gRet, bassLine) = stochBass gB $ head voices -- stochastic bassline

>     in  (gRet, instrument AcousticBass bassLine :=:
>                vsToMusicI (repeat AcousticGrandPiano) (tail voices))

> splitN g = let (g1,g2) = split g in g1 : splitN g2

Direct interface to grammar monad:

> jazzFG1T :: StdGen -> Sentence CType MP -> Constraints -> (StdGen, Music Pitch)
> jazzFG1T g t consts = jazzFG1 g (toChords $ expand [] t) consts

> convOPC :: QSpace AbsChord -> Predicate JChord -> JChord -> EqClass AbsChord
> convOPC q pj (c,m) = filter (\x -> pj (x,m)) $ eqClass q opcEq c

> stochBass :: StdGen -> [TNote] -> (StdGen, Music Pitch)
> stochBass g [] = (g, rest 0)
> stochBass g ((km,d,p):t) =
>     let (g', pat) = pickPattern g d p
>         (g'', t') = stochBass g' t
>     in  (g'', pat :+: t')

> pickPattern g d p =
>     let (r,g') = randomR (0,length pats - 1) g
>         f d p = note d (pitch p)
>         pats = [f d p,
>                 if d>=hn then f qn p :+: f (d-qn) p else f d p,
>                 if d>=hn then f (d-en) p :+: f en p else f d p]
>     in  (g', pats !! r)


=============================

Algorithm 2: simple bossa nova

This approach interprets Roman numerals through three separate
chord spaces in order to cut down the task's combinatorics.

> alg2TempsC = [[0,2,4,6], [1,2,4,6]] -- for chords

> alg2TempsB = [[0,4]] -- for bass

> alg2TempsL = [[0],[2],[4]] -- for lead


> alg2RansB = [(34,49), (34,49)]
> alg2RansC = take 4 $ repeat (50,64)
> alg2RansL = [(65,80)]

> bassRoot2 ([b1,b2], m) = normO [b1,b2] == normO [m !! 0, m!! 4]
> bassRoot2 _ = error "(bassRoot2) Bad arguments."

> alg2FilterC x = sorted x && pianoChord x

> jazzFG2 :: StdGen -> [(Key, Dur, CType)] -> Constraints -> (StdGen, Music (Pitch, Volume))
> jazzFG2 g chords consts =
>     let gs@[gJC, gJB, gJL, gRC, gRB, gRL, gOPC_C, gOPC_B, gOPC_L, gL] = take 10 $ splitN g
>         jts = map toJTriple chords
>         ms = map (\(a,b,c) -> ([],c)) jts
>         qs@[qJC, qJB, qJL] = map modeSpace' [alg2TempsC, alg2TempsB, alg2TempsL] -- jazz spaces

>         [chordsJ, bassJ, leadJ] = -- random walk for chords

>             zipWith (\q gx -> greedyProg q modeEq (const True) nearFallJ gx ms) qs $
>             take 3 gs
>         qOPC_C = filter alg2FilterC (makeRange' alg2RansC) // opcEq
>         qOPC_B = makeRange alg2RansB // opcEq
>         qOPC_L = makeRange' alg2RansL // opcEq
>         esC = map (convOPC qOPC_C (const True)) chordsJ -- OPC equivalence classes for chords

>         esB = map (convOPC qOPC_B bassRoot2) bassJ
>         esL = map (convOPC qOPC_L (const True)) leadJ
>         chordsOPC = greedyLet (const True) nearFall consts esC gOPC_C -- random walk through OPC-space

>         bassOPC = greedyLet (noCPL 7) nearFall consts esB gOPC_B -- random walk for bass

>         leadOPC = greedyLet (noCPL 7) nearFall consts esL gOPC_L -- random walk for lead

>         [cc, bc, lc] = map (zipWith newP jts) [chordsOPC, bassOPC, leadOPC] -- tag with dur & mode

>         cm = bossaChords cc
>         bm = bossaBass bc
>         (gRet, lm) = bossaLead gL lc
>     in  (gRet, chord [addVolume 127 $ instrument AcousticBass bm,
>                       addVolume 75 $ instrument AcousticGrandPiano cm,
>                       addVolume 127 $ instrument Flute lm])

> jazzFG2T :: StdGen -> Sentence CType MP -> Constraints ->  (StdGen, Music (Pitch, Volume))
> jazzFG2T g t consts = jazzFG2 g (toChords $ expand [] t) consts

> bossaBass :: [TChord] -> Music Pitch
> bossaBass [] = rest 0
> bossaBass ((km,d,c@[p1,p2]):t) =
>     if d > wn then bossaBass ((km,wn,c):(km,d-wn,c):t) else
>     if d == wn then f1 p1 p2 :+: bossaBass t else
>     if d == hn then f2 p1 p2 :+: bossaBass t  else f3 p1 d :+: bossaBass t where
>     f1 b1 b2 = f2 b1 b2 :+: f2 b2 b1
>     f2 b1 b2 = f3 b1 (qn+en) :+: f3 b2 en
>     f3 b1 d = note d (pitch b1)
> bossaBass _ = error "(bossaBass) Bad input"

> bossaChords :: [TChord] -> Music Pitch
> bossaChords [] = rest 0
> bossaChords ((km,d,c):t) =
>     if d > wn then bossaChords ((km,wn,c):(km,d-wn,c):t) else
>     if d==wn then f1 c :+: bossaChords t else f2 d c :+: bossaChords t where
>     f1 c = let c' = f2 en c in rest qn :+: c' :+: rest qn :+: c' :+: rest qn
>     f2 d c = chord $ map (\p -> note d $ pitch p) c

> bossaLead :: StdGen -> [TChord] -> (StdGen, Music Pitch)
> bossaLead g ts =
>     let ls = take (length ts - 1) (repeat False) ++ [True]
>         v = head $ toVoices ts
>         (g', v') = addFgToVoice jConsts (foreFunsJ defConsts) g v
>     in  (g', vsToMusic [v']) where
>     foreFunsJ c = [(0.5, f1 c), (0.5, f2 c)] :: [(Double, ForeFun)]
>     jConsts = CConstants 2 3 0.3 0.5 0.8 7


======================

Redefinition of nearest neighbor for modal chords:

> nearFallJ ::  EqClass JChord -> StdGen -> JChord -> (StdGen, JChord)
> nearFallJ e g (x,m) =
>     let ds = map (simpleDist x) (map fst e) :: [Double]
>         y = e !! (head $ findIndices (==minimum ds) ds)
>     in  (g, y)