From 588c0da32d8160e4fedfa2803b8f5bf471fde821 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Thu, 20 Aug 2026 10:50:11 +0700 Subject: [PATCH] native_bridge: recalibrate after preset restore, reject 3rd live VST2 instance --- .../include/NativeInstrumentEngine.h | 5 ++ native_bridge/src/NativeInstrumentEngine.cpp | 17 +++++ native_bridge/src/main.cpp | 63 +++++++++++++++++-- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/native_bridge/include/NativeInstrumentEngine.h b/native_bridge/include/NativeInstrumentEngine.h index 498511d..d1a37dc 100644 --- a/native_bridge/include/NativeInstrumentEngine.h +++ b/native_bridge/include/NativeInstrumentEngine.h @@ -158,6 +158,11 @@ public: void setBypassed(bool on) { isBypassed_.store(on); } bool isBypassed() const { return isBypassed_.load(); } + // Re-run output calibration after a preset/chunk restore (calibrate() in + // assign() runs BEFORE the preset exists - VST2 like Qin probes silent and + // stays at makeup=1.0, quieter than VSTi calibrated after restore). + float recalibrate(uint32_t channel, double sampleRate, uint32_t blockSize); + private: static std::unique_ptr create_instrument(InstrumentType type); // Probe-render 3 notes sau khi load, do peak, tinh makeup de note don ra diff --git a/native_bridge/src/NativeInstrumentEngine.cpp b/native_bridge/src/NativeInstrumentEngine.cpp index 2ba5aeb..6a4c678 100644 --- a/native_bridge/src/NativeInstrumentEngine.cpp +++ b/native_bridge/src/NativeInstrumentEngine.cpp @@ -276,6 +276,23 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type, return true; } +float InstrumentEngineManager::recalibrate(uint32_t channel, double sampleRate, uint32_t blockSize) { + INativeInstrument* inst = nullptr; + { + std::lock_guard lock(mu_); + auto it = channels_.find(channel); + if (it != channels_.end()) inst = it->second.get(); + } + if (!inst) return 1.0f; + // calibrate() probe-renders (up to ~0.9s) - never hold mu_ (audio stall). + const float mk = calibrate(inst, sampleRate, blockSize); + { + std::lock_guard lock(mu_); + makeup_[channel] = mk; + } + return mk; +} + void InstrumentEngineManager::unload(uint32_t channel) { if (channel >= 16) return; // Destructor runs on the CALLER thread (VST3 terminate must run on the diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index ffe075a..ded3ba1 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -1355,10 +1355,44 @@ int main(int argc, char* argv[]) { } if (ok) { if (auto* inst = instruments.get(ch)) { - if ((t == InstrumentType::VST3 || t == InstrumentType::VST2) && !preset.empty()) - inst->loadSerializedState(preset); - else - inst->selectProgram(ch, bank, program); + // QIN LIMIT: same-path VST2 instance #3 would + // flood auxhost with ML_RELEASE_ERROR (engine + // supports 2 live instances max). Restore skips + // it; frontend keeps the track but muted/empty. + if (t == InstrumentType::VST2 && !path.empty()) { + uint32_t samePath = 0; + for (uint32_t y = 0; y < 16; ++y) { + if (y == ch) continue; + if (!instruments.get(y)) continue; + if (lower_plugin_path(y) == lower_path_str(path)) ++samePath; + } + if (samePath >= 2) { + std::cerr << "[NativeBridge] restore SKIP ch=" << ch + << " VST2 path already has " << samePath + << " live instances (" << path + << ") - plugin engine supports 2 max" << std::endl; + instruments.unload(ch); + ok = false; + } + } + } + if (ok) { + auto* inst2 = instruments.get(ch); + if (!inst2) { + std::cerr << "[NativeBridge] restore lost inst ch=" << ch << std::endl; + } else { + if ((t == InstrumentType::VST3 || t == InstrumentType::VST2) && !preset.empty()) + inst2->loadSerializedState(preset); + else + inst2->selectProgram(ch, bank, program); + // Re-calibrate AFTER the preset exists: assign() + // calibrated the empty instance (VST2 like Qin + // probes silent -> makeup=1.0, quieter than the + // other VSTi). Probe-render runs off mu_ so the + // audio loop never stalls. + if (t == InstrumentType::VST3 || t == InstrumentType::VST2) + instruments.recalibrate(ch, sampleRate, block); + } } std::cout << "[NativeBridge] restored ch=" << ch << " type=" << (int)t << " " << path << std::endl; @@ -1421,6 +1455,27 @@ int main(int argc, char* argv[]) { << " (already loaded: " << path << ")" << std::endl; return; } + // QIN LIMIT: Kong Audio Qin bridges all instances through + // ONE auxhost.exe (32-bit engine host); a 3rd live Qin + // instance makes the engine spam ML_RELEASE_ERROR forever + // (observed: ch0+ch3 fine, +ch2 -> bridge log flooded, + // engine unusable). Reject VST2 instance #3 of the same + // path so the session stays playable. + if (t == InstrumentType::VST2 && !path.empty()) { + uint32_t samePath = 0; + for (uint32_t y = 0; y < 16; ++y) { + if (y == ch) continue; + if (!instruments.get(y)) continue; + if (lower_plugin_path(y) == lower_path_str(path)) ++samePath; + } + if (samePath >= 2) { + std::cerr << "[NativeBridge] LOAD REJECT ch=" << ch + << " VST2 path already has " << samePath + << " live instances (" << path + << ") - plugin engine supports 2 max" << std::endl; + return; + } + } // CRASH FIX: this job enters the plugin DLL (createInstance // in loadPlugin, old-instance terminate on replace) while // the audio loop may process() another instance of the SAME