对于这个项目,我正在设计一个音序器/鼓机,它应该能够以精确的速度发送 MIDI 音符。示例:每 2 秒 16 个音符(即在音乐术语中,每小节 16 个 1/16 音符,BPM 120),即每 125 毫秒一个音符。
我在想:
import time
def midi_note_send(...):
...
while True:
midi_note_send(...)
time.sleep(0.125)
如果我这样做,如何确定它恰好是 125 毫秒? 该循环的 1000 次迭代是否存在使用 126 秒而不是 125 秒的风险?如果是这样,如何获得更精确的循环?
最后一点:一台好的鼓机应该能够在 1 小时内保持 120 BPM 的节奏,并且有一定的精度误差 < 1 second.
使用的平台:Linux + RaspberryPi 但这个问题一般来说是有效的。
正如我所展示的这里
import time
def drummer():
counter = 0
# time.sleep(time.time() * 8 % 1 / 8) # enable to sync clock for demo
while counter < 60 * 8:
counter += 1
print time.time()
time.sleep(.125 - time.time() * 8 % 1 / 8)
此计时器会调整每个节拍并重新对齐。
而且调整几乎不需要时间:
timeit.timeit("time.time() * 8 % 1 / 8", number=1000000)
0.2493131160736084
这意味着每次执行大约需要 0.25 微秒
为了准确性:
1488490895.000160
1488490895.125177
1488490895.250167
1488490895.375151
1488490895.500166
1488490895.625179
1488490895.750178
1488490895.875153
~28 微秒的音符之间的漂移。在本地运行较长时间会产生约 130μs 的总漂移 (+- 65μs),但是,由于它每次节拍都与时钟同步,因此不会随着时间的推移而出现偏差。
这里有一个替代方案,可以根据计算机时钟的允许,尽可能准确地保持拍频,只要您愿意。这是以单个节拍最多延迟 1 毫秒为代价的,这可能更糟,但这只是为了演示替代方案:
# Time between beats
beat_length = 0.125
# Send first beat and initialize next beat time
midi_note_send()
next_beat = time.time() + beat_length
while True:
# Poll the time in 1ms increments until enough time has elapsed
while time.time() < next_beat:
time.sleep(0.001)
# Send the next note
midi_note_send()
# Increment the next beat time
next_beat += beat_length
您可以通过更改睡眠时间(例如,更改为
0.0001
以达到 0.1 毫秒的精度)来提高各个节拍的准确性,但代价是您更频繁地轮询时间时会占用 CPU。
您可以使用绝对时间(来自
time.time()
)来计算您的睡眠时间。
starttime = time.time()
for i in range(100):
midi_note_send(...)
sleep_duration = (i + 1) * 0.125 - time.time() + starttime
time.sleep(sleep_duration)
至少,你应该考虑到
midi_note_send
的计算时间
import time
# Define a generator for timing
def next_time(t0, dt):
while 1:
t0 += dt
yield t0
# Initialize timer and start loop
timer = next_time(time.time(), 0.125)
while True:
midi_note_send(...)
time.sleep(next(timer) - time.time())
扩展其他答案,这些答案都建议考虑处理笔记所花费的时间,这是一个可以轻松构建到类中的速率限制器。 如果所需的速率非常接近处理函数的运行时间,它还可以避免尝试休眠。
def rate_limit(rate, previous=0):
duration = time.time() - previous
if duration > 0:
sleep_time = 1.0 / rate - duration
if sleep_time > 0.0:
time.sleep(sleep_time)
return time.time()
previous = 0
for i in range(120):
midi_note_send(...)
previous = rate_limit(120, previous)
这是我的定时循环代码,它比 time.sleep() 准确,但它会慢慢落后于时钟。
from pygame import time
def method1():
clock = time.Clock()
fps = 120
c = 0
while c<100:
clock.tick(fps)
# accurate than time.sleep()
c+=1