纯粹用构建的节拍器NSTimer
不会很准确,正如 Apple 在其文档中所解释的那样。
由于典型运行循环管理的输入源多种多样,因此定时器时间间隔的有效分辨率被限制在 50-100 毫秒的量级。如果计时器的触发时间发生在长标注期间或运行循环处于不监视计时器的模式时,则计时器不会触发,直到运行循环下次检查计时器为止。
我建议使用NSTimer
每个所需滴答声会触发 50 次(例如,如果您想要每分钟 60 次滴答声,您将拥有NSTimeInterval
约为 1/50 秒。
然后你应该存储一个CFAbsoluteTime
它存储“最后一个刻度”时间,并将其与当前时间进行比较。如果当前时间和“最后一个刻度”时间之间的差异的绝对值小于某个容差(我会将其设置为每个间隔的刻度数的 4 倍,例如,如果您选择 1/50 秒每次 NSTimer 触发,您应该应用大约 4/50 秒的容差),您可以播放“滴答声”。
您可能需要校准容差才能达到所需的精度,但这个一般概念将使您的节拍器更加准确。
这里有一些更多信息另一个SO帖子。它还包括一些使用我讨论的理论的代码。我希望这有帮助!
Update您计算公差的方式不正确。在您的计算中,请注意容差与square的节拍数。这样做的问题是容差最终将小于计时器每秒触发的次数。看一眼这个图看看我的意思。这会在高 BPM 时产生问题。另一个潜在的错误来源是您的顶部边界条件。您确实不需要检查容忍度的上限,因为从理论上讲,计时器那时应该已经启动了。因此,如果经过的时间大于理论时间,你可以不管它。 (例如,如果经过的时间为 0.1 秒,而真实 BPM 的实际时间应为 0.05 秒,则无论您的容忍度如何,您都应该继续并触发计时器)。
这是我的计时器“滴答”功能,看起来工作正常。您需要对其进行调整以满足您的需求(包括悲观情绪等),但它在概念上是有效的。
func tick(timer:NSTimer) {
let elapsedTime:CFAbsoluteTime = CFAbsoluteTimeGetCurrent() - lastTick
let targetTime:Double = 60/timer.userInfo!.objectForKey("bpm")!.doubleValue!
if (elapsedTime > targetTime) || (abs(elapsedTime - targetTime) < 0.003) {
lastTick = CFAbsoluteTimeGetCurrent()
# Play the click here
}
}
我的计时器初始化如下:nextTimer = NSTimer(timeInterval: (60.0/Double(bpm)) * 0.01, target: self, selector: "tick:", userInfo: ["bpm":bpm], repeats: true)