'Web Audio API sound interaction stops working after a while
I'm working with the Web Audio API for my javascript project, and I've run into an issue that I can't seem to find the answer for anywhere.
I've added event listeners to respond to keydown events- every time a user presses a certain key on their keyboard, a sound will play. This works for a little while, but after maybe around 6 seconds of pressing keys, something happens that makes the sound stop - the keys won't produce sound for maybe half a second, then they will start working again. Anyone have any idea why this is happening, and how I can fix it?
Here's my code for the event listener :
import Audio from './scripts/audio'
document.addEventListener('keydown', (e) => {
const audio = new Audio();
let key = e.key;
audio.createNotes(key);
})
and here's my code for the audio :
class Audio {
constructor() {
// instantiate web audio api object
this.audioContext = new AudioContext();
// create gain node, gain corresponds with volume
this.gainNode = this.audioContext.createGain();
this.gainNode.gain.setValueAtTime(0.08, 0);
// allows volume to decrease with time
this.gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + 1.5);
}
createNotes(key) {
// C4 to C5 scale, attach frequencies to corresponding keyboard value
const notes = {
's': 261.63,
'd': 293.66,
'f': 329.63,
'g': 349.23,
'h': 392.00,
'j': 440.00,
'k': 493.88,
'l': 523.25,
'e': 587.33,
'r': 659.25,
't': 698.46,
'y': 783.99,
'u': 880.00,
'i': 987.77,
'o': 1046.50,
'p': 1174.66
}
// if e.key corresponds with notes key, we want to play sound
if (notes[key]) {
// oscillator corresponds with frequency,
// create oscillator node to attach frequency from notes object
let oscillator = this.audioContext.createOscillator();
oscillator.frequency.setValueAtTime(notes[key], this.audioContext.currentTime);
// lower gain for higher frequency notes
if (notes[key] > 699) {
this.gainNode.gain.setValueAtTime(0.03, this.audioContext.currentTime);
}
// connect oscillator node to volume node
oscillator.connect(this.gainNode);
// connect gain node to destination (speakers)
this.gainNode.connect(this.audioContext.destination);
oscillator.start(0);
// tone will play for 1.5 seconds
oscillator.stop(this.audioContext.currentTime + 1.5)
}
}
}
export default Audio;
Solution 1:[1]
The problem is that you're creating too many AudioContext instances. This is not the intended usage of the API. Why are you creating so many instances? You should re-use them.
Generally you should only need a single AudioContext. On the mozzila developer page it's clearly stated that some Chrome versions only support 6.
https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/AudioContext#google_chrome
Here's a question/answer that further explains the problem. Chrome produces no audio after reaching 50 audio output streams
To solve your problem basically create a single AudioContext and make it accessible globally like so:
let globalAudioContext = new AudioContext();
class Audio
{
constructor()
{
// instantiate web audio api object
// create gain node, gain corresponds with volume
this.gainNode = globalAudioContext.createGain();
this.gainNode.gain.setValueAtTime(0.08, 0);
// allows volume to decrease with time
this.gainNode.gain.exponentialRampToValueAtTime(0.001, globalAudioContext.currentTime + 1.5);
}
createNotes(key)
{
// C4 to C5 scale, attach frequencies to corresponding keyboard value
const notes = {
's': 261.63,
'd': 293.66,
'f': 329.63,
'g': 349.23,
'h': 392.00,
'j': 440.00,
'k': 493.88,
'l': 523.25,
'e': 587.33,
'r': 659.25,
't': 698.46,
'y': 783.99,
'u': 880.00,
'i': 987.77,
'o': 1046.50,
'p': 1174.66
}
// if e.key corresponds with notes key, we want to play sound
if (notes[key]) {
// oscillator corresponds with frequency,
// create oscillator node to attach frequency from notes object
let oscillator = globalAudioContext.createOscillator();
oscillator.frequency.setValueAtTime(notes[key], globalAudioContext.currentTime);
// lower gain for higher frequency notes
if (notes[key] > 699) {
this.gainNode.gain.setValueAtTime(0.03, globalAudioContext.currentTime);
}
// connect oscillator node to volume node
oscillator.connect(this.gainNode);
// connect gain node to destination (speakers)
this.gainNode.connect(globalAudioContext.destination);
oscillator.start(0);
// tone will play for 1.5 seconds
oscillator.stop(globalAudioContext.currentTime + 1.5);
}
}
}
document.addEventListener('keydown', (e) =>
{
const audio = new Audio();
let key = e.key;
console.log(e.keyCode);
audio.createNotes(key);
})
If you're trying to make a keyboard piano, you should have each key bound to a pre-initialized graph node (Gain Node) and reuse them.
And the reason why it starts working again is because it takes time for the garbage collector to kick in.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |