1

When I use the following code to fade in a file it doesn't work as I expect. I expect a gradual fade in from 0 to 1 over the course of 5 seconds, instead I get an abrupt change five seconds into playing the file where the gain instantly goes from 0 to 1. What am I not understanding ?

soundObj.play = function() {
    playSound.buffer = soundObj.soundToPlay;
    playSound.connect(gainNode);
    gainNode.gain.value = 0;
    gainNode.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 5);
    gainNode.connect(audioContext.destination);
    playSound.start(audioContext.currentTime);
}

Update/Edit

I changed the above code to the following and it seems to work, now I am researching why. I've added a few comments. Mainly inquiring as to if adding a setValueAtTime method is necessary and if a non zero value is necessary for the gain.value properties default value.

soundObj.play = function() {
    playSound.buffer = soundObj.soundToPlay;
    playSound.connect(gainNode);
    gainNode.gain.value = 0.001;  // If set to 0 it doesn't fade in
    gainNode.gain.setValueAtTime(gainNode.gain.value, audioContext.currentTime); // Is this needed to have the other RampToValue methods work ?
    gainNode.gain.exponentialRampToValueAtTime(1, audioContext.currentTime + 7);
    gainNode.connect(audioContext.destination);
    playSound.start(audioContext.currentTime);
}
William
  • 4,422
  • 17
  • 55
  • 108

1 Answers1

4

A non-zero positive value is necessary for exponentialRampToValueAtTime. This isn't a Web Audio thing as much as it's just a math thing.

There's really no way to exponentially grow a value of 0.

Here's a rough version of the algorithm Chrome uses (rewritten in JS):

// start value
var value1 = 0.1;
// target value
var value2 = 1;
// start time (in seconds)
var time1 = 0;
// end time (in seconds)
var time2 = 2;
// duration
var deltaTime = time2 - time1;
// AudioContext sample rate
var sampleRate = 44100;
// total number of samples
var numSampleFrames = deltaTime * sampleRate;
// time incrementer
var sampleFrameTimeIncr = 1 / sampleRate;
// current time (in seconds)
var currentTime = 0;

// per-sample multiplier
var multiplier = Math.pow( value2 / value1, 1 / numSampleFrames );
// output gain values
var values = new Array( numSampleFrames );

// set up first value
var value = value1 * Math.pow( value2 / value1, ( ( currentTime - time1 ) * sampleRate ) / numSampleFrames );

for ( var i = 0; i < numSampleFrames; ++i ) {
  values[ i ] = value;
  value *= multiplier;
  currentTime += sampleFrameTimeIncr;
}

If you change value1 to zero, you'll see that the output array is basically full of NaN. But Chrome also adds a bit of extra code to save you from that by special-casing instances where your value is <= 0 so that you don't actually end up with gain values of NaN.

If none of that makes sense, let me put it this way. In order to exponentially grow a value, you basically need a loop that looks like this:

for ( var i = 0; i < length; ++i  ) {
  values[ i ] = value;
  value *= multiplier;
}

But if your initial value is 0, well, 0 multiplied by any other number is always 0.

Oh, and if you're interested (and can read C++), here's a link to the code that Chrome uses: https://chromium.googlesource.com/chromium/blink/+/master/Source/modules/webaudio/AudioParamTimeline.cpp

Relevant stuff is on line 316.

Edit

Apologies for a Chrome-centric explanation. But the underlying math concept of not being able to exponentially grow a value of zero will hold with any implementation.

Kevin Ennis
  • 14,226
  • 2
  • 43
  • 44