【问题标题】:Precise loop timing in PythonPython中的精确循环计时
【发布时间】:2017-07-22 18:17:03
【问题描述】:

对于this project,我正在设计一个音序器/鼓机,它应该能够以精确的速度发送 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 的节奏,精度误差 使用的平台:Linux + RaspberryPi 但这个问题一般是有效的。

【问题讨论】:

  • 坏消息:time documentation 基本上为零保证实际需要多长时间睡眠。它可以小于或大于请求的时间,任意数量。如您所知, sleep(0.125) 可能需要 0.0001 秒或一百万年。 (当然,这两种情况都不太可能,但是...)
  • 你可能想看看here,我提供了一种方法来阻止睡眠漂移。
  • @Kevin 哇,我以前从未读过time.sleep 的文档字符串。 In 3.5 it was modified 睡眠至少指定的时间量。

标签: python time timer


【解决方案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)

【讨论】:

    【解决方案2】:

    至少,你应该考虑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())
    

    【讨论】:

      【解决方案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 使用率。

      【讨论】:

        【解决方案4】:

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

        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)
        

        【讨论】:

          【解决方案5】:

          正如我所展示的here

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-10-24
            • 2012-02-24
            • 2023-04-10
            • 2011-05-27
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多