Reputation: 321
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
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