I'm trying to wrtie a metronome script which gives me audio feedback as well as sending MIDI messages to a synthesizer. I use Python 2.7.5+
and Pygame 1.9.1.release
on Linux Mint 16. I'm okay with the MIDI part. What troubles me is the timing.
First of all, here is basically what I do:
import time
import pygame.mixer
pygame.mixer.init()
sound = pygame.mixer.Sound('click.wav')
interval = 0.5 #should be equivalent to 120 bpm
t0 = time.time()
while True: #infinite loop, use keyboard interrupt Ctrl-C
if time.time() - t0 >= interval:
sound.play()
t0 = time.time()
However, this is fairly unstable and impossible to play an instrument to.
I also looked into time.clock()
:
import time
import pygame.mixer
pygame.mixer.init()
sound = pygame.mixer.Sound('click.wav')
interval = 0.5 #should be equivalent to 120 bpm
t0 = time.clock()
while True:
if time.clock() - t0 >= interval:
sound.play()
t0 = time.clock()
...as well as pygame.time.Clock()
, specifically pygame.time.Clock.tick_busy_loop()
, which supposedly provides better precision by eating more into the processor:
import pygame.time
import pygame.mixer
pygame.mixer.init()
#pygame.time has no init function
sound = pygame.mixer.Sound('click.wav')
clock = pygame.time.Clock()
interval = 0.5 #should be equivalent to 120 bpm
time_passed = 0
clock.tick_busy_loop()
while True: #infinite loop, use keyboard interrupt Ctrl-C
time_passed += clock.tick_busy_loop()
if time_passed >= interval:
sound.play()
time_passed = 0
All of which yielded the same issues. Although, I have to admit, when I tried these minimal examples, the pygame.time
solution was very stable. Problem here is that in my actual script I do some calculation (like incrementing counters and sending MIDI messages) within the while
loop which appear to influence the time the loop takes and the click start to stutter and stumble.
I looked at this answer, but I didn't quite get the idea of the midi files. May that would help here? Some explanation would be most helpful.
Moreover, I tried to put
import os
os.nice(-19)
at the beginning, to gain a higher process priority, but no improvement was noticeable.
I wonder how I can achive a stable metronome like common DAWs like Cubase, Ableton and Logic do. I read about the approach to generate or prerecord audio samples of the tempo in question and chain those samples to achieve a higher prescision, but I would really like to avoid this approach as it seems very laborious.
Is there some clever way I may use the stable pygame.time
variant and do the processing elsewhere?
If it is of any use: here is the actual while
loop I'm running:
bpm = 80.0
beats_in_one_second = bpm/60.0
midi_send_interval = beats_in_one_second/24.0 #MIDI convention
playing = True
player.write_short(250) #start
frame_count = 0
quarter_count = 0
time_0 = time.time()
while playing:
if time.time() - time_0 >= midi_send_interval:
player.write_short(248) #clock tick
time_0 = time.time()
frame_count += 1
if frame_count == 24:
sound.play()
quarter_count += 1
frame_count = 0
if quarter_count == 16:
playing = False
player.write_short(252) #stop
The problem is that the MIDI standard demands for 24 messages per quarter note, which increases the demand for precision significantly.