HTML5 / jQuery节拍器 - 性能问题

时间:2012-04-18 14:29:05

标签: javascript jquery performance setinterval

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

它有效&#34;好的&#34;,但在我看来,setInterval方法的工作不够准确。我在这里搜索了一些帖子,但是因为我是jQuery和JavaScript的新手,我还没有找到一个可行的解决方案。同样的&#34;打开新标签和setInterval停止或滞后&#34; - 问题。我试图用stop(true,true)阻止它,但它没有像我预期的那样工作。

我希望节拍器在背景中运行&#34;#34;打开新标签并在那里做某事时不改变速度。我也想要一个精确的节拍器;)

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

目前,JS-Code和HTML-markup都在test.html源代码中,所以你可以看一下。

此外,我使用的是(我认为)js-code:

$(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 个答案:

答案 0 :(得分: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();

这将(每百分之一秒)将'timestamp'与当前时间进行比较,以查看diff是否为秒或更长(偏差为0.01秒),如果是log'tick'并重置当前时间戳。

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

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

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

第二次更新:在回顾这篇文章之后,我认为必须有更准确的方法在javascript中使用计时器..所以通过一些研究我得到了this文章。我建议你阅读它。

答案 1 :(得分:1)

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

这是一个最小的例子:

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>

有用的资源: