Audio

Sound is a playable audio clip. Unlike nodes, it lives on a parallel timeline outside the visual scene graph: it does not appear in the tree, but it participates in the same frame-based timeline.

Basic usage

import { Sound } from '@motion-script/core';

export default createScene(function* (stage) {
  const clip = new Sound({ src: './music.mp3' });

  // Play to completion and block
  yield* clip.play();
});

Props

PropTypeDefaultDescription
srcstringPath to the audio file
volumenumber1Playback volume (0 = silent, 1 = full, >1 = amplified)
loopbooleanfalseLoop the clip
trimStartnumber0Start offset into the source file (seconds)
trimEndnumberEnd offset into the source file (seconds)
durationnumberShorthand for trimStart: 0, trimEnd: duration. Mutually exclusive with trimStart/trimEnd.
filtersAudioFilter[]Audio filters (a single filter, an array, or an AudioFilterChain)

Trimming and duration

// Play the first 5 seconds
const short = new Sound({ src: './track.mp3', duration: 5 });
yield* short.play();

// Play from 10s to 20s in the source
const excerpt = new Sound({ src: './track.mp3', trimStart: 10, trimEnd: 20 });
yield* excerpt.play();

Looping

const loop = new Sound({ src: './ambient.mp3', loop: true });

loop.start();
yield* wait(10);   // let it play for 10 seconds
loop.stop();

For a looping clip with a known length, pass duration to play():

yield* loop.play(10);   // start, wait 10s, stop

Imperative control

Use start() and stop() when you want to manage playback separately from yield*:

const sfx = new Sound({ src: './hit.wav', volume: 0.8 });

sfx.start();
// ...other animation work...
sfx.stop();

Audio filters

The AudioFilters builder chains filters fluently. Filters apply in order from index 0 (closest to the source):

import { Sound, AudioFilters } from '@motion-script/core';

const processed = new Sound({
  src: './voice.mp3',
  filters: AudioFilters.gain(1.5).lowpass(3000).echo({ delay: 0.3, feedback: 0.4 }),
});

yield* processed.play();

Available filters

FilterBuilderDescription
GainAudioFilters.gain(value)Volume multiplier. 1 = unchanged, 0 = silent, >1 = louder
High-passAudioFilters.highpass(frequency) or .highpass({ frequency, q? })Rolls off content below frequency Hz
Low-passAudioFilters.lowpass(frequency) or .lowpass({ frequency, q? })Rolls off content above frequency Hz
TremoloAudioFilters.tremolo(rate) or .tremolo({ rate, depth? })Amplitude wobble at rate Hz with depth modulation (0-1)
SpeedAudioFilters.speed(value)Playback-rate multiplier. Alters pitch. 2 = double speed
EchoAudioFilters.echo(delay) or .echo({ delay, feedback?, mix? })Delay delay seconds, feedback 0–<1, wet mix 0-1

Raw filter objects

Filters can also be passed as plain objects or arrays:

const clip = new Sound({
  src: './music.mp3',
  filters: [
    { type: 'gain', value: 0.7 },
    { type: 'lowpass', frequency: 2000 },
  ],
});

Spreading and branching chains

AudioFilters chains are immutable: each method returns a new chain. Safe to share and branch:

const base = AudioFilters.gain(1.2).lowpass(4000);
const wet  = base.echo({ delay: 0.4, feedback: 0.5 });   // 'base' is unchanged
const dry  = base.highpass(80);

// Spread into an array
const filters = [...base, { type: 'tremolo', rate: 4, depth: 0.3 }];

Speed and pitch

speed alters both tempo and pitch (like changing tape speed). A 2 multiplier doubles speed and raises pitch by one octave:

const fast = new Sound({ src: './beat.mp3', filters: AudioFilters.speed(1.5) });
yield* fast.play();

The clip's scene-time duration shrinks proportionally: a 10-second source at speed: 2 occupies 5 seconds of scene time.


Playback ranges

sound.playRanges returns the scene-time segments the sound has been scheduled to play, useful for visualizing audio on a timeline:

clip.start();
yield* wait(3);
clip.stop();

console.log(clip.playRanges);
// [{ startAt: 0, endAt: 3 }]