mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
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:
@@ -18,6 +18,7 @@ import {
|
||||
type HfAutomationLane,
|
||||
} from "./audioAutomation.js";
|
||||
import { mintAudioFxNodeId, parseAudioFxChain, type HfAudioFxChain } from "./audioFx.js";
|
||||
import { MAX_AUDIO_GAIN } from "./audioGain.js";
|
||||
|
||||
const chain: HfAudioFxChain = {
|
||||
version: 1,
|
||||
@@ -116,7 +117,7 @@ describe("normalisation", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("clamps volume into 0..1 at parse time", () => {
|
||||
it("clamps volume into the authoring gain range at parse time", () => {
|
||||
const parsed = parseAutomation(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
@@ -131,7 +132,9 @@ describe("normalisation", () => {
|
||||
],
|
||||
}),
|
||||
);
|
||||
expect(parsed.lanes[0]!.points.map((p) => p.v)).toEqual([1, 0]);
|
||||
// The lane shares the fader's ceiling. Clamping it at unity discarded the
|
||||
// boost of any clip authored above 0 dB the moment it was automated.
|
||||
expect(parsed.lanes[0]!.points.map((p) => p.v)).toEqual([MAX_AUDIO_GAIN, 0]);
|
||||
});
|
||||
|
||||
it("refuses malformed input instead of silently losing an envelope", () => {
|
||||
|
||||
Reference in New Issue
Block a user