PyQt5 应用程序 - 使用计时器运行带有循环的函数使其“冻结”

Posted

技术标签:

【中文标题】PyQt5 应用程序 - 使用计时器运行带有循环的函数使其“冻结”【英文标题】:PyQt5 app - using timer to run a function with a loop makes it "freeze up" 【发布时间】:2020-08-20 10:09:52 【问题描述】:

我正在尝试为我创建的终端应用程序创建一个 GUI,该应用程序监听您的麦克风以确定您在吉他上演奏的音符。我想使用 PyQt5,这是我第一次使用它(尤其是任何 GUI 框架)。

我可以很好地创建 UI,但是我用于笔记检测的主要功能会被 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_())

这是帮助文件:

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,因为您不希望 UI 在录制时阻塞。 https://doc.qt.io/qtforpython/PySide2/QtCore/QThread.html

或者你也可以看看 PyAudio 的回调模式,它也可以非阻塞工作 https://people.csail.mit.edu/hubert/pyaudio/docs/

【讨论】:

以上是关于PyQt5 应用程序 - 使用计时器运行带有循环的函数使其“冻结”的主要内容,如果未能解决你的问题,请参考以下文章

如何在循环中打开(和关闭)PyQt5 应用程序,并让该循环多次运行

从不同类添加的小部件内的 PyQt5 停止计时器

PyQT5 与 matplotlib 图,事件循环已经在运行

While 循环计时如何在带有多个线程的 C# 中工作?

Pyqt5 Qtimer理解

使用 sleep() 函数作为带有 while 循环的计时器不起作用?