@mediamonks/channels v1.1.0
Channels
Channels is a channel based sound player for the web.
Installation
npm install @mediamonks/channelsQuick start
import { Channels } from '@mediamonks/channels';
// create {name: string} list of filenames without extension
const soundFiles = ['sound1', 'sound2'].map(name => ({
name,
}));
// create a Channels instance, extension and path are required
const channelsInstance = new Channels({
soundsExtension: 'mp3',
soundsPath: 'static/audio/',
sounds: soundFiles, // not required, can be set later
});
// load files
await channelsInstance.loadSounds();
// play a sound
channelsInstance.play('sound1');
// play it on a channel
channelsInstance.createChannel('background-music');
channelsInstance.play('sound2', {channel: 'background-music'});
// the exact same through the actual channel
const myChannel = channelsInstance.createChannel('ui-sounds');
myChannel.play('sound2');
// stop a sound
const sound1 = channelsInstance.play('sound1');
sound1.stop();
// stop all sounds
channelsInstance.stopAll();
// stop all sounds on a channel
channelsInstance.stopAll({channel: 'background-music'});
myChannel.stopAll(); // or through the actual channel
// set main volume, or for a channel
channelsInstance.setVolume(0.5);
myChannel.setVolume(0.5);Getting started
Before we can do anything, an instance of Channels has to be created. Two parameters are required: the location of the sound files, and the file extension to use:
new Channels({
soundsPath: 'location/of/your/files',
soundsExtension: 'mp3',
})Don't create more than one
Channelsinstance.
Optionally, an audioContext can be passed in the constructor options. If omitted, one will be created in the Channels constructor.
new Channels({
soundsPath,
soundsExtension,
audioContext: myAudioContext,
})React
For React projects, there is a use-channels hook to easily work with a Channels instance.
Suspended state
An AudioContext created without user interaction (for example a click) will be in the suspended state, in which no sound can be produced. This can happen for example if a Channels instance is created on page landing without supplying a (non-suspended) audioContext, since one will be created then automatically.
Creating a Channels instance this way is fine by itself, just make sure to resume the context once on user interaction before playing any sounds.
const onClick = async () => {
await channelsInstance.resumeContext();
}TLDR: The audioContext that is used must have been created or resumed on user interaction.
To check whether the context is suspended:
channelsInstance.contextIsSuspended
Loading files
Channels uses the sample-manager for dealing with files, and creates an instance of it named sampleManager.
channelsInstance.sampleManagerThe easiest way to load files is to supply a list of objects with a name property, matching the filenames without extension. The file extension has to be set when creating the Channels object (which allows for an easy switch to different filetypes on certain clients).
// - soundfiles/sound1.mp3
// - soundfiles/sound2.mp3
const soundFiles = [{name: 'sound1'}, {name: 'sound2'}];
// list can be used when instantiating Channels
const channelsInstance = new Channels({
soundsPath: 'soundfiles/',
soundsExtension: 'mp3',
sounds: soundFiles,
})
// or can be set at a later stage
channelsInstance.sampleManager.addSamples(soundFiles);
// either way, loading can be done like so:
await channelsInstance.loadSounds();
// optionally, keep track of progress
await channelsInstance.loadSounds((progress) => {
// ...
});The
loadSoundsmethod is an alias forsampleManager.loadAllSamples
For more info on how to define sound files, please refer to the sample-manager page.
Playing a sound
When a sound has been loaded, it can be played by referring to its unique name:
channelsInstance.play('sound');A second argument can be passed with optional properties:
channelsInstance.play('sound', {
volume: 0.5,
channel: 'channel1',
loop: true,
fadeInTime: 2,
pan: 1, // between -1 and 1
startTimeOffset: 0, // in seconds
effects: {
preVolume: myEffectsChain,
}
});The play function returns a reference to the playing sound, containing various methods to interact with the sound.
const sound = channelsInstance.play('sound');
sound.setVolume(0.5);Stopping a sound
Stopping a sound can be done by calling stop() on the playing sound reference.
const playingSound = channelsInstance.play('sound');
playingSound.stop();Sounds can be faded out before stopping by providing a fadeOutTime
playingSound.stop({ fadeOutTime: 2 });Channels
Channels are a way of grouping sounds that are played. They have their own volume and optional effects, and their output connects to the main output. They are completely optional and might not be needed at all, since sounds can also be played without a channel.
The reason to create a channel is to easily manage a group of sounds, for example to:
- change their volume
- apply effects
- fade out
- stop all of them
Creating a channel
The only thing needed to create a channel is a unique name:
channelsInstance.createChannel('my-channel');Second parameter can be used for some optional properties.
channelsInstance.createChannel('my-channel',{
type: 'monophonic',
volume: 0.5,
pan: 1,
effects: {
preVolume: myEffectsChain,
}
});Check the Audio Effects section for more information about the
effectsoption.
A reference to a channel is returned when creating it, or can be retrieved afterwards.
const myChannel = channelsInstance.createChannel('my-channel');
const myChannel = channelsInstance.getChannel('my-channel');Monophonic vs polyphonic
A Channel can be either polyphonic or monophonic, which defines how many sounds can be played simultaneously on a channel:
- A
monophonicchannel can play one sound at a time. When playing a sound on such a channel, all other sounds on that channel will be stopped - A
polyphonicchannel has no restrictions
The term
monophonicis used loosely. Since sounds can fade in and out, even on a monophonic channel multiple sounds may be audible at the same time.
This type can be set during creation. When no type is given, the default polyphonic is used.
channelsInstance.createChannel('monophonic-channel', {type: "monophonic"});
channelsInstance.createChannel('polyphonic-channel');Using a monophonic channel can be very helpful when creating a background music layer where the music loop needs to be changed now and then.
Playing a sound on a channel
There are two ways to play a sound on a channel. First of all, directly on the channelsInstance:
channelsInstance.play('mysound', { channel: 'mychannel'});Or, if you happen to have a reference to an actual channel:
myChannel.play('my-sound');All options for the play() method can still be used, except for the (in this context useless) channel prop.
myChannel.play('sound', {
volume: 0.5,
loop: true,
fadeInTime: 2,
pan: 1,
effects: {
preVolume: myEffectsChain,
}
});Stopping all sounds on a channel
channelsInstance.stopAll({channel: 'channel-name'});
// or:
myChannel.stopAll();
// examples above will override any fade outs.
// if you do want those, set 'immediate' to false:
channelsInstance.stopAll({ channel: 'channel-name', immediate: false });
myChannel.stopAll({ immediate: false });Default play/stop options
Channels can have default options to use when calling play() or stop() for sounds playing on that channel. This can be used for example to create a channel on which every sound automatically always loops when played.
These default options are the combination of the options for play() and stop(), without the channel.
// options for play
const sound = channelsInstance.play({
loop: true,
fadeInTime: 1,
volume: 0.5,
channel: 'my-channel',
pan: -1,
effects: {
preVolume: myEffectsChain,
}
});
// options for stop
sound.stop({
fadeOutTime: 1,
})
// all above props combined (except channel) can be used
const defaultStartStopProps = {
loop: true,
fadeInTime: 1,
volume: 0.5,
fadeOutTime: 1,
pan: -1,
effects: {
preVolume: myEffectsChain,
}
};
// can be set on creation as part of channel options
channelsInstance.createChannel('my-channel', { defaultStartStopProps });
// or can be set directly on a channel
myChannel.defaultStartStopProps = defaultStartStopProps;Passing props to
play()orstop()will override the defaultStartStopProps of a channel.
Default props (in combination with a monophonic channel) can be very helpful when creating a background music layer with music loops that need to change now and then:
const channel = channelsInstance.createChannel('background-music', {
type: 'monophonic',
defaultStartStopProps: { fadeInTime: 2, fadeOutTime: 2, loop: true },
});
// start a loop
channel.play('loop1');
// starting 2nd loop some time later, loop1 will fade out, loop2 will fade in
channel.play('loop2');Signal modifiers
A SignalModifier is something that allows the audio signal to be changed, for example to set the volume. These SignalModifiers exist three places:
- On a sound
- On a channel
- On the main output
Overall structure
Everything in Channels connects to the main output SignalModifier, which is the final step before going to the actual output. A channel has its own SignalModifer, which connects to the main SignalModifier. In the following image, the SignalModifiers are the blue blocks:
Sounds can be played either on a channel, or directly on the main output.
Sound structure
Sounds themselves also have a SignalModifier:
SignalModifier structure
A SignalModifier always contains the following nodes:
- a
GainNodefor setting volume - a separate
GainNodefor applying fades - a
StereoPannerNodeto pan the sound left or right
Optionally, custom effects chains can be added before or after these nodes.
Modifying the signal
The three places that have a SignalModifier (sound, channel or main output) all have a set of methods implemented:
const myChannel = channelsInstance.getChannel('my-channel');
// on a channel
myChannel.setVolume(0.5);
myChannel.getVolume();
myChannel.mute();
myChannel.unmute();
myChannel.fadeOut(1); // time in seconds
myChannel.fadeIn(1);
myChannel.setPan(1); // value between -1 and 1
myChannel.getPan();
// all these also exist on a playing sound
const playingSound = channelsInstance.play('my-sound');
playingSound.setVolume(0.5);
// and on the library instance (to target the main output)
channelsInstance.setVolume(0.5);Volume values should be
0or higher. Keep in mind that going beyond1might result in digital clipping.When calling
mute()thevolumewill be set to0, with the additional effect that the previous volume value will be stored and used when callingunmute()Pan values should be between
-1(left) and1(right).
Events
There are a few things in Channels that dispatch events. First of all: volume and pan changes on either a channel, a sound or the main instance.
// listening to main volume changes
channelsInstance.addEventListener('VOLUME_CHANGE', (event) => {
console.log(event.data.volume);
})
// or a channel
channel.addEventListener('VOLUME_CHANGE', (event) => {
console.log(event.data.volume);
})
// or a playing sound
playingSound.addEventListener('VOLUME_CHANGE', (event) => {
console.log(event.data.volume);
})
// all these three also dispatch pan changes
channel.addEventListener('PAN_CHANGE', (event) => {
console.log(event.data.pan);
})When using React, there are some hooks available to work with volume or pan changes.
Apart from that, the main instance notifies when the list of channels or playing sounds updates:
channelsInstance.addEventListener('CHANNELS_CHANGE', () => {
console.log(channelsInstance.getChannels());
})
channelsInstance.addEventListener('PLAYING_SOUNDS_CHANGE', () => {
console.log(channelsInstance.getPlayingSounds());
})Audio effects
Effects can be placed in the chain of a SignalModifier and are therefor available on the three places that have a SignalModifier:
- on the main output
- on a channel
on a playing sound
In all of these cases, effects can be inserted before and/or after the volume is applied.
Effects have to be defined as an EffectsChain, which is an object with an input and an output (both of type AudioNode). An effect can be a single node (with input and output pointing to the same AudioNode), or a long chain or multiple nodes.
Once an EffectsChain has been created, it can be used in the preVolume or postVolume prop of the effects prop.
const filter = audioContext.createBiquadFilter();
const myEffectsChain = {
input: filter,
output: filter,
}
// setting an effect on the main output, before the volume
const channelsInstance = new Channels({
soundsExtension,
soundsPath,
effects: {
preVolume: myEffectsChain
},
});
// setting it on a channel, after the volume
channelsInstance.createChannel('effect-channel', {
effects: {
postVolume: myEffectsChain,
}
})
// on a sound, before and after the volume
channelsInstance.play('my-sound', {
effects: {
preVolume: myEffectsChain,
postVolume: myOtherEffectsChain,
}
})Both input and output use the in/out with index
0to connect, which will work for nearly all cases. If for some reason you need to use a different index, you can add aGainNodebefore/after your effects as a solution.
Use <audio> or <video> output
It is possible to route the audio from an <audio> or <video> element into Channels, for example to apply effects or to control their volume along with other sounds.
To do so, use the connectMediaElement() method on either a channel or the main instance:
// connect output to a channel
myChannel.connectMediaElement(myVideoElement);
// or to the main output
channelsInstance.connectMediaElement(myVideoElement);