Hi all.
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 wrote a message recently about the same application using the buzzer
with the beep command, called via subprocess.)
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.
Things don't go the way I expected :
- First, I made a mistake : the run() method that calls pcm.write() exits
when the data is buffered (this is quick, say 0.1s), not when it is actualy
played (this is long : 1s). This means that my design is wrong, because the
thread dies long before the sound is finished playing and the button is
going to be enabled too soon.
- But this is not really what happens. 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 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.
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. (After hours of pure random nonsense, I realized a
call to GObject.threads_init() was needed before Gtk.main().)
I'm generally refering to :
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
Python 2.7 documentation
http://docs.python.org/
I don't think any of these told about GObject.threads_init(), I found it on a
sample program in a forum or on a blog. This makes me think I might be
missing some documentation / tutorials.
--
Jérôme
_______________________________________________
pygtk mailing list [email protected]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://faq.pygtk.org/