fix(fx-rt): het crackle master FX - limiter attack smooth + crossfade khi bat/tat chain

- NativeInstrumentEngine: kAttack 0.9->0.1 (het zipper khi sum nhieu track)
- app.jsx: crossfade masterFxDryGain/masterFxGain thay hard-rewire graph luc
  activation/deactivation master chain FX; worklet bao 'started' -> ramp dung
  luc; PDC tre sau khi dry silent (tranh gap 80ms)
- sf-fx-realtime.js: fade-in 512 mau luc FILL xong / resume sau underrun
- index.html + addModule: cache-buster v=202608241200
- rebuild app.precompiled.js, copy bundle vao dist/ + src-tauri/resources/
- keo dai fix cac round truoc: FILL 16, FXRT_BURST_MS, REBUFFER, pacing,
  VST GUI click/focus, solo crackle
- them harness + plans/patch scripts
This commit is contained in:
2026-08-24 07:46:09 +07:00
parent 373a96cf7b
commit 10997a65b9
21 changed files with 1181 additions and 177 deletions
+36 -15
View File
@@ -1,4 +1,4 @@
// native_bridge/src/NativeInstrumentEngine.cpp
// native_bridge/src/NativeInstrumentEngine.cpp
#include "NativeInstrumentEngine.h"
#include <fluidsynth.h>
@@ -25,6 +25,7 @@
#include <iostream>
#include <thread>
#include <chrono>
#include <cmath>
#ifdef _WIN32
#endif
@@ -543,11 +544,14 @@ static bool SafeProcessChannel(INativeInstrument* inst, float* outL, float* outR
#endif
// --- Auto-calibration (am luong on dinh moi engine VST2/VST3/SF2/SFZ) ---
// Probe-render 3 notes (60/64/67, velocity 0.8) ngay sau khi load, do peak,
// tinh makeup sao cho note don ra ~ -6 dBFS (0.5). Clamp: khong bu patch cam
// qua muc, khong tat patch to. renderAll ap dung makeup + peak-safety @1.0
// (chord/transient vuot 1.0 se bi keo ve 1.0 - khong clip, khong meo).
static constexpr float kCalTargetPeak = 0.5f;
// Probe-render 3 notes (60/64/67, velocity 0.8) ngay sau khi load, do peak
// + RMS, tinh makeup = min(gRms, gPeak) sao cho note don ra ~ -18 dBFS RMS
// va ~ -6 dBFS peak (he so nao nho hon thang de ca hai khong vuot nguong).
// Clamp: khong bu patch cam qua muc, khong tat patch to. renderAll ap dung
// makeup + mix limiter cap -3 dBFS (nhieu track cong vao master chain nam
// trong cua so -6..-3 dBFS peak).
static constexpr float kCalTargetPeak = 0.5f; // note don: peak ~ -6 dBFS
static constexpr float kCalTargetRms = 0.1259f; // note don: RMS ~ -18 dBFS (10^(-18/20))
static constexpr float kCalMinMakeup = 0.125f;
static constexpr float kCalMaxMakeup = 8.0f;
@@ -577,12 +581,18 @@ float InstrumentEngineManager::calibrate(INativeInstrument* inst, double sampleR
}
return true;
};
double sumSq = 0.0; // RMS chi do tren phan body (sustain), bo qua tail
uint64_t rmsN = 0;
for (int p : pitches) {
inst->noteOn(0, (uint32_t)p, 0.8f, 0);
uint32_t off = 0;
while (off < body) {
uint32_t n = bs; if (off + n > body) n = body - off;
if (!renderProbe(n)) { inst->noteOff(0, (uint32_t)p, 0); return 1.0f; }
for (uint32_t i = 0; i < n; ++i) {
sumSq += (double)L[i] * (double)L[i] + (double)R[i] * (double)R[i];
}
rmsN += n;
if (realtime) Sleep((DWORD)(n * 1000.0 / sampleRate + 0.5));
off += n;
}
@@ -595,14 +605,17 @@ float InstrumentEngineManager::calibrate(INativeInstrument* inst, double sampleR
off += n;
}
}
const float rms = (float)std::sqrt(sumSq / (2.0 * (double)rmsN));
if (peak < 1e-4f) {
std::cerr << "[Calibrate] silent probe peak=" << peak << " makeup=1.0" << std::endl;
std::cerr << "[Calibrate] silent probe peak=" << peak << " rms=" << rms << " makeup=1.0" << std::endl;
return 1.0f;
}
float g = kCalTargetPeak / peak;
float g = kCalTargetRms / rms; // de RMS chay dung -18 dBFS
const float gPeak = kCalTargetPeak / peak; // de peak khong vuot -6 dBFS
if (gPeak < g) g = gPeak; // min: ca hai khong vuot nguong
if (g < kCalMinMakeup) g = kCalMinMakeup;
if (g > kCalMaxMakeup) g = kCalMaxMakeup;
std::cerr << "[Calibrate] peak=" << peak << " makeup=" << g << std::endl;
std::cerr << "[Calibrate] peak=" << peak << " rms=" << rms << " makeup=" << g << std::endl;
return g;
}
@@ -673,17 +686,25 @@ void InstrumentEngineManager::renderAll(float* outputL, float* outputR, uint32_t
outputR[i] += spR[i];
}
}
// Smoothed brickwall limiter on the summed mix: fast attack (~2-3 samples)
// when the mix exceeds 1.0, slow release back to unity. Replaces the old
// per-block hard clamp whose gain step at block boundaries caused zipper
// crackle. limiterGain_ persists across renderAll calls.
const float kAttack = 0.4f;
// Mixer summing: smoothed brickwall limiter caps the summed mix at
// kCeilingPeak = -3 dBFS -> MASTER CHAIN INPUT nam trong cua so
// -6..-3 dBFS peak (1 track ~ -6 dBFS; nhieu track cong len, toi da -3 dBFS).
// Fast attack (~2-3 samples) khi mix vuot tran, slow release ve unity.
// Thay the hard clamp cu (gain step dau block gay zipper crackle).
// limiterGain_ persists across renderAll calls.
const float kCeilingPeak = 0.7071f; // -3 dBFS
// Attack 0.9 (near-instant) caused zipper crackle: nhieu track sum ~2.0,
// target 0.35 -> gain 1.0->0.42 trong 1 sample = step -7.6 dB moi transient.
// 0.1 = ~10 samples (0.2ms @48k) ve tran: smooth, van giu ceiling -3 dBFS.
const float kAttack = 0.1f;
const float kRelease = 0.0006f;
for (uint32_t i = 0; i < numSamples; ++i) {
float a = outputL[i] < 0.0f ? -outputL[i] : outputL[i];
float b = outputR[i] < 0.0f ? -outputR[i] : outputR[i];
const float m = a > b ? a : b;
const float target = (m * limiterGain_ > 1.0f) ? (1.0f / (m + 1e-12f)) : 1.0f;
const float target = (m * limiterGain_ > kCeilingPeak)
? (kCeilingPeak / (m + 1e-12f))
: 1.0f;
if (target < limiterGain_)
limiterGain_ += (target - limiterGain_) * kAttack;
else