- 91 posts since 24 Jan, 2012
I'm using Delphi and I'm developing my little and nice synth based on the framework by Christian Budde.
These last days I'm facing a new class of problems that I had previously underestimated. Multithreading issues.
I usually ran a test in my host applications where lots of lots of random notes are thrown in the plugin until something weird happens. Three days ago this test lasted 1 minute before an exception is thrown, then I began to fix things and now the test lasts for more than a quarter. Good!
It's terrible to debug something that occasionally can crash, but I know, it's more terribile to have end-users complain for something that occasionally crashes their host.
I managed to solve most of the problems using Critical Sections and TThreadLists. There are other potential causes of bug-fixes on the list, like the Voice scheme that I use that allocates the memory for each voice dynamically, and I plan to review it to have a fixed number of "dormant" voices waiting to be assigned by the host.
Now. Since I want that my plugin do lots of fancy GUI stuff like a live display of the playing voices peaks, I need to implement a robust system to interact between the audio thread and the GUI. The most successful scheme I find around is the FIFO Queue implementation with a single read-single writer scheme.
Now. Apart from saying "That's the right scheme!" and eventually find an implementation of the FIFO list, I don't know how could I control the things I need.
I have some circles on the interface and the size depends by the Peak value of the voices that are playing. The peak Value is obtained by using the GetPeak function inside the voice object, which retrives the value of VoicePeak and then multiplies it for a constant like '0.8' (to get a nice fall back effect).
Obviously if the value of VoicePeak is calculated by the audio callback weird things happen.
With the usage of the FIFO list I could say to the audio thread: "Hey! The GUI thread needs the Peaks Values! Give them to him". The audio thread, that's more busy, will eventually pass them to the lazy GUI part when it has time.
And then..? How can I effectively pass those values? As parameters of which functions?
I need to understand these things in a pseudo-code form, since I'm still not under the philosophy of thread programming, apart from the simple "HEY! STOP YOU ALL" things like critical sections (that I would avoid for priority inversion).
Thank you so much,
- 4 posts since 6 Jul, 2012, from Delft
AUTO-ADMIN: Non-MP3, WAV, OGG, SoundCloud, YouTube, Vimeo, Twitter and Facebook links in this post have been protected automatically. Once the member reaches 5 posts the links will function as normal.Hi,
I've been reading your post and I thought I post a reply because I had similar problems developing a Vst with Delphi.
I made this Clavia modular G2 Vst over here: http://sourceforge.net/projects/nmg2editor/ (http://sourceforge.net/projects/nmg2editor/)
It's an editor for the synth and there's also a Vst that connects with the editor using a TCP connection.
So there is a TCP listening thread runnning in the Vst. When something is changed in the editor, the editor sends a message to all connected vst's.
The vst read's the message in the listening thread, and then something has to change in the main thread.
So there are a couple of rules I try to follow in order to avoid threading problems in Delphi Vst's:
- All memory allocations should be within objects so all memory is on the heap, because otherwise, you risk that multiple instances of the vcl dll share pieces of memory.
- Vcl is not thread safe, so there should only be one thread that initiates changes in Vcl
I don't know the details of your Vst, but reading your post you probably know your stuff. If it was me, I would try an keep it simple:
I would make an object that allocates a peace of memory that represents a static table for the values you want to represent graphically. So if the number of voices is dynamic, the table should be able to hold enough data for he maximum number of voices.
Then your audio callback function writes the values in this table. And I would use the timer object in the PluginEditorWindow of Toby's template to poll the values from the table and update the vcl.
- 91 posts since 24 Jan, 2012
The little part I need to understand better is the "send a message to". Thevinn suggested me the observer pattern that it pratically the behaviour of a typical VCL application in Delphi (click a button, throw an event, handle the event).
But AFAIK, if I create an event in the audio thread, called for example, OnPeaksChanged, and I link the event with a procedure RedrawPeaksDisplay in the GUI unit, the procedure will be part of the same thread, and it will be the the same of calling directly the procedure RedrawPeaksDisplay from the audio thread.
My basic intuitive approach should be: have a shared TThreadList (a list that can be blocked to be read and written through a critical section) between the audio thread and the GUI thread.
The Audio part calculates the Peaks and put them into the List, flag the List to Changed, a timer on the GUI part checks the List for it to be changed and retrive the values from that. This involves the hidden use of critical sections in both the threads.
It is this simple part of sending messages between threads that puzzles me. How could I send a lock free List of values?
- 4 posts since 6 Jul, 2012, from Delft
If you have really a lot of these events, this might lead to a pile up of windows messages, in that case I would still consider polling the static table.
- 7 posts since 3 Jun, 2012
What I would probably do is to have the audio thread update the voicepeak data (maybe at the end of each block?), have the voicepeak data in a variable type that is guaranteed atomic (whatever the delphi equivalent of "volatile sig_atomic_t or (less ideal) volatile int" would probably be a good choice) then just have the GUI read the value during its periodic repaint event.
Message passing betwee the audio and GUI threads is dangerous unless very carefully handled, as whatever you do must NOT block in the audio thread, ever).
Passing parameter changes to the audio thread FROM the GUI one is not always horrible, but again needs consideration and careful reading of the language specifications to avoid anything that could block in the reader.
Threading bugs are horrible, and if you must do dynamic memory for the audio thread the allocation should probably occur outside the audio thread with the ownership of the memory then passed to the audio thread, with the reverse happening before deallocation.
Does delphi have any kind of lock dependency assertions (And what locking are you using (Locking in the audio thread is generally a nono))? They can help a lot with threading issues.
Moderator: Moderators (Main)