@goodboydigital/soundboy
TypeScript icon, indicating that this package has built-in type declarations

1.0.6 • Public • Published

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 1
  • muted: boolean - default is false
  • paused: 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 0
  • rate: number - (range: 0 upwards) - speed of playback, default is 1

methods:

  • restart() - restart sound from beginning
  • seek(seconds: number) - goto specified position and play
  • cue(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 channel
  • crossFadeDuration: number - duration (in seconds) of the cross fade between existing and new sound on a singleFile channel, default is 0

methods:

  • restart() - restart all sounds on channel
  • stop() - stops all sounds on channel
  • forEach( 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)

Readme

Keywords

none

Package Sidebar

Install

npm i @goodboydigital/soundboy

Weekly Downloads

342

Version

1.0.6

License

ISC

Unpacked Size

99.3 kB

Total Files

26

Last publish

Collaborators

  • jjarrell50
  • bbc-childrens-apps
  • doormat23
  • gbd