Feature Request: AAX Meters

I’ve been looking on how this could be extended to other plug-in formats. AU, AUv3 and VST3 use read-only (only the plug-in itself can write to them) output parameters which are then displayed as meters. Maybe we could do the same in JUCE - you can mark certain AudioProcessorParameters as read-only meters + meter-type with some flags. Would something like this work for you?

Thanks for the update.

I’m not sure it would make sense in all cases to actually display these meter parameters in other, non-AAX formats; since currently PT is the only environment which actually supports these meters (AFAIK). It’s possible that it could be useful to present these read-only parameters to the user in other DAWs as well, but the decision would probably vary depending on the specific plug-in.

They are only shown in the generic view of the plug-ins. Cubase and Logic support this, for example.

Yes, I realize that. Just not sure it would make sense to clutter the generic view. Probably not a big deal, though, if it’s easier to design it this way.

I’m a little confused.
Only Pro Tools has “reduction” meters used for AAX .

Without too much complications I think it should be similar to automation<->component handling here:
https://www.juce.com/doc/classAudioProcessorEditor#a5f1501ac7caa5a12cab2d05bd5d020fe

So it should have something like:
virtual bool AudioProcessor::hasGainReductionMeters()

For the buffer it might be worth adding:

virtual void AudioProcessor::processBlock(AudioBuffer< double > & buffer, AudioBuffer< double > & grBuffer, MidiBuffer & midiMessages )

So default implementation will call the abstract processBlock.

That way the implementation as with AudioProcessorEditor::getControllerParameterIndex wouldn’t require any changes for current project.
another benefit from the virtual call if commented well, it shouldn’t be used when plug-in is with different formats or hasGainReductionMeters() is false. saving CPU power.

Bump, Any word on this addition yet?

1 Like

OK. You can find an implementation of this on develop. Here is an example of a gain reduction meter in the NoiseGate example. Simply replace the NoiseGate.cpp file with the following contents:

/*
 ==============================================================================

 This file is part of the JUCE library.
 Copyright (c) 2015 - ROLI Ltd.

 Permission is granted to use this software under the terms of either:
 a) the GPL v2 (or any later version)
 b) the Affero GPL v3

 Details of these licenses can be found at: www.gnu.org/licenses

 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 ------------------------------------------------------------------------------

 To release a closed-source product which uses JUCE, commercial licenses are
 available: visit www.juce.com for more information.

 ==============================================================================
 */

#include "../JuceLibraryCode/JuceHeader.h"
#include "../../GenericEditor.h"

class NoiseGate  : public AudioProcessor
{
public:
	//==============================================================================
	//==============================================================================
	NoiseGate()
		: AudioProcessor (BusesProperties().withInput  ("Input",     AudioChannelSet::stereo())
											 .withOutput ("Output",    AudioChannelSet::stereo())
											 .withInput  ("Sidechain", AudioChannelSet::stereo()))
	{
		addParameter (threshold = new AudioParameterFloat ("threshold", "Threshold", 0.0f, 1.0f, 0.5f));
		addParameter (alpha  = new AudioParameterFloat ("alpha",  "Alpha",   0.0f, 1.0f, 0.8f));
		addParameter (gainReduction = new AudioParameterFloat ("reduction", "Gain Reduction",
															   NormalisableRange<float> (0.0f, 1.0f), 0.0f,
															   "", AudioProcessorParameter::expanderGateGainReductionMeter));
	}

	~NoiseGate() {}

	//==============================================================================
	bool isBusesLayoutSupported (const BusesLayout& layouts) const override
	{
		// the sidechain can take any layout, the main bus needs to be the same on the input and output
		return (layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet() &&
				(! layouts.getMainInputChannelSet().isDisabled()));
	}

	//==============================================================================
	void prepareToPlay (double /*sampleRate*/, int /*maxBlockSize*/) override { lowPassCoeff = 0.0f; sampleCountDown = 0; }
	void releaseResources() override                                          {}

	void processBlock (AudioSampleBuffer& buffer, MidiBuffer&) override
	{
		AudioSampleBuffer mainInputOutput = getBusBuffer(buffer, true, 0);
		AudioSampleBuffer sideChainInput  = getBusBuffer(buffer, true, 1);

		float alphaCopy = *alpha;
		float thresholdCopy = *threshold;
		float maxReduction = 0.0;

		for (int j = 0; j < buffer.getNumSamples(); ++j)
		{
			float mixedSamples = 0.0f;
			for (int i = 0; i < sideChainInput.getNumChannels(); ++i)
				mixedSamples += sideChainInput.getReadPointer (i) [j];

			mixedSamples /= static_cast<float> (sideChainInput.getNumChannels());
			lowPassCoeff = (alphaCopy * lowPassCoeff) + ((1.0f - alphaCopy) * mixedSamples);

			if (lowPassCoeff >= thresholdCopy)
				sampleCountDown = (int) getSampleRate();

			// very in-effective way of doing this
			for (int i = 0; i < mainInputOutput.getNumChannels(); ++i)
			{
				const float inputSample = *mainInputOutput.getReadPointer (i, j);
				const bool gateIsOpen = (sampleCountDown > 0);

				*mainInputOutput.getWritePointer (i, j) = gateIsOpen ? inputSample : 0.0f;
				maxReduction                            = jmax (maxReduction, gateIsOpen ? 0.0f : std::fabs (inputSample));
			}

			if (sampleCountDown > 0)
				--sampleCountDown;
		}

		gainReduction->setValueNotifyingHost (maxReduction);
	}

	//==============================================================================
	AudioProcessorEditor* createEditor() override            { return new GenericEditor (*this); }
	bool hasEditor() const override                          { return true; }
	const String getName() const override                    { return "NoiseGate"; }
	bool acceptsMidi() const override                        { return false; }
	bool producesMidi() const override                       { return false; }
	double getTailLengthSeconds() const override             { return 0.0; }
	int getNumPrograms() override                            { return 1; }
	int getCurrentProgram() override                         { return 0; }
	void setCurrentProgram (int) override                    {}
	const String getProgramName (int) override               { return ""; }
	void changeProgramName (int, const String&) override     {}
	bool isVST2() const noexcept                             { return (wrapperType == wrapperType_VST); }

	//==============================================================================
	void getStateInformation (MemoryBlock& destData) override
	{
		MemoryOutputStream stream (destData, true);

		stream.writeFloat (*threshold);
		stream.writeFloat (*alpha);
	}

	void setStateInformation (const void* data, int sizeInBytes) override
	{
		MemoryInputStream stream (data, static_cast<size_t> (sizeInBytes), false);

		threshold->setValueNotifyingHost (stream.readFloat());
		alpha->setValueNotifyingHost (stream.readFloat());
	}

	enum
	{
		kVST2MaxChannels = 8
	};

private:
	//==============================================================================
	AudioParameterFloat* threshold;
	AudioParameterFloat* alpha;
	AudioParameterFloat* gainReduction;
	int sampleCountDown;

	float lowPassCoeff;

	//==============================================================================
	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NoiseGate)
};

//==============================================================================
// This creates new instances of the plugin..
AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
	return new NoiseGate();
}
4 Likes

I’m just now seeing this reply. This is great I’ll try it out.

Thanks @fabian. Everything works great in AAX.

For Devs, just change the category to compressorLimiterGainReductionMeter, if using Compressor or Limiter.

Just one thing I noticed in Pro Tools. When I dragged the NoiseGate to a new insert below, the parameters default back.

How? Where are they displayed?
(Don’t seem to be able to reproduce this…)

OK. forget it. Found it. Sorry to bother.

We’ve run into some issues implementing AAX meters using @fabian’s above example code:

  1. Certain hosts (like Ableton Live) show the metering parameter in the automation lane (for VST). That automation is constantly written when the DAW is recording. Should metering be disabled entirely for VST?

  2. Adding AAX meters in JUCE gives an assert in Pro Tools for us: “Condition: !plugInAlgContextContainsEmptyFields”.

Anyone else experiencing these problems?

There are VST hosts which do the metering correctly in the generic view (and not in the automation track). You can always not add your meters for certain hosts (PluginHostType().isAbletonLive()) or plugin types (PluginHostType::getPluginLoadedAs).
 
 

Thank you for reporting! This should be fixed on develop now with the commit shown below. Please let me know if this works for you:
https://github.com/WeAreROLI/JUCE/commit/ab13359c6673cd1ac8aaab41a28481695ea22565

1 Like

Thank you! That fixed the assert for us. Cheers!

Just out of curiosity.
We’re going to release a plug-in with AudioProcessorValueTreeState.

I think in current state it’s impossible to add Gain Reduction parameter since you need a category (just as it was with meta-parameter before latest commit).

Adding it manually goes into an assertion as it’s not a proper type when AudioProcessorValueTreeState is in charge.

Any thoughts? thanks!

1 Like

Bump. I am also in need of AAX Meter functionality with AudioProcessorValueTreeState.

Thanks!

A fix for this is now on develop.

Thanks!

Thank you! …can’t wait to try it out!

P.S. - as the meters designed to allow reporting for other formats also,
might be worth adding PreSonus GR API it is now also supported by REAPER.

4 Likes

I know the JUCE Team is busy as always.
It would be nice to add the PreSonus extensions into JUCE as they’re public domain and quite easy to do.

Here is a POC of adding Gain Reduction with very few lines. (it might need to change the parameter methods as this is with JUCE 4.3.x).

https://github.com/soundradix/JUCE/commit/6905c59655ef9e98d32383504531173a8dd56e03

But any plug-in using meters would get it up-and-running out of the box.

3 Likes