Необходимо сделать режим записи, при котором в текстовом поле добавляются ноты по нажатию на кнопки синтезатора

нужна помощь! Необходимо сделать режим записи, при котором в текстовом поле добавляются ноты по нажатию на кнопки синтезатора. Заранее благодарю за помощь.

Файл main.py

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QGridLayout
from PyQt5.QtCore import QUrl
from PyQt5 import QtMultimedia as M
from mainwindow import Ui_MainWindow
from synt import *


class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setupUi(self)

        self.url = None
        self.player = M.QMediaPlayer()
        self.player.setVolume(70)

        synthesizer = Synthesizer()
        self.window = MyWidget(synthesizer=synthesizer)

        grid = QGridLayout(self.centralwidget)
        grid.addWidget(self.pushButton_3, 0, 0)
        grid.addWidget(self.pushButton, 0, 1)
        grid.addWidget(self.pushButton_2, 0, 2)
        grid.addWidget(self.pushButton_4, 0, 4)
        grid.addWidget(self.pushButton_5, 0, 5)
        grid.addWidget(self.label, 1, 0, 1, 3)
        grid.setRowStretch(2, 2)
        grid.addWidget(self.window, 5, 0, 1, 5)
        grid.setRowStretch(5, 1)

    def closeEvent(self, event):
        reply = QMessageBox.question(self,
                                     'Message',
                                     "Are you sure to quit?",
                                     QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.window.synthesizer is not None:
                self.window.synthesizer.terminate()
            event.accept()
        else:
            event.ignore()

    def play(self):
        if self.url:
            self.player.play()

    def generate(self):
        self.window.synthesizer.play(self.sound_edit.text())

    def save(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()","","All Files (*);;Wav Files (*.wav)", options=options)
        if fileName:
            self.window.synthesizer.save(fileName)

    def pause(self):
        if self.url:
            self.player.pause()

    def choose(self):
        self.url, _ = QFileDialog.getOpenFileName(
            None, 'OpenFile', './', 'Music (*.mp3 *.wav)'
        )
        if not self.url:
            self.label.setText('')
            return
        self.url = QUrl.fromLocalFile(self.url)#QUrl(self.url)
        self.content = M.QMediaContent(self.url)
        self.player.setMedia(self.content)
        self.label.setText(self.url.toString().split('/')[-1])


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

Файл mainwindow.py

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(90, 40, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(170, 40, 75, 23))
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(10, 40, 75, 23))
        self.pushButton_3.setObjectName("pushButton_3")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(10, 40, 75, 23))
        self.pushButton_4.setObjectName("pushButton_4")
        self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_5.setGeometry(QtCore.QRect(10, 40, 75, 23))
        self.pushButton_5.setObjectName("pushButton_5")

        self.sound_edit = QtWidgets.QLineEdit(self.centralwidget)
        self.sound_edit.setGeometry(QtCore.QRect(10, 90, 375, 23))
        self.sound_edit.setObjectName("sound_edit")
        self.sound_edit.setText('E4_5 G3_5 B5_5 A4_5 D#3_5 G4_5 B5_5 C5_5 D4_5')

        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 80, 221, 21))
        self.label.setText("")
        self.label.setObjectName("label")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 20))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "play sound"))
        self.pushButton_2.setText(_translate("MainWindow", "pause"))
        self.pushButton_3.setText(_translate("MainWindow", "Open File"))
        self.pushButton_4.setText(_translate("MainWindow", "Save File"))
        self.pushButton_5.setText(_translate("MainWindow", "play melody"))
        self.pushButton.clicked.connect(self.play)
        self.pushButton_2.clicked.connect(self.pause)
        self.pushButton_3.clicked.connect(self.choose)
        self.pushButton_4.clicked.connect(self.save)
        self.pushButton_5.clicked.connect(self.generate)

Файл synt.py

import sys
import pyaudio
import numpy as np
import threading
import wave

from PyQt5.QtCore import QEventLoop, QTimer
from scipy import signal
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout, \
    QVBoxLayout, QGridLayout, QMessageBox
from PyQt5.QtGui import QKeySequence
from functools import partial


class Oscillator:
    def __init__(self, rate, n_chunk, freq, otype, gain=0.1):
        self.rate = rate
        self.n_chunk = n_chunk
        self.freq = freq
        self.gain = gain
        self.state = False
        self.pi2_t0 = 2 * np.pi / (rate / freq)
        self.offset = 0
        self.period = n_chunk * rate
        self.change_waveform(otype)

    def out(self):
        x = np.arange(self.offset, self.offset + self.n_chunk)
        chunk = self.gain * self.generator(self.pi2_t0 * x)
        self.offset += self.n_chunk
        if self.offset == self.period:
            self.offset = 0
        return chunk

    def is_run(self):
        return self.state

    def start(self):
        self.state = True

    def stop(self):
        self.state = False
        self.offset = 0

    def change_waveform(self, type):
        self.type = type
        if self.type == "sin":
            self.generator = np.sin
        elif self.type == "saw":
            self.generator = signal.sawtooth


class Synthesizer:
    waveform = ["sin", "saw"]

    def __init__(self, rate=44100, n_chunk=1024):
        self.rate = rate
        self.n_chunk = n_chunk
        self.p = pyaudio.PyAudio()
        self.stream = self.p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=1,
                                  frames_per_buffer=n_chunk)
        self.oscillators = []
        self.frames = []
        self.type = Synthesizer.waveform[0]
        t = threading.Thread(target=self.render)
        t.start()

    def __seek_osc(self, freq):
        osc = None
        for o in self.oscillators:
            if freq == o.freq:
                osc = o
        if osc is None:
            osc = Oscillator(self.rate, self.n_chunk, freq, self.type)
            self.oscillators.append(osc)
        return osc

    def render(self):
        while self.stream.is_active():
            chunk = np.zeros(self.n_chunk)
            for osc in self.oscillators:
                if osc.is_run():
                    chunk += osc.out()
                    self.frames.append(chunk.astype(np.float32).tostring())
            self.stream.write(chunk.astype(np.float32).tostring())

    def play(self, text: str):
        for s in text.split(' '):
            l = 5
            ss = s.split('_')
            if len(ss) > 1:
                l = int(ss[1])
            freq = MyWidget.pitch_freq_di[ss[0]]
            self.request(freq)
            loop = QEventLoop()
            QTimer.singleShot(l*100, loop.quit)
            loop.exec_()
            self.request(freq)

    def save(self, filename):
        wf = wave.open(filename, 'wb')
        wf.setnchannels(1)
        wf.setsampwidth(self.p.get_sample_size(pyaudio.paFloat32))
        wf.setframerate(self.rate)
        wf.writeframes(b''.join(self.frames))
        wf.close()

        self.frames = []

    def request(self, freq):
        osc = self.__seek_osc(freq)
        if not osc.is_run():
            osc.start()
            return True
        else:
            osc.stop()
            return False

    def terminate(self):
        for osc in self.oscillators:
            osc.stop()
        self.stream.close()
        self.p.terminate()

    def change_waveform(self):
        r = np.random.randint(len(Synthesizer.waveform))
        self.type = Synthesizer.waveform[r]
        for osc in self.oscillators:
            osc.change_waveform(self.type)
        return self.type


class MyWidget(QWidget):
    pitch_class = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"]
    pitch_freq_di = {pn + str(k): 440 * 2 ** (((12 * k + idx) - 48) / 12)
                     for idx, pn in enumerate(pitch_class) \
                     for k in range(0, 9)}

    def __init__(self, synthesizer=None, keyset=(39, 52, 51, 64)):
        super().__init__()
        self.synthesizer = synthesizer
        self.keyset = keyset
        self.keymap = {
            "Q": "C4", "2": "C#4", "W": "D4", "3": "D#4", "E": "E4",
            "R": "F4", "5": "F#4", "T": "G4", "6": "G#4",
            "Y": "A5", "7": "A#5", "U": "B5", "I": "C5",
            "Z": "C3", "S": "C#3", "X": "D3", "D": "D#3", "C": "E3",
            "V": "F3", "G": "F#3", "B": "G3", "H": "G#3",
            "N": "A4", "J": "A#4", "M": "B4", ",": "C4",
        }
        self.params_list = []
        print("# ================================")
        print("# Initialize")
        print("# ================================")
        self.init_ui()
        print("")
        print("# ================================")
        print("# <<Print key input>>")
        print("# ================================")
        self.show()

    def __make_bt(self, id, name):
        bt = QPushButton(name)
        if "#" in name:
            bt.key_off = lambda: bt.setStyleSheet("background-color: #999999")
            bt.key_on = lambda: bt.setStyleSheet("background-color: #9999ff")
        else:
            bt.key_off = lambda: bt.setStyleSheet("background-color: #ffffff")
            bt.key_on = lambda: bt.setStyleSheet("background-color: #9999ff")
        bt.key_off()
        bt.setContentsMargins(0, 0, 0, 0)
        freq = MyWidget.pitch_freq_di[name]
        params = {"freq": freq, "name": name, "id": id, "self_bt": bt}
        bt.clicked.connect(partial(self.buttonClicked, params["freq"], [bt]))
        bt.setMaximumWidth(40)
        bt.setMaximumHeight(300)
        print(f"{id}\t{name}\t{freq}")
        self.params_list.append(params)
        return bt

    def init_ui(self):
        self.setStyleSheet("background-color: #eeeeee")
        self.setWindowTitle('Synthesizer')

        keys = QGridLayout()
        id = 0
        for n in range(self.keyset[0], self.keyset[1]):
            name = MyWidget.pitch_class[n % 12] + str(n // 12)
            pos = n - self.keyset[0]
            keys.addWidget(self.__make_bt(id, name), 1, pos)
            id += 1

        for n in range(self.keyset[2], self.keyset[3]):
            name = MyWidget.pitch_class[n % 12] + str(n // 12)
            pos = n - self.keyset[2]
            keys.addWidget(self.__make_bt(id, name), 0, pos)
            id += 1

        self.setLayout(keys)
        self.setGeometry(300, 300, 660, 160)

    def buttonClicked(self, freq, bt_list):
        sender = self.sender()
        if self.synthesizer is not None:
            state = self.synthesizer.request(freq)
            if state:
                for bt in bt_list:
                    bt.key_on()
            else:
                for bt in bt_list:
                    bt.key_off()

    def closeEvent(self, event):
        reply = QMessageBox.question(self,
                                     'Message',
                                     "Are you sure to quit?",
                                     QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.synthesizer is not None:
                self.synthesizer.terminate()
            event.accept()
        else:
            event.ignore()

    def keyPressEvent(self, event):
        key = QKeySequence(event.key()).toString()
        bt_list = []
        freq = 0.0
        name = None
        if key in self.keymap.keys():
            v = self.keymap[key]
            for p in self.params_list:
                if p["name"] == v:
                    name = p["name"]
                    freq = p["freq"]
                    bt_list.append(p["self_bt"])
            self.buttonClicked(freq, bt_list)
            print(f"{key}: {name}")
        elif key == "@":
            type = self.synthesizer.change_waveform()


if __name__ == '__main__':
    synthesizer = Synthesizer()
    app = QApplication(sys.argv)
    window = MyWidget(synthesizer=synthesizer)
    sys.exit(app.exec_())

Ответы (1 шт):

Автор решения: S. Nick

Обратите внимание я заменил QLineEdit на QTextEdit, просто мне кажется, так будет лучше.

Я не проверял работу других методов не связанных с кнопками синтезатора.

Я поправил фокусировку, теперь вы можете не пользоваться мышкой, а стучать по кнопкам вашей клавиатуры.

main.py

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QGridLayout
from PyQt5.QtCore import QUrl
from PyQt5 import QtMultimedia as M

from synt import *       

#from mainwindow import Ui_MainWindow
class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(90, 40, 75, 23))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setGeometry(QtCore.QRect(170, 40, 75, 23))
        self.pushButton_2.setObjectName("pushButton_2")
        self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_3.setGeometry(QtCore.QRect(10, 40, 75, 23))
        self.pushButton_3.setObjectName("pushButton_3")
        self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_4.setGeometry(QtCore.QRect(10, 40, 75, 23))
        self.pushButton_4.setObjectName("pushButton_4")
        self.pushButton_5 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_5.setGeometry(QtCore.QRect(10, 40, 75, 23))
        self.pushButton_5.setObjectName("pushButton_5")

#        self.sound_edit = QtWidgets.QLineEdit(self.centralwidget)
        self.sound_edit = QtWidgets.QTextEdit(self.centralwidget)                  # QTextEdit
#        self.sound_edit.setGeometry(QtCore.QRect(10, 90, 375, 23))
        self.sound_edit.setGeometry(QtCore.QRect(10, 90, 375, 50))
        
        self.sound_edit.setObjectName("sound_edit")
#        self.sound_edit.setText('E4_5 G3_5 B5_5 A4_5 D#3_5 G4_5 B5_5 C5_5 D4_5')  # -
        self.sound_edit.setText('')                                                # +
        self.sound_edit.setReadOnly(True)                                          # +
        self.sound_edit.setStyleSheet("font-size: 15px;")                          # +

        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(20, 80, 221, 21))
        self.label.setText("")
        self.label.setObjectName("label")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 20))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "play sound"))
        self.pushButton_2.setText(_translate("MainWindow", "pause"))
        self.pushButton_3.setText(_translate("MainWindow", "Open File"))
        self.pushButton_4.setText(_translate("MainWindow", "Save File"))
        self.pushButton_5.setText(_translate("MainWindow", "play melody"))
        self.pushButton.clicked.connect(self.play)
        self.pushButton_2.clicked.connect(self.pause)
        self.pushButton_3.clicked.connect(self.choose)
        self.pushButton_4.clicked.connect(self.save)
        self.pushButton_5.clicked.connect(self.generate)


class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MyWindow, self).__init__()
        self.setupUi(self)

        self.url = None
        self.player = M.QMediaPlayer()
        self.player.setVolume(70)

        synthesizer = Synthesizer()
        self.window = MyWidget(synthesizer=synthesizer, parent=self)          # + , parent=self

        grid = QGridLayout(self.centralwidget)
        grid.addWidget(self.pushButton_3, 0, 0)
        grid.addWidget(self.pushButton, 0, 1)
        grid.addWidget(self.pushButton_2, 0, 2)
        grid.addWidget(self.pushButton_4, 0, 4)
        grid.addWidget(self.pushButton_5, 0, 5)
        grid.addWidget(self.label, 1, 0, 1, 3)
        grid.setRowStretch(2, 2)
        grid.addWidget(self.window, 5, 0, 1, 5)
        grid.setRowStretch(5, 1)
        
        self.window.setFocus()                                                 # +++

    def closeEvent(self, event):
        reply = QMessageBox.question(self,
                                     'Message',
                                     "Are you sure to quit?",
                                     QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.window.synthesizer is not None:
                self.window.synthesizer.terminate()
            event.accept()
        else:
            event.ignore()
        self.window.setFocus()                                                 #+++

    def play(self):
        if self.url:
            self.player.play()
        self.window.setFocus()                                                 #+++


    def generate(self):
#        self.window.synthesizer.play(self.sound_edit.text())
        self.window.synthesizer.play(self.sound_edit.toPlainText())            # toPlainText()
        self.window.setFocus()                                                 #+++

    def save(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getSaveFileName(
            self, "QFileDialog.getSaveFileName()", "", 
            "All Files (*);;Wav Files (*.wav)", options=options
        )
        if fileName:
            self.window.synthesizer.save(fileName)
        self.window.setFocus()                                                 #+++

    def pause(self):
        if self.url:
            self.player.pause()
        self.window.setFocus()                                                 #+++

    def choose(self):
        self.url, _ = QFileDialog.getOpenFileName(
            None, 'OpenFile', './', 'Music (*.mp3 *.wav)'
        )
        self.window.setFocus()                                                 #+++
        if not self.url:
            self.label.setText('')
            return
        self.url = QUrl.fromLocalFile(self.url)#QUrl(self.url)
        self.content = M.QMediaContent(self.url)
        self.player.setMedia(self.content)
        self.label.setText(self.url.toString().split('/')[-1])


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

synt.py

import sys
import pyaudio
import numpy as np
import threading
import wave

from PyQt5.QtCore import QEventLoop, QTimer
from scipy import signal
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QHBoxLayout, \
    QVBoxLayout, QGridLayout, QMessageBox
from PyQt5.QtGui import QKeySequence
from functools import partial


class Oscillator:
    def __init__(self, rate, n_chunk, freq, otype, gain=0.1):
        self.rate = rate
        self.n_chunk = n_chunk
        self.freq = freq
        self.gain = gain
        self.state = False
        self.pi2_t0 = 2 * np.pi / (rate / freq)
        self.offset = 0
        self.period = n_chunk * rate
        self.change_waveform(otype)

    def out(self):
        x = np.arange(self.offset, self.offset + self.n_chunk)
        chunk = self.gain * self.generator(self.pi2_t0 * x)
        self.offset += self.n_chunk
        if self.offset == self.period:
            self.offset = 0
        return chunk

    def is_run(self):
        return self.state

    def start(self):
        self.state = True

    def stop(self):
        self.state = False
        self.offset = 0

    def change_waveform(self, type):
        self.type = type
        if self.type == "sin":
            self.generator = np.sin
        elif self.type == "saw":
            self.generator = signal.sawtooth


class Synthesizer:
    waveform = ["sin", "saw"]

    def __init__(self, rate=44100, n_chunk=1024):
        self.rate = rate
        self.n_chunk = n_chunk
        self.p = pyaudio.PyAudio()
        self.stream = self.p.open(format=pyaudio.paFloat32, channels=1, rate=rate, output=1,
                                  frames_per_buffer=n_chunk)
        self.oscillators = []
        self.frames = []
        self.type = Synthesizer.waveform[0]
        t = threading.Thread(target=self.render)
        t.start()

    def __seek_osc(self, freq):
        osc = None
        for o in self.oscillators:
            if freq == o.freq:
                osc = o
        if osc is None:
            osc = Oscillator(self.rate, self.n_chunk, freq, self.type)
            self.oscillators.append(osc)
        return osc

    def render(self):
        while self.stream.is_active():
            chunk = np.zeros(self.n_chunk)
            for osc in self.oscillators:
                if osc.is_run():
                    chunk += osc.out()
                    self.frames.append(chunk.astype(np.float32).tostring())
            self.stream.write(chunk.astype(np.float32).tostring())

    def play(self, text: str):
        for s in text.split(' '):
            l = 5
            ss = s.split('_')
            if len(ss) > 1:
                l = int(ss[1])
            freq = MyWidget.pitch_freq_di[ss[0]]
            self.request(freq)
            loop = QEventLoop()
            QTimer.singleShot(l*100, loop.quit)
            loop.exec_()
            self.request(freq)

    def save(self, filename):
        wf = wave.open(filename, 'wb')
        wf.setnchannels(1)
        wf.setsampwidth(self.p.get_sample_size(pyaudio.paFloat32))
        wf.setframerate(self.rate)
        wf.writeframes(b''.join(self.frames))
        wf.close()

        self.frames = []

    def request(self, freq):
        osc = self.__seek_osc(freq)
        if not osc.is_run():
            osc.start()
            return True
        else:
            osc.stop()
            return False

    def terminate(self):
        for osc in self.oscillators:
            osc.stop()
        self.stream.close()
        self.p.terminate()

    def change_waveform(self):
        r = np.random.randint(len(Synthesizer.waveform))
        self.type = Synthesizer.waveform[r]
        for osc in self.oscillators:
            osc.change_waveform(self.type)
        return self.type


class MyWidget(QWidget):
    pitch_class = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"]
    pitch_freq_di = {pn + str(k): 440 * 2 ** (((12 * k + idx) - 48) / 12)
                     for idx, pn in enumerate(pitch_class) \
                     for k in range(0, 9)}

    def __init__(self, synthesizer=None, keyset=(39, 52, 51, 64), parent=None):     # + parent
        super().__init__()
        self.parent = parent                                                        # + parent
        
        self.synthesizer = synthesizer
        self.keyset = keyset
        self.keymap = {
            "Q": "C4", "2": "C#4", "W": "D4", "3": "D#4", "E": "E4",
            "R": "F4", "5": "F#4", "T": "G4", "6": "G#4",
            "Y": "A5", "7": "A#5", "U": "B5", "I": "C5",
            "Z": "C3", "S": "C#3", "X": "D3", "D": "D#3", "C": "E3",
            "V": "F3", "G": "F#3", "B": "G3", "H": "G#3",
            "N": "A4", "J": "A#4", "M": "B4", ",": "C4",
        }
        self.params_list = []
        print("# ================================")
        print("# Initialize")
        print("# ================================")
        self.init_ui()
        print("")
        print("# ================================")
        print("# <<Print key input>>")
        print("# ================================")
        self.show()

    def __make_bt(self, id, name):
        bt = QPushButton(name)
        if "#" in name:
            bt.key_off = lambda: bt.setStyleSheet("background-color: #999999")
            bt.key_on = lambda: bt.setStyleSheet("background-color: #9999ff")
        else:
            bt.key_off = lambda: bt.setStyleSheet("background-color: #ffffff")
            bt.key_on = lambda: bt.setStyleSheet("background-color: #9999ff")
        bt.key_off()
        bt.setContentsMargins(0, 0, 0, 0)
        freq = MyWidget.pitch_freq_di[name]
        params = {"freq": freq, "name": name, "id": id, "self_bt": bt}
        bt.clicked.connect(partial(self.buttonClicked, params["freq"], [bt]))
        bt.setMaximumWidth(40)
        bt.setMaximumHeight(300)
        print(f"{id}\t{name}\t{freq}")
        self.params_list.append(params)
        return bt

    def init_ui(self):
        self.setStyleSheet("background-color: #eeeeee")
        self.setWindowTitle('Synthesizer')

        keys = QGridLayout()
        id = 0
        for n in range(self.keyset[0], self.keyset[1]):
            name = MyWidget.pitch_class[n % 12] + str(n // 12)
            pos = n - self.keyset[0]
            keys.addWidget(self.__make_bt(id, name), 1, pos)
            id += 1

        for n in range(self.keyset[2], self.keyset[3]):
            name = MyWidget.pitch_class[n % 12] + str(n // 12)
            pos = n - self.keyset[2]
            keys.addWidget(self.__make_bt(id, name), 0, pos)
            id += 1

        self.setLayout(keys)
        self.setGeometry(300, 300, 660, 160)
        
# !!!
    def buttonClicked(self, freq, bt_list):
        sender = self.sender()
        if self.synthesizer is not None:
            state = self.synthesizer.request(freq)
            if state:
                for bt in bt_list:
                    bt.key_on()
                    self.parent.sound_edit.setPlainText(                                  # +++
                        self.parent.sound_edit.toPlainText() + f'{bt.text()}_5 ')           # +++                
            else:
                for bt in bt_list:
                    bt.key_off()
                    self.parent.sound_edit.setPlainText(                                   # +++
                        self.parent.sound_edit.toPlainText().replace(f'{bt.text()}_5 ', '')) # +++                     

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self,
            'Message',
            "Are you sure to quit?",
            QMessageBox.Yes, QMessageBox.No
        )
        if reply == QMessageBox.Yes:
            if self.synthesizer is not None:
                self.synthesizer.terminate()
            event.accept()
        else:
            event.ignore()

    def keyPressEvent(self, event):
        key = QKeySequence(event.key()).toString()
        bt_list = []
        freq = 0.0
        name = None
        if key in self.keymap.keys():
            v = self.keymap[key]
            for p in self.params_list:
                if p["name"] == v:
                    name = p["name"]
                    freq = p["freq"]
                    bt_list.append(p["self_bt"])
            self.buttonClicked(freq, bt_list)
#            print(f"{key}: {name}")
        elif key == "@":
            type = self.synthesizer.change_waveform()


if __name__ == '__main__':
    synthesizer = Synthesizer()
    app = QApplication(sys.argv)
    window = MyWidget(synthesizer=synthesizer)
    sys.exit(app.exec_())

введите сюда описание изображения

→ Ссылка