Hi-hat self-oscillates and never retires its voice when the pedal is held closed #58

Open
opened 2026-09-05 12:19:54 +00:00 by bcox · 0 comments
Owner

patches/hihat.h does not decay when its pedal is held closed. Past a
macro value of about 0.78 it converges to a self-sustaining limit cycle and
the voice is never retired, which is a permanently stuck voice in the plugin
rather than only an ugly tail.

Pre-existing — present since the patch was written, and not caused by the
modal-cymbals work that found it.

What it does

Pedal held closed, 0.1 s RMS of the shipped patch:

0.05 s  0.142   (the strike)
0.80 s  0.021   (the minimum)
1.20 s  0.037
1.60 s  0.080
2.00 s  0.193
2.30 s  0.243

Rendered past the registry's own 2.5 s, it flattens at RMS 0.261 (-11.7 dBFS)
and holds that from 3 s out to 11.5 s — measured to 11.5 s because that is
where the render stopped, not because it stops. Spectrally it is two
near-sinusoids at about 4210 Hz and 5058 Hz plus their sum at 9265 Hz, which
is the felt's |x|^2.5 intermodulating with itself.

Why it reaches the host

ModalLayer::IsActive() is count_ > 0 && (!loop_.IsRetired() || flush_ > 0)
and a sustaining loop never retires, so ActiveVoices() never returns to
zero for that note. The voice is held for as long as the pedal is down.

Where the edge is

Drive about 0.39-0.41, not the 0.5 that patches/hihat.h's own header claims
("fully pressed lands drive at 0.5, the measured edge"). The macro's authored
travel reaches 0.5, so roughly the top fifth of the pedal is across the
crossing. It is a threshold, not a gradient:

pedal 0.750  (drive 0.3875)  freed at 6.31 s
pedal 0.800  (drive 0.4100)  never freed
pedal 0.875  (drive 0.4437)  never freed
pedal 1.000  (drive 0.5000)  never freed

Why no test or audition has shown it

Bank.TheHihatPedalIsAPressAndNotAGate does hold the pedal closed, so the
articulation is exercised. Its late window is 0.40-0.57 s, on the descending
slope of the curve above (0.042 falling to 0.028; the minimum is about 0.0216
at 0.70 s), and it closes before the climb begins. The 2.5 s audition render
ends while the growth is still under the attack's own peak. The instability
falls outside both windows.

It surfaced only because amplitude_tilt made it worse enough to break the
choke test, and chasing that meant rendering held-closed for 12 s for the
first time in this repo.

Verification

Measured twice independently, the second time against main's own
patches/hihat.h content with no branch code involved.

patches/hihat.h declares low_key = 0 and clips low: 1388 samples on the
Sample rail when rendered at its own declared low_key. The crash patch
added on modal-cymbals had the same shape and was given a measured range
(53-72) instead.

What a fix touches

Site 1's force (0.1875) and the pedal's drive ceiling. Both are measured
numbers that patches/hihat.h's header defends at length, and moving them
changes Bank.TheHihatPedalIsAPressAndNotAGate's three ratios and every
hi-hat render in the bank.

A useful starting point exists: patches/crash.h authored the same felt
mechanism from a fresh held-closed grid and found the loop's stability bound
to be force * drive^1.5 ~= 0.049, which holds to 1% across an 8x range of
force and predicts this patch's own crossing at 0.0483. The shipped hi-hat
sits at 0.0663, about 1.37x over. That bound is recorded in
docs/superpowers/specs/2026-08-31-modal-synthesis-design.md.

Settling this is also what unblocks MaterialSpec::amplitude_tilt on this
patch: the tilt was withdrawn from the hi-hat on modal-cymbals because it
breaks the choke test at sixteen times below its recommended value, and it
cannot return until site 1 is re-authored.

Recorded in

patches/hihat.h (header), patches/crash.h (choke section),
docs/superpowers/specs/2026-09-04-modal-cymbals-design.md, and
docs/superpowers/specs/2026-08-31-modal-synthesis-design.md.

`patches/hihat.h` does not decay when its pedal is held closed. Past a macro value of about 0.78 it converges to a self-sustaining limit cycle and the voice is never retired, which is a permanently stuck voice in the plugin rather than only an ugly tail. Pre-existing — present since the patch was written, and not caused by the `modal-cymbals` work that found it. ## What it does Pedal held closed, 0.1 s RMS of the shipped patch: 0.05 s 0.142 (the strike) 0.80 s 0.021 (the minimum) 1.20 s 0.037 1.60 s 0.080 2.00 s 0.193 2.30 s 0.243 Rendered past the registry's own 2.5 s, it flattens at RMS 0.261 (-11.7 dBFS) and holds that from 3 s out to 11.5 s — measured to 11.5 s because that is where the render stopped, not because it stops. Spectrally it is two near-sinusoids at about 4210 Hz and 5058 Hz plus their sum at 9265 Hz, which is the felt's `|x|^2.5` intermodulating with itself. ## Why it reaches the host `ModalLayer::IsActive()` is `count_ > 0 && (!loop_.IsRetired() || flush_ > 0)` and a sustaining loop never retires, so `ActiveVoices()` never returns to zero for that note. The voice is held for as long as the pedal is down. ## Where the edge is Drive about 0.39-0.41, not the 0.5 that `patches/hihat.h`'s own header claims ("fully pressed lands drive at 0.5, the measured edge"). The macro's authored travel reaches 0.5, so roughly the top fifth of the pedal is across the crossing. It is a threshold, not a gradient: pedal 0.750 (drive 0.3875) freed at 6.31 s pedal 0.800 (drive 0.4100) never freed pedal 0.875 (drive 0.4437) never freed pedal 1.000 (drive 0.5000) never freed ## Why no test or audition has shown it `Bank.TheHihatPedalIsAPressAndNotAGate` does hold the pedal closed, so the articulation is exercised. Its late window is 0.40-0.57 s, on the descending slope of the curve above (0.042 falling to 0.028; the minimum is about 0.0216 at 0.70 s), and it closes before the climb begins. The 2.5 s audition render ends while the growth is still under the attack's own peak. The instability falls outside both windows. It surfaced only because `amplitude_tilt` made it worse enough to break the choke test, and chasing that meant rendering held-closed for 12 s for the first time in this repo. ## Verification Measured twice independently, the second time against `main`'s own `patches/hihat.h` content with no branch code involved. ## Related, and probably part of the same fix `patches/hihat.h` declares `low_key = 0` and clips low: 1388 samples on the `Sample` rail when rendered at its own declared `low_key`. The `crash` patch added on `modal-cymbals` had the same shape and was given a measured range (53-72) instead. ## What a fix touches Site 1's `force` (0.1875) and the pedal's drive ceiling. Both are measured numbers that `patches/hihat.h`'s header defends at length, and moving them changes `Bank.TheHihatPedalIsAPressAndNotAGate`'s three ratios and every hi-hat render in the bank. A useful starting point exists: `patches/crash.h` authored the same felt mechanism from a fresh held-closed grid and found the loop's stability bound to be `force * drive^1.5 ~= 0.049`, which holds to 1% across an 8x range of force and predicts this patch's own crossing at 0.0483. The shipped hi-hat sits at 0.0663, about 1.37x over. That bound is recorded in `docs/superpowers/specs/2026-08-31-modal-synthesis-design.md`. Settling this is also what unblocks `MaterialSpec::amplitude_tilt` on this patch: the tilt was withdrawn from the hi-hat on `modal-cymbals` because it breaks the choke test at sixteen times below its recommended value, and it cannot return until site 1 is re-authored. ## Recorded in `patches/hihat.h` (header), `patches/crash.h` (choke section), `docs/superpowers/specs/2026-09-04-modal-cymbals-design.md`, and `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#58
No description provided.