I encountered a very weird behaviour in Pro Tools with a multi out AAX plug-in.
In the processBlock() callback, buffer.getWritePointer() could return identical addresses for channels (other than in the main bus) that are not currently used (typically routed to an Aux Input track).
If these extra channels are actually being used, their address is unique as expected:
I can’t tell if this is how it’s supposed to work but, if it is, I think this lacks proper documentation, unless there’s a way to check if a bus is used before attempting to rely on its pointers? Note that buffer.getNumChannels() always returns 16 here so this won’t tell anything.
This only happens with AAX in Pro Tools. I literally spent a day checking other DAWs and formats and the returned pointers are always valid wether or not the buses are used.
Here’s a PIP to reproduce the issue.
/*******************************************************************************
BEGIN_JUCE_PIP_METADATA
name: AAXMultiOutTest
version: 1.0.0
dependencies: juce_audio_basics, juce_audio_devices, juce_audio_plugin_client, juce_audio_processors,
juce_audio_utils, juce_audio_formats, juce_core, juce_data_structures,
juce_events, juce_graphics, juce_gui_basics, juce_gui_extra
exporters: xcode_mac, vs2022
moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
type: AudioProcessor
mainClass: AAXMultiOutTestProcessor
useLocalCopy: 1
pluginCharacteristics: pluginIsSynth
extraPluginFormats: AAX
END_JUCE_PIP_METADATA
*******************************************************************************/
#pragma once
#include <JuceHeader.h>
class AAXMultiOutTestProcessor : public juce::AudioProcessor
{
public:
//==============================================================================
AAXMultiOutTestProcessor() : juce::AudioProcessor (BusesProperties()
.withOutput ("Output #1", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #2", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #3", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #4", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #5", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #6", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #7", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output #8", juce::AudioChannelSet::stereo(), true))
{
}
bool isBusesLayoutSupported (const BusesLayout& layouts) const override
{
const auto& outputs = layouts.outputBuses;
return layouts.inputBuses.isEmpty()
&& 1 <= outputs.size()
&& std::all_of (outputs.begin(), outputs.end(), [] (const auto& bus)
{
return bus.isDisabled() || bus == juce::AudioChannelSet::stereo();
});
}
void processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages) override
{
juce::ScopedNoDenormals noDenormals;
buffer.clear();
float* prevWritePointer = nullptr;
for (int i = 0; i < buffer.getNumChannels(); ++i)
{
float* currWritePointer = buffer.getWritePointer (i);
jassert (currWritePointer != prevWritePointer); // uh oh... At least two channel write pointers are identical!
prevWritePointer = currWritePointer;
}
}
void prepareToPlay (double sampleRate, int samplesPerBlock) override { }
void releaseResources() override { }
juce::AudioProcessorEditor* createEditor() override { return new juce::GenericAudioProcessorEditor (*this); }
bool hasEditor() const override { return true; }
const juce::String getName() const override { return "AAX Multi Out Test"; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return false; }
bool isMidiEffect() const override { return false; }
double getTailLengthSeconds() const override { return 0; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int index) override { }
const juce::String getProgramName (int index) override { return "None"; }
void changeProgramName (int index, const juce::String& newName) override { }
void getStateInformation (juce::MemoryBlock& destData) override { }
void setStateInformation (const void* data, int sizeInBytes) override { }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AAXMultiOutTestProcessor)
};
Thanks for your feedback.


