fix(studio): make the volume fader tell the truth about the gain it writes (#3305)

* fix(studio): make the volume fader tell the truth about the gain it writes

The fader travels in dB, so its stops are irrational values; serializing them
through the generic two-decimal numeric formatter collapsed the bottom quarter
of its travel onto "0" — a hard mute — and made the knob jump on release
everywhere below unity. Both panels now use the exact serializer, which
round-trips every integer stop back to itself.

Raise the volume automation lane to the same ceiling the fader reaches.
Clamping the lane at unity meant automating a boosted clip silently discarded
the boost, and the panel disables the fader while a lane owns the level, so
there was no way back. This rescales the lane's vertical axis: unity now sits
a quarter of the way up rather than at the top.

Add audio_volume_tween_overrides_gain. Tween values on `volume` are absolute —
they replace the authored gain rather than scaling it — so a clip carrying both
plays at whatever the tween names, and the fader gives no sign of it. The rule
reuses the tween detector the sibling lane/tween rule already has.

* fix(lint): treat a missing data-volume as unity, not as silence

readAttr returns null when the attribute is absent, and Number(null) is 0 —
finite, and not 1 — so a clip carrying NO data-volume cleared both filters and
was reported as authored at silence. Both halves of that were false: absent
means unity everywhere else in the runtime.

It fired on exactly the case the rule exists to bless. The docs this PR edits
say data-volume is the baseline for elements no tween touches, so a tweened
clip is expected not to carry one — the common audio fade. A warning does not
fail check, but an agent reading the fixHint would have written a gain to
correct a level that was never wrong.
This commit is contained in:
Miguel Ángel
2026-08-19 18:08:23 -04:00
committed by GitHub
parent b3c43e2480
commit 228eabd43f
15 changed files with 243 additions and 108 deletions
+11 -3
View File
@@ -12,6 +12,7 @@
*/
import { getAudioFxDef, type HfAudioFxChain } from "./audioFx.js";
import { MAX_AUDIO_GAIN } from "./audioGain.js";
export const HF_AUDIO_AUTOMATION_ATTR = "data-automation";
@@ -135,8 +136,9 @@ export const PRESET_RANGE: AutomationRange = {
/**
* The value range a lane is drawn and clamped against.
*
* Volume is linear 0..1, matching `data-volume` and the existing volume
* envelope machinery — no dB conversion enters the volume path. Everything
* Volume is linear over the full authoring gain range, matching `data-volume`
* and the existing volume envelope machinery — no dB conversion enters the
* volume path. Everything
* else is read from the effect registry, so a lane can never offer a value the
* renderer would reject, and the log-scaled knobs sweep the way a DAW's do.
*/
@@ -151,9 +153,15 @@ export interface AutomationRange {
default: number;
}
/**
* One ceiling for the fader, the lane, the preview transport and the render
* mixer. Capping the lane at unity while the fader reached +12 dB made
* automating a boosted clip silently discard the boost — and the panel
* disables the fader while a lane owns it, so there was no way back.
*/
export const VOLUME_RANGE: AutomationRange = {
min: 0,
max: 1,
max: MAX_AUDIO_GAIN,
step: 0.01,
unit: "",
label: "Volume",