Skip to content

[VST3] Sidechain input is declared incorrectly and not forwarded to the DSP #155

Description

@spkfb

[VST3] Sidechain input is declared incorrectly and not forwarded to the DSP

Summary

The current VST3 wrapper does not provide functional sidechain support.

Although YUP can declare multiple audio input buses, secondary input buses are currently:

  1. declared as regular main input buses instead of VST3 auxiliary buses;
  2. not forwarded to the audio processor when there is no matching output bus;
  3. not represented separately in AudioProcessContext.

As a result, a DAW may not expose the secondary input as a sidechain, and even when the host sends sidechain audio, the DSP cannot access it.

Severity

P0 / Blocking

This prevents the implementation of standard sidechain-based processors such as compressors, gates, dynamic equalizers, envelope followers, and external-key detectors.

Current behavior

Secondary inputs are declared as main buses

The VST3 wrapper currently registers input buses using:

addAudioInput (
    bus.getName(),
    speakerArrForChannels (bus.getNumChannels()));

Because no bus type is specified, the Steinberg API uses Vst::kMain.

This means that all input buses are exposed as main inputs instead of declaring secondary inputs as Vst::kAux.

Sidechain audio is discarded during processing

The processing code copies input buses to output buses using matching bus indices:

for (int busIdx = 0;
     busIdx < std::min (data.numInputs, data.numOutputs);
     ++busIdx)
{
    // Copy input bus to matching output bus.
}

For a typical sidechain plugin layout:

Input 0  Main stereo input
Input 1  Stereo sidechain input
Output 0 Main stereo output

std::min(data.numInputs, data.numOutputs) evaluates to 1.

Only Input 0 is copied. Input 1, containing the sidechain signal, is never read.

The DSP context exposes only one in-place audio buffer

AudioProcessContext currently exposes a single audio buffer:

AudioBuffer<FloatType>& audio;

It does not provide access to:

  • individual input buses;
  • individual output buses;
  • the main input;
  • auxiliary inputs;
  • sidechain activation state.

Even if the wrapper preserved the sidechain samples, the processor API currently has no way to consume them.

Expected behavior

A plugin should be able to declare the following VST3 layout:

Input 0  kMain  Stereo  Active by default
Input 1  kAux   Stereo  Inactive by default
Output 0 kMain  Stereo  Active by default

During processing, the DSP should receive separate views for:

  • the main input;
  • the sidechain input;
  • the main output.

The sidechain buffer must remain accessible even though it has no corresponding output bus.

Proposed solution

1. Add a role to AudioBus

Introduce an explicit bus role:

class AudioBus
{
public:
    enum class Role
    {
        Main,
        Auxiliary
    };

    AudioBus (
        Type type,
        Direction direction,
        String name,
        int numChannels,
        Role role = Role::Main,
        bool defaultActive = true);

    Role getRole() const noexcept;
    bool isDefaultActive() const noexcept;
};

The generic Auxiliary role can be used for sidechains, reference inputs, or other non-main audio buses.

2. Map auxiliary buses to Vst::kAux

The VST3 wrapper should register buses using their explicit role:

const auto vstBusType =
    bus.getRole() == AudioBus::Role::Auxiliary
        ? Steinberg::Vst::kAux
        : Steinberg::Vst::kMain;

const auto flags =
    bus.isDefaultActive()
        ? Steinberg::Vst::BusInfo::kDefaultActive
        : 0;

addAudioInput (
    bus.getName(),
    speakerArrForChannels (bus.getNumChannels()),
    vstBusType,
    flags);

Main buses must be registered before auxiliary buses.

3. Replace the single-buffer processing model with bus-aware views

AudioProcessContext should expose separate input and output buses:

template <typename SampleType>
struct AudioProcessContext
{
    Span<AudioBusBufferView<const SampleType>> inputs;
    Span<AudioBusBufferView<SampleType>> outputs;

    int numSamples = 0;

    auto getMainInput() const;
    auto getMainOutput() const;
    auto getAuxiliaryInput (int index) const;
};

A sidechain compressor could then use:

const auto mainInput = context.getMainInput();
const auto sidechain = context.getAuxiliaryInput (0);
const auto mainOutput = context.getMainOutput();

compressor.process (
    mainInput,
    sidechain,
    mainOutput,
    context.numSamples);

4. Handle inactive or unavailable buses safely

The wrapper must support:

  • inactive sidechain buses;
  • null channel pointers;
  • missing buffers;
  • mono and stereo auxiliary inputs;
  • VST3 silence flags;
  • runtime bus activation and deactivation.

An inactive sidechain should provide deterministic silence to the DSP and must never cause a crash.

Reproduction layout

Create a processor with:

Main input:      Stereo
Sidechain input: Stereo
Main output:     Stereo

Load it in a VST3 host and route another track to the sidechain input.

Actual result

  • The secondary input may not appear as a standard sidechain.
  • The sidechain signal is not available to the processor.
  • Only the main input reaches the DSP.

Expected result

  • The host exposes the auxiliary input as a sidechain.
  • Sidechain samples are available separately from the main input.
  • The processor can use the sidechain signal to control gain reduction.

Acceptance criteria

  • Secondary input buses can be explicitly declared as auxiliary buses.
  • Auxiliary VST3 input buses use Vst::kAux.
  • Auxiliary buses are inactive by default unless explicitly configured otherwise.
  • Main and auxiliary input samples are preserved independently.
  • Input buses without corresponding output buses are not discarded.
  • The DSP API exposes main and auxiliary buses separately.
  • Inactive sidechain buses do not crash and produce silence.
  • Runtime bus activation and deactivation are supported.
  • Mono main input with mono sidechain is tested.
  • Stereo main input with mono sidechain is tested.
  • Stereo main input with stereo sidechain is tested.
  • The implementation passes the Steinberg VST3 Validator.
  • Sidechain routing is manually validated in at least REAPER, Ableton Live, Cubase or Nuendo, and Studio One.

Regression tests

Add an automated test using:

Main input: constant signal
Sidechain:  impulse or sine wave
Expected:   gain reduction occurs only when the sidechain signal is present

The test should also verify that disabling the auxiliary bus restores the unkeyed processing behavior without reallocating or recreating the processor.

Additional note

Changing only the VST3 bus type from kMain to kAux is not sufficient.

That change may make the sidechain input visible in the host, but the audio data will still be discarded before reaching the processor. The fix must cover both VST3 bus declaration and the internal multibus processing architecture.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions