为Web音频创建音量控制

时间:2014-10-18 22:05:52

标签: javascript html5 audio web-audio

所以我正在通过网络音频制作钢琴,并且无法实现音量控制。只要单击一个键,音量控制就应该决定它的播放音量。我使用了html5rocks中的代码并将其修改为我自己的用途。基本上我没有使用VolumeSample数组,而是将所有的声音剪辑加载到BUFFERS数组中。每当我尝试操纵滑块并改变剪辑的增益时,我都会得到一个“无法读取属性”的内容。为null。我正在通过调试器测试它,一切都运行良好,直到this.gainNode.gain.value = fraction * fraction;我的部分代码。只需看看我的代码,希望你能看到我错过的东西。我想引起注意playSounds(缓冲)方法,这是创建和连接增益节点的方法,以及底部的方法changeVolume,这是增益节点实际发生变化的地方:

    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's 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's 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;
};

这是我创建钢琴并检查单击了哪个键并播放相应声音(单独的JS文件)的地方:

// 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]);
        }
}; 

我在这里定义了HTML中音量控制的范围滑块(不要担心它在我的代码中正确格式化):

<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>

这个问题似乎是用于键盘本身的音量控制在某种程度上无法检测使用和修改哪个声音缓冲区。当您确切知道要调整音量的源时,您提供的代码很好,例如我的Beat1和Beat 2(这些音量控制都可以正常工作)。我需要代码能够修改缓冲区数组中任何源的卷。我使用Raphael软件包来创建键盘,如果这有帮助(它可能不会)。我会注意playSound(缓冲)方法和VolumeMain.changeVolume函数。 LowPFilter方法都不起作用,但是一旦我们弄清楚如何调整任何给定源的音量,方法的问题也将得到解决。

1 个答案:

答案 0 :(得分:3)

编辑(更新)。这将删除错误并允许您访问gainNode值

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
};

上一个回复

我真的不明白这个问题但是如果你只是想要一段代码作为设置带有HTML范围滑块的增益节点的例子,这里有一个带振荡器的例子。你可能想做一个尖峰测试,看看这样的东西是否在你的代码中使用振荡器,然后尝试将它应用到你的音频缓冲区代码。

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;
});