@goodboydigital/soundboy
v1.0.6
Published
Manage playback of sounds
Downloads
3,117
Readme
SoundBoy - a new Sound Manager
SoundBoy provides a simple interface through which to play sounds on different channels (e.g. sfx, vo, music), with a flexible approach to callbacks and sound playback/control. SoundBoy will play SoundInstances on a SoundChannel. A SoundInstance is a piece of audio that can be controlled independently, looped, execute a callback on complete and dispatch signals at various points of it's lifecycle. A SoundInstance is played on a SoundChannel, which provides the ability to control all sounds on that channel at once (e.g. volume, pause, restart, stop).
It currently uses Howler.js for the audio backend, however this dependency is isolated into just two classes, a SoundFactory and a SoundInstance implementation that provide the bridge between SoundBoy and Howler - all SoundBoy logic and features are implemented independently of the underlying audio library.
Usage
Register sounds
Sounds are registered with a SoundOptions object, containing the base data from which all instances of a sound will be created.
SoundBoy.registerSound({
// id and src are required
id: 'laser',
src: 'assets/audio/laser.wav',
// optional (default values shown)
preload: false, // set to true to immediately start loading the sound
stream: false, // set to true to enable sound to start playing before fully loaded
loop: false, // set to true to loop indefinitely
volume: 1 // this is the base volume of the sound, the actual sound volume will be the product of this, the sound instance volume, and the volume of the channel the sound is played on
})
Play sounds
// just play a sound effect
const laserSound = SoundBoy.playSfx('laser');
// sound can be controlled independently:
laserSound.volume = 0.5;
// play some vo with a callback
SoundBoy.playVo('instructions', {
callback: ()=> this.startGame()
});
Sound Channels
Sounds are played on a SoundChannel, by default there are channels for sfx, music and voiceover, but you can create as many channels as you like, they are referenced by unique string id:
SoundBoy.addChannel('animals');
SoundBoy.playOnChannel('woof', 'animals');
A SoundChannel enables all sounds on it to be controlled as a group, providing an api to control mute
, volume
, pause
, resume
and stop
, as well as a forEach
method to access and perform any function on all sound instances currently on the channel.
A SoundChannel has a singleFile
property which, if set to true, will limit the channel to only playing a single sound at any one time. If a sound is already playing when the channel is asked to play a sound, it will crossfade between the two sounds. By default, the vo and music channels are set to behave in this way, with the music channel having a longer crossfade duration.
Sound Instances
Every time a sound is played a new SoundInstance is created. A SoundInstance will play the sound to the end, and then either loop or stop, depending on the settings in the SoundOptions when registering the sound and the (optional) SoundInstanceOptions used when playing the sound.
SoundBoy.playSfx('footstep', {
// these are all optional, default values are shown:
volume: 1,
loops: 1,
panning: 0,
rate: 1,
callback: undefined,
callbackScope: undefined,
wontInterrupt: false // if true, the sound won't interrupt an existing sound on a singleFile SoundChannel
callbackOnInterrupt: false, // if true, execute callback if this sound is subsequently interrupted on a singleFile SoundChannel
})
When a sound is complete (it has played to the end and there are no more loops to play), it will stop, be removed from the SoundChannel it was played on, and be disposed of - the SoundInstance can no longer be used, unless a callback or onComplete handler keeps the sound alive by restarting, cueing or seeking the instance.
Sound Instance Callbacks
The callback
property on the SoundInstanceOptions used when playing the sound can be used to specify a function to be called when the sound has completed playing. The callback should be either a bound or arrow function to avoid scope issues. Alternatively, the callbackScope
can also be specified on the SoundInstanceOptions to specify what this
should resolve to inside the callback.
By default, callbacks are only executed if and when a sound has completed, and not if the sound is stopped, unless specifically instructed to do so:
const laserSound1 = SoundBoy.playSfx('laser', {
callback: () => console.log('laser1 callback!');
});
const laserSound2 = SoundBoy.playSfx('laser', {
callback: ()=> console.log('laser2 callback!')
});
laserSound1.stop(); // callback will not be called
laserSound2.stop(true); // callback will still be called
Similarly, for sounds played on a singleFile
channel, callbacks will not execute if a sound is interrupted, unless specifically instructed in the SoundInstanceOptions used to play the sound:
// the vo channel only plays one sound at a time...
const vo1 = SoundBoy.playVo('vo1', {
callback: () => console.log('vo1 callback!')
});
// vo2 will interrupt vo1 as the vo channel is `singleFile`, vo1's callback will not execute as it has not yet completed
const vo2 = SoundBoy.playVo('vo2', {
callback: () => console.log('vo2 callback'),
callbackOnInterrupt: true
});
// vo3 will interrupt vo2, vo2's callback _will_ execute because `callbackOnInterrupt` is true in it's SoundInstanceOptions
const vo3 = SoundBoy.playVo('vo3', {
callback: () => console.log('vo3 callback!')
});
// vo3's callback will execute if and when it completes
const vo4 = SoundBoy.playVo('vo4', {
wontInterrupt: true; // vo4 won't play because vo3 is already playing
})
Note that a callback will only ever execute once, even if the sound instance is restarted by the callback - if this is not the desired behaviour, use the sound instance's onComplete signal:
//contrived example:
const vo = SoundBoy.playVo('vo');
vo.onComplete.add( ( sound ) => sound.restart() ); // sound will loop indefinitely
A sound instance also has an onLoop
signal, which emits each time a sound reaches the end (also emits before onComplete
at the end of the final loop).
Controlling sound instances and channels
During playback, sound instances can be controlled independently, or as a group via the channel they are being played on. Instance and channel states are independent, for example a paused sound instance on a paused channel will remain paused when the channel resumes. There are also some global controls on SoundBoy itself for muting, pausing and resuming everything, and system controls to set paused and/or muted state based on system level events such as the app or browser tab going into the background.
Common properties
All layers of the Soundboy hierarchy, from the system through SoundBoy, SoundChannels and SoundInstances share some similar properties. Each object stores it's own state and also propogates the calculated state (based on it's state and that of it's parents) to objects further down the hierarchy - at the end of this path the SoundInstance will take care of ensuring the actual sounds reflect this calculated state.
volume: number
- (range: 0 to 1) (you may be able to go over 1 but prepare for distortion), default is 1muted: boolean
- default is falsepaused: boolean
- default is false
Common methods
The common paused
and muted
properties can be set directly or via:
pause(value: boolean = true)
mute(value: boolean = true)
Sound instance controls
properties:
panning: number
- (range: -1 to 1) - stereo panning from left (-1) to right (1), default is 0rate: number
- (range: 0 upwards) - speed of playback, default is 1
methods:
restart()
- restart sound from beginningseek(seconds: number)
- goto specified position and playcue(seconds: number)
- goto specified position and pause
Sound channel controls
properties:
singleFile: boolean
- set to true to only allow one sound at a time on the channelcrossFadeDuration: number
- duration (in seconds) of the cross fade between existing and new sound on asingleFile
channel, default is 0
methods:
restart()
- restart all sounds on channelstop()
- stops all sounds on channelforEach( callback: (sound: SoundInstance) => void )
- execute callback on all sounds in channel
SoundBoy controls:
All the common properties and methods and:
systemMuted: boolean
systemMute(value: boolean = true)