Crash cymbal sounds like a pitched bell: findings from the cymbal-scatter spike #91

Open
opened 2026-09-13 19:52:40 +00:00 by bcox · 0 comments
Owner

patches/crash.h sounds like a struck bell, not a crash: very coherent, too much fundamental, and decidedly pitched. As bcox put it, a drummer wouldn't hit it unless the band were in the same key. The final ringout is a tuned bell with a distinct pitch.

A listening spike on 2026-09-13 found out why, and how far the engine gets without new physics. This ticket records what it found, so a spec can start from it. Nothing here is merged or scheduled.

Where the spike is

Commit a1b22ac on local branch worktree-cymbal-scatter (worktree .claude/worktrees/cymbal-scatter), one WIP commit off main at fd59eca. Not pushed. Every change is marked SPIKE (cymbal-scatter):

  • dsp/modal_loop.h: SetScatter and ScatteredPickup, the pickup-side phase noise and gain tilt.
  • dsp/mode.h and dsp/mode_bank.h: read-only accessors for each mode's quadrature state and pickup weight.
  • dsp/modal_layer.h: note-on plumbing, with spike_* fields on ModalPatch. They aren't serialized.
  • materials/spec.h and materials/generators.h: plates honor second_group_ratio, with their own spike_second_aspect, plus spike_low_scatter.
  • patches/crash.h: BuildCrashSpike(CrashSpike), where every knob below is a field.
  • patches/factory.h: about 60 crash-* variant rows. These break the bank tests. The suite hasn't been run on this branch.
  • tools/spike_lowmodes.cc: prints a plate table's lowest modes and how far they sit from a harmonic series.

To render any variant: bazel run //patches:audition -- --patch crash-r-dm50 <dir>.

The favorite so far is crash-r-dm50:

  • no noise-wash layer;
  • aspect 1.53, plus a second plate group at aspect 1.27 × 1.19, with low_scatter 0.12;
  • pickup phase noise with floor 1.0 rad RMS and depth 2.0 × envelope, no index tilt, and noise cutoff = 2 × each bin's mean frequency;
  • pickup tilt 0.9 + 0.6 × velocity;
  • stick contact of 0.4 ms per segment, 0.8 ms in total;
  • material damping −0.5, with the lowest mode's T60 at 3 s;
  • trim +6 dB.

bcox found crash-r-dm30 nearly as good.

How the numbers were taken

Every figure is from key 60 and the audition renderer, summed to mono:

  • Band levels: ffmpeg volumedetect mean dB over the stated window, with cascaded 2-pole filters setting the bands.
  • Flatness and crest: from aspectralstats.

Flatness and crest are proxies. Each direction was decided by listening, and the verdicts are quoted.

1. Break up the modes' phase on the pickup, never on the resonator

The bell quality is a linear mode bank: steady lines, fixed phase relationships, exponential decay. Randomizing phase addresses it, and the spike tried three ways to do that.

  • v1: a random rotation of each mode's state every sample. The accumulated phase random-walks, and that puts its energy at slow rates. bcox: "I can hear the phase crinkling around; that modulation would need to be much higher frequency."

  • v2: a bounded, audio-rate phase deviation applied to the state. Each mode got a noise skirt, and the result sounded better. It broke the felt choke. The felt damper stays passive only if it reads and pushes the same state, which CoupledMode::Process's comment derives. Rotating the state between that read and the next push breaks the pairing. At amplitude_tilt 0.35 with a 1 rad deviation, a full choke left the tail louder than no choke:

    window choked unchoked
    4.5–5.0 s −64.9 dB −68.9 dB
    5.5–6.0 s −67.7 dB −77.5 dB

    At 0.3 rad it was roughly neutral.

  • v3: the same deviation applied when reading the pickup. Each mode is heard as cos φ · x − sin φ · y, so the resonator, both sites and both junctions never see φ. The choke goes back to behaving like the shipped one:

    patch 4.5–5.0 s (choked / unchoked) 5.5–6.0 s (choked / unchoked)
    shipped crash −58.0 / −56.1 −68.9 / −64.1
    v3 with pickup tilt 0.45 −64.6 / −62.6 −75.4 / −70.6

    An early choke (1.2→1.3 s) takes the v3 tail 11.3 dB below its unchoked level by 5.5–6 s.

The noise shape that worked: each bin gets one one-pole-lowpassed noise sequence, scaled to 1 RMS. The deviation is (floor + depth × envelope) × noise in radians, clamped at 0.24 cycles. The noise cutoff is proportional to the bin's mean frequency.

One number disagrees with the patch's own notes. crash.h's header records the shipped choke as an energy ratio of 0.097 over 8192 samples at 4.5 s. The windowed means above show only 2–5 dB. The two measurements use different methods, and this isn't resolved.

2. amplitude_tilt is the wrong control for "less fundamental"

materials::ApplyScatterAndTilt multiplies each mode's radial profile by ratio^tilt. That weakens the stick, the hand and the pickup together, so the level you hear moves roughly as gain², and the choke loses its grip. With amplitude_tilt 0.35 and no scatter, the choke changed the tail by only −0.4 dB (4.5–5 s) and −1.0 dB (5.5–6 s).

A pickup-only tilt, with gain per bin = (bin mean ratio)^tilt normalized to the largest, leaves the stick and the hand alone. It's also loop-free, so it's safe on a live macro.

It's a weak lever, though, because it can only turn things down. Going from 0.3 to 0.9 moves the balance only 2–3 dB and costs about 7.5 dB of overall level, with trim already at +6 dB.

Velocity. Adding 0.6 × velocity to the pickup tilt gives about 4 dB of velocity-driven swing in the lows' share of the total: −12.0 → −15.9 dB from velocity 0.3 to 1.0. That's the right direction. Real cymbals have much more fundamental on a soft stroke and much less on a hard one.

The shipped-style patch runs the other way. In crash-pk60-*, which has a fixed pickup tilt of 0.6 and still has the noise wash, the lows' share rises with velocity: −11.4 → −8.3 dB. The suspected cause is the wash layer's own velocity scaling. That isn't verified.

3. The plate's aspect ratio is why it's pitched

GeneratePlate enumerates (p² + (q·aspect)²) / (1 + aspect²). At aspect 1.41 ≈ √2, that expression is (p² + 2q²)/3, which lands on thirds. With the shipped ratio_scatter 0.02, the lowest modes come out at:

1 : 1.979 : 2.962 : 3.566 : 3.929 : 5.562 : 5.986 : 6.200

That's nearly a harmonic series. Modes 2–8 average 66 cents from integer multiples of the lowest. √3 (1.73) has the same problem in quarters. The p = q diagonal (1, 4, 9, 16…) is exact at every aspect, which the header already notes.

Denser, irregular lows. A second plate group at an unrelated frequency ratio, with its own non-√n aspect, plus a scatter weighted by 1/ratio so the low modes move most:

layout modes below 16× fundamental top mode off-harmonic (modes 2–8)
shipped: aspect 1.41 21 83× 66 cents
aspect 1.53 only 22 81× 108 cents
1.53 + second group 1.27 × 1.19 39 53× 169 cents
same, low scatter 0.12 (dense-f) 42 49× 149 cents

Below 2 kHz, spectral crest over 0.3–2 s falls from 124 (single group) to 89 (dense-f). Aspect 1.53 alone did not reduce crest (130).

What the second group costs:

  • Top reach drops by about 40%, from about 22 kHz to about 14 kHz for the top mode at key 60 (computed from the table's ratios).
  • Lows rise by about 5 dB as a share of the total.
  • Velocity swing drops from about 4 dB to about 1.5 dB.

bcox: "dense-f sounds great but needs more sibilance."

Remove the noise wash. Removing the unvoiced layer drops the single-group layout (crash-cq2-*) by 12 dB above 8 kHz, but drops dense-f by only 6–7 dB, because dense-f's mode skirts reach higher. bcox preferred dense-f without it. The wash is also hand-drawn and nearly gone by 1.5 s, and a choke can't reach it.

4. Stick contact time is the brightness and velocity lever

This builds on dense-f with no wash, pickup tilt 0.9, and 2× noise cutoff. Mean dB over 0.3–2 s:

total contact overall <500 Hz 500 Hz–2 kHz 2–5 kHz 5–10 kHz >10 kHz peak
1.6 ms (shipped) −39.3 −49.5 −49.2 −49.8 −49.1 −45.1 −16.8 dBFS
0.8 ms −32.7 −45.6 −40.9 −43.0 −42.6 −38.4 −11.2 dBFS
0.4 ms −27.8 −42.7 −36.1 −38.3 −37.4 −33.0 −4.8 dBFS

bcox: 0.8 ms "sounds like a medium hit", and 0.4 ms "a smash".

A shorter contact adds real high-frequency excitation instead of attenuating the lows. That's also what happens physically on a harder stroke. Velocity → contact time is the honest velocity mapping. Whether the modal layer can modulate contact time today hasn't been checked.

5. The ringout is pitched because the lows ring longest

AddMode's decay law is decay_seconds × ratio^−damping. With damping ≥ 0, the lowest modes outlast everything, and the scatter's envelope term fades as the ring decays. So the ringout is the few low lines, clean. Negative damping makes the lows die first. All rows below are the 0.8 ms hit, rendered to 9 s:

damping lowest mode T60 ringout (5–8 s) level <1 kHz share >5 kHz share <2 kHz crest
0.22 (shipped) 8 s −80 dB (voice freed at 8.3 s) −7.0 dB −10.0 dB (too quiet to measure)
0.0 8 s −67 dB −10.5 dB −3.4 dB 58
−0.15 6 s −60 dB −16.5 dB −1.6 dB 31
−0.3 4 s −56 dB −16.9 dB −1.3 dB 31
−0.5 3 s −45 dB −16.4 dB −1.2 dB 30

bcox: dm30 and dm50 are "both pretty good, dm50 may be a little better", and "the dm15 range is also useful."

Over the 2–3.5 s tail, damping 0.22 → 0.0 raises the >5 kHz share by 2.8 dB. Moving the phase noise from its floor onto the envelope term lowers the tail's >3 kHz flatness from 0.75 to 0.66 (both at damping 0.10); bcox had called the tail "a bit white".

Proposed controls

control what it drives status
Choke site-1 felt drive Exists (macro 1).
Brightness pickup-only gain tilt by frequency ratio Loop-free, so safe to sweep live.
Ringout decay tilt by frequency ratio, spanning damping about −0.15 to −0.5 Read per note, not live. See the questions below.
Velocity stick contact time, about 0.8 ms (medium) to 0.4 ms (smash) Modulation route unchecked.

Two patch-level choices underlie all four: a plate layout with no √n aspect plus a second, unrelated group, and pickup-side phase noise.

Open questions and constraints

  • What does the ringout knob hold constant? The renders shortened the lowest mode's T60 by hand as damping went negative: 6 / 4 / 3 s. One knob needs one invariant. The candidates are the lowest mode's T60, the top mode's T60, or a cap tied to voice lifetime. From the decay law, not measured, dm50's top mode (ratio ≈ 54) rings about 3 × 54^0.5 ≈ 22 s, and that holds a voice slot the whole time.
  • ModalPatch::damping_tilt isn't the ringout knob as it stands. It tilts by array position, 2^(−tilt·m/count), and a two-group table restarts at low ratios halfway through the array. It would give the second group's low modes long decays. The knob has to tilt by frequency ratio. The destination-enum comment also keeps damping tilt off the modulation list as a note-on warp the refresh "cannot afford." Reading it at note-on respects that. A live version would reopen the decision: by count it's one exp lookup and one multiply per refreshed mode, but it isn't timed.
  • Voice-slot size. plugin/program.h's sizeof(ModalLayerType) <= sizeof(Layer) guard rejected one weight per mode (512 B). It also rejected two 16-bin tables. The spike runs 8 bins, with 16 modes sharing each noise sequence, and packs its scalars to fit. A real design has to budget this, or put per-mode data such as log ratios in the shared ModeTable.
  • Unmeasured:
    • the choke on the dense layout and on negative damping;
    • retirement and voice lifetime;
    • fabric cost, which is counted above, not timed.
  • Relation to #58. The hi-hat's felt damper already self-oscillates there. Any change near the junction should be checked against it.

What only the physics can do

Everything above is shaped at note-on or driven by the envelope and velocity. The spike gets a convincing one-shot crash, but not one that responds to what the plate is doing right now:

  • Rolls and repeated strokes that build the wash.
  • A choke landing mid-bloom.
  • A soft roll growing into a smash.
  • The bloom itself: energy moving up the spectrum over tens of milliseconds.

Those need the amplitude-dependent, spectrum-spreading term in the bridge bus that the modal design leaves parked ("Cymbals: what this claims, and what it approximates" in docs/superpowers/specs/2026-08-31-modal-synthesis-design.md).

`patches/crash.h` sounds like a struck bell, not a crash: very coherent, too much fundamental, and *decidedly pitched*. As bcox put it, a drummer wouldn't hit it unless the band were in the same key. The final ringout is a tuned bell with a distinct pitch. A listening spike on 2026-09-13 found out why, and how far the engine gets without new physics. This ticket records what it found, so a spec can start from it. **Nothing here is merged or scheduled.** ## Where the spike is Commit `a1b22ac` on local branch `worktree-cymbal-scatter` (worktree `.claude/worktrees/cymbal-scatter`), one WIP commit off `main` at `fd59eca`. **Not pushed.** Every change is marked `SPIKE (cymbal-scatter)`: - `dsp/modal_loop.h`: `SetScatter` and `ScatteredPickup`, the pickup-side phase noise and gain tilt. - `dsp/mode.h` and `dsp/mode_bank.h`: read-only accessors for each mode's quadrature state and pickup weight. - `dsp/modal_layer.h`: note-on plumbing, with `spike_*` fields on `ModalPatch`. They aren't serialized. - `materials/spec.h` and `materials/generators.h`: plates honor `second_group_ratio`, with their own `spike_second_aspect`, plus `spike_low_scatter`. - `patches/crash.h`: `BuildCrashSpike(CrashSpike)`, where every knob below is a field. - `patches/factory.h`: about 60 `crash-*` variant rows. **These break the bank tests.** The suite hasn't been run on this branch. - `tools/spike_lowmodes.cc`: prints a plate table's lowest modes and how far they sit from a harmonic series. To render any variant: `bazel run //patches:audition -- --patch crash-r-dm50 <dir>`. **The favorite so far is `crash-r-dm50`:** - no noise-wash layer; - aspect 1.53, plus a second plate group at aspect 1.27 × 1.19, with `low_scatter` 0.12; - pickup phase noise with floor 1.0 rad RMS and depth 2.0 × envelope, no index tilt, and noise cutoff = 2 × each bin's mean frequency; - pickup tilt 0.9 + 0.6 × velocity; - stick contact of 0.4 ms per segment, 0.8 ms in total; - material damping −0.5, with the lowest mode's T60 at 3 s; - trim +6 dB. bcox found `crash-r-dm30` nearly as good. ## How the numbers were taken Every figure is from key 60 and the audition renderer, summed to mono: - **Band levels:** ffmpeg `volumedetect` mean dB over the stated window, with cascaded 2-pole filters setting the bands. - **Flatness and crest:** from `aspectralstats`. Flatness and crest are proxies. Each direction was decided by listening, and the verdicts are quoted. ## 1. Break up the modes' phase on the pickup, never on the resonator The bell quality is a linear mode bank: steady lines, fixed phase relationships, exponential decay. Randomizing phase addresses it, and the spike tried three ways to do that. - **v1: a random rotation of each mode's state every sample.** The accumulated phase random-walks, and that puts its energy at slow rates. bcox: *"I can hear the phase crinkling around; that modulation would need to be much higher frequency."* - **v2: a bounded, audio-rate phase deviation applied to the state.** Each mode got a noise skirt, and the result sounded better. **It broke the felt choke.** The felt damper stays passive only if it reads and pushes the same state, which `CoupledMode::Process`'s comment derives. Rotating the state between that read and the next push breaks the pairing. At `amplitude_tilt` 0.35 with a 1 rad deviation, a full choke left the tail **louder** than no choke: | window | choked | unchoked | |---|---|---| | 4.5–5.0 s | −64.9 dB | −68.9 dB | | 5.5–6.0 s | −67.7 dB | −77.5 dB | At 0.3 rad it was roughly neutral. - **v3: the same deviation applied when reading the pickup.** Each mode is heard as `cos φ · x − sin φ · y`, so the resonator, both sites and both junctions never see φ. The choke goes back to behaving like the shipped one: | patch | 4.5–5.0 s (choked / unchoked) | 5.5–6.0 s (choked / unchoked) | |---|---|---| | shipped crash | −58.0 / −56.1 | −68.9 / −64.1 | | v3 with pickup tilt 0.45 | −64.6 / −62.6 | −75.4 / −70.6 | An early choke (1.2→1.3 s) takes the v3 tail 11.3 dB below its unchoked level by 5.5–6 s. **The noise shape that worked:** each bin gets one one-pole-lowpassed noise sequence, scaled to 1 RMS. The deviation is `(floor + depth × envelope) × noise` in radians, clamped at 0.24 cycles. The noise cutoff is proportional to the bin's mean frequency. **One number disagrees with the patch's own notes.** `crash.h`'s header records the shipped choke as an energy ratio of 0.097 over 8192 samples at 4.5 s. The windowed means above show only 2–5 dB. The two measurements use different methods, and this isn't resolved. ## 2. `amplitude_tilt` is the wrong control for "less fundamental" `materials::ApplyScatterAndTilt` multiplies each mode's **radial profile** by `ratio^tilt`. That weakens the stick, the hand and the pickup together, so the level you hear moves roughly as gain², and the choke loses its grip. With `amplitude_tilt` 0.35 and no scatter, the choke changed the tail by only −0.4 dB (4.5–5 s) and −1.0 dB (5.5–6 s). A **pickup-only** tilt, with gain per bin = (bin mean ratio)^tilt normalized to the largest, leaves the stick and the hand alone. It's also loop-free, so it's safe on a live macro. It's a weak lever, though, because it can only turn things down. Going from 0.3 to 0.9 moves the balance only 2–3 dB and costs about 7.5 dB of overall level, with trim already at +6 dB. **Velocity.** Adding 0.6 × velocity to the pickup tilt gives about 4 dB of velocity-driven swing in the lows' share of the total: −12.0 → −15.9 dB from velocity 0.3 to 1.0. That's the right direction. Real cymbals have much more fundamental on a soft stroke and much less on a hard one. **The shipped-style patch runs the other way.** In `crash-pk60-*`, which has a fixed pickup tilt of 0.6 and still has the noise wash, the lows' share *rises* with velocity: −11.4 → −8.3 dB. The suspected cause is the wash layer's own velocity scaling. That isn't verified. ## 3. The plate's aspect ratio is why it's pitched `GeneratePlate` enumerates `(p² + (q·aspect)²) / (1 + aspect²)`. At aspect 1.41 ≈ √2, that expression is `(p² + 2q²)/3`, which lands on thirds. With the shipped `ratio_scatter` 0.02, the lowest modes come out at: **1 : 1.979 : 2.962 : 3.566 : 3.929 : 5.562 : 5.986 : 6.200** That's nearly a harmonic series. Modes 2–8 average **66 cents** from integer multiples of the lowest. √3 (1.73) has the same problem in quarters. The p = q diagonal (1, 4, 9, 16…) is exact at every aspect, which the header already notes. **Denser, irregular lows.** A second plate group at an unrelated frequency ratio, with its own non-√n aspect, plus a scatter weighted by 1/ratio so the low modes move most: | layout | modes below 16× fundamental | top mode | off-harmonic (modes 2–8) | |---|---|---|---| | shipped: aspect 1.41 | 21 | 83× | 66 cents | | aspect 1.53 only | 22 | 81× | 108 cents | | 1.53 + second group 1.27 × 1.19 | 39 | 53× | 169 cents | | same, low scatter 0.12 (**dense-f**) | 42 | 49× | 149 cents | Below 2 kHz, spectral crest over 0.3–2 s falls from 124 (single group) to 89 (dense-f). Aspect 1.53 alone did *not* reduce crest (130). **What the second group costs:** - **Top reach drops by about 40%,** from about 22 kHz to about 14 kHz for the top mode at key 60 (computed from the table's ratios). - **Lows rise by about 5 dB** as a share of the total. - **Velocity swing drops** from about 4 dB to about 1.5 dB. bcox: *"dense-f sounds great but needs more sibilance."* **Remove the noise wash.** Removing the unvoiced layer drops the single-group layout (`crash-cq2-*`) by 12 dB above 8 kHz, but drops dense-f by only 6–7 dB, because dense-f's mode skirts reach higher. bcox preferred dense-f without it. The wash is also hand-drawn and nearly gone by 1.5 s, and a choke can't reach it. ## 4. Stick contact time is the brightness and velocity lever This builds on dense-f with no wash, pickup tilt 0.9, and 2× noise cutoff. Mean dB over 0.3–2 s: | total contact | overall | <500 Hz | 500 Hz–2 kHz | 2–5 kHz | 5–10 kHz | >10 kHz | peak | |---|---|---|---|---|---|---|---| | 1.6 ms (shipped) | −39.3 | −49.5 | −49.2 | −49.8 | −49.1 | −45.1 | −16.8 dBFS | | 0.8 ms | −32.7 | −45.6 | −40.9 | −43.0 | −42.6 | −38.4 | −11.2 dBFS | | 0.4 ms | −27.8 | −42.7 | −36.1 | −38.3 | −37.4 | −33.0 | −4.8 dBFS | bcox: 0.8 ms *"sounds like a medium hit"*, and 0.4 ms *"a smash"*. A shorter contact adds real high-frequency excitation instead of attenuating the lows. That's also what happens physically on a harder stroke. Velocity → contact time is the honest velocity mapping. Whether the modal layer can modulate contact time today hasn't been checked. ## 5. The ringout is pitched because the lows ring longest `AddMode`'s decay law is `decay_seconds × ratio^−damping`. With `damping` ≥ 0, the lowest modes outlast everything, and the scatter's envelope term fades as the ring decays. So the ringout is the few low lines, clean. **Negative damping** makes the lows die first. All rows below are the 0.8 ms hit, rendered to 9 s: | damping | lowest mode T60 | ringout (5–8 s) level | <1 kHz share | >5 kHz share | <2 kHz crest | |---|---|---|---|---|---| | 0.22 (shipped) | 8 s | −80 dB (voice freed at 8.3 s) | −7.0 dB | −10.0 dB | (too quiet to measure) | | 0.0 | 8 s | −67 dB | −10.5 dB | −3.4 dB | 58 | | −0.15 | 6 s | −60 dB | −16.5 dB | −1.6 dB | 31 | | −0.3 | 4 s | −56 dB | −16.9 dB | −1.3 dB | 31 | | −0.5 | 3 s | −45 dB | −16.4 dB | −1.2 dB | 30 | bcox: dm30 and dm50 are *"both pretty good, dm50 may be a little better"*, and *"the dm15 range is also useful."* Over the 2–3.5 s tail, damping 0.22 → 0.0 raises the >5 kHz share by 2.8 dB. Moving the phase noise from its floor onto the envelope term lowers the tail's >3 kHz flatness from 0.75 to 0.66 (both at damping 0.10); bcox had called the tail *"a bit white"*. ## Proposed controls | control | what it drives | status | |---|---|---| | **Choke** | site-1 felt drive | Exists (macro 1). | | **Brightness** | pickup-only gain tilt by frequency ratio | Loop-free, so safe to sweep live. | | **Ringout** | decay tilt by frequency ratio, spanning damping about −0.15 to −0.5 | Read per note, not live. See the questions below. | | **Velocity** | stick contact time, about 0.8 ms (medium) to 0.4 ms (smash) | Modulation route unchecked. | Two patch-level choices underlie all four: a plate layout with no √n aspect plus a second, unrelated group, and pickup-side phase noise. ## Open questions and constraints - **What does the ringout knob hold constant?** The renders shortened the lowest mode's T60 by hand as damping went negative: 6 / 4 / 3 s. One knob needs one invariant. The candidates are the lowest mode's T60, the top mode's T60, or a cap tied to voice lifetime. From the decay law, not measured, dm50's top mode (ratio ≈ 54) rings about 3 × 54^0.5 ≈ 22 s, and that holds a voice slot the whole time. - **`ModalPatch::damping_tilt` isn't the ringout knob as it stands.** It tilts by array position, `2^(−tilt·m/count)`, and a two-group table restarts at low ratios halfway through the array. It would give the second group's low modes long decays. The knob has to tilt by frequency ratio. The destination-enum comment also keeps damping tilt off the modulation list as a note-on warp the refresh "cannot afford." Reading it at note-on respects that. A live version would reopen the decision: by count it's one exp lookup and one multiply per refreshed mode, but it isn't timed. - **Voice-slot size.** `plugin/program.h`'s `sizeof(ModalLayerType) <= sizeof(Layer)` guard rejected one weight per mode (512 B). It also rejected two 16-bin tables. The spike runs 8 bins, with 16 modes sharing each noise sequence, and packs its scalars to fit. A real design has to budget this, or put per-mode data such as log ratios in the shared `ModeTable`. - **Unmeasured:** - the choke on the dense layout and on negative damping; - retirement and voice lifetime; - fabric cost, which is counted above, not timed. - **Relation to #58.** The hi-hat's felt damper already self-oscillates there. Any change near the junction should be checked against it. ## What only the physics can do Everything above is shaped at note-on or driven by the envelope and velocity. The spike gets a convincing one-shot crash, but not one that responds to what the plate is doing right now: - **Rolls and repeated strokes** that build the wash. - **A choke landing mid-bloom.** - **A soft roll growing into a smash.** - **The bloom itself:** energy moving up the spectrum over tens of milliseconds. Those need the amplitude-dependent, spectrum-spreading term in the bridge bus that the modal design leaves parked ("Cymbals: what this claims, and what it approximates" in `docs/superpowers/specs/2026-08-31-modal-synthesis-design.md`).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bcox/tymbal#91
No description provided.