OpenCut/apps/web/src/lib/timeline/audio-state.ts

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;
}