Skip to content

Instantly share code, notes, and snippets.

@beefchimi
Last active November 6, 2017 20:52
Show Gist options
  • Save beefchimi/4f6bc59bf11736d3297e8cb0f7f2f942 to your computer and use it in GitHub Desktop.
Save beefchimi/4f6bc59bf11736d3297e8cb0f7f2f942 to your computer and use it in GitHub Desktop.
Loop class
import fetchAudioBuffer from './helpers/fetch-audio-buffer';
const zeroGain = 0.001;
const maxGain = 0.6;
const durationRamp = {
up: 0.2,
down: 0.3,
};
// 310ms: just long enough to allow the fade out to complete
// before suspending playback
const fadeTimeout = (durationRamp.down * 1000) + 10;
const assetPaths = {
accessible: [
'assets/audio/accessible-hover.wav',
'assets/audio/accessible-active.wav',
],
extensible: [
'assets/audio/extensible.wav',
],
};
export default class Loop {
static loopKeys = Object.keys(assetPaths);
constructor(key) {
if (!Loop.loopKeys.includes(key)) {
return Error(`The requested sound is not available: ${key}`);
}
this.context = new AudioContext();
this.trackCount = assetPaths[key].length;
this.sources = {};
this.gainNodes = {};
this.hasStarted = false;
this.play = this.play.bind(this);
this.pause = this.pause.bind(this);
this.resume = this.resume.bind(this);
for (let i = 0; i < this.trackCount; i++) {
this._createGainNodes(i);
fetchAudioBuffer(assetPaths[key][i], this.context).then((response) => {
return this._connectSource(response, i);
}).catch(() => {
return null;
});
}
}
mute(trackNumber) {
this.gainNodes[`node${trackNumber}`].gain.exponentialRampToValueAtTime(
zeroGain,
this.context.currentTime + durationRamp.down
);
}
unmute(trackNumber) {
this.gainNodes[`node${trackNumber}`].gain.exponentialRampToValueAtTime(
maxGain,
this.context.currentTime + durationRamp.up
);
}
play() {
if (!this.hasStarted) {
for (let i = 0; i < this.trackCount; i++) {
this.sources[`source${i}`].start();
}
// Only unmute the first track
this.unmute(0);
this.hasStarted = true;
return;
}
this.resume();
}
pause() {
for (let i = 0; i < this.trackCount; i++) {
this.mute(i);
}
// Pause the sources only once the fadeOut has completed
setTimeout(() => {
this.context.suspend();
}, fadeTimeout);
}
resume() {
this.context.resume().then(() => {
// Resume only the first gainNode
return this.unmute(0);
}).catch((error) => {
return console.warn(`Something went wrong with resuming playback: ${error}`);
});
}
_connectSource(sound, index) {
if (!sound) {
return;
}
const sourceKey = `source${index}`;
this.sources[sourceKey] = this.context.createBufferSource();
this.sources[sourceKey].buffer = sound;
this.sources[sourceKey].loop = true;
this.sources[sourceKey]
.connect(this.gainNodes[`node${index}`])
.connect(this.context.destination);
}
_createGainNodes(index) {
const gainKey = `node${index}`;
this.gainNodes[gainKey] = this.context.createGain();
this.gainNodes[gainKey].gain.setValueAtTime(
zeroGain,
this.context.currentTime
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment