Feature Request: AAX Meters


#1

AAX offers three optional meters that can be displayed in the PT interface. For dynamics plug-ins, a particularly useful one is the Gain-Reduction meter.

We received a lot of requests to implement this in our own plug-in, so we are currently maintaining our own JUCE branch. Here is the commit in which this was implemented:

https://github.com/UnfilteredAudio/JUCE/commit/5a91f615996d2c694ad38d583bf6a1d62e4967df

 

The explanation in the AAX SDK can be found at:

AAX_SDK/Documentation/Doxygen/output/html/a00296.html

("AAX SDK Manual"->"Additional AAX Features"->"Plug-in Meters")

 

It would be great if JUCE offered an easy implementation of this. I imagine a method like setGainReduction() that could be called once per buffer at the start of processBlock(), or perhaps a virtual AAX-only function (similar to AudioProcessorEditor::setControlHighlight) like the one that we implemented.


#2

Bump! Just looking to see if this is on the roadmap, or if we'll need to continue maintaining our branch.


#3

Thanks for bumping, we'd missed this one!

Seems like a reasonable suggestion, though we'd probably want to have a think about the implemention. Will try to have a look soon.

(BTW you seem to have gone to a lot of trouble to add "restrict" to the argument, but it actually won't have any effect when there's only a single array in the argument list. The flag indicates that two or more arrays don't refer to the same memory, so if there's only one array, it'll be ignored)


#4

Just a friendly bump to see if this is planned for 4.2 or 4.3. We’re really hoping to eliminate the need to maintain our fork for this one feature.


#5

Another friendly bump, would be great to benefit from trickyflemming’s work.

Thanks,
Dan


#6

Unfortunately, I’m not experiencing the level of responsiveness on the forum of which we were reassured at ADC.
Seeing as the last reply from Jules is 10 months old, and we are bumping the thread asking about the status, it would be polite to at least update us on the status.


#7

I think this one slipped through the net a bit… mainly because it’s one of those requests where the suggested changes aren’t suitable for us to use, so we’d need to re-think, implement and test the feature ourselves, which is time-consuming and usually just goes in the backlog.

(Fair point though, we should have posted an update on this thread to say what’s going on)


#8

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?


#9

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.


#10

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


#11

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.


#12

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.


#13

Bump, Any word on this addition yet?


#14

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();
}

#15

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


#16

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.


#17

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

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


#18

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?


#19

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


#20

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