Skip to content
MotionActor
Esc
↑↓navigate↵open⌘Jpreview
On this page

Audio and sound effects

Audio plays one clip along the timeline. AudioManager schedules short cues, by name or in answer to another actor's events.

@motionactor/media has two ways to make sound. Audio is one clip that plays along the timeline: a voice-over, a music bed, room tone. AudioManager schedules short cues, such as a click per keystroke, from a named pack.

A clip

Audio plays its source through sourceTime, the seconds into the clip at each frame, like Video. A looped bed is one line:

import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
} from "@motionactor/core";
import { class AudioAudio } from "@motionactor/media"; import { const audio: AssetType<"audio", "audio">audio } from "@motionactor/schema"; createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
(function* (ctx: SceneContextctx) {
ctx: SceneContextctx.
CompileContext.spawn<Audio>(definition: ActorClass<Audio>, overrides: {
    content: {
        source: SignalInputValue<AssetRef<"audio">>;
    };
    volume?: SignalInputValue<number> | undefined;
    fade?: {
        in?: SignalInputValue<number> | undefined;
        out?: SignalInputValue<number> | undefined;
    } | undefined;
    timing?: {
        start?: SignalInputValue<number> | undefined;
        end?: SignalInputValue<number> | undefined;
    } | undefined;
    sourceTime?: SignalInputValue<number> | undefined;
    ... 18 more ...;
    parent?: undefined;
}, options?: {
    ...;
} | undefined): Audio (+1 overload)
spawn
(class AudioAudio, {
content: {
    source: SignalInputValue<AssetRef<"audio">>;
}
content
: { source: SignalInputValue<AssetRef<"audio">>source: const audio: AssetType<"audio", "audio">audio.
AssetType<"audio", "audio">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"audio">
file
("music/bed.mp3") },
volume?: SignalInputValue<number> | undefinedvolume: 0.6,
fade?: {
    in?: SignalInputValue<number> | undefined;
    out?: SignalInputValue<number> | undefined;
} | undefined
fade
: { in?: SignalInputValue<number> | undefinedin: 1, out?: SignalInputValue<number> | undefinedout: 2 },
}).Audio.loop(params?: MediaLoopParams): voidloop(); yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(12); }).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });
Member
content.source The clip, an audio reference. setSource(ref) swaps it
timing.start, timing.end The window of the clip to use, in seconds. end: -1 is the clip’s end
volume, muted Gain, and a switch. Both are signals, so volume.tween(0, 2) fades out
fade.in, fade.out Seconds of ramp at the start and end of each play
play({ from?, to?, rate? }) Plays through the window. Takes that long
loop({ window?, rate? }) Loops the window from here on. Takes no time
hold(seconds), seekTo(time) Stays put, or jumps
fastForward, rewind, rampTo Scrubs at a chosen speed, or eases between rates

Cues

A pack is a map from cue name to clip. defineSoundEffects keeps the names as a type, so a misspelt cue does not compile.

import { function defineSoundEffects<const TMap extends SoundEffectMap>(map: TMap): TMapdefineSoundEffects } from "@motionactor/media";
import { const audio: AssetType<"audio", "audio">audio } from "@motionactor/schema";

export const 
const keys: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
keys
=
defineSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>(map: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}): {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
defineSoundEffects
({
key: {
    readonly source: AssetRef<"audio">;
    readonly duration: 0.15;
    readonly volume: 0.3;
}
key
: {
source: AssetRef<"audio">source: const audio: AssetType<"audio", "audio">audio.
AssetType<"audio", "audio">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"audio">
file
("sfx/key.mp3"),
duration: 0.15duration: 0.15, volume: 0.3volume: 0.3, },
enter: {
    readonly source: AssetRef<"audio">;
    readonly volume: 0.5;
}
enter
: { source: AssetRef<"audio">source: const audio: AssetType<"audio", "audio">audio.
AssetType<"audio", "audio">.file(src: string, options?: {
    mimeType?: string;
}): AssetRef<"audio">
file
("sfx/enter.mp3"), volume: 0.5volume: 0.5 },
});

A cue may set duration in seconds (the whole clip when omitted), volume, playbackRate and loop.

Spawn an AudioManager, install the pack and play cues by name. play schedules the cue at the cursor and takes no time, so the script carries on while it sounds:

import { function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
} from "@motionactor/core";
import { class AudioManagerAudioManager } from "@motionactor/media"; import {
const keys: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
keys
} from "./sounds";
createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
(function* (ctx: SceneContextctx) {
const const sound: AudioManagersound = ctx: SceneContextctx.
CompileContext.spawn<AudioManager>(definition: ActorClass<AudioManager>, overrides?: {
    enter?: undefined;
    enabled?: SignalInputValue<boolean> | undefined;
    track?: {
        cues?: SignalInputValue<AudioTrackCue[]> | undefined;
    } | undefined;
    playDefinition?: undefined;
    installSoundEffects?: undefined;
    bindSounds?: undefined;
    id?: undefined;
    cursor?: undefined;
    cursorSeconds?: undefined;
    sceneFps?: undefined;
    onInit?: undefined;
    onEnter?: undefined;
    onExit?: undefined;
    exit?: undefined;
    parent?: undefined;
} | undefined, options?: {
    ...;
} | undefined): AudioManager (+1 overload)
spawn
(class AudioManagerAudioManager);
const
const pack: InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
pack
= const sound: AudioManagersound.
AudioManager.installSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>({ map, }: {
    map: {
        readonly key: {
            readonly source: AssetRef<"audio">;
            readonly duration: 0.15;
            readonly volume: 0.3;
        };
        readonly enter: {
            readonly source: AssetRef<"audio">;
            readonly volume: 0.5;
        };
    };
}): InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
installSoundEffects
({
map: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
map
:
const keys: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
keys
});
yield*
const pack: InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
pack
.InstalledSoundEffects<{ readonly key: { readonly source: AssetRef<"audio">; readonly duration: 0.15; readonly volume: 0.3; }; readonly enter: { readonly source: AssetRef<"audio">; readonly volume: 0.5; }; }>.play(cue: "key" | "enter", override?: Partial<AudioCueDefinition>): Generator<number, void, unknown>play("key");
yield* ctx: SceneContextctx.CompileContext.wait(duration: Duration): Generator<Frame, void, unknown>wait(0.4); yield*
const pack: InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
pack
.InstalledSoundEffects<{ readonly key: { readonly source: AssetRef<"audio">; readonly duration: 0.15; readonly volume: 0.3; }; readonly enter: { readonly source: AssetRef<"audio">; readonly volume: 0.5; }; }>.play(cue: "key" | "enter", override?: Partial<AudioCueDefinition>): Generator<number, void, unknown>play("enter", { volume?: number | undefinedvolume: 1 });
}).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });

The second argument overrides the pack’s values for that one cue.

mergeSoundEffects(a, b) joins packs. @motionactor/remotion exports remotionSoundEffects, a pack of Remotion’s stock effects: whoosh, whip, pageTurn, uiSwitch, mouseClick, shutterModern, shutterOld, ding, bruh, vineBoom and windowsXpError.

Sounds from events

A keyboard actor that plays its own clicks is stuck with them. Have it emit an event instead, and let the scene choose the sound:

import { class Actor<TEvents extends ActorEventMap = {}>Actor, function createScene<TSceneExports, const I extends SceneInputs = Readonly<Record<never, SceneInput>>, const L extends SceneLayers = Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<I, L, TSceneExports>, options?: SceneDefinitionOptions<I, L>): SceneDefinition<I, L, TSceneExports>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
,
function signal<T>(): RequiredSignal<T> (+1 overload)
module signal
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
} from "@motionactor/core";
import { class AudioManagerAudioManager } from "@motionactor/media"; import {
const keys: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
keys
} from "./sounds";
class class KeyboardKeyboard extends class Actor<TEvents extends ActorEventMap = {}>Actor<{
pressed: {
    key: string;
}
pressed
: { key: stringkey: string } }> {
Keyboard.text: ActorFieldSignal<string>text = signal<string>(initial: string): ActorFieldSignal<string> (+1 overload)
A timeline value on an actor, and an input to every spawn of it. Without an initial value the signal is required: `ctx.spawn` must supply it.
signal
("");
*Keyboard.type(word: string): Generator<number, void, unknown>type(word: stringword: string) { for (const const key: stringkey of word: stringword) { this.Keyboard.text: ActorFieldSignal<string>text.LiveSignal<string>.set(value: string): void
Set value instantly at current cursor (no yield needed)
set
(this.Keyboard.text: () => stringtext() + const key: stringkey);
yield* this.
Actor<{ pressed: { key: string; }; }>.emit: ActorEmitter<{
    pressed: {
        key: string;
    };
}>
emit
.
pressed: (payload: {
    key: string;
}) => Generator<Frame, void, unknown>
pressed
({ key: stringkey });
yield* this.Actor<{ pressed: { key: string; }; }>.runtime: ActorRuntimeOrchestration<this>runtime.ActorRuntimeOrchestration<this>.wait(duration: Duration): Generator<Frame, void, unknown>wait(0.1); } yield* this.
Actor<{ pressed: { key: string; }; }>.emit: ActorEmitter<{
    pressed: {
        key: string;
    };
}>
emit
.
pressed: (payload: {
    key: string;
}) => Generator<Frame, void, unknown>
pressed
({ key: stringkey: "Enter" });
} } createScene<void, Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>>(script: SceneScript<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>, options?: SceneDefinitionOptions<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>> | undefined): SceneDefinition<...>
Declare a scene. Nothing compiles until a host builds it: `scene.build({ viewport, fps, inputs })` runs the script as `(ctx, inputs, viewport, layers)` and returns the compiled scene. `ctx.spawn` places a root actor in the default layer; `layers.<key>.spawn` places it in a declared one. `ctx.stage` stages another scene on this scene's clock, and a staged scene may stage again.
createScene
(function* (ctx: SceneContextctx) {
const const sound: AudioManagersound = ctx: SceneContextctx.
CompileContext.spawn<AudioManager>(definition: ActorClass<AudioManager>, overrides?: {
    enter?: undefined;
    id?: undefined;
    cursor?: undefined;
    cursorSeconds?: undefined;
    sceneFps?: undefined;
    onInit?: undefined;
    onEnter?: undefined;
    onExit?: undefined;
    exit?: undefined;
    parent?: undefined;
    enabled?: SignalInputValue<boolean> | undefined;
    track?: {
        cues?: SignalInputValue<AudioTrackCue[]> | undefined;
    } | undefined;
    playDefinition?: undefined;
    installSoundEffects?: undefined;
    bindSounds?: undefined;
} | undefined, options?: {
    ...;
} | undefined): AudioManager (+1 overload)
spawn
(class AudioManagerAudioManager);
const
const pack: InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
pack
= const sound: AudioManagersound.
AudioManager.installSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>({ map, }: {
    map: {
        readonly key: {
            readonly source: AssetRef<"audio">;
            readonly duration: 0.15;
            readonly volume: 0.3;
        };
        readonly enter: {
            readonly source: AssetRef<"audio">;
            readonly volume: 0.5;
        };
    };
}): InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
installSoundEffects
({
map: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
map
:
const keys: {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}
keys
});
const const keyboard: Keyboardkeyboard = ctx: SceneContextctx.
CompileContext.spawn<Keyboard>(definition: ActorClass<Keyboard>, overrides?: {
    enter?: undefined;
    type?: undefined;
    text?: SignalInputValue<string> | undefined;
    id?: undefined;
    cursor?: undefined;
    cursorSeconds?: undefined;
    sceneFps?: undefined;
    onInit?: undefined;
    onEnter?: undefined;
    onExit?: undefined;
    exit?: undefined;
    parent?: undefined;
} | undefined, options?: {
    id?: string;
} | undefined): Keyboard (+1 overload)
spawn
(class KeyboardKeyboard);
const sound: AudioManagersound.
AudioManager.bindSounds<{
    pressed: {
        key: string;
    };
}, {
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>(source: {
    id: string;
    emit: ActorEmitter<{
        pressed: {
            key: string;
        };
    }>;
}, pack: InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>, bindings: SoundBindings<...>): void
Attach sounds to another actor's semantic events. The actor stays sound-agnostic — the event→cue mapping is a composition-level choice.
bindSounds
(const keyboard: Keyboardkeyboard,
const pack: InstalledSoundEffects<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>
pack
, {
pressed?: "key" | "enter" | ((payload: {
    key: string;
}) => SoundBindingResult<{
    readonly key: {
        readonly source: AssetRef<"audio">;
        readonly duration: 0.15;
        readonly volume: 0.3;
    };
    readonly enter: {
        readonly source: AssetRef<"audio">;
        readonly volume: 0.5;
    };
}>) | undefined
pressed
: ({ key: stringkey }) => (key: stringkey === "Enter" ? "enter" : "key"),
}); yield* const keyboard: Keyboardkeyboard.Keyboard.type(word: string): Generator<number, void, unknown>type("hi"); // key at 0s, key at 0.1s, enter at 0.2s }).SceneDefinition<Readonly<Record<never, SceneInput>>, Readonly<Record<never, SceneLayer<BackendRef<string>>>>, void>.build(args: SceneBuildInput<Readonly<Record<never, SceneInput>>>): CompiledScene<void, CompiledLayersOf<Readonly<Record<never, SceneLayer<BackendRef<string>>>>>>build({ viewport: SceneViewportviewport: { SceneViewport.width: numberwidth: 1920, SceneViewport.height: numberheight: 1080 }, fps: numberfps: 30 });

A binding is a cue name, or a function of the event’s payload that returns a cue name, { cue, ...overrides }, or null for silence. Both the event names and the cue names are checked against their types. bindSounds is a subscribe underneath, so use subscribe directly when an event should do more than play a cue.

Muting

sound.enabled.set(false) stops the manager scheduling cues from that point of the script. The check happens when play runs or when the event fires, so cues already scheduled still sound and a key pressed while it is off stays silent after it is turned back on.

Where it plays

Previews play Audio clips and scheduled cues alongside the picture, through the host’s media player. Exported video files have no sound yet. See Capture and export.

Was this page helpful?