Skip to content

perf(core): decimate once per channel after the stereo mix - #4

Merged
YueyuHoshizora merged 3 commits into
mainfrom
claude/project-thread-s0g320
Oct 21, 2026
Merged

YueyuHoshizora merged 3 commits into
mainfrom
claude/project-thread-s0g320

Conversation

@YueyuHoshizora

@YueyuHoshizora YueyuHoshizora commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by 小語 · project thread

Summary

  • renderVoice pans each oversampled sample into synth-level mixLeft/mixRight buffers. render then runs one shared decimator per output channel after the mix, instead of one per voice. Per-voice filters state is removed.
  • Dual-mono fast path: while both channel inputs are identical (centered pan), only the left filter runs. The right state is copied the first time the inputs diverge.
  • Release-tail detection is envelope-based. A voice retires as soon as all operator releases end (releaseEnd), instead of waiting for its own filter state to drain. The shared decimator drains the tail. Once nothing is sounding, buffered history below the −96 dB floor (decimatorPeak) snaps to zero. A live but silent voice simply flushes the finite FIR to exact zeros. The empty-synth fast path waits for idle (decimatorIdle).
  • panic() resets the shared decimator. Non-finite decimator output resets it and increments errorCount.
  • Merged with main after perf(core): polyphase half-band FIR decimation #5: the half-band FIR from perf(core): polyphase half-band FIR decimation #5 now runs post-mix (158 state scalars per channel instead of per voice).

Behavior change

Note gain, expression, pan and steal fades are now applied before the decimator. A hard gain step (e.g. expression: 0) is band-limited and rings through the FIR (≤26 output frames) instead of cutting instantly, and ended fires at the envelope end.

Test changes:

  • Tests asserting instant output-rate cuts now skip a 64-frame settle window.
  • Ramp-timeline tests use a derived bound s·p·Σk|h[k]|/4.
  • NaN-injection tests corrupt phases instead of the removed filters.

New tests:

  • Linear post-mix equivalence: the mix equals the per-voice sum before saturation, for all three profiles, with pan diverging after a centered start.
  • Envelope retirement, followed by exact idle silence.

Benchmark

Focused Synth.render loop (48 kHz, 128 frames, 1000 blocks × best-of-7, median of 3 interleaved runs, Node 22, cloud x64), compared with main after #5 (per-voice FIR):

Case main ms/block this PR Δ
standard × 8 0.677 0.611 −10%
standard × 32 2.772 2.477 −11%
high × 8 1.115 0.996 −11%
high × 32 4.626 4.154 −10%
eco × 32 1.737 1.719 −1%
standard × 1 0.085 0.096 +14%
eco × 1 0.056 0.066 +18%

Against the earlier per-voice Butterworth base the gains were 9–19%. A single voice costs about 0.01 ms/block more (mix-buffer overhead), well under 1% of the 2.67 ms deadline. npm run benchmark on this host is too noisy (±20% between same-build runs) to resolve these.

Verification

  • npm test: 443/443 pass after the merge.
  • npm run typecheck, npm run sound-quality and npm run security pass.
  • windows-arm was red on main's docs-build tests before perf(core): polyphase half-band FIR decimation #5. If it still fails here, it is that same issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_012VX1mXsKvNnkr22msEbfwA

Pan and sum voices at the oversampled rate, then run one shared
decimator per output channel instead of one per voice; a centered mix
filters once until the channels diverge. Voices retire on their
envelope end, and the shared decimator drains the tail and snaps a
silent channel to exact zero below the -96 dB floor.

Note gain, expression, pan and steal fades now apply before the
decimator, so hard gain steps ring for a few frames; tests that
asserted instant output-rate cuts now allow that settle window or a
derived ramp bound.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VX1mXsKvNnkr22msEbfwA
@YueyuHoshizora YueyuHoshizora self-assigned this Oct 21, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-21T19:33:48.297370Z 24ad739 New commits
🔒 Security Review ✅ Completed 2026-10-21T16:47:48.336682Z 9e9d348 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copy link
Copy Markdown
Collaborator Author

windows-arm failure is not from this PR: the same job is red on main (17bd3a4, run 37132511410), the commit that introduced it. All 5 failing tests are in test/docs-build.test.ts, which this diff does not touch. Two of them write fixture files whose names contain ?, which is invalid on Windows (ENOENT). The other three fail with Missing declaration for public export opm.js:compile, which looks like a path-separator mismatch in scripts/docs-build.ts declaration lookup. All DSP/core tests pass on that runner (437/442). No fix exists yet. It needs a separate Windows-portability change to the docs-build test/script, so I'm not widening this PR. I'm not re-running because the failure is deterministic.


Generated by Claude Code

Resolve conflicts with the FIR state layout: tail checks use
decimatorPeak, and sub-floor history is cleared only once no voice,
fade or spill is sounding. A live silent voice now flushes the finite
FIR to exact zeros instead, keeping envelope tails intact. Docs and
CHANGELOG describe the combined design.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VX1mXsKvNnkr22msEbfwA

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94ef9e5ee0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/core/synth.ts
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012VX1mXsKvNnkr22msEbfwA
@YueyuHoshizora
YueyuHoshizora merged commit 7e51724 into main Oct 21, 2026
14 checks 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.

2 participants