Issue
I am trying to create a QThread object whose task will be to handle all the requests from different widgets to play signals on the sound card. I name this object 'sound_engine' and keep it as the only object using the sounddevice module in the application.
The operation of sound_engine is non-blocking when I start it. I ask it to play a 2 second beep within its 'run' method and it does this without blocking the GUI. Yet when I signal its 'beep' slot to play another sound, it is blocking the object which emitted the signal, in this case my main window 'mw'. I assume this is related to the thread exiting once 'run' returns but then what is the right way to go about this?
import sys
import numpy as np
import sounddevice as sd
from PySide6 import QtWidgets as qtw
from PySide6 import QtCore as qtc
from __feature__ import snake_case
from __feature__ import true_property
class SoundEngine(qtc.QThread):
def __init__(self):
super().__init__()
self.FS = 48000
def run(self):
self.start_stream()
# do a start beep for testing
self.beep(2, 200)
def start_stream(self):
self.stream = sd.Stream(samplerate=self.FS, channels=2)
self.dtype = self.stream.dtype
self.channel_count = self.stream.channels[0]
self.stream.start()
@qtc.Slot(float, float)
def beep(self, T, freq):
t = np.arange(T * self.FS) / self.FS
y = np.tile(0.1 * np.sin(t * 2 * np.pi * freq), self.channel_count)
y = y.reshape((len(y) // self.channel_count, self.channel_count)).astype(self.dtype)
self.stream.write(y)
class MainWindow(qtw.QMainWindow):
signal_beep = qtc.Signal(float, float)
def __init__(self, sound_engine):
super().__init__()
self.create_widgets()
self.place_widgets()
self.make_connections()
def create_widgets(self):
self._beep_freq_dial = qtw.QDial(minimum=200,
maximum=2000,
wrapping=False,
)
self._beep_freq_display = qtw.QLCDNumber()
self._beep_pusbutton = qtw.QPushButton("Beep")
def place_widgets(self):
self._center_layout = qtw.QVBoxLayout()
self._center_widget = qtw.QWidget()
self._center_widget.set_layout(self._center_layout)
self.set_central_widget(self._center_widget)
self._center_layout.add_widget(self._beep_freq_dial)
self._center_layout.add_widget(self._beep_freq_display)
self._center_layout.add_widget(self._beep_pusbutton)
def make_connections(self):
self._beep_pusbutton.clicked.connect(
lambda: self.signal_beep.emit(1, self._beep_freq_dial.value)
)
self.signal_beep.connect(sound_engine.beep)
self._beep_freq_display.display(self._beep_freq_dial.value)
self._beep_freq_dial.valueChanged.connect(self._beep_freq_display.display)
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
sound_engine = SoundEngine()
sound_engine.start(qtc.QThread.HighPriority)
mw = MainWindow(sound_engine)
mw.show()
app.exec()
I tried putting in other functions instead of the 'sounddevice' thinking maybe the hardware access causes this. That was not the case, even a simple 'sleep' is blocking.
Solution
You don't gain anything by subclassing QThread in your case. A more default approach would be to hand over a worker object to a thread and then simply run this thread.
The SoundEngine would become a simple QObject:
...
class SoundEngine(qtc.QObject):
def __init__(self):
...
The main function would look like that:
if __name__ == "__main__":
app = qtw.QApplication(sys.argv)
sound_engine = SoundEngine()
thread = qtc.QThread()
sound_engine.move_to_thread(thread)
thread.started.connect(sound_engine.run)
thread.start()
mw = MainWindow(sound_engine)
mw.show()
app.exec()
You are doing youself and others no favor by importing modules with aliases and using snake_case for a framework that is built in C++. The chance that anybody finds your issue is lowered dramatically.
Answered By - Lungen Brötchen
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.