Audio and sound effects
The audio family from @motionactor/media — Audio for long-form clips, AudioManager and AudioTrack for one-shot sound effects.
The three actors
Audio— one persistent clip: narration, ambience, a music bed. An ordinary media actor —setSource(...), and tweenplayback.volume/playback.ratethroughsetPlayback(...).AudioTrack— a list of scheduled one-shot cues, each with an authored-secondsstart.AudioManager— the scene's sound-effect entry point. Owns anAudioTrackchild and routes cues into it;enabled.set(false)mutes everything it routes.
Sound effect packs
A pack is a typed map of cue id to cue definition, seconds-native.
defineSoundEffects preserves the literal map type, so cue ids flow through play and
bind calls as a typed union rather than as strings.
const keyboardSounds = defineSoundEffects({
key: { src: "keyboard-sfx/mxbrown-travel/press/GENERIC_R0.mp3", duration: 0.1667, volume: 0.32 },
backspace: { src: "keyboard-sfx/mxbrown-travel/press/BACKSPACE.mp3", duration: 0.1667, volume: 0.32 },
return: { src: "keyboard-sfx/mxbrown-travel/press/ENTER.mp3", duration: 0.1667, volume: 0.32 },
space: { src: "keyboard-sfx/mxbrown-travel/press/SPACE.mp3", duration: 0.1667, volume: 0.32 },
});
const allSounds = mergeSoundEffects(keyboardSounds, remotionSoundEffects);remotionSoundEffects is a ready-made pack of Remotion's stock @remotion/sfx clips.
src is a bare public-relative string, or an @remotion/sfx URL export; the renderer
resolves it to staticFile(...). Never pre-resolve a path in actor state — a resolved
URL is a host fact, and baking it in makes the actor unusable in any other host.
Installing and playing
const audio = ctx.spawn(AudioManager);
const sounds = audio.installSoundEffects({ map: keyboardSounds });
yield* sounds.play("return"); // typed cue id
yield* sounds.play("key", { volume: 0.5 }); // per-call overridesplay schedules the cue on the manager's track at the current timeline cursor. It is a
compile-time scheduling write, which is what keeps resolution deterministic — the cue's
position is authored, not decided at playback.
Event-driven sounds
This is the pattern that matters. Actors stay sound-agnostic: they emit typed semantic events and never reference cues or media paths. Which events make noise is declared at the composition.
audio.bindSounds(keyboard, sounds, {
keyPressed: ({ keyId }) => getKeyboardSoundCue(keyId),
});Each binding is a static cue id, a function of the event payload returning a cue id, a
{ cue, ...overrides } request, or null to stay silent. Bindings are typed over the
source's event map and the pack's cue ids, so
neither side can drift.
bindSounds is thin sugar over subscribe(audio, source, handlers) plus pack.play(...).
Drop to the unsugared form when a reaction needs more than a cue lookup.
Do not use it for
- Hardwiring media paths into reusable interaction actors. Emit a semantic event and bind the sound at the composition.
- Long-form clips through the cue system — that is what
Audiois for.
Why sound lives at the composition
A keyboard actor that plays its own sounds can only ever be one keyboard. Emitting
keyPressed and letting the composition decide means the same actor is silent in a
preview, mechanical in one video and soft in another.