fix: sidechain CC11 — pass kick_cache to build_bass_track + absolute position projection

Root cause: build_bass_track() never received the kick_cache in main().
Second issue: kick times were WAV-relative (0-12 beats) but bass expects
absolute positions (16+ beats). Added loop-duration projection to convert
relative → absolute positions across clip duration.

285 CC11 events now generated in output. 302/302 tests pass.
This commit is contained in:
renato97
2026-05-04 00:26:03 -03:00
parent 014e636889
commit e99fa231dd
3 changed files with 117 additions and 1 deletions

View File

@@ -265,6 +265,7 @@ def build_section_structure():
# ---------------------------------------------------------------------------
_kick_cache: dict[str, list[float]] = {}
_LOOP_DURATIONS: dict[str, float] = {}
_KICK_CONFIDENCE_THRESHOLD = 0.6
_CC11_DIP = 50
@@ -293,6 +294,7 @@ def _get_kick_cache(drumloop_paths: list[str], bpm: float) -> dict[str, list[flo
]
beat_dur = 60.0 / bpm
_kick_cache[path] = [t.time / beat_dur for t in kicks]
_LOOP_DURATIONS[path] = analysis.duration / beat_dur
except Exception:
_kick_cache[path] = []
@@ -758,12 +760,40 @@ def main() -> None:
drumloop_paths = sorted({c.audio_path for c in drumloop_track.clips if c.audio_path})
if drumloop_paths:
_get_kick_cache(drumloop_paths, bpm)
for path, kicks in _kick_cache.items():
print(f" Kick cache: {len(kicks)} kicks in {Path(path).name}")
# Project relative kicks to absolute timeline positions per clip
_abs_kicks: dict[str, list[float]] = {}
for clip in drumloop_track.clips:
path = clip.audio_path
if not path or path not in _kick_cache:
continue
rel_kicks = _kick_cache[path]
if not rel_kicks:
continue
loop_beats = _LOOP_DURATIONS.get(path, 8.0)
abs_positions: list[float] = []
loop_n = 0
while loop_n * loop_beats < clip.length:
offset = clip.position + loop_n * loop_beats
for k in rel_kicks:
abs_pos = offset + k
if abs_pos < clip.position + clip.length:
abs_positions.append(abs_pos)
loop_n += 1
if path in _abs_kicks:
_abs_kicks[path].extend(abs_positions)
else:
_abs_kicks[path] = abs_positions
_kick_cache.clear()
_kick_cache.update(_abs_kicks)
# Build tracks
tracks = [
build_drumloop_track(sections, offsets, seed=args.seed or 0),
build_perc_track(sections, offsets, seed=args.seed or 0),
build_bass_track(sections, offsets, key_root, key_minor),
build_bass_track(sections, offsets, key_root, key_minor, kick_cache=_kick_cache),
build_chords_track(sections, offsets, key_root, key_minor,
emotion=args.emotion, inversion=args.inversion),
build_lead_track(sections, offsets, key_root, key_minor, seed=args.seed or 42),