AlgoRhythm-0.1.0.0: src/Grammar/VoiceLeading.hs
{-# LANGUAGE ImplicitParams #-}
module Grammar.VoiceLeading (voiceLead) where
import Grammar.Utilities
import Grammar.Harmony
import Music
-- | Produce concrete chords out of a harmonic structure.
voiceLead :: (?harmonyConfig :: HarmonyConfig) => Music SemiChord -> IO (Music Chord)
voiceLead m' = do
vl <- foldl f (pure [(initC, t)]) ms
return $ fromList vl
where
initC = toBaseChord c
((c, t) : ms) = toList m'
f :: IO [(Chord, Duration)] -> (SemiChord, Duration) -> IO [(Chord, Duration)]
f cs' (sc, d) = do
cs <- cs'
c' <- smoothTransition initC (fst $ last cs) sc
return $ cs ++ [(c', d)]
-- | Get a basic voicing of a chord in a given octave.
toBaseChord :: (?harmonyConfig :: HarmonyConfig) => SemiChord -> Chord
toBaseChord = fmap (\pc -> (pc, baseOct ?harmonyConfig))
-- | Get all inversions of +-1 octave.
allInversions :: (?harmonyConfig :: HarmonyConfig) => SemiChord -> [Chord]
allInversions c =
let initC = toBaseChord c
n = length c
invs ch = take n $ iterate invert ch
in invs (initC ~> P8) ++ invs initC ++ invs (initC <~ P8)
-- | Smooth voice-leading from one chord to another (i.e. minimal pitch distance).
smoothTransition :: (?harmonyConfig :: HarmonyConfig) => Chord -> Chord -> SemiChord -> IO Chord
smoothTransition initC prevC curC =
chooseWith setWeight (allInversions curC)
where
-- | Set probability weight based on (inverse) pitch distance.
setWeight :: Chord -> Double
setWeight c = 1.0 / fromIntegral (2 * chordDistance initC c + chordDistance prevC c)