{-# 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)