Приложение PyQt5 — использование таймера для запуска функции с циклом приводит к ее «зависанию»

#python #pyqt5 #pyaudio #aubio

#python #pyqt5 #pyaudio #aubio

Вопрос:

Я пытаюсь создать графический интерфейс для созданного мной терминального приложения, которое прослушивает ваш микрофон, чтобы определить, какую ноту вы играете на гитаре. Я хочу использовать PyQt5, и я впервые использую его (или, в частности, любой графический фреймворк).

Я могу создать пользовательский интерфейс просто отлично, однако основная функция, которую я использую для обнаружения заметок, вызываемая QTimer, делает приложение очень вялым и не реагирующим. Я думаю, что я делаю что-то очень неправильное с точки зрения того, как я должен это настроить, но не смог найти лучший способ сделать это самостоятельно.

Вот мой код:

 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_())
  

И это файл helpers:

 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)
  

Ответ №1:

Я бы рекомендовал использовать здесь QThread, поскольку вы не хотите, чтобы пользовательский интерфейс блокировался во время записи. https://doc.qt.io/qtforpython/PySide2/QtCore/QThread.html

В качестве альтернативы вы также можете взглянуть на режим обратного вызова PyAudio, который также работает без блокировкиhttps://people.csail.mit.edu/hubert/pyaudio/docs /