I’m doing a project where I’m controlling Csound with ctcsound and using PyQt6 to build the GUI. It’s a real-time improvisation system. I’m on a 2021 14" MacBook Pro with the M1 Pro chip running macOS 13.4.1.
On rare occasions my program crashes and I get the error message below. While it doesn’t happen often (maybe once every 100 times I run the program) it would be pretty bad if it happened while performing live. Can anyone help me interpret this error message and suggest what might be wrong? I’m not even sure if this is a Csound, PyQt6, or Python issue. Any help would be appreciated!
My guess is that it is a threading problem. Do you use csoundPerformanceThread or create your own thread or execute somehow Csound in the Qt event loop?
How do you call Csound functions? I have worked much with C++ and QML in Qt but not much with Python. I guess there is is similar signal and slot connection system? Signals and slots should be the right way to connect your UI (or other components) with Csound funtions.
Can you attach the Python code? Otherwise it is hard to guess. The reason may be also elsewhere…
tarmo
Kontakt Jason Hallen via The Csound Community (<noreply@forum.csound.com>) kirjutas kuupäeval K, 30. august 2023 kell 21:26:
Thanks for the response, Tarmo. I’m using ctcsound.CsoundPerformanceThread to run the Csound thread. Yes, PyQt has the same signals and slots connection system as in Qt, though so far I’m only using it to send changes in a couple GUIs widgets to Csound with .setControlChannel.
The way I’m reading channels from Csound is currently via a QTimer loop which reads from a few .channelPtrs. I’ve also successfully experimented with 1) using a separate thread in PyQt and 2) using OSC to do communication with Csound, but I’m using QTimer for now because it’s so simple and meets my needs.
Do you think using the signals/slots system more consistently for all Csound communication might help avoid threading errors? Or perhaps using a separate thread or OSC for Csound communication rather than a QTimer loop?
I’ve attached the Python code as a .txt file because I can’t upload a .py file.
It’s pretty messy because it’s in early stages of development, but maybe it’ll help. Searching for “csound” in the code will take you to the parts of the code where I’m sending and receiving messages from Csound. I’ve also included the .csd file in case that helps.
Yes, IMO, most every API app should. The signal handlers csound installs are appropriate for the csound CLI app but they can interfere with other signal handlers that get installed by things like Python’s interpreter or Java’s JVM. I don’t know if that is the root cause here (I saw the stack trace mentioned signal_handler), but I’d suggest trying to put it in and seeing if the problem persists.
After adding in the csoundInitialize call at the beginning of the code, I’m now getting this shorter error message when my program crashes. The frequency of the crashing has gone up a lot, around once every 7 times I run the program.
libc++abi: terminating due to uncaught exception of type std::__1::system_error: condition_variable wait failed: Invalid argument
It’s no longer producing the very long backtrace report, just this short message. I’m still not sure if this is a macOS/PyQt thing or a Csound thing. Perhaps I’m playing fast and loose with the communication between PyQt and Csound, so I’ll experiment with different ways to improve that.
I was now able to look at your code. Looks cool! At the first glance I don’t see anything suspicious… It seems right to me how you do it.
I tried to run it but could not do it without samples.
Some things I suggest you to try:
do not use any audio output (for testing), instead of Coreaudio try -+rtaudio=null If that works, try wiht portaudio or jack
tr bigger buffers (-b and -B options in Csound file)
try longer QTimer update time
The problem could be still elsewhere, I cannot tell.
As a fallback, Are you a CsoundQt user? Not all, but I believe essential part of the UI could be realized also with the CsoundQt widgets. And if you need more, you could use PythonQt functionality from withing CsoundQt. Last official CsoundQt MacOS build is wihtoug PythonQt support though… See https://csoundqt.github.io/pages/python.html for some information.
OR if you are comfortable with html and javascript you can add missing parts of the UI in html.
Not sure if it helps…
tarmo
Kontakt Cordelia via The Csound Community (<noreply@forum.csound.com>) kirjutas kuupäeval R, 1. september 2023 kell 15:36:
Thanks very much for taking the time to review the code and send your suggestions, Tarmo!
Can you say a little more about why I should not use any audio output for testing? I’m open to that but would appreciate understanding the reasoning. The program does work with -+rtaudio=null, but it doesn’t work with portaudio. I’ve had problems with portaudio on my computer, which is why I’ve been using coreaudio. Do you suggest portaudio and jack because they perform better than coreaudio?
Trying bigger buffers and update times also sounds good to me. I can keep tweaking them to get a balance of performance and responsiveness.
Yes, I’ve been a CsoundQt user in the past. The past couple years I’ve been delving into building my own GUIs in order to have complete control over the programs. I spent a lot of time using JS/HTML/CSS for Csound GUI development but bailed on that because it was getting too complicated (for a novice like me) operating in the browser environment, and PyQt has been a breath of fresh air since then. It’s good to know though that I can return to CsoundQt if I can’t find a way forward with PyQt.
It was a blind guess. I have heard about problems sometimes with Coreaudio. Anyway, seems that your code is fine, the problem is somewhere in communication with the audio driver. Might be that Python is not fast enough sometimes to fulfill some needs, I don’t know.
Yes, try with Jack, Joachim Heintz has found that it works the best on MacOS (at least with CsoundQt that is using Csound API as well).
And make all buffers bigger and do anything that makes the life of the program easier, later you can breing them down to via trial- and error
I agree with you - creating a custom GUI with Qt is a good way. Performance wise C++ is of course much quicker. My preferred solution recently is to use QML (Qt Meta Language) for the UI and a C++ class that deals with Csound.
Best!
tarmo
Kontakt Jason Hallen via The Csound Community (<noreply@forum.csound.com>) kirjutas kuupäeval L, 2. september 2023 kell 16:56:
Here’s an update in case this helps anyone in the future. The program kept crashing in the same way as in my initial post. I noticed that the backtrace report often suggested that the crash came when the QtGui module was running the drawText function. In my GUI there’s an event loop that uses the drawText function, and it’s pulling a value from Csound to draw.
I wondered if the Csound value couldn’t be pulled in time to draw the text, so I commented out all the drawText code. That was a few days ago, and there hasn’t been a crash since. I’m not confident the problem won’t re-emerge, but at least for now the crashing has stopped.
I’ve also incorporated Eduardo’s csoundengine to control Csound with Python, and I’m sure his mechanisms for communicating with Csound are more informed and efficient than mine were. That’s probably also helping reduce crashes.
Hilariously, after just posting my previous message my program just crashed in the same way again for the first time in a few days. Gotta figure out how these PyQt drawing functions are related to the crashing and whether it involves Csound. Time for more debugging…
I would guess that it is a threading issue. You should not call csound from the gui thread. Instead read any value from csound within a qtimer and put that on a queue. Then when drawing read from that queue. Or do all communication via osc.
Thanks for the tips, Eduardo! I’ll experiment with different ways to read Csound values from outside of the GUI thread. I’ve been able to use QThreads and OSC to read Csound values in separate threads in previous PyQt experiments but was hoping I could keep things simpler for this project. I guess not!
I think uou are close - if you have csound running in separate thread, the Qt way would be to send a signal when a value changes from that thread and and connect it to a drawing slot in your UI thread.
Tarmo
T, 12. september 2023 18:27 Jason Hallen via The Csound Community <noreply@forum.csound.com> kirjutas:
I’ve changed the code to only read Csound data from a separate QThread and send it to the GUI thread via signals/slots. However, I’m still getting crashes. I think I’m misunderstanding how to manage concurrency and thread communication. If I explain what I’m doing can you let me know what I’m doing wrong?
Threads
The main GUI thread is a PyQt QWidget.
Csound is run in a performance thread managed by csoundengine.Engine().session().
Csound data is read in a separate QThread.
Reading Csound Data
The QThread reads data from Csound. Specifically, the instruments defined in the Session use Csound tables to read/write the p-arguments, so the QThread reads the data from these tables with .get(). The data is sent from the QThread to the GUI thread via signals/slots. The slots that receive the data in the GUI trigger updates to the widgets.
Here’s where I’m wondering if I’m doing it wrong. In order to know what Csound data to read in the QThread, I pass the main GUI thread object (main) into the QThread upon instantiation. It looks like this:
class WorkerAPI(QObject):
'''Runs a loop in a separate thread that reads Csound data.'''
playback = pyqtSignal(tuple)
main_volume = pyqtSignal(float)
def __init__(self, main):
super(WorkerAPI, self).__init__()
self.main = main
def run(self):
self._active = True
while self._active:
for track in self.main.tracks_panel.track_list:
if track.synth is not None:
playback_position = track.synth.get("kplayback_position")
if playback_position is not None:
self.playback.emit((track, playback_position))
meter_maximum = self.main.mixer.get("kmeter_maximum")
self.main_volume.emit(meter_maximum)
time.sleep(0.05)
Does it defeat the purpose of using a QThread to read Csound data if I’m still using the objects defined in the main GUI thread (i.e. self.main.tracks_panel.track_list and self.main.mixer) within the QThread? How else would I approach this? I’m struggling to know how to fully isolate the Csound communication from the main GUI.
Sending Csound Data
Control signals are sent from the GUI thread to Csound within the GUI thread using synth.set(). I was thinking it’s okay to send control signals from the GUI thread because timing isn’t important as long as Csound eventually receives the control signal and the GUI thread is not awaiting a response from synth.set(). But maybe I should also be sending all control signals from the QThread?