HTML5/jQuery节拍器 - 性能问题

6
如标题所述,我正在尝试创建一个基于jQuery/JavaScript的节拍器,并使用HTML <audio />标签播放声音。
它“还行”,但我觉得setInterval方法不够准确。我在这里搜索了一些帖子,但由于我对jQuery和JavaScript都很新,而且我没有找到可行的解决方案。同样适用于“打开新选项卡并且setInterval停止或延迟”的问题。我试图用stop(true,true)来避免这个问题,但它没有像我预期的那样工作。
我希望节拍器在打开新选项卡并在那里进行其他操作时能“在后台”运行而不改变速度。此外,我想要一个确切的节拍器 ;)
这是我的测试环境:http://nie-wieder.net/metronom/test.html 目前,JS代码和HTML标记都在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);
            }
        });
});

任何帮助都将是极好的,我现在已经没有更多的想法了。

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

这段代码会每100毫秒比较“时间戳”和当前时间,看差值是否大于1秒(偏差为0.01秒),如果是,则记录“tick”,并重置当前时间戳。

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

在需要精确计时的情况下,这是最好的方法(我个人认为)。

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

第二次更新:在查阅了本文后,我想必须有一种更准确的方法来使用javascript中的定时器...所以经过一些研究,我发现了这篇文章。我建议你阅读一下。


首先非常感谢您的快速回复。我在这里更新了您的fiddle:http://jsfiddle.net/ping/UqbwT/3/,以查看您的方法是否适用于我。似乎并不是,至少在我的Macbook(Firefox 11)上不是。或者只有我看到那些延迟?(tick显示> 600或> 700一段时间)。 - Dominik
可能实现上存在问题,但基本思想仍然成立。你不能依赖浏览器的计时函数,它们不准确,你会看到偏差。相反,你需要在后台运行一个恒定的循环,查看真实的日期和时间,并使用它来确定是否已经过去了一秒钟。 - rlemon
没错,你说得对,非常感谢你到目前为止给我的答案。我想我会继续使用你的解决方案,并为那些想要“更多”的人创建一个Java应用程序或类似的东西;) 非常感谢。至少你是对的,这是一个实现问题,我想我不能再做更多了。 - Dominik
非常感谢 :) 我明天会尝试,现在德国已经是晚上9点了。 - Dominik

1

在尝试用requestAnimationFramesetTimeout精确控制鼓机应用的时序失败后(我的代码比三岁孩子的节奏还差),我放弃了并切换到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>

有用的资源:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接