防止BPM代码缓慢地与实际的节拍器不同步漂移 [英] Preventing a BPM ticker from slowly drifting out of sync with a real metronome

查看:167
本文介绍了防止BPM代码缓慢地与实际的节拍器不同步漂移的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个BPM值作为输入的音乐生成器,此后它将开始生成一些和弦,低音音符,并使用MIDI信号触发鼓VSTi.

I'm working on a music generator that takes a BPM value as input, after which it will start generating some chords, bass notes, and triggering a drum VSTi using MIDI signals.

为了使所有内容以正确的每分钟节拍数运行,我使用了挂钟计时器,当您敲击演奏时,该钟从0开始计时,然后在1/128th音符处开始计数为滴答声"定期间隔.每次函数滴答结束时,我都可以通过简单地计算time-since-start内适合的滴答声数量来检查将来会有多少滴答声:

In order to keep everything running at the correct number of beats per minutes, I'm using a wall clock timer that starts the clock at 0 when you hit play, and then starts counting 1/128th notes as "ticks" at a regular interval. Every time the function ticks over, I check how many ticks into the future we are by simply computing the number of ticks fit in the time-since-start:

class TrackManager {
  constructor(BPM) {
    this.tracks = ... 
    this.v128 = 60000/(BPM*32);
  }

  ...

  play() {
    this.tickCount = 0;
    this.playing = true;
    this.start = Date.now();
    this.tick();
  }

  tick() {
    if (!this.playing) return;

    // Compute the number of ticks that fit in the
    // amount of time passed since we started
    let diff = Date.now() - this.start;
    let tickCount = this.tickCount = (diff/this.v128)|0;

    // Inform each track that there is a tick update,
    // and then schedule the next tick.
    this.tracks.forEach(t => t.tick(this.tickCount));
    setTimeout(() => this.tick(), 2);
  }

  ...
}

曲目根据Step生成音乐,该音乐以滴答声表示其预期的播放长度(使用.duration作为持续长度指示符,而.end则设置为在以后播放步调时设置为将来的滴答值) ),并在播放代码中添加了要执行步骤的滴答数的更正,以确保如果通过的滴答声超过了预期(例如由于复合舍入误差),则下一步播放了-许多滴答声-减少必要的时间,以保持同步.

Tracks generate music based on Steps which indicate their intended play length in terms of ticks (using .duration as persistent length indicator, and a .end that is set to a future tick value anytime a step gets played), with the playback code adding a correction to the number of ticks to play a step for, to make sure that if more ticks pass than expected (due to compound rounding errors for instance) the next step is played however-many-ticks-necessary less, to keep things in sync.

class Track {
  ...

  tick(tickCount) {
    if (this.step.end <= tickCount) {
      this.playProgramStep(tickCount);
    }
  }

  playProgramStep(tickCount) {
    // Ticks are guaranteed monotonically increasing,
    // but not guaranteed to be sequential, so if we
    // find a gap of N ticks, we need to correct the
    // play length of the next step by that many ticks:
    let correction = this.stopPreviousStep(tickCount);
    let step = this.setNextStep();
    if (step) {
      step.end = tickCount + step.duration - correction;
      this.playStep(step);
    }
  }

  stopPreviousStep(tickCount) {
    this.step.stop();
    return (tickCount - this.step.end);
  }

  ...
}

这很好用,但是在最终的音轨速度上仍然有一些漂移,尤其是在运行单独的节拍器时(在我的情况下,是鼓模式VSTi,它被告知以哪个BPM演奏哪个模式,以及然后剩下要做自己的事情).虽然最初听起来还不错,但是大约一分钟后,节拍器播放的BPM和发电机正在运行的BPM之间出现了轻微但值得注意的不同步,我不确定这种不同步可能从何而来.

This works reasonably well, but there is still some drift left in the resulting track speed, particularly noticable when running a separate metronome (in my case, a drum pattern VSTi, which is told which pattern to play at which BPM, and is then left to do its own thing). While initially things sound just fine, after about a minute there is a slight but noticable desync between the BPM played by the metronome and the BPM that the generator is running at, and I'm not sure where that desync might still be coming from.

我希望在滴答级别能够实现最细微的同步(对于120 BPM小于16ms),这远低于可察觉的水平,但是代码中似乎还存在复合的异步,我不确定会在哪里.滴答声是在系统时钟之外生成的,因此,我不希望在JS遇到Date.now()的不稳定整数值之前会出现异步,我们不会再遇到285左右的millenia

I would have expected the subtlest of desync at the tick level (which for 120 BPM is less than 16ms), which is well below noticable, but there seems to be a compound desync left in the code and I'm not sure where it would be. The ticks are generated off of the system clock, so I wouldn't expect a desync to kick in until JS runs into unstable-integer values for Date.now(), which we're not about to run into for another 285 or so millenia

什么仍然可能导致不同步?

What might still be causing desync?

推荐答案

事实证明,this.v128的计算仍会导致引入漂移的值.例如,120 BPM的每刻度产生15.625毫秒,这是相当可靠的,但是118 BPM的每刻度产生15.889830508474576271186440677966ms. tickCount计算.

Turns out that the computation of this.v128 can still lead to values that introduce drift. For example, 120 BPM yields a 15.625ms per tick, which is fairly reliable, but 118 BPM yields 15.889830508474576271186440677966[...]ms per tick, any rounding of which (to any number of significant digits) will end up yielding an increasingly incorrect tickCount calculation.

这里的解决方案是保留滴答计算整数中涉及的所有值,方法是将this.v128值替换为this.tickFactor = BPM * 32;,然后更改tick()函数以将tickCount计算为:

The solution here is to keep all values involved in tick calculation integers, by replacing the this.v128 value with a this.tickFactor = BPM * 32; and then changing the tick() function to calculate tickCount as:

tick() {
  if (!this.playing) return;

  // Compute the number of ticks that fit in the
  // amount of time passed since we started
  let diff = Date.now() - this.start;

  // first form a large integer, which JS can cope with just fine,
  // and only use division as the final operation.
  let tickCount = this.tickCount = ((diff*this.tickFactor)/60000)|0;

  // Inform each track that there is a tick update,
  // and then schedule the next tick.
  this.tracks.forEach(t => t.tick(this.tickCount));
  setTimeout(() => this.tick(), 2);
}

这篇关于防止BPM代码缓慢地与实际的节拍器不同步漂移的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆