Search code examples
javascriptweb-audio-api

Smooth volume change with Web Audio API


javascript newbie here. So I've been messing around with the Web Audio API trying to figure stuff out and I found that exponentialRampToValueAtTime does just what I want except it only seems to do it once(?) Take this generic code:

context = new AudioContext();
oscillator = context.createOscillator();
contextGain = context.createGain();
oscillator.type = 'sine';
oscillator.frequency = 440
oscillator.connect(contextGain);
contextGain.connect(context.destination);
oscillator.start(0);

contextGain.gain.value = 1 by default so if I run contextGain.gain.exponentialRampToValueAtTime(0.1,context.currentTime + 2) it drops nice and smoothly from 1 to 0.1. But if I try to get it back at 1, say contextGain.gain.exponentialRampToValueAtTime(1,context.currentTime + 2) it instead jumps suddenly to 1. Why does this happen? Is there any way I can do this ramp as many times as I want? Thanks in advance.


Solution

  • You need to call setValueAtTime() first to mark beginning of a change:

    contextGain.gain.setValueAtTime(contextGain.gain.value, context.currentTime);
    contextGain.gain.exponentialRampToValueAtTime(0.1, context.currentTime + 2);
    

    Without this, your gradual change starts in the past.

    This is needed because the gradual change of an audio param starts from the previous event. That previous event is a previous gain change using the methods setValueAtTime(), linearRampToValueAtTime(), exponentialRampToValueAtTime(), etc.

    Quote from the documentation at MDN:

    The exponentialRampToValueAtTime() method of the AudioParam Interface schedules a gradual exponential change in the value of the AudioParam. The change starts at the time specified for the previous event, follows an exponential ramp to the new value given in the value parameter, and reaches the new value at the time given in the endTime parameter.

    Here is small demo:

    var context = new AudioContext();
    var oscillator = context.createOscillator();
    var gain = context.createGain();
    oscillator.type = 'sine';
    oscillator.frequency = 440
    oscillator.connect(gain);
    gain.connect(context.destination);
    
    document.getElementById('bplay').addEventListener('click', function() {
      context.resume();
      oscillator.start(0);
      gain.gain.setValueAtTime(0.01, context.currentTime);
      gain.gain.exponentialRampToValueAtTime(1, context.currentTime + 2);
    });
    
    document.getElementById('bdown').addEventListener('click', function() {
      gain.gain.cancelScheduledValues(context.currentTime);
      gain.gain.setValueAtTime(gain.gain.value, context.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, context.currentTime + 2);
    });
    
    document.getElementById('bup').addEventListener('click', function() {
      gain.gain.cancelScheduledValues(context.currentTime);
      gain.gain.setValueAtTime(gain.gain.value, context.currentTime);
      gain.gain.exponentialRampToValueAtTime(1, context.currentTime + 2);
    });
    <button type="button" id="bplay">Play</button>
    <button type="button" id="bdown">Volume down</button>
    <button type="button" id="bup">Volume up</button>