Inconsistencies in AudioChannelSet surround formats?

AudioUnit "Quadraphonic" and AAX "Quad" formats have speaker channels L R Ls Rs but they get mapped to AudioChannelSet::quadraphonic() which has L R Sl Sr (so the surround speakers are at the sides instead of the rear). 

Similarly, AAX "7.0 DTS" format has speaker channels L C R Sl Sr Ls Rs (using JUCE's channel naming scheme) but maps to AudioChannelSet::create7point0() which has L R C Lfe Ls Rs Trl Trr (so instead of side and rear surrounds there are rear and top rear).

For AudioUnits, kAudioChannelLayoutTag_DTS_7_1 maps to AudioChannelSet::create7point1() but looks like it should be AudioChannelSet::createFront7point1().

Will these things be fixed in a future update, or will they definitely not change so I can work around them depending on the plug-in format?

Thanks,

Thanks for the heads up. I'll fix this.

In my case, all my ProTools surround plugins end up with discreteChannelSet (channel type 64 to 69).

Will this get fixed? Or do I use my ProTools wrong? (12.3.x HD 3rdParty).

And ProTools has two layouts, LCR... or for use with C|24 LRC... Are the channel numbers switched in that case? I understand that the AAX SDK has no different stem formats for that, so it should be ProTools responsibility to swap that...

Thanks for hints...

Hi daniel,

Thank you for the bug report. We are aware of this problem and working on a fix.

Fabian

Hi Fabian,

good to know, thanks :-)

Daniel

This is now fixed on the latest tip. JUCE will now re-map the channels in a way that the always have the same order in JUCE code. The order will now always be the same as the channel order in the AudioChannelSet::getAudioTypes() array. So you can simply iterate through this array to find the order of the channels.

I'm not very experencied with surround so I may have mixed up a channel here and there, so please keep testing and let me know if the current implementation requires any further changes.

Hi Fabian,

great, thanks. Unfortunately it doesn't work, at least as I am using it, see screenshot.

I pulled JUCE from origin, rebuilt Introjucer, created new plug-in and put this code into the AudioProcessorEditor::paint():

void SurroundTestPlugAudioProcessorEditor::paint (Graphics& g)
{
    g.fillAll (Colours::white);

    g.setColour (Colours::black);
    g.setFont (15.0f);
    
    if (processor.busArrangement.inputBuses.size() > 0) {
        AudioChannelSet input = processor.busArrangement.inputBuses.getUnchecked (0).channels;
        Array<AudioChannelSet::ChannelType> types = input.getChannelTypes();
        for (int i=0; i < input.size(); ++i) {
            g.drawFittedText (String (i+1) + ": " + input.getChannelTypeName(types.getUnchecked (i)),
                              3, i * 20, getWidth()-6, 20, Justification::left, 1);
        }
    }
}

Did I miss something? I left setPreferredBusArrangement() untouched...

Thanks for help,

Daniel

 

As far as I can see from your commit this does still not change the problem, let me elaborate:

 

You are using a bitmask to store the channel layout, this method however loses track of the arrangement.

For instance:

AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); }

equals

AudioChannelSet AudioChannelSet::stereoRL() { return AudioChannelSet ((1u << right) | (1u << left)); }

What I expect is "L R" and "R L", however both are stored as 0x3 so that when I try to recover the arrangement with this method


Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const
{
    Array<ChannelType> result;
    for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1))
        result.add (static_cast<ChannelType> (bit));
    return result;
}

I will always end up with "L R"

 

IMO you should use a different storage than the bitmask, maybe an array of enums?

 

Regards

Hi Daniel,

this is expected behaviour. If you do not overload the setPreferredBusArrangement method, then JUCE will assume that your plug-in is not really interested in the exact layout and advertise the plug-in as only having "discrete" channels (or multi-mono). To avoid this, overload the setPreferredBusArrangement method and reject or accept channel layouts by comparing them to the layouts defined in AudioChannelSet (i.e. NOT by only comparing the number of channels by using the size() method of AudioChannelSet). This will indicate to JUCE that the actual layout seems to be relevant to your plug-in. See for example the setPreferredBusArrangemtn in a new Surround plug-in example I added to JUCE yesterday (this plug-in will show the channel names in the plug-ins editor).

Let me know if this works for you.

 

Hi Fabian,

thanks for your time. I tried your code from the surround example, and it works for stereo and for 7.1 however it does NOT show up on the 5.1 bus, i.e. I cannot select it (I tested only those three as they are the most common cases afaik).

What I tried yesterday was the other way round to reject all discrete layouts:

bool SurroundTestPlugAudioProcessor::setPreferredBusArrangement (bool isInputBus, int busIndex, const AudioChannelSet &preferred)
{
    if (preferred.size() > 0 && preferred.getChannelTypes().getUnchecked(0) != AudioChannelSet::discreteChannel0) {
        return AudioProcessor::setPreferredBusArrangement(isInputBus, busIndex, preferred);
    }
    return false;
}

In that case the plugins show up in the selection but if I select the plugin, I get an DAE error -14018 and the plug in is inserted deactivated.

Can you explain why this doesn't work?

Thanks, Daniel

EDIT: I realized that your code has LFE as 4th channel (seen this on some movie decoders too), but protools has the LFE always as last channel. Maybe that is the reason...

Hi Fabian,

I changed the if statement to

if (preferred.size() > 0 && preferred.getChannelTypes().getUnchecked(0) < AudioChannelSet::discreteChannel0) {
    return AudioProcessor::setPreferredBusArrangement(isInputBus, busIndex, preferred);
}
return false;

And now I have the same behaviour like the code you wrote in the surround example. That means, I can select the plugin in several busses, but not in a 5.1 bus -> see screenshot.

Do you have an explanation for this?

Thanks,

Daniel

N.B. strange that the < operator and != lead to different results... < should be a subset of != in this case...

I'll have a look at this. I think there may be a problem with 5.1 surround at the moment. So many surround formats, so many DAWs to test...

Hi Fabian,

did you get a chance to figure why in ProTools the 5.1 bus format is no longer present?

I double checked also with todays latest tip and checked with ProTools 12.2.1 and 12.3.0 3P_Dev. All other formats are present.

And second issue:

In 5.0 the side channels are named as AudioChannelSet::leftSurround and rightSurround. One can argue about it, but as I see it in literature as well, the rear channels are the enhancements to the side surrounds. So at the moment the AudioChannelSet::surroundLeft in 5.x needs to be placed where the AudioChannel::sideLeft in 7.x is located.

It would simplify things a lot, if 5.x would return sideLeft and sideRight in getAudioTypes instead of surroundLeft and surroundRight.

When I look at AAX_EStemFormat it can't be determined how Avid's intention is, as Ls could mean left side as well as left surround...

Thanks for checking,

Daniel

@Chris: this does solve the problem. As the plug-in wrappers etc. will always re-order the channels in such a way that stereo will always be in the order of "L R" etc. There is no need to use "R L". For example, Pro Tool's LCR layout order is "L C R". However - as you point out correctly - the AudioChannelSet bitmask can only support the order "L C R". But this is not a problem as JUCE will automatically re-arrange the channel ptr to match the AudioChannelSet order of channels (i.e. "L C R"). In JUCE land, you should always be using the JUCE channel order. JUCE will then take care of converting to the various orders in it's backends.

Hi Daniel,

If I look at the 5.x layout definition used by Apple (in Logic, for example), it uses the CoreAudio Tag called "kAudioChannelLayoutTag_MPEG_5_0_B". You can find the definition for this in CoreAudio/CoreAudioTypes.h:

    // Some channel abbreviations used below:
    // L - left
    // R - right
    // C - center
    // Ls - left surround
    // Rs - right surround
    // Cs - center surround
    // Rls - rear left surround
    // Rrs - rear right surround
    // Lw - left wide
    // Rw - right wide
    // Lsd - left surround direct
    // Rsd - right surround direct
    // Lc - left center
    // Rc - right center
    // Ts - top surround
    // Vhl - vertical height left
    // Vhc - vertical height center
    // Vhr - vertical height right
    // Lt - left matrix total. for matrix encoded stereo.
    // Rt - right matrix total. for matrix encoded stereo.

.
.
.
    kAudioChannelLayoutTag_MPEG_5_0_B               = (118U<<16) | 5,                       //  L R Ls Rs C
    kAudioChannelLayoutTag_MPEG_5_1_A               = (121U<<16) | 6,                       //  L R C LFE Ls Rs

i.e. Apple seems to think that the 5.x layouts use surroundLeft & surroundRight. 

The broken 5.1 surround format in ProTools should now be fixed btw. Please check the latest tip.

Hi Fabian,

you are right, Apple uses surround left and surround right for 5.x and therefore the speakers are setup at usually 115 degrees from the center.

But unlike JUCE, for 7.x they introduce additional "Rls - rear left surround", which can be set from behind, sometimes 160 degrees, and the Left surround can stay where it was in 5.x

The Juce naming is unfortunate, because when for 7.x the AudioChannelSet::sideLeft is added, it is put where the AudioChannelSet::surroundLeft was, and the surround left is moved to a new position in the back.

When I implement now a placement for AudioChannelSet::leftSurround, I need to check if I am in 5.x and put it on the side, or if there is already the sideLeft sitting and move to the rear position.

The moment for confusion is, that "surround" is unspecific and in Apple-land it means side and in Juce land it means rear. The name doesn't matter, the issue is, that the 5.x needs to have rather the side speakers than the rear speakers.

I hope I could explain it in a understandable way...

b) The 5.1 in ProTools works now, thanks for fixing.

Best, daniel

N.B.: speakers setup according to the dolby website: http://www.dolby.com/us/en/guide/surround-sound-speaker-setup/5-1-setup.html

This surround layout stuff is really making my brain hurt. I think you know a lot more than me on this. Can you suggest the necessary changes to the code? I'll be happy to implement them.

Thanks for the compliments :-)

There are two options IMHO,

a) simply change the sideLeft against surroundLeft and right the same:

AudioChannelSet AudioChannelSet::create5point0()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft) | (1u << sideRight)); }
AudioChannelSet AudioChannelSet::create5point1()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft)  | (1u << sideRight)); }
AudioChannelSet AudioChannelSet::create6point0()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft)  | (1u << sideRight) | (1u << surround)); }
AudioChannelSet AudioChannelSet::create6point1()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass)  | (1u << sideLeft)  | (1u << sideRight) | (1u << surround)); }
AudioChannelSet AudioChannelSet::create7point0()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << sideLeft)  | (1u << sideRight) | (1u << surroundLeft) | (1u << surroundRight)); }
AudioChannelSet AudioChannelSet::create7point1()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << sideLeft)  | (1u << sideRight) | (1u << surroundLeft) | (1u << surroundRight)); }

Pro: no changes needed in the enums

Con: there will be people asking, why the surround format 5.x doesn't return the surroundLeft / surroundRight channels...

b) drop the name sideLeft and sideRight. Instead add the rearLeft and rearRight. This way the names are the same like in Apples logic.

This changes also the mapping in the VST wrapper.

AudioChannelSet AudioChannelSet::create5point0()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft) | (1u << surroundRight)); }
AudioChannelSet AudioChannelSet::create5point1()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft)  | (1u << surroundRight)); }
AudioChannelSet AudioChannelSet::create6point0()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft)  | (1u << surroundRight) | (1u << surround)); }
AudioChannelSet AudioChannelSet::create6point1()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass)  | (1u << surroundLeft)  | (1u << surroundRight) | (1u << surround)); }
AudioChannelSet AudioChannelSet::create7point0()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surroundLeft)  | (1u << surroundRight) | (1u << rearLeft) | (1u << rearRight)); }
AudioChannelSet AudioChannelSet::create7point1()      { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << subbass) | (1u << surroundLeft)  | (1u << surroundRight) | (1u << rearLeft) | (1u << rearRight)); }

in juce_VST3Common.h:

static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelType type) noexcept
{
    using namespace Steinberg::Vst;

    switch (type)
    {
        case AudioChannelSet::left:              return kSpeakerL;
        case AudioChannelSet::right:             return kSpeakerR;
        case AudioChannelSet::centre:            return kSpeakerC;
        case AudioChannelSet::subbass:           return kSpeakerLfe;
        case AudioChannelSet::surroundLeft:      return kSpeakerSl;
        case AudioChannelSet::surroundRight:     return kSpeakerSr;
        case AudioChannelSet::centreLeft:        return kSpeakerLc;
        case AudioChannelSet::centreRight:       return kSpeakerRc;
        case AudioChannelSet::surround:          return kSpeakerS;
        case AudioChannelSet::rearLeft:          return kSpeakerLs;
        case AudioChannelSet::rearRight:         return kSpeakerRs;
        case AudioChannelSet::topMiddle:         return kSpeakerTm;
        case AudioChannelSet::topFrontLeft:      return kSpeakerTfl;
        case AudioChannelSet::topFrontCentre:    return kSpeakerTfc;
        case AudioChannelSet::topFrontRight:     return kSpeakerTfr;
        case AudioChannelSet::topRearLeft:       return kSpeakerTrl;
        case AudioChannelSet::topRearCentre:     return kSpeakerTrc;
        case AudioChannelSet::topRearRight:      return kSpeakerTrr;
        case AudioChannelSet::subbass2:          return kSpeakerLfe2;
        default: break;
    }

    return 0;
}

static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speaker type) noexcept
{
    using namespace Steinberg::Vst;

    switch (type)
    {
        case kSpeakerL:     return AudioChannelSet::left;
        case kSpeakerR:     return AudioChannelSet::right;
        case kSpeakerC:     return AudioChannelSet::centre;
        case kSpeakerLfe:   return AudioChannelSet::subbass;
        case kSpeakerLs:    return AudioChannelSet::rearLeft;
        case kSpeakerRs:    return AudioChannelSet::rearRight;
        case kSpeakerLc:    return AudioChannelSet::centreLeft;
        case kSpeakerRc:    return AudioChannelSet::centreRight;
        case kSpeakerS:     return AudioChannelSet::surround;
        case kSpeakerSl:    return AudioChannelSet::surroundLeft;
        case kSpeakerSr:    return AudioChannelSet::surroundRight;
        case kSpeakerTm:    return AudioChannelSet::topMiddle;
        case kSpeakerTfl:   return AudioChannelSet::topFrontLeft;
        case kSpeakerTfc:   return AudioChannelSet::topFrontCentre;
        case kSpeakerTfr:   return AudioChannelSet::topFrontRight;
        case kSpeakerTrl:   return AudioChannelSet::topRearLeft;
        case kSpeakerTrc:   return AudioChannelSet::topRearCentre;
        case kSpeakerTrr:   return AudioChannelSet::topRearRight;
        case kSpeakerLfe2:  return AudioChannelSet::subbass2;
        default: break;
    }

    return AudioChannelSet::unknown;
}

Pro: channels named surround represent the more dominant surround channels

Con: enum names changes -> compilation breaks / people accustomed to the VST names might look for the sideLeft and sideRight names.

Personally I would go for version b, sticking with apples names...

And for double check AAX: they have no channel name enum, so nothing can break. And in the comments in AAX_Enums.h they simply have different abbreviations in 5.x and 7.x:

    AAX_eStemFormat_5_0            = AAX_STEM_FORMAT ( 5,     5 ),    ///<  L C R Ls Rs
    AAX_eStemFormat_5_1            = AAX_STEM_FORMAT ( 6,     6 ),    ///<  L C R Ls Rs LFE
    [...]
    AAX_eStemFormat_7_0_DTS        = AAX_STEM_FORMAT ( 11,     7 ),    ///<  L C R Lss Rss Lsr Rsr
    AAX_eStemFormat_7_1_DTS        = AAX_STEM_FORMAT ( 12,     8 ),    ///<  L C R Lss Rss Lsr Rsr LFE

Ls becomes either Lss or Lsr, we can chose ourselves... The definition is only in the create5point1 etc.

This really sounds like an endless mountain of problems and discussions, I don't envy you for that job. Keep your head up, it's gonna be good :-)

Best, daniel

 

I started to refactor variant b, and I realize, that the Apple definitions are not consistent. Looking at the CoreAudioTypes.h in the comment you quoted, they add a rear left surround and rear right surround. But when I look at the channel names enum, the rear left surround is gone, instead there is a kAudioChannelLabel_LeftSurround and a kAudioChannelLabel_LeftSurroundDirect. The direct one is commented to refer to the side channels. At least for the types present in WAVE files. In the later numbers there is also a kAudioChannelLabel_RearSurroundLeft.

Looking further to the predefined layouts the only thing I could deduce was, that the rear channels always appear after the side surrounds. And in doubt the rear ones are missing, never the side ones.

Long story short, the least changes and fitting all purpose is what I proposed as version a) returning sideLeft instead surroundLeft

I think this fit's best the common setups, but seems there are different views on that.

...what a mess ;-)

OK. I've just pushed option a). Thanks for your help. Let me know if you require any other changes.