bridge: fix Qin VST2 silent playback via per-child SHM sandbox

- out-of-process VST2 sandbox: each Qin track spawns its own plugin_host.exe child
  with unique SHM (SonicForge_PluginHost2_<bridgePid>_<ch>_<seq>), watchdog <=3 respawns
- preset round-trip bridge<->child over SHM (presetIn/presetOut 64KB), stub rejection MIN_PRESET=100
- seed qin_default.b64 default preset into every fresh child; saved patch wins on restore
- forward MIDI on channel 0 (Qin_RV only listens on ch0); one instrument per child
- controls 4/5/6 = OPEN_GUI/CLOSE_GUI/QUIT
- gitignore runtime carla_projects
This commit is contained in:
2026-08-20 19:16:43 +07:00
parent d6649e4474
commit b91e6d3875
10 changed files with 589 additions and 34 deletions
+30 -1
View File
@@ -7,6 +7,7 @@
#include "Vst3Instrument.h"
#include "Vst2Instrument.h"
#include "SandboxVst3Host.h"
#include "SandboxVst2Host.h"
#endif
// void* members keep fluid types out of the public header; cast here.
@@ -19,6 +20,7 @@
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <thread>
@@ -181,6 +183,24 @@ void SfizzInstrument::processAudioBlock(float* outputL, float* outputR, uint32_t
// -----------------------------------------------------------------
// 3. MULTI-CHANNEL INSTRUMENT MANAGER (A10)
// -----------------------------------------------------------------
// jBridge stub detection: jBridge-wrapped VST2s (e.g. Qin_RV_x64.dll) embed
// the string "Bridger64.dll". The bundled wrapper allows only ONE instance
// of the same plugin per host process (2nd instance loses the shared audio
// buffer -> silent), so these MUST run in a sandbox child (one process per
// instance). Native VST2s (Nexus) host in-process with an embedded editor.
static bool is_jbridge_dll(const std::string& path) {
FILE* f = fopen(path.c_str(), "rb");
if (!f) return false;
std::vector<char> d;
char tmp[16384];
size_t n;
while ((n = fread(tmp, 1, sizeof(tmp), f)) > 0)
d.insert(d.end(), tmp, tmp + n);
fclose(f);
static const char needle[] = "Bridger64.dll";
return std::search(d.begin(), d.end(), needle, needle + sizeof(needle) - 1) != d.end();
}
std::unique_ptr<INativeInstrument> InstrumentEngineManager::create_instrument(InstrumentType type) {
switch (type) {
case InstrumentType::SOUNDFONT_SF2_SF3: return std::make_unique<FluidSynthInstrument>();
@@ -211,6 +231,12 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
if (channel >= 16) return false;
auto inst = create_instrument(type);
if (!inst) return false;
#ifdef _WIN32
// jBridge VST2 (Qin): sandbox in a child process - one Bridger64
// instance per process. Native VST2 stays in-process.
if (type == InstrumentType::VST2 && is_jbridge_dll(path))
inst = std::make_unique<SandboxVst2Host>();
#endif
bool loaded = false;
if (type == InstrumentType::SOUNDFONT_SF2_SF3)
loaded = static_cast<FluidSynthInstrument*>(inst.get())->loadSoundFontFile(path, sampleRate);
@@ -227,6 +253,8 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
if (auto* v2 = dynamic_cast<Vst2Instrument*>(inst.get())) {
v2->setChannel(channel);
loaded = v2->loadPlugin(path, sampleRate);
} else if (auto* s2 = dynamic_cast<SandboxVst2Host*>(inst.get())) {
loaded = s2->loadPlugin(path, sampleRate, channel);
}
}
if (type == InstrumentType::VST3) {
@@ -344,7 +372,8 @@ bool InstrumentEngineManager::snapshot(BridgeStateSnapshot& out) {
// here: mu_ is held, so the audio loop cannot be inside
// process() on this instance while getState runs.
i->captureState();
e.presetBase64 = i->serializeState();
std::string saved = i->serializeState();
e.presetBase64 = saved.size() >= 100 ? saved : std::string();
e.bank = i->bankOf(ch);
e.program = i->programOf(ch);
}