6

正如标题中提到的,我正在尝试创建一个基于 jQuery/JavaScript 的节拍器以及 HTML<audio />标签来播放声音。

它工作“没问题”,但在我看来,该setInterval方法工作得不够准确。我在这里搜索了一些线程,但是因为我对 jQuery 和 JavaScript 都是新手,而且我还没有找到可行的解决方案。“打开新选项卡并 setInterval 停止或滞后” - 问题相同。我试图阻止这种情况,stop(true,true)但它没有按我的预期工作。

我希望节拍器在打开新标签并在那里做某事时“在后台”运行而不改变速度。我当然也想要一个精确的节拍器;)

这是我的测试环境: http: //nie-wieder.net/metronom/test.html

目前 JS-Code 和 HTML-markup 都在 test.html 源码中,你可以去那里看看。

另外,这是我使用的相关(我认为)js代码:

$(document).ready(function() {

    //vars
    var intervalReference   = 0;
    var currentCount        = 1;      
    var countIncrement      = .5;      
    var smin = 10;
    var smax =240;
    var svalue = 120;

    //soundchkbox
    $(".sndchck").attr("disabled", true);

    //preload sound
    $.ajax({
        url: "snd/tick.ogg",
        success: function() {
            $(".sndchck").removeAttr("disabled");
        }
    });

    // tick event
    var met = $("#bpm").slider({
            value: 120,
            min: smin,
            max: smax,
            step: 1,
            change: function( event, ui ) {
                var delay = (1000*60/ui.value)/2
                clearInterval(intervalReference);

                //seems to be the Problem for me
                intervalReference = setInterval(function(){
                    var $cur_sd = $('#sub_div_'+currentCount);
                    $cur_sd
                    .stop(true,true)
                    .animate({opacity: 1},15,
                                function() {
                                //Play HTML5 Sound
                                if($('#sound_check:checked').val()){
                                    $('#tick')
                                    .stop(true,true)
                                    .trigger("play");
                                }
                                    $(this).
                                    stop(true,true).
                                    animate({opacity:0});
                                }
                    );
                    currentCount += countIncrement;
                    if(currentCount > 4.5) currentCount = 1
                }, delay);
                createMusicTag(ui);
            }
        });
});

任何帮助都会很棒,我现在没有想法。

4

2 回答 2

5

setInterval 不准确。你可以尝试做的是:

var timestamp = (new Date()).getTime();
function run() {

     var now = (new Date()).getTime();

     if( now - timestamp >= 1000 ) {
         console.log( 'tick' );
         timestamp = now;
     }

     setTimeout(run, 10);
}
run();

这将(每百分之一秒)将“时间戳”与当前时间进行比较,以查看差异是否为一秒或更多(偏差为 0.01 秒)以及是否记录“滴答”并重置当前时间戳。

http://jsfiddle.net/rlemon/UqbwT/

这是需要时间准确(imo)的最佳方法。

更新:如果你改变 setTimeout 时间设置......你会得到更少的偏差。http://jsfiddle.net/rlemon/UqbwT/1/

第二次更新:在查看了这篇文章之后,我认为必须有一种更准确的方式在 javascript 中使用计时器。所以通过一些研究,我发现了这篇文章。我建议你阅读它。

于 2012-04-18T14:36:26.750 回答
1

在尝试为鼓机应用程序哄骗requestAnimationFramesetTimeout准确计时但失败后(一个 3 岁的孩子可以保持比我的代码更好的节奏),我放弃并切换到Web Audio API,它立即为我的目的提供了准确的音频计时。

这是一个最小的例子:

const scheduleBeep = time => {
  const osc = audioContext.createOscillator();
  osc.connect(audioContext.destination);
  osc.frequency.value = 300;
  osc.start(time);
  osc.stop(time + 0.1);
};

window.AudioContext = window.AudioContext || window.webkitAudioContext;
const audioContext = new AudioContext();
let timeBetweenSteps = 60 / 120;
let nextStepTime;
let interval;
const lookahead = 0.1;
const timeoutDelay = 30;

const schedule = () => {
  while (nextStepTime < audioContext.currentTime + lookahead) {
    nextStepTime += timeBetweenSteps;
    scheduleBeep(nextStepTime);
  }
};
document.querySelector("button")
  .addEventListener("click", evt => {
    clearInterval(interval);

    if (evt.target.textContent === "Run") {
      evt.target.textContent = "Stop";
      nextStepTime = audioContext.currentTime;
      interval = setInterval(schedule, timeoutDelay);
    }
    else {
      evt.target.textContent = "Run";
    }
  })
;
<button>Run</button>

还有一个稍微复杂的例子,它添加了一个声音文件和一个 BPM 滑块:

<body>
<form class="metronome">
  <button class="run">Run</button>
  <input class="bpm" type="range" min="60" max="500" value="120">
  <span class="bpm-readout">120</span>
</form>

<script>

const url = "https://upload.wikimedia.org/wikipedia/commons/e/e5/Abadie.jo-Marteau-1.ogg";

const $ = document.querySelector.bind(document);
const metroEls = {
  run: $(".metronome .run"),
  bpm: $(".metronome .bpm"),
  bpmReadout: $(".metronome .bpm-readout"),
};

window.AudioContext = 
  window.AudioContext || window.webkitAudioContext
;
const audioContext = new AudioContext();

(async () => {
  const response = await fetch(url);
  const arrayBuffer = await response.arrayBuffer();
  const audioBuffer = await audioContext
    .decodeAudioData(arrayBuffer)
  ;

  const scheduleSample = time => {
    const source = audioContext.createBufferSource();
    source.buffer = audioBuffer;
    source.connect(audioContext.destination);
    source.start(time);
  };

  let timeBetweenSteps = 60 / 120;
  let nextStepTime;
  let interval;
  const lookahead = 0.1;
  const timeoutDelay = 30;

  const schedule = () => {
    while (nextStepTime < 
             audioContext.currentTime + lookahead) {
      nextStepTime += timeBetweenSteps;
      scheduleSample(nextStepTime);
    }
  };
  metroEls.run.addEventListener("click", () => {
    if (metroEls.run.textContent === "Run") {
      metroEls.run.textContent = "Stop";
      nextStepTime = audioContext.currentTime;
      clearInterval(interval);
      interval = setInterval(schedule, timeoutDelay);
    }
    else {
      metroEls.run.textContent = "Run";
      clearInterval(interval);
    }
  });
  metroEls.bpm.addEventListener("change", e => {
    timeBetweenSteps = 60 / e.target.value;
    metroEls.bpmReadout.innerText = e.target.value;
  });
})();

</script>
</body>

有用的资源:

于 2021-07-05T00:20:21.177 回答