Python 中精确的循环计时

问题描述 投票:0回答:6

对于这个项目,我正在设计一个音序器/鼓机,它应该能够以精确的速度发送 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 但这个问题一般来说是有效的。

python time timer
6个回答
6
投票

正如我所展示的这里

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),但是,由于它每次节拍都与时钟同步,因此不会随着时间的推移而出现偏差。


3
投票

这里有一个替代方案,可以根据计算机时钟的允许,尽可能准确地保持拍频,只要您愿意。这是以单个节拍最多延迟 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。


1
投票

您可以使用绝对时间(来自

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)

1
投票

至少,你应该考虑到

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())

0
投票

扩展其他答案,这些答案都建议考虑处理笔记所花费的时间,这是一个可以轻松构建到类中的速率限制器。 如果所需的速率非常接近处理函数的运行时间,它还可以避免尝试休眠。

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)

0
投票

这是我的定时循环代码,它比 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
© www.soinside.com 2019 - 2024. All rights reserved.