pyalsa: synchronizing queue with MIDI clock events
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)
No pyalsa or MIDI experts here?
Chris
Quoted 9 lines · Christopher Arndt
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?
Large enough that you get a stable average, but small enough that you can detect actual changes.
Quoted 1 line
Should I use another reference timer than Python's
time.time()function?
That timer probably has a high enough resolution, but you measure the time when your code is executed, which may be later then when the event was actually received.
You can tell ALSA to timestamp all events that are received; just call the snd_seq_port_info_set_timestamping() function and set a queue that is running, then you'll get the queue's current time in each event. This isn't implemented in pyalsa, but you can set this for a specific connection with the queue, time_update, and time_real parameters of the connect_ports function.
Quoted 2 lines
Is pyalsa generally suitable for this kind of application even on older/weaker hardware (e.g. a NSLU2)?
The biggest problem is probably scheduling delays, so I guess using Python instead of C should not add any noticeable delay.
Best regards,
Clemens
Quoted 6 lines · Clemens Ladisch
Quoted 2 lines · Christopher Arndt
What is a sensible number of ticks to take
into account for measurement?Large enough that you get a stable average, but small enough that you can detect actual changes.
I figured out that much on my own :) I was just wondering if anybody had already experimented with this...
Quoted 6 lines
You can tell ALSA to timestamp all events that are received; just call the
snd_seq_port_info_set_timestamping()function and set a queue that is running, then you'll get the queue's current time in each event. This isn't implemented in pyalsa, but you can set this for a specific connection with the queue,time_update, andtime_realparameters of theconnect_portsfunction.
I'll look into this.
Many thanks for your comments!
Chris
pyalsa: SeqEvent.time strangeness (Was: synchronizing queue with MIDI clock events)
Quoted 6 lines · Christopher Arndt
Quoted 6 lines
You can tell ALSA to timestamp all events that are received; just call the
snd_seq_port_info_set_timestamping()function and set a queue that is running, then you'll get the queue's current time in each event. This isn't implemented in pyalsa, but you can set this for a specific connection with the queue,time_update, andtime_realparameters of theconnect_portsfunction.
I've tested this now and encountered a very strange bevahior of the event time set by the queue:
a) SeqEvent.time seems to be in milliseconds instead of seconds as would be expected by the ALSA documentation and looking at the source of pyalsa.alsaseq.
5) Consecutive timestamp go up to ~1000,00 and then seem to wrap around and start from zereo again.
I have created a small test program, which demonstrates this:
http://paste.chrisarndt.de/paste/fa8ca7201bc7419f9e001315ca02b1ef
It outputs the time of each MIDI clock signal it receives and whether it is in tick or real time and whether it's absolute (= from the start of the queue) or relative (to the preceding event). Here's some output from the script:
MIDI CLOCK 0.0 ABS REAL
MIDI CLOCK 23.697488 ABS REAL
MIDI CLOCK 48.701538 ABS REAL
MIDI CLOCK 73.70515 ABS REAL
MIDI CLOCK 98.705762 ABS REAL
MIDI CLOCK 123.709812 ABS REAL
MIDI CLOCK 148.702424 ABS REAL
MIDI CLOCK 173.707474 ABS REAL
MIDI CLOCK 198.711086 ABS REAL
[...]
MIDI CLOCK 898.73904 ABS REAL
MIDI CLOCK 923.74909 ABS REAL
MIDI CLOCK 948.744702 ABS REAL
MIDI CLOCK 973.742314 ABS REAL
MIDI CLOCK 998.749364 ABS REAL
MIDI CLOCK 24.738976 ABS REAL
MIDI CLOCK 49.741026 ABS REAL
MIDI CLOCK 74.743638 ABS REAL
MIDI CLOCK 99.74725 ABS REAL
As you can see, once the time would exceed 1000.0 it seems to start from zero again. This happens only when the queue sets the event times to REAL. When ticks are used, the increase infinitely, as expected.
I fail to understand why this is and what I am doing wrong. I would expect SeqEvent.time to be in seconds and always ascending until I stop the queue. Please, somebody enlighten me!
Chris
Re: pyalsa: SeqEvent.time strangeness (Was: synchronizing queue with MIDI clock events)
Quoted 3 lines · Christopher Arndt
I've tested this now and encountered a very strange bevahior of the event time set by the queue:
[...]
Should I report this as a bug then? Is there a special category or convention for filing pyalsa issues at https://bugtrack.alsa-project.org/ ?
Chris
Re: pyalsa: SeqEvent.time strangeness (Was: synchronizing queue with MIDI clock events)
In reply to Christopher ArndtQuoted 9 lines · Christopher Arndt
I've tested this now and encountered a very strange bevahior of the event time set by the queue:
a) SeqEvent.time seems to be in milliseconds instead of seconds as would be expected by the ALSA documentation and looking at the source of pyalsa.alsaseq.
5) Consecutive timestamp go up to ~1000,00 and then seem to wrap around and start from zereo again.
Apparently, you are the first one who tried to use this. Fixed now:
http://git.alsa-project.org/?p=alsa-python.git;a=commitdiff;h=9a7dea33dddeb2d9d7693257323d43ccffbffed2
HTH
Clemens
Re: pyalsa: SeqEvent.time strangeness
Quoted 6 lines · Clemens Ladisch
Quoted 2 lines · Christopher Arndt
I've tested this now and encountered a very strange bevahior of the event time set by the queue:
Apparently, you are the first one who tried to use this. Fixed now:
http://git.alsa-project.org/?p=alsa-python.git;a=commitdiff;h=9a7dea33dddeb2d9d7693257323d43ccffbffed2
Excelllent! The test script now produces correct results. Thanks for the fix!
Might I suggest another patch to pyalsa? This would allow to use developers to use "python setup.py develop" to install their git version as a Python egg-link and makes it much easier to use pyalasa with virtual Python environments.
Chris
setup.py +4 -1
@@ -3,7 +3,10 @@ import os import sys -from distutils.core import setup, Extension +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension VERSION='1.0.20'
_______________________________________________
Alsa-devel mailing list
[email protected]
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
Re: pyalsa: SeqEvent.time strangeness
Quoted 1 line · Christopher Arndt
Might I suggest another patch to pyalsa?
Please provide a Signed-off-by line.
Best regards,
Clemens
Re: pyalsa: SeqEvent.time strangeness
Quoted 4 lines · Clemens Ladisch
Quoted 1 line · Christopher Arndt
Might I suggest another patch to pyalsa?
Please provide a Signed-off-by line.
Please forgive my ignorance, but what is that? I'm unfamiliar with the alsa development process. Is that a git-specific thing? Is there some documentation on contributing to alsa?
Chris
Re: pyalsa: SeqEvent.time strangeness
Quoted 5 lines · Christopher Arndt
Quoted 1 line · Clemens Ladisch
Please provide a Signed-off-by line.
Please forgive my ignorance, but what is that? I'm unfamiliar with the alsa development process.
It's originally a Linux kernel development process thing, but we use it for all of ALSA. See section 12 of
<http://git.alsa-project.org/?p=alsa-kernel.git;a=blob;f=Documentation/SubmittingPatches;hb=HEAD>.
HTH
Clemens
PATCH 1/1 alsa-python: Add support for setuptools
This patch adds support for setuptools to the setup.py file of python-alsa.
setup.py +4 -1
@@ -3,7 +3,10 @@ import os import sys -from distutils.core import setup, Extension +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension VERSION='1.0.20'
Re: [PATCH] alsa-python: Add support for setuptools
Quoted 1 line · Christopher Arndt
This patch adds support for setuptools to the
setup.pyfile of python-alsa.
Applied now. Thanks!
Clemens