Skip to content

feat(editor): onset detection strip — blocky attack markers over the waveform - #95

Merged
byrongamatos merged 2 commits into
mainfrom
feat/editor-onset-strip
Jul 6, 2026
Merged

feat(editor): onset detection strip — blocky attack markers over the waveform#95
byrongamatos merged 2 commits into
mainfrom
feat/editor-onset-strip

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

A new Onsets toolbar toggle (Shift+W, both shortcut profiles) draws amber blocks over the waveform band where sharp attacks are detected in the recording — a visual hint of where notes/beats likely live while placing notes by eye.

  • Client-side detection from the existing waveform RMS cache — no server round-trip, no new dependencies. An onset fires where loudness rises sharply above a sliding local baseline, gated by an absolute noise floor and a ~50 ms refractory window so one attack registers exactly once (drum flams ~60 ms apart still read as two). Block brightness and height scale with attack strength, so ghost hits stay visible but understated.
  • Independent of the waveform toggle: with the waveform on it's an overlay; with the waveform hidden (W off + Onsets on) it becomes a pure "blocky" detection view.
  • Display only — the strip never places or moves notes. The analysis is cached per audio load and invalidated when audio is replaced.
  • typeof guards keep drawWaveform extractable by the existing waveform_render geometry test (same pattern as the editorWaveformVisible guard).

Verification

  • node --check screen.js clean
  • New tests/onset_strip.test.js — 7 cases via the @pure:onset-strip block (isolated attacks detected once each, refractory single-fire within one burst, two hits at flam spacing both fire, silence/noise-floor rejection, slow swells are not onsets, strength ordering, degenerate inputs)
  • All 26 JS test files pass, including the waveform_render geometry test against the modified drawWaveform

🤖 Generated with Claude Code

https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu

Summary by CodeRabbit

  • New Features

    • Added an Onsets toolbar toggle and Shift+W shortcut to show an onset detection strip in the editor.
    • The strip renders detected attack blocks (as an overlay or blocky view) and never places notes, updating automatically when audio changes.
    • Onset detection is generated client-side from the waveform RMS cache with noise-floor gating and a short refractory period, with results cached per audio load.
  • Tests

    • Added automated tests covering quiet audio, repeated attacks, refractory behavior, ramp rejection, and onset strength scaling.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ad24d8e4-bbbf-412d-8110-38490e250a72

📥 Commits

Reviewing files that changed from the base of the PR and between f1e1fea and 0387aa1.

📒 Files selected for processing (1)
  • screen.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • screen.js

📝 Walkthrough

Walkthrough

This PR adds an onset detection strip to the waveform editor, with pure RMS-based onset detection, independent strip rendering, a localStorage-backed toggle and shortcut, tests for the detector, and changelog documentation.

Changes

Onset detection strip

Layer / File(s) Summary
Pure onset detection logic and cache invalidation
screen.js
Implements _onsetTimesFromPeaksPure for transient times/strengths from RMS data and clears _onsetCache when waveform peaks are recomputed.
Onset strip rendering and toggle infrastructure
screen.js
Adds onset-strip drawing, keeps it renderable when the waveform is hidden, and adds lazy caching, toggle state, button refresh, and the global toggle function.
Keyboard shortcut and UI wiring
screen.js, screen.html
Adds the Shift+W onset-strip command, wires EOF and feedback dispatch, handles EOF execution, and adds the toolbar button.
Onset detector tests and changelog entry
tests/onset_strip.test.js, CHANGELOG.md
Adds the detector test harness and assertions, plus the changelog entry describing the feature.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant editorToggleOnsetStrip
  participant DrawWaveform
  participant OnsetCache

  User->>editorToggleOnsetStrip: Click Onsets / Shift+W
  editorToggleOnsetStrip->>editorToggleOnsetStrip: persist toggle state
  editorToggleOnsetStrip->>DrawWaveform: trigger draw()
  DrawWaveform->>OnsetCache: request onset data
  OnsetCache->>OnsetCache: compute via _onsetTimesFromPeaksPure if needed
  OnsetCache-->>DrawWaveform: return onset times and strengths
  DrawWaveform->>DrawWaveform: render onset blocks
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main editor feature: an onset detection strip with blocky attack markers over the waveform.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-onset-strip

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
screen.js

ast-grep timed out on this file


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
screen.js (1)

1359-1365: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Onset overlay silently skipped when the visible pixel span collapses.

drawOnsets() is correctly called on the "waveform hidden" and "no peaks" early-return paths (lines 1352, 1357), and at the end of the function (line 1411), but the pre-existing if (xHi <= xLo) return; at line 1365 returns without calling drawOnsets(). In that edge case (e.g. degenerate zoom/pixel range) the onset overlay won't render even though the waveform is visible and peaks exist — undermining the PR's stated goal of the strip working as an overlay independent of the rest of the waveform draw.

🐛 Proposed fix
-    if (xHi <= xLo) return;
+    if (xHi <= xLo) { drawOnsets(); return; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@screen.js` around lines 1359 - 1365, The `drawWaveform` path currently exits
early on the `if (xHi <= xLo) return;` check without ever reaching
`drawOnsets()`, so the onset overlay is skipped in collapsed-span edge cases.
Update this branch so the onset overlay still renders when the waveform is
otherwise visible and peaks exist, by calling `drawOnsets()` before returning or
by restructuring the early-return handling in `drawWaveform` around the
`xLo`/`xHi` clamp logic.
🧹 Nitpick comments (1)
tests/onset_strip.test.js (1)

17-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

new Function() extraction is test-only and low-risk here, but fragile.

Static analysis flags dynamic code execution and non-literal fs path, but both operate on a fixed, repo-owned path (__dirname-derived) and the repository's own trusted screen.js — not attacker-controlled input, so there's no real injection vector. The bigger practical risk is fragility: any edit to the @pure:onset-strip markers or surrounding syntax in screen.js silently breaks this extraction. Consider exposing _onsetTimesFromPeaksPure via a small typeof module !== 'undefined' && (module.exports = { _onsetTimesFromPeaksPure }) guard in screen.js (mirroring the typeof guard already added for drawWaveform per the PR summary) instead of regex+Function extraction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/onset_strip.test.js` around lines 17 - 26, The onset-strip test is
relying on brittle regex-plus-Function extraction from screen.js to access
_onsetTimesFromPeaksPure. Expose that helper directly from screen.js using the
existing typeof module guard pattern already used for drawWaveform, so the test
can import it without parsing source text. Update tests/onset_strip.test.js to
require the exported symbol instead of matching the `@pure`:onset-strip markers
and constructing a Function.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@screen.js`:
- Around line 1359-1365: The `drawWaveform` path currently exits early on the
`if (xHi <= xLo) return;` check without ever reaching `drawOnsets()`, so the
onset overlay is skipped in collapsed-span edge cases. Update this branch so the
onset overlay still renders when the waveform is otherwise visible and peaks
exist, by calling `drawOnsets()` before returning or by restructuring the
early-return handling in `drawWaveform` around the `xLo`/`xHi` clamp logic.

---

Nitpick comments:
In `@tests/onset_strip.test.js`:
- Around line 17-26: The onset-strip test is relying on brittle
regex-plus-Function extraction from screen.js to access
_onsetTimesFromPeaksPure. Expose that helper directly from screen.js using the
existing typeof module guard pattern already used for drawWaveform, so the test
can import it without parsing source text. Update tests/onset_strip.test.js to
require the exported symbol instead of matching the `@pure`:onset-strip markers
and constructing a Function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d56aa8b0-353d-4eee-9484-002d2f9d2027

📥 Commits

Reviewing files that changed from the base of the PR and between e067f25 and f1e1fea.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • screen.html
  • screen.js
  • tests/onset_strip.test.js

ChrisBeWithYou and others added 2 commits July 6, 2026 21:44
…waveform

Adds an "Onsets" toolbar toggle (Shift+W in both shortcut profiles):
amber blocks over the waveform band mark where sharp attacks are
detected in the recording — a visual hint of where notes/beats likely
live while charting by eye.

- Detection is client-side from the existing waveform RMS cache (no
  server round-trip, no new deps): an onset fires where loudness rises
  sharply above the local sliding baseline, gated by an absolute noise
  floor and a ~50 ms refractory gap so one attack registers exactly
  once. Block brightness/height scale with attack strength.
- Independent of the waveform toggle: overlay with the waveform on, or
  a pure "blocky" view with the waveform hidden (W off + Onsets on).
- Display only — the strip never places notes (design D22). Analysis
  cached per audio load, invalidated by computeWaveform on replace.
- typeof guards keep drawWaveform extractable by the existing
  waveform_render geometry test.

Tests: tests/onset_strip.test.js (7 cases: isolated attacks, refractory
single-fire, flam spacing, silence/noise floor, slow-swell rejection,
strength ordering, degenerate inputs). node --check clean; all 26 JS
test files pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JEoFeTPSnz4NpwwCG52hnu
Two per-frame hot-path fixes in _drawOnsetStrip (both reviewer-confirmed LOW
perf, behavior unchanged):

1. _onsetStripEnabled() no longer reads localStorage on every draw()/frame.
   The flag is cached in a module-scope var, seeded once from storage and
   kept in sync by _editorToggleOnsetStrip.
2. _drawOnsetStrip no longer scans the whole time-sorted onsets array each
   frame. Since timeToX is monotonic, binary-search the first visible onset
   and break past the visible window. Rendered output is identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants