Create volume control for web audio

So, I am creating a piano via web audio and I am having trouble adjusting the volume control. Whenever a key is pressed, the volume control should dictate what volume it plays at. I used the html5rocks codeand changed it for his own purposes. Basically, instead of the VolumeSample array, I have all my audio clicks loaded into the BUFFERS array. Whenever I try to manipulate the slider and change the gain of a clip, I get "can't read property" get "null. I test it through the debugger and everything works fine as long as this fraction is this.gainNode.gain. value = fraction *; part of my code. Just take a look at my code and hopefully you can see what I'm missing. I would like to draw your attention to the playSounds (buffer) method, which creates and concatenates the gain of a node, and the changeVolume method at the bottom where the actual node gain change occurs:

    var context;
    var bufferLoader;
    var BUFFERS = {};
    var VolumeMain = {};
    var LowPFilter = {FREQ_MUL: 7000,
                  QUAL_MUL: 30};


    var BUFFERS_TO_LOAD = {
    Down1: 'mp3/0C.mp3',
    Down2: 'mp3/0CS.mp3',
    Down3: 'mp3/0D.mp3',
    Down4: 'mp3/0DS.mp3',
    Down5: 'mp3/0E.mp3',
    Down6: 'mp3/0F.mp3',
    Down7: 'mp3/0FS.mp3',
    Down8: 'mp3/0G.mp3',
    Down9: 'mp3/0GS.mp3',
    Down10: 'mp3/0A.mp3',
    Down11: 'mp3/0AS.mp3',
    Down12: 'mp3/0B.mp3',
    Up13: 'mp3/1C.mp3',
    Up14: 'mp3/1CS.mp3',
    Up15: 'mp3/1D.mp3',
    Up16: 'mp3/1DS.mp3',
    Up17: 'mp3/1E.mp3',
    Up18: 'mp3/1F.mp3',
    Up19: 'mp3/1FS.mp3',
    Up20: 'mp3/1G.mp3',
    Up21: 'mp3/1GS.mp3',
    Up22: 'mp3/1A.mp3',
    Up23: 'mp3/1AS.mp3',
    Up24: 'mp3/1B.mp3',
    Beat1: 'mp3/beat1.mp3',
        Beat2: 'mp3/beat2.mp3'
    };



    function loadBuffers() {
    var names = [];
    var paths = [];
    for (var name in BUFFERS_TO_LOAD) {
    var path = BUFFERS_TO_LOAD[name];
    names.push(name);
    paths.push(path);
    }
    bufferLoader = new BufferLoader(context, paths, function(bufferList) {
    for (var i = 0; i < bufferList.length; i++) {
      var buffer = bufferList[i];
      var name = names[i];
      BUFFERS[name] = buffer;
     }
    });
    bufferLoader.load();
    }
    document.addEventListener('DOMContentLoaded', function() {
    try {
    // Fix up prefixing
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    context = new AudioContext();
    }
    catch(e) {
    alert("Web Audio API is not supported in this browser");
    }
    loadBuffers();
    });




    function playSound(buffer) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    var filter1 = context.createBiquadFilter();
    filter1.type = 0;
    filter1.frequency.value = 5000;
    var gainNode = context.createGain();
    source.connect(gainNode);
    source.connect(filter1);
    gainNode.connect(context.destination);
    filter1.connect(context.destination);
    source.start(0);
    }
    //volume control
    VolumeMain.gainNode = null;
    VolumeMain.changeVolume = function(element) {
    var volume = element.value;
    var fraction = parseInt(element.value) / parseInt(element.max);
    this.gainNode.gain.value = fraction * fraction; //error occurs here
    };




// Start off by initializing a new context.
context = new (window.AudioContext || window.webkitAudioContext)();

if (!context.createGain)
  context.createGain = context.createGainNode;
if (!context.createDelay)
  context.createDelay = context.createDelayNode;
if (!context.createScriptProcessor)
  context.createScriptProcessor = context.createJavaScriptNode;

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return  window.requestAnimationFrame       || 
  window.webkitRequestAnimationFrame || 
  window.mozRequestAnimationFrame    || 
  window.oRequestAnimationFrame      || 
  window.msRequestAnimationFrame     || 
  function( callback ){
  window.setTimeout(callback, 1000 / 60);
};
})();




function BufferLoader(context, urlList, callback) {
  this.context = context;
  this.urlList = urlList;
  this.onload = callback;
  this.bufferList = new Array();
  this.loadCount = 0;
}

BufferLoader.prototype.loadBuffer = function(url, index) {
  // Load buffer asynchronously
  var request = new XMLHttpRequest();
  request.open("GET", url, true);
  request.responseType = "arraybuffer";

  var loader = this;

  request.onload = function() {
    // Asynchronously decode the audio file data in request.response
    loader.context.decodeAudioData(
      request.response,
      function(buffer) {
        if (!buffer) {
          alert('error decoding file data: ' + url);
          return;
        }
        loader.bufferList[index] = buffer;
        if (++loader.loadCount == loader.urlList.length)
          loader.onload(loader.bufferList);
      },
      function(error) {
        console.error('decodeAudioData error', error);
      }
    );
  }

  request.onerror = function() {
    alert('BufferLoader: XHR error');
  }

  request.send();
};

BufferLoader.prototype.load = function() {
  for (var i = 0; i < this.urlList.length; ++i)
  this.loadBuffer(this.urlList[i], i);
}
  LowPFilter.changeFrequency = function(element) {
  // Clamp the frequency between the minimum value (40 Hz) and half of the
  // sampling rate.
  var minValue = 40;
  var maxValue = context.sampleRate / 2;
  // Logarithm (base 2) to compute how many octaves fall in the range.
  var numberOfOctaves = Math.log(maxValue / minValue) / Math.LN2;
  // Compute a multiplier from 0 to 1 based on an exponential scale.
  var multiplier = Math.pow(2, numberOfOctaves * (element.value - 1.0));
  // Get back to the frequency value between min and max.
  this.filter1.frequency.value = maxValue * multiplier;
};

LowPFilter.changeQuality = function(element) {
  this.filter1.Q.value = element.value * this.QUAL_MUL;
};

LowPFilter.toggleFilter = function(element) {
  this.source.disconnect(0);
  this.filter1.disconnect(0);
  // Check if we want to enable the filter.
  if (element.checked) {
    // Connect through the filter.
    this.source.connect(this.filter1);
    this.filter1.connect(context.destination);
  } else {
    // Otherwise, connect directly.
    this.source.connect(context.destination);
  }
};
function Beat1() {
  this.isPlaying = false;
};

Beat1.prototype.play = function() {
  this.gainNode = context.createGain();
  this.source = context.createBufferSource();
  this.source.buffer = BUFFERS.Beat1;

  // Connect source to a gain node
  this.source.connect(this.gainNode);
  // Connect gain node to destination
  this.gainNode.connect(context.destination);
  // Start playback in a loop
  this.source.loop = true;
  this.source[this.source.start ? 'start' : 'noteOn'](0);
};

Beat1.prototype.changeVolume = function(element) {
  var volume = element.value;
  var fraction = parseInt(element.value) / parseInt(element.max);
  // Let use an x*x curve (x-squared) since simple linear (x) does not
  // sound as good.
  this.gainNode.gain.value = fraction * fraction;
};

Beat1.prototype.stop = function() {
  this.source[this.source.stop ? 'stop' : 'noteOff'](0);
};

Beat1.prototype.toggle = function() {
  this.isPlaying ? this.stop() : this.play();
  this.isPlaying = !this.isPlaying;
};

function Beat2() {
  this.isPlaying = false;
};

Beat2.prototype.play = function() {
  this.gainNode = context.createGain();
  this.source = context.createBufferSource();
  this.source.buffer = BUFFERS.Beat2;

  // Connect source to a gain node
  this.source.connect(this.gainNode);
  // Connect gain node to destination
  this.gainNode.connect(context.destination);
  // Start playback in a loop
  this.source.loop = true;
  this.source[this.source.start ? 'start' : 'noteOn'](0);
};

Beat2.prototype.changeVolume = function(element) {
  var volume = element.value;
  var fraction = parseInt(element.value) / parseInt(element.max);
  // Let use an x*x curve (x-squared) since simple linear (x) does not
  // sound as good.
  this.gainNode.gain.value = fraction * fraction;
};

Beat2.prototype.stop = function() {
  this.source[this.source.stop ? 'stop' : 'noteOff'](0);
};

Beat2.prototype.toggle = function() {
  this.isPlaying ? this.stop() : this.play();
  this.isPlaying = !this.isPlaying;
};

      

Here I create a piano and check which key was pressed and play the corresponding sound (separate JS file):

// keyboard creation function
window.onload = function () {   
    // Keyboard Height
    var keyboard_height = 120;

    // Keyboard Width
    var keyboard_width = 980;

    // White Key Color
    var white_color = 'white';

    // Black Key Color
    var black_color = 'black';

    // Number of octaves
    var octaves = 2;

    // ID of containing Div
    var div_id = 'keyboard';

    //------------------------------------------------------------

    var paper = Raphael(div_id, keyboard_width, keyboard_height);

    // Define white key specs
    var white_width = keyboard_width / 14;

    // Define black key specs
    var black_width = white_width/2;
    var black_height = keyboard_height/1.6;

    var repeat = 0;
    var keyboard_keys = [];

    //define white and black key names
    var wkn = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
    var bkn = ['Csharp', 'Dsharp', 'Fsharp', 'Gsharp', 'Asharp'];

    //create octave groups
    for (i=0;i<octaves;i++) {


        //create white keys first
        for (var w=0; w <= 6 ; w++) {
            keyboard_keys[wkn[w]+i] = paper.rect(white_width*(repeat + w), 0, white_width, keyboard_height).attr("fill", white_color);
        };

        //set multiplier for black key placement
        var bw_multiplier = 1.5;

        //then black keys on top
        for (var b=0; b <= 4 ; b++) {   
            keyboard_keys[bkn[b]+i] = paper.rect((white_width*repeat) + (black_width*bw_multiplier), 0, black_width, black_height).attr("fill", black_color);
            bw_multiplier = (b == 1) ? bw_multiplier + 4 : bw_multiplier + 2;
        };

        repeat = repeat + 7;
    }


        for (var i in keyboard_keys) {

            (function (st) {
                st.node.onclick = function(event) {
                    var newColor = '#'+(0x1000000+(Math.random())*0xffffff).toString(16).substr(1,6);
                    st.animate({fill:newColor}, 100);
                    var testKey = st.paper.getElementByPoint(event.pageX, event.pageY);
                    var indexOfKey = testKey.id;
                    if (indexOfKey == 0)
                    {
                        playSound(BUFFERS.Down1);
                    }
                    else if (indexOfKey == 1)
                    {
                        playSound(BUFFERS.Down3);
                    }
                    else if (indexOfKey == 2)
                    {
                        playSound(BUFFERS.Down5);
                    }
                    else if (indexOfKey == 3)
                    {
                        playSound(BUFFERS.Down6);
                    }
                    else if (indexOfKey == 4)
                    {
                        playSound(BUFFERS.Down8);
                    }
                    else if (indexOfKey == 5)
                    {
                        playSound(BUFFERS.Down10);
                    }
                    else if (indexOfKey == 6)
                    {
                        playSound(BUFFERS.Down12);
                    }
                    else if (indexOfKey == 7)
                    {
                        playSound(BUFFERS.Down2);
                    }
                    else if (indexOfKey == 8)
                    {
                        playSound(BUFFERS.Down4);
                    }
                    else if (indexOfKey == 9)
                    {
                        playSound(BUFFERS.Down7);
                    }
                    else if (indexOfKey == 10)
                    {
                        playSound(BUFFERS.Down9);
                    }
                    else if (indexOfKey == 11)
                    {
                        playSound(BUFFERS.Down11);
                    }
                    else if (indexOfKey == 12)
                    {
                        playSound(BUFFERS.Up13);
                    }
                    else if (indexOfKey == 13)
                    {
                        playSound(BUFFERS.Up15);
                    }
                    else if (indexOfKey == 14)
                    {
                        playSound(BUFFERS.Up17);
                    }
                    else if (indexOfKey == 15)
                    {
                        playSound(BUFFERS.Up18);
                    }
                    else if (indexOfKey == 16)
                    {
                        playSound(BUFFERS.Up20);
                    }
                    else if (indexOfKey == 17)
                    {
                        playSound(BUFFERS.Up22);
                    }
                    else if (indexOfKey == 18)
                    {
                        playSound(BUFFERS.Up24);
                    }
                    else if (indexOfKey == 19)
                    {
                        playSound(BUFFERS.Up14);
                    }
                    else if (indexOfKey == 20)
                    {
                        playSound(BUFFERS.Up16)
                    }
                    else if (indexOfKey == 21)
                    {
                        playSound(BUFFERS.Up19);
                    }
                    else if (indexOfKey == 22)
                    {
                        playSound(BUFFERS.Up21);
                    }
                    else
                    {
                        playSound(BUFFERS.Up23);
                    }
                };
            })(keyboard_keys[i]);
        }
}; 

      

Here's where I define the range slider for the volume control in my HTML (don't worry, it's formatted correctly in my code):

<div id="keyboard">
                    <script>
                    loadBuffers();
                    var beat1 = new Beat1();
                    var beat2 = new Beat2();
                    </script>
                </div>

                <div>Volume: <input type="range" min="0" max="100" value="100" oninput="VolumeMain.changeVolume(this);" /></div>
                <div>Low Pass Filter on: <input type="checkbox" checked="false" oninput="LowPFilter.toggleFilter(this);" />
                Frequency: <input type="range" min="0" max="1" step="0.01" value="1" oninput="LowPFilter.changeFrequency(this);" />
                Quality: <input type="range" min="0" max="1" step="0.01" value="0" oninput="LowPFilter.changeQuality(this);" /></div>
                <div>Beat 1: <input type="button" onclick="beat1.toggle();" value="Play/Pause"/>
                      Volume: <input type="range" min="0" max="100" value="100" onchange="beat1.changeVolume(this);"></div>
                <div>Beat 2: <input type="button" onclick="beat2.toggle();" value="Play/Pause"/>
                      Volume: <input type="range" min="0" max="100" value="100" onchange="beat2.changeVolume(this);"></div>
    </div>

      

This problem seems to be that the volume control used for the keyboard itself is somehow unable to determine which sound buffer to use and modify. The code you provided is good when you know exactly which source you are going to control the volume on, like in the case of Beat1 and Beat 2 (these volume controls work fine). I need some code to be able to change the volume of any source in the buffer array. I am using the Raphael package to create a keyboard, if that helps (maybe it doesn't). I would like to draw your attention to the playSound method (buffer) and the VolumeMain.changeVolume function. None of the LowPFilter methods work, but once we figure out how to adjust the volume for any source, the problem with that method will be fixed as well.

+3


source to share


1 answer


Change (update). This removes the error and allows access to the gainNode value

var gainNode = context.createGain();

function playSound(buffer) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    var filter1 = context.createBiquadFilter();
    filter1.type = 0;
    filter1.frequency.value = 5000;
    source.connect(gainNode);
    source.connect(filter1);
    gainNode.connect(context.destination);
    filter1.connect(context.destination);
    source.start(audioContext.currentTime);
}


//volume control

VolumeMain.changeVolume = function(element) {
    var volume = element.value;
    var fraction = parseInt(element.value) / parseInt(element.max);
    gainNode.gain.value = fraction * fraction;

    console.log(gainNode.gain.value);      // Console log of gain value when slider is moved
};

      


Previous answer

I don't really understand the problem, but if you just want the code snippet to be an example of a node gain setting with an HTML range slider, here is an example with a generator. You might want to do a little spike test and see if something like this works in your code using a generator, and then try to apply it to your sound buffer code.



http://jsfiddle.net/vqb9dmrL/

<input id="gainSlider" type="range" min="0" max="1" step="0.05" value="0.5"/>

      


var audioContext = new webkitAudioContext();



var osc = audioContext.createOscillator();
osc.start(audioContext.cueentTime);


var gainChan1 = audioContext.createGain();
osc.connect(gainChan1);
gainChan1.connect(audioContext.destination);   


var gainSlider = document.getElementById("gainSlider");
gainSlider.addEventListener('change', function() {
gainChan1.gain.value = this.value;
}); 

      


+3


source







All Articles