refactor(bridge): split fx bridge, stabilize VST3 host chain, drop old install engines
- remove diagnostic canaries from audio hot path (crackle fix) and stuck C5 note raw-MIDI test (Vst2Instrument::init noteOff) - split native bridge into daw_vst_bridge (VSTi) + fx_vst_bridge (VST FX) - VST3: processData prepare before setActive, zero input buses, accept both .vstpreset and bridge-state blob in RenderJob presets - native_render/vst_engine: bridge-exe discovery + render_fx_chain + /fx-gui - delete old install/ engine binaries (install via sidecar+resources now) - add BUILD_LAYOUT.md, build_selfcheck, tools/ diagnostics
This commit is contained in:
@@ -24,7 +24,6 @@
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#ifdef _WIN32
|
||||
#include <malloc.h> // _heapchk (TEMP diag)
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
@@ -438,9 +437,8 @@ void InstrumentEngineManager::allNotesOff() {
|
||||
// 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) {
|
||||
// 64B-aligned pointer can sit up to 63B into the allocation; TEMP diag:
|
||||
// huge tail so we can measure how far DUNE writes past n.
|
||||
const size_t kTail = 2097152 + 64;
|
||||
// 64B-aligned pointer can sit up to 63B into the allocation.
|
||||
const size_t kTail = 64;
|
||||
if (v.size() < n + kTail) v.assign(n + kTail, 0.0f);
|
||||
uintptr_t a = reinterpret_cast<uintptr_t>(v.data());
|
||||
return reinterpret_cast<float*>((a + 63) & ~(uintptr_t)63);
|
||||
@@ -562,17 +560,6 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
|
||||
if (channelQuiet(ch)) continue; // editor open / crashed: do not process
|
||||
float* spL = alignF(scratchL_, numSamples);
|
||||
float* spR = alignF(scratchR_, numSamples);
|
||||
std::cerr << "[NativeBridge][diag] renderAll ch=" << ch << " n=" << numSamples
|
||||
<< " spL=0x" << std::hex << (uintptr_t)spL
|
||||
<< " spR=0x" << (uintptr_t)spR
|
||||
<< " capL=" << std::dec << scratchL_.size()
|
||||
<< " capR=" << scratchR_.size() << std::endl;
|
||||
// TEMP diag: fill canary with a NaN pattern so writes of 0.0 (silence)
|
||||
// are still detected.
|
||||
for (uint32_t i = 0; i < numSamples + 2097152; ++i) {
|
||||
spL[i] = 0x7FC00000u; // quiet NaN
|
||||
spR[i] = 0x7FC00000u;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
DWORD sehCode = 0;
|
||||
#else
|
||||
@@ -617,28 +604,6 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// TEMP diag: plugin output-overrun canary + heap health.
|
||||
{
|
||||
static uint64_t diagBlock = 0; ++diagBlock;
|
||||
int farL = -1, farR = -1;
|
||||
for (uint32_t i = 0; i < 2097152; ++i) {
|
||||
uint32_t bL, bR;
|
||||
std::memcpy(&bL, &spL[numSamples + i], 4);
|
||||
std::memcpy(&bR, &spR[numSamples + i], 4);
|
||||
if (bL != 0x7FC00000u) farL = (int)i;
|
||||
if (bR != 0x7FC00000u) farR = (int)i;
|
||||
}
|
||||
if (farL >= 0 || farR >= 0)
|
||||
std::cerr << "[NativeBridge][diag] block " << diagBlock
|
||||
<< " OUTPUT OVERRUN past n: L=" << farL
|
||||
<< " R=" << farR << " (ch=" << ch << ")" << std::endl;
|
||||
#ifdef _WIN32
|
||||
int h = _heapchk();
|
||||
if (h != _HEAPOK)
|
||||
std::cerr << "[NativeBridge][diag] block " << diagBlock
|
||||
<< " heap NOT OK code=" << h << std::endl;
|
||||
#endif
|
||||
}
|
||||
// Auto-normalize: makeup calibrated (constant per-channel gain, no steps).
|
||||
auto mit = makeup_.find(ch);
|
||||
const float g = (mit != makeup_.end()) ? mit->second : 1.0f;
|
||||
|
||||
@@ -351,11 +351,24 @@ int run_render_job(const std::string& jobPath, const std::string& outPath) {
|
||||
std::cerr << "[RenderJob] preset ignored for non-VST3 type " << (int)type
|
||||
<< " (SF2/SFZ use soundfont_bank/soundfont_program)" << std::endl;
|
||||
#ifdef HAVE_VST3SDK
|
||||
} else if (!applyVst3Preset(mgr.get(0), preset)) {
|
||||
fail("preset import FAILED for " + pluginPath);
|
||||
return rc; // rc stays 1: job/argument error
|
||||
} else {
|
||||
std::cout << "[RenderJob] preset imported (VST3 .vstpreset) type=" << (int)type << std::endl;
|
||||
// Accept BOTH .vstpreset ('VST3' magic) and the bridge state
|
||||
// blob ([4B BE compLen][comp][4B BE ctrlLen][ctrl]) -- realtime
|
||||
// LOAD restores with loadSerializedState, so offline --render
|
||||
// must too (plugins whose default preset is silent -- Ample,
|
||||
// Toontrack, Kong Audio -- otherwise render digital silence).
|
||||
std::vector<uint8_t> head;
|
||||
bool isVstPreset = base64Decode(preset, head) && head.size() >= 4 &&
|
||||
std::memcmp(head.data(), "VST3", 4) == 0;
|
||||
if (!isVstPreset) {
|
||||
mgr.get(0)->loadSerializedState(preset);
|
||||
std::cout << "[RenderJob] preset restored (bridge state blob) type=" << (int)type << std::endl;
|
||||
} else if (!applyVst3Preset(mgr.get(0), preset)) {
|
||||
fail("preset import FAILED for " + pluginPath);
|
||||
return rc; // rc stays 1: job/argument error
|
||||
} else {
|
||||
std::cout << "[RenderJob] preset imported (VST3 .vstpreset) type=" << (int)type << std::endl;
|
||||
}
|
||||
}
|
||||
#else
|
||||
} else {
|
||||
|
||||
@@ -25,8 +25,6 @@ std::unordered_map<const void*, Vst2Instrument*> g_v2Registry;
|
||||
// the callback may be invoked with a null effect (some plugins query the
|
||||
// sample rate during construction).
|
||||
thread_local Vst2Instrument* t_loading = nullptr;
|
||||
// TEMP probe: log every audioMaster call while effEditOpen is running.
|
||||
thread_local bool t_editingProbe = false;
|
||||
// Resize hook registered by the bridge (main.cpp): plugin audioMaster
|
||||
// (SizeWindow) -> move the host window on the UI worker thread.
|
||||
static void (*g_vst2ResizeHook)(void*, int, int) = nullptr;
|
||||
@@ -98,9 +96,6 @@ struct Vst2HostState {
|
||||
intptr_t audioMaster(AEffect* effect, int32_t opcode, int32_t index,
|
||||
intptr_t value, void* ptr, float opt) {
|
||||
(void)index; (void)value; (void)ptr; (void)opt;
|
||||
if (t_editingProbe || true)
|
||||
std::cerr << "[Vst2Instrument][editprobe] audioMaster op=" << opcode
|
||||
<< " idx=" << index << " val=" << value << " ptr=" << ptr << std::endl;
|
||||
Vst2Instrument* self = nullptr;
|
||||
if (effect) {
|
||||
std::lock_guard<std::mutex> lock(g_v2Mutex);
|
||||
@@ -155,7 +150,6 @@ intptr_t audioMaster(AEffect* effect, int32_t opcode, int32_t index,
|
||||
ti.ppqPos = pos * 120.0 / 60.0 / 44100.0;
|
||||
ti.flags = kVstTempoValid | kVstTransportPlaying; // TEMP diag: DUNE silent - try forcing transport playing
|
||||
pos += (self ? self->maxBlockSize() : 512);
|
||||
std::cerr << "[Vst2Instrument][diag] getTime flags=" << ti.flags << " pos=" << pos << std::endl;
|
||||
return (intptr_t)&ti;
|
||||
}
|
||||
case kAudioMasterGetCurrentProcessLevel: return 1; // audio thread
|
||||
@@ -268,9 +262,7 @@ static intptr_t v2_dispatch(AEffect* fx, int32_t op, int32_t idx, intptr_t val,
|
||||
static void* g_sehAddr = nullptr;
|
||||
static CONTEXT g_ctxCopy;
|
||||
__try {
|
||||
if (op == kEffectEditOpen) t_editingProbe = true;
|
||||
intptr_t r = fx->dispatcher(fx, op, idx, val, ptr, opt);
|
||||
if (op == kEffectEditOpen) t_editingProbe = false;
|
||||
return r;
|
||||
} __except (g_sehCode = GetExceptionCode(),
|
||||
g_sehAddr = GetExceptionInformation()->ExceptionRecord->ExceptionAddress,
|
||||
@@ -388,73 +380,11 @@ bool Vst2Instrument::init(double sampleRate, uint32_t maxBlockSize) {
|
||||
maxBlockSize_ = maxBlockSize;
|
||||
AEffect* fx = s->effect;
|
||||
try {
|
||||
// TEMP diag variant B: sampleRate/blockSize BEFORE effOpen, block size
|
||||
// matches the actual render size (256), no StartProcess.
|
||||
fx->dispatcher(fx, kEffectSetSampleRate, 0, 0, nullptr, (float)sampleRate);
|
||||
fx->dispatcher(fx, kEffectSetBlockSize, 0, (intptr_t)maxBlockSize, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectOpen, 0, 0, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectSetProgram, 0, 0, nullptr, 0);
|
||||
fx->dispatcher(fx, kEffectMainsChanged, 0, 1, nullptr, 0);
|
||||
// TEMP diag: dump first params to see if the default patch is audible
|
||||
{
|
||||
const int pis[] = {0, 1, 10, 100, 500, 1000, 1160, 1168};
|
||||
for (int pi : pis) {
|
||||
if (pi >= fx->numParams) continue;
|
||||
char nm[128] = {0}, disp[128] = {0}, lbl[128] = {0};
|
||||
fx->dispatcher(fx, kEffectGetParamName, 0, pi, nm, 0);
|
||||
fx->dispatcher(fx, kEffectGetParamDisplay, 0, pi, disp, 0);
|
||||
fx->dispatcher(fx, kEffectGetParamLabel, 0, pi, lbl, 0);
|
||||
std::cerr << "[Vst2Instrument][diag] param " << pi << " name=" << nm << " disp=" << disp << " label=" << lbl << std::endl;
|
||||
}
|
||||
}
|
||||
// TEMP diag: does the plugin itself claim to receive MIDI?
|
||||
{
|
||||
const char* probes[] = {"receiveVstEvents", "receiveVstMidiEvent", "midiProgramNames", "bypass"};
|
||||
for (auto pr : probes) {
|
||||
intptr_t r = fx->dispatcher(fx, kEffectCanDo, 0, 0, (void*)pr, 0);
|
||||
std::cerr << "[Vst2Instrument][diag] plugin canDo " << pr << " -> " << r << std::endl;
|
||||
}
|
||||
}
|
||||
// TEMP diag: send raw MIDI directly (bypass SHM path). CC7 vol max + noteOn
|
||||
// with kVstMidiEventIsRealtime + retriggers.
|
||||
{
|
||||
auto sendMidi = [&](int b0, int b1, int b2) {
|
||||
VstMidiEvent e;
|
||||
std::memset(&e, 0, sizeof(e));
|
||||
e.type = kVstMidiType;
|
||||
e.byteSize = (int32_t)sizeof(e);
|
||||
e.flags = kVstMidiEventIsRealtime;
|
||||
e.midiData[0] = (char)b0; e.midiData[1] = (char)b1; e.midiData[2] = (char)b2;
|
||||
VstEvent* evs[1] = { reinterpret_cast<VstEvent*>(&e) };
|
||||
VstEvents events;
|
||||
std::memset(&events, 0, sizeof(events));
|
||||
events.numEvents = 1;
|
||||
events.events[0] = evs[0];
|
||||
fx->dispatcher(fx, kEffectProcessEvents, 0, 0, &events, 0);
|
||||
};
|
||||
std::cerr << "[Vst2Instrument][diag] raw CC7 127 + noteOn 60/100 x8" << std::endl;
|
||||
sendMidi(0xB0, 7, 127);
|
||||
for (int r = 0; r < 8; ++r) sendMidi(0x90, 60, 100);
|
||||
}
|
||||
// TEMP diag: does DUNE hold a preset chunk? dump its head as hex+ascii
|
||||
{
|
||||
void* chunkPtr = nullptr;
|
||||
intptr_t sz = fx->dispatcher(fx, kEffectGetChunk, 0, 1, &chunkPtr, 0);
|
||||
std::cerr << "[Vst2Instrument][diag] getChunk size=" << sz << " ptr=" << chunkPtr << std::endl;
|
||||
if (sz > 0 && chunkPtr) {
|
||||
const unsigned char* b = (const unsigned char*)chunkPtr;
|
||||
std::string ascii;
|
||||
for (intptr_t i = 0; i < sz && i < 512; ++i) {
|
||||
ascii += (b[i] >= 32 && b[i] < 127) ? (char)b[i] : '.';
|
||||
}
|
||||
std::cerr << "[Vst2Instrument][diag] chunkhead: " << ascii << std::endl;
|
||||
std::cerr << "[Vst2Instrument][diag] chunkbytes: ";
|
||||
for (intptr_t i = 0; i < sz && i < 64; ++i) {
|
||||
char hx[8]; snprintf(hx, sizeof(hx), "%02x ", b[i]); std::cerr << hx;
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
std::cerr << "[Vst2Instrument] init dispatcher EXCEPTION ch=" << channel_ << std::endl;
|
||||
return false;
|
||||
@@ -683,29 +613,25 @@ void Vst2Instrument::loadSerializedState(const std::string& base64) {
|
||||
}
|
||||
|
||||
void Vst2Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) {
|
||||
std::cerr << "[Vst2Instrument] pab START n=" << numSamples << std::endl;
|
||||
auto* s = static_cast<Vst2HostState*>(state_);
|
||||
std::cerr << "[Vst2Instrument] pab state ok=" << (s && s->effect) << std::endl;
|
||||
if (!s || !s->effect) {
|
||||
std::memset(outputL, 0, numSamples * sizeof(float));
|
||||
std::memset(outputR, 0, numSamples * sizeof(float));
|
||||
return;
|
||||
}
|
||||
std::cerr << "[Vst2Instrument] pab before alignF" << std::endl;
|
||||
AEffect* fx = s->effect;
|
||||
// 64B-align inputs: DUNE 3 etc. use aligned SIMD on audio buffers.
|
||||
auto alignF = [](std::vector<float>& v, size_t n) -> float* {
|
||||
// TEMP diag (heap-corrupt hunt): huge tail + input canary to measure
|
||||
// how far DUNE writes past n.
|
||||
const size_t kTail = 2097152 + 64;
|
||||
// 64B-aligned pointer can sit up to 63B into the allocation.
|
||||
const size_t kTail = 64;
|
||||
if (v.size() < n + kTail) v.assign(n + kTail, 0.0f);
|
||||
uintptr_t a = reinterpret_cast<uintptr_t>(v.data());
|
||||
return reinterpret_cast<float*>((a + 63) & ~(uintptr_t)63);
|
||||
};
|
||||
float* inL = alignF(inL_, numSamples);
|
||||
float* inR = alignF(inR_, numSamples);
|
||||
for (uint32_t i = 0; i < numSamples + 2097152; ++i) {
|
||||
inL[i] = 0.0f; // TEMP: clean input (NaN poisons effect plugins)
|
||||
for (uint32_t i = 0; i < numSamples; ++i) {
|
||||
inL[i] = 0.0f;
|
||||
inR[i] = 0.0f;
|
||||
}
|
||||
// Build pointer arrays covering the plugin's full ins/outs: plugins with
|
||||
@@ -731,42 +657,7 @@ void Vst2Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t
|
||||
}
|
||||
if (nIn == 0)
|
||||
inBufs[0] = nullptr; // ins=0: pass NULL input array entry
|
||||
std::cerr << "[Vst2Instrument] processReplacing IN ch=" << channel_ << " n=" << numSamples
|
||||
<< " ins=" << nIn << " outs=" << nOut
|
||||
<< " inL=0x" << std::hex << (uintptr_t)inL << " inR=0x" << (uintptr_t)inR
|
||||
<< " outL=0x" << (uintptr_t)outputL << " outR=0x" << (uintptr_t)outputR
|
||||
<< std::dec << std::endl;
|
||||
fx->processReplacing(fx, inBufs.data(), outBufs.data(), (int32_t)numSamples);
|
||||
std::cerr << "[Vst2Instrument] processReplacing OUT ch=" << channel_ << std::endl;
|
||||
// TEMP diag: does the plugin write any audio?
|
||||
{
|
||||
static int diagBlk = 0;
|
||||
float peakL = 0.f, peakR = 0.f;
|
||||
for (uint32_t i = 0; i < numSamples; ++i) {
|
||||
float a = outputL[i], b = outputR[i];
|
||||
if (a > peakL) peakL = a; else if (-a > peakL) peakL = -a;
|
||||
if (b > peakR) peakR = b; else if (-b > peakR) peakR = -b;
|
||||
}
|
||||
if (peakL != 0.f || peakR != 0.f || diagBlk < 10) {
|
||||
std::cerr << "[Vst2Instrument][diag] PEAK blk=" << diagBlk
|
||||
<< " n=" << numSamples << " L=" << peakL << " R=" << peakR << std::endl;
|
||||
diagBlk++;
|
||||
}
|
||||
}
|
||||
// TEMP diag: how far past n did DUNE write into the INPUT buffers?
|
||||
{
|
||||
int farL = -1, farR = -1;
|
||||
for (uint32_t i = 0; i < 2097152; ++i) {
|
||||
uint32_t bL, bR;
|
||||
std::memcpy(&bL, &inL[numSamples + i], 4);
|
||||
std::memcpy(&bR, &inR[numSamples + i], 4);
|
||||
if (bL != 0x7FC00000u) farL = (int)i;
|
||||
if (bR != 0x7FC00000u) farR = (int)i;
|
||||
}
|
||||
if (farL >= 0 || farR >= 0)
|
||||
std::cerr << "[Vst2Instrument][diag] INPUT OVERRUN past n: L=" << farL
|
||||
<< " R=" << farR << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
const void* Vst2Instrument::effect() const {
|
||||
|
||||
@@ -321,12 +321,6 @@ bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) {
|
||||
std::cerr << "[Vst3Instrument] setupProcessing failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
if (component->setActive(true) != kResultOk) {
|
||||
std::cerr << "[Vst3Instrument] setActive failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
processor->setProcessing(true);
|
||||
|
||||
// Build per-bus buffers sized maxBlockSize_ (HostProcessData owns them;
|
||||
// we must NOT override channelBuffers with external pointers — unprepare
|
||||
// would delete[] them).
|
||||
@@ -335,6 +329,13 @@ bool Vst3Instrument::loadPlugin(const std::string& path, double sampleRate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (component->setActive(true) != kResultOk) {
|
||||
std::cerr << "[Vst3Instrument] setActive failed" << std::endl;
|
||||
return false;
|
||||
}
|
||||
processor->setProcessing(true);
|
||||
|
||||
// Count output channels of bus 0 (mono plugins duplicate ch0 → both outs).
|
||||
int32 outChannels = 2;
|
||||
if (component->getBusCount(kAudio, kOutput) > 0) {
|
||||
@@ -848,6 +849,14 @@ void Vst3Instrument::processAudioBlock(float* outputL, float* outputR, uint32_t
|
||||
std::memset(ob.channelBuffers32[c], 0, numSamples * sizeof(float));
|
||||
}
|
||||
}
|
||||
if (s->processData.numInputs > 0) {
|
||||
for (int32 b = 0; b < s->processData.numInputs; ++b) {
|
||||
const Steinberg::Vst::AudioBusBuffers& ib = s->processData.inputs[b];
|
||||
for (int32 c = 0; c < ib.numChannels; ++c)
|
||||
if (ib.channelBuffers32[c])
|
||||
std::memset(ib.channelBuffers32[c], 0, numSamples * sizeof(float));
|
||||
}
|
||||
}
|
||||
tresult pr = processor->process(s->processData);
|
||||
float mx = 0.f;
|
||||
if (pr >= 0 && s->processData.numOutputs > 0) {
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
// native_bridge/src/main_fx.cpp
|
||||
// Entry point of fx_vst_bridge.exe — VST FX host bridge (task: split FX out
|
||||
// of daw_vst_bridge). Windows only. One-shot modes only:
|
||||
// --render-fx <job.json> --in <input.wav> --out <output.wav>
|
||||
// --scan <dir>
|
||||
// --open-fx-gui <job.json>
|
||||
// --fx-gui <job.json>
|
||||
// No SHM realtime loop: the realtime master FX chain (control type 6) stays
|
||||
// in daw_vst_bridge because it processes the live instrument mix in-process.
|
||||
#include "RenderFxJob.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <imm.h>
|
||||
#include <dbghelp.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
// Minidump on crash -> %TEMP%\sf_dumps (same convention as daw_vst_bridge).
|
||||
static LONG WINAPI FxCrashDumpHandler(EXCEPTION_POINTERS* ep) {
|
||||
static LONG once = 0;
|
||||
if (InterlockedCompareExchange(&once, 1, 0)) return EXCEPTION_CONTINUE_SEARCH;
|
||||
PEXCEPTION_RECORD er = ep ? ep->ExceptionRecord : nullptr;
|
||||
std::cerr << "[CRASH] fx_vst_bridge exception code=0x" << std::hex
|
||||
<< (er ? er->ExceptionCode : 0) << " addr=0x"
|
||||
<< (er ? (void*)er->ExceptionAddress : nullptr) << std::dec << std::endl;
|
||||
char dumpDir[MAX_PATH];
|
||||
if (GetTempPathA(MAX_PATH, dumpDir)) {
|
||||
std::strncat(dumpDir, "sf_dumps", MAX_PATH - std::strlen(dumpDir) - 1);
|
||||
CreateDirectoryA(dumpDir, nullptr);
|
||||
char dumpPath[MAX_PATH];
|
||||
std::snprintf(dumpPath, sizeof(dumpPath), "%s/fx_bridge_crash_%lu.dmp",
|
||||
dumpDir, GetCurrentProcessId());
|
||||
HMODULE hDbg = LoadLibraryA("dbghelp.dll");
|
||||
if (hDbg) {
|
||||
typedef BOOL(WINAPI* MiniDumpWriteDumpFn)(
|
||||
HANDLE, DWORD, HANDLE, MINIDUMP_TYPE,
|
||||
PMINIDUMP_EXCEPTION_INFORMATION, PMINIDUMP_USER_STREAM_INFORMATION,
|
||||
PMINIDUMP_CALLBACK_INFORMATION);
|
||||
auto pMiniDumpWriteDump =
|
||||
(MiniDumpWriteDumpFn)GetProcAddress(hDbg, "MiniDumpWriteDump");
|
||||
if (pMiniDumpWriteDump) {
|
||||
HANDLE hFile = CreateFileA(dumpPath, GENERIC_WRITE, 0, nullptr,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
MINIDUMP_EXCEPTION_INFORMATION mei = {};
|
||||
mei.ThreadId = GetCurrentThreadId();
|
||||
mei.ExceptionPointers = ep;
|
||||
mei.ClientPointers = TRUE;
|
||||
pMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
|
||||
hFile, MiniDumpNormal, &mei, nullptr,
|
||||
nullptr);
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
static LONG WINAPI FxVectoredCrashHandler(PEXCEPTION_POINTERS ep) {
|
||||
PEXCEPTION_RECORD er = ep ? ep->ExceptionRecord : nullptr;
|
||||
DWORD code = er ? er->ExceptionCode : 0;
|
||||
if (code != 0xC0000374 && code != 0xC0000005 && code != 0xC0000409)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
return FxCrashDumpHandler(ep);
|
||||
}
|
||||
|
||||
// Same daemon init as daw_vst_bridge main(): no console flash, no power
|
||||
// throttling, per-monitor DPI (plugin renderers must not be OS-scaled),
|
||||
// no default IME on the main thread (IME recursion fix). Redirected stdio
|
||||
// (bridge.log pipe / SF_FXGUI_PORT reader) survives FreeConsole.
|
||||
static void win_daemon_init() {
|
||||
if (!std::getenv("SF_KEEP_CONSOLE")) FreeConsole();
|
||||
#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
|
||||
#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x00000004
|
||||
#endif
|
||||
#ifndef PROCESS_POWER_THROTTLING_EXECUTION_SPEED
|
||||
#define PROCESS_POWER_THROTTLING_EXECUTION_SPEED 0x00000001
|
||||
#endif
|
||||
PROCESS_POWER_THROTTLING_STATE powerState = {};
|
||||
powerState.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
|
||||
powerState.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION |
|
||||
PROCESS_POWER_THROTTLING_EXECUTION_SPEED;
|
||||
powerState.StateMask = 0;
|
||||
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling,
|
||||
&powerState, sizeof(powerState));
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
ImmAssociateContextEx(nullptr, nullptr, IACE_DEFAULT);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
SetUnhandledExceptionFilter(FxCrashDumpHandler);
|
||||
AddVectoredExceptionHandler(1, FxVectoredCrashHandler);
|
||||
win_daemon_init();
|
||||
#endif
|
||||
// stderr, not stdout: stdout is reserved for machine-readable output in
|
||||
// one-shot modes (--scan JSON, --fx-gui "SF_FXGUI_PORT=...").
|
||||
std::cerr << "[FxBridge] Starting DAW FX Host Bridge..." << std::endl;
|
||||
|
||||
std::string renderFxJob, renderFxIn, renderFxOut;
|
||||
std::string scanDir;
|
||||
std::string openFxGuiJob;
|
||||
std::string fxGuiServerJob;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--render-fx") == 0 && i + 1 < argc)
|
||||
renderFxJob = argv[i + 1];
|
||||
else if (std::strcmp(argv[i], "--in") == 0 && i + 1 < argc)
|
||||
renderFxIn = argv[i + 1];
|
||||
else if (std::strcmp(argv[i], "--out") == 0 && i + 1 < argc)
|
||||
renderFxOut = argv[i + 1];
|
||||
else if (std::strcmp(argv[i], "--scan") == 0 && i + 1 < argc)
|
||||
scanDir = argv[i + 1];
|
||||
else if (std::strcmp(argv[i], "--open-fx-gui") == 0 && i + 1 < argc)
|
||||
openFxGuiJob = argv[i + 1];
|
||||
else if (std::strcmp(argv[i], "--fx-gui") == 0 && i + 1 < argc)
|
||||
fxGuiServerJob = argv[i + 1];
|
||||
}
|
||||
if (!renderFxJob.empty()) {
|
||||
if (renderFxIn.empty() || renderFxOut.empty()) {
|
||||
std::cerr << "[RenderFxJob] --render-fx requires --in <input.wav>"
|
||||
" --out <output.wav>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
return run_render_fx_job(renderFxJob, renderFxIn, renderFxOut);
|
||||
}
|
||||
if (!scanDir.empty()) return run_scan_dir(scanDir);
|
||||
if (!openFxGuiJob.empty()) return run_open_fx_gui(openFxGuiJob);
|
||||
if (!fxGuiServerJob.empty()) return run_fx_gui_server(fxGuiServerJob);
|
||||
|
||||
std::cerr << "fx_vst_bridge: no mode. Usage:\n"
|
||||
" --render-fx <job.json> --in <input.wav> --out <output.wav>\n"
|
||||
" --scan <dir>\n"
|
||||
" --open-fx-gui <job.json>\n"
|
||||
" --fx-gui <job.json>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
Reference in New Issue
Block a user