{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE PostfixOperators #-}
module Grammar.Melody
( MelodyConfig (..), defMelodyConfig
, melody, mkSolo
) where
import Control.Arrow (first)
import Grammar.Types
import Grammar.Utilities
import Music
data NT = MQ
| Q
| MN
| N
| HT
| CT
| L
| AT
| ST
| R
deriving (Eq, Show)
melody :: Grammar () NT
melody = MQ |:
[
(MQ, 1, (== 0)) |-> R:%:0
, (MQ, 1, (== qn)) |-> Q:%:qn
, (MQ, 1, (== hn)) |-> Q:%:hn
, (MQ, 1, (== (hn^.))) |-> Q:%:hn :-: Q:%:qn
, (MQ, 25, (> (hn^.))) :-> \t -> Q:%:hn :-: MQ:%:(t - hn)
, (MQ, 75, (> wn)) :-> \t -> Q:%:wn :-: MQ:%:(t - wn)
, (Q, 52, (== wn)) |-> Q:%:hn :-: MN:%:qn :-: MN:%:qn
, (Q, 47, (== wn)) |-> MN:%:qn :-: Q:%:hn :-: MN:%:qn
, (Q, 1, (== wn)) |-> MN:%:en :-: N:%:qn :-: N:%:qn :-: N:%:qn :-: MN:%:en
, (Q, 60, (== hn)) |-> MN:%:qn :-: MN:%:qn
, (Q, 16, (== hn)) |-> HT:%:(qn^.) :-: N:%:en
, (Q, 12, (== hn)) |-> MN:%:en :-: N:%:qn :-: MN:%:en
, (Q, 6, (== hn)) |-> N:%:hn
, (Q, 6, (== hn)) |-> HT:%:(qn^^^) :-: HT:%:(qn^^^) :-: HT:%:(qn^^^)
, (Q, 1, (== qn)) |-> CT:%:qn
, (MN, 1, (== wn)) |-> MN:%:qn :-: MN:%:qn :-: MN:%:qn :-: MN:%:qn
, (MN, 72, (== qn)) |-> MN:%:en :-: MN:%:en
, (MN, 22, (== qn)) |-> N:%:qn
, (MN, 5, (== qn)) |-> HT:%:(en^^^) :-: HT:%:(en^^^) :-: HT:%:(en^^^)
, (MN, 1, (== qn)) |-> HT:%:(en^^^) :-: HT:%:(en^^^) :-: AT:%:(en^^^)
, (MN, 99, (== en)) |-> N:%:en
, (MN, 1, (== en)) |-> HT:%:sn :-: AT:%:sn
, (N, 1, (== hn)) |-> CT:%:hn
, (N, 50, (== qn)) |-> CT:%:qn
, (N, 50, (== qn)) |-> ST:%:qn
, (N, 45, (== qn)) |-> R:%:qn
, (N, 20, (== qn)) |-> L:%:qn
, (N, 1, (== qn)) |-> AT:%:qn
, (N, 40, (== en)) |-> CT:%:en
, (N, 40, (== en)) |-> ST:%:en
, (N, 20, (== en)) |-> L:%:en
, (N, 20, (== en)) |-> R:%:en
, (N, 1, (== en)) |-> AT:%:en
]
mkSolo :: (?melodyConfig :: MelodyConfig) => Music SemiChord -> Music NT -> IO Melody
mkSolo chs nts =
fromListM <$> go Nothing [] (synchronize (toList chs) (toList nts))
where
go :: Maybe Pitch -> [Duration] -> ListMusic (SemiChord, NT) -> IO (ListMusicM Pitch)
go _ _ [] = return []
go prevP approach (((ch, nt), t):rest) =
case nt of
HT -> do
nt' <- choose [(5, CT), (3, AT), (2, L)]
go prevP approach (((ch, nt'), t):rest)
AT -> if null rest then return [] else go prevP (approach ++ [t]) rest
_ -> do m <- interpretNT prevP approach ch nt t
(++) <$> pure m <*> go (fst $ last m) [] rest
interpretNT :: Maybe Pitch
-> [Duration]
-> SemiChord
-> NT
-> Duration
-> IO (ListMusicM Pitch)
interpretNT prevP approach ch nt t =
case nt of
R -> return $ (,) Nothing <$> (t : approach)
CT -> mkPitch prevP approach t ch
ST ->
let scales' = [(w, sc) | (w, sc) <- scales ?melodyConfig, all (`elem` sc) (toIntervals ch)]
in if null scales'
then interpretNT prevP approach ch CT t
else do sc <- choose scales'
mkPitch prevP approach t (head ch +| sc)
L -> let colors = colorTones ch
in if null colors
then interpretNT prevP approach ch CT t
else mkPitch prevP approach t colors
_ -> error $ "intrepret: incomplete grammar rewrite " ++ show nt ++ " <| " ++ show t
mkPitch :: Maybe Pitch -> [Duration] -> Duration -> [PitchClass] -> IO (ListMusicM Pitch)
mkPitch prevP approach t pcs =
let ps = [(pc#oct, w) | pc <- pcs, (w, oct) <- normally $ octaves ?melodyConfig]
setWeight (p', w') =
w' * (1.0 - (fromIntegral (pitchDistanceM prevP p') / 12.0))
in (fst <$> chooseWith setWeight ps) >>= approachPitch approach prevP t
approachPitch :: [Duration] -> Maybe Pitch -> Duration -> Pitch -> IO (ListMusicM Pitch)
approachPitch approach prevP t p = reverse <$> oneOf [move dir | dir <- directions]
where
move dir = first Just <$> zip (iterate (`dir` Mi2) p) (t : approach)
directions = case prevP of
Just p' -> if p' > p then [(<~)] else [(~>)]
Nothing -> [(~>), (<~)]
synchronize :: ListMusic SemiChord -> ListMusic NT -> ListMusic (SemiChord, NT)
synchronize [] _ = []
synchronize _ [] = []
synchronize ((ch, t):back) front =
let (ps', front') = takeTime front t
in [((ch, p'), t') | (p', t') <- ps' ] ++ synchronize back front'
takeTime :: ListMusic NT -> Duration -> (ListMusic NT, ListMusic NT)
takeTime ntz d
| d <= 0 = ([], ntz)
| otherwise = case ntz of
[] -> ([], [])
(nt@(_, d'):ntz') ->
let (ntz'', rest) = takeTime ntz' (d - d')
in (nt:ntz'', rest)
colorTones :: SemiChord -> [PitchClass]
colorTones (p:ps) = filter (\p' -> distancePc p p' `elem` colorIntervals) ps
where colorIntervals = [M3, Mi3, Mi7, M7, Mi9, M9, M13, Mi13]
colorTones [] = []
toIntervals :: SemiChord -> AbstractChord
toIntervals ch = P1 : (uncurry distancePc <$> zip ch (tail ch))
data MelodyConfig = MelodyConfig
{ scales :: [(Weight, AbstractScale)]
, octaves :: [(Weight, Octave)]
, chordWeight :: Weight
, approachWeight :: Weight
, colorWeight :: Weight
}
defMelodyConfig :: MelodyConfig
defMelodyConfig = MelodyConfig
{ scales = equally allScales
, octaves = equally allOctaves
, chordWeight = 10
, approachWeight = 5
, colorWeight = 3
}