phase4: Linux native_bridge build (Docker) — sfizz from source, --render SF2/SFZ
- Dockerfile: build sfizz tu native_bridge/sfizz-src (hoac git clone v1.2.3), sed fix sfizz.pc (-llibsfizz -> -lsfizz), cmake bridge voi 3 flag SMTG OFF (VSTGUI/plugin/hosting examples) — bridge headless chi can --render. - CMakeLists: nhanh non-WIN32 build daw_vst_bridge tu main_linux.cpp + RenderJob.cpp + NativeInstrumentEngine.cpp (SF2/SF3 + SFZ; VST3/VST2 hosting Windows-only, guard #ifdef _WIN32). gui_probe/plugin_host/ seh_channel_test chi build tren Windows. - RenderJob.cpp: JSON parser (sheredom json.h) khong con phu thuoc HAVE_VST3SDK — render SF2/SFZ chay duoc khong can VST3 SDK. - NativeInstrumentEngine.cpp: guard windows.h/VST3/VST2/Sandbox/SEH. - main.cpp: g_engine unconditional + guard disable_ime_contexts (_WIN32). - engine.spec: bo pedalboard collect_dynamic_libs + hidden imports. - vst_engine/render_engine: DecentSampler -> NotImplementedError + huong dan convert .dspreset -> .vstpreset; pianobook fallback synth. - Verified: WSL Ubuntu 24.04 build OK, bridge Linux --render SF2 ra WAV 44100 stereo peak 1575; Python tests 115 passed 1 skipped. - Dockerfile build chua test duoc (may khong co Docker).
This commit is contained in:
+27
@@ -37,6 +37,33 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# ── Build native_bridge (Linux) — thay pedalboard GPL-3.0 ──────────────────
|
||||||
|
# Xem PLAN_REPLACE_PEDALBOARD_NATIVE_BRIDGE.md §4. sfizz khong co trong apt
|
||||||
|
# Debian → build tu nguon vendored (native_bridge/sfizz-src, co trong build
|
||||||
|
# context) hoac git clone fallback khi thieu. Bridge chay --render duoi
|
||||||
|
# Xvfb :99 (DISPLAY da set o tren) — khong can GUI plugin editor.
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
cmake pkg-config libfluidsynth-dev libfreetype-dev git \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& if [ -d native_bridge/sfizz-src ]; then cp -r native_bridge/sfizz-src /tmp/sfizz; \
|
||||||
|
else git clone --recursive --depth 1 --branch v1.2.3 https://github.com/sfztools/sfizz.git /tmp/sfizz; fi \
|
||||||
|
&& cmake -S /tmp/sfizz -B /tmp/sfizz/build -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DSFIZZ_SHARED=ON -DSFIZZ_JACK=OFF -DSFIZZ_RENDER=OFF -DSFIZZ_TESTS=OFF \
|
||||||
|
-DSFIZZ_BENCHMARKS=OFF -DSFIZZ_DEMOS=OFF -DSFIZZ_DEVTOOLS=OFF \
|
||||||
|
-DSFIZZ_GIT_SUBMODULE_CHECK=OFF -DBUILD_TESTING=OFF \
|
||||||
|
&& cmake --build /tmp/sfizz/build --target sfizz_shared -j"$(nproc)" \
|
||||||
|
&& cmake --install /tmp/sfizz/build \
|
||||||
|
&& sed -i 's/-llibsfizz/-lsfizz/' /usr/local/lib/pkgconfig/sfizz.pc \
|
||||||
|
&& cmake -S native_bridge -B /tmp/bridge-build -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DSMTG_ENABLE_VST3_PLUGIN_EXAMPLES=OFF \
|
||||||
|
-DSMTG_ENABLE_VST3_HOSTING_EXAMPLES=OFF \
|
||||||
|
-DSMTG_ENABLE_VSTGUI_SUPPORT=OFF \
|
||||||
|
&& cmake --build /tmp/bridge-build --target daw_vst_bridge -j"$(nproc)" \
|
||||||
|
&& mkdir -p /app/install \
|
||||||
|
&& cp /tmp/bridge-build/daw_vst_bridge /app/install/ \
|
||||||
|
&& rm -rf /tmp/sfizz /tmp/sfizz/build /tmp/bridge-build
|
||||||
|
|
||||||
|
|
||||||
# Copy VST3 plugins if present
|
# Copy VST3 plugins if present
|
||||||
COPY ./vst_plugins/ /opt/daw_engine/vst3/
|
COPY ./vst_plugins/ /opt/daw_engine/vst3/
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# PLAN: thay pedalboard (GPL-3.0) bằng native_bridge — render VSTi offline
|
# PLAN: thay pedalboard (GPL-3.0) bằng native_bridge — render VSTi offline
|
||||||
|
|
||||||
Ngày: 2026-08-15
|
Ngày: 2026-08-15
|
||||||
Trạng thái: Phase 2 xong (app/core/native_render.py client spawn bridge --render; plugins.py preview + /midi-render dùng bridge khi SF_RENDER_ENGINE=bridge; velocity float 0..1 đã fix double-scaling; bank/program parity; golden test bridge vs pedalboard RMS; test Python 111 pass 1 skip, C++ 20/20). Windows sửa code bridge/desktop;
|
Trạng thái: Phase 4 xong (engine.spec bỏ pedalboard; DecentSampler → hướng dẫn convert .dspreset→.vstpreset; Dockerfile build native_bridge Linux — sfizz build từ nguồn + sed fix sfizz.pc + bridge --render SF2/SFZ; Linux port: CMakeLists nhánh non-WIN32, main_linux.cpp, guard Windows-only NativeInstrumentEngine.cpp/RenderJob.cpp; verified WSL Ubuntu 24.04 — bridge Linux render SF2 ra WAV 44100 stereo peak 1575; test Python 115 pass 1 skip). Phase 5 còn lại: đảo flag mặc định bridge, gỡ pedalboard khỏi requirements/source, LICENSE.
|
||||||
Linux làm bridge Linux + Docker + test so sánh.
|
|
||||||
|
|
||||||
## 1. Mục tiêu
|
## 1. Mục tiêu
|
||||||
Xóa dependency GPL-3.0 (`pedalboard`) khỏi cả 2 môi trường, chuyển toàn bộ
|
Xóa dependency GPL-3.0 (`pedalboard`) khỏi cả 2 môi trường, chuyển toàn bộ
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from app.config import settings
|
|||||||
from app.core.vst_engine import (
|
from app.core.vst_engine import (
|
||||||
render_midi_events_to_audio,
|
render_midi_events_to_audio,
|
||||||
PluginManager,
|
PluginManager,
|
||||||
DecentSamplerManager,
|
|
||||||
HAS_PEDALBOARD,
|
HAS_PEDALBOARD,
|
||||||
HAS_PYFLUIDSYNTH,
|
HAS_PYFLUIDSYNTH,
|
||||||
apply_preset_to_plugin,
|
apply_preset_to_plugin,
|
||||||
@@ -204,35 +203,16 @@ class PythonRenderEngine:
|
|||||||
vst = plugin_mgr.load_vst(instrument_id) if instrument_id else None
|
vst = plugin_mgr.load_vst(instrument_id) if instrument_id else None
|
||||||
|
|
||||||
if instrument_source == "pianobook":
|
if instrument_source == "pianobook":
|
||||||
|
# Phase 4 (§3.4): .dspreset không hỗ trợ nữa (pedalboard
|
||||||
|
# GPL-3.0 gỡ; spike load .dspreset fail). Chặn + fallback synth.
|
||||||
dspreset_path = track.get("dspreset_path", "")
|
dspreset_path = track.get("dspreset_path", "")
|
||||||
if dspreset_path and os.path.exists(dspreset_path) and HAS_PEDALBOARD:
|
logger.warning(
|
||||||
from pedalboard import Pedalboard
|
"[RenderEngine] Pianobook track bỏ qua: .dspreset không còn hỗ trợ. "
|
||||||
ds_manager = DecentSamplerManager()
|
"Convert sang .vstpreset (DecentSampler → Save preset) rồi gán lại track."
|
||||||
try:
|
)
|
||||||
vst = ds_manager.create_decent_sampler_instance(dspreset_path)
|
synth_buffer = render_midi_events_to_audio(
|
||||||
midi_messages = PluginManager.midi_events_to_messages(
|
midi_events=midi_events, sr=self.sample_rate, bpm=bpm, instrument='synth'
|
||||||
midi_events, bpm, self.sample_rate
|
)
|
||||||
)
|
|
||||||
total_needed = 0
|
|
||||||
for ev in midi_events:
|
|
||||||
end_sec = (ev.get("start_beat", 0) + ev.get("duration_beats", 1)) * (60.0 / bpm)
|
|
||||||
dur_samples = int(end_sec * self.sample_rate)
|
|
||||||
if dur_samples > total_needed:
|
|
||||||
total_needed = dur_samples
|
|
||||||
total_needed = max(total_needed, 1024)
|
|
||||||
silent = np.zeros((2, total_needed), dtype=np.float32)
|
|
||||||
# pedalboard >= 0.9: instrument KHÔNG vào Pedalboard container
|
|
||||||
synth_buffer = vst(midi_messages, sample_rate=self.sample_rate,
|
|
||||||
duration=total_needed / float(self.sample_rate), num_channels=2)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"[RenderEngine] DecentSampler/Pianobook error: {e}")
|
|
||||||
synth_buffer = render_midi_events_to_audio(
|
|
||||||
midi_events=midi_events, sr=self.sample_rate, bpm=bpm, instrument='synth'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
synth_buffer = render_midi_events_to_audio(
|
|
||||||
midi_events=midi_events, sr=self.sample_rate, bpm=bpm, instrument='synth'
|
|
||||||
)
|
|
||||||
elif instrument_id and not instrument_id.startswith("sf_"):
|
elif instrument_id and not instrument_id.startswith("sf_"):
|
||||||
# VSTi track: native_bridge --render (SF_RENDER_ENGINE=bridge)
|
# VSTi track: native_bridge --render (SF_RENDER_ENGINE=bridge)
|
||||||
# thay pedalboard; ngược lại giữ pedalboard path cũ.
|
# thay pedalboard; ngược lại giữ pedalboard path cũ.
|
||||||
|
|||||||
+9
-19
@@ -521,25 +521,15 @@ class DecentSamplerManager:
|
|||||||
self.vst_path = vst_path
|
self.vst_path = vst_path
|
||||||
|
|
||||||
def create_decent_sampler_instance(self, dspreset_path: str):
|
def create_decent_sampler_instance(self, dspreset_path: str):
|
||||||
if not HAS_PEDALBOARD:
|
# Phase 4 (PLAN_REPLACE_PEDALBOARD_NATIVE_BRIDGE.md §3.4): .dspreset là
|
||||||
raise RuntimeError("pedalboard not available")
|
# format riêng của DecentSampler (không phải VST3 state) — spike đã fail
|
||||||
if not os.path.exists(self.vst_path):
|
# cả trên pedalboard (container: "failed to load data from preset file").
|
||||||
raise FileNotFoundError(f"DecentSampler VST3 not found at {self.vst_path}")
|
# pedalboard GPL-3.0 bị gỡ → chặn Pianobook + hướng dẫn convert.
|
||||||
if not os.path.exists(dspreset_path):
|
raise NotImplementedError(
|
||||||
raise FileNotFoundError(f"Preset file not found at {dspreset_path}")
|
"Pianobook .dspreset không còn được hỗ trợ (pedalboard GPL-3.0 đã gỡ). "
|
||||||
|
"Chuyển đổi: mở preset trong DecentSampler → save .vstpreset (VST3 state) "
|
||||||
plugin = VST3Plugin(self.vst_path)
|
"rồi upload .vstpreset qua web UI — native_bridge đọc được."
|
||||||
|
)
|
||||||
abs_preset = os.path.abspath(dspreset_path)
|
|
||||||
preset_dir = os.path.dirname(abs_preset)
|
|
||||||
cwd_before = os.getcwd()
|
|
||||||
try:
|
|
||||||
os.chdir(preset_dir)
|
|
||||||
plugin.load_preset(abs_preset)
|
|
||||||
finally:
|
|
||||||
os.chdir(cwd_before)
|
|
||||||
|
|
||||||
return plugin
|
|
||||||
|
|
||||||
|
|
||||||
# ── Preset bridge (Carla ↔ pedalboard) ─────────────────────────────────────
|
# ── Preset bridge (Carla ↔ pedalboard) ─────────────────────────────────────
|
||||||
|
|||||||
+3
-9
@@ -5,9 +5,8 @@
|
|||||||
import os
|
import os
|
||||||
from PyInstaller.utils.hooks import collect_dynamic_libs, collect_data_files
|
from PyInstaller.utils.hooks import collect_dynamic_libs, collect_data_files
|
||||||
|
|
||||||
# Native DLL cho pedalboard va soundfile (Windows .pyd/.dll, Linux .so)
|
# Native DLL cho soundfile (Windows .pyd/.dll, Linux .so)
|
||||||
binaries = collect_dynamic_libs('pedalboard')
|
binaries = collect_dynamic_libs('soundfile')
|
||||||
binaries += collect_dynamic_libs('soundfile')
|
|
||||||
|
|
||||||
# Root tuyet doi cua thu muc chua spec — dung cho cac datas KHONG nam trong
|
# Root tuyet doi cua thu muc chua spec — dung cho cac datas KHONG nam trong
|
||||||
# package 'app' (vd thu muc md/). Cac assets cua app (static/templates/models)
|
# package 'app' (vd thu muc md/). Cac assets cua app (static/templates/models)
|
||||||
@@ -89,12 +88,7 @@ _hidden = [
|
|||||||
'uvicorn.protocols.http.auto',
|
'uvicorn.protocols.http.auto',
|
||||||
'uvicorn.protocols.websockets',
|
'uvicorn.protocols.websockets',
|
||||||
'uvicorn.protocols.websockets.auto',
|
'uvicorn.protocols.websockets.auto',
|
||||||
'pedalboard',
|
# pedalboard (GPL-3.0) DA XOA — native_bridge thay the (xem PLAN_REPLACE_PEDALBOARD_NATIVE_BRIDGE.md)
|
||||||
# C++ ExternalPlugin.h import pedalboard.midi_utils khi tao VST3Plugin
|
|
||||||
# (py::module_::import) - PyInstaller khong thay duoc import tu C++, phai
|
|
||||||
# khai bao hiddenimport thu cong (thieu -> "No module named
|
|
||||||
# 'pedalboard.midi_utils'" khi /midi-render).
|
|
||||||
'pedalboard.midi_utils',
|
|
||||||
'soundfile',
|
'soundfile',
|
||||||
'sf2utils',
|
'sf2utils',
|
||||||
# pyfluidsynth (fluidsynth.py cffi) - render soundfont native cho
|
# pyfluidsynth (fluidsynth.py cffi) - render soundfont native cho
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ if(MSVC)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ── 1. VST3 SDK (Steinberg, vendored as git submodule — NOT in vcpkg) ──
|
# ── 1. VST3 SDK (Steinberg, vendored as git submodule — NOT in vcpkg) ──
|
||||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vst3sdk/CMakeLists.txt")
|
if(WIN32 AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/vst3sdk/CMakeLists.txt")
|
||||||
add_subdirectory(vst3sdk EXCLUDE_FROM_ALL)
|
add_subdirectory(vst3sdk EXCLUDE_FROM_ALL)
|
||||||
set(VST3_SDK_TARGET sdk)
|
set(VST3_SDK_TARGET sdk)
|
||||||
else()
|
else()
|
||||||
message(WARNING "vst3sdk submodule missing — VST3 host disabled; SF2/SF3/SFZ still build")
|
message(STATUS "vst3sdk build skipped (Windows-only) — VST3 host disabled; SF2/SF3/SFZ still build")
|
||||||
set(VST3_SDK_TARGET "")
|
set(VST3_SDK_TARGET "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ include_directories(
|
|||||||
${SFIZZ_INCLUDE_DIR}
|
${SFIZZ_INCLUDE_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
add_executable(daw_vst_bridge
|
add_executable(daw_vst_bridge
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/RenderJob.cpp
|
src/RenderJob.cpp
|
||||||
@@ -64,6 +65,15 @@ add_executable(daw_vst_bridge
|
|||||||
src/StateStore.cpp
|
src/StateStore.cpp
|
||||||
src/SandboxVst3Host.cpp
|
src/SandboxVst3Host.cpp
|
||||||
)
|
)
|
||||||
|
else()
|
||||||
|
# Linux: offline --render only (SF2/SF3 + SFZ; VST3/VST2 hosting is
|
||||||
|
# Windows-only). main_linux.cpp is the tiny --render entry.
|
||||||
|
add_executable(daw_vst_bridge
|
||||||
|
src/main_linux.cpp
|
||||||
|
src/RenderJob.cpp
|
||||||
|
src/NativeInstrumentEngine.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# VST3 SDK hosting: sdk_hosting lacks the platform module loader — module_win32.cpp
|
# VST3 SDK hosting: sdk_hosting lacks the platform module loader — module_win32.cpp
|
||||||
# is added to the bridge itself (same approach as the audiohost sample).
|
# is added to the bridge itself (same approach as the audiohost sample).
|
||||||
@@ -86,6 +96,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ── gui_probe: standalone VST3 GUI attach probe (debug tool, not shipped) ──
|
# ── gui_probe: standalone VST3 GUI attach probe (debug tool, not shipped) ──
|
||||||
|
if(WIN32)
|
||||||
add_executable(gui_probe
|
add_executable(gui_probe
|
||||||
src/gui_probe.cpp
|
src/gui_probe.cpp
|
||||||
src/NativeInstrumentEngine.cpp
|
src/NativeInstrumentEngine.cpp
|
||||||
@@ -147,3 +158,4 @@ endif()
|
|||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(seh_channel_test PRIVATE ${FLUIDSYNTH_LIBRARY} ${SFIZZ_LIBRARY} winmm)
|
target_link_libraries(seh_channel_test PRIVATE ${FLUIDSYNTH_LIBRARY} ${SFIZZ_LIBRARY} winmm)
|
||||||
endif()
|
endif()
|
||||||
|
endif() # WIN32-only debug tools (gui_probe, plugin_host, seh_channel_test)
|
||||||
|
|||||||
@@ -3,15 +3,19 @@
|
|||||||
|
|
||||||
#include <fluidsynth.h>
|
#include <fluidsynth.h>
|
||||||
#include <sfizz.hpp>
|
#include <sfizz.hpp>
|
||||||
|
#ifdef _WIN32
|
||||||
#include "Vst3Instrument.h"
|
#include "Vst3Instrument.h"
|
||||||
#include "Vst2Instrument.h"
|
#include "Vst2Instrument.h"
|
||||||
#include "SandboxVst3Host.h"
|
#include "SandboxVst3Host.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// void* members keep fluid types out of the public header; cast here.
|
// void* members keep fluid types out of the public header; cast here.
|
||||||
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
|
#define FS_SYNTH (static_cast<fluid_synth_t*>(synth))
|
||||||
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))
|
#define FS_SETTINGS (static_cast<fluid_settings_t*>(settings))
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -168,6 +172,7 @@ std::unique_ptr<INativeInstrument> InstrumentEngineManager::create_instrument(In
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case InstrumentType::SOUNDFONT_SF2_SF3: return std::make_unique<FluidSynthInstrument>();
|
case InstrumentType::SOUNDFONT_SF2_SF3: return std::make_unique<FluidSynthInstrument>();
|
||||||
case InstrumentType::SFZ: return std::make_unique<SfizzInstrument>();
|
case InstrumentType::SFZ: return std::make_unique<SfizzInstrument>();
|
||||||
|
#ifdef _WIN32
|
||||||
case InstrumentType::VST3:
|
case InstrumentType::VST3:
|
||||||
// G4.2: SF_SANDBOX_VST3=1 -> host the VST3 in a child
|
// G4.2: SF_SANDBOX_VST3=1 -> host the VST3 in a child
|
||||||
// process (plugin_host.exe). Crash kills only the child.
|
// process (plugin_host.exe). Crash kills only the child.
|
||||||
@@ -176,6 +181,13 @@ std::unique_ptr<INativeInstrument> InstrumentEngineManager::create_instrument(In
|
|||||||
return std::make_unique<Vst3Instrument>();
|
return std::make_unique<Vst3Instrument>();
|
||||||
case InstrumentType::VST2:
|
case InstrumentType::VST2:
|
||||||
return std::make_unique<Vst2Instrument>();
|
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;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,6 +203,7 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
|||||||
loaded = static_cast<FluidSynthInstrument*>(inst.get())->loadSoundFontFile(path, sampleRate);
|
loaded = static_cast<FluidSynthInstrument*>(inst.get())->loadSoundFontFile(path, sampleRate);
|
||||||
else if (type == InstrumentType::SFZ)
|
else if (type == InstrumentType::SFZ)
|
||||||
loaded = static_cast<SfizzInstrument*>(inst.get())->loadSfzFile(path, sampleRate);
|
loaded = static_cast<SfizzInstrument*>(inst.get())->loadSfzFile(path, sampleRate);
|
||||||
|
#ifdef _WIN32
|
||||||
else if (type == InstrumentType::VST3) {
|
else if (type == InstrumentType::VST3) {
|
||||||
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
||||||
loaded = v->loadPlugin(path, sampleRate);
|
loaded = v->loadPlugin(path, sampleRate);
|
||||||
@@ -207,6 +220,7 @@ bool InstrumentEngineManager::assign(uint32_t channel, InstrumentType type,
|
|||||||
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
if (auto* v = dynamic_cast<Vst3Instrument*>(inst.get()))
|
||||||
v->setChannel(channel);
|
v->setChannel(channel);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (!loaded) return false;
|
if (!loaded) return false;
|
||||||
// init() AFTER load: FluidSynth creates its synth inside loadSoundFontFile.
|
// init() AFTER load: FluidSynth creates its synth inside loadSoundFontFile.
|
||||||
if (!inst->init(sampleRate, blockSize)) return false;
|
if (!inst->init(sampleRate, blockSize)) return false;
|
||||||
@@ -415,8 +429,11 @@ static float* alignF(std::vector<float>& v, size_t n) {
|
|||||||
// (0xC0000005) is caught here — the channel is muted instead of killing the
|
// (0xC0000005) is caught here — the channel is muted instead of killing the
|
||||||
// whole bridge process. /EHa (set in CMakeLists) allows mixing with the
|
// whole bridge process. /EHa (set in CMakeLists) allows mixing with the
|
||||||
// outer C++ try/catch net in main.cpp.
|
// outer C++ try/catch net in main.cpp.
|
||||||
|
#ifdef _WIN32
|
||||||
static DWORD g_sehCode = 0;
|
static DWORD g_sehCode = 0;
|
||||||
static void* g_sehAddr = nullptr;
|
static void* g_sehAddr = nullptr;
|
||||||
|
#endif
|
||||||
|
#ifdef _WIN32
|
||||||
static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR, uint32_t n, DWORD* outCode) {
|
static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR, uint32_t n, DWORD* outCode) {
|
||||||
*outCode = 0;
|
*outCode = 0;
|
||||||
__try {
|
__try {
|
||||||
@@ -429,6 +446,18 @@ static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR
|
|||||||
return false;
|
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) {
|
void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t numSamples) {
|
||||||
if (isBypassed_.load()) {
|
if (isBypassed_.load()) {
|
||||||
@@ -446,11 +475,12 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
|
|||||||
float* spR = alignF(scratchR_, numSamples);
|
float* spR = alignF(scratchR_, numSamples);
|
||||||
std::memset(spL, 0, numSamples * sizeof(float));
|
std::memset(spL, 0, numSamples * sizeof(float));
|
||||||
std::memset(spR, 0, numSamples * sizeof(float));
|
std::memset(spR, 0, numSamples * sizeof(float));
|
||||||
DWORD sehCode = 0;
|
uint32_t sehCode = 0;
|
||||||
if (!SafeProcessChannel(inst.get(), spL, spR, numSamples, &sehCode)) {
|
if (!SafeProcessChannel(inst.get(), spL, spR, numSamples, &sehCode)) {
|
||||||
// G3.3: plugin fault — mute this channel only, keep the bridge up.
|
// G3.3: plugin fault — mute this channel only, keep the bridge up.
|
||||||
if (!crashedCh_[ch]) {
|
if (!crashedCh_[ch]) {
|
||||||
crashedCh_[ch] = true;
|
crashedCh_[ch] = true;
|
||||||
|
#ifdef _WIN32
|
||||||
{
|
{
|
||||||
HMODULE m = nullptr;
|
HMODULE m = nullptr;
|
||||||
char mod[MAX_PATH] = "?";
|
char mod[MAX_PATH] = "?";
|
||||||
@@ -477,6 +507,11 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
|
|||||||
}
|
}
|
||||||
std::cerr << std::endl;
|
std::cerr << std::endl;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
std::cerr << "[NativeBridge] G3.3: channel " << ch
|
||||||
|
<< " plugin crashed in processAudioBlock — muted until reload"
|
||||||
|
<< std::endl;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,10 @@
|
|||||||
#include "RenderJob.h"
|
#include "RenderJob.h"
|
||||||
#include "NativeInstrumentEngine.h"
|
#include "NativeInstrumentEngine.h"
|
||||||
|
|
||||||
#ifdef HAVE_VST3SDK
|
// sheredom/json.h (public domain, vendored with the VST3 SDK) is header-only
|
||||||
|
// and needed for SF2/SFZ render even without the VST3 SDK — include it directly.
|
||||||
#include "vst3sdk/public.sdk/source/vst/moduleinfo/json.h"
|
#include "vst3sdk/public.sdk/source/vst/moduleinfo/json.h"
|
||||||
|
#ifdef HAVE_VST3SDK
|
||||||
#include "public.sdk/source/vst/vstpresetfile.h"
|
#include "public.sdk/source/vst/vstpresetfile.h"
|
||||||
#include "public.sdk/source/common/memorystream.h"
|
#include "public.sdk/source/common/memorystream.h"
|
||||||
using Steinberg::int32;
|
using Steinberg::int32;
|
||||||
@@ -37,7 +39,6 @@ using Steinberg::uint32;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
#ifdef HAVE_VST3SDK
|
|
||||||
// --- sheredom/json.h helpers -------------------------------------------------
|
// --- sheredom/json.h helpers -------------------------------------------------
|
||||||
const json_object_element_s* member(const json_object_s* o, const char* key) {
|
const json_object_element_s* member(const json_object_s* o, const char* key) {
|
||||||
for (const json_object_element_s* e = o ? o->start : nullptr; e; e = e->next)
|
for (const json_object_element_s* e = o ? o->start : nullptr; e; e = e->next)
|
||||||
@@ -73,7 +74,6 @@ int64_t memberInt(const json_object_s* o, const char* key, int64_t def) {
|
|||||||
double d;
|
double d;
|
||||||
return memberNumber(o, key, d) ? (int64_t)d : def;
|
return memberNumber(o, key, d) ? (int64_t)d : def;
|
||||||
}
|
}
|
||||||
#endif // HAVE_VST3SDK
|
|
||||||
|
|
||||||
// --- WAV writer (stdlib only, 16-bit PCM stereo, little-endian) --------------
|
// --- WAV writer (stdlib only, 16-bit PCM stereo, little-endian) --------------
|
||||||
void writeU16(std::ofstream& f, uint16_t v) {
|
void writeU16(std::ofstream& f, uint16_t v) {
|
||||||
@@ -247,10 +247,6 @@ int run_render_job(const std::string& jobPath, const std::string& outPath) {
|
|||||||
std::remove(outPath.c_str()); // never leave a partial clip behind
|
std::remove(outPath.c_str()); // never leave a partial clip behind
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifndef HAVE_VST3SDK
|
|
||||||
fail("built without the VST3 SDK submodule — JSON parser unavailable, --render disabled");
|
|
||||||
return rc;
|
|
||||||
#else
|
|
||||||
struct RootHolder { json_value_s* p = nullptr; ~RootHolder() { if (p) std::free(p); } } rootH;
|
struct RootHolder { json_value_s* p = nullptr; ~RootHolder() { if (p) std::free(p); } } rootH;
|
||||||
|
|
||||||
// 1. Read + parse the job.
|
// 1. Read + parse the job.
|
||||||
@@ -347,12 +343,18 @@ int run_render_job(const std::string& jobPath, const std::string& outPath) {
|
|||||||
if (type != InstrumentType::VST3) {
|
if (type != InstrumentType::VST3) {
|
||||||
std::cerr << "[RenderJob] preset ignored for non-VST3 type " << (int)type
|
std::cerr << "[RenderJob] preset ignored for non-VST3 type " << (int)type
|
||||||
<< " (SF2/SFZ use soundfont_bank/soundfont_program)" << std::endl;
|
<< " (SF2/SFZ use soundfont_bank/soundfont_program)" << std::endl;
|
||||||
|
#ifdef HAVE_VST3SDK
|
||||||
} else if (!applyVst3Preset(mgr.get(0), preset)) {
|
} else if (!applyVst3Preset(mgr.get(0), preset)) {
|
||||||
fail("preset import FAILED for " + pluginPath);
|
fail("preset import FAILED for " + pluginPath);
|
||||||
return rc; // rc stays 1: job/argument error
|
return rc; // rc stays 1: job/argument error
|
||||||
} else {
|
} else {
|
||||||
std::cout << "[RenderJob] preset imported (VST3 .vstpreset) type=" << (int)type << std::endl;
|
std::cout << "[RenderJob] preset imported (VST3 .vstpreset) type=" << (int)type << std::endl;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
} else {
|
||||||
|
std::cerr << "[RenderJob] preset ignored: built without VST3 SDK" << std::endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Offline render loop — same dispatch/render split as the realtime
|
// 6. Offline render loop — same dispatch/render split as the realtime
|
||||||
@@ -404,5 +406,4 @@ int run_render_job(const std::string& jobPath, const std::string& outPath) {
|
|||||||
<< " samples, " << (dataBytes / 4) << " frames)" << std::endl;
|
<< " samples, " << (dataBytes / 4) << " frames)" << std::endl;
|
||||||
rc = 0;
|
rc = 0;
|
||||||
return rc;
|
return rc;
|
||||||
#endif // HAVE_VST3SDK
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class ChannelWorker; // fwd — WM_DESTROY posts closeGUI() to the channel work
|
|||||||
static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker
|
static void post_close_gui(uint32_t ch, HWND hwnd); // defined after ChannelWorker
|
||||||
static void post_resize_view(uint32_t ch, int w, int h); // defined after ChannelWorker
|
static void post_resize_view(uint32_t ch, int w, int h); // defined after ChannelWorker
|
||||||
|
|
||||||
static InstrumentEngineManager* g_engine = nullptr;
|
static InstrumentEngineManager* g_engine = nullptr; // declared unconditionally (Linux --render build needs the symbol)
|
||||||
static std::mutex g_guiMutex;
|
static std::mutex g_guiMutex;
|
||||||
static std::map<uint32_t, void*> g_guiWindows; // channel -> HWND (keep window alive)
|
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 std::map<HWND, uint32_t> g_hwndToCh; // HWND -> channel (WM_DESTROY cleanup)
|
||||||
@@ -1010,7 +1010,9 @@ int main(int argc, char* argv[]) {
|
|||||||
// a JUCE_ window + plugin wndproc (ImmIsUIMessageW ->
|
// a JUCE_ window + plugin wndproc (ImmIsUIMessageW ->
|
||||||
// SendMessageW WM_IME_SETCONTEXT same hwnd) = infinite
|
// SendMessageW WM_IME_SETCONTEXT same hwnd) = infinite
|
||||||
// recursion (0xC00000FD, observed inside attachView).
|
// recursion (0xC00000FD, observed inside attachView).
|
||||||
|
#ifdef _WIN32
|
||||||
if (ok) disable_ime_contexts((HWND)hwnd);
|
if (ok) disable_ime_contexts((HWND)hwnd);
|
||||||
|
#endif
|
||||||
if (ok) ok = inst->attachView(hwnd);
|
if (ok) ok = inst->attachView(hwnd);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
std::cerr << "[NativeBridge] GUI attach EXCEPTION ch=" << guiCh
|
std::cerr << "[NativeBridge] GUI attach EXCEPTION ch=" << guiCh
|
||||||
@@ -1033,7 +1035,9 @@ int main(int argc, char* argv[]) {
|
|||||||
unmute_if_not_closing(y, "openGUI");
|
unmute_if_not_closing(y, "openGUI");
|
||||||
if (ok) {
|
if (ok) {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
disable_ime_contexts((HWND)hwnd);
|
disable_ime_contexts((HWND)hwnd);
|
||||||
|
#endif
|
||||||
|
|
||||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||||
<< " plugin=" << arg2 << " ch=" << guiCh
|
<< " plugin=" << arg2 << " ch=" << guiCh
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// native_bridge/src/main_linux.cpp
|
||||||
|
// Linux entry point of daw_vst_bridge: offline --render only (Docker).
|
||||||
|
// The Windows realtime SHM loop / VST3 GUI hosting is Windows-only and is
|
||||||
|
// not built here. See main.cpp for the full Windows entry.
|
||||||
|
#include "RenderJob.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
std::string renderJob, renderOut;
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
if (std::strcmp(argv[i], "--render") == 0 && i + 1 < argc) renderJob = argv[i + 1];
|
||||||
|
else if (std::strcmp(argv[i], "--out") == 0 && i + 1 < argc) renderOut = argv[i + 1];
|
||||||
|
}
|
||||||
|
if (renderJob.empty() || renderOut.empty()) {
|
||||||
|
std::cerr << "usage: daw_vst_bridge --render <job.json> --out <clip.wav>" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return run_render_job(renderJob, renderOut);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user