fix(bridge): 3 bug âm thanh Qin/Nexus standalone
1) wavesynth tự phát khi mở app: probe calibrate() render vào live mix (renderAll process cùng instance) -> thêm calibratingCh_[16], renderAll bỏ qua channel đang calibrate; plugin_host panic (sustain off + all notes off + 128 noteOff) ngay sau preset apply để startup im lặng. 2) bấm GUI mở 2 cửa sổ Qin: sandbox VST2 child tự sở hữu top-level window -> ownsGuiWindow() + main.cpp forward OPEN_GUI control, không tạo native parent / không registry double-open. 3) play phát nhạc cụ sai: restore clobber — seed preset đè patch thật trong state file (ch3 63ba3667 -> 7f07473c) do saveStateJob chạy lúc child còn seed. Fix: queuePreset(waitApply=true) chờ child apply xong (presetOut memcmp), consume-before-apply (zero presetInLen trước khi apply), deferred double-load re-check trong main.cpp skip assign trùng path, watchdog respawn re-queue lastPreset_.
This commit is contained in:
@@ -107,6 +107,19 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
INativeInstrument& inst = vst2 ? (INativeInstrument&)instV2 : (INativeInstrument&)instV3;
|
||||
|
||||
// G4.6 auto-play fix: a loaded chunk can embed a stuck/sustained note
|
||||
// (factory state, seed, or a patch captured mid-note). Release everything
|
||||
// right after a preset apply so the channel starts silent instead of
|
||||
// sounding on app open (STOP later releases it too, but startup must not
|
||||
// play).
|
||||
auto panicAfterPreset = [&inst, channel]() {
|
||||
inst.controlChange(0, 64, 0); // sustain pedal off
|
||||
inst.controlChange(0, 123, 0); // all notes off
|
||||
for (uint32_t p = 0; p < 128; ++p) inst.noteOff(0, p, 0);
|
||||
printf("[plugin_host] panic after preset ch=%u\n", channel);
|
||||
fflush(stdout);
|
||||
};
|
||||
|
||||
// Optional preset restore: Qin_RV and other jBridge romplers render SILENT
|
||||
// from their factory state - they need their program/bank chunk. The bridge
|
||||
// forwards presetBase64 (base64 text, one line) via a temp file.
|
||||
@@ -145,15 +158,28 @@ int main(int argc, char* argv[]) {
|
||||
// G4.5: publish the initial chunk so the bridge can persist it even
|
||||
// without a preset round-trip yet (factory state = the silent chunk;
|
||||
// the GUI or a presetIn apply replaces it later).
|
||||
std::string lastOut;
|
||||
inst.captureState();
|
||||
{
|
||||
std::string out = inst.serializeState();
|
||||
if (!out.empty() && out.size() < PRESET_BLOB_SIZE) {
|
||||
memcpy((void*)ipc->presetOut, out.data(), out.size());
|
||||
ipc->presetOutLen = (uint32_t)out.size();
|
||||
lastOut = out;
|
||||
printf("[plugin_host] initial preset published ch=%u (%uB)\n", channel, ipc->presetOutLen);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
// G4.6 auto-play fix (clobber-safe): release stuck notes AFTER the real
|
||||
// chunk is already published, then absorb the panic-mutated chunk so the
|
||||
// periodic republish never echoes it back over presetOut. Startup stays
|
||||
// silent AND the state file keeps the real patch.
|
||||
panicAfterPreset();
|
||||
inst.captureState();
|
||||
{
|
||||
std::string postPanic = inst.serializeState();
|
||||
if (!postPanic.empty()) lastOut = postPanic;
|
||||
}
|
||||
}
|
||||
|
||||
// Heartbeat ~10Hz, independent of the audio loop (G3.1 pattern).
|
||||
@@ -168,7 +194,6 @@ int main(int argc, char* argv[]) {
|
||||
bool idle = false;
|
||||
bool quit = false;
|
||||
uint32_t lastCaptureTick = 0;
|
||||
std::string lastOut;
|
||||
for (;;) {
|
||||
MSG msg;
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
@@ -213,19 +238,29 @@ int main(int argc, char* argv[]) {
|
||||
fflush(stdout);
|
||||
ipc->presetInLen = 0;
|
||||
} else {
|
||||
std::string b64(ipc->presetIn, ipc->presetInLen);
|
||||
inst.loadSerializedState(b64);
|
||||
printf("[plugin_host] preset applied from SHM ch=%u (%uB)\n", channel, ipc->presetInLen);
|
||||
fflush(stdout);
|
||||
ipc->presetInLen = 0;
|
||||
// republish immediately so a just-after restore save has it
|
||||
inst.captureState();
|
||||
std::string out = inst.serializeState();
|
||||
if (!out.empty() && out.size() < PRESET_BLOB_SIZE) {
|
||||
memcpy((void*)ipc->presetOut, out.data(), out.size());
|
||||
ipc->presetOutLen = (uint32_t)out.size();
|
||||
lastOut = out;
|
||||
}
|
||||
// consume-before-apply: copy and zero presetInLen FIRST so the
|
||||
// bridge can safely queue the next preset while this plugin
|
||||
// applies the current one (prevents the restore clobber race).
|
||||
uint32_t plen = ipc->presetInLen;
|
||||
std::string b64(ipc->presetIn, plen);
|
||||
ipc->presetInLen = 0;
|
||||
inst.loadSerializedState(b64);
|
||||
printf("[plugin_host] preset applied from SHM ch=%u (%uB)\n", channel, (unsigned)plen);
|
||||
fflush(stdout);
|
||||
// republish immediately so a just-after restore save has it
|
||||
inst.captureState();
|
||||
std::string out = inst.serializeState();
|
||||
if (!out.empty() && out.size() < PRESET_BLOB_SIZE) {
|
||||
memcpy((void*)ipc->presetOut, out.data(), out.size());
|
||||
ipc->presetOutLen = (uint32_t)out.size();
|
||||
lastOut = out;
|
||||
}
|
||||
panicAfterPreset();
|
||||
// absorb the panic-mutated chunk so the ~2s periodic republish
|
||||
// never overwrites presetOut with it
|
||||
inst.captureState();
|
||||
std::string postPanic = inst.serializeState();
|
||||
if (!postPanic.empty()) lastOut = postPanic;
|
||||
}
|
||||
}
|
||||
if (++lastCaptureTick >= 200) { // ~2s of loop passes
|
||||
|
||||
Reference in New Issue
Block a user