简体   繁体   English

使用Web Audio API安排样本播放

[英]Scheduling sample playback with Web Audio API

I'm trying to build a drum machine that uses samples instead of oscillators, just for the sake of learning. 我正试图构建一个使用样本而不是振荡器的鼓机,只是为了学习。 I'm however having trouble scheduling sounds to play in rhythm for the first two beats. 但是我在安排前两个节拍的节奏时遇到麻烦。 What happens is that the first two beats play out of sync while the rest seem to play in rhythm as intended. 发生的情况是前两个节拍不同步,而其余两个节拍似乎按照预期的节奏演奏。 This issue doesn't occur when playing oscillators instead of samples. 播放振荡器而不是样本时,不会发生此问题。

I read The Tale of Two Clocks and every relevant tutorial I could find but all of them work with oscillators which don't seem to exhibit this issue. 我读了《两个时钟的故事》和我能找到的每个相关教程,但它们都与振荡器一起工作,而振荡器似乎没有出现这个问题。 The following is just one of the ways I tried to implement the code - I tried OOP and various functional programming versions but the problem occurs with all of them. 以下只是我尝试实现代码的方法之一-我尝试了OOP和各种功能编程版本,但是所有这些都出现了问题。

In this example I made to functions called playSound() and playKick(). 在此示例中,我制作了名为playSound()和playKick()的函数。 playSound() triggers an oscillator note, whereas the playKick() function triggers a kick sample. playSound()会触发振荡器音符,而playKick()函数会触发踢音采样。 Try switching between the two in the scheduler() function to hear how the problem occurs. 尝试在scheduler()函数中在两者之间切换以了解问题如何发生。

let audioContext = new (window.AudioContext || window.webkitAudioContext)();
var nextNotetime = audioContext.currentTime;
var startBtn = document.getElementById("startBtn");
var stopBtn = document.getElementById("stopBtn");
var timerID;
let kickBuffer;
loadKick('sounds/kick.wav');

function loadKick(url) {
  let xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'arraybuffer';
  xhr.onload = function() {
    audioContext.decodeAudioData(xhr.response, decoded => {
      kickBuffer = decoded;
    });
  }
  xhr.send();
}

function playKick(time) {
  let source = audioContext.createBufferSource();
  source.connect(audioContext.destination);
  source.buffer = kickBuffer;
  source.start(time);
}

function playSound(time) {

  var osc = audioContext.createOscillator();
  osc.connect(audioContext.destination);
  osc.frequency.value = 200;
  osc.start(time);
  osc.stop(time + 0.1);

};

function scheduler() {
    while(nextNotetime < audioContext.currentTime + 0.1) {
        // switch between playSound and playKick to hear the problem
        nextNotetime += 0.5;
        playSound(nextNotetime);
        // playKick(nextNotetime);
    }
   timerID = window.setTimeout(scheduler, 0);
}

startBtn.addEventListener('click', function() {
    scheduler();
  }, false);

stopBtn.addEventListener('click', function() {
    clearTimeout(timerID);
  }, false);

if(audioContext.state === 'suspended'){
  audioContext.resume();
};

As you can see the buffer is preloaded as soon as the file is loaded so that is not the root of the cause. 如您所见,缓冲区在文件加载后即被预加载,这不是原因的根源。 Any suggestions on how to fix the issue would be much appreciated. 任何有关如何解决此问题的建议将不胜感激。

Working fix. 工作修复。

"use strict";



var audioContext = new AudioContext(),
    futureTickTime = audioContext.currentTime,
    counter = 1,
    tempo = 120,
    secondsPerBeat = 60 / tempo,
    counterTimeValue = (secondsPerBeat / 4),
    timerID = undefined,
    isPlaying = false;


//_____________________________________________BEGIN load sound samples
let kickBuffer;
loadKick('sounds/kick.mp3');

function loadKick(url) {
    let xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'arraybuffer';
    xhr.onload = function() {
        audioContext.decodeAudioData(xhr.response, function(decoded) {
            kickBuffer = decoded;
        });
    }
    xhr.send();
}

function playKick(time) {
    let source = audioContext.createBufferSource();
    source.connect(audioContext.destination);
    source.buffer = kickBuffer;
    source.start(audioContext.currentTime + time);
}


//_____________________________________________END load sound samples


function scheduleSound(time) {
    playKick(time)

}


function playTick() {
    console.log(counter);
    secondsPerBeat = 60 / tempo;
    counterTimeValue = (secondsPerBeat / 1);
    counter += 1;
    futureTickTime += counterTimeValue;
}


function scheduler() {
    if (futureTickTime < audioContext.currentTime + 0.1) {
        scheduleSound(futureTickTime - audioContext.currentTime);

        playTick();
    }

    timerID = window.setTimeout(scheduler, 0);
}


function play() {
    isPlaying = !isPlaying;

    if (isPlaying) {
        counter = 1;
        futureTickTime = audioContext.currentTime;
        scheduler();
    } else {
        window.clearTimeout(timerID);
    }
}



var playStop = document.getElementsByClassName("play-stop-button")[0];

playStop.addEventListener("click",function(){
    play();
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM