Hi all. I am currently learning python (and pyGTK) and I'm having a hard time understanding some threading stuff.
I'm writing here hoping I can get - pointers to some documentation that could help me - a lead concerning the specific problem described in the message - a hint about which library to use for audio playback As an exercise, I am trying to write an application that plays sounds through the speakers. For that, I use alsaaudio (http://pyalsaaudio.sourceforge.net/). The sound is just a sine wave. I guess using threading is the good way to go. So I create a thread. The run() method calls PCM.write() from alsaudio to send the sound to a playback buffer. Once the thread is started, the button is disabled, and it is enabled again when the thread is dead. This is done via GObject.idle_add(), which triggers a regular call to the thread is_alive() function. There's a design flow, here, as the run() method that calls pcm.write() exits when the sound data is buffered (this is quick, say 0.1s), not when it is actualy finished playing (this is long : 1s). Therefore, the thread dies long before the sound is finished playing and the button is going to be enabled too soon. But this is not what happens, and before finding another way to proceed (this is likely to be a timer), I would like to understand what actually happens (remember, this is just an exercise). What happens is that the idle_add polling occurs as long as the run() method is running, but then, it gets stuck until the sound has actually finished playing. The button is enabled at the end of the playback (which is what I wanted in the first place, except I'd like to understand why...). Besides, a second click (on the then disabled button) during the playback is not ignored, but generates another sound after the first. It seems that while the thread is dead (or should be, as the last print has been reached and executed), as long as the sound is being played, the Gtk.main() is stuck and does not process either gtk events or idle tasks. I don't understand that. I wonder what happens and in which thread lies my program while the sound is playing, between the thread run() end and the Gtk.main(). (There's a note in alsaaudio documentation about pcm.write normal and non-block modes but I don't think it is relevant here because, as far as I understand, the data sent does not fill the buffer so in both modes, pcm.write() should exit without blocking. (I might be wrong about this.)) I tried to remove from the code all that has nothing to do with this issue, and I left the prints that reveal what happens. Here it is : --------------------------------------------------------------------------- #!/usr/bin/env python # -*- coding: UTF8 -*- from gi.repository import Gtk, GObject import alsaaudio from numpy import arange, sin, ceil, pi, float32, hstack import threading import time ######################################################################### # audio functions ######################################################################### class Alsa_play_note(threading.Thread): def run(self): print "Entering thread run() method" length = 1 # 1 second freq = 440 channels = 1 sample_size = 1 # bytes per sample frame_size = channels * sample_size # bytes per frame frame_rate = 44000 # frames per second period_size = int(frame_rate * length) pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL) #pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK) pcm.setchannels(channels) pcm.setrate(frame_rate) pcm.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE) pcm.setperiodsize(period_size) nb_samp = int(ceil(frame_rate / freq)) sample = arange(0, nb_samp, dtype=float32) sample *= pi * 2 / 44100 * freq sample = sin(sample) (nw, ex) = divmod(period_size * frame_size, nb_samp) wave_data = sample for i in range(nw-2): wave_data = hstack((wave_data, sample)) wave_data = hstack((wave_data, sample[:ex])) print "run() -> entering pcm.write" #time.sleep(1) a = pcm.write(wave_data.tostring()) #print a print "run() -> pcm.write done" print "Leaving thread run() method" def __init__(self): threading.Thread.__init__(self) ######################################################################### # class Keyboard ######################################################################### class Keyboard: ##################################################################### # Play note ##################################################################### def play_note(self, widget): print "Entering play_note() callback" self._button.set_sensitive(False) print "Creating thread" self._alsa_thread = Alsa_play_note() print "Calling thread.start()" self._alsa_thread.start() print "Back from thread.start()" GObject.idle_add(self._poll_alsa_play_in_progress) print "GObject.idle_add() done" ##################################################################### # Poll alsa play in progress ##################################################################### def _poll_alsa_play_in_progress (self): print "." if (self._alsa_thread.is_alive()): return True print "Button enabled" self._button.set_sensitive(True) return False ##################################################################### # Init ##################################################################### def __init__(self): # Declare thread handler self._alsa_thread = 0 # Create window ############### self._window = Gtk.Window() self._window.connect("destroy", Gtk.main_quit) self._window.connect("destroy", Gtk.main_quit) # Key button ############ self._button = Gtk.Button() self._button.set_size_request(400, 100) self._button.set_label("A") self._button.show() # Connect handler self._handler = self._button.connect("clicked", self.play_note) # Show everything ################# self._window.add(self._button) self._window.show() ######################################################################### # main ######################################################################### def main(): GObject.threads_init() Gtk.main() return 0 if __name__ == "__main__": Keyboard() main() --------------------------------------------------------------------------- I'd be thankful for any tip. I've been pulling my hair quite some time with these threading issues. Regarding python and pyGTK, I'm generally refering to : Python 2.7 documentation http://docs.python.org/ python GTK+3 tutorial (not really complete) http://readthedocs.org/docs/python-gtk-3-tutorial/en/latest/index.html python GTK2.0 tutorial http://pygtk.org/pygtk2tutorial/index.html I may be missing some documentation / tutorials. Anything else I should read extensively before asking here ? Regarding the playback library, I've been searching before choosing alsaaudio. I chose ALSA because I understand OSS is deprecated, or on its way to be. And alsaaudio seemed to be the project with the most recent updates. Yet it does not pretend to be (or aim at being) complete. Was that a sensible choice ? Would you recommend something else ? The learning curve is sometimes steep... I would be thankful for any lead. -- Jérôme -- http://mail.python.org/mailman/listinfo/python-list