AAX (Mono->Stereo) Bypass Distortion/Noise


#1

Hey jucers,

I’ve noticed some unintentional behaviour when bypassing an AAX plugin, with Mono to Stereo channel configuration AND a sidechain input. As a test, I reproduced this bug with the NoiseGate example plug-in to verify the bug.

When bypassing, Distortion/Noise becomes present in the output of the DAW.

REPRO STEPS, using the NoiseGate demo (turn your volume down):

  • Allow Mono->Stereo channel configuration in NoiseGate demo app.
  • Open AAX plugin, and then Bypass in Pro Tools.
  • Notice Distortion/Noise.

Fix:

  • Override ProcessBlockBypassed function in the AudioProcessor class to allow audio to pass through.

#2

Update:

The ‘fix’ posted above didn’t solve the issue, and I notice that configuring any of the sample plugin examples to mono->stereo results in sound out of one channel still (left channel).

Again, this is only an issue with AAX plugins.


#3

IMHO this is the thing to do, but you need to do it right. You cannot ignore the ChannelTypes and only copy index to index in that case…
I wrote a generic bypass method (rewrite from some prototypes I did), but didn’t test in detail, because I am not at my DAW right now, but it should work like that:

void processBlockBypassed (AudioSampleBuffer& buffer, MidiBuffer&) override
{
    if (! busArrangement.inputBuses.isEmpty() && ! busArrangement.outputBuses.isEmpty()) {
        AudioChannelSet input  = busArrangement.inputBuses.getUnchecked (0).channels;
        AudioChannelSet output = busArrangement.outputBuses.getUnchecked (0).channels;

        if (input.size() == 1) {
            // mono: copy to all channels
            const int inputIndex = busArrangement.getChannelIndexInProcessBlockBuffer (true, 0, 0);
            const float* inputReadPointer = buffer.getReadPointer (inputIndex);
            for (int i=0; i < output.size(); ++i) {
                const int outputIndex = busArrangement.getChannelIndexInProcessBlockBuffer (false, 0, i);
                buffer.copyFrom (outputIndex, 0, inputReadPointer, buffer.getNumSamples());
            }
        }
        else {
            // copy to matching channel type or clear
            Array<int> toBeCleared;
            for (int i=0; i < output.size(); ++i) {
                const AudioChannelSet::ChannelType outputType = output.getTypeOfChannel (i);
                const int inputIndex = input.getChannelIndexForType (outputType);
                const int outputIndex = busArrangement.getChannelIndexInProcessBlockBuffer (false, 0, i);
                if (isPositiveAndBelow(inputIndex, input.size())) {
                    const float* inputReadPointer = buffer.getReadPointer (inputIndex);
                    buffer.copyFrom (outputIndex, 0, inputReadPointer, buffer.getNumSamples());
                }
                else {
                    toBeCleared.add (outputIndex);
                }
            }
            for (int c : toBeCleared) {
                buffer.clear (c, 0, buffer.getNumSamples());
            }
        }
    }
}

This is, because the indexing of channels depends on wrapper and eventually on the host (didn’t read into the source)
The more simple the channel layout is, the better the chance that it fits by chance. But it might break anytime, if you assume, the indices would match from input to output…


#4

I greatly appreciate you taking the time to reply to this. I’ll be implementing this today, and will let you know if there are any changes that I make along the way.

Cheers!


#5

This cleared out the distortion/noise. Thanks so much.

Currently with Mono->Stereo settings, nothing is coming out of the right channel. Has anyone experienced this before? This issue still applies only to AAX.


#6

If I use the following code block for stereo output, I can copy the first buffer to both channels. This doesn’t give me a stereo sound, rather it gives me a single copied stereo channel, sent to both channels (essentially mono out of both channels).

if (! busArrangement.outputBuses.isEmpty()) { AudioChannelSet output = busArrangement.outputBuses.getUnchecked (0).channels; for (int i=0; i < output.size(); ++i) { const int outputIndex = busArrangement.getChannelIndexInProcessBlockBuffer (false, 0, i); buffer.copyFrom (outputIndex, 0, outputBuf, buffer.getNumSamples()); } }

if I change up the code to do something like this, then only audio comes out of the first channel. I’ve tried varieties of this all day and have seemingly gotten nowhere with it:

`if (! busArrangement.outputBuses.isEmpty())
{
AudioChannelSet output = busArrangement.outputBuses.getUnchecked (0).channels;
for (int i=0; i < output.size(); ++i) {
const int outputIndex = busArrangement.getChannelIndexInProcessBlockBuffer (false, 0, i);

        if(i == 1)
             buffer.copyFrom (outputIndex, 0, outputBufL, buffer.getNumSamples());
        else 
             buffer.copyFrom (outputIndex, 0, outputBufR, buffer.getNumSamples());
    }
}`

Thoughts anyone? Rather stumped. Again, this is only an issue with aax :confused:


#7

Right, because if you use it on a mono track, you’re lacking the information of the difference left-right aka side signal. You can for sure make one up with psycho acoustics, like an exciter or any other stereo enhancer… But I would suggest to leave that decision to the user, if he wants that.
Why you usually copy the mono signal is that in a stereo setup you don’t have an corresponding channel to mono.

You are misunderstanding the parameters of copyFrom. outputIndex is the buffer where the samples are copied to. I don’t know what outputBufL means in your case, but in the copyFrom call this needs to be an input buffer.

This is again a problem, because you cannot rely on what kind of channel is at index 1.
If you want to pick the left output channel it would read:
if (i == output.getChannelIndexOfType (AudioChannelSet::left))

Hope this helps…


#8

@fabian
Shouldn’t the default processBlockBypassed be fixed, to handle such situations right?


#9

I propose to add something like this:

void AudioProcessor::processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer&)
{
    for (int ch = getMainBusNumInputChannels(); ch < getTotalNumOutputChannels(); ++ch)
        buffer.clear (ch, 0, buffer.getNumSamples());
}

This would clear all channels not on the main bus and pass through all inputs. I’ve pushed this to the develop branch.

I understand that it might be useful for some plug-ins to copy the input to several channels (for example copy the mono input to both left and right stereo outputs) but this is really an implementation detail of the plug-in. If you want this behaviour then you should be overriding processBlockBypassed. Obviously, JUCE should never produce noise by default so the above fix is definitely necessary.

Also, I see so much confusion about the channel ordering in JUCE. Calling getChannelIndexOfType is not necessary if you know which format will be coming into your plug-in. The first channel, for example, of a bus buffer will be the first channel type in the AudioChannelSet::ChannelType list which is a member of the buffer layout. For example, if you have an ambisonic input, then the first channel in the buffer will be ambisonicW as ambisonicW is a channel of the ambisonic layout and ambisonicW appears first in the AudioChannelSet::ChannelType list.


#10

…yes, but my plugins allow several output channel configurations, so I rather rely on getChannelIndexOfType().
Also the OPs configuration was mono to stereo, so AudioChannelSet::mono becomes AudioChannelSet::left, which should not be taken as normal. It is an reinterpretation of the channels content.

And I agree, that this copying should not be the default behaviour. There are many valid options.
It is just one option which works for me very well and I think so it does for many others…