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.
3.4 KiB
Proposal: Fix Sidechain CC11 — Zero Events
Intent
scripts/compose.py populates _kick_cache via _get_kick_cache() → DrumLoopAnalyzer.analyze() but never passes it to build_bass_track() at line 767. Result: kick_cache parameter defaults to {}, zero CC11 events generated in every .rpp. Drumloop WAV files exist on disk (verified — all 5 variants present). 302/302 tests pass because tests call build_bass_track(kick_cache={...}) directly, bypassing the broken main() wiring.
Scope
In Scope
- Pass
kick_cache=_kick_cachetobuild_bass_track()inmain()(1 line) - Add diagnostic log: kick count per drumloop after cache population (2 lines)
- Add integration test exercising
_get_kick_cache()→build_bass_track()full path, assertingmidi_ccnon-empty (catches regression)
Out of Scope
- Refactoring module-level
_kick_cachestate - Fixing double-build of
drumloop_track(separate optimization) - Changing CC11 duck depth, shape, or DrumLoopAnalyzer accuracy
Capabilities
New Capabilities
sidechain-cc-diagnostics: diagnostic output logs kick count per analyzed drumloop at composition time
Modified Capabilities
bass-generation:build_bass_track()now receives live kick cache in production path (was always{}); bass clips gain CC11 Expression ducking events
Approach
Fix (1 line, scripts/compose.py):
build_bass_track(sections, offsets, key_root, key_minor, kick_cache=_kick_cache),
Diagnostic log (after _get_kick_cache() call):
for path, kicks in _kick_cache.items():
print(f" Kick cache: {len(kicks)} kicks in {Path(path).name}")
Test: New test_bass_track_populates_cc_from_main_path in test_compose_integration.py — calls _get_kick_cache() with real drumloop paths, then build_bass_track(kick_cache=_kick_cache), asserts at least one bass clip has len(midi_cc) > 0.
Why existing tests missed it: All 4 sidechain tests pass kick_cache explicitly to build_bass_track(), so the main() wiring gap is untested.
Verified non-causes:
- Pipeline:
CCEventdataclass ✅,ClipDef.midi_cc✅,RPPBuilderemitsB0 0BE-lines ✅,build_bass_track()filter+triplet logic ✅ - Disk: all 5 drumloop WAV files exist at
ABLETON_DRUMLOOP_DIR
Affected Areas
| Area | Impact | Description |
|---|---|---|
scripts/compose.py |
3 lines added/changed | Pass kick_cache, diagnostic log |
tests/test_compose_integration.py |
+1 test | Full-path kick cache → bass CC regression test |
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| DrumLoopAnalyzer fails on WAV | Med | try/except returns [] per path — no crash, just no ducking for that section |
| librosa unavailable on CI | Low | Already a project dependency; existing analyzer tests pass |
| CC events malform REAPER playback | Very Low | Valid delta-encoded E-lines interleaved with notes |
Rollback Plan
Remove kick_cache=_kick_cache argument. Reverts to zero CC events (current behavior). No schema changes.
Dependencies
None — pure wiring fix using existing code paths.
Success Criteria
rg "B0 0B" output/test.rppreturns non-empty results aftercompose.pyrun- Diagnostic prints kick counts (e.g., "Kick cache: 32 kicks in 90bpm reggaeton antiguo drumloop.wav")
- All 302 existing tests pass
- New integration test fails before fix, passes after