2

I tried to do a very simple metronome that is working at 30 bpm:

While True:
    winsound.Beep(1000, 200)
    time.sleep(2 - 0.2)

Then, I turned on a metronome app on my phone together with the beep in the computer, and it became less precise as the time went on. Is there another way to do the "time.sleep()"? Is it even the "time.sleep()" guilt?

Python
  • 127
  • 1
  • 14
  • If you need a real timer, don't rely on sleep intervals. Run your interval poll, and then check clock time. I ran into this in Nodejs the other day, I'm sure the solution can be adapted to Python with minimal effort. https://stackoverflow.com/questions/49540098/preventing-a-bpm-ticker-from-slowly-drifting-out-of-sync-with-a-real-metronome - effectively: you know the clock time when your timer starts, you know the clock time when your metronome should "tick", so run your poller faster than the metronome ticks, and tick whenever your BPM value says you should. – Mike 'Pomax' Kamermans Jul 17 '18 at 20:42

1 Answers1

4

This is difficult to do on a multitasking operating system: it's not easy for the system to give you the exact sleep delay that you request. See How accurate is python's time.sleep()? for details.

But we can get fairly close by measuring the time span and adjusting the sleep delay. Python 3.3+ provides time.perf_counter, which is supposed to be pretty good at measuring time intervals, although the exact precision depends on your OS and hardware.

Here's a simple demo that just prints the difference between the requested and the measured time delay for each tick. The initial outputs are a bit sloppy, but it soon settles down to giving ticks that are within 10 microseconds of the requested interval. However, playing a sound takes more system resources than printing to the terminal, and that may impact the precision of this technique. For best results, minimize the task load on your machine, especially if it's single core.

I've set the delay here to 0.2 seconds = 300 bpm. Slower beat rates may give less precision because there's more time during each sleep cycle for the CPU to do other tasks that may cause the sleep to be a little longer than requested.

from time import sleep, perf_counter

delay = d = 0.2
print(60 / delay, 'bpm')
prev = perf_counter()
for i in range(20):
    sleep(d)
    t = perf_counter()
    delta = t - prev - delay
    print('{:+.9f}'.format(delta))
    d -= delta
    prev = t

typical output

300.0 bpm
+0.000262488
+0.000151862
-0.000019085
-0.000011358
+0.000023078
-0.000015817
-0.000004357
+0.000009283
-0.000012252
+0.000020515
-0.000009061
+0.000003249
-0.000011482
+0.000029230
+0.000001554
-0.000023614
-0.000003286
+0.000000127
-0.000003732
+0.000016311

These results are from an old single core 32 bit 2GHz machine running Linux, YMMV.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • So is there no real solution for all the speeds of the metronome? Can't I get over this? – Python Jul 18 '18 at 18:56
  • @Python You can set the BPM to anything you want, using `delay = d = 60 / bpm`. The errors will generally be small, mostly less than a millisecond, and usually less than that. Eg, I just tested it on 30 BPM and the errors were mostly around 5 microseconds. And since they tend to alternate between positive & negative fairly regularly the long-term drift should be quite small. But you'll have to run it on your system at various speeds to see what results you get. – PM 2Ring Jul 18 '18 at 21:06