Phase 2: BuiltinFxChain C++ (8 DSP) + control ring SET_PARAM/REPORT_LATENCY
- native_bridge/src/BuiltinFxChain.{h,cpp}: 8 builtin DSP port 1:1 từ
Python _apply_builtin_fx_chain (eq/eqpro/compressor/limiter/exciter/rebalance)
và JS MASTER_MODULE_IO (imager/maximizer). Biquad RBJ DF2T, block-wise
stateful. createBuiltinFx trả nullptr cho gain/normalize (legacy).
- RenderFxJob: FxSlot thêm bfx, parse builtin 8 loại, runBuiltinSlotSafe SEH,
realtimeRunChain nhánh builtin, chainGen()/entryLatencies(), setParam
áp live lên bfx; Vst3Fx latencySamples từ getLatencySamples().
- RealtimeFxLoop: drain ctrl ring (SET_PARAM -> chain.setParam, drop cũ khi
đầy) + REPORT_LATENCY khi gen đổi (ipc->lat[] ring, lastGen).
- FxRealtimeIPC.h: header 18 u32 (72B), ctrl/lat slots 8, FxCtrlCmd 24B,
FxLatReport 8B, static_asserts.
- app/core/fx_realtime.py: mirror header 72B (ctrl_write/read, lat_write/read,
slots), set_param ghi ctrl ring qua _ctrl_enqueue_locked (guard shm),
get_latencies drain lat ring qua _lat_drain.
- tests/test_builtin_fx_golden.py: golden SNR C++ vs Python (8 cases, ngưỡng
30dB) + order test eq/compressor xen kẽ.
- TASKS_DAW_A.md: tick 2.1-2.9.
Test: 136 passed (4 fx_realtime_chain + 8 builtin golden + regression).
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// native_bridge/include/BuiltinFxChain.h
|
||||
// Builtin DSP chain (PLAN_DAW_A.md Phase 2): 8 custom FX được port từ Python
|
||||
// (render_engine.py `_apply_builtin_fx_chain`) + JS WebAudio (imager/maximizer,
|
||||
// MASTER_MODULE_IO). Serial, in-place, stereo — engine xử lý TOÀN chain; chạy
|
||||
// xen kẽ VST3 đúng thứ tự UI trong RenderFxJob (offline) + RealtimeFxChain.
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Forward: sheredom JSON object (params). Khai báo để factory dùng.
|
||||
struct json_object_s;
|
||||
|
||||
// Một FX builtin — stateful giữa các block (biquad z1/z2, env compressor...).
|
||||
class BuiltinFx {
|
||||
public:
|
||||
virtual ~BuiltinFx() = default;
|
||||
// Xử lý 1 block stereo in-place (L/R song song, interleaved-wise).
|
||||
virtual void process(float* L, float* R, uint32_t n) = 0;
|
||||
// SET_PARAM (SHM control ring, Phase 2.8): cập nhật tham số live.
|
||||
// Trả false nếu slot không có param này (gọi từ realtime loop thread,
|
||||
// cùng thread process → không cần lock).
|
||||
virtual bool setParam(const std::string& key, double value) { (void)key; (void)value; return false; }
|
||||
};
|
||||
|
||||
// Chain serial các BuiltinFx — giữ NGUYÊN thứ tự thêm vào.
|
||||
class BuiltinFxChain {
|
||||
public:
|
||||
explicit BuiltinFxChain(double sampleRate) : sampleRate_(sampleRate) {}
|
||||
void add(std::unique_ptr<BuiltinFx> fx, bool bypass);
|
||||
// Chạy toàn chain serial, in-place.
|
||||
void process(float* L, float* R, uint32_t n);
|
||||
bool setParam(int slot, const std::string& key, double value);
|
||||
size_t size() const { return entries_.size(); }
|
||||
private:
|
||||
struct Entry { std::unique_ptr<BuiltinFx> fx; bool bypass = false; };
|
||||
std::vector<Entry> entries_;
|
||||
double sampleRate_;
|
||||
};
|
||||
|
||||
// Factory: id ∈ eq|eqpro|imager|maximizer|compressor|limiter|exciter|rebalance
|
||||
// |gain|normalize (gain/normalize legacy giữ ở RenderFxJob — factory trả
|
||||
// nullptr cho 2 loại này). params = JSON object (sheredom), có thể null.
|
||||
std::unique_ptr<BuiltinFx> createBuiltinFx(const std::string& id,
|
||||
const json_object_s* params,
|
||||
double sampleRate);
|
||||
@@ -10,11 +10,13 @@
|
||||
#define FXRT_BLOCK 256
|
||||
#define FXRT_IN_SLOTS 4 // engine -> bridge
|
||||
#define FXRT_OUT_SLOTS 8 // bridge -> engine
|
||||
#define FXRT_CTRL_SLOTS 8 // SET_PARAM commands (engine -> bridge)
|
||||
#define FXRT_LAT_SLOTS 8 // REPORT_LATENCY reports (bridge -> engine)
|
||||
#define FXRT_STATE_STARTING 0
|
||||
#define FXRT_STATE_READY 1
|
||||
#define FXRT_STATE_ERROR 2
|
||||
|
||||
// Header 48 bytes (12 u32) — giữ thứ tự khớp fx_realtime.py.
|
||||
// Header 72 bytes (18 u32) — giữ thứ tự khớp fx_realtime.py.
|
||||
struct FxRealtimeHeader {
|
||||
volatile uint32_t magic;
|
||||
volatile uint32_t state;
|
||||
@@ -28,12 +30,35 @@ struct FxRealtimeHeader {
|
||||
volatile uint32_t outWrite; // producer: bridge
|
||||
volatile uint32_t outRead; // consumer: engine
|
||||
uint32_t outSlots;
|
||||
volatile uint32_t ctrlWrite; // producer: engine (SET_PARAM ring)
|
||||
volatile uint32_t ctrlRead; // consumer: bridge
|
||||
uint32_t ctrlSlots;
|
||||
volatile uint32_t latWrite; // producer: bridge (REPORT_LATENCY ring)
|
||||
volatile uint32_t latRead; // consumer: engine
|
||||
uint32_t latSlots;
|
||||
};
|
||||
|
||||
// SET_PARAM: {slot_idx, key[16], value} — 24 bytes.
|
||||
struct FxCtrlCmd {
|
||||
volatile uint32_t slot;
|
||||
char key[16];
|
||||
volatile float value;
|
||||
};
|
||||
static_assert(sizeof(FxCtrlCmd) == 24, "FxCtrlCmd must be 24 bytes");
|
||||
|
||||
// REPORT_LATENCY: {slot_idx, samples} — 8 bytes.
|
||||
struct FxLatReport {
|
||||
volatile uint32_t slot;
|
||||
volatile uint32_t samples;
|
||||
};
|
||||
static_assert(sizeof(FxLatReport) == 8, "FxLatReport must be 8 bytes");
|
||||
|
||||
struct FxRealtimeIPC {
|
||||
FxRealtimeHeader h;
|
||||
float inL[FXRT_IN_SLOTS][FXRT_BLOCK];
|
||||
float inR[FXRT_IN_SLOTS][FXRT_BLOCK];
|
||||
float outL[FXRT_OUT_SLOTS][FXRT_BLOCK];
|
||||
float outR[FXRT_OUT_SLOTS][FXRT_BLOCK];
|
||||
FxCtrlCmd ctrl[FXRT_CTRL_SLOTS];
|
||||
FxLatReport lat[FXRT_LAT_SLOTS];
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Offline FX render mode (Phase 0 of PLAN_MASTERBUS_FX_RACK_VST.md):
|
||||
// `daw_vst_bridge --render-fx <job.json> --in <input.wav> --out <output.wav>`
|
||||
@@ -72,6 +73,15 @@ public:
|
||||
// JSON: [{"path":"...","bypass":true|false},...] — chain entries replace
|
||||
// the previous chain atomically. May be called from any thread.
|
||||
void setChain(const std::string& json, double sampleRate, int32_t blockSize);
|
||||
// SET_PARAM (Phase 2.8): áp dụng live vào builtin slot (slot index theo
|
||||
// chain JSON). Gọi từ loop thread.
|
||||
void setParam(int slot, const std::string& key, double value);
|
||||
// Phase 2.8 (SHM control ring): chain generation — tăng mỗi lần worker
|
||||
// swap chain; RealtimeFxLoop so gen → báo lại latency qua lat ring.
|
||||
uint64_t chainGen();
|
||||
// Per-slot latency samples (thứ tự khớp chain JSON): builtin = 0 (IIR
|
||||
// zero-latency), VST3 = getLatencySamples (đọc lúc load) — PDC Phase 3.
|
||||
std::vector<uint32_t> entryLatencies();
|
||||
// Audio thread only: run the chain in-place over one stereo block.
|
||||
void process(float* inL, float* inR, uint32_t n);
|
||||
void shutdown();
|
||||
|
||||
Reference in New Issue
Block a user