kAmplitudeOnly on a kLowPassGate slot escapes the oversample ratio's cutoff ceiling #86
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
bcox/tymbal#86
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ResolveGatesetscutoff = sweep_cutoff ? swept_cutoff : kSvfFrequencyLimit— underLowPassGateMode::kAmplitudeOnlyit pins the corner wide open, not unchanged. On akLowPassGateslot nothing narrows that afterwards, so the SVF runs its corner atkSvfFrequencyLimitregardless of the layer's derived oversample ratio.That is fs_internal/4: 12 kHz at 1x and 48 kHz at 4x, where the decimator removes it. Same patch, same notes, a different render depending on a ratio the user cannot see — which is the invariant
dsp/va_layer.h's header calls the stage's governing one ("the derived ratio must be inaudible except for aliasing").It is not a correctness bug. The two-pole is linear, the value is in-format, and no factory patch is affected — the bank has no
kLowPassGateslot in a VA filter row at all (lpg-kickis on the unvoiced engine with its own gate). It is a ratio-invariance dent, and it is reachable from a stored patch byte and from the editor.The cheap fix exists and was deliberately not taken during
ladder-gate:std::min(resolved.cutoff, cutoff_limit). That is bit-exact at 1x, sinceVaCutoffLimitForRatio(1) == kSvfFrequencyLimitmakes theminan identity there, so it changes no shipped 1x render. It keeps "wide open" meaning the format ceiling and holds it at the same hertz at every ratio, which is exactly whatVaCutoffLimitForRatioexists for.Note the distinction that made this its own decision: narrowing to
cutoff— the row's own knob, which is what the ladder gate does — would redefine whatkAmplitudeOnlymeans on a shipped topology, sincegate_mode's field comment promises "a plain VCA off the vactrol". Narrowing tocutoff_limitredefines nothing. The code at thekLowPassGatebranch now states the dent and names this fix without taking it.Fixed on
ladder-gate-followups, commit271645b. Taken as the issue specified it, with the clamp moved one level down.ResolveGatenow takes alimit(defaulting to the format ceiling) and applies it to the corner it resolves, rather than the call site clampingresolved.cutoffafterwards. That placement matters:ResolveGatebuilds its coefficients from the mode-selected cutoff, so aminapplied to the returned field alone would have satisfied every obvious assertion and changed no sound at all. There is an injection for exactly that, and it fails.The
mincovers both arms rather than only the pinned one, so the ceiling is a statement about the function's output instead of a property of one branch. The swept arm is already underneath it -- a sweep only scalesbasedownward -- so it is an identity there, and the test pins that a swept corner is not lifted to a ceiling by being handed one.Deliberately the ceiling and not
cutoff, exactly as the issue argued: narrowing to the row's own knob would make a VCA's filter follow it and redefine whatgate_modepromises. Bit-exact at 1x, and the default keepsdsp/layer_strip.h's damper and every existing test on the same bits.Three injections, each caught by the assertion that names it: restoring the bare limit inside
ResolveGate; applying the min to the reported field but not the coefficients; and dropping the argument at the VA call site, where the two probes come back bit-identical because the limit reached nothing.