Sidechain is not silent as expected (AU, Logic X 10.2.2)


#1

As per subject,

For a plug-in that supports sidechain, when the Sidechain input in Logic is set to None, on the sidechain bus I get the same data that is fed to the main bus rather than silence (which is what I’d expect when None is selected, right?)

steps to reproduce:

  1. Grab the latest JUCE tip (which was af66027 at the moment I detected this, but I see no work has been done in this area since)

  2. Build the NoiseGate example plug-in with a printout of the content of the channels received by processBlock() (details below)

  3. Launch debugging with Logic X

  4. Create an empty project and add one track with actual stereo data in it

  5. Insert the Noise Gate plug-in in the track

  6. Check that “None” is selected in the “Sidechain” combo in the top right corner of the plug-in window

  7. Play the track

  8. Observe from the printout that the plug-in is getting 4 channels:

  • the first two are the main input channels (stereo)

  • the following two (the sidechain stereo channels) are an exact replica of the first two, rather than containing only silence as expected

    samp:2048, chan: 4 | ch0: 0.1304 | ch1: 0.0824 | ch2: 0.1304 | ch3: 0.0824 // call #10
    samp:2048, chan: 4 | ch0: 0.1003 | ch1: 0.0688 | ch2: 0.1003 | ch3: 0.0688 // call #11
    samp:2048, chan: 4 | ch0: 0.1154 | ch1: 0.0840 | ch2: 0.1154 | ch3: 0.0840 // call #12
    samp:2048, chan: 4 | ch0: 0.0994 | ch1: 0.0753 | ch2: 0.0994 | ch3: 0.0753 // call #13
    samp:2048, chan: 4 | ch0: 0.0864 | ch1: 0.0672 | ch2: 0.0864 | ch3: 0.0672 // call #14
    samp:2048, chan: 4 | ch0: 0.0765 | ch1: 0.0613 | ch2: 0.0765 | ch3: 0.0613 // call #15
    samp:2048, chan: 4 | ch0: 0.0806 | ch1: 0.0583 | ch2: 0.0806 | ch3: 0.0583 // call #16
    samp:2048, chan: 4 | ch0: 0.0789 | ch1: 0.0550 | ch2: 0.0789 | ch3: 0.0550 // call #17
    samp:2048, chan: 4 | ch0: 0.0549 | ch1: 0.0606 | ch2: 0.0549 | ch3: 0.0606 // call #18
    samp:2048, chan: 4 | ch0: 0.0743 | ch1: 0.0544 | ch2: 0.0743 | ch3: 0.0544 // call #19
    samp:2048, chan: 4 | ch0: 0.9598 | ch1: 0.9406 | ch2: 0.9598 | ch3: 0.9406 // call #20

Why is it so? What’s happening here?

Details regarding the printout:

I simply have made a little helper function that takes an AudioSampleBuffer and prints the magnitude of each of its channels in columns, so that I can DBG() that and get a nice stream organized in columns that shows the evolution of the single channels content over time, as seen above.

The function is (feel free to use it):

String dbgAudioBuffer (const AudioSampleBuffer& buffer, const String& tag)
{
    String s;
    const int numSamples = buffer.getNumSamples();
    const int numChannels = buffer.getNumChannels();

    s << String::formatted ("samp:%4d, chan:%2d", numSamples, numChannels);

    for (int channelIndex = 0; channelIndex < numChannels; ++channelIndex)
    {
        s << String::formatted (" | ch%d: ", channelIndex);

        const float magnitude = buffer.getMagnitude (channelIndex, 0, numSamples);
 
        if (magnitude != 0.0f) s << String (magnitude, 4);
        else                   s << "------";
    }

    s << " // " << tag;
     
    return s;
}

and I have called it at the very beginning of the processBlock():

void processBlock (AudioSampleBuffer& buffer, MidiBuffer&) override
{
    static int tag = 0;
    DBG (dbgAudioBuffer (buffer, "call #" + String (tag++)));
    
    ....

#2

I have investigated this issue further

What is being fed to the sidechain is not a copy of the data sent to the main bus, but rather it is the same data because the read pointers are the same: the read pointer for channel 0 in main bus (bus 0) is reused for channel 0 in sidechain (bus 1), and the same happens for channel 1 of the two buses

I have changed my printout function a little to display that, and I get the following

samp:2048, chan: 4 | ch0: 0.1712 | ch1: 0.0823 | ch2: *ch0   | ch3: *ch1   // call #2200
samp:2048, chan: 4 | ch0: 0.1304 | ch1: 0.0824 | ch2: *ch0   | ch3: *ch1   // call #2201
samp:2048, chan: 4 | ch0: 0.1003 | ch1: 0.0688 | ch2: *ch0   | ch3: *ch1   // call #2202
samp:2048, chan: 4 | ch0: 0.1154 | ch1: 0.0840 | ch2: *ch0   | ch3: *ch1   // call #2203
samp:2048, chan: 4 | ch0: 0.0994 | ch1: 0.0753 | ch2: *ch0   | ch3: *ch1   // call #2204
samp:2048, chan: 4 | ch0: 0.0864 | ch1: 0.0672 | ch2: *ch0   | ch3: *ch1   // call #2205
samp:2048, chan: 4 | ch0: 0.0765 | ch1: 0.0613 | ch2: *ch0   | ch3: *ch1   // call #2206
samp:2048, chan: 4 | ch0: 0.0806 | ch1: 0.0583 | ch2: *ch0   | ch3: *ch1   // call #2207
samp:2048, chan: 4 | ch0: 0.0789 | ch1: 0.0550 | ch2: *ch0   | ch3: *ch1   // call #2208
samp:2048, chan: 4 | ch0: 0.0549 | ch1: 0.0606 | ch2: *ch0   | ch3: *ch1   // call #2209
samp:2048, chan: 4 | ch0: 0.0743 | ch1: 0.0544 | ch2: *ch0   | ch3: *ch1   // call #2210

The notation *ch0 and *ch1 means that those data have the same read pointer of the mentioned channel (ch0 and ch1 in this case)

If interested, the modified printout function that supports this notation is copied below

String dbgAudioBuffer (const AudioSampleBuffer& buffer, const String& tag)
{
    String s;
    
    const int numSamples = buffer.getNumSamples();
    const int numChannels = buffer.getNumChannels();

    s << String::formatted ("samp:%4d, chan:%2d", numSamples, numChannels);

    Array <const float*> readPointers;

    for (int channelIndex = 0; channelIndex < numChannels; ++channelIndex)
    {
        s << String::formatted (" | ch%d: ", channelIndex);

        const float* readPointer = buffer.getReadPointer (channelIndex);
        const int referredPreviousChannelIndex = readPointers.indexOf (readPointer);

        if (referredPreviousChannelIndex >= 0)
        {
            s << String::formatted ("*ch%-3d", referredPreviousChannelIndex);
        }
        else
        {
            const float magnitude = buffer.getMagnitude (channelIndex, 0, numSamples);
            if (magnitude != 0.0f) s << String (magnitude, 4);
            else s << "0 ";
        }
        readPointers.add (readPointer);
    }
    s << " // " << tag;

    return s;
}

#3

This problem is still present with the latest 4.2.1 develop

Again, steps to reproduce:

  1. Grab the latest JUCE develop tip (b9aaa88 at the moment of writing)

  2. Build the NoiseGate example plug-in

  3. Launch debugging with Logic X

  4. Create an empty project and add one track with actual stereo data in it

  5. Insert the Noise Gate plug-in in the track

  6. Check that “None” is selected in the “Sidechain” combo in the top right corner of the plug-in window

  7. Play the track

Expected result:

The plug-in should receive 4 channels in total:

  • the first pair containing actual audio data (main bus)
  • the second pair containing only silence (only zeroes)

Observed result

The plug-in receives 4 channels in total, but

  • the first pair contains actual audio data (main bus)
  • the second pair contains the same data as the first pair.

Something must have changed since 4.1 because in my previous posts, the data being fed to the sidechain had the same read pointers as the channels for the main bus.
In this case (4.2.1) the pointers are different but the data is exactly the same, and still is not right because the sidechain when “None” is selected should be silent. Right?


#4

Just expierienced the same. Is this the default behavior, or bug in the wrapper? @fabian

Also the plugin is opened with 4 channels, even if the main track is stereo und the side-chain is mono.

Using the 4.2.2 dev from yesterday


#5

This is expected behaviour: you can use other third-party AUs and many behave the same way. It’s technically possible to detect “None” being selected as a sidechain by checking if Logic passes identical pointers for both buffers. Unfortunately, this is identical to the behaviour of Logic, when the user selects the same track for both sidechain and the main bus I tried various third-party plug-ins and none of them was able to either detect that None is selected - or function properly if the user selects the same track for both sidechain and main input (they simply ignore the sidechain data). This lead me to conclude that this is a limitation of Logic. But I’m happy to be proven wrong.

I needed to make a decision on which behaviour is more important: let the plug-in detect no sidechain being selected - or having plug-ins function properly if the sidechain is the same track as the main bus. I opted for the latter as I believe this is more future proof if Logic ever decides to fix this problem.

In JUCE 4.1, I used to pass the same pointers to the processBlock function. But I re-worked this to copy the data instead as this caused problems with some developer’s plugin if the pointers were identical - especially as the main bus buffers are also used for the main out.

Well the main bus and the sidechain is opened in stereo (so total is 4 channels) if the main bus is in stereo (regardless if the sidechain is mono) Logic seems to open the sidechain as well.


Issues with the multi-bus support
#6

I can totally understand why it would be convenient in some situations to have silence on the side chain when there is no input selected, but the behaviour described in the first post is an accurate model of hardware right?

In a ‘real’ noise gate, there are two parts to the circuit: the gain stage and the control circuit. When nothing is plugged into the side chain, then the input signal goes into the control circuit. When you plug something in then you physically break that connection and feef the side chain signal in instead.


#7

(FYI: Fabian is on holiday for a couple of weeks, so you may have to wait for a reply here… I can’t really help as I didn’t have much to do with the side-chaining)


#8

That’s just how Logic behaves.
Our solution (not only ours, others do the same) is to have an additional button on the plugin’s UI to enable using the side-chain.
That’s the “SC” button in the lower-right here:

Cheers, Yair


#9

Its is for my utterly important to know, if the sidechain is activated in the host. So, the informations seems to be there, is there any chance to get this info inside the processor class?


#10

No the information is not there. It’s not possible to distinguish between a deactivated sidechain or an activated sidechain using the same source as the main bus.


#11

Thats okay, it would be okay if i now if “deactivated sidechain OR an activated sidechain using the same source” is used, any idea how this could be implemented?


#12

I’m a bit hesitant to add a new API for this as there already is a similar API with the AudioChannelSet::disabled method. I could of course disable the bus if the wrapper detects the same buffer being used for the sidechain and the main bus, but users may genuinely want to route the same track to the sidechain and the main bus. Maybe yet another JUCE macro could be used to switch between both behaviours.

Can you suggest a better solution?