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
Sentence
[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
> i = head $ findIndices (==ct) [I, II, III, IV, V, VI, VII]
> in map (s !!) $ map (+i) [0,2,4]
> toAsNoDim :: CType -> Mode -> [AbsPitch]
> toAsNoDim ct m =
> let s = getScale m ++ map (+12) s
> i = head $ findIndices (==ct) [I, II, III, IV, V, VI, VII]
> 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