Skip to content

Commit

Permalink
VST3: Add support for new wide/Atmos speaker layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
reuk committed Jun 11, 2024
1 parent 34e454e commit a42a498
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 35 deletions.
43 changes: 42 additions & 1 deletion BREAKING_CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ HarfBuzz Typeface implementation. New support for automatic font fallback
will be introduced in JUCE 8, and this will obviate much of the need for
CustomTypeface.

>>>>>>> 94454123d6 (Typeface: Implement platform typefaces using Harfbuzz hb_font_t)

## Change

Expand Down Expand Up @@ -428,6 +427,48 @@ the licensing situation and encourages the creation of more open source software
without impacting personal use of the JUCE framework.


# Version 7.0.12

## Change

The function AudioChannelSet::create9point0point4, along with variants for
9.1.4, 9.0.6, and 9.1.6, used to correspond to VST3 layouts k90_4, k91_4,
k90_6, and k91_6 respectively. These functions now correspond to k90_4_W,
k91_4_W, k90_6_W, and k91_6_W respectively.

**Possible Issues**

VST3 plugins that used these AudioChannelSet layouts to specify initial bus
layouts, or to validate layouts in isBusesLayoutSupported, will now behave
differently.

For example, if the host wants to check whether the k90_4 layout is supported,
previously isBusesLayoutSupported() would have received the layout created by
create9point0point4(), but will now receive the layout created by
create9point0point4ITU().

**Workaround**

If you already have special-case handling for specific surround layouts,
e.g. to enable or disable them in isBusesLayoutSupported(), you may need to
add cases to handle the new AudioChannelSet::create*ITU() layout variants.

**Rationale**

Previously, the VST3 SDK only contained ITU higher-order surround layouts, but
the higher-order layouts specified in JUCE used Atmos speaker positions rather
than ITU speaker positions. This meant that JUCE had to remap speaker layouts
between Atmos/ITU formats when communicating with VST3 plugins. This was
confusing, as it required that the meaning of some channels was changed during
the conversion.

In newer versions of the VST3 SDK, new "wide" left and right speaker
definitions are available, allowing both ITU and Atmos surround layouts to be
represented. The change in JUCE surfaces this distinction to the user, allowing
them to determine e.g. whether the host has requested an ITU or an Atmos
layout, and to handle these cases separately if necessary.


# Version 7.0.10

## Change
Expand Down
44 changes: 31 additions & 13 deletions modules/juce_audio_basics/buffers/juce_AudioChannelSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,33 +268,51 @@ class JUCE_API AudioChannelSet
*/
static AudioChannelSet JUCE_CALLTYPE create7point1point6();

/** Creates a set for a 9.0.4 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight).
/** Creates a set for a 9.0.4 Atmos surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight).
Is equivalent to: k90_4 (VST3), AAX_eStemFormat_9_0_4 (AAX).
Is equivalent to: k90_4_W (VST3), AAX_eStemFormat_9_0_4 (AAX).
@see create9point0point4ITU()
*/
static AudioChannelSet JUCE_CALLTYPE create9point0point4();

/** Creates a set for a 9.1.4 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight).
/** Creates a set for a 9.1.4 Atmos surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topRearLeft, topRearRight).
Is equivalent to: k91_4_W (VST3), AAX_eStemFormat_9_1_4 (AAX).
Is equivalent to: k91_4 (VST3), AAX_eStemFormat_9_1_4 (AAX).
@see create9point1point4ITU()
*/
static AudioChannelSet JUCE_CALLTYPE create9point1point4();

/** Creates a set for a 9.0.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight).
/** Creates a set for a 9.0.6 Atmos surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight).
Is equivalent to: k90_6 (VST3), AAX_eStemFormat_9_0_6 (AAX).
Is equivalent to: k90_6_W (VST3), AAX_eStemFormat_9_0_6 (AAX).
@see create9point0point6ITU()
*/
static AudioChannelSet JUCE_CALLTYPE create9point0point6();

/** Creates a set for a 9.1.6 surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight).
/** Creates a set for a 9.1.6 Atmos surround setup (left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, wideLeft, wideRight, topFrontLeft, topFrontRight, topSideLeft, topSideRight, topRearLeft, topRearRight).
Older versions of the VST3 SDK only supported ITU versions of the 9.0.4, 9.1.4, 9.0.6, and
9.1.6 layouts, which have the front-channel ordering "L Lc C Rc R".
To maintain the correct relative channel ordering, JUCE would perform the following mapping:
L -> wideLeft, Lc -> left, Rc -> right, R -> wideRight
The version of the VST3 SDK bundled with JUCE now supports Atmos versions of the above
layouts, which have the front-channel ordering "Lw L C R Rw". This order matches the
JUCE ordering, so no remapping is required.
create9point0point4(), create9point1point4(), create9point0point6(), and
create9point1point6() now correspond to the VST3 k90_4_W, k91_4_W, k90_6_W, and k91_6_W
Atmos layouts respectively.
If you need to support the old ITU layouts, use create9point0point4ITU(),
create9point1point4ITU(), create9point0point6ITU(), and create9point1point6ITU() instead.
Note that the VST3 layout arranges the front speakers "L Lc C Rc R", but the JUCE layout
uses the arrangement "wideLeft left centre right wideRight". To maintain the relative
positions of the speakers, the channels will be remapped accordingly. This means that the
VST3 host's "L" channel will be received on a JUCE plugin's "wideLeft" channel, the
"Lc" channel will be received on a JUCE plugin's "left" channel, and so on.
Is equivalent to: k91_6_W (VST3), kAudioChannelLayoutTag_Atmos_9_1_6 (CoreAudio).
Is equivalent to: k91_6 (VST3), kAudioChannelLayoutTag_Atmos_9_1_6 (CoreAudio).
@see create9point1point6ITU()
*/
static AudioChannelSet JUCE_CALLTYPE create9point1point6();

Expand Down
20 changes: 13 additions & 7 deletions modules/juce_audio_processors/format_types/juce_VST3Common.h
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ static std::optional<Steinberg::Vst::Speaker> getSpeakerType (const AudioChannel
case AudioChannelSet::bottomRearLeft: return Steinberg::Vst::kSpeakerBrl;
case AudioChannelSet::bottomRearCentre: return Steinberg::Vst::kSpeakerBrc;
case AudioChannelSet::bottomRearRight: return Steinberg::Vst::kSpeakerBrr;
case AudioChannelSet::wideLeft: return Steinberg::Vst::kSpeakerLw;
case AudioChannelSet::wideRight: return Steinberg::Vst::kSpeakerRw;

case AudioChannelSet::discreteChannel0: return Steinberg::Vst::kSpeakerM;

Expand Down Expand Up @@ -311,8 +313,6 @@ static std::optional<Steinberg::Vst::Speaker> getSpeakerType (const AudioChannel
case AudioChannelSet::ambisonicACN61:
case AudioChannelSet::ambisonicACN62:
case AudioChannelSet::ambisonicACN63:
case AudioChannelSet::wideLeft:
case AudioChannelSet::wideRight:
case AudioChannelSet::unknown:
break;
}
Expand Down Expand Up @@ -383,6 +383,8 @@ static std::optional<AudioChannelSet::ChannelType> getChannelType (Steinberg::Vs
case Steinberg::Vst::kSpeakerBrl: return AudioChannelSet::bottomRearLeft;
case Steinberg::Vst::kSpeakerBrc: return AudioChannelSet::bottomRearCentre;
case Steinberg::Vst::kSpeakerBrr: return AudioChannelSet::bottomRearRight;
case Steinberg::Vst::kSpeakerLw: return AudioChannelSet::wideLeft;
case Steinberg::Vst::kSpeakerRw: return AudioChannelSet::wideRight;
}

return {};
Expand Down Expand Up @@ -433,11 +435,15 @@ namespace detail
{ k71_6, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } },
{ k70_6, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } },

// The VST3 layout uses 'left/right' and 'left-of-center/right-of-center', but the JUCE layout uses 'left/right' and 'wide-left/wide-right'.
{ k91_4, { X::wideLeft, X::wideRight, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } },
{ k90_4, { X::wideLeft, X::wideRight, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } },
{ k91_6, { X::wideLeft, X::wideRight, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } },
{ k90_6, { X::wideLeft, X::wideRight, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::left, X::right, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } },
{ k90_4_W, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::wideLeft, X::wideRight } },
{ k91_4_W, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::wideLeft, X::wideRight } },
{ k90_6_W, { X::left, X::right, X::centre, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight, X::wideLeft, X::wideRight } },
{ k91_6_W, { X::left, X::right, X::centre, X::LFE, X::leftSurroundRear, X::rightSurroundRear, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight, X::wideLeft, X::wideRight } },

{ k90_4, { X::left, X::right, X::centre, X::leftSurround, X::rightSurround, X::leftCentre, X::rightCentre, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } },
{ k91_4, { X::left, X::right, X::centre, X::LFE, X::leftSurround, X::rightSurround, X::leftCentre, X::rightCentre, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight } },
{ k90_6, { X::left, X::right, X::centre, X::leftSurround, X::rightSurround, X::leftCentre, X::rightCentre, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } },
{ k91_6, { X::left, X::right, X::centre, X::LFE, X::leftSurround, X::rightSurround, X::leftCentre, X::rightCentre, X::leftSurroundSide, X::rightSurroundSide, X::topFrontLeft, X::topFrontRight, X::topRearLeft, X::topRearRight, X::topSideLeft, X::topSideRight } },
};

#if JUCE_DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ class VST3PluginFormatTests final : public UnitTest
expect (map.getJuceChannelForVst3Channel (1) == 1); // R -> right
}

beginTest ("ChannelMapping for a 9.1.6 bus remaps the channels appropriately");
beginTest ("ChannelMapping for a k91_6 bus remaps the channels appropriately");
{
ChannelMapping map (AudioChannelSet::create9point1point6());
ChannelMapping map (AudioChannelSet::create9point1point6ITU());
expect (map.size() == 16);

// VST3 order is:
Expand All @@ -80,6 +80,64 @@ class VST3PluginFormatTests final : public UnitTest
// Tsl
// Tsr
// JUCE order is:
// left
// right
// centre
// LFE
// leftSurround
// rightSurround
// leftCentre
// rightCentre
// leftSurroundSide
// rightSurroundSide
// topFrontLeft
// topRearRight
// topRearLeft
// topRearRight
// topSideLeft
// topSideRight

expect (map.getJuceChannelForVst3Channel (0) == 0); // L -> left
expect (map.getJuceChannelForVst3Channel (1) == 1); // R -> right
expect (map.getJuceChannelForVst3Channel (2) == 2); // C -> centre
expect (map.getJuceChannelForVst3Channel (3) == 3); // Lfe -> LFE
expect (map.getJuceChannelForVst3Channel (4) == 4); // Ls -> leftSurround
expect (map.getJuceChannelForVst3Channel (5) == 5); // Rs -> rightSurround
expect (map.getJuceChannelForVst3Channel (6) == 6); // Lc -> leftCentre
expect (map.getJuceChannelForVst3Channel (7) == 7); // Rc -> rightCentre
expect (map.getJuceChannelForVst3Channel (8) == 8); // Sl -> leftSurroundSide
expect (map.getJuceChannelForVst3Channel (9) == 9); // Sr -> rightSurroundSide
expect (map.getJuceChannelForVst3Channel (10) == 10); // Tfl -> topFrontLeft
expect (map.getJuceChannelForVst3Channel (11) == 11); // Tfr -> topFrontRight
expect (map.getJuceChannelForVst3Channel (12) == 12); // Trl -> topRearLeft
expect (map.getJuceChannelForVst3Channel (13) == 13); // Trr -> topRearRight
expect (map.getJuceChannelForVst3Channel (14) == 14); // Tsl -> topSideLeft
expect (map.getJuceChannelForVst3Channel (15) == 15); // Tsr -> topSideRight
}

beginTest ("ChannelMapping for a k91_6_W bus remaps the channels appropriately");
{
ChannelMapping map (AudioChannelSet::create9point1point6());
expect (map.size() == 16);

// VST3 order is:
// L
// R
// C
// Lfe
// Ls
// Rs
// Sl
// Sr
// Tfl
// Tfr
// Trl
// Trr
// Tsl
// Tsr
// Lw
// Rw
// JUCE order is:
// Left
// Right
// Centre
Expand All @@ -97,22 +155,22 @@ class VST3PluginFormatTests final : public UnitTest
// Top Side Left
// Top Side Right

expect (map.getJuceChannelForVst3Channel (0) == 12); // L -> wideLeft
expect (map.getJuceChannelForVst3Channel (1) == 13); // R -> wideRight
expect (map.getJuceChannelForVst3Channel (0) == 0); // L -> left
expect (map.getJuceChannelForVst3Channel (1) == 1); // R -> right
expect (map.getJuceChannelForVst3Channel (2) == 2); // C -> centre
expect (map.getJuceChannelForVst3Channel (3) == 3); // Lfe -> LFE
expect (map.getJuceChannelForVst3Channel (4) == 10); // Ls -> leftSurroundRear
expect (map.getJuceChannelForVst3Channel (5) == 11); // Rs -> rightSurroundRear
expect (map.getJuceChannelForVst3Channel (6) == 0); // Lc -> left
expect (map.getJuceChannelForVst3Channel (7) == 1); // Rc -> right
expect (map.getJuceChannelForVst3Channel (8) == 4); // Sl -> leftSurroundSide
expect (map.getJuceChannelForVst3Channel (9) == 5); // Sl -> leftSurroundSide
expect (map.getJuceChannelForVst3Channel (10) == 6); // Tfl -> topFrontLeft
expect (map.getJuceChannelForVst3Channel (11) == 7); // Tfr -> topFrontRight
expect (map.getJuceChannelForVst3Channel (12) == 8); // Trl -> topRearLeft
expect (map.getJuceChannelForVst3Channel (13) == 9); // Trr -> topRearRight
expect (map.getJuceChannelForVst3Channel (14) == 14); // Tsl -> topSideLeft
expect (map.getJuceChannelForVst3Channel (15) == 15); // Tsr -> topSideRight
expect (map.getJuceChannelForVst3Channel (6) == 4); // Sl -> leftSurroundSide
expect (map.getJuceChannelForVst3Channel (7) == 5); // Sr -> rightSurroundSide
expect (map.getJuceChannelForVst3Channel (8) == 6); // Tfl -> topFrontLeft
expect (map.getJuceChannelForVst3Channel (9) == 7); // Tfr -> topFrontRight
expect (map.getJuceChannelForVst3Channel (10) == 8); // Trl -> topRearLeft
expect (map.getJuceChannelForVst3Channel (11) == 9); // Trr -> topRearRight
expect (map.getJuceChannelForVst3Channel (12) == 14); // Tsl -> topSideLeft
expect (map.getJuceChannelForVst3Channel (13) == 15); // Tsr -> topSideRight
expect (map.getJuceChannelForVst3Channel (14) == 12); // Lw -> wideLeft
expect (map.getJuceChannelForVst3Channel (15) == 13); // Lw -> wideRight
}

const auto blockSize = 128;
Expand Down

0 comments on commit a42a498

Please sign in to comment.