528 lines
21 KiB
C++
528 lines
21 KiB
C++
// native_bridge/src/NativeInstrumentEngine.cpp
|
|
#include "NativeInstrumentEngine.h"
|
|
|
|
#include <fluidsynth.h>
|
|
#include <sfizz.hpp>
|
|
#ifdef _WIN32
|
|
#include "Vst3Instrument.h"
|
|
#include "Vst2Instrument.h"
|
|
#include "SandboxVst3Host.h"
|
|
#endif
|
|
|
|
// void* members keep fluid types out of the public header; cast here.
|
|
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
|
|
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <thread>
|
|
#include <chrono>
|
|
|
|
// -----------------------------------------------------------------
|
|
// 1. SOUNDFONT ENGINE (.SF2 / .SF3) VIA FLUIDSYNTH C API
|
|
// -----------------------------------------------------------------
|
|
FluidSynthInstrument::FluidSynthInstrument()
|
|
: 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);
|
|
if (settings) delete_fluid_settings(FS_SETTINGS);
|
|
}
|
|
|
|
bool FluidSynthInstrument::loadSoundFontFile(const std::string& path, double sampleRate) {
|
|
if (synth) { delete_fluid_synth(FS_SYNTH); synth = nullptr; }
|
|
if (settings) { delete_fluid_settings(FS_SETTINGS); settings = nullptr; }
|
|
settings = new_fluid_settings();
|
|
fluid_settings_setnum(FS_SETTINGS, "synth.sample-rate", sampleRate);
|
|
fluid_settings_setint(FS_SETTINGS, "synth.polyphony", 256);
|
|
fluid_settings_setint(FS_SETTINGS, "synth.verbose", 0);
|
|
synth = new_fluid_synth(FS_SETTINGS);
|
|
if (!synth) return false;
|
|
sfontId = fluid_synth_sfload(FS_SYNTH, path.c_str(), 1);
|
|
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;
|
|
program_[ch] = 0;
|
|
fluid_synth_program_select(FS_SYNTH, ch, sfontId, 0, 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FluidSynthInstrument::init(double sampleRate, uint32_t maxBlockSize) {
|
|
return synth != nullptr;
|
|
}
|
|
|
|
void FluidSynthInstrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {
|
|
if (!synth || channel >= 16) return;
|
|
bank_[channel] = bank;
|
|
program_[channel] = program;
|
|
fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank, program);
|
|
}
|
|
|
|
void FluidSynthInstrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) {
|
|
if (!synth) return;
|
|
int velInt = static_cast<int>(velocity * 127.0f);
|
|
fluid_synth_noteon(FS_SYNTH, channel, pitch, velInt);
|
|
}
|
|
|
|
void FluidSynthInstrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) {
|
|
if (!synth) return;
|
|
fluid_synth_noteoff(FS_SYNTH, channel, pitch);
|
|
}
|
|
|
|
void FluidSynthInstrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
|
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 || 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).
|
|
program_[channel] = program;
|
|
fluid_synth_program_select(FS_SYNTH, channel, sfontId, bank_[channel], program);
|
|
}
|
|
|
|
void FluidSynthInstrument::pitchBend(uint32_t channel, uint32_t bend14) {
|
|
if (!synth) return;
|
|
// fluid_synth_pitch_bend takes the raw 14-bit value (center 8192).
|
|
fluid_synth_pitch_bend(FS_SYNTH, channel, bend14);
|
|
}
|
|
|
|
bool FluidSynthInstrument::openGUI(void* parentWindowHandle) {
|
|
return false; // SoundFont uses Web GUI Manager / Reskinned Knobs
|
|
}
|
|
|
|
void FluidSynthInstrument::closeGUI() {}
|
|
|
|
// G2.1: expose last bank/program for the state snapshot.
|
|
uint32_t FluidSynthInstrument::bankOf(uint32_t channel) const {
|
|
return channel < 16 ? bank_[channel] : 0;
|
|
}
|
|
|
|
uint32_t FluidSynthInstrument::programOf(uint32_t channel) const {
|
|
return channel < 16 ? program_[channel] : 0;
|
|
}
|
|
|
|
void FluidSynthInstrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
|
|
if (!synth) return;
|
|
fluid_synth_write_float(FS_SYNTH, numSamples, outputL, 0, 1, outputR, 0, 1);
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// 2. SFZ ENGINE (.SFZ) VIA SFIZZ C++ API
|
|
// -----------------------------------------------------------------
|
|
bool SfizzInstrument::loadSfzFile(const std::string& path, double sampleRate) {
|
|
sfizzSynth.setSampleRate(sampleRate);
|
|
return sfizzSynth.loadSfzFile(path);
|
|
}
|
|
|
|
bool SfizzInstrument::init(double sampleRate, uint32_t maxBlockSize) {
|
|
sfizzSynth.setSampleRate(sampleRate);
|
|
sfizzSynth.setSamplesPerBlock(maxBlockSize);
|
|
return true;
|
|
}
|
|
|
|
void SfizzInstrument::selectProgram(uint32_t channel, uint32_t bank, uint32_t program) {}
|
|
|
|
void SfizzInstrument::noteOn(uint32_t channel, uint32_t pitch, float velocity, uint32_t sampleOffset) {
|
|
sfizzSynth.hdNoteOn(sampleOffset, pitch, velocity);
|
|
}
|
|
|
|
void SfizzInstrument::noteOff(uint32_t channel, uint32_t pitch, uint32_t sampleOffset) {
|
|
sfizzSynth.hdNoteOff(sampleOffset, pitch, 0.0f);
|
|
}
|
|
|
|
void SfizzInstrument::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
|
sfizzSynth.cc(0, static_cast<int>(cc), static_cast<int>(value));
|
|
}
|
|
|
|
void SfizzInstrument::programChange(uint32_t channel, uint32_t program) {
|
|
sfizzSynth.programChange(0, static_cast<int>(program));
|
|
}
|
|
|
|
void SfizzInstrument::pitchBend(uint32_t channel, uint32_t bend14) {
|
|
sfizzSynth.pitchWheel(0, static_cast<int>(bend14));
|
|
}
|
|
|
|
bool SfizzInstrument::openGUI(void* parentWindowHandle) { return false; }
|
|
void SfizzInstrument::closeGUI() {}
|
|
|
|
void SfizzInstrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
|
|
float* channels[2] = { outputL, outputR };
|
|
sfizzSynth.renderBlock(channels, numSamples, 1); // numOutputs=1 = stereo L/R pair (2 ch)
|
|
}
|
|
|
|
// -----------------------------------------------------------------
|
|
// 3. MULTI-CHANNEL INSTRUMENT MANAGER (A10)
|
|
// -----------------------------------------------------------------
|
|
std::unique_ptr<INativeInstrument> InstrumentEngineManager::create_instrument(InstrumentType type) {
|
|
switch (type) {
|
|
case InstrumentType::SOUNDFONT_SF2_SF3: return std::make_unique<FluidSynthInstrument>();
|
|
case InstrumentType::SFZ: return std::make_unique<SfizzInstrument>();
|
|
#ifdef _WIN32
|
|
case InstrumentType::VST3:
|
|
// G4.2: SF_SANDBOX_VST3=1 -> host the VST3 in a child
|
|
// process (plugin_host.exe). Crash kills only the child.
|
|
if (std::getenv("SF_SANDBOX_VST3"))
|
|
return std::make_unique<SandboxVst3Host>();
|
|
return std::make_unique<Vst3Instrument>();
|
|
case InstrumentType::VST2:
|
|
return std::make_unique<Vst2Instrument>();
|
|
#else
|
|
case InstrumentType::VST3:
|
|
case InstrumentType::VST2:
|
|
// Linux bridge: SF2/SF3 + SFZ only (Docker render). VST3/VST2
|
|
// hosting is Windows-only (COM/HWND/SHM realtime loop).
|
|
return nullptr;
|
|
#endif
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
|
const std::string& path, double sampleRate,
|
|
uint32_t blockSize) {
|
|
if (channel >= 16) return false;
|
|
auto inst = create_instrument(type);
|
|
if (!inst) return false;
|
|
bool loaded = false;
|
|
if (type == InstrumentType::SOUNDFONT_SF2_SF3)
|
|
loaded = static_cast<FluidSynthInstrument*>(inst.get())->loadSoundFontFile(path, sampleRate);
|
|
else if (type == InstrumentType::SFZ)
|
|
loaded = static_cast<SfizzInstrument*>(inst.get())->loadSfzFile(path, sampleRate);
|
|
#ifdef _WIN32
|
|
else if (type == InstrumentType::VST3) {
|
|
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
|
loaded = v->loadPlugin(path, sampleRate);
|
|
else if (auto* s = dynamic_cast<SandboxVst3Host*>(inst.get()))
|
|
loaded = s->loadPlugin(path, sampleRate, channel);
|
|
}
|
|
else if (type == InstrumentType::VST2) {
|
|
if (auto* v2 = dynamic_cast<Vst2Instrument*>(inst.get())) {
|
|
v2->setChannel(channel);
|
|
loaded = v2->loadPlugin(path, sampleRate);
|
|
}
|
|
}
|
|
if (type == InstrumentType::VST3) {
|
|
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
|
v->setChannel(channel);
|
|
}
|
|
#endif
|
|
if (!loaded) return false;
|
|
// init() AFTER load: FluidSynth creates its synth inside loadSoundFontFile.
|
|
if (!inst->init(sampleRate, blockSize)) return false;
|
|
// Replacing an existing instrument drops its voices with the old engine.
|
|
// Load may run on a detached thread (VST3 init is slow): only the map
|
|
// write is under the mutex so renderAll on the audio loop never stalls.
|
|
// CRITICAL: the OLD instrument is destroyed AFTER mu_ is released. VST3
|
|
// teardown (setProcessing(false) / terminate / view removed) can block;
|
|
// under the lock it would stall renderAll -> bridge Not Responding,
|
|
// transport stop hangs, notes never turn off (must kill daw_engine).
|
|
std::unique_ptr<INativeInstrument> oldInst;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
auto it = channels_.find(channel);
|
|
if (it != channels_.end()) oldInst = std::move(it->second);
|
|
channels_[channel] = std::move(inst);
|
|
paths_[channel] = path;
|
|
types_[channel] = type;
|
|
// Fresh instance is by construction not reloading — clear the channel
|
|
// flag so real-time MIDI dispatch (which drops reloading channels)
|
|
// flows to it again. G3.3: a reload also clears the crash-mute.
|
|
reloadingCh_[channel] = false;
|
|
crashedCh_[channel] = false;
|
|
}
|
|
if (oldInst) {
|
|
isBypassed_.store(true);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
try {
|
|
oldInst.reset();
|
|
} catch (...) {
|
|
isBypassed_.store(false);
|
|
throw;
|
|
}
|
|
isBypassed_.store(false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void InstrumentEngineManager::unload(uint32_t channel) {
|
|
if (channel >= 16) return;
|
|
// Destructor runs on the CALLER thread (VST3 terminate must run on the
|
|
// channel worker). The map write is under mu_ so renderAll never stalls.
|
|
std::unique_ptr<INativeInstrument> oldInst;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
auto it = channels_.find(channel);
|
|
if (it != channels_.end()) oldInst = std::move(it->second);
|
|
channels_.erase(channel);
|
|
paths_.erase(channel);
|
|
types_.erase(channel);
|
|
reloadingCh_[channel] = false;
|
|
}
|
|
if (oldInst) {
|
|
isBypassed_.store(true);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
try {
|
|
oldInst.reset();
|
|
} catch (...) {
|
|
isBypassed_.store(false);
|
|
throw;
|
|
}
|
|
isBypassed_.store(false);
|
|
}
|
|
}
|
|
|
|
std::string InstrumentEngineManager::pathOf(uint32_t channel) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
auto it = paths_.find(channel);
|
|
return it == paths_.end() ? std::string() : it->second;
|
|
}
|
|
|
|
bool InstrumentEngineManager::snapshot(BridgeStateSnapshot& out) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
out.instruments.clear();
|
|
for (auto& kv : channels_) {
|
|
uint32_t ch = kv.first;
|
|
if (ch < 16 && reloadingCh_[ch]) continue; // old instance dying
|
|
InstrumentStateEntry e;
|
|
e.channel = ch;
|
|
auto tp = types_.find(ch);
|
|
e.type = tp == types_.end() ? 0 : (int)tp->second;
|
|
auto pp = paths_.find(ch);
|
|
e.path = pp == paths_.end() ? std::string() : pp->second;
|
|
if (INativeInstrument* i = kv.second.get()) {
|
|
// VST3: refresh the saved buffers then serialize them. Safe
|
|
// here: mu_ is held, so the audio loop cannot be inside
|
|
// process() on this instance while getState runs.
|
|
i->captureState();
|
|
e.presetBase64 = i->serializeState();
|
|
e.bank = i->bankOf(ch);
|
|
e.program = i->programOf(ch);
|
|
}
|
|
out.instruments.push_back(std::move(e));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
INativeInstrument* InstrumentEngineManager::get(uint32_t channel) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
auto it = channels_.find(channel);
|
|
return it == channels_.end() ? nullptr : it->second.get();
|
|
}
|
|
|
|
bool InstrumentEngineManager::has(uint32_t channel) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
return channels_.count(channel) != 0;
|
|
}
|
|
|
|
void InstrumentEngineManager::setReloading(uint32_t channel, bool on) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channel < 16) reloadingCh_[channel] = on;
|
|
auto it = channels_.find(channel);
|
|
if (it != channels_.end()) it->second->setReloading(on);
|
|
}
|
|
|
|
bool InstrumentEngineManager::isReloading(uint32_t channel) const {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
return channel < 16 && reloadingCh_[channel];
|
|
}
|
|
|
|
void InstrumentEngineManager::markCrashed(uint32_t channel) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channel < 16) crashedCh_[channel] = true;
|
|
}
|
|
|
|
bool InstrumentEngineManager::isCrashed(uint32_t channel) const {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
return channel < 16 && crashedCh_[channel];
|
|
}
|
|
|
|
// Quiet = reloading (mid-rebuild) OR crashed (G3.3). G1.4: bo
|
|
// mute-when-editor-open — editor co the mo trong luc PLAY, audio loop van
|
|
// process() binh thuong. G3.3: channel crash -> mute vi trang thai plugin
|
|
// khong xac dinh sau fault; xu ly tiep se fault lai.
|
|
// Called with mu_ held by the real-time dispatch.
|
|
bool InstrumentEngineManager::channelQuiet(uint32_t ch) const {
|
|
if (ch >= 16 || reloadingCh_[ch] || crashedCh_[ch]) return true;
|
|
return false;
|
|
}
|
|
|
|
// Real-time MIDI dispatch: hold mu_ for the WHOLE call so assign()/unload()
|
|
// (map swap + old-instance destruction outside the lock) and reload()/reloadForGUI()
|
|
// (state_ deleted in place on the worker) can never destroy/free the instance
|
|
// while dispatch is inside a method on it — use-after-free when loading a new
|
|
// VSTi while other channels keep playing. Reloading channels are skipped: their
|
|
// instance is mid-teardown and must not be touched.
|
|
void InstrumentEngineManager::noteOn(uint32_t channel, uint32_t pitch, float velocity) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channelQuiet(channel)) return;
|
|
auto it = channels_.find(channel);
|
|
if (it == channels_.end()) return;
|
|
it->second->noteOn(channel, pitch, velocity, 0);
|
|
}
|
|
|
|
void InstrumentEngineManager::noteOff(uint32_t channel, uint32_t pitch) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channelQuiet(channel)) return;
|
|
auto it = channels_.find(channel);
|
|
if (it == channels_.end()) return;
|
|
it->second->noteOff(channel, pitch, 0);
|
|
}
|
|
|
|
void InstrumentEngineManager::controlChange(uint32_t channel, uint32_t cc, uint32_t value) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channelQuiet(channel)) return;
|
|
auto it = channels_.find(channel);
|
|
if (it == channels_.end()) return;
|
|
it->second->controlChange(channel, cc, value);
|
|
}
|
|
|
|
void InstrumentEngineManager::programChange(uint32_t channel, uint32_t program) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channelQuiet(channel)) return;
|
|
auto it = channels_.find(channel);
|
|
if (it == channels_.end()) return;
|
|
it->second->programChange(channel, program);
|
|
}
|
|
|
|
void InstrumentEngineManager::pitchBend(uint32_t channel, uint32_t bend14) {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
if (channelQuiet(channel)) return;
|
|
auto it = channels_.find(channel);
|
|
if (it == channels_.end()) return;
|
|
it->second->pitchBend(channel, bend14);
|
|
}
|
|
|
|
void InstrumentEngineManager::allNotesOff() {
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
for (auto& [ch, inst] : channels_) {
|
|
if (channelQuiet(ch)) continue; // mid-rebuild / editor open: do not touch the instance
|
|
for (uint32_t n = 0; n < 128; ++n) inst->noteOff(ch, n, 0);
|
|
}
|
|
}
|
|
|
|
// Some commercial plugins (DUNE 3) use aligned SIMD loads/stores on their
|
|
// audio buffers; plain std::vector is only 16B-aligned. Over-allocate and
|
|
// align the pointer handed to plugins to 64B.
|
|
static float* alignF(std::vector<float>& v, size_t n) {
|
|
if (v.size() < n + 16) v.assign(n + 16, 0.0f);
|
|
uintptr_t a = reinterpret_cast<uintptr_t>(v.data());
|
|
return reinterpret_cast<float*>((a + 63) & ~(uintptr_t)63);
|
|
}
|
|
|
|
// G3.3: SEH cannot live inside a function that needs C++ unwinding (C2712),
|
|
// so per-channel process() runs in this helper. A plugin access violation
|
|
// (0xC0000005) is caught here — the channel is muted instead of killing the
|
|
// whole bridge process. /EHa (set in CMakeLists) allows mixing with the
|
|
// outer C++ try/catch net in main.cpp.
|
|
#ifdef _WIN32
|
|
static DWORD g_sehCode = 0;
|
|
static void* g_sehAddr = nullptr;
|
|
#endif
|
|
#ifdef _WIN32
|
|
static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR, uint32_t n, DWORD* outCode) {
|
|
*outCode = 0;
|
|
__try {
|
|
inst->processAudioBlock(outL, outR, n);
|
|
return true;
|
|
} __except (g_sehCode = GetExceptionCode(),
|
|
g_sehAddr = GetExceptionInformation()->ExceptionRecord->ExceptionAddress,
|
|
EXCEPTION_EXECUTE_HANDLER) {
|
|
*outCode = g_sehCode;
|
|
return false;
|
|
}
|
|
}
|
|
#else
|
|
static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR, uint32_t n, uint32_t* outCode) {
|
|
*outCode = 0;
|
|
try {
|
|
inst->processAudioBlock(outL, outR, n);
|
|
return true;
|
|
} catch (...) {
|
|
*outCode = 1;
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t numSamples) {
|
|
if (isBypassed_.load()) {
|
|
std::memset(outputL, 0, numSamples * sizeof(float));
|
|
std::memset(outputR, 0, numSamples * sizeof(float));
|
|
return;
|
|
}
|
|
std::lock_guard<std::mutex> lock(mu_);
|
|
std::memset(outputL, 0, numSamples * sizeof(float));
|
|
std::memset(outputR, 0, numSamples * sizeof(float));
|
|
if (channels_.empty()) return;
|
|
for (auto& [ch, inst] : channels_) {
|
|
if (channelQuiet(ch)) continue; // editor open / crashed: do not process
|
|
float* spL = alignF(scratchL_, numSamples);
|
|
float* spR = alignF(scratchR_, numSamples);
|
|
std::memset(spL, 0, numSamples * sizeof(float));
|
|
std::memset(spR, 0, numSamples * sizeof(float));
|
|
#ifdef _WIN32
|
|
DWORD sehCode = 0;
|
|
#else
|
|
uint32_t sehCode = 0;
|
|
#endif
|
|
if (!SafeProcessChannel(inst.get(), spL, spR, numSamples, &sehCode)) {
|
|
// G3.3: plugin fault — mute this channel only, keep the bridge up.
|
|
if (!crashedCh_[ch]) {
|
|
crashedCh_[ch] = true;
|
|
#ifdef _WIN32
|
|
{
|
|
HMODULE m = nullptr;
|
|
char mod[MAX_PATH] = "?";
|
|
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
(LPCSTR)g_sehAddr, &m))
|
|
GetModuleFileNameA(m, mod, MAX_PATH);
|
|
std::cerr << "[NativeBridge] G3.3: channel " << ch
|
|
<< " plugin crashed in processAudioBlock (SEH code=0x" << std::hex << sehCode
|
|
<< " addr=0x" << (uintptr_t)g_sehAddr
|
|
<< std::dec << " mod=" << mod
|
|
<< ") — muted until reload"
|
|
<< std::endl;
|
|
void* bt[16];
|
|
USHORT nf = RtlCaptureStackBackTrace(0, 16, bt, nullptr);
|
|
std::cerr << "[NativeBridge] stack:";
|
|
for (USHORT fi = 0; fi < nf; ++fi) {
|
|
HMODULE bm = nullptr;
|
|
char bmod[MAX_PATH] = "?";
|
|
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
|
(LPCSTR)bt[fi], &bm))
|
|
GetModuleFileNameA(bm, bmod, MAX_PATH);
|
|
std::cerr << " " << std::hex << (uintptr_t)bt[fi] << std::dec
|
|
<< "(" << bmod << ")";
|
|
}
|
|
std::cerr << std::endl;
|
|
}
|
|
#else
|
|
std::cerr << "[NativeBridge] G3.3: channel " << ch
|
|
<< " plugin crashed in processAudioBlock — muted until reload"
|
|
<< std::endl;
|
|
#endif
|
|
}
|
|
continue;
|
|
}
|
|
for (uint32_t i = 0; i < numSamples; ++i) {
|
|
outputL[i] += spL[i];
|
|
outputR[i] += spR[i];
|
|
}
|
|
}
|
|
}
|