VST3: set VSTSpeakerArrangements to match track

This fixes some plugins that require a valid Bus/SpeakerArrangement
setting to set the available per bus I/O channel-count.
(e.g. Altiverb). Most other VST3s initially announce all
available channels.

TODO: check that this does not break plugins with optional
busses (sidechain in, multi-out instruments)
This commit is contained in:
Robin Gareus 2025-11-29 20:23:55 +01:00
parent 1190dd3a3e
commit be445a8079
No known key found for this signature in database
GPG key ID: A090BCE02CF57F04
6 changed files with 237 additions and 35 deletions

View file

@ -315,6 +315,7 @@ public:
virtual bool reconfigure_io (ChanCount /*in*/, ChanCount /*aux_in*/, ChanCount /*out*/) { return true; } virtual bool reconfigure_io (ChanCount /*in*/, ChanCount /*aux_in*/, ChanCount /*out*/) { return true; }
virtual bool match_variable_io (ChanCount& /*in*/, ChanCount& /*aux_in*/, ChanCount& /*out*/) { return false; } virtual bool match_variable_io (ChanCount& /*in*/, ChanCount& /*aux_in*/, ChanCount& /*out*/) { return false; }
virtual void request_bus_layout (ChanCount const& /*in*/, ChanCount const& /*aux_in*/, ChanCount const& /*out*/);
virtual ChanCount output_streams () const; virtual ChanCount output_streams () const;
virtual ChanCount input_streams () const; virtual ChanCount input_streams () const;
@ -532,6 +533,11 @@ public:
/* @return true if the plugin can change its inputs or outputs on demand. */ /* @return true if the plugin can change its inputs or outputs on demand. */
virtual bool reconfigurable_io () const { return false; } virtual bool reconfigurable_io () const { return false; }
/* @return true if the plugin has configurable busses but no AU style reconfigureable I/O (VST3)
* implies request_bus_layout ()
*/
virtual bool variable_bus_layout () const { return false; }
/* max [re]configurable outputs (if finite, 0 otherwise) */ /* max [re]configurable outputs (if finite, 0 otherwise) */
virtual uint32_t max_configurable_outputs () const virtual uint32_t max_configurable_outputs () const
{ {

View file

@ -202,6 +202,7 @@ public:
void set_owner (ARDOUR::SessionObject* o); void set_owner (ARDOUR::SessionObject* o);
void set_non_realtime (bool); void set_non_realtime (bool);
void request_bus_layout (uint32_t main_in, uint32_t aux_in, uint32_t main_out);
void enable_io (std::vector<bool> const&, std::vector<bool> const&, bool force = false); void enable_io (std::vector<bool> const&, std::vector<bool> const&, bool force = false);
void process (float** ins, float** outs, uint32_t n_samples); void process (float** ins, float** outs, uint32_t n_samples);
@ -242,8 +243,10 @@ private:
bool disconnect_components (); bool disconnect_components ();
bool update_processor (); bool update_processor ();
void query_io_config ();
int32 count_channels (Vst::MediaType, Vst::BusDirection, Vst::BusType); int32 count_channels (Vst::MediaType, Vst::BusDirection, Vst::BusType);
bool evoral_to_vst3 (Vst::Event&, Evoral::Event<samplepos_t> const&, int32_t); bool evoral_to_vst3 (Vst::Event&, Evoral::Event<samplepos_t> const&, int32_t);
void update_shadow_data (); void update_shadow_data ();
@ -403,6 +406,12 @@ public:
IOPortDescription describe_io_port (DataType dt, bool input, uint32_t id) const; IOPortDescription describe_io_port (DataType dt, bool input, uint32_t id) const;
PluginOutputConfiguration possible_output () const; PluginOutputConfiguration possible_output () const;
void request_bus_layout (ChanCount const& /*in*/, ChanCount const& /*aux_in*/, ChanCount const& /*out*/);
bool reconfigure_io (ChanCount /*in*/, ChanCount /*aux_in*/, ChanCount /*out*/);
ChanCount output_streams () const;
ChanCount input_streams () const;
void set_automation_control (uint32_t, std::shared_ptr<ARDOUR::AutomationControl>); void set_automation_control (uint32_t, std::shared_ptr<ARDOUR::AutomationControl>);
std::string state_node_name () const std::string state_node_name () const
@ -487,6 +496,8 @@ public:
bool is_instrument () const; bool is_instrument () const;
PBD::Searchpath preset_search_path () const; PBD::Searchpath preset_search_path () const;
bool variable_bus_layout () const { return true; }
std::optional<bool> has_editor; std::optional<bool> has_editor;
std::shared_ptr<VST3PluginModule> m; std::shared_ptr<VST3PluginModule> m;

View file

@ -46,6 +46,7 @@
#include "ardour/port.h" #include "ardour/port.h"
#include "ardour/session.h" #include "ardour/session.h"
#include "ardour/types.h" #include "ardour/types.h"
#include "ardour/vst3_plugin.h"
#include "pbd/i18n.h" #include "pbd/i18n.h"
@ -2100,6 +2101,10 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
/* NB. When resolving impossible matches, "replicate 1 time" is valid. /* NB. When resolving impossible matches, "replicate 1 time" is valid.
* e.g. add a MIDI filter (1 MIDI in, 1 MIDI out) after some audio plugin */ * e.g. add a MIDI filter (1 MIDI in, 1 MIDI out) after some audio plugin */
assert (!_plugins.front()->get_info()->reconfigurable_io ()); assert (!_plugins.front()->get_info()->reconfigurable_io ());
/* VST3 */
for (auto const& p : _plugins) {
p->reconfigure_io (natural_input_streams (), aux_in, natural_output_streams ());
}
break; break;
default: default:
@ -2260,6 +2265,24 @@ PluginInsert::configure_io (ChanCount in, ChanCount out)
bool bool
PluginInsert::can_support_io_configuration (const ChanCount& in, ChanCount& out) PluginInsert::can_support_io_configuration (const ChanCount& in, ChanCount& out)
{ {
if (plugin()->get_info ()->variable_bus_layout ()) {
ChanCount input_streams = natural_input_streams ();
ChanCount sc;
if (_sidechain) {
_sidechain->can_support_io_configuration (sc, sc);
}
for (auto const& p : _plugins) {
if (_custom_cfg) {
p->request_bus_layout (_custom_sinks, sc, _custom_sinks);
} else {
p->request_bus_layout (in, sc, in);
}
}
if (input_streams != natural_input_streams ()) {
mapping_changed ();
}
}
if (_sidechain) { if (_sidechain) {
_sidechain->can_support_io_configuration (in, out); // never fails, sets "out" _sidechain->can_support_io_configuration (in, out); // never fails, sets "out"
} }

View file

@ -32,6 +32,7 @@
#include "ardour/readonly_control.h" #include "ardour/readonly_control.h"
#include "ardour/region_fx_plugin.h" #include "ardour/region_fx_plugin.h"
#include "ardour/session.h" #include "ardour/session.h"
#include "ardour/vst3_plugin.h"
using namespace std; using namespace std;
using namespace ARDOUR; using namespace ARDOUR;
@ -835,6 +836,12 @@ RegionFxPlugin::can_support_io_configuration (const ChanCount& in, ChanCount& ou
out = ChanCount::min (in, out); out = ChanCount::min (in, out);
return true; return true;
} }
if (plugin()->get_info ()->variable_bus_layout ()) {
ChanCount sc;
for (auto const& p : _plugins) {
p->request_bus_layout (in, sc, in);
}
}
return private_can_support_io_configuration (in, out).method != Impossible; return private_can_support_io_configuration (in, out).method != Impossible;
} }

View file

@ -98,22 +98,6 @@ VST3Plugin::init ()
_plug->OnResizeView.connect_same_thread (_connections, std::bind (&VST3Plugin::forward_resize_view, this, _1, _2)); _plug->OnResizeView.connect_same_thread (_connections, std::bind (&VST3Plugin::forward_resize_view, this, _1, _2));
_plug->OnParameterChange.connect_same_thread (_connections, std::bind (&VST3Plugin::parameter_change_handler, this, _1, _2, _3)); _plug->OnParameterChange.connect_same_thread (_connections, std::bind (&VST3Plugin::parameter_change_handler, this, _1, _2, _3));
_plug->OnProcessorChange.connect_same_thread (_connections, [&](ARDOUR::RouteProcessorChange const& rpc) { Plugin::send_processors_changed (rpc); }); _plug->OnProcessorChange.connect_same_thread (_connections, [&](ARDOUR::RouteProcessorChange const& rpc) { Plugin::send_processors_changed (rpc); });
/* assume only default active busses are connected */
for (auto const& abi : _plug->bus_info_in ()) {
for (int32_t i = 0; i < abi.second.n_chn; ++i) {
_connected_inputs.push_back (abi.second.dflt);
}
}
for (auto const& abi : _plug->bus_info_out ()) {
for (int32_t i = 0; i < abi.second.n_chn; ++i) {
_connected_outputs.push_back (abi.second.dflt);
}
}
/* pre-configure from GUI thread */
_plug->enable_io (_connected_inputs, _connected_outputs, true);
} }
void void
@ -337,6 +321,47 @@ VST3Plugin::possible_output () const
#endif #endif
} }
ChanCount
VST3Plugin::input_streams () const
{
ChanCount cc;
cc.set_audio (_plug->n_audio_inputs (true));
cc.set_midi (_plug->n_midi_inputs ());
return cc;
}
ChanCount
VST3Plugin::output_streams () const
{
ChanCount cc;
cc.set_audio (_plug->n_audio_outputs (true));
cc.set_midi (_plug->n_midi_outputs ());
return cc;
}
void
VST3Plugin::request_bus_layout (ChanCount const& in, ChanCount const& aux_in, ChanCount const& out)
{
_plug->request_bus_layout (in.n_audio (), aux_in.n_audio (), out.n_audio ());
}
bool
VST3Plugin::reconfigure_io (ChanCount in, ChanCount aux_in, ChanCount out)
{
_connected_inputs.clear ();
_connected_inputs.resize (in.n_audio () + aux_in.n_audio ());
_connected_inputs.flip ();
_connected_outputs.clear ();
_connected_outputs.resize (out.n_audio ());
_connected_outputs.flip ();
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3Plugin::reconfigure_io %1 %2 %3\n", in, aux_in, out));
_plug->enable_io (_connected_inputs, _connected_outputs);
return true;
}
/* **************************************************************************** /* ****************************************************************************
* Plugin UI * Plugin UI
*/ */
@ -838,7 +863,7 @@ VST3Plugin::connect_and_run (BufferSet& bufs,
} }
} }
_plug->enable_io (_connected_inputs, _connected_outputs); //_plug->enable_io (_connected_inputs, _connected_outputs); // XXX
_plug->process (ins, outs, n_samples); _plug->process (ins, outs, n_samples);
@ -1303,13 +1328,13 @@ VST3PI::VST3PI (std::shared_ptr<ARDOUR::VST3PluginModule> m, std::string unique_
_busbuf_in.resize (_n_bus_in); _busbuf_in.resize (_n_bus_in);
_busbuf_out.resize (_n_bus_out); _busbuf_out.resize (_n_bus_out);
/* do not re-order, _io_name is build in sequence */ query_io_config ();
_n_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kMain);
_n_aux_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kAux); if (n_audio_inputs () == 0 && n_audio_outputs () == 0 && n_midi_inputs () == 0 && n_midi_outputs () == 0) {
_n_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kMain); /* see also vst3_scan discover_vst3 -- assume stereo by default */
_n_aux_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kAux); request_bus_layout (2, 0, 2);
_n_midi_inputs = count_channels (Vst::kEvent, Vst::kInput, Vst::kMain); query_io_config ();
_n_midi_outputs = count_channels (Vst::kEvent, Vst::kOutput, Vst::kMain); }
if (!connect_components ()) { if (!connect_components ()) {
//_controller->terminate(); // XXX ? //_controller->terminate(); // XXX ?
@ -1547,6 +1572,26 @@ VST3PI::queryInterface (const TUID _iid, void** obj)
return kNoInterface; return kNoInterface;
} }
void
VST3PI::query_io_config ()
{
_io_name[Vst::kAudio][Vst::kInput].clear ();
_io_name[Vst::kAudio][Vst::kOutput].clear ();
_io_name[Vst::kEvent][Vst::kInput].clear ();
_io_name[Vst::kEvent][Vst::kOutput].clear ();
_bus_info_in.clear ();
_bus_info_out.clear ();
/* do not re-order, _io_name is build in sequence */
_n_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kMain);
_n_aux_inputs = count_channels (Vst::kAudio, Vst::kInput, Vst::kAux);
_n_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kMain);
_n_aux_outputs = count_channels (Vst::kAudio, Vst::kOutput, Vst::kAux);
_n_midi_inputs = count_channels (Vst::kEvent, Vst::kInput, Vst::kMain);
_n_midi_outputs = count_channels (Vst::kEvent, Vst::kOutput, Vst::kMain);
}
tresult tresult
VST3PI::restartComponent (int32 flags) VST3PI::restartComponent (int32 flags)
{ {
@ -2253,6 +2298,72 @@ VST3PI::set_event_bus_state (bool enable)
} }
} }
void
VST3PI::request_bus_layout (uint32_t in, uint32_t aux_in, uint32_t out)
{
// TODO only if changed .. and if plugin doesn't have defaults
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::request_bus_layout: in = %1 aux-in = %2 out = %3\n", in, aux_in, out));
bool was_active = _is_processing;
if (!deactivate ()) {
DEBUG_TRACE (DEBUG::VST3Config, "VST3PI::request_bus_layout failed to deactivate plugin\n");
}
typedef std::vector<Vst::SpeakerArrangement> VSTSpeakerArrangements;
VSTSpeakerArrangements sa_in;
VSTSpeakerArrangements sa_out;
Vst::SpeakerArrangement sa = ((uint64_t)1 << in) - 1;
if (in == 1 /*Vst::SpeakerArr::kSpeakerL */ && !_no_kMono) {
sa = Vst::SpeakerArr::kMono; /* 1 << 19 */
}
if (_n_bus_in > 0) {
sa_in.push_back (sa);
}
sa = ((uint64_t)1 << out) - 1;
if (out == 1 /*Vst::SpeakerArr::kSpeakerL */ && !_no_kMono) {
sa = Vst::SpeakerArr::kMono; /* 1 << 19 */
}
if (_n_bus_out > 0) {
sa_out.push_back (sa);
}
sa = ((uint64_t)1 << aux_in) - 1;
if (_n_bus_in > 1) {
sa_in.push_back (sa);
}
sa = 0;
while (sa_in.size () < (VSTSpeakerArrangements::size_type) _n_bus_in) {
sa_in.push_back (sa);
}
while (sa_out.size () < (VSTSpeakerArrangements::size_type) _n_bus_out) {
sa_out.push_back (sa);
}
Vst::SpeakerArrangement null_arrangement = {};
#ifndef NDEBUG
tresult rv =
#endif
_processor->setBusArrangements (sa_in.size () > 0 ? &sa_in[0] : &null_arrangement, sa_in.size (),
sa_out.size () > 0 ? &sa_out[0] : &null_arrangement, sa_out.size ());
DEBUG_TRACE (DEBUG::VST3Config, string_compose ("VST3PI::request_bus_layout setBusArrangements ins = %1 outs = %2 | rv = %3\n", sa_in.size (), sa_out.size (), rv));
query_io_config ();
if (was_active) {
activate ();
}
}
void void
VST3PI::enable_io (std::vector<bool> const& ins, std::vector<bool> const& outs, bool force) VST3PI::enable_io (std::vector<bool> const& ins, std::vector<bool> const& outs, bool force)
{ {
@ -2273,8 +2384,8 @@ VST3PI::enable_io (std::vector<bool> const& ins, std::vector<bool> const& outs,
_enabled_audio_in = ins; _enabled_audio_in = ins;
_enabled_audio_out = outs; _enabled_audio_out = outs;
assert (_enabled_audio_in.size () == n_audio_inputs ()); //assert (_enabled_audio_in.size () == n_audio_inputs ());
assert (_enabled_audio_out.size () == n_audio_outputs ()); //assert (_enabled_audio_out.size () == n_audio_outputs ());
/* check that settings have not changed */ /* check that settings have not changed */
assert (_n_bus_in == _component->getBusCount (Vst::kAudio, Vst::kInput)); assert (_n_bus_in == _component->getBusCount (Vst::kAudio, Vst::kInput));
assert (_n_bus_out == _component->getBusCount (Vst::kAudio, Vst::kOutput)); assert (_n_bus_out == _component->getBusCount (Vst::kAudio, Vst::kOutput));

View file

@ -74,7 +74,7 @@ static const char* fmt_type (Vst::BusType t) {
} }
static int32 static int32
count_channels (Vst::IComponent* c, Vst::MediaType media, Vst::BusDirection dir, Vst::BusType type, bool verbose = false) count_channels (Vst::IComponent* c, Vst::MediaType media, Vst::BusDirection dir, Vst::BusType type, bool verbose = false, bool can_fail = true)
{ {
/* see also libs/ardour/vst3_plugin.cc VST3PI::count_channels */ /* see also libs/ardour/vst3_plugin.cc VST3PI::count_channels */
int32 n_busses = c->getBusCount (media, dir); int32 n_busses = c->getBusCount (media, dir);
@ -100,13 +100,57 @@ count_channels (Vst::IComponent* c, Vst::MediaType media, Vst::BusDirection dir,
} else { } else {
n_channels += bus.channelCount; n_channels += bus.channelCount;
} }
} else if (verbose && rv != kResultTrue) { } else if (rv != kResultTrue) {
PBD::info << "VST3: \\ error getting busInfo for bus: " << i << " rv: " << rv << ", got type: " << fmt_type (bus.busType) << endmsg; if (verbose) {
PBD::info << "VST3: \\ error getting busInfo for bus: " << i << " rv: " << rv << ", got type: " << fmt_type (bus.busType) << endmsg;
}
if (!can_fail) {
return -1;
}
} }
} }
return n_channels; return n_channels;
} }
static bool
count_all_count_channels (ARDOUR::VST3Info& nfo, Vst::IComponent* c, bool verbose, bool require_result)
{
nfo.n_inputs = count_channels (c, Vst::kAudio, Vst::kInput, Vst::kMain, verbose, require_result);
nfo.n_aux_inputs = count_channels (c, Vst::kAudio, Vst::kInput, Vst::kAux, verbose);
nfo.n_outputs = count_channels (c, Vst::kAudio, Vst::kOutput, Vst::kMain, verbose, require_result);
nfo.n_aux_outputs = count_channels (c, Vst::kAudio, Vst::kOutput, Vst::kAux, verbose);
nfo.n_midi_inputs = count_channels (c, Vst::kEvent, Vst::kInput, Vst::kMain, verbose);
nfo.n_midi_outputs = count_channels (c, Vst::kEvent, Vst::kOutput, Vst::kMain, verbose);
return nfo.n_inputs < 0 || nfo.n_outputs < 0;
}
static void
set_speaker_arrangement (Vst::IComponent* c, IPtr<Vst::IAudioProcessor> p)
{
Vst::SpeakerArrangement null_arrangement = {};
typedef std::vector<Vst::SpeakerArrangement> VSTSpeakerArrangements;
VSTSpeakerArrangements sa_in;
VSTSpeakerArrangements sa_out;
/* assume stereo by default */
int n_bus_in = c->getBusCount (Vst::kAudio, Vst::kInput);
int n_bus_out = c->getBusCount (Vst::kAudio, Vst::kOutput);
while (sa_in.size () < (VSTSpeakerArrangements::size_type) n_bus_in) {
Vst::SpeakerArrangement sa = Vst::SpeakerArr::kStereo;
sa_in.push_back (sa);
}
while (sa_out.size () < (VSTSpeakerArrangements::size_type) n_bus_out) {
Vst::SpeakerArrangement sa = Vst::SpeakerArr::kStereo;
sa_out.push_back (sa);
}
p->setBusArrangements (sa_in.size () > 0 ? &sa_in[0] : &null_arrangement, sa_in.size (),
sa_out.size () > 0 ? &sa_out[0] : &null_arrangement, sa_out.size ());
}
static bool static bool
discover_vst3 (std::shared_ptr<ARDOUR::VST3PluginModule> m, std::vector<ARDOUR::VST3Info>& rv, bool verbose) discover_vst3 (std::shared_ptr<ARDOUR::VST3PluginModule> m, std::vector<ARDOUR::VST3Info>& rv, bool verbose)
{ {
@ -209,12 +253,12 @@ discover_vst3 (std::shared_ptr<ARDOUR::VST3PluginModule> m, std::vector<ARDOUR::
continue; continue;
} }
nfo.n_inputs = count_channels (component, Vst::kAudio, Vst::kInput, Vst::kMain, verbose); /* first try to get default layout ..*/
nfo.n_aux_inputs = count_channels (component, Vst::kAudio, Vst::kInput, Vst::kAux, verbose); if (!count_all_count_channels (nfo, component, verbose, false)) {
nfo.n_outputs = count_channels (component, Vst::kAudio, Vst::kOutput, Vst::kMain, verbose); /* some plugins e.g. Altiverb require a valid Bus/SpeakerArrangement */
nfo.n_aux_outputs = count_channels (component, Vst::kAudio, Vst::kOutput, Vst::kAux, verbose); set_speaker_arrangement (component, processor);
nfo.n_midi_inputs = count_channels (component, Vst::kEvent, Vst::kInput, Vst::kMain, verbose); count_all_count_channels (nfo, component, verbose, true);
nfo.n_midi_outputs = count_channels (component, Vst::kEvent, Vst::kOutput, Vst::kMain, verbose); }
processor->setProcessing (false); processor->setProcessing (false);
component->setActive (false); component->setActive (false);