Help with Optimization of Python software: real-time audio controller

C

craiglewiston

I've been working on a Python-based music training application for a
few months, and have recently run into what I believe are some basic
limitations (or at least constraints) of Python with regards to
timing. I'll try and give a decently thorough description, and
hopefully some of you can process this with your knowledge and help
steer me in the right direction.

As mentioned above, my application deals with music training. I have a
Tkinter GUI, which communicates via pyserial with an external device I
built myself. The code reads in a MIDI file and converts the song to
"output data " for the external device and the GUI, all adjusted to
the user-selected tempo. While the MIDI file is playing, there are 4
main actions the code must take:
1) Send "output data" signals out to the external hardware.
2) Poll the input from the external hardware device for incoming
keypresses. If detected, then the play the sound corresponding to
that key.
3) Start and keep a metronome running for the durations of the song.
4) Update GUI

I'm able to get all the above "accomplished" through the use of
threads and a top-level loop (for updating the Tkinter GUI - if
you've worked with threads and Tkinter, then you know that you can't
update a Tkinter GUI from a thread, but instead have to do it on the
top level loop).

While running, I have the following threads implemented :
1) song processing thread - this thread steps incrementally through a
data matrix (the "output data"), and updates two variables: GUI_vars
and Device_vars
2) metronome thread - this thread starts at the same time as thread
#1, and outputs a "beep" to the audio chain
3) poll input thread - this thread loops continually, looking for
data on the serial Input. When data is found, it starts playing the
sound corresponding to that key until a "note off" signal is received
4) top-level recursive loop (technically not a thread) for updating
GUI and calling Serial Write subfunction. This loop monitors GUI_vars
and Device_vars, updating the GUI and writing out to the device when
either variable has changed

Currently, I have all of the above "working", although I'm running
into some serious timing issues. When I run the program, I get
irregular timing for my metronome (if it sounds at all), as well as
irregular timing in writing to the external device. It's extremely
crucial that threads #1 & #2 are executed as close to real-time as
possible, as they form the "core" of the song, and their elements
can't be delayed without "messing" the song up considerably.


I've read up quite a bit on different optimization methods in Python,
but am not sure which direction to head. I've checked out profile,
Psyco, Pyrex, as well as just porting everything over to C. Since I'm
on a Mac (Power PC), I can't use Psyco. And doing any of the others
seemed like a big enough project that I should really ask someone else
before I embark.

So, for a music-based application where it's crucial to have real-time
execution of serial writeouts and audio, as well as keeping a
continual poll on the input from the same port....can this be done
successfully in Python? Does using Tkinter have anything to do with
my timing issues? Would it benefit me to move over to wxPython
(something I've been considering doing)? As for the metronome, should
I incorporate the metronome thread into the "song processing" thread,
since both are dealing with events whose timing is crucial?

I'm a relative newbie (this is my first Python project) to Python, so
any help is GREATLY appreciated!

Also, just for reference, here's a list of the modules I'm using and
my system info:
Audio: SndObj
Ser com: pyserial
GUI: Tkinter
System: Apple PowerBook G4 (PowerPC), Mac OS 10.4, Python 2.4.4

Thanks,
Craig Lewiston
 
V

Vasily Sulatskov

Perhaps boosting priorities for time critical threads will help.

I don't know about MacOS but for Win32 something like that helps:

thread = win32api.GetCurrentThread()
win32process.SetThreadPriority(thread,
win32process.THREAD_PRIORITY_TIME_CRITICAL)

current_process = win32process.GetCurrentProcess()
win32process.SetPriorityClass(current_process,
win32process.REALTIME_PRIORITY_CLASS)
 
P

Paul Rubin

So, for a music-based application where it's crucial to have real-time
execution of serial writeouts and audio, as well as keeping a
continual poll on the input from the same port....can this be done
successfully in Python? Does using Tkinter have anything to do with
my timing issues? Would it benefit me to move over to wxPython
(something I've been considering doing)? As for the metronome, should
I incorporate the metronome thread into the "song processing" thread,
since both are dealing with events whose timing is crucial?

I think you can't really do that, not just because of Python but also
as a result of using a multitasking OS that's not especially designed
for real time. You have to rely on some buffering in the audio
hardware, so you don't have to be sample-accurate with the timings.
 
R

Ross Ridge

Paul said:
I think you can't really do that, not just because of Python but also
as a result of using a multitasking OS that's not especially designed
for real time. You have to rely on some buffering in the audio
hardware, so you don't have to be sample-accurate with the timings.

For his application you don't need "sample-accurate" timings. Between
MIDI and synthesizer latency a few milliseconds of delay are inherent
in what he's trying to do, regardless of operatings system, and a
latency of 20 milliseconds or more is probably acceptable. Assuming
he's not trying to write his own synthesizer, he might just be able to
write his application in Python under Mac OS X.

Ross Ridge
 
L

Laurent Pointal

(e-mail address removed) a écrit :
As mentioned above, my application deals with music training. I have a
Tkinter GUI, which communicates via pyserial with an external device I
built myself. The code reads in a MIDI file and converts the song to
"output data " for the external device and the GUI, all adjusted to
the user-selected tempo. While the MIDI file is playing, there are 4
main actions the code must take:
1) Send "output data" signals out to the external hardware.
2) Poll the input from the external hardware device for incoming
keypresses. If detected, then the play the sound corresponding to
that key.
3) Start and keep a metronome running for the durations of the song.
4) Update GUI
I'm able to get all the above "accomplished" through the use of
threads and a top-level loop (for updating the Tkinter GUI - if
you've worked with threads and Tkinter, then you know that you can't
update a Tkinter GUI from a thread, but instead have to do it on the
top level loop).
Also, just for reference, here's a list of the modules I'm using and
my system info:
Audio: SndObj
Ser com: pyserial
GUI: Tkinter
System: Apple PowerBook G4 (PowerPC), Mac OS 10.4, Python 2.4.4

Thanks,
Craig Lewiston


You may take a look at PureData, interface is in TCL/Tk, but realtime
sound management layers are in compiled C.
"just" copy it using Python as high level language...
I dont think Python itself just using scripting can achieve needed
performance for *realtime* (like) audio.

http://www.puredata.info/
http://www-crca.ucsd.edu/~msp/
 
K

Klaas

Currently, I have all of the above "working", although I'm running
into some serious timing issues. When I run the program, I get
irregular timing for my metronome (if it sounds at all), as well as
irregular timing in writing to the external device. It's extremely
crucial that threads #1 & #2 are executed as close to real-time as
possible, as they form the "core" of the song, and their elements
can't be delayed without "messing" the song up considerably.

I've read up quite a bit on different optimization methods in Python,
but am not sure which direction to head. I've checked out profile,
Psyco, Pyrex, as well as just porting everything over to C. Since I'm
on a Mac (Power PC), I can't use Psyco. And doing any of the others
seemed like a big enough project that I should really ask someone else
before I embark.

Your problems do not necessarily stem from slow code. Python only
performs thread context switches every 100 øpcodes, and the switch
might not arrive at the right time.

You can lower this value (sys.setcheckinterval). Your computationally-
intensive threads may effectively lower their priority by calling
time.sleep(.00001) every so often.

Ultimately, maintaining explicity control over the scheduling of
events is probably the way to go.

Pyrex is my preferred optimization method, but it can take some
knowledge of what's going on to get the most out of it. numpy is
another option.

-Mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,152
Members
46,697
Latest member
AugustNabo

Latest Threads

Top