bende
bende

Reputation: 321

PyQt5 app - using timer to run a function with a loop makes it "freeze up"

I am trying to create a GUI for a terminal app I created that listens to your microphone to determine which note you are playing on Guitar. I want to use PyQt5 and it is the first time I am using it (or any GUI framework in particular).

I can create the UI just fine, however the main function I am using for note detection that gets called by a QTimer makes the app very sluggish and unresponsive. I think I am doing something VERY wrong in terms of how I should set it up, but couldn't find the best way to do it by myself.

Here is my code:

import sys 
from PyQt5.QtWidgets import QApplication, QWidget 
from PyQt5.QtWidgets import QGridLayout, QLabel 
from PyQt5.QtGui import QFont 
from PyQt5.QtCore import QTimer, QTime, Qt 
import pyaudio
import numpy as np
from aubio import pitch
from helpers import pitch2note

# set up globals
CHUNK = 1024
FORMAT = pyaudio.paFloat32
CHANNELS = 1
RATE = 44100
RECORD_SECONDS = 5000

tolerance = 0.8
downsample = 1
win_s = 4096 // downsample # fft size
hop_s = 1024  // downsample # hop size
pitch_o = pitch("yinfft", win_s, hop_s, RATE)
pitch_o.set_unit("Hz")
pitch_o.set_silence(-40)
pitch_o.set_tolerance(tolerance)

class Window(QWidget): 
  
    def __init__(self): 
        super().__init__() 
        self.setupUI()

    def setupUI(self):
        # UI design is very much in progress
        # not final but it shouldn't matter for this case
  
        self.setGeometry(100, 100, 800, 400) 
        self.noteLabel = QLabel('Note played:')
        layout = QGridLayout()
  
        font = QFont('Arial', 60, QFont.Bold) 
        self.note = QLabel() # the variable I will modify later
        self.note.setAlignment(Qt.AlignCenter) 
  
        # setting font to the label 
        self.note.setFont(font) 
  
        # adding widgets to the layout 
        layout.addWidget(self.noteLabel, 1, 0) 
        layout.addWidget(self.note, 1, 1)

  
        # setting the layout to main window 
        self.setLayout(layout) 
  
        # creating a timer object 
        timer = QTimer(self) 
  
        # adding action to timer 
        # this is what might be wrong
        timer.timeout.connect(self.getNote) 
  
        # update the timer every 33ms (30fps?) 
        # or is it too much?
        timer.start(33) 

    def getNote(self):
         # main function handling note detection

        p = pyaudio.PyAudio()

        stream = p.open(format=FORMAT,
                        channels=CHANNELS,
                        rate=RATE,
                        input=True,
                        frames_per_buffer=CHUNK)

        frames = []
        notes_list = []

        for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
            buffer = stream.read(CHUNK)
            frames.append(buffer)

            signal = np.frombuffer(buffer, dtype=np.float32)

            pitch_of_note = pitch_o(signal)[0]
            if pitch_of_note != 0:
                note_played = pitch2note(pitch_of_note)
                notes_list.append(note_played[:-1]) 
                # we append only note and not number (eg. E and not E2)

                if len(notes_list) == 10:
                    # We listen for 10 signals and then select the most frequent note in the list
                    # This is because when you pluck a note the frequency can deviate as you pluck it strongly
                    most_likely_note = max(notes_list, key=notes_list.count)

                    stream.stop_stream()
                    stream.close()
                    p.terminate()
                    
                    self.label.setText(most_likely_note)
                    return most_likely_note

  
  
# create pyqt5 app 
App = QApplication(sys.argv) 
  
# create the instance of our Window 
window = Window() 
  
# showing all the widgets 
window.show() 
  
# start the app 
App.exit(App.exec_())

And this is the helpers file:

from math import log2

A4 = 440
C0 = A4*pow(2, -4.75)
NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]

def pitch2note(freq):
    h = round(12*log2(freq/C0))
    octave = h // 12
    n = h % 12
    return NOTE_NAMES[n] + str(octave)

Upvotes: 0

Views: 810

Answers (1)

cagcoach
cagcoach

Reputation: 679

I would recommend using a QThread here, since you don't want the UI to block, while recording. https://doc.qt.io/qtforpython/PySide2/QtCore/QThread.html

Alternatively you can also have a look at the callback mode of PyAudio, which also works nonblocking https://people.csail.mit.edu/hubert/pyaudio/docs/

Upvotes: 0

Related Questions