feat: native host bridge integration — C++ bridge, Rust SHM, JS routing, build scripts, docs
- native_bridge/: InstrumentEngineManager multi-channel, sample-accurate, CC/program/pitchbend, transport, Vst3Instrument stub (HAVE_VST3SDK) - src-tauri: shm.rs, bridge spawn + audio pump + health monitor, open_vst_gui, externalBin, commands - app: UnifiedMidiRouter, NativeBridgeService, bridgeAudioNode, audioRoutingEngine, Plugin Manager UI, Bridge/WASM indicator, set_position sync - build: 3 ps1 (force-added, build/ ignored), verify_bundle --check-bridge, CI workflow - docs: TASKS.md, TEST_NOTES.md (Windows verify checklist), install/report updates
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
// native_bridge/src/Vst3Instrument.cpp
|
||||
// VST3 host via the Steinberg VST3 SDK (submodule vst3sdk/).
|
||||
//
|
||||
// Without the SDK (HAVE_VST3SDK undefined — vst3sdk submodule missing) every
|
||||
// method is a no-op so the bridge still builds for SF2/SF3/SFZ only.
|
||||
//
|
||||
// With the SDK: load the .vst3 module, create the component + edit controller,
|
||||
// connect them, process MIDI events + audio blocks, and attach the editor
|
||||
// view to a native HWND (openGUI) for the floating GUI (B9).
|
||||
#include "Vst3Instrument.h"
|
||||
|
||||
#ifdef HAVE_VST3SDK
|
||||
#include "public.sdk/source/main/pluginfactory.h"
|
||||
#include "pluginterfaces/base/ibstream.h"
|
||||
#include "pluginterfaces/vst/ivstaudioprocessor.h"
|
||||
#include "pluginterfaces/vst/ivsteditcontroller.h"
|
||||
#include "pluginterfaces/vst/ivstmidicontrollers.h"
|
||||
#include "pluginterfaces/gui/iplugview.h"
|
||||
|
||||
#include "public.sdk/source/common/pluginview.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// With-SDK implementation
|
||||
// ---------------------------------------------------------------------------
|
||||
#ifdef HAVE_VST3SDK
|
||||
#include "public.sdk/source/main/module.h"
|
||||
|
||||
namespace {
|
||||
using Steinberg::Vst::IComponent;
|
||||
using Steinberg::Vst::IEditController;
|
||||
using Steinberg::Vst::IAudioProcessor;
|
||||
using Steinberg::Vst::IComponentHandler;
|
||||
using Steinberg::Vst::IParameterChanges;
|
||||
using Steinberg::Vst::IEventList;
|
||||
using Steinberg::Vst::Event;
|
||||
using Steinberg::Vst::NoteOnEvent;
|
||||
using Steinberg::Vst::NoteOffEvent;
|
||||
using Steinberg::Vst::DataEvent;
|
||||
using Steinberg::Vst::kMidiCC;
|
||||
using Steinberg::Vst::kMidiPitchBend;
|
||||
using Steinberg::Vst::kMidiProgramChange;
|
||||
using Steinberg::IPlugView;
|
||||
using Steinberg::tresult;
|
||||
using Steinberg::kResultOk;
|
||||
using Steinberg::kResultFalse;
|
||||
|
||||
// Minimal IComponentHandler so the plugin can inform the host of param edits.
|
||||
class HostComponentHandler : public IComponentHandler {
|
||||
public:
|
||||
Steinberg::tresult queryInterface(const Steinberg::TUID&, void** v) override {
|
||||
*v = nullptr;
|
||||
return Steinberg::kNoInterface;
|
||||
}
|
||||
Steinberg::uint32 addRef() override { return 1; }
|
||||
Steinberg::uint32 release() override { return 1; }
|
||||
Steinberg::tresult beginEdit(Steinberg::Vst::ParamID) override { return Steinberg::kResultOk; }
|
||||
Steinberg::tresult performEdit(Steinberg::Vst::ParamID, Steinberg::Vst::ParamValue) override {
|
||||
return Steinberg::kResultOk;
|
||||
}
|
||||
Steinberg::tresult endEdit(Steinberg::Vst::ParamID) override { return Steinberg::kResultOk; }
|
||||
Steinberg::tresult restartComponent(Steinberg::int32) override { return Steinberg::kResultOk; }
|
||||
};
|
||||
|
||||
// Bundle both components in one factory entry so module.load() gives us the
|
||||
// component; controller is created via IComponent::createController.
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
Vst3Instrument::Vst3Instrument()
|
||||
: module_(nullptr),
|
||||
processor_(nullptr),
|
||||
controller_(nullptr),
|
||||
view_(nullptr),
|
||||
sampleRate_(44100.0),
|
||||
maxBlockSize_(256),
|
||||
loaded_(false),
|
||||
guiAttached_(false) {}
|
||||
|
||||
Vst3Instrument::~Vst3Instrument() { closeGUI(); }
|
||||
|
||||
bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) {
|
||||
#ifndef HAVE_VST3SDK
|
||||
(void)path; (void)sampleRate;
|
||||
return false; // vst3sdk submodule missing — VST3 disabled
|
||||
#else
|
||||
if (loaded_) return true;
|
||||
// ponytail: vst3sdk module loading is platform-specific
|
||||
// (Module::create on Windows .vst3 bundle / macOS .vst3 framework). Keep
|
||||
// the classic pluginfactory-based load for Windows bundles:
|
||||
// void* handle = Steinberg::Vst::Module::create(...)
|
||||
// and use module->getFactory().createInstance<...>(cid).
|
||||
// Verified on Windows in A6 (G3 Vital.vst3). Until then VST3 returns false
|
||||
// so SF2/SFZ keep working.
|
||||
(void)path; (void)sampleRate;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Vst3Instrument::init(double sampleRate, uint32_t maxBlockSize) {
|
||||
sampleRate_ = sampleRate;
|
||||
maxBlockSize_ = maxBlockSize;
|
||||
return loaded_;
|
||||
}
|
||||
|
||||
void Vst3Instrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {
|
||||
(void)channel; (void)bank; (void)program;
|
||||
// ponytail: needs IEditController::setParamNormalized on program list params
|
||||
}
|
||||
|
||||
void Vst3Instrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) {
|
||||
(void)channel; (void)pitch; (void)velocity; (void)sampleOffset;
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
if (!processor_) return;
|
||||
// TODO(A6): queue NoteOnEvent into the per-block event list; see
|
||||
// processAudioBlock. Wiring requires IAudioProcessor::process with
|
||||
// ProcessData — implemented together with loadPlugin on Windows.
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) {
|
||||
(void)channel; (void)pitch; (void)sampleOffset;
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
if (!processor_) return;
|
||||
// TODO(A6): queue NoteOffEvent (see noteOn)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
||||
(void)channel; (void)cc; (void)value;
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
if (!controller_) return;
|
||||
// TODO(A6): controller_->setParamNormalized(kMidiCC | cc, value/127.0)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::programChange(uint32_t channel, uint32_t program) {
|
||||
(void)channel; (void)program;
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
if (!controller_) return;
|
||||
// TODO(A6): setParamNormalized(kMidiProgramChange | program, ...)
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::pitchBend(uint32_t channel, uint32_t bend14) {
|
||||
(void)channel; (void)bend14;
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
if (!controller_) return;
|
||||
// TODO(A6): setParamNormalized(kMidiPitchBend, bend14/16383.0)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Vst3Instrument::openGUI(void* parentWindowHandle) {
|
||||
#ifndef HAVE_VST3SDK
|
||||
(void)parentWindowHandle;
|
||||
return false;
|
||||
#else
|
||||
if (!processor_ || !controller_ || !parentWindowHandle) return false;
|
||||
if (guiAttached_) return true;
|
||||
// TODO(A6): FUnknownPtr<IPlugView> view(controller_);
|
||||
// view->setFrame(parentWindowHandle); view->attached(...); view_ = view;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::closeGUI() {
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
if (view_ && guiAttached_) {
|
||||
// view_->removed(); view_->setFrame(nullptr);
|
||||
}
|
||||
view_ = nullptr;
|
||||
guiAttached_ = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Vst3Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
|
||||
(void)outputL; (void)outputR; (void)numSamples;
|
||||
#ifndef HAVE_VST3SDK
|
||||
return;
|
||||
#else
|
||||
// TODO(A6): ProcessData with 2 output buffers + event list; called by
|
||||
// InstrumentEngineManager::renderAll via processAudioBlock.
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user