'Precise loop timing in Python
For this project I'm designing a sequencer/drummachine that should be able to send MIDI notes with a precise tempo. Example: 16 notes per 2 seconds (i.e. in music terminology sixteen 1/16-notes per bar at BPM 120), i.e. one note every 125 milliseconds.
I'm thinking about:
import time
def midi_note_send(...):
....
while True:
midi_note_send(...)
time.sleep(0.125)
If I do like this, how to be sure it will be exactly 125 milliseconds? Isn't there a risk that 1000 iterations of this loop will be using 126 seconds instead of 125 seconds? If so, how to have a more precise loop?
Final note: a good drummachine should be able to keep a 120 BPM rythm during 1 hour, with a precision error < 1 second.
Used platform: Linux + RaspberryPi but this question is valid in general.
Solution 1:[1]
As I showed 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)
This timer adjusts every beat and realigns.
And the adjustment takes almost no time:
timeit.timeit("time.time() * 8 % 1 / 8", number=1000000)
0.2493131160736084
which means every time it does that it takes about 0.25 microseconds
and for accuracy:
1488490895.000160
1488490895.125177
1488490895.250167
1488490895.375151
1488490895.500166
1488490895.625179
1488490895.750178
1488490895.875153
~28 microseconds of drift between notes. Running it locally for a longer time yields ~130?s of total drift (+- 65?s), but, as it syncs to the clock every beat, it won't deviate over time.
Solution 2:[2]
Here's an alternative that should maintain the beat frequency as accurately as your computer's clock allows, for as long as you want. It comes at the expense of individual beats being up to 1ms off, which is probably worse, but this is just to demonstrate the alternative:
# 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
You can increase the accuracy of individual beats by changing the sleep time (to 0.0001
for 0.1ms accuracy, for example), at the expense of CPU use as you poll the time more often.
Solution 3:[3]
You could use absolute time (from time.time()
) to calculate your sleep 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)
Solution 4:[4]
At the very least, you should account for the computation time of 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())
Solution 5:[5]
Expanding on other answers which all suggest taking into account the time taken to process your note, here is a rate limiter that can be easily built into a class. It also avoids trying to sleep if the rate required is very close the elapsed time of the processing function.
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)
Solution 6:[6]
this is my code for timed loop this is accurate than time.sleep(), but it'll lag slowly slowly behind clock.
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | glibdud |
Solution 3 | Sjoerd |
Solution 4 | Chris Mueller |
Solution 5 | Will |
Solution 6 | Vijay Panchal |