Skip to main content

PlayOptions

PlayOptions is the object you pass as the second argument to play and playSprite. Every field is optional. Leave one out and the sound keeps what it had, which for a new sound is the default from the config.

export interface PlayOptions {
/** @deprecated Renamed to `overlap`. Still honoured, and removed in v7. `overlap` wins when both are set. */
createNewInstance?: boolean;
/** Seconds to play before the sound ends. Wall clock time, so at double speed it covers twice as much of the file. */
duration?: number;
/** Seconds to fade in when the sound starts. */
fadeInDuration?: number;
/** Volume the fade in starts from, 0 to 1. */
fadeInStartVolume?: number;
/** Seconds to fade out, starting as soon as the sound plays. For a fade at the end, use fadeOutBeforeEndDuration. */
fadeOutDuration?: number;
/** Volume the fade out ends at, 0 to 1. Default: 0. */
fadeOutEndVolume?: number;
/** Seconds before the end at which a fade out starts. */
fadeOutBeforeEndDuration?: number;
/** Group to play the sound into. The group's play options apply, and its maxInstances caps it. */
groupId?: string;
/** @deprecated Never read by the hub. Removed in v7. */
isSeeking?: boolean;
/** Start over when the end is reached. Default: false. */
loop?: boolean;
/** How often a looping sound plays. 0 or -1 loops forever. */
maxLoops?: number;
/**
* Loop inside the audio graph instead of restarting the source, so there is
* no gap between iterations. Needs loop: true. The loop never ends by itself,
* so maxLoops is ignored and no loop_completed event is dispatched. Use it for
* beds and drones, where a restart is audible.
*/
seamlessLoop?: boolean;
/**
* Let the sound overlap itself instead of restarting. Every call gets its own
* instance with the id "<id>:<n>", which is what play() returns and what the
* event's instanceId carries. Use it for footsteps, lasers and clicks. Not
* available on streams. Default: false.
*/
overlap?: boolean;
/** Stereo pan, from -1 (left) to 1 (right). */
pan?: number;
/** Direction the sound points in. Only audible when the panner has a cone set through coneInnerAngle and coneOuterAngle. */
panSpatialOrientation?: { x: number; y: number; z: number };
/** Position in 3D space. Set panType to SoundPanType.Spatial as well. */
panSpatialPosition?: { x: number; y: number; z: number };
/** 'stereo' or 'spatial'. Default: 'stereo'. */
panType?: SoundPanType;
/** Pause instead of stop when duration is reached. Needs duration, and loop set to false. */
pauseAtDurationReached?: boolean;
/** Playback speed. 1 is normal, 2 is double speed. */
playbackRate?: number;
/** Second in the file to start from. */
startTime?: number;
/** Dispatch `progress` events while the sound plays, for a seek bar or a timer. */
trackProgress?: boolean;
/** Volume from 0 to 1. */
volume?: number;
}

Fields​

Where the default comes from the config, the config key is named.

FieldTypeDefaultDescription
createNewInstancebooleanfalseOld name for overlap, removed in v7. overlap wins when both are set.
durationnumberwhole soundSeconds to play, in wall clock time. At double speed it covers twice as much of the file.
fadeInDurationnumberno fadeSeconds to fade in at the start. The fade ends at volume.
fadeInStartVolumenumber0Volume the fade in starts from.
fadeOutDurationnumberno fadeSeconds to fade out, starting as soon as the sound plays.
fadeOutEndVolumenumber0Volume a fade out ends at.
fadeOutBeforeEndDurationnumberno fadeFade out over the last this many seconds of the sound.
groupIdstringnoneGroup to play into. The group must exist.
isSeekingbooleannoneNever read. Removed in v7.
loopbooleanloopSounds (false)Start over at the end.
maxLoopsnumbermaxLoops (-1)Plays in total, counting the first. 0 or -1 loops forever.
seamlessLoopbooleanfalseLoop inside the audio graph, without a gap. Needs loop: true.
overlapbooleanoverlap (false)Start a new instance on every call instead of restarting.
pannumberdefaultPan (0)Stereo pan from -1 (left) to 1 (right).
panSpatialOrientation{ x: number; y: number; z: number }noneDirection a spatial sound points in.
panSpatialPosition{ x: number; y: number; z: number }nonePosition of a spatial sound in 3D.
panTypeSoundPanType'stereo''stereo' or 'spatial'. See Good to know.
pauseAtDurationReachedbooleanfalsePause instead of stop when duration is reached. Needs loop: false.
playbackRatenumberdefaultPlaybackRate (1)Speed. 1 is normal, 2 is double.
startTimenumberdefaultStartTime (0)Second in the file to start from.
trackProgressbooleantrackProgress (true)Dispatch progress events while the sound plays.
volumenumberdefaultVolume (1)Volume from 0 to 1.

Example​

import { SoundHub } from 'soundhub';

const soundHub = new SoundHub();
await soundHub.loadSound('music', '/audio/theme.mp3');

soundHub.play('music', {
volume: 0.8,
pan: 0.3,
loop: true,
fadeInDuration: 2,
fadeOutBeforeEndDuration: 4,
});

Try it​

Edit the options and press play. Every field of PlayOptions is editable, so you can hear what each one does.

Try it

Play with options

Idle

Edit the options, then press Play. They go to play() exactly as written.

Try playbackRate or volume. With "seamlessLoop": true the loop has no gap, and maxLoops no longer applies: it runs until you stop it.

Code
Your clicks show up here as soundhub calls

More examples​

Fades​

await soundHub.loadSound('rain', '/audio/rain.mp3');

// Fade in over 2 seconds, from silence up to 0.9
soundHub.play('rain', { volume: 0.9, fadeInDuration: 2 });

// A jingle that fades out over its last 4 seconds
await soundHub.loadSound('jingle', '/audio/jingle.mp3');
soundHub.play('jingle', { fadeOutBeforeEndDuration: 4 });

fadeOutDuration starts fading as soon as the sound plays. For a fade at the end, use fadeOutBeforeEndDuration.

Looping a set number of times​

await soundHub.loadSound('music', '/audio/theme.mp3');

// Plays three times in total, then stops
soundHub.play('music', { loop: true, maxLoops: 3 });

Seamless looping​

Added in 5.9.0

A normal loop restarts the source when the buffer ends, and that restart leaves a small gap. Noise hides it, a sustained tone does not. With seamlessLoop the source node loops the buffer itself, so there is no gap.

await soundHub.loadSound('drone', '/audio/drone.wav');

soundHub.play('drone', { loop: true, seamlessLoop: true, volume: 0.4 });

// The buffer repeats until you stop it
soundHub.stop('drone');

The same twelve second pad, looped both ways:

Try it

Restarting loop or seamless loop

Idle
Loop mode

The pad is twelve seconds long, so wait for the seam. The restarting loop drops out for a moment every lap; the seamless loop runs on. Headphones make it easier to hear.

Code
Your clicks show up here as soundhub calls

Such a loop never ends by itself. maxLoops is ignored and no loop_completed event is dispatched, because there is no pass for the library to count. seamlessLoop without loop: true does nothing.

Whether a loop is inaudible also depends on the file. The audio has to line up at its own seam, and it should not be an MP3: MP3 carries encoder padding at both ends, and that padding puts a hole in the loop. Use WAV, OGG or FLAC for material that has to loop cleanly.

Play part of a sound and pause​

await soundHub.loadSound('podcast', '/audio/podcast.mp3');

// Play 5 seconds from the 30 second mark, then pause
soundHub.play('podcast', { startTime: 30, duration: 5, pauseAtDurationReached: true });

// Later
soundHub.resume('podcast');

Progress events​

import { SoundHub, SoundEventsEnum } from 'soundhub';

const soundHub = new SoundHub();
await soundHub.loadSound('podcast', '/audio/podcast.mp3');

soundHub.addEventListener(SoundEventsEnum.PROGRESS, (event) => {
// progress is a ratio from 0 to 1
console.log(`${Math.round((event.progress ?? 0) * 100)}%`, event.currentTime, event.duration);
});

soundHub.play('podcast', { trackProgress: true });

Progress events fire every 50 ms by default. Change that with setProgressUpdateInterval.

Overlapping instances​

await soundHub.loadSound('click', '/audio/click.mp3');

// Each call starts a new instance: 'click:1', 'click:2', ...
soundHub.play('click', { overlap: true, pan: -0.5 });
soundHub.play('click', { overlap: true, pan: 0.5, playbackRate: 1.5 });
The old name for overlap

overlap was called createNewInstance until 6.2.0. The old name still works in the play options and in the config, and is removed in v7. If you set both, overlap wins.

Groups​

await soundHub.loadSound('click', '/audio/click.mp3');

soundHub.createSoundGroup('sfx', { maxInstances: 4, playOptions: { volume: 0.6 } });
soundHub.play('click', { groupId: 'sfx', overlap: true });

Good to know​

  • play() merges the options into the options stored on the sound, and they stay there. After play('music', { loop: true }), a later play('music') loops too.
  • panType is only read when overlap creates a new instance. For a normal sound, call setSpatialPosition first: that switches the sound to spatial, and from then on panSpatialPosition and panSpatialOrientation in the play options are applied. On a stereo sound they are ignored.
  • A groupId without a group made by createSoundGroup stops the sound from playing at all, without an error.
  • On a stream from loadStream only volume, pan, loop, playbackRate, startTime and trackProgress are used.
  • A restart for resume, seek or a loop does not apply volume, fadeInDuration or fadeOutDuration again. Those belong to a fresh play().

See also​