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:
locpham
2026-08-11 23:37:29 +07:00
parent aae0b05473
commit c3368d0a91
37 changed files with 2970 additions and 80 deletions
+199
View File
@@ -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
}