Playing sound to emulate an ordinary metronome doesn't require "real-time" capabilities.
It looks like you use Tkinter framework to create the GUI. root.after()
allows you to call a function with a delay. You could use it to implement ticks:
def tick(interval, function, *args):
root.after(interval - timer() % interval, tick, interval, function, *args)
function(*args) # assume it doesn't block
tick()
runs function
with given args
every interval
milliseconds. Duration of individual ticks is affected by root.after()
precision but in the long run, the stability depends only on timer()
function.
Here's a script that prints some stats, 240
beats per minute:
#!/usr/bin/env python
from __future__ import division, print_function
import sys
from timeit import default_timer
try:
from Tkinter import Tk
except ImportError: # Python 3
from tkinter import Tk
def timer():
return int(default_timer() * 1000 + .5)
def tick(interval, function, *args):
root.after(interval - timer() % interval, tick, interval, function, *args)
function(*args) # assume it doesn't block
def bpm(milliseconds):
"""Beats per minute."""
return 60000 / milliseconds
def print_tempo(last=[timer()], total=[0], count=[0]):
now = timer()
elapsed = now - last[0]
total[0] += elapsed
count[0] += 1
average = total[0] / count[0]
print("{:.1f} BPM, average: {:.0f} BPM, now {}"
.format(bpm(elapsed), bpm(average), now),
end='
', file=sys.stderr)
last[0] = now
interval = 250 # milliseconds
root = Tk()
root.withdraw() # don't show GUI
root.after(interval - timer() % interval, tick, interval, print_tempo)
root.mainloop()
The tempo osculates only by one beat: 240±1 on my machine.
Here's asyncio
analog:
#!/usr/bin/env python3
"""Metronome in asyncio."""
import asyncio
import sys
async def async_main():
"""Entry point for the script."""
timer = asyncio.get_event_loop().time
last = timer()
def print_tempo(now):
nonlocal last
elapsed = now - last
print(f"{60/elapsed:03.1f} BPM", end="
", file=sys.stderr)
last = now
interval = 0.250 # seconds
while True:
await asyncio.sleep(interval - timer() % interval)
print_tempo(timer())
if __name__ == "__main__":
asyncio.run(async_main())
See Talk: ?ukasz Langa - AsyncIO + Music