fix: SF2 bank select, midi multi-key cut, VSTi reopen hang, multi-track stuck
- NativeInstrumentEngine: track GM bank per channel (CC0/CC32), use bank in programChange - app.jsx: send CC0/CC32+PROGRAM before notes via __ensureBridgeProgram, dedupe, clear dedupe after async LOAD - audioRoutingEngine/bridgeAudioNode: idempotent connect (no disconnect-flush on re-connect), fixes note cut & multi-track stuck - main.cpp: remove 10s poll in OPEN_GUI control job (blocked realtime loop, watchdog race), VstWindowProc stores channel not inst pointer, cleanup gui maps on WM_DESTROY
This commit is contained in:
@@ -35,6 +35,9 @@ private:
|
||||
void* settings; // fluid_settings_t*
|
||||
void* synth; // fluid_synth_t*
|
||||
int sfontId;
|
||||
// Per-channel bank select (CC0<<7 | CC32) — programChange phai dung bank
|
||||
// that (truoc day hardcode bank 0 -> preset o bank != 0 khong chon duoc).
|
||||
uint32_t bank_[16];
|
||||
};
|
||||
|
||||
// sfizz (.sfz)
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
// 1. SOUNDFONT ENGINE (.SF2 / .SF3) VIA FLUIDSYNTH C API
|
||||
// -----------------------------------------------------------------
|
||||
FluidSynthInstrument::FluidSynthInstrument()
|
||||
: settings(nullptr), synth(nullptr), sfontId(-1) {}
|
||||
: settings(nullptr), synth(nullptr), sfontId(-1) {
|
||||
for (uint32_t i = 0; i < 16; ++i) bank_[i] = 0;
|
||||
}
|
||||
|
||||
FluidSynthInstrument::~FluidSynthInstrument() {
|
||||
if (synth) delete_fluid_synth(FS_SYNTH);
|
||||
@@ -36,6 +38,7 @@ bool FluidSynthInstrument::loadSoundFontFile(const std::string& path, double sam
|
||||
if (sfontId == -1) return false;
|
||||
// Reset all channels to font preset 0 (spec §VII: bank0/prog0 piano)
|
||||
for (uint32_t ch = 0; ch < 16; ++ch) {
|
||||
bank_[ch] = 0;
|
||||
fluid_synth_program_select(FS_SYNTH, ch, sfontId, 0, 0);
|
||||
}
|
||||
return true;
|
||||
@@ -46,7 +49,8 @@ bool FluidSynthInstrument::init(double sampleRate, uint32_t maxBlockSize) {
|
||||
}
|
||||
|
||||
void FluidSynthInstrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {
|
||||
if (!synth) return;
|
||||
if (!synth || channel >= 16) return;
|
||||
bank_[channel] = bank;
|
||||
fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank, program);
|
||||
}
|
||||
|
||||
@@ -62,13 +66,18 @@ void FluidSynthInstrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sa
|
||||
}
|
||||
|
||||
void FluidSynthInstrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
||||
if (!synth) return;
|
||||
if (!synth || channel >= 16) return;
|
||||
// Bank select MSB/LSB (A12): CC0 = (bank>>7)&0x7F, CC32 = bank&0x7F
|
||||
if (cc == 0) bank_[channel] = (bank_[channel] & 0x7Fu) | ((value & 0x7Fu) << 7);
|
||||
else if (cc == 32) bank_[channel] = (bank_[channel] & ~0x7Fu) | (value & 0x7Fu);
|
||||
fluid_synth_cc(FS_SYNTH, channel, cc, value);
|
||||
}
|
||||
|
||||
void FluidSynthInstrument::programChange(uint32_t channel, uint32_t program) {
|
||||
if (!synth) return;
|
||||
fluid_synth_program_select(FS_SYNTH, channel, sfontId, 0, program);
|
||||
if (!synth || channel >= 16) return;
|
||||
// Dung bank da nhan tu CC0/CC32 — bank hardcode 0 lam preset o bank != 0
|
||||
// khong duoc chon (fluid giu preset cu -> ra piano sai).
|
||||
fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank_[channel], program);
|
||||
}
|
||||
|
||||
void FluidSynthInstrument::pitchBend(uint32_t channel, uint32_t bend14) {
|
||||
|
||||
+49
-26
@@ -49,13 +49,29 @@ static void sleep_ms(uint32_t ms) {
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Native VST editor windows registry — global de WM_DESTROY (chay tren worker
|
||||
// thread cua channel tao window) co the don map. USERDATA luu channel+1 (KHONG
|
||||
// luu con tro inst truc tiep: assign() thay inst moi moi lan load — con tro cu
|
||||
// bi huy → WM_DESTROY tren con tro dangling → crash/hang bridge).
|
||||
static InstrumentEngineManager* g_engine = nullptr;
|
||||
static std::mutex g_guiMutex;
|
||||
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
|
||||
static std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
|
||||
|
||||
static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
||||
if (uMsg == WM_DESTROY) {
|
||||
void* ptr = (void*)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
|
||||
if (ptr) {
|
||||
INativeInstrument* inst = static_cast<INativeInstrument*>(ptr);
|
||||
inst->closeGUI();
|
||||
INativeInstrument* instToClose = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_hwndToCh.find(hwnd);
|
||||
if (it != g_hwndToCh.end()) {
|
||||
uint32_t ch = it->second;
|
||||
g_hwndToCh.erase(it);
|
||||
g_guiWindows.erase(ch);
|
||||
if (g_engine) instToClose = g_engine->get(ch);
|
||||
}
|
||||
}
|
||||
if (instToClose) instToClose->closeGUI();
|
||||
}
|
||||
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
@@ -184,12 +200,15 @@ int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
|
||||
InstrumentEngineManager instruments;
|
||||
#ifdef _WIN32
|
||||
g_engine = &instruments;
|
||||
#endif
|
||||
// Per-channel persistent workers: loadPlugin + openGUI run on the SAME
|
||||
// thread whose COM STA apartment stays alive for the channel's lifetime
|
||||
// (see ChannelWorker comment — a dead apartment hangs Nexus attached()).
|
||||
std::map<uint32_t, std::unique_ptr<ChannelWorker>> workers;
|
||||
// B9: native editor windows per channel — keep alive (HWND outlives the job).
|
||||
std::map<uint32_t, void*> guiWindows;
|
||||
// Registry la global (g_guiWindows) — WM_DESTROY cleanup can tu VstWindowProc.
|
||||
// B8: sample rate from the DAW (Rust spawns us with SF_SAMPLE_RATE).
|
||||
// Block size is fixed by the SHM layout (AUDIO_BLOCK_SIZE) — SF_BLOCK_SIZE
|
||||
// is accepted but must match, otherwise warned and ignored.
|
||||
@@ -310,50 +329,55 @@ int main(int argc, char* argv[]) {
|
||||
// cho openGUI lai tao COM apartment moi, con plugin thi song o
|
||||
// apartment cu da chet (load thread exit) -> Nexus attached()
|
||||
// hang (gui_probe: bridge_like treo, same_thread OK).
|
||||
// LOAD va OPEN_GUI duoc drain trong cung vong lap: LOAD post job
|
||||
// len worker (async, VST3 init co the mat giay) truoc khi type=4
|
||||
// duoc xu ly — khong doi, instruments.get() con rong -> "no
|
||||
// instrument loaded". Poll toi da 10s cho LOAD hoan tat.
|
||||
uint32_t guiCh = c.channel;
|
||||
if (guiCh >= 16) guiCh = 0;
|
||||
for (int tries = 0; tries < 200 && !instruments.get(guiCh); ++tries) {
|
||||
sleep_ms(50);
|
||||
}
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << c.arg1
|
||||
<< " plugin=" << c.arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
||||
} else {
|
||||
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
|
||||
workers[guiCh]->post([&instruments, &guiWindows, guiCh, arg1 = c.arg1, arg2 = std::string(c.arg2)]() {
|
||||
// Bo poll 10s tren real-time loop (writeIndex stall > 3s -> Rust
|
||||
// tuong bridge chet va restart -> 2 bridge cung map SHM -> race).
|
||||
// LOAD (type=2) post truoc OPEN_GUI tren CUNG ChannelWorker (FIFO)
|
||||
// -> job openGUI chay sau khi LOAD xong -> kiem tra inst trong job.
|
||||
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
|
||||
workers[guiCh]->post([&instruments, guiCh, arg1 = c.arg1, arg2 = std::string(c.arg2)]() {
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << arg1
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
||||
return;
|
||||
}
|
||||
std::cerr << "[dbg] openGUI thread start hwnd=" << arg1
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
void* hwnd = (void*)(uintptr_t)arg1;
|
||||
#ifdef _WIN32
|
||||
if (arg1 == 0) {
|
||||
HWND existingHwnd = nullptr;
|
||||
auto it = guiWindows.find(guiCh);
|
||||
if (it != guiWindows.end()) {
|
||||
existingHwnd = (HWND)it->second;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
auto it = g_guiWindows.find(guiCh);
|
||||
if (it != g_guiWindows.end()) existingHwnd = (HWND)it->second;
|
||||
}
|
||||
if (existingHwnd && IsWindow(existingHwnd)) {
|
||||
hwnd = existingHwnd;
|
||||
SetWindowTextA((HWND)hwnd, arg2.c_str());
|
||||
ShowWindow((HWND)hwnd, SW_SHOW);
|
||||
SetForegroundWindow((HWND)hwnd);
|
||||
// Reuse: cap nhat USERDATA (channel+1) — inst CU
|
||||
// da bi thay the boi assign() -> WM_DESTROY sau
|
||||
// nay lookup inst MOI, khong dung con tro dangling.
|
||||
SetWindowLongPtrA((HWND)hwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
} else {
|
||||
hwnd = create_native_vst_window(arg2.c_str());
|
||||
if (!hwnd) {
|
||||
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl;
|
||||
return;
|
||||
}
|
||||
guiWindows[guiCh] = hwnd; // keep window alive
|
||||
if (auto* inst = instruments.get(guiCh)) {
|
||||
SetWindowLongPtrA((HWND)hwnd, GWLP_USERDATA, (LONG_PTR)inst);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
g_guiWindows[guiCh] = hwnd; // keep window alive
|
||||
g_hwndToCh[(HWND)hwnd] = guiCh; // WM_DESTROY cleanup
|
||||
}
|
||||
SetWindowLongPtrA((HWND)hwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)guiWindows;
|
||||
(void)0;
|
||||
#endif
|
||||
if (auto* inst = instruments.get(guiCh)) {
|
||||
if (inst->openGUI(hwnd))
|
||||
@@ -366,7 +390,6 @@ int main(int argc, char* argv[]) {
|
||||
// Editor windows song tren thread nay — ChannelWorker
|
||||
// pump message queue khi idle (xem class comment).
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
shmIPC->controlQueueCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user