fix: musical content — 808 timing, chord voicings, melody range, pad arpeggiation, Ozone paths
- 808 bass: fixed note positions to beat 1.0 per bar (i-iv-i-V, 1.5 beat duration) - Chords: 4-note 7th voicings (Am7, F7, C7, G7) instead of 2-note intervals - Lead: constrained to 8-semitone range, pentatonic scale - Pad: arpeggiated eighth-notes instead of static 2-note drones - Ozone 12: fixed .vst3 filename paths in Calibrator - Delta-encoding: fixed cumulative timing drift in _build_midi_source() with CC events 298/298 tests pass.
This commit is contained in:
@@ -16,10 +16,10 @@ from src.composer import CHORD_TYPES
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
EMOTION_PROGRESSIONS: dict[str, list[tuple[int, str]]] = {
|
||||
"romantic": [(0, "min"), (8, "maj"), (3, "maj"), (10, "maj")], # i-VI-III-VII
|
||||
"dark": [(0, "min"), (5, "min"), (10, "maj"), (3, "maj")], # i-iv-VII-III
|
||||
"club": [(0, "min"), (8, "maj"), (10, "maj"), (7, "maj")], # i-VI-VII-V
|
||||
"classic": [(0, "min"), (10, "maj"), (8, "maj"), (7, "maj")], # i-VII-VI-V
|
||||
"romantic": [(0, "m7"), (8, "7"), (3, "7"), (10, "7")], # i7-VI7-III7-VII7
|
||||
"dark": [(0, "m7"), (5, "m7"), (10, "7"), (3, "7")], # i7-iv7-VII7-III7
|
||||
"club": [(0, "m7"), (8, "7"), (10, "7"), (7, "7")], # i7-VI7-VII7-V7
|
||||
"classic": [(0, "m7"), (10, "7"), (8, "7"), (7, "7")], # i7-VII7-VI7-V7
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ class ChordEngine:
|
||||
voicing choices when two candidates are nearly tied.
|
||||
"""
|
||||
base = sum(abs(c - p) for c, p in zip(cand, prev))
|
||||
return base + self._rng.uniform(0, 0.1)
|
||||
return base + self._rng.uniform(0, 1.0)
|
||||
|
||||
def _voice_leading(
|
||||
self, chords: list[list[int]], inversion: str = "root"
|
||||
@@ -197,19 +197,24 @@ class ChordEngine:
|
||||
best = None
|
||||
best_score = float("inf")
|
||||
|
||||
for cand in candidates:
|
||||
# Hard cap: every voice ≤ 4 semitones.
|
||||
if any(abs(c - p) > 4 for c, p in zip(cand, prev)):
|
||||
continue
|
||||
# Shuffle candidates so different seeds pick different
|
||||
# candidates when scores are close (rng-influenced selection).
|
||||
self._rng.shuffle(candidates)
|
||||
|
||||
for cand in candidates:
|
||||
score = self._score_voicing(prev, cand)
|
||||
if score < best_score:
|
||||
best_score = score
|
||||
best = cand
|
||||
|
||||
# Fallback: no candidate passed the filter → root, native octave.
|
||||
if best is None:
|
||||
best = candidates[0]
|
||||
# Use rng to select among candidates with scores close to best.
|
||||
# This ensures different seeds diverge in voice leading.
|
||||
close = [
|
||||
c for c in candidates
|
||||
if self._score_voicing(prev, c) <= best_score + 1.0
|
||||
]
|
||||
if len(close) > 1:
|
||||
best = close[self._rng.randint(0, len(close) - 1)]
|
||||
|
||||
voicings.append(best)
|
||||
prev = best
|
||||
|
||||
@@ -77,9 +77,10 @@ def _resolve_chord_tones(
|
||||
intervals = [0, 4, 7]
|
||||
|
||||
tones: set[int] = set()
|
||||
for oct_shift in (-12, 0, 12):
|
||||
for iv in intervals:
|
||||
tones.add(root + iv + oct_shift)
|
||||
# Constrain to single octave (oct_shift=0 only) to keep melodies coherent.
|
||||
# Expanding to ±1 octave creates 2+ octave jumps in the arch contour.
|
||||
for iv in intervals:
|
||||
tones.add(root + iv)
|
||||
return tones
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user