Repository navigation
Add plucked_string: a Karplus-Strong string with harmonic partials - #807
Merged
Merged
Conversation
A delay line a period long, closed through a first-order allpass for the fractional period and a one-pole loss filter. A pluck adds one period of shaped noise to the line. Its controls are the Resonator's less `structure`, mapped the same way, so one stands in for the other: an instrument that wants a nylon or steel string rather than a modal bank swaps the type and drops one control. - `freq` holds to within 3 cents from 82 Hz to 2 kHz at any brightness: the allpass is solved exactly at the fundamental for what the line and the loss filter's phase delay leave. - `brightness` sets how much faster partial k decays than the fundamental, `1 + h·(k - 1)` times, and the loss filter's pole is fitted to that at the 8th partial. A fixed pole cut a high, dark string's fundamental by 4% a period and killed it in 50 ms. - `damping` maps to the Resonator's ring times, and `in` is normalized to unity at the fundamental, as the Resonator's sustained path is. - `position` is a circular comb over the period, so plucking the middle cancels the even partials whole. - `with_lowest_freq` sizes the line for a host whose heap is short, as `Delay::with_max_time` does: 100 Hz at 48 kHz is 1.9 KB. Its cost does not grow with pitch or brightness; the Resonator's grows with its mode count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A one-pole lowpass leaves the burst's partials falling as 1/k, a sawtooth's, so a pluck buzzed at any brightness: a dark 220 Hz pluck measured an RMS frequency of 870 Hz, four times its pitch. A plucked string's shape falls as 1/k², so the burst now runs through two poles, and its noise gain is the exact one for two stages rather than one stage squared. The filters also run through the period once before the kept pass, so the burst ends where it starts and the loop has no seam to repeat. The same pluck now measures under 550 Hz, which `a_dark_pluck_has_no_buzz` holds. Pluck gain goes from 0.38 to 0.4: a unit pluck peaks between 0.31 (dark) and 0.87 (bright). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
A new operator,
plucked_string: an extended Karplus–Strong string. Its controls areresonator's lessstructure, mapped the same way, so an instrument swaps one for the other by type.brightness: partial k decays1 + h·(k − 1)times faster than the fundamental. The loss pole is fitted to that at the 8th partial, and the same control sets the pluck's colour.damping: the fundamental's ring time, onresonator's curve.in: normalized to unity at f₀.with_lowest_freq(hz): sizes the line, the wayDelay::with_max_timedoes.resonator's cost grows with its mode count.Wanted by ii-handhelds' Emotional Support Synth for a nylon-string sound. There it renders the four-string graph in 27% fewer instructions than four
resonators.Evidence
16
OpDrivertests inplucked_string.rs, among them:freqfrom 82 Hz to 2 kHz, dark and bright; partials 2–6 within 5 cents of harmonic.damping1 andbrightness1, from 20 Hz to 8 kHz; finite under NaN controls.The two bugs the first draft had, each with the test that caught it:
a_pluck_rings_at_its_freqfailed at 1046.5 Hz, dark. A fixed loss pole cut the fundamental 4% a period, so the string died in ~50 ms.After: the loss pole is fitted per partial, and the test passes.
a_dark_pluck_has_no_buzzmeasured an RMS frequency of 870 Hz on a dark 220 Hz pluck. A one-pole burst falls as 1/k, a sawtooth.After: a two-pole burst, settled over a period first, measures under 550 Hz.
Gates:
cargo testandcargo test -p reuben-core --features benchgreen;cargo fmt --all --checkandcargo clippy --all-targets -- -D warningsclean.reuben describe plucked_stringmatches the contract.breath-harp.json, with its resonators swapped forplucked_string, passesreuben validate.Not ear-checked: the tests prove the oracle, not the sound.
Merge Danger
Door: two-way
Additive: a new operator and its bench workload, with no existing operator changed.
Blast Radius: none
sync-docshas not been run for it: authoring.md, the library index and the operator lists don't mentionplucked_stringyet.🤖 Generated with Claude Code