'change parameters during sounddevice processing in Pyqt dial
I want to turn the dial on pyqt's GUI to change parameters while sounddevice's outputstream is processing, but the GUI freezes. I've tried everything I could find, and I've pieced together a bunch of code, but I'd like to know what the solution is. I want to change the equalizer of the music being played in near real time by changing the parameters of the pedalboard that grants the equalizer.
import sys
import time
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtWidgets import *
from PyQt5.QtMultimedia import QSound
from pedalboard import *
import sounddevice as sd
import librosa
from threading import *
import threading
import soundfile as sf
def long_running_function(update_ui,board):
event = threading.Event()
try:
data, fs = sf.read('continue.wav', always_2d=True)
current_frame = 0
def callback(outdata, frames, time, status):
nonlocal current_frame
if status:
print(status)
chunksize = 2024
#print(chunksize, current_frame,len(data),frames)
outdata[:chunksize] = board(data[current_frame:current_frame + chunksize])
if chunksize < 2024:
outdata[chunksize:] = 0
raise sd.CallbackStop()
current_frame += chunksize
stream = sd.OutputStream(
samplerate=fs, blocksize=2024 , channels=data.shape[1],
callback=callback, finished_callback=event.set)
with stream:
event.wait()
except KeyboardInterrupt:
exit('\nInterrupted by user')
class Worker(QObject):
finished = pyqtSignal()
progress = pyqtSignal(int)
def __init__(self, main_window):
self.main_window = main_window
super(Worker, self).__init__(main_window)
def run(self):
long_running_function(self.update_progress,self.main_window.make_board())
self.finished.emit()
def update_progress(self, percent):
self.progress.emit(percent)
class MainWindow(QMainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.progress = QProgressBar()
self.button = QPushButton("Start")
self.dial = QDial()
self.init_ui()
self.qsound = None
self.qsound2 = None
self.effected_audio = None
self.audio = None
self.sr = None
self.dial.valueChanged.connect(self.make_board)
self.button.clicked.connect(self.execute)
self.show()
def init_ui(self):
self.setGeometry(100, 100, 250, 250)
layout = QVBoxLayout()
layout.addWidget(self.progress)
layout.addWidget(self.button)
layout.addWidget(self.dial)
self.setWindowTitle('Audio Player')
button_play = QPushButton("環境音を再生")
button_play2 = QPushButton("音楽と環境音を再生")
button_stop = QPushButton("Stop")
button_dialog = QPushButton("音楽を選択")
button_dialog2 = QPushButton("環境音を選択")
dial = QDial()
live = QPushButton("LIVE")
self.label = QLabel(self)
self.label2 = QLabel(self)
layout.addWidget(button_dialog)
layout.addWidget(button_dialog2)
layout.addWidget(button_play)
layout.addWidget(button_stop)
layout.addWidget(button_play2)
layout.addWidget(live)
layout.addWidget(self.dial)
layout.addWidget(self.label)
layout.addWidget(self.label2)
button_dialog.clicked.connect(self.button_openfile)
button_dialog2.clicked.connect(self.button_openfile2)
button_play.clicked.connect(self.button_play)
button_play2.clicked.connect(self.button_play2)
button_stop.clicked.connect(self.button_stop)
live.clicked.connect(self.start)
self.dial.valueChanged.connect(self.sliderMoved)
self.dial.valueChanged.connect(self.make_board)
self.dial.setMinimum(0)
self.dial.setMaximum(20)
self.dial.setValue(5)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
def sliderMoved(self):
number = self.dial.value()
print(number)
return number
def make_board(self):
board = Pedalboard([
Compressor(ratio=10, threshold_db=-20),
Gain(gain_db=self.dial.value()),
Phaser(),
Reverb()
],sample_rate=44100)
return board
def button_play(self):
print("")
def button_play2(self):
if self.qsound is not None:
board = self.make_board(self.effects)
self.effected_audio = board(self.audio,self.sr)
sd.play(self.effected_audio)
self.qsound2.play()
def button_stop(self):
if self.qsound is not None:
sd.stop()
self.qsound2.stop()
def button_openfile(self):
filepath, _ = QFileDialog.getOpenFileName(self, 'Open file','c:\\',"Audio files (*.wav)")
filepath = os.path.abspath(filepath)
self.qsound = QSound(filepath)
self.filename = os.path.basename(filepath)
self.audio, self.sr = librosa.load(self.filename, sr=44100)
self.label.setText(self.filename)
self.label.adjustSize()
def button_openfile2(self):
filepath2, _ = QFileDialog.getOpenFileName(self, 'Open file','c:\\',"Audio files (*.wav)")
filepath2 = os.path.abspath(filepath2)
self.qsound2 = QSound(filepath2)
self.filename2 = os.path.basename(filepath2)
self.label2.setText(self.filename2)
self.label2.adjustSize()
def start(self):
self.thread.start()
def execute(self):
self.update_progress(0)
self.thread = QThread()
self.worker = Worker(self)
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.worker.progress.connect(self.update_progress)
self.thread.start()
self.button.setEnabled(False)
def update_progress(self, progress):
self.progress.setValue(progress)
self.button.setEnabled(progress == 100)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
Solution 1:[1]
Qt Docs for moveToThread() say:
Changes the thread affinity for this
object and its children. The object
cannot be moved if it has a parent.
I cannot see where Worker instance gets started. Maybe you wanted to derive Worker from QThread, and call self.worker.start()?
OutputStream and the Qt GUI both have their own internal main loops which are responsible for handling their respective events. When you execute both on the same thread, one or the other might freeze.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | cidermole |
