Accurate timing in Python/Pygame on Raspi

My bell-ringing machine needs very accurate timing in order to ring at a precise beat. In a previous post, I mentioned problems with irregularity  in the bell sound which I fixed by keeping the sound data in RAM.  But the sounds need to be triggered accurately, too.

Originally, I just relied on using the Python time.sleep(s) function, where s is a real variable containing the (floating-point) number of seconds and fractional seconds to wait.  Whilst this worked well, it has a disadvantage: whilst the program is sleeping, it can’t do anything else (although the sounds keep playing, because they are running in a background thread). However, a process that is sleeping could be interrupted by by an ‘event’.

Since I have pygame loaded, there is an alternative:
pygame.time.wait(s) – this sleeps the process for s milliseconds.  Here s is an integer in milliseconds, but this doesn’t make it more accurate than the time.sleep() function.

Another alternative is pygame.time.delay()
This uses the processor rather than sleeping, and is said to be more accurate than the wait() function, since the sleep and wait functions can get interrupted by events (which I’m not dealing with here).

Using signals

But there is a further option which I think is superior to any of these for my purposes, and that is the signal function.  I’ve previously written about how it can be used to pause execution of the program until an event occurs (it has many other uses as well).

I’m interested in the timer method, which is called as follows: signal.setitimer(signal.ITIMER_REAL,mydelay)
This sets up the interval timer such that it will issue the SIGALRM signal  after an interval of mydelay which is a floating point number of seconds and fractions of a second.  (There is in fact a similar signal.alarm() function but this only accepts an integer number of seconds, which is no good for me.) When the specified time interval has passed, the function sends the signal SIGALRM to (i.e. it runs) a handler function which you write. This can be very simple, for example:

def myhandler(signum, frame):
  global gap
  gap=False
  return

Set up the signal handler

You have to set up Python with the signal you want it to respond to and the name of the function to handle the signal as follows:

signal.signal(signal.SIGALRM, myhandler)
This needs to be put in the main routine of your program.

You can now use this in either or both of two ways.

The simplest is to set the timer and then make the program wait in a while loop, for example

signal.setitimer(signal.ITIMER_REAL,mydelay)
while gap : continue

This is fine for very short delays.  If the delay may be long, you can pause the program until the alarm is received:

signal.pause()
This sleeps the process until a signal is received and the handler is called.  If you are using something like the very simple handler I have shown above, the program will continue execution with the next statement after the pause command, but the handler has set the flag called gap, which your program could use.

This is better than using a while loop because the software will be sleeping during the wait time, which reduces processor load and hence heat generation.

Note that setitimer can also issue alarms at regular intervals, which could be very useful for producing the regular beat of the bells, although I have chosen not to use it like that.

Pygame events

Pygame has functions to create ‘events’ which are very similar in concept to the signals of Python, and timing functions which are very similar to those in Python.  However, I don’t want or need to get involved in handling Pygame’s events queue, so I haven’t gone that route.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.