[alsa-devel] pyalsa: synchronizing queue with MIDI clock events

Christopher Arndt chris.arndt at web.de
Fri Jul 17 14:54:00 CEST 2009


Hi everybody,

this is my first post on this list, so please excuse me, should it not
be the right place to post this.

I started exploring programming the ALSA sequencer via the pyalsa Python
interface, i.e the alsaseq module. I wrote a small utility that reads
MIDI events from one port, filters/processes them and writes it to
another. This allows for routing by channel, note range, controller
number etc. and to filter out, replace or add MIDI events.

Now to synchronize the queue tempo to incoming MIDI clock events, to be
able implement synchronized delays or arpeggiators,  I use the method in
the code shown below. This works ok, but I notice that the measured BPM
oscillates +-1 BPM around the value displayed by the clock source (my
synth's sequencer) if I measure/average only a few (~10) ticks or do not
 round the result to an integer value.

Is this the right approach? What is a sensible number of ticks to take
into account for measurement? Should I use another reference timer than
Python's time.time() function? Is pyalsa generally suitable for this
kind of application even on older/weaker hardware (e.g. a NSLU2)?

Any comments or suggestions for improvement would be very much appreciated!


Chris


class MidiProcessor(object):

    def run(self):
        # This is simplified to only show the general logic
        while True:
            events = self.sequencer.receive_events(
                timeout=RECEIVE_TIMEOUT, maxevents=1)
            for event in events:
                if event.type == SEQ_EVENT_CLOCK:
                    self.sync_queue(event)
                else:
                    # do other MIDI processing / routing

    def sync_queue(self, event):
        """Sync queue tempo to incoming MIDI clock events."""
        # list to collect the timestamps of the last few ticks
        lt = self._last_ticks
        lt.append(time.time())
        ltlen = len(lt)
        if ltlen > 1:
            # calculate & set bpm: calculate difference between
            # the times the last few ticks were received and average
            # all results
            avg_delta = sum(
                 [y-x for x,y in zip(lt, lt[1:])]) / (ltlen-1)
            # tick length is a 24th of a quarter note
            bpm = round(60 / avg_delta / 24)
            if bpm != self.bpm:
                self.bpm = bpm
                self.sequencer.queue_tempo(self.queue,
                    tempo=int(6e7 / self.bpm), ppq=self.ppq)
        # only remember last 24 received ticks
        # (length of a quarter note)
        if ltlen > 24:
            lt.pop(0)


More information about the Alsa-devel mailing list