Планирование воспроизведения сэмплов с помощью Web Audio API - PullRequest
0 голосов
/ 27 октября 2018

Я пытаюсь построить драм-машину, которая использует сэмплы вместо осцилляторов, просто для обучения. У меня, однако, возникают проблемы с планированием воспроизведения звуков в ритме для первых двух ударов. Что происходит, так это то, что первые два удара воспроизводятся несинхронно, а остальные, кажется, играют в ритме, как и предполагалось. Эта проблема не возникает при воспроизведении осцилляторов вместо сэмплов.

Я прочитал «Повесть о двух часах» и все соответствующие учебные пособия, которые я смог найти, но все они работают с осцилляторами, которые, похоже, не демонстрируют эту проблему. Следующее является лишь одним из способов, которыми я пытался реализовать код - я пробовал ООП и различные версии функционального программирования, но проблема возникает со всеми из них.

В этом примере я использовал функции playSound () и playKick (). playSound () запускает ноту осциллятора, тогда как функция playKick () запускает сэмпл. Попробуйте переключиться между ними в функции планировщика (), чтобы узнать, как возникла проблема.

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

Как видите, буфер предварительно загружен, как только файл загружен, так что это не является причиной причины. Будем весьма благодарны за любые предложения о том, как решить проблему.

1 Ответ

0 голосов
/ 10 ноября 2018

Рабочее исправление.

"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();
})
...