diff options
| author | cashmere <cashmere@autistici.org> | 2026-08-16 06:16:56 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-08-16 06:16:56 +0200 |
| commit | f1043734438c8518a302f698d9ae6583a75f0c14 (patch) | |
| tree | d3e636a96e6349c1ad17afa7b28c9ffe5c882e37 /apps/web/src/media/audio.ts | |
| parent | 37d22b1e390bd6cea04bf8e15069281ee125cf10 (diff) | |
| download | opencut-f1043734438c8518a302f698d9ae6583a75f0c14.tar.gz | |
feat: add transitions, track mixing, and voiceover controls
Diffstat (limited to 'apps/web/src/media/audio.ts')
| -rw-r--r-- | apps/web/src/media/audio.ts | 42 |
1 files changed, 33 insertions, 9 deletions
diff --git a/apps/web/src/media/audio.ts b/apps/web/src/media/audio.ts index 7abaf90e..0e8ee826 100644 --- a/apps/web/src/media/audio.ts +++ b/apps/web/src/media/audio.ts @@ -10,6 +10,7 @@ import type { MediaAsset } from "@/media/types"; import { applyAudioMasteringToBuffer } from "@/media/audio-mastering"; import type { AudioCapableElement } from "@/timeline/audio-state"; import { + getTrackVolume, hasAnimatedVolume, isElementMuted, resolveEffectiveAudioGain, @@ -21,10 +22,7 @@ import { mediaSupportsAudio } from "@/media/media-utils"; import { getSourceTimeAtClipTime, renderRetimedBuffer } from "@/retime"; import { Input, ALL_FORMATS, BlobSource, AudioBufferSink } from "mediabunny"; import { TICKS_PER_SECOND } from "@/wasm"; -import { - computeRmsBuckets, - type SampleBucket, -} from "@/media/waveform-summary"; +import { computeRmsBuckets, type SampleBucket } from "@/media/waveform-summary"; const MAX_AUDIO_CHANNELS = 2; const EXPORT_SAMPLE_RATE = 44100; @@ -37,6 +35,7 @@ export interface CollectedAudioElement { trimStart: number; trimEnd: number; volume: number; + trackVolume: number; muted: boolean; retime?: RetimeConfig; } @@ -89,6 +88,7 @@ export async function decodeAudioToFloat32({ export interface AudibleElementCandidate { element: AudioElement | VideoElement; mediaAsset: MediaAsset | null; + trackVolume: number; } export function collectAudibleCandidates({ @@ -114,7 +114,11 @@ export function collectAudibleCandidates({ : null; if (!doesElementHaveEnabledAudio({ element, mediaAsset })) continue; - candidates.push({ element, mediaAsset }); + candidates.push({ + element, + mediaAsset, + trackVolume: canTrackHaveAudio(track) ? getTrackVolume({ track }) : 0, + }); } } @@ -148,7 +152,7 @@ export async function collectAudioElements({ ); const pendingElements: Array<Promise<CollectedAudioElement | null>> = []; - for (const { element, mediaAsset } of candidates) { + for (const { element, mediaAsset, trackVolume } of candidates) { if (element.type === "audio") { pendingElements.push( resolveAudioBufferForElement({ @@ -166,10 +170,11 @@ export async function collectAudioElements({ trimEnd: element.trimEnd / TICKS_PER_SECOND, volume: resolveEffectiveAudioGain({ element, - trackMuted: false, + trackVolume, localTime: 0, }), muted: isElementMuted({ element }), + trackVolume, retime: element.retime, }; }), @@ -195,10 +200,11 @@ export async function collectAudioElements({ trimEnd: element.trimEnd / TICKS_PER_SECOND, volume: resolveEffectiveAudioGain({ element, - trackMuted: false, + trackVolume, localTime: 0, }), muted: isElementMuted({ element }), + trackVolume, retime: element.retime, }; }), @@ -359,6 +365,7 @@ export interface AudioClipSource { trimStart: number; trimEnd: number; volume: number; + trackVolume: number; muted: boolean; retime?: RetimeConfig; } @@ -401,10 +408,12 @@ async function fetchLibraryAudioClip({ element, muted, volume, + trackVolume, }: { element: LibraryAudioElement; muted: boolean; volume: number; + trackVolume: number; }): Promise<AudioClipSource | null> { try { const response = await fetch(element.sourceUrl); @@ -427,6 +436,7 @@ async function fetchLibraryAudioClip({ trimStart: element.trimStart, trimEnd: element.trimEnd, volume, + trackVolume, muted, retime: element.retime, }; @@ -462,11 +472,13 @@ function collectMediaAudioClip({ mediaAsset, muted, volume, + trackVolume, }: { element: AudioCapableElement; mediaAsset: MediaAsset; muted: boolean; volume: number; + trackVolume: number; }): AudioClipSource { return { timelineElement: element, @@ -478,6 +490,7 @@ function collectMediaAudioClip({ trimStart: element.trimStart / TICKS_PER_SECOND, trimEnd: element.trimEnd / TICKS_PER_SECOND, volume, + trackVolume, muted, retime: element.retime, }; @@ -507,8 +520,12 @@ export async function collectAudioMixSources({ ? (mediaMap.get(element.mediaId) ?? null) : null; if (!doesElementHaveEnabledAudio({ element, mediaAsset })) continue; + const trackVolume = canTrackHaveAudio(track) + ? getTrackVolume({ track }) + : 0; const volume = resolveEffectiveAudioGain({ element, + trackVolume, localTime: 0, }); @@ -562,6 +579,9 @@ export async function collectAudioClips({ for (const track of orderedTracks) { const isTrackMuted = canTrackHaveAudio(track) && track.muted; + const trackVolume = canTrackHaveAudio(track) + ? getTrackVolume({ track }) + : 0; for (const element of track.elements) { if (!canElementHaveAudio(element)) continue; @@ -575,6 +595,7 @@ export async function collectAudioClips({ const volume = resolveEffectiveAudioGain({ element, trackMuted: isTrackMuted, + trackVolume, localTime: 0, }); @@ -589,11 +610,12 @@ export async function collectAudioClips({ mediaAsset, muted, volume, + trackVolume, }), ); } else { pendingLibraryClips.push( - fetchLibraryAudioClip({ element, muted, volume }), + fetchLibraryAudioClip({ element, muted, volume, trackVolume }), ); } continue; @@ -607,6 +629,7 @@ export async function collectAudioClips({ mediaAsset, muted, volume, + trackVolume, }), ); } @@ -855,6 +878,7 @@ function mixAudioChannels({ const gain = hasAnimatedVolume({ element: element.timelineElement }) ? resolveEffectiveAudioGain({ element: element.timelineElement, + trackVolume: element.trackVolume, localTime: clipTime, }) : element.volume; |
