M5.4: VST3 host conformance (Reaper-style) for FX GUI attach

- queryInterface now answers FUnknown::iid + own iid (IComponentHandler /
  IPlugFrame) with addRef+this per SDK QUERY_INTERFACE contract; plugins that
  QI the host handler no longer get kNoInterface unconditionally.
- setBusArrangements negotiation before activateBus: offer mono/stereo for
  <=2ch audio buses, keep wider arrangements; result logged, not fatal.
  Matches REAPER load path so Ozone-class editors see a valid arrangement
  on first attach. Local float regression: Ozone 11 editor opens OK.
This commit is contained in:
2026-09-04 17:34:54 +07:00
parent 014b9ac460
commit cede51ae0c
+53 -3
View File
@@ -25,6 +25,7 @@
#include "public.sdk/source/vst/vstpresetfile.h"
#include "public.sdk/source/common/memorystream.h"
#include "pluginterfaces/vst/ivstaudioprocessor.h"
#include "pluginterfaces/vst/vstspeaker.h"
#include "pluginterfaces/vst/ivstcomponent.h"
#include "pluginterfaces/vst/ivsteditcontroller.h"
#include "pluginterfaces/vst/ivstprocesscontext.h"
@@ -355,7 +356,18 @@ using Steinberg::Vst::kSample32;
class HostComponentHandlerFx : public IComponentHandler {
public:
tresult queryInterface(const char*, void** v) override { *v = nullptr; return kNoInterface; }
tresult queryInterface(const char* iid, void** v) override {
// FUnknown contract: answer for our own iid (+FUnknown::iid) with
// addRef + this; kNoInterface otherwise (SDK QUERY_INTERFACE pattern).
if (Steinberg::FUnknownPrivate::iidEqual(iid, Steinberg::FUnknown::iid) ||
Steinberg::FUnknownPrivate::iidEqual(iid, IComponentHandler::iid)) {
addRef();
*v = this;
return kResultOk;
}
*v = nullptr;
return kNoInterface;
}
Steinberg::uint32 addRef() override { return 1; }
Steinberg::uint32 release() override { return 1; }
tresult beginEdit(Steinberg::Vst::ParamID) override { return kResultOk; }
@@ -377,7 +389,15 @@ using Steinberg::kPlatformTypeHWND;
// Minimal IPlugFrame so the plugin can resize its editor view.
class HostPlugFrameFx : public Steinberg::IPlugFrame {
public:
tresult queryInterface(const char*, void** v) override {
tresult queryInterface(const char* iid, void** v) override {
// FUnknown contract: answer for our own iid (+FUnknown::iid) with
// addRef + this; kNoInterface otherwise (SDK QUERY_INTERFACE pattern).
if (Steinberg::FUnknownPrivate::iidEqual(iid, Steinberg::FUnknown::iid) ||
Steinberg::FUnknownPrivate::iidEqual(iid, Steinberg::IPlugFrame::iid)) {
addRef();
*v = this;
return kResultOk;
}
*v = nullptr;
return kNoInterface;
}
@@ -515,8 +535,38 @@ bool vst3FxLoadInner(Vst3FxState* s, const std::string& path, double sampleRate,
std::cerr << "[RenderFx] STEP connection points OK" << std::endl;
int32 numAudioInputs = component->getBusCount(kAudio, kInput);
for (int32 i = 0; i < numAudioInputs; ++i) component->activateBus(kAudio, kInput, i, true);
int32 numAudioOutputs = component->getBusCount(kAudio, kOutput);
// Reaper-style negotiation BEFORE activation: offer mono/stereo for the
// <=2ch audio busses so plugins (Ozone 11 family) never see an empty or
// unset arrangement on their first GUI attach. Wider busses keep their
// current arrangement. Failure to negotiate is not fatal (log only).
{
FUnknownPtr<IAudioProcessor> proc(component);
if (proc) {
std::vector<Steinberg::Vst::SpeakerArrangement> inArr, outArr;
for (int32 i = 0; i < numAudioInputs; ++i) {
Steinberg::Vst::SpeakerArrangement arr = 0;
proc->getBusArrangement(kInput, i, arr);
int32 ch = Steinberg::Vst::SpeakerArr::getChannelCount(arr);
inArr.push_back(ch == 1 ? Steinberg::Vst::SpeakerArr::kMono
: (ch == 2 ? Steinberg::Vst::SpeakerArr::kStereo : arr));
}
for (int32 i = 0; i < numAudioOutputs; ++i) {
Steinberg::Vst::SpeakerArrangement arr = 0;
proc->getBusArrangement(kOutput, i, arr);
int32 ch = Steinberg::Vst::SpeakerArr::getChannelCount(arr);
outArr.push_back(ch == 1 ? Steinberg::Vst::SpeakerArr::kMono
: (ch == 2 ? Steinberg::Vst::SpeakerArr::kStereo : arr));
}
tresult res = proc->setBusArrangements(inArr.data(), (int32)inArr.size(),
outArr.data(), (int32)outArr.size());
std::cerr << "[RenderFx] STEP bus arrangements "
<< (res == kResultOk || res == kResultTrue ? "OK" : "skip")
<< " (ain=" << numAudioInputs << " aout=" << numAudioOutputs << ")"
<< std::endl;
}
}
for (int32 i = 0; i < numAudioInputs; ++i) component->activateBus(kAudio, kInput, i, true);
for (int32 i = 0; i < numAudioOutputs; ++i) component->activateBus(kAudio, kOutput, i, true);
int32 numEventInputs = component->getBusCount(kEvent, kInput);
for (int32 i = 0; i < numEventInputs; ++i) component->activateBus(kEvent, kInput, i, true);