How to access the GUI from within an AudioIODeviceCallback?

gui

#1

Hi all. What is the best way to access and manipulate GUI elements in the MainComponent from within an AudioDeviceCallback? Currently my MainContentComponent constructor creates an AudioIODevice, and passes a custom AudioIODeviceCallback to it when it starts. But how can the AudioIODeviceCallback know about and communicate with the MainComponent. If it helps, I want to create VU meters for the audio streams. Also, if it helps, here is the code in MainContentComponent()

    JackAudioIODeviceType* jackAudioIODeviceType = new JackAudioIODeviceType();
    jackAudioIODevice =  jackAudioIODeviceType->createDevice("foo_out", "foo_in");
    jackAudioIODevice->open(BigInteger(), BigInteger(), 0, 0);
    jackAudioIODevice->start(new JackAudioIODeviceCallback);

#2

Pass the pointer of your maincontentcomponent into the AudioIODeviceCallback, for example via the constructor.

jackAudioIODevice->start(new JackAudioIODeviceCallback(this));

You will probably need to use a forward declaration in the callback class’s header file and include your maincomponent header only in the callback’s cpp file.

(You could also do things more complicatedly with some kind of an observer pattern, which would get rid of the direct dependency between your audio callback and the main component class.)

When you have managed to do that, you will have the problem of how to notify the GUI of the changes in the audio so that you won’t be using the GUI directly from the audio thread. And also how to do that efficiently, since the audio thread should not be made to wait for long.


#3

You can also use my meter class:

You add a FFAU::MeterSource instance in your custom callback and call each block

meterSource.measureBlock (buffer);

And in your GUI add a FFAU::LevelMeter and connect it to that source:

levelMeter->setMeterSource (&meterSource);

…or just get inspiration…


#4

Great these are very helpful. Thanks! :grinning:


#5

Quick question about using ff_meters. Audio data “enters” audioDeviceCallback() as const float ** but to create an AudioBuffer I need a float *const* pointer. Any advice around how to safely/properly create an AudioBuffer to use as an argument to meterSource.measureBlock?


#6

Try something like :

AudioBuffer tempbuf((float* const*)inputChannelData,numChannels,numSamples);

That should compile but of course there can be some subtlety involved in your use case that won’t allow it to work at runtime…


#7

That worked great. Thanks! Though I could have sworn I tried an equivalent approach and was denied by the compiler. :confused:


#8

Good shout, there is no need to have the pointer non-const. I will add that restriction, then it should work without the const cast.

Thanks @smile_rko and @Xenakios


#9

It’s working well now and looks great. I’m seeing a significant increase in process usage ~18% when running the meter at full speed. If I lower to 1Hz refresh rate I get only ~1% increase. Might there be a way to trade level accuracy for speed? I don’t mind if the level reporting is a bit off, but I’d like it to feel quick.


#10

Are you testing that with a release build?


#11

Mmm. No it’s debug. I’ll check using release.


#12

what is your “full speed”? I usually run at 30 FPS, it is not video, so IMHO everything above is overkill.
Also, there is the RMS window that you can resize, I have this line in my prepareToPlay:

    outputMeterSource.resize (getTotalNumInputChannels(), (samplesPerBlock > 0 && sampleRate > 0) ? 0.2 * sampleRate / samplesPerBlock : 1);

…which means, it will smooth the RMS over 200ms. Otherwise the bars are very jumpy…


#13

I did a number of speed experiments and it seems like the performance is directly tied to the refresh rate and the number of LevelMeters. The drawing thread uses the most processing.

I have some questions. (1) When we call repaint() does it call paint() on all the child components? (2) Is ff_meter calling a general repaint of all components? (3) It appears that each LevelMeter has its own timer. I plan to have an arbitrary number of meters. Would it improve performance to create one master timer for all meters?

I’m pretty new to JUCE graphics, so I apologize if I’m getting anything wildly wrong here. And also, sorry for harping on the performance. I’m just trying to understand what factors affect it. Delving into the code has been a pleasure! It’s very well-written and I’m learning quite a bit.


#15

Yes that is the case

No, it only calls repaint for the meter component. But there might be optimisations in JUCE I am unaware of.

I actually was just gonna suggest this. I had problems in the past (not with the meters), when I have a repaint timer for the whole GUI and another for a single component inside that, I got them fighting and even stalling the host’s drawing… Maybe you have a similar situation?

The meters paint is completely decoupled, so there are no side effects. It can just repaint together with the parent, if that fits your setup.

Hope you get it solved…