Python Qt5 Running two methods simultaneously

33 Views Asked by At

I have a Qt timer that I want to count down while I record (using the sound device package), I'll attach what I have below (I know it is very far off from anything correct). I've been reading up on multiprocessing and threads, and I think the timeout allows for other functions to be ran, but it seems the record function holds while its writing the file, so I am a bit stumped. Any advice helps, thanks!

class CountDown(QWidget):


    def __init__(self, name, index):
        super().__init__()
        self.initUI(name, index)
    
    def initUI(self, name, index):
        self.setWindowTitle("Timer for "+name)

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.time_label = QLabel(self)
        layout.addWidget(self.time_label)
        self.setFixedWidth(500)
        self.setFixedHeight(500)

        # Create a QTimer that updates the time label every second
        timer = QTimer(self)
        self.current_time = 11
        timer.timeout.connect(lambda: self.update_time())
        timer.start(1000)  # Update every 1000 milliseconds (1 second)  

        print("START RECORDING" + name)
        self.record(index, name)
        self.show()

    #need to update time and record simultaneously
    def update_time(self):
        # Get the current time and set it on the label
        if(self.current_time==0): 
            self.close()
        self.current_time-=1
        print(self.current_time)
        self.time_label.setText(f"Current Time: {self.current_time}")

    #Takes in name selected from GUI and finds its index to use for audio device-> records for 1 second
    def record(self, index, name):
        #1 Microphone (USB Lavalier Microp, MME (2 in, 0 out)

        # Sample rate:
        fs = 44100
        # Duration of recording:
        seconds = 5
        myrecording = sd.rec(int(seconds * fs), samplerate=fs, channels=1, device=index)
        
        # Wait until recording is finished:
        sd.wait()

        print("STOPPED RECORDING" + name)


        # Save as WAV file:
        write(name+".wav", fs, myrecording)
        return name+".wav"
1

There are 1 best solutions below

0
Aaron On

I would reach for threading here. Not all of Qt is thread-safe, but if your recording thread does not directly interact with the GUI, There's almost no work necessary:

...
print("START RECORDING" + name)
self.recording_thread = threading.Thread(target = self.record, args = (index, name))
self.recording_thread.start()
self.show()
...

It is possible to emit signals from a thread, so you can create a slot (a callback) which can be triggered by the thread. If you start trying to do stuff like label.setText() from a thread that's where you get into trouble.

from PyQt6.QtCore import pyqtSignal, pyqtSlot #custom signals and slots
from PyQt6.QtWidgets import ...
from PyQt6 ...

import threading

class CountDown(QWidget):

    recording_done = pyqtSignal() #define a signal our thread will emit when done
    
    def __init__(self, name, index):
        super().__init__()
        self.initUI(name, index)
    
    def initUI(self, name, index):
        self.setWindowTitle("Timer for "+name)

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.time_label = QLabel(self)
        layout.addWidget(self.time_label)
        self.setFixedWidth(500)
        self.setFixedHeight(500)
        self.setLayout(layout) #explicitly set the layout of the main widget rather than rely on parent-child relationship of label

        # Create a QTimer that updates the time label every second
        self.timer = QTimer(self)
        self.current_time = 11
        self.timer.timeout.connect(self.update_time) #you shouldn't need a lambda here, just pass the method
        self.timer.start(1000)  # Update every 1000 milliseconds (1 second)  

        print("START RECORDING" + name)
        self.recording_thread = threading.Thread(target=self.record, args=(index, name))
        self.recording_thread.start()
        
        self.recording_done.connect(self.on_recording_done)
        self.show()

    #need to update time and record simultaneously
    @pyqtSlot()
    def update_time(self):
        # Get the current time and set it on the label
        if(self.current_time==0): 
            self.timer.stop()
        self.current_time-=1
        print(self.current_time)
        self.time_label.setText(f"Current Time: {self.current_time}")
        
    @pyqtSlot()
    def on_recording_done(self): #main process will execute this in the main thread when it gets the signal from the recording thread.
        if self.timer.isActive():
            self.timer.stop()
        self.time_label.setText("recording done")

    #Takes in name selected from GUI and finds its index to use for audio device-> records for 1 second
    def record(self, index, name):
        #1 Microphone (USB Lavalier Microp, MME (2 in, 0 out)
        fs = 44100
        seconds = 5
        myrecording = sd.rec(int(seconds * fs), samplerate=fs, channels=1, device=index)
        sd.wait()
        print("STOPPED RECORDING" + name)
        write(name+".wav", fs, myrecording)
        self.recording_done.emit() #signal the Qt event loop in the main process
        #return name+".wav" #you can't get the return value from a thread. If you need to return some value, send it via signals and slots, or mutate a shared variable.

^note: code is untested.. read for changes and understanding.