Fix AI proxy origin in Tauri, SF2 program_select, VSTi GUI reopen + sample rate bridge

- aiGateway.js: use window.API_BASE_URL (engine port) instead of window.location.origin (tauri://localhost asset protocol returned index.html -> JSON parse error); check content-type before parsing response
- NativeInstrumentEngine.cpp: fluid_synth_program_select for SF2 instrument + program change
- Vst3Instrument.cpp: activate all audio/event buses, channel=0 forced, GUI resize/reopen, kEvent using fix
- main.cpp: VST GUI reopen via WM_DESTROY, OleInitialize, steady_clock loop
- lib.rs: BridgeSampleRate state, restart_bridge_with_sample_rate command, borrow fix
- app.jsx/nativeBridgeService.js: isMidiTrack widening, bridge masterbus connect, held MIDI notes, sample-rate restart, openNativeGUI channel
This commit is contained in:
2026-08-13 11:26:39 +07:00
parent 49549aba2e
commit d168328004
8 changed files with 218 additions and 59 deletions
+45 -10
View File
@@ -1,4 +1,4 @@
// native_bridge/src/Vst3Instrument.cpp
// native_bridge/src/Vst3Instrument.cpp
// VST3 host via the Steinberg VST3 SDK (submodule vst3sdk/).
//
// Without the SDK (HAVE_VST3SDK undefined — vst3sdk submodule missing) every
@@ -10,6 +10,9 @@
#include "Vst3Instrument.h"
#ifdef HAVE_VST3SDK
#ifdef _WIN32
#include <windows.h>
#endif
#include "public.sdk/source/vst/hosting/module.h"
#include "public.sdk/source/vst/hosting/hostclasses.h"
#include "public.sdk/source/vst/hosting/processdata.h"
@@ -75,6 +78,7 @@ using Steinberg::Vst::IParamValueQueue;
using Steinberg::Vst::kNoParamId;
using Steinberg::Vst::BusInfo;
using Steinberg::Vst::kAudio;
using Steinberg::Vst::kEvent;
using Steinberg::Vst::kInput;
using Steinberg::Vst::kOutput;
using Steinberg::Vst::kRealtime;
@@ -287,6 +291,26 @@ bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) {
ctrlCP->connect(compCP);
}
// Activate all audio and event buses (MIDI & Audio) - required by VST3 specification.
{
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);
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);
}
int32 numEventOutputs = component->getBusCount(kEvent, kOutput);
for (int32 i = 0; i < numEventOutputs; ++i) {
component->activateBus(kEvent, kOutput, i, true);
}
}
// Audio processor: setup → activate → start processing (audioclient.cpp).
FUnknownPtr<IAudioProcessor> processor(component);
if (!processor) {
@@ -372,7 +396,7 @@ void Vst3Instrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, ui
e.ppqPosition = 0;
e.flags = Event::kIsLive;
e.type = Event::kNoteOnEvent;
e.noteOn.channel = (int16)channel;
e.noteOn.channel = 0; // Force to MIDI Channel 1 (0) for VSTi compatibility
e.noteOn.pitch = (int16)pitch;
e.noteOn.tuning = 0.f;
e.noteOn.velocity = velocity;
@@ -395,7 +419,7 @@ void Vst3Instrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOf
e.ppqPosition = 0;
e.flags = Event::kIsLive;
e.type = Event::kNoteOffEvent;
e.noteOff.channel = (int16)channel;
e.noteOff.channel = 0; // Force to MIDI Channel 1 (0) for VSTi compatibility
e.noteOff.pitch = (int16)pitch;
e.noteOff.velocity = 0.f;
e.noteOff.noteId = -1;
@@ -411,8 +435,8 @@ void Vst3Instrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value
#else
auto* s = static_cast<Vst3HostState*>(state_);
if (!s || !s->controller) return;
ParamID tag = midiControllerTag(s->controller, (int16)channel, (int16)cc,
kHostMidiCC | (ParamID)(cc & 0x7F));
ParamID tag = midiControllerTag(s->controller, 0, (int16)cc,
kHostMidiCC);
ParamValue v = value / 127.0;
// setParamNormalized covers single-component plugins; the param queue
// reaches split plugins that read inputParameterChanges inside process().
@@ -429,7 +453,7 @@ void Vst3Instrument::programChange(uint32_t channel, uint32_t program) {
#else
auto* s = static_cast<Vst3HostState*>(state_);
if (!s || !s->controller) return;
ParamID tag = kHostMidiProgramChange | (ParamID)(channel & 0xF);
ParamID tag = kHostMidiProgramChange;
// ponytail: normalized value should be program/(count-1) from the program
// list param stepCount; 127ths is a reasonable approximation.
ParamValue v = (program & 0x7F) / 127.0;
@@ -446,8 +470,8 @@ void Vst3Instrument::pitchBend(uint32_t channel, uint32_t bend14) {
#else
auto* s = static_cast<Vst3HostState*>(state_);
if (!s || !s->controller) return;
ParamID tag = midiControllerTag(s->controller, (int16)channel, (int16)kPitchBend,
kHostMidiPitchBend | (ParamID)(channel & 0xF));
ParamID tag = midiControllerTag(s->controller, 0, (int16)kPitchBend,
kHostMidiPitchBend);
ParamValue v = bend14 / 16383.0;
s->controller->setParamNormalized(tag, v);
int32 idx = 0;
@@ -498,10 +522,21 @@ bool Vst3Instrument::openGUI(void* parentWindowHandle) {
view->setFrame(&s->plugFrame);
tresult ts = view->isPlatformTypeSupported(kPlatformTypeHWND);
std::cerr << "[dbg] openGUI: isPlatformTypeSupported=" << (int)ts << std::endl;
if (ts != kResultTrue && ts != kResultOk) return false;
// Log only — proceed to attach anyway to support non-compliant plugins.
tresult ta = view->attached(parentWindowHandle, kPlatformTypeHWND);
std::cerr << "[dbg] openGUI: attached=" << (int)ta << std::endl;
if (ta != kResultOk) return false;
#ifdef _WIN32
HWND hwnd = (HWND)parentWindowHandle;
Steinberg::ViewRect rect;
if (view->getSize(&rect) == kResultOk) {
int w = rect.right - rect.left;
int h = rect.bottom - rect.top;
RECT r = { 0, 0, w, h };
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
SetWindowPos(hwnd, nullptr, 0, 0, r.right - r.left, r.bottom - r.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
#endif
s->view = view;
guiAttached_ = true;
// ponytail: the bridge loop is a worker thread without a Windows message
@@ -556,7 +591,7 @@ void Vst3Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t
}
tresult pr = processor->process(s->processData);
float mx = 0.f;
if (pr == kResultOk && s->processData.numOutputs > 0) {
if (pr >= 0 && s->processData.numOutputs > 0) {
const Steinberg::Vst::AudioBusBuffers& out = s->processData.outputs[0];
const float* buf0 = out.numChannels > 0 ? out.channelBuffers32[0] : nullptr;
const float* buf1 = out.numChannels > 1 ? out.channelBuffers32[1] : nullptr;