Sending signal/events from audio to GUI thread?

Inspired by @cpenny’s sharing an example using atomics, I thought I’d share a thread-safe way to trigger a simple peak light in the GUI in response to an audio signal. In this example, it’s just triggering off the input signal, but obviously you could set the value of the peakLevel member from any point in the DSP code. (If all you did want was the peak level of the input, it would be simpler to use AudioBuffer::getMagnitude rather than iterate through the buffer as this code does.)

Note that this does not do any “peak decay” processing – but if you are running your GUI Timer at 30 Hz, that’s an average of 33ms per analysis window, which should be long enough to see even if the peak is only momentary. And this approach also guarantees that if there IS a peak, it won’t be missed by the display (since it’s the display timer itself that resets the peak value).

class MyAudioProcessorClass : public AudioProcessor, etc...
{
private:
    Atomic<float> peakLevel;
    
public:
	float getAndResetPeak()
	{
	    // The exchange() method atomically grabs the old peak level, and resets it to 0
		return peakLevel.exchange (0.0f);
	}

    void processBlock (AudioBuffer<float> &buffer, MidiBuffer &midiMessages) override
    {        
        for (int i = 0; i < buffer.getNumSamples(); i++)
    	{
    		const float audioLevelAbs = fabsf (buffer.getSample (0, i));  // this only looks at 1st buffer channel
    	
    		if (audioLevelAbs > peakLevel.get())
    			peakLevel.set (audioLevelAbs);
    	}
    }

}

MyPluginGuiClass : private Timer, public Component, etc...
{
	const float peakThreshold = Decibels::decibelsToGain<float> (-12.0f);
	ImageButton peakLight;  // set this up in constructor with normal/down images, and setClickingTogglesState (false)
    MyAudioProcessorClass& processor;

	// include in constructor: startTimerHz (30);

    void timerCallback() override
    {
    	const bool lightOn = processor.getAndResetPeak() > peakThreshold;
    	peakLight.setState (lightOn ? Button::buttonDown : Button::buttonNormal);
    	repaint(); 
	}
}