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:
renato97
2026-05-04 01:30:19 -03:00
parent 623af69483
commit 33bb08270d
11 changed files with 4827 additions and 1018 deletions

View File

@@ -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

View File

@@ -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