Javascript 防止BPM滴答器与真正的节拍器缓慢偏离同步

Javascript 防止BPM滴答器与真正的节拍器缓慢偏离同步,javascript,midi,timing,Javascript,Midi,Timing,我正在开发一个音乐生成器,它将BPM值作为输入,然后它将开始生成一些和弦、低音,并使用MIDI信号触发鼓VSTi 为了保持每分钟的正确节拍数,我使用了一个挂钟计时器,当你按play键时,它会在0开始计时,然后开始以固定的间隔将1/128个音符作为“滴答”计数。每次函数结束时,我都会通过简单计算从开始到现在的时间内适合的节拍数来检查未来的节拍数: class TrackManager { constructor(BPM) { this.tracks = ... this.v1

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

为了保持每分钟的正确节拍数,我使用了一个挂钟计时器,当你按play键时,它会在0开始计时,然后开始以固定的间隔将1/128个音符作为“滴答”计数。每次函数结束时,我都会通过简单计算从开始到现在的时间内适合的节拍数来检查未来的节拍数:

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

  ...
}
曲目基于
步数
s生成音乐,该步数以节拍表示其预期播放长度(使用
.duration
作为持续长度指示器,并使用
.end
,该节拍在播放某一步数时设置为未来的节拍值),播放代码添加了对节拍数的校正,为了确保如果通过的滴答声比预期的多(例如,由于复合舍入错误),则播放下一步,不管需要多少滴答声,以保持同步

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

  ...
}
班级轨道{
...
滴答声(滴答声){

如果(this.step.end的计算结果表明,
this.v128
仍然会导致产生漂移的值。例如,120 BPM产生15.625ms/tick,这相当可靠,但118 BPM产生15.88983050847576271186440677966[…]ms/tick,任何舍入(到任何有效位数)最终将产生越来越不正确的
滴答数
计算

这里的解决方案是通过将
this.v128
值替换为
this.tickFactor=BPM*32;
,然后将
tick()
函数更改为计算
tickCount
,从而保留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;

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

为什么不使用真正的MIDI,它通常以每四分之一音符96个脉冲的速度运行?还有,你在同步什么?不管BPM是什么,你都应该按照它的时钟信号保持完全同步。另外,使用性能计时器,而不是
Date.now()
,以获得更准确的计时。由于不能保证主DAW运行,因此实际上它将“独立”运行在三个虚拟设备上生成MIDI-OUT信号,这可能会被播放其他内容的DAW读入,但也可能不会。性能计时器是一个好主意,尽管给定了足够同步所需的时间分辨率,Date.now()确实完成了更改后的工作。