AudioDemoTabComponent & The Threading Model


#1

audioDeviceIOCallback()

timerCallback()

AudioDemoTabComponent from the demo implements the above methods. Is there a way to call repaint() instead from the audioDeviceIOCallback instead of from the separate Timer loop? Having tried that directly I know that it crashes because it doesn't fit the JUCE threading model (lock problem). There doesn't seem to be a way to trigger Timer to call immediately (single fire), which I could call from the audio callback.

However, I'd like my software to react quickly as the data comes in. I'm calculating a pitch, and would like to call repaint() as it calculates the change. Previously, I've used a BlockingQueue to do this... Put the pitch in the Queue and trigger repaint when a pitch gets added. Maybe I can just call timer to react immediately? Should I not be doing computation in the audioDeviceIOCallback?

Confused by the event/processing model, any pointers appreciated.

-J


#2

In short: no, that’s a bad idea.
In length: no, that’s a very bad idea.

The audio thread is
a) intended to return as quickly as possible
b) not the owner of the Window
c) not the same thread that the Window’s message queue is exectued in.

What you need to do is defer the repaint() call into the message queue thread’s scope.


#3

http://www.codeproject.com/Articles/21130/Rabbit-Threads-Making-Threads-Jump is an example on how to defer method invoking to another thread.


#4

Okay, that's what I thought. How does one defer the repaint() call. I saw a method called callFunctionOnMessageThread(), but its probably because of my rusty C++ skills, that I was having trouble figuring out how to call my class method (not function) with it, or if it is indeed the proper fix.

I didn't see a BlockingQueue implementation as well. I thought I'd mimic Timer, but that for some reason is much more complicated than what I thought it would be (I figured it was a wait() with a loop, but its not), and I'm not sure why.

Thanks for the help so far.

J


#5

The thing is, you absolutely should not make any blocking calls in the audio thread.
If you block the thread, and the audio driver cycles out, there will be a buffer underrun and thus distorted audio playback,
audible as clicks and pops.


#6

callFunctionOnMessageThread(repaint());

although most surely you would make that

callFunctionOnMessageThread(audioProcessorEditorComponentInstance->repaint())

since the Editor component and the AudioProcessor are two different objects with two different lifecycles


#7

callFunctionOnMessageThread would be far worse than even calling repaint directly! It blocks until the function has finished running on the message thread.


#8

Okay, didn’t know that. Thanks for clearing that up!
So I guess this means you’d need to whip it up yourself (maybe pack it into a lambda and pass that to a callback queue on the message thread).


#9

So right now, when I call repaint on the audio callback's thread, I get a problem on CHECK_MESSAGE_MANAGER_IS_LOCKED inside internalRepaintUnchecked(...). 

How do I do processing on another thread as the data is available rather than on the audiocallback thread? BlockingQueue is what I've used before, although I hear that a RingBuffer may be appropriate here as well.

Either way, I'm not understanding how to trigger the message thread w/o using Timer???

-J


#10

The MessageManager class, which handles the message queue on any TopLevelWindow and derived classes, requires to be locked to ensure that only one strand of code at a time may access it (to prevent event loss, mostly).
You don’t need to trigger the message thread, you just add code to it that consumes the content of your message passing interface. It will get called by the OS, not by you.


#11

Have a look at http://www.juce.com/api/classCallbackMessage.html


#12

I think the biggest issue here is why are you dealing with the UI from the audio callback (or indeed any AudioProcessor methods) at all? Not only is it a bad idea for all the complicated threading and time-sensitive reasons already discussed, but what happens if your plugin is running without a UI? How do you even have access to the UI from your audio classes? If you've passed a reference or pointer in some way then it's almost guaranteed to cause a crash sooner or later. You should build your AudioProcesser with the assumption there can be no UI.

By far the simplest solution to this is to just keep a member variable in your AudioProcessor subclass, update it in the audio callback and then poll it from the AudioProcessorEditor, triggering a repaint if it's changed. I really wouldn't worry about the overhead of running the timer, it's pretty insignificant compared to all the processing that goes on in even a simple process block or all the event handling that goes into just moving the mouse over your UI.

Even if your timer only runs at 25fps it will look pretty instant in the UI. You'll be lucky to get up to a screen refresh of 60Hz let alone the 200-800Hz that the audio block can be run at. It's just not worth it.

Having said that, yes the cleanest way would be to add some message to a lock-free fifo and then pull the messages off it in the UI thread or even some other syncronising thread but to be honest probably isn't worth it unless you have a lot of things to syncronise. Even then you'll have to check to see if it already contains a pitch message and then replace it if necessary. The best thing to do would be to a have a pitchNeedsUpdate message which you can just set if it isn't already present and then unset once your UI has checked it.


#13

So, I do the post(), my messageCallback() calls repaint(), and it seems to "die". Not sure what's going on, have to look at the threads, but that's not looking promising.


#14

Agreed, I wasn't *trying* to do processing on the audio thread, its just not clear (outside of Timer), how to do otherwise. I'll probably do as you suggest next, which is what the examples have as well.

However, it seems like what I'm trying to do *should* be trivial. There will be a small delay from processing the signal, and I didn't want to add more by relying on the timer, over message passing. I was hoping someone would say, "there's already a BlockingQueue" or some-such.

Thanks for the input.


#15

Yes, it's a big talking point with many solutions, each with their pros and cons, you'll just have to see which one fits your needs best. I really would advise just doing the simplest non-blocking method first and then see if there are any problems. Polling for a UI element such as displaying a pitch will be fine, remember no one can see as quickly as your timer will refresh (unless of course you make it really slow).

If you're doing something more complex such as tracking pitch over time and you need an accurate history then you'll probably have to use some custom container. Most likely this will be based around an AbstractFifo which deals with all the positioning logic for you.


#16

@joelt

You might be interested in this forum thread:

http://www.juce.com/forum/topic/whats-best-practice-gui-change-notification


#17

So, if I start my own thread, how do I safely call repaint()?


#18

Wish there was documenation about the threading issues in JUCE. From the thread, I completely agree with theVinn. I may look at VFLib.


#19

The bad news: http://www.juce.com/forum/topic/vflib-significant-defects-found

 

You'll also find some build issues with VFLib under recent versions of Juce. You can hack it a bit and get it to compile. Send me a pm if you need a hand.


#20

I found the answer in the Demo code. Just need to use MessageBase and make the callbacks to my UI on that thread to notify them of pitch changes.

I'm also using the AbstractFifo as a ring buffer and the result is already noticeably better than the Timer approach. Its a bit more code, and I still need to clean up the extra thread, but its worth it.