Post Processing Module to Link Grammar with OPTIC Functions Donya Quick and Paul Hudak Last modified: 19-Dec-2014 For paper: Grammar-Based Automated Music Composition in Haskell Post processing module to turn Terms into music using Euterpea. > module Kulitta.PostProc where > import Kulitta.EuterpeaSpecial > import Kulitta.PTGG > import Kulitta.Grammars.MusicGrammars > import Kulitta.ChordSpaces > import Data.List > import System.Random Intermediate types: (NOTE: AbsPitch = PitchNum) > type Key = (AbsPitch, Mode) > type RChord = (Key, Dur, CType) > type TChord = (Key, Dur, AbsChord) > type TNote = (Key, Dur, AbsPitch) > type Voice = [TNote] Accessing the members of a TNote: > tnK (k,d,p) = k > tnD (k,d,p) = d > tnP (k,d,p) = p > newP (k,d,p) p' = (k,d,p') The goal using these intermediate types is the following: INPUT STEP OUTPUT FUNCTION Seeds -----(grammar)-------> Sentence gen Sentence --(mode info)-----> [TChord] toAbsChords [TChord] ------------------> [Voice] toVoices [Voice] -------------------> Music Pitch vsToMusic or vsToMusicI > unTerm :: [Term a MP] -> [(Key, Dur, a)] > unTerm = map (\(a,mp) -> ((key mp, mode mp), dur mp, a)) . toPairs . expand [] > toChords :: [Term CType MP] -> [RChord] > toChords = unTerm > toAbsChords :: [Term CType MP] -> [TChord] > toAbsChords ts = map toAbsChord $ toChords ts > toAbsChord :: RChord -> TChord > toAbsChord ((k,m),d,c) = ((k,m), d, t k $ toAs c m) We also provide an alternate version that doesn't use diminished chords. > toAbsChordNoDim :: RChord -> TChord > toAbsChordNoDim ((k,m),d,c) = ((k,m), d, t k $ toAsNoDim c m) > toAbsChordsNoDim :: [Term CType MP] -> [TChord] > toAbsChordsNoDim ts = map toAbsChordNoDim $ toChords ts Conversion of a single chord to a mode rooted at zero: > toAs :: CType -> Mode -> [AbsPitch] > toAs ct m = > let s = getScale m ++ map (+12) s -- fininite scale > i = head $ findIndices (==ct) [I, II, III, IV, V, VI, VII] -- can be updated w/enum > in map (s !!) $ map (+i) [0,2,4] > toAsNoDim :: CType -> Mode -> [AbsPitch] > toAsNoDim ct m = > let s = getScale m ++ map (+12) s -- fininite scale > i = head $ findIndices (==ct) [I, II, III, IV, V, VI, VII] -- can be updated w/enum > in map (s !!) $ fixDim $ map (+i) [0,2,4] where > fixDim x = if optEq x [0,3,6] then t (head x) [0,3,7] else x Transposition using a key (to avoid C-major assignment only): > atTrans :: AbsPitch -> [(Key, Dur, AbsChord)] -> [(Key, Dur, AbsChord)] > atTrans a = map (\((k,m),d,c) -> (((k+a) `mod` 12,m), d, t a c)) map (\((k,m),d,c) -> ((fixK k a m,m),d, t (a `mod` 12) c)) The toCords functon does a similar thing, but returns a CType and its key/mode context without performing the conversion to AbsChord. > ctTrans :: AbsPitch -> [(Key, Dur, CType)] -> [(Key, Dur, CType)] > ctTrans a = map (\((k,m),d,c) -> (((k+a) `mod` 12,m),d,c)) map (\((k,m),d,c) -> ((fixK k a m,m),d,c)) > fixK k a Major = (k + a) `mod` 12 > fixK k a Minor = ((k + a) `mod` 12) + 12 Conversion of intermediate type to Music Pitch: > tChordsToMusic :: [TChord] -> Music Pitch > tChordsToMusic = line . map f where > f ((k,m),d, as) = chord $ map (\a -> note d (pitch a)) as ============ SPLITTING VOICES APART =========== The code here places TChords into a form more suitable for additional musical processing. A Voice is a list of pitches with duration and key/mode context. > toVoices :: [TChord] -> [Voice] > toVoices ts = > let (ks,ds,ps) = unzip3 ts > in if checkMatrix ps then map (\v -> zip3 ks ds v) $ Data.List.transpose $ ps > else error "(toVoices) chords must all have the same number of voices!" where > checkMatrix [] = True > checkMatrix (x:xs) = and $ map (==length x) $ map length xs This alternative version of the function turns the list of chords into a matrix by filling in holes with pitch number -1 (which will be interpreted as a rest). Chords are padded on the right. So, the progression [[0,4], [0,4,7]] would become [[0,4,-1], [0,4,7]]. > toVoices' :: [TChord] -> [Voice] > toVoices' ts = > let (ks,ds,ps) = unzip3 ts > in map (\v -> zip3 ks ds v) $ Data.List.transpose $ fillGaps ps where > fillGaps [] = [] > fillGaps cs = > let maxLen = maximum $ map length cs > f x = x ++ take (maxLen - length x) (repeat (-1)) > in map f cs > toNotes :: Voice -> Music Pitch > toNotes = line . map (\(k,d,p) -> note' d p) where > note' d p = if p<0 then rest d else note d (pitch p) > vsToMusic :: [Voice] -> Music Pitch > vsToMusic = > chord . map toNotes > vsToMusicI :: [InstrumentName] -> [Voice] -> Music Pitch > vsToMusicI is = > chord . zipWith (\i m -> instrument i m) is . map toNotes