In my object, I got an AudioBufferSourceNode object (this.bSrc), connected to a gain node (this.audioDestination) that plays a (valid) AudioBuffer already stored in the object (this.audioBuffer).
If this.audioDestination is connected to the audio context final destination this.mainContext.destination and I call the start(0) method on my AudioBufferSourceNode object (this.bSrc), it will play fine.
If, instead, this.audioDestination is not yet connected to the Audio Context final destination, the buffer won’t play (and it’s ok), but its playback will be delayed until the connection is made (thus invalidating any assumption on when the AudioBufferSourceNode finishes playing).
I’m currently implementing a plugin architecture, in which every plugin has its audioDestination gain node and doesn’t (and must not) know whether it is indirectly connected to the final Audio Context destination. What seems logical to me is that the AudioBufferSourceNode should ‘play’ immediately and finish, even if it’s not connected to the final Audio Context destination. But it seems it doesn’t work like this. Am I right or am I getting it wrong? Is there some way to change this behaviour?
Code:
/* Create the audio Context. */
this.mainContext = new webkitAudioContext;
/* Create an audio gain node */
this.audioDestination = this.mainContext.createGainNode();
/* [...] An AudioBuffer gets decoded and stored into this.audioBuffer*/
/* Create an AudioBufferSourceNode and try to play it immediately */
this.bSrc = this.audioContext.createBufferSource();
this.bSrc.connect (this.audioDestination);
this.bSrc.buffer = this.audioBuffer;
this.bSrc.start(0);
/* Create a callback for when the AudioBufferSourceNode finishes playing */
if (!this.bSrc.loop) {
var pbTimer = setTimeout(function() {
this.playFinishedCallback();
}.bind(this), this.audioBuffer.duration * 1000 / this.bSrc.playbackRate.value);
}
/* Connect this.audioDestination to the final AudioContext destination */
/* If this statement is executed after the previous ones, playback will start NOW */
this.audioDestination.connect(this.mainContext.destination);
This is the way the Web Audio API is currently implemented in Chrome, and the design used therein. There’s been talk of changing it; in fact, I was a proponent of the “audio cable model” you describe. 🙂 It’s not the only sane model, though. There isn’t any way to change this behavior in current implementation.
However, there is an easy workaround – simply keep a zeroed-out gain node connected to context.destination, and connect all your ABSNs to that as well as their (eventual) connection to the context.destination.