javascript - 为网络音频创建音量控制

标签 javascript html audio web-audio-api

所以我正在通过网络音频创建一架钢琴,但在实现音量控制时遇到了问题。每当单击一个键时,音量控制应指示播放时的音量。我使用了 html5rocks 中的代码并将其修改为我自己的用途。基本上,我将所有声音片段加载到 BUFFERS 数组中,而不是 VolumeSample 数组。每当我尝试操纵 slider 并更改剪辑的增益时,我都会收到 null 的“无法读取属性”增益。我正在通过调试器对其进行测试,一切运行良好,直到 this.gainNode.gain.value = fraction * fraction;我的代码的一部分。看看我的代码,希望你能看到我遗漏了什么。我想提请注意 playSounds(buffer) 方法,这是创建和连接增益节点的地方,以及底部的方法 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 中为音量控制定义范围 slider 的地方(不用担心它在我的代码中的格式正确):

<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(buffer) 方法和 VolumeMain.changeVolume 函数。 LowPFilter 方法都不起作用,但一旦我们弄清楚如何调整任何给定源的音量,该方法的问题也将得到解决。

最佳答案

编辑(更新)。这会消除错误并允许您访问 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 范围 slider 设置增益节点的示例,这里有一个带有振荡器的示例。您可能想做一些尖峰测试,看看类似这样的东西是否可以在您的代码中使用振荡器工作,然后尝试将它应用到您的音频缓冲区代码中。

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

关于javascript - 为网络音频创建音量控制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26445011/

相关文章:

javascript - 多选组合框在 Javascript 中转换为字符串

javascript - 如何使用 Angular JavaScript 从(php 编码的 json)获取值?

javascript - 为什么这些内联 block div 不适合它们的容器?

javascript - 在网页上编辑、拖放框并保存内容和位置

audio - 从 Rust 中的多个音频流中并行获取大小相等的 block

html - Windows上的Safari无法播放音频

iphone - 如何减小 iPhone 应用程序的 .wav 音频文件的大小?

javascript - 发生错误后,Axios “get”仍会转到 “then”

javascript - 如何自动化这个 slider ?

javascript - 如何查找其中包含特定标签的 li 标签的索引