diff --git a/.gitignore b/.gitignore index cd8f64c..a5992cb 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,4 @@ native_bridge/debug/shm_selfcheck.exe # Linux-only runtime artifacts (built via Docker; never committed) install/daw_vst_bridge install/libsfizz.so.1 +build_juce diff --git a/.gitmodules b/.gitmodules index 27a9ebe..5b279e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,8 @@ [submodule "native_bridge/vst3sdk"] path = native_bridge/vst3sdk url = https://github.com/steinbergmedia/vst3sdk.git +[submodule "native_bridge/JUCE"] + path = native_bridge/JUCE + url = https://github.com/juce-framework/JUCE + branch = 8.0.7 + shallow = true diff --git a/app/core/fx_realtime.py b/app/core/fx_realtime.py index 2564aae..5fdb5f2 100644 --- a/app/core/fx_realtime.py +++ b/app/core/fx_realtime.py @@ -162,7 +162,8 @@ class FxRealtimeSession: return out def _spawn(self): - exe = find_fx_bridge_exe() + juce_exe = os.getenv("SF_JUCE_FX_BRIDGE_PATH", "").strip() + exe = juce_exe or find_fx_bridge_exe() if not exe: raise RuntimeError("Không tìm thấy fx_vst_bridge.exe") job = { @@ -174,8 +175,12 @@ class FxRealtimeSession: fd, self._job_path = tempfile.mkstemp(suffix=".json", prefix="fxrt_") with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(job, f) - cmd = [exe, "--realtime-fx", self._job_path, "--shm", self.name, - "--parent", str(os.getpid())] + if juce_exe: + cmd = [exe, "--juce-fx", self._job_path, "--shm", self.name, + "--parent", str(os.getpid())] + else: + cmd = [exe, "--realtime-fx", self._job_path, "--shm", self.name, + "--parent", str(os.getpid())] # FXRT_BRIDGE_LOG=: bat stderr bridge (FxRTPerf) vao file — chan # doan outFull/drop; mac dinh DEVNULL nhu cu. # V34 diag: mac dinh ghi FxRTPerf vao fxrt_bridge.log de phan tich diff --git a/native_bridge/JUCE b/native_bridge/JUCE new file mode 160000 index 0000000..10a5896 --- /dev/null +++ b/native_bridge/JUCE @@ -0,0 +1 @@ +Subproject commit 10a589619b452c261b2940767eb253171eb5a823 diff --git a/native_bridge/include/FxShm.h b/native_bridge/include/FxShm.h new file mode 100644 index 0000000..a8e0fac --- /dev/null +++ b/native_bridge/include/FxShm.h @@ -0,0 +1,50 @@ +// native_bridge/include/FxShm.h +// openShm/closeShm dùng chung giữa RealtimeFxLoop.cpp (fx_vst_bridge +// --realtime-fx) và juce_fx/JuceFxLoop.cpp (juce_fx_bridge --juce-fx). +// Windows-only (FileMapping); mở mapping có sẵn do engine (Python) tạo. +#pragma once +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +#include + +namespace fxshm { + +struct ShmView { + void* map = nullptr; + void* view = nullptr; +}; + +inline ShmView* openShm(const std::string& name, size_t size) { +#ifdef _WIN32 + int wlen = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, nullptr, 0); + std::wstring wname(wlen, L'\0'); + MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &wname[0], wlen); + HANDLE map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wname.c_str()); + if (!map) return nullptr; + void* view = MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, size); + if (!view) { CloseHandle(map); return nullptr; } + ShmView* v = new ShmView(); + v->map = map; + v->view = view; + return v; +#else + (void)name; (void)size; + return nullptr; +#endif +} + +inline void closeShm(ShmView* v) { + if (!v) return; +#ifdef _WIN32 + if (v->view) UnmapViewOfFile(v->view); + if (v->map) CloseHandle((HANDLE)v->map); +#endif + delete v; +} + +} // namespace fxshm diff --git a/native_bridge/juce_fx/CMakeLists.txt b/native_bridge/juce_fx/CMakeLists.txt new file mode 100644 index 0000000..b7949a1 --- /dev/null +++ b/native_bridge/juce_fx/CMakeLists.txt @@ -0,0 +1,35 @@ +# native_bridge/juce_fx/CMakeLists.txt +# G1 POC: juce_fx_bridge.exe — JUCE AudioProcessorGraph thay RealtimeFxChain +# trong SHM realtime loop. Windows only. Build riêng (không chung với +# daw_vst_bridge) để JUCE không kéo opengl/fluidsynth vào bridge cũ. +cmake_minimum_required(VERSION 3.20) +project(juce_fx_bridge LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../JUCE/CMakeLists.txt") + message(FATAL_ERROR "JUCE submodule missing — run: git submodule update --init native_bridge/JUCE") +endif() + +# JUCE framework (submodule, tag 8.0.7). add_subdirectory tạo targets +# juce::core, juce::audio_basics, juce::audio_processors, ... +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../JUCE JUCE EXCLUDE_FROM_ALL) + +add_executable(juce_fx_bridge + main_juce_fx.cpp + JuceFxLoop.cpp + JuceFxEngine.cpp +) +target_include_directories(juce_fx_bridge PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../include +) +target_link_libraries(juce_fx_bridge PRIVATE + juce::juce_core + juce::juce_audio_basics + juce::juce_audio_processors +) +if(WIN32) + # timeBeginPeriod (winmm) — như fx_vst_bridge + target_link_libraries(juce_fx_bridge PRIVATE winmm) +endif() diff --git a/native_bridge/juce_fx/JuceFxEngine.cpp b/native_bridge/juce_fx/JuceFxEngine.cpp new file mode 100644 index 0000000..fe6355d --- /dev/null +++ b/native_bridge/juce_fx/JuceFxEngine.cpp @@ -0,0 +1,82 @@ +// native_bridge/juce_fx/JuceFxEngine.cpp +// Implementation: AudioProcessorGraph với input/output node — chain rỗng G1. +#include "JuceFxEngine.h" + +#include + +#include +#include +#include + +struct JuceFxEngine::Impl { + juce::AudioProcessorGraph graph; + juce::AudioBuffer buf; + uint32_t sampleRate = 0; + uint32_t blockSize = 0; + bool prepared = false; +}; + +JuceFxEngine::JuceFxEngine() : impl_(std::make_unique()) {} + +JuceFxEngine::~JuceFxEngine() { shutdown(); } + +void JuceFxEngine::shutdown() { + if (impl_->prepared) { + impl_->graph.releaseResources(); + impl_->graph.clear(); + impl_->prepared = false; + impl_->sampleRate = 0; + impl_->blockSize = 0; + } +} + +void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) { + if (impl_->prepared && impl_->sampleRate == sampleRate && + impl_->blockSize == blockSize) + return; // không đổi — giữ graph + shutdown(); + + impl_->sampleRate = sampleRate; + impl_->blockSize = blockSize; + impl_->graph.setPlayConfigDetails(2, 2, (double)sampleRate, (int)blockSize); + + // Chain rỗng G1: audio input -> audio output (passthrough). G2: chèn + // plugin node giữa hai node này theo fx_chain. + auto& g = impl_->graph; + const auto inNode = g.addNode(std::make_unique< + juce::AudioProcessorGraph::AudioGraphIOProcessor>( + juce::AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode)); + const auto outNode = g.addNode(std::make_unique< + juce::AudioProcessorGraph::AudioGraphIOProcessor>( + juce::AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode)); + using NCh = juce::AudioProcessorGraph::NodeAndChannel; + using Conn = juce::AudioProcessorGraph::Connection; + for (int ch = 0; ch < 2; ++ch) { + NCh src; src.nodeID = inNode->nodeID; src.channelIndex = ch; + NCh dst; dst.nodeID = outNode->nodeID; dst.channelIndex = ch; + g.addConnection(Conn(src, dst)); + } + + g.prepareToPlay((double)sampleRate, (int)(blockSize * 4)); // batch tối đa 4 block + impl_->buf.setSize(2, (int)(blockSize * 4), false, false, true); + impl_->prepared = true; +} + +void JuceFxEngine::process(float* inL, float* inR, uint32_t n) { + if (!impl_->prepared || n == 0) return; + auto& b = impl_->buf; + b.setSize(2, (int)n, false, false, true); + b.copyFrom(0, 0, inL, (int)n); + b.copyFrom(1, 0, inR, (int)n); + juce::MidiBuffer midi; + impl_->graph.processBlock(b, midi); + // G1 passthrough: copy kết quả về buffer đầu vào (in-place như contract). + const auto* outL = b.getReadPointer(0); + const auto* outR = b.getReadPointer(1); + std::copy(outL, outL + n, inL); + std::copy(outR, outR + n, inR); +} + +uint32_t JuceFxEngine::latencySamples() const { + return impl_->prepared ? (uint32_t)impl_->graph.getLatencySamples() : 0u; +} diff --git a/native_bridge/juce_fx/JuceFxEngine.h b/native_bridge/juce_fx/JuceFxEngine.h new file mode 100644 index 0000000..6e92c40 --- /dev/null +++ b/native_bridge/juce_fx/JuceFxEngine.h @@ -0,0 +1,29 @@ +// native_bridge/juce_fx/JuceFxEngine.h +// JUCE AudioProcessorGraph engine (G1): đọc input ring → graph (rỗng) → +// ghi output ring. Tách khỏi loop để G2 chỉ thêm plugin vào graph. +#pragma once +#include +#include + +class JuceFxEngine { +public: + JuceFxEngine(); + ~JuceFxEngine(); + // Không copy. + JuceFxEngine(const JuceFxEngine&) = delete; + JuceFxEngine& operator=(const JuceFxEngine&) = delete; + + // Tạo graph mới (input node -> output node, passthrough), prepareToPlay. + // sampleRate/blockSize đổi → teardown graph cũ + prepare lại (validate + // header G1). blockSize là kích thước mỗi SHM block (256). + void prepare(uint32_t sampleRate, uint32_t blockSize); + // Audio thread: xử lý n mẫu stereo in-place (n <= blockSize * FXRT_IN_SLOTS). + void process(float* inL, float* inR, uint32_t n); + // Graph latency samples (G1 chain rỗng = 0; G2 = getLatencySamples thật). + uint32_t latencySamples() const; + void shutdown(); + +private: + struct Impl; + std::unique_ptr impl_; +}; diff --git a/native_bridge/juce_fx/JuceFxLoop.cpp b/native_bridge/juce_fx/JuceFxLoop.cpp new file mode 100644 index 0000000..da175d8 --- /dev/null +++ b/native_bridge/juce_fx/JuceFxLoop.cpp @@ -0,0 +1,241 @@ +// native_bridge/juce_fx/JuceFxLoop.cpp +// juce_fx_bridge --juce-fx --shm [--parent ] +// G1 POC: vòng lặp SHM giống RealtimeFxLoop.cpp nhưng xử lý qua JuceFxEngine +// (JUCE AudioProcessorGraph chain rỗng) thay vì RealtimeFxChain. Job format +// giữ nguyên {sample_rate, block_size, fx_chain} — G1 bỏ qua fx_chain. +// Validate header mỗi iteration: sampleRate/blockSize đổi giữa chừng → +// teardown + prepareToPlay lại + report latency mới qua FxLatReport. +#include "JuceFxEngine.h" +#include "FxRealtimeIPC.h" +#include "FxShm.h" + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +static bool parentAlive(uint32_t pid) { + if (pid == 0) return true; + HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (!h) return false; + CloseHandle(h); + return true; +} + +static void sleepMs(uint32_t ms) { Sleep(ms); } + +std::string readFile(const std::string& path) { + std::ifstream f(path, std::ios::binary); + return std::string((std::istreambuf_iterator(f)), + std::istreambuf_iterator()); +} + +double jsonNumber(const std::string& s, const std::string& key, double def) { + std::string k = "\"" + key + "\""; + size_t p = s.find(k); + if (p == std::string::npos) return def; + p += k.size(); + while (p < s.size() && (s[p] == ':' || s[p] == ' ' || s[p] == '\t' || + s[p] == '\r' || s[p] == '\n')) + ++p; + size_t e = p; + while (e < s.size() && (std::isdigit((unsigned char)s[e]) || s[e] == '.' || + s[e] == '-' || s[e] == '+' || s[e] == 'e' || + s[e] == 'E')) + ++e; + if (e == p) return def; + try { return std::stod(s.substr(p, e - p)); } catch (...) { return def; } +} + +} // namespace + +int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName, + uint32_t parentPid) { + std::cerr << "[JuceFxLoop] start job=" << jobPath << " shm=" << shmName + << " parent=" << parentPid << std::endl; + const std::string job = readFile(jobPath); + if (job.empty()) { + std::cerr << "[JuceFxLoop] cannot read job file" << std::endl; + return 1; + } + const double srD = jsonNumber(job, "sample_rate", 44100.0); + const uint32_t sampleRate = (uint32_t)srD; + const double blkD = jsonNumber(job, "block_size", (double)FXRT_BLOCK); + const uint32_t block = (uint32_t)std::max(32.0, std::min(blkD, (double)FXRT_BLOCK)); + // G1: fx_chain bỏ qua (chain rỗng). G2: đọc fx_chain -> build graph. + + fxshm::ShmView* v = fxshm::openShm(shmName, sizeof(FxRealtimeIPC)); + if (!v) { + std::cerr << "[JuceFxLoop] cannot open SHM: " << shmName + << " (engine phải tạo trước)" << std::endl; + return 2; + } + auto* ipc = static_cast(v->view); + + for (int i = 0; i < 200 && ipc->h.magic != FXRT_MAGIC; ++i) sleepMs(10); + if (ipc->h.magic != FXRT_MAGIC) { + std::cerr << "[JuceFxLoop] SHM magic mismatch (engine chưa init?)" << std::endl; + fxshm::closeShm(v); + return 2; + } + if (ipc->h.inSlots != FXRT_IN_SLOTS || ipc->h.outSlots != FXRT_OUT_SLOTS) { + std::cerr << "[JuceFxLoop] slot count mismatch" << std::endl; + fxshm::closeShm(v); + return 2; + } + +#ifdef _WIN32 + timeBeginPeriod(1); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); +#endif + + // Engine: prepare graph (chain rỗng). sr/block từ job; header là nguồn + // thật (engine ghi lúc tạo SHM) — nếu khác, lấy header. + uint32_t curSr = sampleRate; + uint32_t curBlock = block; + JuceFxEngine engine; + if (ipc->h.sampleRate) curSr = ipc->h.sampleRate; + if (ipc->h.blockSize) curBlock = ipc->h.blockSize; + engine.prepare(curSr, curBlock); + std::cerr << "[JuceFxLoop] prepared sr=" << curSr << " block=" << curBlock + << " latency=" << engine.latencySamples() << std::endl; + + // Report latency ban đầu (slot 0 = tổng chain). + { + const uint32_t ls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1); + ipc->lat[ls].slot = 0; + ipc->lat[ls].samples = engine.latencySamples(); + MemoryBarrier(); + ipc->h.latWrite++; + } + ipc->h.state = FXRT_STATE_READY; + std::cerr << "[JuceFxLoop] ready" << std::endl; + + std::thread hb([&]() { + while (ipc->h.running && ipc->h.state == FXRT_STATE_READY) { + ipc->h.heartbeat++; + sleepMs(100); + } + }); + + const uint32_t n = curBlock; + const uint32_t inMask = ipc->h.inSlots - 1; + const uint32_t outMask = ipc->h.outSlots - 1; + float L[FXRT_BLOCK * FXRT_IN_SLOTS], R[FXRT_BLOCK * FXRT_IN_SLOTS]; + uint64_t processed = 0; + uint64_t perfIter = 0, perfProc = 0, perfIdle = 0, perfOutFull = 0; + double perfProcSum = 0.0, perfProcMax = 0.0; + double perfLoopSum = 0.0, perfLoopMax = 0.0; + uint32_t perfTakeSum = 0; + auto perfT0 = std::chrono::steady_clock::now(); + auto perfRunStart = perfT0; + + while (ipc->h.running) { + if (parentPid && !parentAlive(parentPid)) { + std::cerr << "[JuceFxLoop] parent gone — exiting" << std::endl; + break; + } + // Validate header mỗi iteration: sampleRate/blockSize đổi giữa chừng + // (đổi thiết bị audio / session mới khác rate) → teardown graph + + // prepareToPlay lại + report latency mới. WebAudio sampleRate bất + // biến — check này phòng header bị ghi lại. + if (ipc->h.sampleRate && (ipc->h.sampleRate != curSr || + ipc->h.blockSize != curBlock)) { + curSr = ipc->h.sampleRate; + curBlock = ipc->h.blockSize; + engine.prepare(curSr, curBlock); + const uint32_t ls = ipc->h.latWrite & (FXRT_LAT_SLOTS - 1); + ipc->lat[ls].slot = 0; + ipc->lat[ls].samples = engine.latencySamples(); + MemoryBarrier(); + ipc->h.latWrite++; + std::cerr << "[JuceFxLoop] re-prepared sr=" << curSr + << " block=" << curBlock + << " latency=" << engine.latencySamples() << std::endl; + } + // OUT-ring backpressure (giữ nguyên cơ chế RealtimeFxLoop — pointer + // collision → torn frame nếu ghi đè slot chưa đọc). + const uint32_t outFree = ipc->h.outSlots - (ipc->h.outWrite - ipc->h.outRead); + if (outFree < std::min(ipc->h.inWrite - ipc->h.inRead, FXRT_IN_SLOTS)) { + ++perfOutFull; + std::this_thread::sleep_until(std::chrono::steady_clock::now() + + std::chrono::milliseconds(1)); + continue; + } + const uint32_t avail = ipc->h.inWrite - ipc->h.inRead; + if (avail == 0) { + ++perfIdle; + std::this_thread::sleep_until(std::chrono::steady_clock::now() + + std::chrono::milliseconds(1)); + continue; + } + const uint32_t take = std::min(avail, FXRT_IN_SLOTS); + uint32_t off = 0; + for (uint32_t i = 0; i < take; ++i) { + const uint32_t slot = ipc->h.inRead & inMask; + std::memcpy(L + off, ipc->inL[slot], n * sizeof(float)); + std::memcpy(R + off, ipc->inR[slot], n * sizeof(float)); + ipc->h.inRead++; + off += n; + } + const auto perfP0 = std::chrono::steady_clock::now(); + engine.process(L, R, off); // chain rỗng = passthrough + const auto perfP1 = std::chrono::steady_clock::now(); + const double perfProcMs = std::chrono::duration(perfP1 - perfP0).count(); + ++perfProc; perfProcSum += perfProcMs; perfTakeSum += take; + if (perfProcMs > perfProcMax) perfProcMax = perfProcMs; + for (uint32_t i = 0, o = 0; i < take; ++i, o += n) { + const uint32_t oslot = ipc->h.outWrite & outMask; + std::memcpy(ipc->outL[oslot], L + o, n * sizeof(float)); + std::memcpy(ipc->outR[oslot], R + o, n * sizeof(float)); + MemoryBarrier(); + ipc->h.outWrite++; + ++processed; + } + const auto perfIterT1 = std::chrono::steady_clock::now(); + const double perfLoopMs = std::chrono::duration(perfIterT1 - perfT0).count(); + perfT0 = perfIterT1; ++perfIter; + perfLoopSum += perfLoopMs; if (perfLoopMs > perfLoopMax) perfLoopMax = perfLoopMs; + if (perfIter % 100 == 0) { + const double runSec = std::chrono::duration(std::chrono::steady_clock::now() - perfRunStart).count(); + std::cerr << "[JuceFxPerf] iter=" << perfIter + << " procN=" << perfProc + << " procAvg=" << (perfProc ? perfProcSum / perfProc : 0.0) << "ms" + << " procMax=" << perfProcMax << "ms" + << " loopAvg=" << (perfIter ? perfLoopSum / perfIter : 0.0) << "ms" + << " loopMax=" << perfLoopMax << "ms" + << " idle=" << perfIdle + << " outFull=" << perfOutFull + << " takeAvg=" << (perfProc ? (double)perfTakeSum / perfProc : 0.0) + << " procBlocks=" << processed + << " rate=" << (runSec > 0 ? processed / runSec : 0.0) << "blk/s" + << " outDepth=" << (ipc->h.outWrite - ipc->h.outRead) + << std::endl; + perfProc = 0; perfProcSum = 0.0; perfTakeSum = 0; perfIdle = 0; perfOutFull = 0; + } + } + ipc->h.state = FXRT_STATE_STARTING; + hb.join(); + engine.shutdown(); + fxshm::closeShm(v); +#ifdef _WIN32 + timeEndPeriod(1); +#endif + std::cerr << "[JuceFxLoop] exit processed=" << processed << std::endl; + return 0; +} diff --git a/native_bridge/juce_fx/JuceFxLoop.h b/native_bridge/juce_fx/JuceFxLoop.h new file mode 100644 index 0000000..8af60eb --- /dev/null +++ b/native_bridge/juce_fx/JuceFxLoop.h @@ -0,0 +1,8 @@ +// native_bridge/juce_fx/JuceFxLoop.h +#pragma once +#include +#include + +// juce_fx_bridge --juce-fx --shm [--parent ] +int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName, + uint32_t parentPid); diff --git a/native_bridge/juce_fx/main_juce_fx.cpp b/native_bridge/juce_fx/main_juce_fx.cpp new file mode 100644 index 0000000..833de37 --- /dev/null +++ b/native_bridge/juce_fx/main_juce_fx.cpp @@ -0,0 +1,52 @@ +// native_bridge/juce_fx/main_juce_fx.cpp +// Entry point của juce_fx_bridge.exe — G1 POC: JUCE AudioProcessorGraph +// thay RealtimeFxChain trong SHM realtime loop. Windows only. +// --juce-fx --shm [--parent ] +#include "JuceFxLoop.h" + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include +#include +#include + +#ifdef _WIN32 +static LONG WINAPI JuceCrashDumpHandler(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] juce_fx_bridge exception code=0x" << std::hex + << (er ? er->ExceptionCode : 0) << " addr=0x" + << (er ? (void*)er->ExceptionAddress : nullptr) << std::dec << std::endl; + return EXCEPTION_CONTINUE_SEARCH; +} +#endif + +int main(int argc, char** argv) { +#ifdef _WIN32 + SetUnhandledExceptionFilter(JuceCrashDumpHandler); +#endif + std::string mode, jobPath, shmName; + uint32_t parentPid = 0; + for (int i = 1; i < argc; ++i) { + const std::string a = argv[i]; + if (a == "--juce-fx" && i + 1 < argc) { + mode = a; + jobPath = argv[++i]; + } else if (a == "--shm" && i + 1 < argc) { + shmName = argv[++i]; + } else if (a == "--parent" && i + 1 < argc) { + parentPid = (uint32_t)std::strtoul(argv[++i], nullptr, 10); + } + } + if (mode != "--juce-fx" || jobPath.empty() || shmName.empty()) { + std::cerr << "usage: juce_fx_bridge --juce-fx --shm " + "[--parent ]" << std::endl; + return 1; + } + return run_juce_fx_loop(jobPath, shmName, parentPid); +} diff --git a/native_bridge/src/RealtimeFxLoop.cpp b/native_bridge/src/RealtimeFxLoop.cpp index 3e0b8e0..2af7d64 100644 --- a/native_bridge/src/RealtimeFxLoop.cpp +++ b/native_bridge/src/RealtimeFxLoop.cpp @@ -10,6 +10,7 @@ // nhận JSON mảng {path,bypass,preset_b64} (type/name bỏ qua). #include "RenderFxJob.h" #include "FxRealtimeIPC.h" +#include "FxShm.h" #ifdef _WIN32 #ifndef NOMINMAX @@ -31,32 +32,6 @@ namespace { -struct ShmView { - void* map = nullptr; - void* view = nullptr; -}; - -ShmView* openShm(const std::string& name, size_t size) { - int wlen = MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, nullptr, 0); - std::wstring wname(wlen, L'\0'); - MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &wname[0], wlen); - HANDLE map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wname.c_str()); - if (!map) return nullptr; - void* view = MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, size); - if (!view) { CloseHandle(map); return nullptr; } - ShmView* v = new ShmView(); - v->map = map; - v->view = view; - return v; -} - -void closeShm(ShmView* v) { - if (!v) return; - if (v->view) UnmapViewOfFile(v->view); - if (v->map) CloseHandle((HANDLE)v->map); - delete v; -} - static bool parentAlive(uint32_t pid) { if (pid == 0) return true; HANDLE h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); @@ -137,7 +112,7 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName, RealtimeFxChain chain; chain.setChain(chainJson, sampleRate, (int32_t)batchN); - ShmView* v = openShm(shmName, sizeof(FxRealtimeIPC)); + fxshm::ShmView* v = fxshm::openShm(shmName, sizeof(FxRealtimeIPC)); if (!v) { std::cerr << "[RealtimeFxLoop] cannot open SHM: " << shmName << " (engine phải tạo trước)" << std::endl; @@ -149,12 +124,12 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName, for (int i = 0; i < 200 && ipc->h.magic != FXRT_MAGIC; ++i) sleepMs(10); if (ipc->h.magic != FXRT_MAGIC) { std::cerr << "[RealtimeFxLoop] SHM magic mismatch (engine chưa init?)" << std::endl; - closeShm(v); + fxshm::closeShm(v); return 2; } if (ipc->h.inSlots != FXRT_IN_SLOTS || ipc->h.outSlots != FXRT_OUT_SLOTS) { std::cerr << "[RealtimeFxLoop] slot count mismatch" << std::endl; - closeShm(v); + fxshm::closeShm(v); return 2; } // Windows timer resolution 1ms — bỏ granularity Sleep 15.6ms cho toàn @@ -359,7 +334,7 @@ int run_realtime_fx_loop(const std::string& jobPath, const std::string& shmName, } ipc->h.state = FXRT_STATE_STARTING; // đã dừng hb.join(); - closeShm(v); + fxshm::closeShm(v); #ifdef _WIN32 timeEndPeriod(1); #endif diff --git a/src-tauri/binaries/juce_fx_bridge-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/juce_fx_bridge-x86_64-pc-windows-msvc.exe new file mode 100644 index 0000000..4e39e1f Binary files /dev/null and b/src-tauri/binaries/juce_fx_bridge-x86_64-pc-windows-msvc.exe differ