101 lines
2.2 KiB
TypeScript
101 lines
2.2 KiB
TypeScript
import { hasKeyframesForPath } from "@/lib/animation/keyframe-query";
|
|
import { resolveNumberAtTime } from "@/lib/animation/resolve";
|
|
import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "./audio-constants";
|
|
import type { TimelineElement } from "./types";
|
|
const DEFAULT_STEP_SECONDS = 1 / 60;
|
|
|
|
export type AudioCapableElement = Extract<
|
|
TimelineElement,
|
|
{ type: "audio" | "video" }
|
|
>;
|
|
|
|
export function clampDb(value: number): number {
|
|
if (!Number.isFinite(value)) {
|
|
return 0;
|
|
}
|
|
|
|
return Math.min(VOLUME_DB_MAX, Math.max(VOLUME_DB_MIN, value));
|
|
}
|
|
|
|
export function dBToLinear(db: number): number {
|
|
return 10 ** (clampDb(db) / 20);
|
|
}
|
|
|
|
export function hasAnimatedVolume({
|
|
element,
|
|
}: {
|
|
element: AudioCapableElement;
|
|
}): boolean {
|
|
return hasKeyframesForPath({
|
|
animations: element.animations,
|
|
propertyPath: "volume",
|
|
});
|
|
}
|
|
|
|
export function resolveEffectiveAudioGain({
|
|
element,
|
|
trackMuted = false,
|
|
localTime,
|
|
}: {
|
|
element: AudioCapableElement;
|
|
trackMuted?: boolean;
|
|
localTime: number;
|
|
}): number {
|
|
if (trackMuted || element.muted === true) {
|
|
return 0;
|
|
}
|
|
|
|
const resolvedDb = resolveNumberAtTime({
|
|
baseValue: element.volume ?? 0,
|
|
animations: element.animations,
|
|
propertyPath: "volume",
|
|
localTime,
|
|
});
|
|
|
|
return dBToLinear(resolvedDb);
|
|
}
|
|
|
|
export function buildAudioGainAutomation({
|
|
element,
|
|
trackMuted = false,
|
|
fromLocalTime,
|
|
toLocalTime,
|
|
stepSeconds = DEFAULT_STEP_SECONDS,
|
|
}: {
|
|
element: AudioCapableElement;
|
|
trackMuted?: boolean;
|
|
fromLocalTime: number;
|
|
toLocalTime: number;
|
|
stepSeconds?: number;
|
|
}): Array<{ localTime: number; gain: number }> {
|
|
const startTime = Math.max(0, fromLocalTime);
|
|
const endTime = Math.max(startTime, toLocalTime);
|
|
const safeStep =
|
|
Number.isFinite(stepSeconds) && stepSeconds > 0
|
|
? stepSeconds
|
|
: DEFAULT_STEP_SECONDS;
|
|
const points: Array<{ localTime: number; gain: number }> = [];
|
|
|
|
for (let localTime = startTime; localTime < endTime; localTime += safeStep) {
|
|
points.push({
|
|
localTime,
|
|
gain: resolveEffectiveAudioGain({
|
|
element,
|
|
trackMuted,
|
|
localTime,
|
|
}),
|
|
});
|
|
}
|
|
|
|
points.push({
|
|
localTime: endTime,
|
|
gain: resolveEffectiveAudioGain({
|
|
element,
|
|
trackMuted,
|
|
localTime: endTime,
|
|
}),
|
|
});
|
|
|
|
return points;
|
|
}
|