Tkinter and threading

Our drum machine plays patterns in the way that we want it to. However, there is a small problem. The play method blocks the main loop of our Tkinter program. It does not relinquish control back to the main loop until it is done playing all of the sound samples.

This means that if you now want to click on the Stop button or change some other widget, you will have to wait for the play loop to complete.

You might have noticed that when you hit the Play button, it remains pressed for the time the sound loops are being played. During that time you cannot access any other widget in the Toplevel window.

This clearly is a glitch. We need some method to confer back the control to Tkinter main loop while the play is still in progress.

Prepare for Lift Off

One of the simplest ways that we can achieve this is to use the root.update()method within our play loop. This updates the root.mainloop() method after each sound sample is played (see the commented code in 3.07.py).

However, this is an inelegant method because the control is passed to the main loop with some staggering experienced in the GUI. Thus, you may experience a slight delay in responses of other widgets in the Toplevel.

Further, if some other event causes the method to be called, it could result in a nested event loop.

A better solution would be to run the play method from a separate thread. To do that let us employ the threading module of Python.

Engage Thrusters

  1. Let us first import the threading module into our namespace (refer to the code in 3.07.py):
    import threading
  2. Now, let us create a method that calls the self.play() method to run in a separate thread. This redirects play through the threading model:
    def play_in_thread(self):
      self.thread = threading.Thread(None, self.play, None, (), {})
      self.thread.start()
  3. Finally, change the command callback for the Play button in the play_bar method from the existing self.play() method to self.play_in_thread():
    button=Button(playbar_frame, text ='Play', command= self.play_in_thread)

    Now if you load some drum samples, define the beat patterns, and hit the Play button, the sound will play in a separate thread without preventing the main loop from updating (refer to the code in 3.07.py).

  4. The next step would be that of coding the Stop button. The role of the Stop button is simple; it merely stops the currently playing pattern. To do that, we first add a command callback to the Stop button calling on a method stop_play as follows (see the code in 3.07.py):
    button=Button(playbar_frame, text='Stop', command= self.stop_play)

    Then we define the stop_play method as follows:

    def stop_play(self):
      self.keep_playing = False
  5. Our thread system now runs the play method from a separate thread. However, if the user clicks on the button more than once, this will spawn more threads, which will play the beat. To avoid this, the button should be configured with state='disabled', and enabled again when the sequence finishes.

    To disable the Play button when the program starts running, we add the following line to our play_in_thread method (refer to the code in 3.07.py):

    self.start_button.config(state='disabled')

    Similarly, when the sequence finishes playing or the Stop button is clicked, we want to enable the Play button again. To enable it, we add the following line to our play and stop_play methods:

    self.start_button.config(state='normal')

    Note

    Tkinter and thread safety

    Tkinter is not thread safe. The Tkinter interpreter is valid only in the thread that runs the main loop. Any call to widgets must ideally be done from the thread that created the main loop. Invoking widget-specific commands from other threads is possible (as we do here), but is not reliable.

    When you call a widget from another thread, the events get queued for the interpreter thread, which executes the command and passes the result back to the calling thread. If the main loop is running but not processing events, it sometimes results in unpredictable exceptions.

    The only change we make to our existing play method is to include the entire code in a try-except block. We do this because Tkinter is not thread safe and can cause some unwanted exceptions when dealing with the play thread. The best we can do here is ignore those cases using a try-except block.

    Tip

    mtTkinter – a thread-safe version of Tkinter

    If you find yourself working on an inherently multithreaded project, you might consider looking at mtTkinter a thread-safe version of Tkinter. For more information on mtTkinter, visit http://Tkinter.unPythonic.net/wiki/mtTkinter.

    For more specialized multiprocessing needs you may also want to take a look at multiprocessing module or an event model such as Twisted.

  6. The last step sees us code the Loop Checkbutton. The role of the Loop checkbox is simple. If the Loop checkbox is unchecked, the pattern plays only once. If it is checked, the pattern keeps playing in an endless loop. The pattern stops playing only if the Loop Checkbutton is unchecked or if the Stop button is pressed.

    We add a command callback to the Loop checkbox:

    loopbutton = Checkbutton(playbar_frame, text='Loop', variable=loop, command=lambda: self.LoopPlay(loop.get())) )

    We then define the loop_play method as follows:

    def loop_play(self, xval):
      self.loop = xval

    Equipped with these two variables, we modify our play method to keep playing while self.keep_playing is equal to True see the code in 3.07.py).

    If the value of self.loop is equal to False, we set the value of self.keep_playing equal to False, which breaks out of the play loop.

Objective Complete – Mini Debriefing

This completes the project iteration. In this round, we refined our play method to play the audio files from a separate thread.

We used Python's built-in threaded module to play the loops in separate thread. We looked at some of the threading-related limitations of Tkinter and some ways in which we can overcome those limitations.

We also coded for the Stop button and Loop checkbox functionality.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.42.243