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.
| Field | Type | Default | Description |
|---|---|---|---|
createNewInstance | boolean | false | Old name for overlap, removed in v7. overlap wins when both are set. |
duration | number | whole sound | Seconds to play, in wall clock time. At double speed it covers twice as much of the file. |
fadeInDuration | number | no fade | Seconds to fade in at the start. The fade ends at volume. |
fadeInStartVolume | number | 0 | Volume the fade in starts from. |
fadeOutDuration | number | no fade | Seconds to fade out, starting as soon as the sound plays. |
fadeOutEndVolume | number | 0 | Volume a fade out ends at. |
fadeOutBeforeEndDuration | number | no fade | Fade out over the last this many seconds of the sound. |
groupId | string | none | Group to play into. The group must exist. |
isSeeking | boolean | none | Never read. Removed in v7. |
loop | boolean | loopSounds (false) | Start over at the end. |
maxLoops | number | maxLoops (-1) | Plays in total, counting the first. 0 or -1 loops forever. |
seamlessLoop | boolean | false | Loop inside the audio graph, without a gap. Needs loop: true. |
overlap | boolean | overlap (false) | Start a new instance on every call instead of restarting. |
pan | number | defaultPan (0) | Stereo pan from -1 (left) to 1 (right). |
panSpatialOrientation | { x: number; y: number; z: number } | none | Direction a spatial sound points in. |
panSpatialPosition | { x: number; y: number; z: number } | none | Position of a spatial sound in 3D. |
panType | SoundPanType | 'stereo' | 'stereo' or 'spatial'. See Good to know. |
pauseAtDurationReached | boolean | false | Pause instead of stop when duration is reached. Needs loop: false. |
playbackRate | number | defaultPlaybackRate (1) | Speed. 1 is normal, 2 is double. |
startTime | number | defaultStartTime (0) | Second in the file to start from. |
trackProgress | boolean | trackProgress (true) | Dispatch progress events while the sound plays. |
volume | number | defaultVolume (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.
Play with options
IdleEdit 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.
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:
Restarting loop or seamless loop
IdleThe 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.
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 });
overlapoverlap 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. Afterplay('music', { loop: true }), a laterplay('music')loops too.panTypeis only read whenoverlapcreates a new instance. For a normal sound, callsetSpatialPositionfirst: that switches the sound to spatial, and from then onpanSpatialPositionandpanSpatialOrientationin the play options are applied. On a stereo sound they are ignored.- A
groupIdwithout a group made bycreateSoundGroupstops the sound from playing at all, without an error. - On a stream from
loadStreamonlyvolume,pan,loop,playbackRate,startTimeandtrackProgressare used. - A restart for
resume,seekor a loop does not applyvolume,fadeInDurationorfadeOutDurationagain. Those belong to a freshplay().
See also
play: the method that takes these options.updateSoundOptions: change the stored options without playing.soundHubConfig: the defaults behind this table.soundGroup: groups and their play options.