Repository navigation
Let a host size the Delay's line and cap the Resonator's modes - #806
Merged
Merged
Conversation
…antiate `Delay` reserved a fixed 2 s line, allocated lazily on its first `process`. At 48 kHz that is 384 KB, which does not fit a bare-metal heap of a few hundred KB, so a firmware host could not use the operator at all. `Delay::with_max_time` takes the ceiling at construction; `Delay::new` keeps 2 s, so documents and every existing caller are unchanged. `time` clamps to the chosen ceiling, and `spawn` carries it to copies. The line is now allocated in `on_instantiate`, where the sample rate is fixed, rather than on the first `process`. The lazy branch stays as a guard for a box processed without a Plan. The ceiling is not reachable from a document: constants are read by the loader, not by the operator, so exposing it there would need that plumbing first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAsy4haLapatc2ssNNUZKo
The bank advanced all 32 modes every sample, including the ones `recompute` had muted for sitting past Nyquist. On a Cortex-M7 a four-string patch of these is most of a 32-frame block, and a host with a small speaker has no use for partials it cannot reproduce. `Resonator::with_modes(n)` caps the bank at construction; `Resonator::new` keeps 32, and `spawn` carries the cap to copies. Independently of the cap, the bank now advances only up to the last mode `recompute` left sounding, and clears the ring of any mode that leaves that range so a later retune down does not bring it back mid-ring. Muted modes already decayed to exact zero within two samples, so the output is bit-identical (an FNV hash of 3000 blocks, with a strike and a retune past Nyquist and back, matches the previous code). The loop walks slices cut to the active count instead of indexing below it, which removes the bounds checks a runtime bound would otherwise keep: the default 32-mode path is 12% fewer instructions than before (callgrind, one resonator, 3000 blocks: 53.4M -> 47.1M). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAsy4haLapatc2ssNNUZKo
- `with_max_time(NaN)` kept the NaN (`f32::clamp` passes it through), and `process` then clamped `time` against it, which panics on the render thread. A NaN now takes the 1 ms floor. - A 1 ms ceiling at a sample rate of 1 kHz or less made a one-sample line, and the read head's `clamp(1.0, len - 1)` became an empty range and panicked. The line is at least two samples. - The Delay module doc says what instantiate-time allocation costs a Swap: the new Plan holds a line for every Delay, survivors' discarded twins included. - The Resonator docs and operator-dev.md describe the mode cap, the skipped muted modes, and `on_instantiate` as the hook for config-dependent state generally, not only the Voicer's. - `instantiate_sizes_the_line_so_process_never_allocates_it` asserted only the size; it is now named for what it checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MAsy4haLapatc2ssNNUZKo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two construction-time ceilings a bare-metal host needs. They are for
ii-handhelds' Emotional Support Synth, a Daisy Seed patch with four resonators and an echo. Neither changes what a document produces, or what any existing caller gets.Delay::with_max_timeDelayreserved a fixed 2 s line, allocated lazily on its firstprocess. At 48 kHz that is 384 KB, more than the Seed's whole internal heap (about 287 KB). It was also an allocation on the audio thread for every new Delay.Delay::with_max_time(secs)takes the ceiling at construction:Delay::newkeeps 2 s.timeclamps to the chosen ceiling.spawncarries the ceiling to copies.f32::clampwould have passed it through to a panicking clamp inprocess.on_instantiate, where the sample rate is fixed, not on the firstprocess. The lazy branch stays as a guard for a box processed without a Plan.Resonator::with_modesThe problem. The bank advanced all 32 modes every sample, including the ones
recomputehad muted for sitting past Nyquist.The cap.
with_modes(n)caps the bank at construction.newkeeps 32, andspawncarries the cap to copies.Skipping muted modes. Separately from the cap, the bank now advances only up to the last mode left sounding. It clears the ring of any mode that leaves that range.
Output: bit-identical, with one exception. Two checks agree:
The exception is a mode that leaves and comes back exactly one frame later. Before, it kept one sample of its old ring; now it comes back silent, which is the intended behaviour.
Speed. The loop walks slices cut to the active count instead of indexing below a runtime bound, which removes the bounds checks. Measured with callgrind on one 32-mode resonator over 3000 blocks, instructions go from 53.4M to 47.1M (−12%).
Tests
New tests cover:
max_time;Locally,
cargo test,fmt --checkandclippy -D warningsall pass. CI, including the perf gate, was green on the first push.Follow-up, not in this PR
reverb.rsstill sizes its buffers on the firstprocess, which is the same audio-thread allocation this PR removes from Delay.🤖 Generated with Claude Code
https://claude.ai/code/session_01MAsy4haLapatc2ssNNUZKo