NarraLeaf

Sound

Sound effects, background music, and voice clips with Sound, and running playback alongside the story with Control.doAsync.

Use Sound for sound effects, background music, and voice clips. The static constructors mark each sound with the correct type.

import { Sound } from "narraleaf-react";

const door = Sound.sound({
    src: "/audio/door.ogg",
    volume: 0.8,
});

const theme = Sound.bgm({
    src: "/audio/theme.ogg",
    volume: 0.5,
    loop: true,
});

Sound effects

play() is an action. By default, the story waits for the clip to finish.

scene.action([
    door.play(),
    alice.say("Someone is here."),
]);

To let the story continue while a sound plays, run it on an asynchronous control stack.

import { Control } from "narraleaf-react";

scene.action([
    Control.doAsync([door.play()]),
    alice.say("The door closes behind you."),
]);

The old sync option no longer exists. Use Control.doAsync() or Control.allAsync() for non-blocking audio.

Background music

Set BGM on the scene config, or change it during the scene. The fade value is in milliseconds.

const room = new Scene("room", {
    backgroundMusic: theme,
    backgroundMusicFade: 800,
});

room.action([
    room.setBackgroundMusic(Sound.bgm("/audio/night.ogg"), 800),
]);

Pass null to stop the current BGM.

room.setBackgroundMusic(null, 500);

Intros and loop regions

seek is a clip's in point and endTime is its out point. With loop, the pair becomes a loop region, and loopStart says where each repeat returns to — which is how background music with an intro plays the opening once and then repeats only its body.

const theme = Sound.bgm({
    src: "/audio/theme.ogg",
    loop: true,
    seek: 0,       // the first pass starts at the top
    loopStart: 12, // every repeat returns here, after the intro
    endTime: 90,
});

The repeat is sample-accurate — no gap at the seam, no drift over a long session. See ISoundUserConfig.

To move the play head of a clip that is already playing, use seek() as an action:

room.action([
    theme.seek(12), // skip the intro
]);

Volume and buses

Every clip plays on an audio bus, chosen by its type. A game gets bgm, sound and voice without asking, and can declare more — a bus per character, say — to give the player controls the three fixed ones cannot express. See Audio Buses.

Playback controls

Sound actions can change playback state and values.

scene.action([
    door.setVolume(0.3, 300),
    door.setRate(1.2),
    door.pause(200),
    door.resume(200),
    door.mute(),
    door.unmute(),
    door.stop(300),
]);

Inside React UI, use liveGame.playSound() for immediate playback instead of adding a story action.

const liveGame = useLiveGame();

<button onClick={() => liveGame.playSound("/audio/click.ogg")}>Play</button>

On this page