0

I am stumped as to why the Display class isn't running in the following code. I call it using Display(root) in main(). The main class runs just fine. I don't get any errors in terminal.

#!/usr/bin/python

import alsaaudio as aa
import audioop
import Tkinter as tk
import tkFont


class Display():

    def __init__(self, parent):
        self.parent = parent

        self._geom = '200x200+0+0'
        parent.geometry("{0}x{1}+0+0".format(parent.winfo_screenwidth(), parent.winfo_screenheight()))
        parent.overrideredirect(1)

        parent.title('Listen')
        parent.configure(background='#000000')
        parent.displayFont = tkFont.Font(family="Unit-Bold", size=150)

    def printMessage(self, parent, messageString):
        self.message = tk.Message(self.parent, text=messageString, bg="#000000", font=parent.displayFont, fg="#777777", justify="c")
        self.message.place(relx=.5, rely=.5, anchor="c")

def main():

    root = tk.Tk()
    window = Display(root)

    # Set up audio
    data_in = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NONBLOCK, 'hw:1')
    data_in.setchannels(2)
    data_in.setrate(44100)
    data_in.setformat(aa.PCM_FORMAT_S16_LE)

    data_in.setperiodsize(256)

    while True:
       # Read data from device
       l,data = data_in.read()
       if l:
          # catch frame error
          try:
             max_vol=audioop.rms(data,2)
             scaled_vol = max_vol//4680      
             print scaled_vol

             window.printMessage(root, scaled_vol)

          except audioop.error, e:
             if e.message !="not a whole number of frames":
                raise e

    root.mainloop()

if __name__ == '__main__':
    main()

Also, both classes run perfectly separately. I'm just having problems using them together.

interwebjill
  • 920
  • 13
  • 38
  • 1
    instead of while True use Tkinter's after. You can use a function that calls itself. http://effbot.org/tkinterbook/widget.htm and http://stackoverflow.com/questions/25753632/tkinter-how-to-use-after-method –  Mar 22 '16 at 15:05

1 Answers1

0

I think it's because there is an infinite loop (while True) that does not allow the execution to reach to Tk event loop root.mainloop().

Using threads might help to solve this issue. You can run the data input loop in a separate thread, and run Tk event loop in the main thread as it is.

Update: I ran the code (Python 2.7.6 on Linux 3.13.0-24-generic #47-Ubuntu SMP, pyalsaaudio==0.8.2) and it was showing the data. However it could be possible that using threads on different platforms behave differently. I'm having doubts about threads in this case now, because in the comments you mentioned that even the print statement is not being called. Another approach is not to use threads and the while True loop, but to use the mainloop from Tkinter itself.

Also please not that Display.printMessage is creating a new message widget on each call. This will end up with many message widgets showing (and occupying memory). To avoid this, I'd suggest using Tkinter variables, as used on the second code example using Tkinter mainloop.

Solution 1 Using threads (minimum change to code in the question):

#!/usr/bin/python

import alsaaudio as aa
import audioop
import Tkinter as tk
import tkFont
from threading import Thread, Event

class Display():
    def __init__(self, parent):
        self.parent = parent

        self._geom = '200x200+0+0'
        parent.geometry("{0}x{1}+0+0".format(parent.winfo_screenwidth(), parent.winfo_screenheight()))
        parent.overrideredirect(1)

        parent.title('Listen')
        parent.configure(background='#000000')
        parent.displayFont = tkFont.Font(family="Unit-Bold", size=150)

    def printMessage(self, messageString):
        self.message = tk.Message(self.parent, text=messageString, bg="#000000", font=self.parent.displayFont, fg="#777777", justify="c")
        self.message.place(relx=.5, rely=.5, anchor="c")


def setup_audio(window, stop_event):
    data_in = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NONBLOCK, 'hw:1')
    data_in.setchannels(2)
    data_in.setrate(44100)
    data_in.setformat(aa.PCM_FORMAT_S16_LE)

    data_in.setperiodsize(256)

    while not stop_event.is_set():
       # Read data from device
       l,data = data_in.read()
       if l:
          # catch frame error
          try:
             max_vol=audioop.rms(data,2)
             scaled_vol = max_vol//4680
             print scaled_vol

             window.printMessage(scaled_vol)

          except audioop.error, e:
             if e.message !="not a whole number of frames":
                raise e

def main():
    root = tk.Tk()
    window = Display(root)

    stop_event = Event()
    audio_thread = Thread(target=setup_audio, args=[window, stop_event])
    audio_thread.start()
    try:
        root.mainloop()
    finally:
        stop_event.set()
        audio_thread.join()


if __name__ == '__main__':
    main()

Changes:

  • Moved reading sound loop into a separate function that accepts a threading Event to signal stop the loop
  • Removed the parent arg from Display.printMesssage() definition and use self.parent instead. So in the setup_autdio function when calling the printMessage(), there is no need to pass in root as an argument anymore.

Solution 2 Using Tkinter mainloop, and run all in the main thread (note that I also changed the Display.printMessage() class, mainly for performance improvements):

#!/usr/bin/python

import alsaaudio as aa
import audioop
import Tkinter as tk
import tkFont


class Display():
    def __init__(self, parent):
        self.parent = parent

        self._geom = '200x200+0+0'
        parent.geometry("{0}x{1}+0+0".format(parent.winfo_screenwidth(), parent.winfo_screenheight()))
        parent.overrideredirect(1)

        parent.title('Listen')
        parent.configure(background='#000000')
        parent.displayFont = tkFont.Font(family="Unit-Bold", size=150)

        # using a Tkinter variable to display different messages, on the same widget
        self.messageVar = tk.StringVar()
        self.message = tk.Message(self.parent, textvar=self.messageVar, bg="#000000", font=self.parent.displayFont, fg="#777777", justify="c")
        self.message.place(relx=.5, rely=.5, anchor="c")

    def printMessage(self, messageString):
        self.messageVar.set(messageString)


def update_ui_from_audio(data_in, window):
    l, data = data_in.read()
    if l:
        # catch frame error
        try:
            max_vol=audioop.rms(data,2)
            scaled_vol = max_vol//4680
            print scaled_vol
            window.printMessage(scaled_vol)
        except audioop.error, e:
            if e.message !="not a whole number of frames":
                raise e

def main():
    root = tk.Tk()
    window = Display(root)

    # Set up audio
    data_in = aa.PCM(aa.PCM_CAPTURE, aa.PCM_NONBLOCK, 'hw:1')
    data_in.setchannels(2)
    data_in.setrate(44100)
    data_in.setformat(aa.PCM_FORMAT_S16_LE)
    data_in.setperiodsize(256)

    def audio_read_loop():
        update_ui_from_audio(data_in, window)
        root.after(1, audio_read_loop)

    root.after(1, audio_read_loop)
    root.mainloop()

if __name__ == '__main__':
    main()

Changes:

  • Move the code that reads data from PCM device, and updates the UI into a separate function named update_ui_from_audio
  • Display.printMessage uses a StringVar to update the message, so only 1 message widget is used.
  • Replace the while True loop, with another function that calls the update_ui_from_audio function, and asks Tkinter to call it again in a milisecond (hence, acting like a loop).
farzad
  • 8,775
  • 6
  • 32
  • 41
  • When I run this, tKinter gives me an interface on the monitor, but it displays the word "Listen" (parent.title) instead of the scaled_vol. – interwebjill Mar 23 '16 at 04:39
  • In the provided code, there is a `print` statement (for debugging I guess). Is it printing correct values to stdout? By the way from the code sample I'm guessing you're using Python 2 on Linux. Can you provide more details on the platform and Python/Tk version? – farzad Mar 24 '16 at 13:03
  • Hi Farzad, thanks for your help. I am running python 2.7 on Raspberry Pi. Sorry for not mentioning that. No, the print statement is not printing anything to the terminal using this code. Basically, I can get both parts of the code (interface & sound signal code) to work independently, but I can't put them together. The sound signal code works just fine when I'm not trying to integrate the GUI. – interwebjill Mar 24 '16 at 13:11
  • I've updated the answer with another solution. (as suggested by Curly Joe as a comment), it is a common technique to use the UI mainloop instead of using a separate loop. Also I think for this type of issues, providing more details about the software platform could be more helpful (for example OS name and version (Raspbian, Raspbmc, etc.), Kernel version (affects ALSA), `pyalsaaudio` version, etc.). – farzad Mar 25 '16 at 04:40
  • 1
    Aha! That does work in the sense that I am getting the values printed to the display. Thank you! However the text flickers, and while control-c stops the program, I cannot exit. – interwebjill Mar 26 '16 at 23:53
  • I am on Raspbian Jessie v.8 and pyalsaaudio 0.8.2. – interwebjill Mar 26 '16 at 23:55
  • I think my original problem was solved, so I will mark the answer as correct. Maybe Tkinter isn't up to the task. I switched my GUI package to Kivy, and with that modification, the code works. – interwebjill Mar 28 '16 at 01:32