Files
reaper-control/.sdd/changes/fix-sidechain-cc11/proposal.md
renato97 e99fa231dd 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.
2026-05-04 00:26:03 -03:00

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_cache to build_bass_track() in main() (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, asserting midi_cc non-empty (catches regression)

Out of Scope

  • Refactoring module-level _kick_cache state
  • 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: CCEvent dataclass , ClipDef.midi_cc , RPPBuilder emits B0 0B E-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.rpp returns non-empty results after compose.py run
  • 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