Skip to content

Slice 12 — Beat Grid: detection, nudge, energy (#13) - #32

Merged
cbaucom merged 1 commit into
mainfrom
feat/13-beat-grid-detection-nudge-energy
Jun 11, 2026
Merged

cbaucom merged 1 commit into
mainfrom
feat/13-beat-grid-detection-nudge-energy

Conversation

@cbaucom

@cbaucom cbaucom commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Closes #13

Approach

Pure-module Beat Grid implementation in src/beat-grid/ with planner integration.

Detection (detection.ts): energy-based onset detection (RMS per 512-sample hop → positive first-difference) followed by autocorrelation over the 60–200 BPM lag range. Phase accumulation then finds the first-beat offset. All math is parameter-free and tested via synthetic click tracks.

Nudge (nudge.ts): snaps a slide's target duration to the nearest beat multiple. Energy applies a multiplier before snapping (calm 1.5×, medium 1.0×, punchy 0.67×) so average durations differ by energy level while energy=medium always stays within ±½ beat interval of the passed-in target.

Planner wiring (planner.ts): new optional beatGrid? parameter; beatSyncActive = !!beatGrid && settings.beatSync !== false; getDuration calls nudge for non-title, non-video slides. Energy is read via resolved(slide).energy so per-slide overrides are honoured correctly.

Schema (schema.ts): beatGridCache?: BeatGrid added to SlideshowJson for future persistence.

Adapter (adapter.ts): thin decodeMono wrapper over AudioContext.decodeAudioData for use in the next UI-integration slice.

Acceptance criteria

  • Given a synthesized click track of known BPM, when analyzed, detected BPM is within ±2% and offset within ±30ms — tested in detection.test.ts at 90 and 120 BPM
  • Given beat sync on, when planned, every slide boundary lands on a beat and each duration deviates from its target by at most half a beat interval — tested in planner.test.ts "beat sync on (AC2)"
  • Given beat sync off, when planned, durations equal targets exactly — tested in planner.test.ts "beat sync off (AC3)"
  • Given each energy level on the same timeline+track, when planned, average slide duration differs in the expected direction — tested in both nudge.test.ts and planner.test.ts "beat sync energy effect (AC4)"

Code review verdict

APPROVE — one confirmed finding fixed: energy now reads through resolved(slide) instead of settings directly, so per-slide energy overrides in SlideOverrides are honoured. Also added Math.max(lagMin, ...) guard on lagMax for non-standard sample rates.

Follow-ups noted (out of scope)

  • Wiring decodeMono + detectBeatGrid into the editor shell (UI integration slice)
  • Persisting beatGridCache through slidesToJson / read-back in Project Store
  • UI controls for beatSync toggle and energy selector in GlobalSettingsPanel

)

- detectBeatGrid: energy-based onset detection + autocorrelation; detects BPM
  within ±2% and first-beat offset within ±30ms from synthetic click tracks
- nudge: snaps slide target durations to nearest beat; energy multipliers
  (calm 1.5×, medium 1.0×, punchy 0.67×) produce directionally different
  average durations (AC4) while energy=medium preserves ≤half-beat deviation (AC2)
- planner wired with beatGrid? + beatSync toggle: off returns exact targets (AC3);
  energy reads through resolved(slide) so per-slide overrides are honoured
- BeatGrid type, decodeMono adapter, and beatGridCache in SlideshowJson schema
  lay the ground for UI integration in the next slice

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cbaucom
cbaucom merged commit 60606b5 into main Jun 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slice 12 — Beat Grid: detection, nudge, energy

1 participant