I have something like this : self.pushButton.clicked.connect(self.clicked)
I click my button and it calls the function clicked()
, all good so far.
The Clicked()
function plays a midi note and then does time.sleep()
however instead of actually using time.sleep()
i call another funciton loop()
that does the time.sleep
for me:
def loop(self):
print('we in the loop')
global a
for x in range(10):
if a == 4:
print('break')
break
else:
print('0.1 second sleep')
time.sleep(0.1)
This will do a 1 second time.sleep
unless a == 4
.clicked()
will then turn off the midi note after the loop is finished.
Now i want it so if i click that same pushButton again while the loop is running then a = 4 and the loop will be broken and the original note will stop before 1 second has passed. e.g. if i click pushbutton while on the 5th cycle of the loop it will break, go back to clicked()
and turn the note off early.
I want to be able to break this loop as soon as i have clicked the button for the second time.(the same button)
The problem is that while this loop is running it refuses to accept the second button press. Once the whole of Clicked()
is finished running it will then que that second click, but i dont want this. I dont want to wait for Clicked
to finish. I want to click the button while clicked()
is running and have that second button press make a = 4 so that the loop will stop.
I have tried doing this with threading but it doesn't seem to make a difference, the second click of the button will not register until clicked()
is finished.
What i want to see:
I click the button and it plays a midi note for 1 second. If i press that same button again before 1 second is up then it cuts the note early and starts the note again.
Basically i want to change the length of the loop if i click the button again.
Here is my actual code:
from PyQt5 import QtCore, QtGui, QtWidgets
import pygame
import pygame.midi
import time
import threading
pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(1)
a = 0
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(998, 759)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(430, 80, 121, 71))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 998, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
'THIS IS THE BUTTON'
self.pushButton.clicked.connect(self.clicked)
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", "C"))
'during each loop of loop() check to see if a == 4 and if it does then break'
def loop(self):
print('we in the loop')
global a
for x in range(10):
if a == 4:
print('break')
break
else:
print('0.1 second sleep')
time.sleep(0.1)
def clicked(self):
print('clicked')
global player
global a
a = a + 2
player.note_on(60, 127)
self.loop()
player.note_off(60, 127)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I assume the answer has something to do with threading but when i tried this it made no difference because i had one thread in loop()
and one thread in clicked()
but this made no difference as the second click will never be recognised as long as clicked()
is running because clicked()
is the function the button is actually calling. I made clicked()
do a = a + 2 so that on the second click i know that a == 4 and wanted to use that to end the loop.
Your code has several problems:
Do not use time.sleep in an eventloop even in small intervals since they will still block the eventloop and generate for example: GUI freeze, the slots associated with the signals are not invoked, etc.
Do not use global variables as they are difficult to debug.
In this case it is better to use a QTimer and implement the logic in a controller class, on the other hand do not modify the code generated by pyuic so in this case you will have to restore that code and save it in a ui.py file.
from functools import cached_property
from dataclasses import dataclass
from PyQt5 import QtCore, QtGui, QtWidgets
import pygame.midi
from ui import Ui_MainWindow
@dataclass
class MidiController(QtCore.QObject):
note: int = 60
velocity: int = 127
channel: int = 0
@cached_property
def timer(self):
timer = QtCore.QTimer(singleShot=True)
timer.timeout.connect(self.off)
return timer
@cached_property
def player(self):
pygame.midi.init()
player = pygame.midi.Output(0)
player.set_instrument(1)
return player
def start(self, dt=1 * 1000):
self.timer.setInterval(dt)
self.timer.start()
self.on()
@property
def running(self):
return self.timer.isActive()
def stop(self):
self.timer.stop()
self.off()
def on(self):
self.player.note_on(self.note, self.velocity, self.channel)
def off(self):
self.player.note_off(self.note, self.velocity, self.channel)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.handle_clicked)
@cached_property
def midi_controller(self):
return MidiController()
def handle_clicked(self):
# update parameters
self.midi_controller.note = 60
# self.midi_controller.velocity = 127
if self.midi_controller.running:
self.midi_controller.stop()
else:
self.midi_controller.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())