Как периодически запускать функцию в python
У меня есть простой метроном, и по какой-то причине, когда он находится на более низком удвоении, это нормально, но при более высоких значениях bpms это противоречиво и не устойчиво.
Я не знаю, что происходит.
Я хочу попробовать что-то, чтобы запускать его периодически. Есть ли способ сделать это?
Вот мой код:
class thalam():
def __init__(self,root,e):
self.lag=0.2
self.root=root
self.count=0
self.thread=threading.Thread(target=self.play)
self.thread.daemon=True
self.tempo=60.0/120
self.e=e
self.pause=False
self.tick=open("tick.wav","rb").read()
self.count=0
self.next_call = time.time()
def play(self):
if self.pause:
return
winsound.PlaySound(self.tick,winsound.SND_MEMORY)
self.count+=1
if self.count==990:
self.thread=threading.Thread(target=self.play)
self.thread.daemon=True
self.thread.start()
return
self.next_call+=self.tempo
new=threading.Timer(self.next_call-time.time(),self.play)
new.daemon=True
new.start()
def stop(self):
self.pause=True
winsound.PlaySound(None,winsound.SND_ASYNC)
def start(self):
self.pause=False
def settempo(self,a):
self.tempo=a
class Metronome(Frame):
def __init__(self,root):
Frame.__init__(self,root)
self.first=True
self.root=root
self.e=Entry(self)
self.e.grid(row=0,column=1)
self.e.insert(0,"120")
self.play=Button(self,text="Play",command=self.tick)
self.play.grid(row=1,column=1)
self.l=Button(self,text="<",command=lambda:self.inc("l"))
self.l.grid(row=0,column=0)
self.r=Button(self,text=">",command=lambda:self.inc("r"))
self.r.grid(row=0,column=2)
def tick(self):
self.beat=thalam(root,self.e)
self.beat.thread.start()
self.play.configure(text="Stop",command=self.notick)
def notick(self):
self.play.configure(text="Start",command=self.tick)
self.beat.stop()
def inc(self,a):
if a=="l":
try:
new=str(int(self.e.get())-5)
self.e.delete(0, END)
self.e.insert(0,new)
self.beat.settempo(60.0/(int(self.e.get())))
except:
print "Invalid BPM"
return
elif a=="r":
try:
new=str(int(self.e.get())+5)
self.e.delete(0, END)
self.e.insert(0,new)
self.beat.settempo((60.0/(int(self.e.get()))))
except:
print "Invalid BPM"
return
Ответы
Ответ 1
Выполнение чего-либо, требующего точности во времени, очень сложно из-за необходимости того, чтобы процессор делился с другими программами. К сожалению, для критически важных программ времени операционная система может переключаться на другой процесс, когда он выбирает. Это может означать, что он не может вернуться в вашу программу до тех пор, пока не произойдет заметная задержка. Использование time.sleep после времени импорта - более последовательный способ попытаться сбалансировать время между звуковыми сигналами, потому что процессор имеет меньше "разума" для переключения. Хотя сон в Windows имеет гранулярность по умолчанию 15,6 мс, но я предполагаю, что вам не нужно будет проигрывать более 64 Гц. Также кажется, что вы используете многопоточность, чтобы попытаться решить вашу проблему, однако реализация потоковой обработки python иногда заставляет потоки запускаться последовательно. Это еще более осложняет переход от вашего процесса.
Я считаю, что лучшее решение будет генерировать звуковые данные, содержащие звуковой сигнал метронома на требуемой частоте. Затем вы можете воспроизводить звуковые данные так, как хорошо понимает ОС. Поскольку система знает, как правильно обрабатывать звук, ваш метроном будет работать.
Извините, что разочаровывает, но критически важные приложения - это ОЧЕНЬ, если вы не хотите, чтобы ваши руки были грязными с системой, с которой вы работаете.
Ответ 2
Воспроизведение звука для эмулирования обычного метронома не требует возможности "реального времени".
Похоже, вы используете инфраструктуру Tkinter для создания графического интерфейса. root.after()
позволяет вызывать функцию с задержкой. Вы можете использовать его для реализации тиков:
def tick(interval, function, *args):
root.after(interval - timer() % interval, tick, interval, function, *args)
function(*args) # assume it doesn't block
tick()
запускает function
с заданным args
каждые interval
миллисекунд. Длительность отдельных тиков зависит от точности root.after()
, но в долгосрочной перспективе стабильность зависит только от функции timer()
.
Здесь script, который печатает некоторую статистику, 240
ударов в минуту:
#!/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='\r', 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()
Темп осциллирует только одним ударом: 240 ± 1 на моей машине.