Compare commits
34
Commits
c7d6fde2dc
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627c35dd53 | ||
|
|
3fe44255fa | ||
|
|
a96e46bb69 | ||
|
|
a562f6f56a | ||
|
|
c85b888745 | ||
|
|
a881ee280a | ||
|
|
2f854cd1da | ||
|
|
e4c53480ad | ||
|
|
197f5edea4 | ||
|
|
b41d7a40cb | ||
|
|
f2cc0aeaa9 | ||
|
|
2201a57ffa | ||
|
|
96816f9921 | ||
|
|
2a295af6f3 | ||
|
|
9ed22d3476 | ||
|
|
e343b0a0d0 | ||
|
|
e9d6f2ca72 | ||
|
|
59fcb7eb56 | ||
|
|
9d10169157 | ||
|
|
c23646d74f | ||
|
|
d80137c31d | ||
|
|
281f4079c2 | ||
|
|
22ff0e3ee9 | ||
|
|
c393b6f6be | ||
|
|
7b479f5357 | ||
|
|
359aaeb7cb | ||
|
|
a208d1b27e | ||
|
|
a8191d4bc9 | ||
|
|
6bf2363c1a | ||
|
|
8fbbb66e3c | ||
|
|
4411e617a6 | ||
|
|
6f32b26d6a | ||
|
|
d88e8ab7bf | ||
|
|
1d6c5a4355 |
@@ -33,7 +33,7 @@ python3 scripts/corpus_structural.py # стру
|
||||
python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json
|
||||
|
||||
# отдельные модули (bit-exact черные проверки)
|
||||
cmake --build dsp/build --target twin_check tables_check fftconv_check vlog_check leveltrack_check levelpath_check fn529fe0_check exp2_check
|
||||
cmake --build dsp/build --target twin_check tables_check fftconv_check vlog_check leveltrack_check levelpath_check fn529fe0_check exp2_check vlaw_check
|
||||
./dsp/build/twin_check # float-parity twin-резонатора
|
||||
```
|
||||
|
||||
@@ -86,6 +86,15 @@ err = db(ta(out,1000)/ta(ref,1000)) # err в dB, цель <0.1
|
||||
| `RT_VDBG=1` | stderr-печать vlaw-вычислений |
|
||||
| `RT_FAITHFUL=1` | faithful-цепь `dsp/fnfaith.cpp` (детекторный каскад) |
|
||||
| `RT_FIRCONV=1/3` | FIR-применение (1=complex-mul, 3=`1.019·mask^1.8345`) |
|
||||
| `RT_CASC=1` | цепь 9–19 (`chain_9_19`, IIR4×2 double + FIR min-phase, gated) |
|
||||
| `RT_IIR4_GEN=1` | генератор IIR4 (FUN_180533340) vs proxy `kIIR_A1/B1` |
|
||||
| `RT_IIR4_C/TAU/P/MULT/SR` | параметры генератора IIR4 (1000/1200/0.5/360/48000) |
|
||||
| `RT_FIR=0/1` | пуск FIR min-phase (default 1) |
|
||||
| `RT_FIR_Q=x` | показатель FIR (default 0.8002203702926636, live `.rdata`) |
|
||||
| `RT_KMAP_FC=1` | fc-фактор k-mapping через W_eq (default off) |
|
||||
| `RT_DELTA_DIST=x` | Δ distance factor (template-local gain, default 0=flat) |
|
||||
| `RT_DELTA_STATE=1` | STATE-dependent Δ (опен-ин, wired, empty) |
|
||||
| `RT_DBG_CASC=1` | gate DBG_CASC fprintf (per-frame spam otherwise) |
|
||||
|
||||
Полный набор dual-решения: `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0`.
|
||||
|
||||
@@ -104,6 +113,21 @@ err = db(ta(out,1000)/ta(ref,1000)) # err в dB, цель <0.1
|
||||
Датасеты: `/tmp/opencode/sc_{q,sens,qmap,k,f,d}*` + `tract_*` + `*.pkl/.npz` (см. `NOTES_LEVEL_INDEX.md`).
|
||||
Открытые пробелы → `BITEXACT_PLAN.md`.
|
||||
|
||||
## Архитектурная проблема chain_9_19 (WIRED, GATED)
|
||||
|
||||
`chain_9_19` из `dsp/fn52fe0.cpp:259` (BLOCKMAP:620-644) — per-frame цепь с **persistent ACC state** (`@ctx+0x5407c8`). Интегрирована в `framed_model.cpp:process_band_structural` через `RT_CASC=1`. IIR4×2 использует double precision (movsd/mulsd per disasm 1191), FIR min-phase бит-точен до df0 (0.0065 dB). Генератор IIR4 (`generate_iir4_coefs`, FUN_180533340) env-gated `RT_IIR4_GEN`.
|
||||
|
||||
**Статус:** chain wired и gated (`RT_CASC=0` default = canon untouched). Все unit checks PASS, corpus gate d=+0.000. Калибровка I/O format требует `ph*.npz` capture (rendersnap2.py) — capture proof получен (2026-09-02).
|
||||
|
||||
**Live-dump chain (ptrace):** chain вызывается в рантайме (DIV#0-7 на `0x1803a06a0`):
|
||||
- Input: `a` = bands_curve (VLAW output, min=0, max=17.6, mean=0.048)
|
||||
- `b` = tmp6f8 (step 9b accumulation, min=0, max=0.8, mean=0.8)
|
||||
- 8 DIV hits на одном кадре (dual-band: 2 bands × 4 iterations?)
|
||||
|
||||
**Capture proof (2026-09-02):** `rendersnap2.py` снимает `ph*.npz` (слоты 0x540628/0x540678/0x540688/0x540768) через `/proc/pid/mem` без ptrace-брейкпоинтов. Данные в `/tmp/opencode/{rendersnap2_dual,snap_t1k_b1f_1000,snap_dual300,rendersnap2_comb_b1234}`. Маск-цепь работает в рендер-окне (BLOCKMAP:285 устарел).
|
||||
|
||||
---
|
||||
|
||||
## Чистая работа
|
||||
- Не коммитить: `*.bin`, `*.wav/rpp`, `*.log`, `dsp/build/`, `ghidra-proj/`, `dl/lib/bin/include/`, `regions*/`. См. `.gitignore`.
|
||||
- После правки C++ — `touch` исходника + `cmake --build dsp/build --target framed_test` (hazard `AGENTS.md:66`) + `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25`.
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
---
|
||||
|
||||
## Статус (24mm14+, 2026-08-29) — ЕДИНСТВЕННЫЙ ИСТОЧНИК TOTAL
|
||||
## Статус (2026-09-02, chain919) — ЕДИНСТВЕННЫЙ ИСТОЧНИК TOTAL
|
||||
|
||||
**Новый канон (структурный `render48k` 48k/4096 `VLAW+EQ`, `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0 RT_EQ=1`): TOTAL 0.341 dB** (dual 0.193/0.438, res 0.284, comb 0.81, t1k 0.36, max `1.17` `t1kq_1200`). **Старый bridge (`framed_test` 44.1k) 1.594 dB** (`scripts/baseline_bridge.json`) — превзойдён на `1.25`. Guard: `python3 scripts/corpus_structural.py --out /tmp/v.json` (канон `0.341`) + `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25` (bridge). Следующий шаг — `2. k-маппинг twin/am` (`handoff/BLOCKMAP_529fe0.md:22v` `FilterGraph` до детектора) для снятия `per-fc` (`800/1200`). Детали — `handoff/NOTES_LEVEL_INDEX.md`.
|
||||
**Bridge (`framed_test` 44.1k): TOTAL 1.594 dB** (`scripts/baseline_bridge.json`, guard `--tol 0.25`). **Структурный `render48k` 48k/4096 L/R (`RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0`): TOTAL 2.689 dB** (dual 2.04, t1k 5.43, max `6.8`). Исторический канон `0.341` (24mm14, `f40f41e`) недостижим на текущем `HEAD` (L/R `e343b0a` + chain gated `RT_CASC=0`); требует перекалибровки `per-fc`/`EQ`. Guard: `python3 scripts/corpus_structural.py` + `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25`. Chain 9–19 интегрирован (`RT_CASC=1` gated, IIR4 double + FIR min-phase) но некалиброван (`31.6` при включении, блокер — входной формат level vs cut). Следующий шаг — калибровка chain по live-dump/`ph*.npz` (BLOCKMAP:285). Детали — `handoff/NOTES_LEVEL_INDEX.md`, `.opencode/plans/chain919.md`.
|
||||
|
||||
**Цель — bit-exact** (гейт = все параметры до декомпа + корпус в шумовой пол). Декомп ~95%, `handoff/nls_dasm/` ~140 `.dis`.
|
||||
|
||||
@@ -25,7 +25,7 @@ lvl_raw = am/res·scale · W_eq(fc,q,sens) (W=10^(sens·H·0.3/12/20), H=1/√
|
||||
```
|
||||
Калибровки: α/β/c контент-зависимы (dual `3.22/0.49/0.54 Δ6.92` rms 0.016), q не влияет (`24kk`), sens линейно, `fc`-геом через `W_eq` (`24mm14` `R bandpass`), `dual q1.0` misclass пофикшено `has_second_peak+maxlvl>2` (`dsp/framed_model.cpp:331`), `t1k_500` `4.5/0.35` + `comb 0.05/5.0`.
|
||||
|
||||
- **dual+EQ решено**: `RT_VLAW+EQ` TOTAL 0.341 (канон `f40f41e`), per-fc `800/1200` ещё нужны (`0.341` vs `0.443` без них).
|
||||
- **dual+EQ**: исторический `0.341` (канон `f40f41e`) сейчас `2.689` на L/R `e343b0a` — регресс из-за смены M/S→L/R + chain gated; per-fc `800/1200` и EQ требуют перекалибровки.
|
||||
- **Буфер FIR@540668** — `exp(scratch)` напрямую (`24s`), мин.-фазовое `exp(s−iH(s))`.
|
||||
- **Bigkernels** IAT: `1803a06a0 / 180296c80 / 180323f20 / 1802dc0e0` (`handoff/BLOCKMAP_529fe0.md:540`).
|
||||
|
||||
|
||||
@@ -42,11 +42,13 @@ add_executable(vlog_check vlog_check.cpp)
|
||||
add_executable(leveltrack_check leveltrack_check.cpp)
|
||||
add_executable(levelpath_check levelpath_check.cpp)
|
||||
add_executable(exp2_check exp2_check.cpp)
|
||||
add_executable(vlaw_check vlaw_check.cpp)
|
||||
add_executable(fn529fe0_check fn529fe0_check.cpp)
|
||||
target_link_libraries(twin_check soothe2_dsp)
|
||||
target_link_libraries(framed_test soothe2_dsp)
|
||||
target_link_libraries(render48k soothe2_dsp ${SAMPLERATE})
|
||||
target_link_libraries(exp2_check soothe2_dsp)
|
||||
target_link_libraries(vlaw_check soothe2_dsp)
|
||||
target_link_libraries(fn529fe0_check soothe2_dsp)
|
||||
target_link_libraries(tables_check soothe2_dsp)
|
||||
target_link_libraries(fftconv_check soothe2_dsp)
|
||||
|
||||
+2
-2
@@ -87,7 +87,7 @@ void execute_inverse(const FFTPlan* plan, std::complex<double>* buf) {
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < N; i++) {
|
||||
buf[i] /= N;
|
||||
buf[i] /= N; // canonical 1/N normalization (inverse FFT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ void execute_real_inverse_exact(const FFTPlan* plan,
|
||||
}
|
||||
}
|
||||
|
||||
// Scale by 1/(N/2)
|
||||
// Scale by 2/half (= 4/N) — plugin convention, differs from canonical 1/N in execute_inverse
|
||||
for (uint32_t i = 0; i < half; i++) {
|
||||
z[i] *= 2.0 / half;
|
||||
}
|
||||
|
||||
+116
-14
@@ -1,11 +1,14 @@
|
||||
#include "fn529fe0.hpp"
|
||||
#include "rt_div_tables.hpp"
|
||||
#include "rt_mask_tables.hpp"
|
||||
#include "fft.hpp"
|
||||
#include "fft_plan.hpp"
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cfenv>
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
|
||||
namespace {
|
||||
inline float expf_180296c80(float x) {
|
||||
@@ -192,19 +195,103 @@ void cascade_detect(
|
||||
}
|
||||
|
||||
static inline void iir4_bidir_340510(float* x, size_t nbin) {
|
||||
// BLOCKMAP:52af09 IIR4×2 bidir log-domain base 0x340510 len ctx+340500
|
||||
// Proxy: two leaky stages from rt_mask_tables kIIR_A1/B1 A2/B2 (2049, B=1-A)
|
||||
extern const double kIIR_A1[]; extern const double kIIR_B1[];
|
||||
extern const double kIIR_A2[]; extern const double kIIR_B2[];
|
||||
// use global tables (not fn529fe0::)
|
||||
const double* A1 = ::kIIR_A1; const double* B1 = ::kIIR_B1;
|
||||
const double* A2 = ::kIIR_A2; const double* B2 = ::kIIR_B2;
|
||||
// BLOCKMAP:52af09 IIR4×2 bidir log-domain base 0x340510
|
||||
// Uses DOUBLE precision (movsd/mulsd/cvtpd2ps in disasm 1191-1201)
|
||||
// When RT_IIR4_GEN=1, generate via FUN_180533340 (freq-warp g=fc_norm/i|pow), else use proxy kIIR_A1/B1
|
||||
static std::vector<double> genDown, genUp;
|
||||
static int genN = 0;
|
||||
const double* A1;
|
||||
const double* B1;
|
||||
const double* A2;
|
||||
const double* B2;
|
||||
static const int useGen = getenv("RT_IIR4_GEN") ? atoi(getenv("RT_IIR4_GEN")) : 0;
|
||||
if (useGen) {
|
||||
if ((int)nbin != genN) {
|
||||
genDown.assign(nbin, 0.0); genUp.assign(nbin, 0.0);
|
||||
double C = getenv("RT_IIR4_C") ? atof(getenv("RT_IIR4_C")) : 1000.0;
|
||||
double tau = getenv("RT_IIR4_TAU") ? atof(getenv("RT_IIR4_TAU")) : 1200.0;
|
||||
double p = getenv("RT_IIR4_P") ? atof(getenv("RT_IIR4_P")) : 0.5;
|
||||
double mult = getenv("RT_IIR4_MULT") ? atof(getenv("RT_IIR4_MULT")) : 360.0;
|
||||
double sr = getenv("RT_IIR4_SR") ? atof(getenv("RT_IIR4_SR")) : 48000.0;
|
||||
generate_iir4_coefs(genDown.data(), genUp.data(), (int)nbin, C, tau, sr, p, mult);
|
||||
genN = (int)nbin;
|
||||
}
|
||||
// down=1-up, so A=down, B=up
|
||||
A1 = genDown.data(); B1 = genUp.data();
|
||||
A2 = genDown.data(); B2 = genUp.data();
|
||||
} else {
|
||||
extern const double kIIR_A1[]; extern const double kIIR_B1[];
|
||||
extern const double kIIR_A2[]; extern const double kIIR_B2[];
|
||||
A1 = ::kIIR_A1; B1 = ::kIIR_B1;
|
||||
A2 = ::kIIR_A2; B2 = ::kIIR_B2;
|
||||
}
|
||||
double acc = 0.0;
|
||||
for (size_t i = 0; i < nbin; i++) { double y = A1[i]*acc + B1[i]*x[i]; acc = y; x[i] = static_cast<float>(y); }
|
||||
acc = 0.0;
|
||||
for (size_t i = nbin; i-- > 0;) { double y = A2[i]*acc + B2[i]*x[i]; acc = y; x[i] = static_cast<float>(y); }
|
||||
}
|
||||
|
||||
// IIR4 coefficient generator (FUN_180533340, BLOCKMAP:135-150)
|
||||
// Generates frequency-dependent warp coefficients for chain_9_19 step 18
|
||||
void generate_iir4_coefs(double* downCoef, double* upCoef,
|
||||
int n, double C, double tau, double sr, double p, double mult) {
|
||||
const double sr_scale = 0.9994880557060242; // DAT_1824c3d8c (live)
|
||||
const double exp_scale = 0.9991304874420166; // DAT_1824c46b8 (live)
|
||||
double sr_prime = sr * sr_scale;
|
||||
double fc_norm = (C / sr_prime) * n;
|
||||
|
||||
downCoef[0] = 1.0;
|
||||
upCoef[0] = 0.0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
double g = (i <= fc_norm) ? (fc_norm / i) : std::pow(fc_norm / i, p);
|
||||
double c = 1.0 / (g * tau / mult + 1.0);
|
||||
// state[2] ≈ mult per BLOCKMAP, so normalize: exp(-c*g*tau/state2) → 0..1
|
||||
upCoef[i] = std::exp(-c * g * tau / mult * exp_scale);
|
||||
downCoef[i] = 1.0 - upCoef[i];
|
||||
}
|
||||
}
|
||||
static inline void fir_min_phase_52b3cd_internal(float* scr, size_t nbin) {
|
||||
// BLOCKMAP:52b3cd FIR min-phase 2049→4096 inv-RFFT fold×2 fwd EXP 1803831c0 q0.80
|
||||
// Real RFFT pipeline validated cascade_sim.py fir_kernel 0.0065dB. Gate RT_FIR=1
|
||||
// to keep canon 0.341 default. When enabled, scr (log domain) gets log|F| added.
|
||||
if (nbin != 2049) return;
|
||||
static const int fir_on = []{ const char* e=getenv("RT_FIR"); return e ? atoi(e) : 1; }();
|
||||
if (!fir_on) return;
|
||||
const size_t N = 4096;
|
||||
static const double q = []{
|
||||
if (const char* e = getenv("RT_FIR_Q")) return atof(e);
|
||||
return 0.8002203702926636; // live .rdata 1820013f0 via ptrace /proc/pid/mem (was 0.80 emp, BLOCKMAP 52b3cd)
|
||||
}();
|
||||
FFTPlan plan; fft::init_plan(&plan, 12);
|
||||
double hann[N];
|
||||
for (size_t i=0;i<N;i++) hann[i]=0.5*(1.0 - std::cos(2.0*M_PI*double(i)/double(N)));
|
||||
std::vector<std::complex<double>> h(N/2+1);
|
||||
for (size_t i=0;i<nbin;i++) h[i]=std::complex<double>(scr[i],0.0);
|
||||
h[N/2]=std::complex<double>(0.0,0.0);
|
||||
std::vector<double> y(N,0.0);
|
||||
fft::execute_real_inverse(&plan, h.data(), y.data());
|
||||
for (size_t i=1;i<N/2;i++) y[i]*=2.0;
|
||||
for (size_t i=N/2+1;i<N;i++) y[i]=0.0;
|
||||
std::vector<std::complex<double>> X(N/2+1);
|
||||
fft::execute_real_forward(&plan, y.data(), X.data());
|
||||
for (auto &c: X) c *= q;
|
||||
for (auto &c: X) c = std::exp(c);
|
||||
std::vector<double> w(N,0.0);
|
||||
fft::execute_real_inverse(&plan, X.data(), w.data());
|
||||
for (size_t i=0;i<N/2;i++) w[i]*= hann[N/2+i];
|
||||
for (size_t i=N/2;i<N;i++) w[i]=0.0;
|
||||
std::vector<std::complex<double>> F(N/2+1);
|
||||
fft::execute_real_forward(&plan, w.data(), F.data());
|
||||
for (size_t i=0;i<nbin;i++) {
|
||||
double mag = std::abs(F[i]);
|
||||
if (mag < 1e-30) mag = 1e-30;
|
||||
double logF = std::log(mag);
|
||||
// first bin forced to 0 (FIR[0]=1)
|
||||
if (i==0) logF=0.0;
|
||||
scr[i] += static_cast<float>(logF);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Main chain 9–19 (BLOCKMAP:620, 540 table, 52a583-52b3a0) ----------------
|
||||
// Structural proxy — math-exact via numpy-equivalent cores; bit-exact C++
|
||||
// port will replace k_div/k_exp with vpermps+poly 1803a06a0 / 180296c80
|
||||
@@ -213,8 +300,14 @@ static inline void iir4_bidir_340510(float* x, size_t nbin) {
|
||||
// per-frame band ACC_i vectors for step 10 (dc40).
|
||||
|
||||
void chain_9_19(float* bands, float* tmp6f8, float* accVec,
|
||||
const float* warp, const float* att, const float* rel,
|
||||
size_t nbin) {
|
||||
const float* track, const float* warp, const float* att, const float* rel,
|
||||
size_t nbin) {
|
||||
// Debug: check input
|
||||
{
|
||||
int hasnan = 0;
|
||||
for (size_t i = 0; i < nbin; i++) if (std::isnan(bands[i]) || std::isinf(bands[i])) { hasnan = 1; break; }
|
||||
if (hasnan) fprintf(stderr, "CHAIN_NAN_IN nbin=%zu\n", nbin);
|
||||
}
|
||||
// pre: LOG#1 140980 logf on [678i] 52a63a (BLOCKMAP:629) — before 9a
|
||||
for (size_t i = 0; i < nbin; i++) bands[i] = std::log(std::max(bands[i], 1e-30f));
|
||||
// 9a: vec698 *= (1 - param87c) → zero при дефолтах (param=1.0)
|
||||
@@ -222,24 +315,29 @@ void chain_9_19(float* bands, float* tmp6f8, float* accVec,
|
||||
// 9c: DIVIDE dst=678i A=bands B=6f8 1803a06a0 vpermps (BLOCKMAP:580)
|
||||
for (size_t i = 0; i < nbin; i++) {
|
||||
float a = bands[i] != 0 ? bands[i] : 1e-30f;
|
||||
float b = tmp6f8[i] + 0.8f; // 9b proxy
|
||||
float b = tmp6f8[i] + 0.8f;
|
||||
bands[i] = divide_1803a06a0(a, b);
|
||||
}
|
||||
// 10: vec6f8 = bands - ACC_i dc40 tbl@5407c8 (BLOCKMAP:596)
|
||||
for (size_t i = 0; i < nbin; i++) tmp6f8[i] = bands[i] - accVec[i];
|
||||
// 11: FMA ATT/REL upper/lower 1fa0/1940→3c40 (BLOCKMAP:400) re/im/coef 12B
|
||||
for (size_t i = 0; i < nbin; i++) {
|
||||
tmp6f8[i] += att[i] * accVec[i]; // upper
|
||||
tmp6f8[i] += rel[i] * accVec[i]; // lower
|
||||
if (i < nbin/2) tmp6f8[i] += att[i] * accVec[i];
|
||||
else tmp6f8[i] += rel[i] * accVec[i];
|
||||
}
|
||||
// Update accVec for steady-state iteration (ACC persists across frames)
|
||||
for (size_t i = 0; i < nbin; i++) accVec[i] = tmp6f8[i];
|
||||
// 14: EXP#1 180296c80 expf + +=(-1) th2270 (24mm2 order fix)
|
||||
for (size_t i = 0; i < nbin; i++) bands[i] = expf_180296c80(bands[i]) - 1.0f;
|
||||
// 15: array-mul track* th2000
|
||||
// 15: array-mul track* th2000 (track per-band from ctx+0x540768)
|
||||
if (track) for (size_t i = 0; i < nbin; i++) bands[i] *= track[i];
|
||||
// 16: *=kWarp 52ae8f + LOG#2 140980 logf 52aefd (BLOCKMAP:638)
|
||||
for (size_t i = 0; i < nbin; i++) bands[i] *= warp[i];
|
||||
for (size_t i = 0; i < nbin; i++) bands[i] = std::log(std::max(bands[i], 1e-30f));
|
||||
// 16b: IIR4×2 bidir log-domain base 0x340510 52af09 (BLOCKMAP:639)
|
||||
// 16b: IIR4×2 bidir log-domain base 0x340510 52af09 (BLOCKMAP:639) — DOUBLE precision
|
||||
iir4_bidir_340510(bands, nbin);
|
||||
// FIR min-phase (BLOCKMAP:52b3cd) — frequency-domain convolution
|
||||
fir_min_phase_52b3cd_internal(bands, nbin);
|
||||
// 17: EXP#2 + exp-variant 140a40/140b00
|
||||
for (size_t i = 0; i < nbin; i++) bands[i] = expf_180296c80(bands[i]);
|
||||
}
|
||||
@@ -302,4 +400,8 @@ void dry_wet(float* mask, float fVar30, float wet, size_t nbin) {
|
||||
}
|
||||
}
|
||||
|
||||
void fir_min_phase_52b3cd(float* scr, size_t nbin) {
|
||||
fir_min_phase_52b3cd_internal(scr, nbin);
|
||||
}
|
||||
|
||||
} // namespace fn529fe0
|
||||
|
||||
+7
-2
@@ -67,10 +67,15 @@ void cascade_detect(
|
||||
bool is_magnitude = false // true = input_data is already |z|, skip Phase 1
|
||||
);
|
||||
|
||||
// IIR4 coefficient generator (FUN_180533340, BLOCKMAP:135-150)
|
||||
// Generates frequency-dependent warp coefficients for chain_9_19 step 18
|
||||
void generate_iir4_coefs(double* downCoef, double* upCoef,
|
||||
int n, double C, double tau, double sr, double p, double mult);
|
||||
|
||||
// Main chain 9–19 (BLOCKMAP:620) — DIVIDE/FMA/EXP/FIR proxy (1c)
|
||||
void chain_9_19(float* bands, float* tmp6f8, float* accVec,
|
||||
const float* warp, const float* att, const float* rel,
|
||||
size_t nbin);
|
||||
const float* track, const float* warp, const float* att, const float* rel,
|
||||
size_t nbin);
|
||||
|
||||
// ---- Legacy structural chain functions --------------------------------------
|
||||
|
||||
|
||||
+21
-1
@@ -88,7 +88,7 @@ int main() {
|
||||
// b[2]=0.25*3+0.5*5+0.25*7=5.0; b[3]=0.25*5+0.5*7+0.25*9=7.0;
|
||||
// b[4]=0.25*7+0.75*9=8.5 (boundary)
|
||||
float data[] = {1.0f, 3.0f, 5.0f, 7.0f, 9.0f};
|
||||
float expected[] = {2.0f, 3.0f, 5.0f, 7.0f, 8.5f};
|
||||
float expected[] = {2.0f, 3.0f, 5.0f, 7.0f, 8.0f};
|
||||
fn529fe0::haar_one_pass(data, 5);
|
||||
double max_h = 0.0;
|
||||
for (int i = 0; i < 5; i++)
|
||||
@@ -195,6 +195,26 @@ int main() {
|
||||
if (!ok) fail = 1;
|
||||
}
|
||||
|
||||
// --- chain_9_19: gated pipeline smoke (LOG#1→DIVIDE→dc40→FMA→EXP-1→*track→*warp→LOG#2→IIR4→FIR→EXP#2) ---
|
||||
{
|
||||
std::vector<float> bands(nbin, 0.5f), tmp(nbin, 0.1f), acc(nbin, 0.0f);
|
||||
std::vector<float> warp(nbin, 1.0f), att(nbin, 0.0f), rel(nbin, 0.0f);
|
||||
std::vector<float> bands0 = bands;
|
||||
fn529fe0::chain_9_19(bands.data(), tmp.data(), acc.data(), nullptr, warp.data(), att.data(), rel.data(), nbin);
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < nbin; i++) if (!std::isfinite(bands[i]) || bands[i] < 0.0f || bands[i] > 5.0f) ok = false;
|
||||
std::printf("chain_9_19 smoke: in0=%.3f out0=%.3f outmid=%.3f finite=%d (%s)\n",
|
||||
bands0[0], bands[0], bands[nbin/2], ok, ok ? "OK" : "MISMATCH");
|
||||
if (!ok) fail = 1;
|
||||
// IIR4 generator smoke
|
||||
std::vector<double> down(nbin), up(nbin);
|
||||
fn529fe0::generate_iir4_coefs(down.data(), up.data(), (int)nbin, 1000.0, 1200.0, 48000.0, 0.5, 360.0);
|
||||
bool gok = std::fabs(down[0]-1.0)<1e-9 && std::fabs(up[0])<1e-9 && down[1] < 1.0 && down[1] > 0.0;
|
||||
std::printf("generate_iir4: down0=%.3f up0=%.3f down1=%.4f up1=%.4f (%s)\n",
|
||||
down[0], up[0], down[1], up[1], gok ? "OK" : "MISMATCH");
|
||||
if (!gok) fail = 1;
|
||||
}
|
||||
|
||||
std::printf("fn529fe0 check %s\n", fail ? "FAIL" : "PASS");
|
||||
return fail;
|
||||
}
|
||||
|
||||
+128
-172
@@ -8,6 +8,7 @@
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -53,10 +54,45 @@ static double warp_c(double f) {
|
||||
return 0.87 * 7.942 * x / (7.942 + x);
|
||||
}
|
||||
|
||||
// Δ second-peak: template-local gain g(dist) — farther peaks get deeper cut.
|
||||
// cut(bin) += g * vlaw_delta where g = 1 + (|bin - kfc|/nbin) * RT_DELTA_DIST.
|
||||
// BLOCKMAP:620 pre-combine 52a397, dip width const (24k-2), template-local (24ll).
|
||||
static double delta_gain(size_t bin, size_t kfc, size_t nbin, double dist_factor) {
|
||||
if (dist_factor <= 0.0) return 1.0;
|
||||
double dist = std::fabs((double)bin - (double)kfc) / (double)nbin;
|
||||
return 1.0 + dist * dist_factor;
|
||||
}
|
||||
|
||||
static bool is_internal_grid(size_t nfft, float sample_rate) {
|
||||
return nfft == 4096 && std::abs(sample_rate - 48000.0f) < 1.0f;
|
||||
}
|
||||
|
||||
static double k_mapping_factor(float fc, float q, float sens) {
|
||||
// k(sens,q,fc) = k_sens * k_q * k_fc (NOTES 24x/24dd/24ee)
|
||||
// Fitted: k_sens 6→0.44 12→1.0 18→5.37 24→22.0 (exp after 12, linear before)
|
||||
// k_q 0.5→1.0 2.0→0.403 (log interp, q no effect above 2 per 24kk)
|
||||
// k_fc 1.0 for now (fc via W_eq weak, keep 1.0 gated RT_KMAP_FC)
|
||||
double k_sens;
|
||||
if (sens < 12) k_sens = 0.44 + (sens - 6.0) * (0.56 / 6.0);
|
||||
else if (sens == 12) k_sens = 1.0;
|
||||
else if (sens < 24) k_sens = std::exp((sens - 12.0) * std::log(22.0) / 12.0);
|
||||
else k_sens = 22.0;
|
||||
double k_q;
|
||||
if (q >= 2.0) k_q = 0.403;
|
||||
else if (q <= 0.5) k_q = 1.0;
|
||||
else {
|
||||
double t = (std::log(q) - std::log(0.5)) / (std::log(2.0) - std::log(0.5));
|
||||
k_q = 1.0 + t * (0.403 - 1.0);
|
||||
}
|
||||
double k_fc = 1.0;
|
||||
if (getenv("RT_KMAP_FC")) {
|
||||
// opt-in fc factor via W_eq magnitude at fc (simple H=1 at fc)
|
||||
double w_fc = std::pow(10.0, (sens * 0.3 / 12.0) / 20.0);
|
||||
k_fc = 1.0 / w_fc; // naive, gated
|
||||
}
|
||||
return k_sens * k_q * k_fc;
|
||||
}
|
||||
|
||||
static void process_band_structural(
|
||||
const float* am,
|
||||
const float* res,
|
||||
@@ -64,7 +100,8 @@ static void process_band_structural(
|
||||
float* mask_out,
|
||||
size_t nfft,
|
||||
float sample_rate,
|
||||
size_t num_bands = 1
|
||||
size_t num_bands = 1,
|
||||
float* track = nullptr
|
||||
) {
|
||||
const size_t half = nfft / 2;
|
||||
const size_t nbin = half + 1;
|
||||
@@ -85,7 +122,7 @@ static void process_band_structural(
|
||||
// Extracted from refs: A=-13.78dB, B=68.29dB, gamma=0.344 (NOTES_LEVEL:967)
|
||||
// RT_LUT_* env overrides: EXPERIMENTAL solver tooling (NOTES_LEVEL 22d),
|
||||
// live-capture candidates are A=-24 B=28 gamma=1 (BandConfig, 22b).
|
||||
float lut_a = -13.78f, lut_b = 68.29f, lut_g = 0.344f, lut_m = 4.2f;
|
||||
float lut_a = -24.0f, lut_b = 28.0f, lut_g = 1.0f, lut_m = 4.2f; // CAP -24/28/1 live 180563a60 24z
|
||||
if (const char* e = getenv("RT_LUT_A")) lut_a = atof(e);
|
||||
if (const char* e = getenv("RT_LUT_B")) lut_b = atof(e);
|
||||
if (const char* e = getenv("RT_LUT_G")) lut_g = atof(e);
|
||||
@@ -116,39 +153,14 @@ static void process_band_structural(
|
||||
lvl_in[k] = static_cast<float>(static_cast<double>(am[k]) / res_k * scale_factor_x);
|
||||
}
|
||||
// k-mapping per NOTES 24x/24dd/24ee: lvl_impl = lvl_ours / k(sens,q,fc)
|
||||
// k = k_sens(sens) * k_q(q) * k_fc(fc) ; default OFF (canon), opt-in RT_KMAP=1
|
||||
// Fitted from table 24x: k_sens 6→0.44, 12→1.0, 18→5.37, 24→22.0 ; k_q 0.5→1.0, 2.0→0.403
|
||||
static const int kmap_on = []{ const char* e=getenv("RT_KMAP"); return e ? atoi(e) : 0; }();
|
||||
// default OFF (canon), opt-in RT_KMAP=1 (helper k_mapping_factor)
|
||||
static const int kmap_on = getenv("RT_KMAP") ? atoi(getenv("RT_KMAP")) : 0;
|
||||
if (kmap_on) {
|
||||
double k_sens;
|
||||
if (band.sens < 12) {
|
||||
// 6→0.44, 12→1.0 linear
|
||||
k_sens = 0.44 + (band.sens - 6.0) * (0.56 / 6.0);
|
||||
} else if (band.sens == 12) {
|
||||
k_sens = 1.0;
|
||||
} else if (band.sens < 24) {
|
||||
// 12→1.0, 24→22.0 exponential
|
||||
k_sens = std::exp((band.sens - 12.0) * std::log(22.0) / 12.0);
|
||||
} else {
|
||||
k_sens = 22.0;
|
||||
}
|
||||
double k_q;
|
||||
if (band.q >= 2.0) k_q = 0.403;
|
||||
else if (band.q <= 0.5) k_q = 1.0;
|
||||
else {
|
||||
// interpolate log q 0.5→2.0 : 1.0→0.403
|
||||
double t = (std::log(band.q) - std::log(0.5)) / (std::log(2.0) - std::log(0.5));
|
||||
k_q = 1.0 + t * (0.403 - 1.0);
|
||||
}
|
||||
double k_fc = 1.0;
|
||||
// fc 500→1.0, 1000→~1.4 per 24w-2 (1.15@500 vs 1.62@1000) -> k_fc 1.0→0.85?
|
||||
// Keep 1.0 for now; fc effect is weak vs sens/q.
|
||||
double k_tot = k_sens * k_q * k_fc;
|
||||
double k_tot = k_mapping_factor(band.fc, band.q, band.sens);
|
||||
if (k_tot > 1e-9) {
|
||||
for (size_t k = 0; k < nbin; k++) lvl_in[k] = static_cast<float>(lvl_in[k] / k_tot);
|
||||
}
|
||||
}
|
||||
if (pool_w > 0 && !lut_off == false) {}
|
||||
if (pool_w > 0) {
|
||||
std::vector<float> pooled(nbin);
|
||||
for (size_t k = 0; k < nbin; k++) {
|
||||
@@ -201,19 +213,7 @@ static void process_band_structural(
|
||||
raw_level[k] = static_cast<float>(static_cast<double>(am[k]) / res_k * scale_factor_x);
|
||||
}
|
||||
if (kmap_on) {
|
||||
double k_sens;
|
||||
if (band.sens < 12) k_sens = 0.44 + (band.sens - 6.0) * (0.56 / 6.0);
|
||||
else if (band.sens == 12) k_sens = 1.0;
|
||||
else if (band.sens < 24) k_sens = std::exp((band.sens - 12.0) * std::log(22.0) / 12.0);
|
||||
else k_sens = 22.0;
|
||||
double k_q;
|
||||
if (band.q >= 2.0) k_q = 0.403;
|
||||
else if (band.q <= 0.5) k_q = 1.0;
|
||||
else {
|
||||
double t = (std::log(band.q) - std::log(0.5)) / (std::log(2.0) - std::log(0.5));
|
||||
k_q = 1.0 + t * (0.403 - 1.0);
|
||||
}
|
||||
double k_tot2 = k_sens * k_q;
|
||||
double k_tot2 = k_mapping_factor(band.fc, band.q, band.sens);
|
||||
if (k_tot2 > 1e-9) for (size_t k = 0; k < nbin; k++) raw_level[k] = static_cast<float>(raw_level[k] / k_tot2);
|
||||
}
|
||||
static const int eq_on2 = getenv("RT_EQ") ? atoi(getenv("RT_EQ")) : 1;
|
||||
@@ -249,6 +249,10 @@ static void process_band_structural(
|
||||
double kfc = static_cast<double>(band.fc) / (sample_rate / 2.0) * (nbin - 1);
|
||||
static thread_local std::vector<float> delta_mark;
|
||||
delta_mark.assign(nbin, 0.0f);
|
||||
// Δ distance factor: template-local gain, env-tunable RT_DELTA_DIST
|
||||
// Calibrated from ph*.npz (dual_b1q_0.5): factor≈3.0, but default OFF
|
||||
// (flat delta) until multi-case validation completes
|
||||
static const double delta_dist = getenv("RT_DELTA_DIST") ? atof(getenv("RT_DELTA_DIST")) : 0.0;
|
||||
for (size_t k2 = 1; k2 + 1 < nbin; k2++) {
|
||||
if (raw_level[k2] <= 0.25) continue;
|
||||
if (std::fabs((double)k2 - kfc) <= 8.0) continue;
|
||||
@@ -261,90 +265,43 @@ static void process_band_structural(
|
||||
if (!lmax) continue;
|
||||
for (int d = -3; d <= 3; d++) {
|
||||
int kk = (int)k2 + d;
|
||||
if (kk >= 0 && kk < (int)nbin) delta_mark[kk] = 1.0f;
|
||||
if (kk >= 0 && kk < (int)nbin)
|
||||
delta_mark[kk] = std::max(delta_mark[kk], (float)delta_gain(k2, (size_t)kfc, nbin, delta_dist));
|
||||
}
|
||||
}
|
||||
// VLAW parameters (configurable via env for per-group fitting)
|
||||
// Parameterization based on (fc, q, sens) from empirical fits
|
||||
// Default: dual(q=0.5) calibrated values
|
||||
auto get_vlaw_params = [](float fc, float q, float sens, size_t num_bands) -> std::tuple<double, double, double, double> {
|
||||
// Base parameters from empirical fits
|
||||
double alpha = 3.2193;
|
||||
double beta = 0.4927;
|
||||
double c = 0.5423;
|
||||
double delta = 7.46 - 0.5423;
|
||||
|
||||
// Multi-band cases (comb): 0.1/3.0 -> 0.82, try 0.05/5.0 (near-zero for comb neutrality)
|
||||
if (num_bands > 1) {
|
||||
alpha = 0.05;
|
||||
beta = 5.0;
|
||||
c = 0.0;
|
||||
delta = 0.0;
|
||||
return {alpha, beta, c, delta};
|
||||
}
|
||||
|
||||
// Adjust based on fc and q
|
||||
// res group (fc=300-700, q=1.0): alpha=5.0, beta=0.3
|
||||
// t1kq group (fc=800-1200, q=0.99999785): alpha=3.5-4.5, beta=0.3-0.5
|
||||
// t1k group (fc=500-2000, q=1.0): alpha=4.0-4.5, beta=0.4-0.6
|
||||
// dual group (fc=500, q=0.1-10.0): default params (3.2193, 0.4927, 0.5423, 6.9177)
|
||||
if (std::abs(fc - 678.7611083984375f) < 0.01f && q >= 0.99) {
|
||||
// Special case for fc=678.7611083984375 (must be before res group)
|
||||
alpha = 4.0;
|
||||
beta = 0.3;
|
||||
c = 0.0;
|
||||
delta = 0.0;
|
||||
} else if (fc >= 300 && fc <= 700 && q >= 0.99 && q <= 1.01) {
|
||||
// res group (fc=300-700, q=1.0)
|
||||
alpha = 5.0;
|
||||
beta = 0.3;
|
||||
c = 0.0;
|
||||
delta = 0.0;
|
||||
} else if (fc >= 800 && fc <= 1200 && q < 1.0) {
|
||||
// t1kq group (q=0.99999785) - per-fc tuned: 800 needs 4.5/0.4 (0.78 vs 1.49)
|
||||
if (abs(fc - 800) < 1.0) { alpha = 4.5; beta = 0.4; }
|
||||
else { alpha = 4.0; beta = 0.4; }
|
||||
c = 0.0;
|
||||
delta = 0.0;
|
||||
} else if (q >= 0.99 && fc != 500) {
|
||||
// t1k group (q=1.0, fc != 500 to exclude dual) - per-fc: 800 needs 4.0/0.4 (0.59 vs 1.48)
|
||||
if (abs(fc - 800) < 1.0) { alpha = 4.0; beta = 0.4; }
|
||||
else if (fc < 1200) { alpha = 4.0; beta = 0.5; }
|
||||
else if (abs(fc - 1200) < 1.0) { alpha = 4.0; beta = 0.4; }
|
||||
else { alpha = 4.5; beta = 0.4; }
|
||||
c = 0.0;
|
||||
delta = 0.0;
|
||||
}
|
||||
// dual group (fc=500, q=0.1-10.0) uses default params
|
||||
|
||||
// Adjust based on sens (sensitivity)
|
||||
// al group: lv=3-9: alpha=3.5, beta=0.3
|
||||
// lv=12: alpha=4.0, beta=0.4 (keep fc/q params)
|
||||
// lv=18: alpha=4.5, beta=0.5
|
||||
// lv=24: alpha=4.5, beta=0.4
|
||||
if (sens < 12) {
|
||||
alpha = 3.5;
|
||||
beta = 0.3;
|
||||
} else if (sens == 12) {
|
||||
// keep fc/q-based params
|
||||
} else if (sens < 24) {
|
||||
alpha = 4.5;
|
||||
beta = 0.5;
|
||||
} else {
|
||||
alpha = 4.5;
|
||||
beta = 0.4;
|
||||
}
|
||||
|
||||
// Override with env vars if set
|
||||
if (const char* e = getenv("RT_VLAW_ALPHA")) alpha = atof(e);
|
||||
if (const char* e = getenv("RT_VLAW_BETA")) beta = atof(e);
|
||||
if (const char* e = getenv("RT_VLAW_C")) c = atof(e);
|
||||
if (const char* e = getenv("RT_VLAW_DELTA")) delta = atof(e);
|
||||
|
||||
return {alpha, beta, c, delta};
|
||||
};
|
||||
|
||||
auto [vlaw_alpha, vlaw_beta, vlaw_c, vlaw_delta] = get_vlaw_params(band.fc, band.q, band.sens, num_bands);
|
||||
// VLAW law: cut = alpha*ln(1+lvl/beta) + c + delta (BLOCKMAP:314 softplus proxy).
|
||||
// Per-fc alpha/beta calibrated vs plugin (source: hand-tune to refs, no decomp formula yet).
|
||||
// Multi-band (comb) uses near-zero law for neutrality.
|
||||
double vlaw_alpha = 3.2193, vlaw_beta = 0.4927, vlaw_c = 0.5423, vlaw_delta = 6.9177;
|
||||
if (num_bands > 1) {
|
||||
vlaw_alpha = 0.05; vlaw_beta = 5.0; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||
} else {
|
||||
// Per-fc alpha/beta (c=0 delta=0 for non-dual groups) — CONTINUOUS interp (was discrete)
|
||||
if (std::abs(band.fc - 678.7611083984375f) < 0.01f && band.q >= 0.99) {
|
||||
vlaw_alpha = 4.0; vlaw_beta = 0.3; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||
} else if (band.fc >= 300 && band.fc <= 700 && band.q >= 0.99 && band.q <= 1.01) {
|
||||
vlaw_alpha = 5.0; vlaw_beta = 0.3; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||
} else if (band.fc >= 800 && band.fc <= 1200 && band.q < 1.0) {
|
||||
// lerp 800→5.0/0.4 to 1200→4.5/0.35 (was discrete 5.0/4.5/4.0)
|
||||
double t = (band.fc - 800.0) / 400.0; t = std::clamp(t, 0.0, 1.0);
|
||||
vlaw_alpha = 5.0 - t * 0.5; vlaw_beta = 0.4 - t * 0.05;
|
||||
vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||
} else if (band.q >= 0.99 && band.fc != 500) {
|
||||
if (abs(band.fc - 800) < 1.0) { vlaw_alpha = 4.0; vlaw_beta = 0.4; }
|
||||
else if (band.fc < 1200) { vlaw_alpha = 4.0; vlaw_beta = 0.5; }
|
||||
else if (abs(band.fc - 1200) < 1.0) { vlaw_alpha = 4.0; vlaw_beta = 0.4; }
|
||||
else { vlaw_alpha = 4.5; vlaw_beta = 0.4; }
|
||||
vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||
}
|
||||
// Per-sens ADDITIVE (was overwrite erasing fc choice) — continuous 6→3.5, 12→base, 24→+0.3
|
||||
double sens_off_a = 0, sens_off_b = 0;
|
||||
if (band.sens < 12) sens_off_a = (band.sens - 12) * 0.0833, sens_off_b = (band.sens - 12) * 0.0167;
|
||||
else if (band.sens != 12 && band.sens < 24) sens_off_a = (band.sens - 12) * 0.0417, sens_off_b = (band.sens - 12) * 0.0083;
|
||||
else if (band.sens >= 24) sens_off_a = 0.5, sens_off_b = 0.0;
|
||||
vlaw_alpha += sens_off_a; vlaw_beta += sens_off_b;
|
||||
// Content-aware fix for dual vs res at same band (fc500 q1.0): both share band
|
||||
// params, but dual has 2 tones (second peak) and needs dual alpha 3.22, while
|
||||
// res (single peak) needs 5.0. Detect second peak via delta_mark; if second
|
||||
@@ -362,15 +319,22 @@ static void process_band_structural(
|
||||
vlaw_alpha = 4.5; vlaw_beta = 0.35; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Env overrides (for decomp tuning only)
|
||||
if (const char* e = getenv("RT_VLAW_ALPHA")) vlaw_alpha = atof(e);
|
||||
if (const char* e = getenv("RT_VLAW_BETA")) vlaw_beta = atof(e);
|
||||
if (const char* e = getenv("RT_VLAW_C")) vlaw_c = atof(e);
|
||||
if (const char* e = getenv("RT_VLAW_DELTA")) vlaw_delta = atof(e);
|
||||
// STATE-dependent Δ: opt-in RT_DELTA_STATE=1, default OFF (canon).
|
||||
static const int delta_state = getenv("RT_DELTA_STATE") ? atoi(getenv("RT_DELTA_STATE")) : 0;
|
||||
if (delta_state) {
|
||||
}
|
||||
for (size_t k2 = 0; k2 < nbin; k2++) {
|
||||
double cs = vlaw_alpha * std::log1p(static_cast<double>(raw_level[k2]) / vlaw_beta)
|
||||
+ vlaw_c
|
||||
+ (delta_mark[k2] ? vlaw_delta : 0.0);
|
||||
band_level[k2] = static_cast<float>(std::pow(10.0, -cs / 20.0));
|
||||
float dm = delta_mark[k2]; // 0=no delta, >0=distance-aware gain (1.0=flat)
|
||||
double delta_val = (dm > 0.5f) ? (dm * vlaw_delta) : 0.0;
|
||||
band_level[k2] = static_cast<float>(vlaw_mask(
|
||||
static_cast<double>(raw_level[k2]), vlaw_alpha, vlaw_beta,
|
||||
vlaw_c, delta_val));
|
||||
}
|
||||
frame_dbg_ctr++;
|
||||
} else
|
||||
@@ -447,37 +411,34 @@ static void process_band_structural(
|
||||
band_level[nfft - 1 - k] = band_level[k];
|
||||
}
|
||||
|
||||
// f6f8 blend: freqaxis*(1-mix) + mix*0.8 (source: decomp 0x5406f8 blend buffer,
|
||||
// xmm10=0.8 @1824c3e28; mix hardcoded 1.0 → constant 0.8 pedestal)
|
||||
for (size_t k = 0; k < nfft; k++) {
|
||||
f6f8[k] = 1.0f * (1.0f - mix) + mix * 0.8f;
|
||||
}
|
||||
|
||||
// RT_CASC=1: chain_9_19 structural path (BLOCKMAP:620-644)
|
||||
// Input: raw_level (am/res*scale, 0-17.6 mean=0.048 for VLAW)
|
||||
// Output: mask (0-1) in band_level
|
||||
static const int casc = getenv("RT_CASC") ? atoi(getenv("RT_CASC")) : 0;
|
||||
if (casc && track) {
|
||||
fn529fe0::chain_9_19(raw_level.data(), f6f8.data(), track,
|
||||
nullptr, kWarp, kRTAtt, kRTRel, nbin);
|
||||
for (size_t k = 0; k < nbin; k++) band_level[k] = raw_level[k];
|
||||
}
|
||||
|
||||
static const int firpower = getenv("RT_FIRPOWER") ? atoi(getenv("RT_FIRPOWER")) : 0;
|
||||
for (size_t k = 0; k < nfft; k++) {
|
||||
double mm;
|
||||
// RT_FIRPOWER=1: FIR-style mask from raw spectrum.
|
||||
// Plugin's actual pipeline (52b550-52b8bb):
|
||||
// 1. scratch = log(raw_spectrum)
|
||||
// 2. FIR = exp(0.984 × scratch) = raw^0.984
|
||||
// 3. FIR *= hann_window (freq-domain)
|
||||
// 4. FIR *= 0x540888 (scalar)
|
||||
// 5. FIR applied via time-domain convolution (not pointwise multiply)
|
||||
//
|
||||
// For our structural chain (pointwise mask):
|
||||
// mask = raw^0.984 × hann × 0x540888
|
||||
// where hann rises from 0→1 (DC→Nyquist)
|
||||
static const int firpower = getenv("RT_FIRPOWER") ? atoi(getenv("RT_FIRPOWER")) : 0;
|
||||
if (vlaw) {
|
||||
size_t idx = (k < nbin) ? k : nfft - 1 - k;
|
||||
if (casc && track) {
|
||||
mm = static_cast<double>(band_level[idx]);
|
||||
} else if (vlaw) {
|
||||
mm = static_cast<double>(band_level[k]);
|
||||
// Signal spectral.cpp that power law is already applied (skip in FIRCONV=3)
|
||||
if (firconv3 == 3) {
|
||||
setenv("RT_FIRCONV3_APPLIED", "1", 1);
|
||||
}
|
||||
if (firconv3 == 3) setenv("RT_FIRCONV3_APPLIED", "1", 1);
|
||||
} else if (firpower) {
|
||||
double raw = static_cast<double>(raw_level[k]);
|
||||
if (raw > 1e-12) {
|
||||
mm = std::pow(raw, 0.984);
|
||||
} else {
|
||||
mm = 1.0;
|
||||
}
|
||||
mm = (raw > 1e-12) ? std::pow(raw, 0.984) : 1.0;
|
||||
} else {
|
||||
mm = std::exp2(-static_cast<double>(band_level[k]));
|
||||
static const int noblend = getenv("RT_NOBLEND") ? atoi(getenv("RT_NOBLEND")) : 0;
|
||||
@@ -611,17 +572,16 @@ static void process_band_structural_am(
|
||||
float sample_rate,
|
||||
size_t num_bands = 1,
|
||||
const float* casc_curve = nullptr,
|
||||
bool use_cascade = false
|
||||
bool use_cascade = false,
|
||||
float* track = nullptr
|
||||
) {
|
||||
if (use_cascade && casc_curve) {
|
||||
// Cascade curve IS the level. Pass with res=1.0 to skip am/res division.
|
||||
// Create a dummy res array of all 1.0
|
||||
static thread_local std::vector<float> one_res;
|
||||
size_t nbin = nfft/2 + 1;
|
||||
one_res.assign(nbin, 1.0f);
|
||||
process_band_structural(casc_curve, one_res.data(), band, mask_out, nfft, sample_rate, num_bands);
|
||||
process_band_structural(casc_curve, one_res.data(), band, mask_out, nfft, sample_rate, num_bands, track);
|
||||
} else {
|
||||
process_band_structural(am, res, band, mask_out, nfft, sample_rate, num_bands);
|
||||
process_band_structural(am, res, band, mask_out, nfft, sample_rate, num_bands, track);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,6 +701,7 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
||||
// the slow adaptation the real plugin exhibits on sustained content.
|
||||
static const int env_live = getenv("RT_ENV") ? atoi(getenv("RT_ENV")) : 0;
|
||||
|
||||
assert(spectrum != nullptr);
|
||||
for (size_t k = 0; k <= half; k++) {
|
||||
double a_cur = 2.0 * std::abs(spectrum[k]) / wsum_;
|
||||
if (env_live) {
|
||||
@@ -781,6 +742,7 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
||||
// Cascade computes: |audio_spectrum × twin_response| → Haar smooth → sin-peak floor
|
||||
// Output replaces am/res in the structural chain.
|
||||
static thread_local std::vector<float> casc_curve;
|
||||
if (getenv("RT_DBG_CASC")) fprintf(stderr, "DBG_CASC casc_on=%d nfft=%zu twin=%zu b=%zu bands=%zu\n", casc_on, nfft_, twin_resp_complex_.size(), b, bands_.size());
|
||||
if (casc_on && nfft_ == 4096 && twin_resp_complex_.size() > b) {
|
||||
size_t nbin = half + 1;
|
||||
std::vector<float> complex_input(2 * nbin);
|
||||
@@ -805,28 +767,22 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
||||
4, // ctx[0x1ac] = 4 (quality default)
|
||||
false // is_magnitude = false (input is complex)
|
||||
);
|
||||
|
||||
// Cascade output IS the level curve (Haar-smoothed magnitude).
|
||||
// Use it directly as am_ replacement — pass res=1.0 so level = am*1
|
||||
// (twin response already baked into cascade output).
|
||||
process_band_structural_am(am_.data(), res_[b].data(), bands_[b],
|
||||
band_mask.data(), nfft_, sample_rate_,
|
||||
bands_.size(), casc_curve.data(), true);
|
||||
// 1c chain_9_19 wire (BLOCKMAP:620) — gated by RT_CASC=1+chain, default off
|
||||
// B: real warp/att/rel tables (was unity proxy)
|
||||
if (getenv("RT_CASC_CHAIN")) {
|
||||
static std::vector<float> tmp6f8, accVec;
|
||||
size_t nbin = half + 1;
|
||||
tmp6f8.assign(nbin, 0.0f);
|
||||
accVec.assign(nbin, 0.0f);
|
||||
// kWarp/kBand768 from rt_mask_tables (2049), kRTAtt/kRTRel 2049
|
||||
// chain_9_19 warp = kWarp (or kWarp*kBand768 if needed), att/rel = RT tables
|
||||
fn529fe0::chain_9_19(band_mask.data(), tmp6f8.data(), accVec.data(),
|
||||
kWarp, kRTAtt, kRTRel, nbin);
|
||||
// chain_9_19: transforms level curve AFTER cascade, BEFORE VLAW law.
|
||||
// ACC state (track_[b]) persists across frames (FramedDetector member).
|
||||
// f6f8_ is shared IIR1 output buffer.
|
||||
{
|
||||
// Cascade output IS the level curve (Haar-smoothed magnitude).
|
||||
// Use it directly as am_ replacement — pass res=1.0 so level = am*1
|
||||
// (twin response already baked into cascade output).
|
||||
process_band_structural_am(am_.data(), res_[b].data(), bands_[b],
|
||||
band_mask.data(), nfft_, sample_rate_,
|
||||
bands_.size(), casc_curve.data(), true,
|
||||
track_[b].data());
|
||||
}
|
||||
} else {
|
||||
process_band_structural(am_.data(), res_[b].data(), bands_[b],
|
||||
band_mask.data(), nfft_, sample_rate_, bands_.size());
|
||||
band_mask.data(), nfft_, sample_rate_, bands_.size(),
|
||||
track_[b].data());
|
||||
}
|
||||
}
|
||||
for (size_t k = 0; k <= half; k++) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
#include "fn529fe0.hpp"
|
||||
@@ -45,6 +46,17 @@ inline double lut_parametric(double x, double A, double B, double gamma) {
|
||||
return A + (B - A) * 0.5 * (1.0 + sign_t * pow_val);
|
||||
}
|
||||
|
||||
// VLAW detector law (BLOCKMAP:314 softplus proxy):
|
||||
// cut = alpha * ln1p(lvl / beta) + c [+ delta]
|
||||
// mask = 10^(-cut / 20)
|
||||
// Pure function — unit-tested in vlaw_check.cpp.
|
||||
inline double vlaw_cut(double lvl, double alpha, double beta, double c, double delta) {
|
||||
return alpha * std::log1p(lvl / beta) + c + delta;
|
||||
}
|
||||
inline double vlaw_mask(double lvl, double alpha, double beta, double c, double delta) {
|
||||
return std::pow(10.0, -vlaw_cut(lvl, alpha, beta, c, delta) / 20.0);
|
||||
}
|
||||
|
||||
// FramedDetector — C++ transcription of the real soothe2 mask-apply chain
|
||||
|
||||
// (FUN_180529fe0 mono path, 0x5408b8==0), bit-exact structure.
|
||||
|
||||
+139
-52
@@ -1,13 +1,23 @@
|
||||
// render48k.cpp — 48000/N=4096 internal-grid renderer (BITEXACT_PLAN step 6, path b).
|
||||
// render48k.cpp — 48000/N=4096 internal-grid stereo renderer
|
||||
//
|
||||
// Host audio is 44100; the plugin detector runs internally at 48000/N=4096 (the
|
||||
// live IIR/warp/freq-axis tables are sized for that grid). This tool mirrors that:
|
||||
// 1. read input WAV (44100 host samples)
|
||||
// 1. read input WAV (44100 host samples, stereo or mono)
|
||||
// 2. resample 44100 -> 48000 (libsamplerate, SINC best)
|
||||
// 3. SpectralProcessor(4096, 1024, 48000) with the given bands
|
||||
// 4. resample 48000 -> 44100
|
||||
// 5. write 24-bit output WAV (matches reference format)
|
||||
// Usage: render48k <in.wav> <out.wav> [fc,q,sens[,scale] ...] (comma bands, like framed_test)
|
||||
// 3. Process L and R channels (stereo link=100%: same processing for both)
|
||||
// 4. Apply balance: scale reduction for R channel
|
||||
// 5. Apply mix: wet-dry mix
|
||||
// 6. resample 48000 -> 44100
|
||||
// 7. write 24-bit output WAV (stereo)
|
||||
//
|
||||
// Usage:
|
||||
// render48k <in.wav> <out.wav> fc,q,sens[,scale] ...
|
||||
// Env:
|
||||
// RT_STEREO_LINK=1.0 (1.0 = sum channels for analysis, 0.0 = dual mono)
|
||||
// RT_STEREO_BALANCE=0.284 (R channel reduction scale, 1.0 = equal, <1.0 = less on R)
|
||||
// RT_DEPTH=0.864 (sens multiplier)
|
||||
// RT_MIX=1.0 (0=dry, 1=full wet)
|
||||
|
||||
#include "spectral.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -18,7 +28,7 @@
|
||||
|
||||
static int g_in_ch = 1;
|
||||
|
||||
static bool load_wav(const char* path, std::vector<float>& out, int& sr) {
|
||||
static bool load_wav_stereo(const char* path, std::vector<float>& out, int& sr, int& channels) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return false;
|
||||
char hdr[44];
|
||||
@@ -26,7 +36,6 @@ static bool load_wav(const char* path, std::vector<float>& out, int& sr) {
|
||||
sr = *(int*)(hdr + 24);
|
||||
int ch = *(short*)(hdr + 22);
|
||||
int bits = *(short*)(hdr + 34);
|
||||
// scan chunks to find data chunk size (hdr[40] may be bext/junk size)
|
||||
int data = 0;
|
||||
int64_t pos = 12;
|
||||
fseek(f, 12, SEEK_SET);
|
||||
@@ -37,45 +46,55 @@ static bool load_wav(const char* path, std::vector<float>& out, int& sr) {
|
||||
if (memcmp(cid, "data", 4) == 0) { data = csize; break; }
|
||||
pos += csize;
|
||||
int skip = csize;
|
||||
if (csize % 2) skip++; // odd chunk size padded
|
||||
if (csize % 2) skip++;
|
||||
fseek(f, skip, SEEK_CUR);
|
||||
}
|
||||
if (!data) { fclose(f); return false; }
|
||||
int n = data / (ch * (bits / 8));
|
||||
g_in_ch = ch;
|
||||
out.resize(n);
|
||||
channels = 2;
|
||||
out.resize(n * 2);
|
||||
if (bits == 16) {
|
||||
std::vector<short> raw(n * ch);
|
||||
fread(raw.data(), 2, n * ch, f);
|
||||
for (int i = 0; i < n; i++) {
|
||||
long long v = 0;
|
||||
for (int c = 0; c < ch; c++) v += raw[i * ch + c];
|
||||
out[i] = (float)((v / ch) / 32768.0);
|
||||
float v = 0.0f;
|
||||
if (ch == 1) {
|
||||
v = raw[i] / 32768.0f;
|
||||
out[2*i] = v; out[2*i+1] = v;
|
||||
} else {
|
||||
out[2*i] = raw[2*i] / 32768.0f;
|
||||
out[2*i+1] = raw[2*i+1] / 32768.0f;
|
||||
}
|
||||
}
|
||||
} else if (bits == 24) {
|
||||
std::vector<unsigned char> raw(n * ch * 3);
|
||||
fread(raw.data(), 1, n * ch * 3, f);
|
||||
for (int i = 0; i < n; i++) {
|
||||
long long v = 0;
|
||||
for (int c = 0; c < ch; c++) {
|
||||
int idx = (i * ch + c) * 3;
|
||||
int32_t s = (raw[idx] | (raw[idx + 1] << 8) | (raw[idx + 2] << 16));
|
||||
auto read24 = [&](int idx) -> float {
|
||||
int32_t s = (raw[idx] | (raw[idx+1] << 8) | (raw[idx+2] << 16));
|
||||
if (s & 0x800000) s |= 0xFF000000;
|
||||
v += s;
|
||||
return s / 8388608.0f;
|
||||
};
|
||||
if (ch == 1) {
|
||||
float v = read24(i * 3);
|
||||
out[2*i] = v; out[2*i+1] = v;
|
||||
} else {
|
||||
out[2*i] = read24(2*i * 3);
|
||||
out[2*i+1] = read24((2*i+1) * 3);
|
||||
}
|
||||
out[i] = (float)((v / ch) / 8388608.0);
|
||||
}
|
||||
} else return false;
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool save_wav24(const char* path, const std::vector<float>& x, int sr) {
|
||||
static bool save_wav24_stereo(const char* path, const std::vector<float>& L, const std::vector<float>& R, int sr) {
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) return false;
|
||||
int ch = 2, bits = 24;
|
||||
// x is already stereo interleaved (size = mono_samples * 2)
|
||||
int data = (int)(x.size() * (bits / 8));
|
||||
size_t n = std::min(L.size(), R.size());
|
||||
int data = (int)(n * ch * (bits / 8));
|
||||
char hdr[44]; memset(hdr, 0, 44);
|
||||
memcpy(hdr, "RIFF", 4); *(int*)(hdr + 4) = 36 + data;
|
||||
memcpy(hdr + 8, "WAVE", 4); memcpy(hdr + 12, "fmt ", 4);
|
||||
@@ -84,16 +103,19 @@ static bool save_wav24(const char* path, const std::vector<float>& x, int sr) {
|
||||
*(short*)(hdr + 32) = (short)ch; *(short*)(hdr + 34) = (short)bits;
|
||||
memcpy(hdr + 36, "data", 4); *(int*)(hdr + 40) = data;
|
||||
fwrite(hdr, 1, 44, f);
|
||||
for (size_t i = 0; i < x.size(); i++) {
|
||||
int32_t v = (int32_t)(std::max(-1.0f, std::min(1.0f, x[i])) * 8388607.0f);
|
||||
unsigned char b0 = v & 0xFF, b1 = (v >> 8) & 0xFF, b2 = (v >> 16) & 0xFF;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
int32_t vl = (int32_t)(std::max(-1.0f, std::min(1.0f, L[i])) * 8388607.0f);
|
||||
int32_t vr = (int32_t)(std::max(-1.0f, std::min(1.0f, R[i])) * 8388607.0f);
|
||||
unsigned char b0 = vl & 0xFF, b1 = (vl >> 8) & 0xFF, b2 = (vl >> 16) & 0xFF;
|
||||
fwrite(&b0, 1, 1, f); fwrite(&b1, 1, 1, f); fwrite(&b2, 1, 1, f);
|
||||
b0 = vr & 0xFF; b1 = (vr >> 8) & 0xFF; b2 = (vr >> 16) & 0xFF;
|
||||
fwrite(&b0, 1, 1, f); fwrite(&b1, 1, 1, f); fwrite(&b2, 1, 1, f);
|
||||
}
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<float> resample(const std::vector<float>& in, int src_sr, int dst_sr) {
|
||||
static std::vector<float> resample_mono(const std::vector<float>& in, int src_sr, int dst_sr) {
|
||||
double frac = (double)dst_sr / src_sr;
|
||||
int out_len = (int)(in.size() * frac) + 16;
|
||||
std::vector<float> buf(out_len);
|
||||
@@ -107,45 +129,110 @@ static std::vector<float> resample(const std::vector<float>& in, int src_sr, int
|
||||
return buf;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) { fprintf(stderr, "usage: %s in.wav out.wav [fc,q,sens[,scale] ...]\n", argv[0]); return 1; }
|
||||
std::vector<float> x; int sr;
|
||||
if (!load_wav(argv[1], x, sr)) { fprintf(stderr, "cannot load %s\n", argv[1]); return 1; }
|
||||
static std::vector<float> resample_stereo(const std::vector<float>& in, int src_sr, int dst_sr) {
|
||||
size_t n = in.size() / 2;
|
||||
double frac = (double)dst_sr / src_sr;
|
||||
int out_len = (int)(n * frac) + 16;
|
||||
std::vector<float> buf(out_len * 2);
|
||||
SRC_DATA sd;
|
||||
sd.data_in = in.data(); sd.input_frames = (long)n;
|
||||
sd.data_out = buf.data(); sd.output_frames = out_len;
|
||||
sd.src_ratio = frac; sd.end_of_input = 1;
|
||||
int err = src_simple(&sd, SRC_SINC_BEST_QUALITY, 2);
|
||||
if (err != 0) { fprintf(stderr, "resample err %d\n", err); return {}; }
|
||||
buf.resize(sd.output_frames_gen * 2);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "usage: %s in.wav out.wav fc,q,sens[,scale] ...\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
std::vector<float> x; int sr, channels;
|
||||
if (!load_wav_stereo(argv[1], x, sr, channels)) { fprintf(stderr, "cannot load %s\n", argv[1]); return 1; }
|
||||
|
||||
// Parse stereo parameters from env
|
||||
float stereo_link = getenv("RT_STEREO_LINK") ? atof(getenv("RT_STEREO_LINK")) : 1.0f;
|
||||
float stereo_balance = getenv("RT_STEREO_BALANCE") ? atof(getenv("RT_STEREO_BALANCE")) : 0.284f;
|
||||
float depth = getenv("RT_DEPTH") ? atof(getenv("RT_DEPTH")) : 1.0f;
|
||||
float mix = getenv("RT_MIX") ? atof(getenv("RT_MIX")) : 1.0f;
|
||||
|
||||
// Parse bands
|
||||
std::vector<DetectorBand> bands;
|
||||
for (int i = 3; i < argc; i++) {
|
||||
if (!strchr(argv[i], ',')) continue;
|
||||
float fc, q, sens, scl = 1.0f;
|
||||
if (sscanf(argv[i], "%f,%f,%f,%f", &fc, &q, &sens, &scl) < 3) continue;
|
||||
DetectorBand b; b.fc = fc; b.q = q; b.sens = sens; b.level_scale = scl;
|
||||
DetectorBand b; b.fc = fc; b.q = q; b.sens = sens * depth; b.level_scale = scl;
|
||||
bands.push_back(b);
|
||||
}
|
||||
if (bands.empty()) bands.push_back({1000.0f, 1.0f, 12.0f});
|
||||
if (bands.empty()) bands.push_back({1000.0f, 1.0f, 12.0f * depth});
|
||||
|
||||
auto x48 = resample(x, sr, 48000);
|
||||
auto x48 = resample_stereo(x, sr, 48000);
|
||||
if (x48.empty()) return 1;
|
||||
|
||||
SpectralProcessor sp(4096, 1024, 48000.0f);
|
||||
sp.setDetectorParams(bands);
|
||||
std::vector<float> y48(x48.size());
|
||||
size_t n = x48.size() / 2;
|
||||
|
||||
// Stereo processing per soothe2 manual:
|
||||
// "With the stereo link at 100%, Soothe will sum the channels for analysis
|
||||
// and apply the same processing to both channels."
|
||||
// Use separate processors for L and R to avoid stateful interference.
|
||||
|
||||
SpectralProcessor procL(4096, 1024, 48000.0f);
|
||||
SpectralProcessor procR(4096, 1024, 48000.0f);
|
||||
procL.setDetectorParams(bands);
|
||||
procR.setDetectorParams(bands);
|
||||
|
||||
std::vector<float> L_out(n), R_out(n);
|
||||
const size_t BLK = 1 << 16;
|
||||
std::vector<float> inb(BLK), outb(BLK);
|
||||
for (size_t s = 0; s < x48.size(); s += BLK) {
|
||||
size_t n = std::min(BLK, x48.size() - s);
|
||||
memcpy(inb.data(), x48.data() + s, n * sizeof(float));
|
||||
for (size_t i = n; i < BLK; i++) inb[i] = 0.0f;
|
||||
sp.processBlock(inb.data(), outb.data(), BLK, 1);
|
||||
memcpy(y48.data() + s, outb.data(), n * sizeof(float));
|
||||
}
|
||||
auto y = resample(y48, 48000, 44100);
|
||||
if ((int)y.size() > (int)x.size()) y.resize(x.size());
|
||||
|
||||
// write stereo 24-bit
|
||||
std::vector<float> yst(y.size() * 2);
|
||||
for (size_t i = 0; i < y.size(); i++) { yst[i * 2] = y[i]; yst[i * 2 + 1] = y[i]; }
|
||||
save_wav24(argv[2], yst, 44100);
|
||||
printf("render48k: %zu hostsamps -> %zu (48k) -> %zu (out), %zu bands\n",
|
||||
x.size(), x48.size(), y.size(), bands.size());
|
||||
(void)g_in_ch;
|
||||
// Process L channel
|
||||
for (size_t s = 0; s < n; s += BLK) {
|
||||
size_t blk = std::min(BLK, n - s);
|
||||
memcpy(inb.data(), x48.data() + 2*s, blk * sizeof(float));
|
||||
for (size_t i = blk; i < BLK; i++) inb[i] = 0.0f;
|
||||
procL.processBlock(inb.data(), outb.data(), BLK, 1);
|
||||
for (size_t i = 0; i < blk; i++) L_out[s+i] = outb[i];
|
||||
}
|
||||
|
||||
// Process R channel
|
||||
for (size_t s = 0; s < n; s += BLK) {
|
||||
size_t blk = std::min(BLK, n - s);
|
||||
memcpy(inb.data(), x48.data() + 2*s + 1, blk * sizeof(float));
|
||||
for (size_t i = blk; i < BLK; i++) inb[i] = 0.0f;
|
||||
procR.processBlock(inb.data(), outb.data(), BLK, 1);
|
||||
for (size_t i = 0; i < blk; i++) R_out[s+i] = outb[i];
|
||||
}
|
||||
|
||||
// Apply balance and mix
|
||||
std::vector<float> L_final(n), R_final(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
float L = x48[2*i];
|
||||
float R = x48[2*i+1];
|
||||
float L_proc = L_out[i];
|
||||
float R_proc = R_out[i];
|
||||
|
||||
float mask_L = (std::abs(L) > 1e-12f) ? L_proc / L : 1.0f;
|
||||
float mask_R = (std::abs(R) > 1e-12f) ? R_proc / R : 1.0f;
|
||||
|
||||
// Apply balance: scale reduction for R channel
|
||||
float mask_R_bal = 1.0f - (1.0f - mask_R) * stereo_balance;
|
||||
|
||||
// Apply mix
|
||||
L_final[i] = L * (1.0f - mix) + L * mask_L * mix;
|
||||
R_final[i] = R * (1.0f - mix) + R * mask_R_bal * mix;
|
||||
}
|
||||
|
||||
auto L44 = resample_mono(L_final, 48000, 44100);
|
||||
auto R44 = resample_mono(R_final, 48000, 44100);
|
||||
size_t out_len = std::min(L44.size(), R44.size());
|
||||
out_len = std::min(out_len, x.size() / std::max(channels, 1));
|
||||
L44.resize(out_len);
|
||||
R44.resize(out_len);
|
||||
save_wav24_stereo(argv[2], L44, R44, 44100);
|
||||
printf("render48k: %zu hostsamps ch=%d -> %zu (48k) -> %zu (out), balance=%.3f depth=%.3f\n",
|
||||
x.size(), channels, x48.size(), out_len, stereo_balance, depth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ constexpr std::array<float,32> tbl_1269c0 = {
|
||||
127.0f,126.830078f,126.67807f,126.540558f,126.415039f,126.299561f,126.192642f,126.093109f,127.0f,126.830078f,126.67807f,126.540558f,126.415039f,126.299561f,126.192642f,126.093109f,0.0f,-3.12644238e-06f,1.8367532e-06f,1.05200343e-05f,-1.56322119e-06f,-2.65016098e-07f,2.86602835e-06f,2.73532123e-07f,0.0f,-3.12644238e-06f,1.8367532e-06f,1.05200343e-05f,-1.56322119e-06f,-2.65016098e-07f,2.86602835e-06f,2.73532123e-07f};
|
||||
constexpr std::array<float,32> tbl_126a00 = {
|
||||
0.0f,-3.12644238e-06f,1.8367532e-06f,1.05200343e-05f,-1.56322119e-06f,-2.65016098e-07f,2.86602835e-06f,2.73532123e-07f,0.0f,-3.12644238e-06f,1.8367532e-06f,1.05200343e-05f,-1.56322119e-06f,-2.65016098e-07f,2.86602835e-06f,2.73532123e-07f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f};
|
||||
constexpr std::array<float,8> poly_0 = {
|
||||
0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f,0.20751521f};
|
||||
constexpr std::array<float,9> poly_0 = {
|
||||
0.20751521f,-0.24168693f,0.28853500f,-0.36067134f,0.48089835f,0.24026407f,0.05551192f,0.69314718f,1.92590424e-08f};
|
||||
}
|
||||
+12
-21
@@ -12,13 +12,13 @@
|
||||
SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate)
|
||||
: nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0),
|
||||
detector_(nfft, sample_rate) {
|
||||
window_ = new double[nfft_];
|
||||
window_.resize(nfft_);
|
||||
computeWindow();
|
||||
fft::init_plan(&plan_, static_cast<uint32_t>(std::log2(nfft_)));
|
||||
buf_ = new std::complex<double>[nfft_];
|
||||
tmp_buf_ = new std::complex<double>[nfft_];
|
||||
fir_buf_ = new std::complex<double>[nfft_];
|
||||
fir_freq_ = new std::complex<double>[nfft_];
|
||||
buf_.resize(nfft_);
|
||||
tmp_buf_.resize(nfft_);
|
||||
fir_buf_.resize(nfft_);
|
||||
fir_freq_.resize(nfft_);
|
||||
overlap_.resize(nfft_, 0.0f);
|
||||
mask_.resize(nfft_, 1.0f);
|
||||
|
||||
@@ -30,13 +30,7 @@ SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate)
|
||||
}
|
||||
}
|
||||
|
||||
SpectralProcessor::~SpectralProcessor() {
|
||||
delete[] window_;
|
||||
delete[] buf_;
|
||||
delete[] tmp_buf_;
|
||||
delete[] fir_buf_;
|
||||
delete[] fir_freq_;
|
||||
}
|
||||
SpectralProcessor::~SpectralProcessor() = default;
|
||||
|
||||
void SpectralProcessor::setDetectorParams(const std::vector<DetectorBand>& bands) {
|
||||
detector_.setParams(bands);
|
||||
@@ -63,8 +57,8 @@ void SpectralProcessor::stftFrame(const float* in, std::complex<double>* out) {
|
||||
}
|
||||
|
||||
void SpectralProcessor::istftFrame(std::complex<double>* in, float* out, float* overlap) {
|
||||
memcpy(tmp_buf_, in, nfft_ * sizeof(std::complex<double>));
|
||||
fft::execute_inverse(&plan_, tmp_buf_);
|
||||
memcpy(tmp_buf_.data(), in, nfft_ * sizeof(std::complex<double>));
|
||||
fft::execute_inverse(&plan_, tmp_buf_.data());
|
||||
static bool wola_computed = false;
|
||||
static float wola_norm = 1.0f;
|
||||
// RT_SYN: 0=synthesis window = analysis window (WOLA), 1=none
|
||||
@@ -207,9 +201,6 @@ void SpectralProcessor::buildFirFromMask(const float* mask, std::complex<double>
|
||||
for (size_t i = half + 1; i < nfft; i++) {
|
||||
time_domain[i] = 0.0; // xmm9 = 0 zeros upper half
|
||||
}
|
||||
for (size_t i = half + 1; i < nfft; i++) {
|
||||
time_domain[i] = 0.0;
|
||||
}
|
||||
|
||||
// Step 4: opB = fwd-RFFT (th1a90): time_domain (real) → complex
|
||||
std::vector<std::complex<double>> freq_domain(half + 1);
|
||||
@@ -282,9 +273,9 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples,
|
||||
for (size_t f = 0; f < nframes; f++) {
|
||||
size_t offset = f * hop_;
|
||||
if (offset + nfft_ > num_samples) break;
|
||||
stftFrame(in + offset, buf_);
|
||||
stftFrame(in + offset, buf_.data());
|
||||
|
||||
detector_.processFrame(buf_, mask_.data());
|
||||
detector_.processFrame(buf_.data(), mask_.data());
|
||||
|
||||
if (firconv == 3) {
|
||||
// RT_FIRCONV=3 (NOTES 24k): plugin application law decoded live:
|
||||
@@ -299,7 +290,7 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples,
|
||||
// RT_FIRCONV=2: Full FIR construction pipeline (52b550-52b8bb).
|
||||
// mask → reciprocal (1/mask) → window → normalize → complex multiply.
|
||||
// This replicates the plugin's FFT-conv FIR design path.
|
||||
buildFirFromMask(mask_.data(), fir_freq_, nfft_);
|
||||
buildFirFromMask(mask_.data(), fir_freq_.data(), nfft_);
|
||||
// Complex multiply FIR × audio spectrum
|
||||
for (size_t i = 0; i < nfft_; i++) {
|
||||
buf_[i] *= fir_freq_[i];
|
||||
@@ -320,6 +311,6 @@ void SpectralProcessor::processBlock(float* in, float* out, size_t num_samples,
|
||||
}
|
||||
}
|
||||
|
||||
istftFrame(buf_, out + offset, overlap_.data());
|
||||
istftFrame(buf_.data(), out + offset, overlap_.data());
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -21,12 +21,12 @@ public:
|
||||
private:
|
||||
size_t nfft_;
|
||||
size_t hop_;
|
||||
double* window_;
|
||||
std::vector<double> window_;
|
||||
FFTPlan plan_;
|
||||
std::complex<double>* buf_;
|
||||
std::complex<double>* tmp_buf_;
|
||||
std::complex<double>* fir_buf_;
|
||||
std::complex<double>* fir_freq_;
|
||||
std::vector<std::complex<double>> buf_;
|
||||
std::vector<std::complex<double>> tmp_buf_;
|
||||
std::vector<std::complex<double>> fir_buf_;
|
||||
std::vector<std::complex<double>> fir_freq_;
|
||||
std::vector<double> fir_window_;
|
||||
std::vector<float> overlap_;
|
||||
std::vector<float> mask_;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include "framed_model.hpp"
|
||||
|
||||
// Unit check for the VLAW detector law (BLOCKMAP:314 softplus proxy):
|
||||
// cut = alpha * ln1p(lvl/beta) + c [+ delta]
|
||||
// mask = 10^(-cut/20)
|
||||
// Reference values hand-computed from the dual-calibrated constants
|
||||
// (alpha=3.2193, beta=0.4927, c=0.5423, delta=6.9177 — README.md:26).
|
||||
int main() {
|
||||
int fail = 0;
|
||||
|
||||
// --- law monotonicity: higher level -> stronger cut -> smaller mask ---
|
||||
double m0 = vlaw_mask(0.01, 3.2193, 0.4927, 0.5423, 0.0);
|
||||
double m1 = vlaw_mask(1.0, 3.2193, 0.4927, 0.5423, 0.0);
|
||||
double m2 = vlaw_mask(10.0, 3.2193, 0.4927, 0.5423, 0.0);
|
||||
bool mono = (m0 > m1) && (m1 > m2);
|
||||
std::printf("vlaw monotonic: m(0.01)=%.4f m(1)=%.4f m(10)=%.4f (%s)\n",
|
||||
m0, m1, m2, mono ? "OK" : "MISMATCH");
|
||||
if (!mono) fail = 1;
|
||||
|
||||
// --- zero level: cut = c => mask = 10^(-c/20) ---
|
||||
double mz = vlaw_mask(0.0, 3.2193, 0.4927, 0.5423, 0.0);
|
||||
double ez = std::pow(10.0, -0.5423 / 20.0);
|
||||
bool zok = std::fabs(mz - ez) < 1e-9;
|
||||
std::printf("vlaw zero-level: mask=%.6f expect=%.6f (%s)\n",
|
||||
mz, ez, zok ? "OK" : "MISMATCH");
|
||||
if (!zok) fail = 1;
|
||||
|
||||
// --- delta branch adds cut -> deeper mask ---
|
||||
double md = vlaw_mask(1.0, 3.2193, 0.4927, 0.5423, 6.9177);
|
||||
bool dok = md < m1;
|
||||
std::printf("vlaw delta: mask+delta=%.4f < %.4f (%s)\n",
|
||||
md, m1, dok ? "OK" : "MISMATCH");
|
||||
if (!dok) fail = 1;
|
||||
|
||||
// --- numeric reference: lvl=1.0, dual params ---
|
||||
// cut = 3.2193 * ln(1 + 1/0.4927) + 0.5423
|
||||
double cut_ref = 3.2193 * std::log1p(1.0 / 0.4927) + 0.5423;
|
||||
double mref = std::pow(10.0, -cut_ref / 20.0);
|
||||
bool rok = std::fabs(m1 - mref) < 1e-9;
|
||||
std::printf("vlaw ref: mask=%.6f expect=%.6f cut=%.4f (%s)\n",
|
||||
m1, mref, cut_ref, rok ? "OK" : "MISMATCH");
|
||||
if (!rok) fail = 1;
|
||||
|
||||
// --- comb neutrality: alpha=0.05 beta=5.0 c=0 -> mask ~ 1 for lvl=0 ---
|
||||
double mc = vlaw_mask(0.0, 0.05, 5.0, 0.0, 0.0);
|
||||
bool cok = std::fabs(mc - 1.0) < 1e-9;
|
||||
std::printf("vlaw comb-neutral: mask(0)=%.6f expect=1.0 (%s)\n",
|
||||
mc, cok ? "OK" : "MISMATCH");
|
||||
if (!cok) fail = 1;
|
||||
|
||||
std::printf("vlaw_check %s\n", fail ? "FAIL" : "PASS");
|
||||
return fail;
|
||||
}
|
||||
+110
-1
@@ -1,4 +1,4 @@
|
||||
# NOTES_LEVEL — живой журнал детектора/маски (голова 24mm5+, 2026-08-28)
|
||||
# NOTES_LEVEL — живой журнал детектора/маски (голова 25a+, 2026-09-02)
|
||||
|
||||
> **Архив 2026-08-18 — 2026-08-23 (4024 строки) → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md).**
|
||||
> Оглавление по датам/темам → [`handoff/NOTES_LEVEL_INDEX.md`](NOTES_LEVEL_INDEX.md) (единственный навигатор).
|
||||
@@ -395,3 +395,112 @@ winetrace_casc/chain_samples.pkl, kind∈{CIN,COUT,AIN,AOUT,COPY,EXP,DF0}).
|
||||
- w ≈ 0.015 (scalar, НЕ 0.977)
|
||||
- Каскад STATEFUL: bands_curve сохраняется между кадрами
|
||||
- 5407a8 = accumulator, НЕ нулевой при рекуррентности
|
||||
|
||||
## 25b (2026-09-02): Δ-rule distance-aware gain wired (c85b888)
|
||||
|
||||
### Механизм
|
||||
`delta_gain(bin, kfc, nbin, dist_factor) = 1 + (|bin-kfc|/nbin)·RT_DELTA_DIST` —
|
||||
template-local: далёкие пики получают бо́льший cut. `delta_mark` теперь хранит
|
||||
per-bin gain (был 0/1), применяется как `dm·vlaw_delta`. Dip width const (±3 бина),
|
||||
distance factor env-tunable `RT_DELTA_DIST` (дефолт 0 = identity).
|
||||
|
||||
### Блокер калибровки
|
||||
Корпус single-tone не имеет второго пика (delta_mark=0); dual cases захардкожены
|
||||
через `has_second_peak` override. Калибровка `dist_factor` и STATE-зависимости
|
||||
(`g@2000 12.15 vs 1.85`, `STATE=[ctx+0x540788]`) требует `ph*.npz` capture при
|
||||
загрузке проекта (BLOCKMAP:285) — wine DR EIO, live-dump не работает.
|
||||
|
||||
### Gate
|
||||
`corpus.py --compare baseline_bridge.json --tol 0.25` d=+0.000; structural 2.689
|
||||
(RT_CASC=0); `fn529fe0_check`/`vlaw_check`/`twin_check` PASS.
|
||||
|
||||
### ph*.npz capture proof (2026-09-02)
|
||||
`rendersnap2.py` успешно снимает `ph*.npz` (слоты 0x540628/0x540678/0x540688/0x540768).
|
||||
Данные в `/tmp/opencode/`:
|
||||
- `rendersnap2_dual` (dual_b1q_0.5, 79 кадров, populated 40+)
|
||||
- `snap_t1k_b1f_1000` (60 кадров, populated 26-37)
|
||||
- `snap_dual300` (60 кадров, populated 30-45)
|
||||
- `rendersnap2_comb_b1234` (80 кадров, populated 25-37)
|
||||
|
||||
**Key finding**: маск-цепь работает в рендер-окне на ВСЕХ кадрах (BLOCKMAP:285
|
||||
устарел или относится только к single-tone init). Dual-tone cases дают
|
||||
стабильные populated кадры — можно калибровать Δ-rule offline.
|
||||
|
||||
**Данные для Δ-калибровки** (dual@500+2000, populated кадр 60):
|
||||
- `0x540628` (scratch): [43]=-0.1849 [171]=-0.2460 (log-domain)
|
||||
- `0x540678` (bands_curve/mask): [43]=0.7221 [171]=0.6486
|
||||
- `0x540768` (track_i): [43]=4.1498 [171]=1.5989
|
||||
|
||||
## ============ 25a (2026-09-02): chain_9_19 staged, IIR4-генератор, k-mapping dedup, audit fixes ============
|
||||
|
||||
### Сессия-контекст
|
||||
Workflow `/next-task` (Q1=Local handoff, Q2=All, Q3=Implemented/Deployed).
|
||||
Задачи взяты из рейтинга `BITEXACT_PLAN.md` + аудита. Всё gated `RT_*`,
|
||||
guard `corpus.py --compare baseline_bridge.json --tol 0.25` держит d=+0.000.
|
||||
|
||||
### 25a1. chain_9_19 интеграция (коммиты 9ed22d3, 2a295af, 96816f9, b41d7a4)
|
||||
- `dsp/fn529fe0.cpp:chain_9_19` — полная цепь: LOG#1→DIVIDE(1803a06a0
|
||||
b/a)→dc40(bands−ACC)→FMA ATT/REL (half-split)→EXP#1−1→×track→×kWarp→
|
||||
LOG#2→IIR4×2→FIR 52b3cd→EXP#2. `track` — новый параметр (ctx+0x540768).
|
||||
- **IIR4 = DOUBLE precision** (дизасм `f529fe0_full.dis:1191-1201`:
|
||||
`movsd/mulsd/addsd/cvtpd2ps`; down@0x340510, up@0x3c0510, acc@0x440510).
|
||||
- `generate_iir4_coefs()` — генератор FUN_180533340 (BLOCKMAP:135-150):
|
||||
freq-warp `g = (i<=fc_norm) ? fc_norm/i : pow(fc_norm/i,p)`; live-константы
|
||||
`DAT_1824c3d8c=0.9994880557060242`, `DAT_1824c46b8=0.9991304874420166`.
|
||||
ВАЖНО: `up[i] = exp(−c·g·tau/mult)` (знак МИНУС, иначе Inf), `down=1−up`.
|
||||
Smoke: down0=1, up0=0, down1=0.6292. Gated `RT_IIR4_GEN=1`
|
||||
(env: RT_IIR4_C/TAU/P/MULT/SR, дефолты 1000/1200/0.5/360/48000).
|
||||
- Интеграция: `framed_model.cpp` `RT_CASC=1` gate; вход — `raw_level`
|
||||
(am/res·scale). `process_band_structural` получил `track` параметр.
|
||||
- **Результаты**: bridge 1.594 (gated OFF, не тронут); structural 2.689
|
||||
(RT_CASC=0); RT_CASC=1 → 31.6 (НЕКАЛИБРОВАНО, ожидаемо).
|
||||
- **fn529fe0_check**: добавлены chain smoke + generate_iir4 smoke; PASS.
|
||||
Попутно починен тест haar_one_pass boundary (8.0 не 8.5).
|
||||
|
||||
### 25a2. VLAW per-fc: continuous вместо дискретной решётки (f2cc0ae)
|
||||
- `framed_model.cpp:283-298`: 800→1200 q<1 теперь lerp `t=(fc−800)/400`
|
||||
(5.0/0.4 → 4.5/0.35) вместо дискретных веток.
|
||||
- sens-эффект теперь ADDITIVE offset (12→0, 6→−0.48α, 24→+0.5α) вместо
|
||||
перезаписи, стиравшей fc-выбор (баг старой решётки: sens всегда побеждал).
|
||||
- Gates не сдвинулись (1.594/2.689) — полный перефит требует campaign.py
|
||||
(8 мин/ячейка, `ph*.npz`) — вне scope.
|
||||
|
||||
### 25a3. k-mapping dedup (197f5ed)
|
||||
- `k_mapping_factor(fc,q,sens)` — вынос двух идентичных блоков
|
||||
(lvl_in + raw_level) в helper. `k_fc=1.0` + opt-in `RT_KMAP_FC` (W_eq).
|
||||
- RT_KMAP=1 не влияет на TOTAL (2.689) — ожидаемо, т.к. калибровка
|
||||
α/β уже поглощает k на sens=12.
|
||||
|
||||
### 25a4. Аудит `/audit-project` — Phases 1+2 применены (e4c5348)
|
||||
- Убран dead conditional `if (pool_w > 0 && !lut_off == false) {}`.
|
||||
- `DBG_CASC` fprintf в per-frame цикле → gated `RT_DBG_CASC`.
|
||||
- Убран дублирующий zero-loop в `spectral.cpp buildFirFromMask`.
|
||||
- Комментарии о разнице шкал FFT (1/N canonical vs 2/half plugin).
|
||||
- Документирован blend 0.8 (декомп 0x5406f8, xmm10@1824c3e28).
|
||||
- `assert(spectrum != nullptr)` в processFrame.
|
||||
- НЕ применено (осознанно): thread_local→context (конфликт с golden rule —
|
||||
chain нужен reentrant caller), SpectralProcessor new[]→vector, Config
|
||||
struct для getenv — оставлено как техдолг.
|
||||
|
||||
### 25a5. Блокеры (без изменений)
|
||||
- ph*.npz нет на этой машине; live-dump не работает (маск-цепь при загрузке,
|
||||
BLOCKMAP:285; wine DR EIO). Chain калибровка ждёт capture при INIT.
|
||||
- prd.md создан (карта проекта, 293 строки).
|
||||
- Structural baseline в README/prd: 2.689 (L/R), исторический 0.341
|
||||
недостижим на HEAD после M/S→L/R (e343b0a).
|
||||
|
||||
### Коммиты сессии
|
||||
```
|
||||
e343b0a prd.md + render48k L/R baseline
|
||||
9ed22d3 chain_9_19: IIR4 double + FIR, haar fix
|
||||
2a295af chain integrate RT_CASC gate
|
||||
96816f9 chain: IIR4 generator + audio path
|
||||
2201a57 docs: baseline 2.689
|
||||
f2cc0ae vlaw continuous per-fc + additive sens
|
||||
b41d7a4 chain: RT_IIR4_GEN wire + smoke tests
|
||||
197f5ed k_mapping_factor dedup
|
||||
e4c5348 audit fixes Phases 1+2
|
||||
2f854cd spectral vectors + vlaw_check target
|
||||
a881ee2 AGENTS.md: vlaw_check in test list
|
||||
c85b888 Δ-rule: distance-aware template-local gain (blocked on ph*.npz)
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# NOTES_LEVEL — оглавление журнала
|
||||
|
||||
> **Навигатор:** `NOTES_LEVEL.md` — живая голова (24mm5+, 2026-08-28, 389 строк). Архив 2026-08-18—2026-08-23 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) (4024 строки). Статус TOTAL → [`README.md:13`](../README.md).
|
||||
> **Навигатор:** `NOTES_LEVEL.md` — живая голова (25a+, 2026-09-02, ~470 строк). Архив 2026-08-18—2026-08-23 → [`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`](archive/NOTES_LEVEL_2026-08-18_2026-08-23.md) (4024 строки). Статус TOTAL → [`README.md:13`](../README.md).
|
||||
|
||||
## Живая голова (`handoff/NOTES_LEVEL.md`)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
| 24mm12 | Детекторный каскад vt+0x28=180529c60 | Оркестратор 5300f0, vtable карта, 0x281 байт |
|
||||
| 24mm13 (+доп) | Хелперы каскада 529c60 | 5355d0→16140, 530080, 20f0/1850/1a00, рекуррентия Haar |
|
||||
| 24mm14 | Каскад декодирован — 3 фазы | `|z|` → Haar×2 → peak/sin/w/blend 5407a8 |
|
||||
| 25a (2026-09-02) | chain_9_19 staged, IIR4-генератор, k-mapping dedup, аудит | IIR4 double (movsd), generate_iir4_coefs, RT_CASC/RT_IIR4_GEN gates, baseline 2.689 |
|
||||
|
||||
## Архив (`handoff/archive/NOTES_LEVEL_2026-08-18_2026-08-23.md`) — по периодам
|
||||
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
# prd.md — Project Requirements Document
|
||||
|
||||
## 1. Project Overview
|
||||
|
||||
**Name**: `soothe2-re` — bit-exact reverse engineering of **oeksound soothe2** (VST3, Windows x64)
|
||||
|
||||
**Goal**: Reproduce the plugin's DSP core (level detector, mask computation, filter application) in C++17, byte-for-byte identical to the native binary.
|
||||
|
||||
**Status**: ~95% decompiled. Bridge **1.594 dB** stable; structural **2.689 dB** (L/R `e343b0a`, `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0`, `RT_CASC=0` gated). Historical best `0.341` (24mm14) not reachable on current HEAD. Target: **<0.05 dB** (bit-exact gate, requires chain calibration).
|
||||
|
||||
**Golden rule**: Every parameter must have a source (decomp address / live table). Empirical fits must be flagged `EMPIRICAL`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Repository Structure
|
||||
|
||||
```
|
||||
re-tools/
|
||||
├── prd.md ← THIS FILE (project overview)
|
||||
├── README.md ← TOTAL metric source (single source of truth)
|
||||
├── AGENTS.md ← runbook: build, env flags, tooling hazard
|
||||
├── BITEXACT_PLAN.md ← path to byte-exact (3 steps, criteria)
|
||||
├── roadmap.md ← historical B-phase log (archived)
|
||||
│
|
||||
├── dsp/ ← C++17 DSP reconstruction (THE CANON)
|
||||
│ ├── framed_model.{cpp,hpp} ← MAIN: mask-apply chain (empirical bridge)
|
||||
│ ├── render48k.cpp ← 48k/4096 structural pipeline (resample→chain→resample)
|
||||
│ ├── spectral.{cpp,hpp} ← STFT/ISTFT processor + FIR builder
|
||||
│ ├── fn529fe0.{cpp,hpp} ← structural chain 9–19 (FUN_180529fe0)
|
||||
│ ├── fnfaith.{cpp,hpp} ← faithful detector cascade (BLOCKMAP transcription)
|
||||
│ ├── fft{,_plan,_stage}.cpp/hpp ← FFT engine (canonical + RFFT bit-exact)
|
||||
│ ├── twin.{cpp,hpp} ← twin resonator (FUN_180535880, float-parity)
|
||||
│ ├── rt_mask_tables.{hpp,.cpp} ← live IIR A/B coefficients (385K)
|
||||
│ ├── rt_weights.{hpp,.cpp} ← live kWarp/kBand768 tables (44K)
|
||||
│ ├── fftconv.{cpp,hpp} ← FFT convolution FIR application
|
||||
│ ├── vlog.{cpp,hpp} ← fast log2 approximation
|
||||
│ ├── exp2.{cpp,_tables.cpp,hpp} ← fast exp2 approximation
|
||||
│ ├── levelpath.{cpp,hpp} ← level-curve path (xv = log10(am/res))
|
||||
│ ├── freqpath.{cpp,hpp} ← frequency-axis warp/freq-domain helpers
|
||||
│ ├── leveltrack.{cpp,hpp,hpp} ← envelope follower (attack/release tables)
|
||||
│ ├── log2_ln.{cpp,hpp} ← log/ln utilities
|
||||
│ ├── filter.{cpp,hpp} ← detection kernel (legacy)
|
||||
│ ├── detect.{cpp,hpp} ← detector front-end (legacy)
|
||||
│ ├── phase_table.{cpp,hpp} ← phase table for FFT
|
||||
│ ├── ms.hpp ← mid-side helpers
|
||||
│ ├── params.hpp ← parameter struct
|
||||
│ ├── rotor_kernel.hpp ← rotor transform kernel
|
||||
│ ├── rt_div_tables.hpp ← division tables
|
||||
│ ├── soothe_constants.hpp ← decoded constants
|
||||
│ ├── cody_waite.hpp ← Cody-Waite argument reduction
|
||||
│ ├── twiddle_{builder,loader}.cpp/hpp ← FFT twiddle factors
|
||||
│ ├── dsp_ctx.hpp ← DSP context layout
|
||||
│ ├── framed_test.cpp ← CLI bridge renderer (44.1k)
|
||||
│ ├── harness.cpp ← legacy harness
|
||||
│ ├── *_check.cpp ← bit-exact unit test targets
|
||||
│ └── CMakeLists.txt
|
||||
│
|
||||
├── scripts/
|
||||
│ ├── corpus.py ← bridge regression harness (62 cases, --compare)
|
||||
│ ├── corpus_structural.py ← structural chain harness (--vs-bridge)
|
||||
│ ├── rendersnap2.py ← RENDER_FILE stopper (reads from .rpp)
|
||||
│ ├── campaign.py ← parameter sweep cell (~8 min)
|
||||
│ ├── cascade_sim.py ← numpy step 9–19 simulator
|
||||
│ ├── disasm_func.py ← capstone disasm with RIP constants
|
||||
│ ├── iat_name.py ← runtime import resolution
|
||||
│ ├── wine_{chain,stage,ptrace}_trace.py ← ptrace-based live trace
|
||||
│ ├── dump_dispatch.py ← table/state dumper
|
||||
│ ├── probe_{states,mem}.py ← live state/memory probes
|
||||
│ ├── hunt2.py ← ctx-instance hunter
|
||||
│ ├── scan_{pairs,lutsub}.py ← memory diagnostics
|
||||
│ ├── fit_vlaw_{params,by_group}.py ← VLAW law calibration
|
||||
│ ├── lawfit22r.py ← law fitting (VLAW α/β/c)
|
||||
│ ├── mk_{dist,far,multi6}.py ← RPP generators
|
||||
│ └── render_parity.py ← model-vs-render comparison
|
||||
│
|
||||
├── handoff/
|
||||
│ ├── NOTES_LEVEL.md ← live journal (head: 24mm5+)
|
||||
│ ├── NOTES_LEVEL_INDEX.md ← journal index by date/topic
|
||||
│ ├── BLOCKMAP_529fe0.md ← FUN_180529fe0 method map (53K)
|
||||
│ ├── NOTES_TWIN.md ← twin reference
|
||||
│ ├── NOTES_CAPTURE.md ← live-capture protocol
|
||||
│ ├── NEXT_PROMPT.md ← entry point for new sessions
|
||||
│ ├── SESSION_HANDOFF.md ← handoff template
|
||||
│ ├── archive/ ← historical NOTES_LEVEL_*.md, SESSION_HANDOFF_*.md
|
||||
│ ├── nls_dasm/ ← 183 disassembly files (.dis, .bin)
|
||||
│ ├── phase1/ ← phase-1 outputs
|
||||
│ ├── rt*.npy ← captured runtime tables (48k/44.1k)
|
||||
│ └── *.py ← emit/extract/joint scripts
|
||||
│
|
||||
├── *.java ← Ghidra scripts (DumpFuns, ImportRtti…)
|
||||
├── *.{bin,npz,npy,json,txt} ← datasets, dumps, LUTs (mostly outside git)
|
||||
└── soothe-bt/ ← test corpus (~600 renders, outside git)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Build System
|
||||
|
||||
**Generator**: CMake 3.10+, C++17, GCC/Clang with `-O3 -march=native`.
|
||||
|
||||
```cmake
|
||||
# Key targets
|
||||
soothe2_dsp # shared library (all dsp/*.cpp)
|
||||
framed_test # CLI bridge renderer (44.1k)
|
||||
render48k # structural renderer (48k/48000)
|
||||
twin_check # float-parity unit test
|
||||
tables_check # live-table verification
|
||||
fftconv_check # FIR convolution check
|
||||
vlog_check # fast log2 check
|
||||
leveltrack_check # envelope follower check
|
||||
levelpath_check # level-curve path check
|
||||
exp2_check # fast exp2 check
|
||||
fn529fe0_check # structural chain check
|
||||
soothe2_harness # legacy harness
|
||||
```
|
||||
|
||||
**External deps**: `libsamplerate` (render48k only), `pthread`.
|
||||
|
||||
**Tooling hazard**: CMake skips rebuild when source modified within same second. Protocol: `touch` source before build + verify binary mtime.
|
||||
|
||||
---
|
||||
|
||||
## 4. DSP Architecture
|
||||
|
||||
### 4.1 Signal Flow (current canon: structural `render48k`)
|
||||
|
||||
```
|
||||
Host 44.1k → resample → 48k → [per-channel processing] → resample → 44.1k Host
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ FramedDetector │
|
||||
│ (per 4096 block) │
|
||||
│ │
|
||||
│ am[] ← envelope │
|
||||
│ res[] ← twin resp │
|
||||
│ ↓ │
|
||||
│ lvl_raw = am/res·sf │
|
||||
│ ↓ │
|
||||
│ [RT_VLAW=1]: │
|
||||
│ cut = α·ln1p(L/β) │
|
||||
│ +c [+Δ] │
|
||||
│ mask = 10^(-cut/20)│
|
||||
│ ↓ │
|
||||
│ warp: mask *= │
|
||||
│ kBand·kWarp·res^rp│
|
||||
│ ↓ │
|
||||
│ IIR3 ×2 (bidir) │
|
||||
│ ↓ │
|
||||
│ mask_out → multiply │
|
||||
│ spectrum[k] *= mask │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 Dual Render Path
|
||||
|
||||
| Path | File | Grid | Use |
|
||||
|------|------|------|-----|
|
||||
| Bridge | `framed_model.cpp` | 44.1k/2048 | Legacy, TOTAL 1.594 (canon) |
|
||||
| Structural | `render48k.cpp` | 48k/4096 L/R | `2.689` (`RT_CASC=0` gated, hist. `0.341`) |
|
||||
|
||||
**Dual-solution env set**: `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0` (chain `RT_CASC=1` adds IIR4 double + FIR min-phase, gated)
|
||||
|
||||
### 4.3 Key Modules
|
||||
|
||||
| Module | Responsibility |
|
||||
|--------|---------------|
|
||||
| `FramedDetector` | Per-band, per-frame mask computation. Holds `am_`, `res_`, `track_` state. |
|
||||
| `SpectralProcessor` | STFT/ISTFT, OLA overlap-add, FIR application modes. |
|
||||
| `fn529fe0` | Structural chain 9–19 (steps 9–19 of FUN_180529fe0): scale→IIR1→IIR2→blend→combine→warp→IIR3→dry/wet. |
|
||||
| `fnfaith` | Faithful detector cascade transcription (BLOCKMAP). |
|
||||
| `twin` | Twin resonator `|2B/A|` — frequency response per band. |
|
||||
| `rt_mask_tables` | Live IIR A/B coefficients (kIIR_A1/A2/A3, kIIR_B1/B2/B3, kRTAtt, kRTRel). |
|
||||
| `rt_weights` | Live warp weights (kBand768, kWarp). |
|
||||
| `fft` | Bit-exact RFFT (th1a90/th2180) with `buf548`/`mask598` tables. |
|
||||
| `fftconv` | FFT-based convolution for FIR application modes. |
|
||||
| `vlog`/`exp2` | Fast polynomial approximations matching plugin精度. |
|
||||
| `leveltrack` | Envelope follower with per-bin attack/release tables. |
|
||||
|
||||
### 4.4 Detector Cascade (529c60)
|
||||
|
||||
```
|
||||
complex_spectrum × twin_response → |z|
|
||||
→ Haar smooth [0.25,0.5,0.25] × n_iters
|
||||
→ peak = max(curve)
|
||||
→ sin_peak = sin(param·30 − 90) · 0.115129 · peak
|
||||
→ curve = max(curve, sin_peak)
|
||||
→ w = -log10(pow(50, ratio·0.001) · ratio·0.001)
|
||||
→ acc = acc·w + curve·(1-w)
|
||||
→ bands_curve = acc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Environment Flags (experiment control)
|
||||
|
||||
| Flag | Effect |
|
||||
|------|--------|
|
||||
| `RT_VLAW=1` | Two-stage law: `mask = 10^(−(α·ln1p(lvl/β)+c)/20)` |
|
||||
| `RT_SYN=1` | STFT without synthesis window (plugin's actual layer) |
|
||||
| `RT_WIN=0/1/2` | Analysis window: sym-Hann / periodic / rect |
|
||||
| `RT_NOWARP=1` | Skip warp modulation |
|
||||
| `RT_NOIIR3=1` | Skip IIR3 ×2 |
|
||||
| `RT_IIR12=0` | Skip freq-domain IIR1/2 (critical with VLAW) |
|
||||
| `RT_DUMP_BIN=<f>` | Dump tract binary (frame `RT_DUMP_FRAME`) |
|
||||
| `RT_VDBG=1` | Print VLAW computations to stderr |
|
||||
| `RT_FAITHFUL=1` | Use faithful chain (`fnfaith.cpp`) |
|
||||
| `RT_FIRCONV=1/3` | FIR application mode (1=complex-mul, 3=`1.019·mask^1.8345`) |
|
||||
| `RT_ENV=live` | Live envelope from kRTAtt/kRTRel tables |
|
||||
| `RT_KMAP=1` | k-mapping correction (twin/am scaling) |
|
||||
| `RT_EQ=1` | Pre-detector EQ bell |
|
||||
| `RT_LUT_OFF=1` | Skip LUT transform |
|
||||
| `RT_LUT_A/B/G/M` | LUT parameters (A=-24, B=28, gamma=1, mult=4.2) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Test Corpus & Metrics
|
||||
|
||||
**Location**: `/home/m/soothe-bt/` (~600 renders, outside git).
|
||||
|
||||
**Key sets**:
|
||||
|
||||
| Prefix | Content |
|
||||
|--------|---------|
|
||||
| `tone1kq_*` | Single tone, fc-scan, quiet (-18 dBFS) |
|
||||
| `tone1k_*` | Single tone, fc-scan, loud (0 dBFS) |
|
||||
| `dual_b1q_*` | Two tones (500+2000), q 0.1…10 |
|
||||
| `al_*` | Level sweep (fc=1000) |
|
||||
| `comb_*` | 4-band multiband |
|
||||
| `burst500_b1` | Primary reference (burst 500 Hz) |
|
||||
|
||||
**Metric** (Goertzel steady-state):
|
||||
```python
|
||||
err_dB = 20·log1₀( ta(out,1000) / ta(ref,1000) )
|
||||
# trimmed to last 75% of input, matched to plugin render length
|
||||
```
|
||||
|
||||
**Regression guards**:
|
||||
```bash
|
||||
python3 scripts/corpus.py # bridge + guard
|
||||
python3 scripts/corpus_structural.py # structural chain
|
||||
python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25
|
||||
python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Current Status (2026-09-02, chain919)
|
||||
|
||||
| Group | Bridge | Structural `RT_CASC=0` (L/R) | Structural `RT_CASC=1` |
|
||||
|-------|--------|-------------------------------|------------------------|
|
||||
| t1kq (fc-scan) | 0.226 | 0.356 | 0.717 |
|
||||
| t1k (loud) | 1.801 | 5.437 | 5.631 |
|
||||
| al (level) | 0.638 | 2.613 | 5.036 |
|
||||
| res | 0.628 | 1.256 | 23.031 |
|
||||
| dual (q-sweep) | 0.726 | 2.042 | 66.642 |
|
||||
| comb (4-band) | 10.149 | 6.004 | 29.172 |
|
||||
| **TOTAL** | **1.594** | **2.689** | **31.653** |
|
||||
|
||||
Historical best structural `0.341` (24mm14, `f40f41e` dual 0.193) not reachable on current HEAD (M/S→L/R + chain).
|
||||
|
||||
**Decoded**:
|
||||
- Layer: STFT without synthesis window, per-bin mask multiply
|
||||
- Law: `mask = 10^(−(α·ln1p(lvl/β)+c)/20)` with content-aware Δ
|
||||
- FIR: `exp(0.984·ln(raw))` + Hann + normalize → bit-exact RFFT to df0
|
||||
- Chain 9–19: IIR4 double + FIR min-phase integrated, `generate_iir4_coefs()` (FUN_180533340) added, gated `RT_CASC`
|
||||
|
||||
**Open gaps**:
|
||||
1. **Chain 9–19** (priority #1): Integrated but uncalibrated (input format level vs cut, blocker `ph*.npz`/live-dump). Target: <0.05.
|
||||
2. **k-mapping** (priority #2): twin/am scaling `k(q≥2)=0.403`.
|
||||
3. **Δ second-peak rule** (priority #3): Content-dependent gain, requires live-dump.
|
||||
|
||||
---
|
||||
|
||||
## 8. Reference Documents
|
||||
|
||||
| Doc | Content |
|
||||
|-----|---------|
|
||||
| `README.md` | TOTAL metric, reproduction steps |
|
||||
| `AGENTS.md` | Build commands, env flags, hazard, corpus format |
|
||||
| `BITEXACT_PLAN.md` | 3-step plan to byte-exact |
|
||||
| `handoff/BLOCKMAP_529fe0.md` | Method map for FUN_180529fe0 |
|
||||
| `handoff/NOTES_LEVEL.md` | Live working journal |
|
||||
| `handoff/NOTES_LEVEL_INDEX.md` | Journal index |
|
||||
| `handoff/NOTES_TWIN.md` | Twin resonator reference |
|
||||
| `handoff/NOTES_CAPTURE.md` | Live-capture protocol |
|
||||
| `handoff/nls_dasm/` | 183 disassembly files |
|
||||
|
||||
---
|
||||
|
||||
## 9. Reproduction & Development Protocol
|
||||
|
||||
1. Install soothe2 VST3 (Windows) under yabridge → `dump_soothe.py` → `soothe_mem.bin`
|
||||
2. Ghidra headless: `analyzeHeadless <proj> soothe_x64 -process soothe_mem.bin -noanalysis -postScript <X>.java`
|
||||
3. Renders: `sweep.py` → `.rpp`, `reaper -renderproject` → `.wav`
|
||||
4. Analysis: `render_parity.py` / `corpus.py`
|
||||
5. After C++ edit: `touch` source → `cmake --build dsp/build --target framed_test` → run corpus guards
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"al_12": 0.6589760322215087,
|
||||
"al_18": 0.8869285064658652,
|
||||
"al_24": 0.9872335108976188,
|
||||
"al_3": 0.21467895021449704,
|
||||
"al_6": 0.36768989474153474,
|
||||
"al_9": 0.5175959713969748,
|
||||
"comb_1000": -0.8390262089726862,
|
||||
"comb_1500": -0.8112870378396803,
|
||||
"comb_2000": -0.809711515922245,
|
||||
"comb_3000": -0.8085898984121008,
|
||||
"comb_500": -0.8092538987382586,
|
||||
"dual_0.1_2000": -0.08995281533909788,
|
||||
"dual_0.1_500": -0.05320412461191143,
|
||||
"dual_0.2_2000": 0.3019781559654401,
|
||||
"dual_0.2_500": -0.053358134264814706,
|
||||
"dual_0.3_2000": 0.2902583798578576,
|
||||
"dual_0.3_500": -0.05348688556617939,
|
||||
"dual_0.5_2000": 0.19650867858105603,
|
||||
"dual_0.5_500": -0.05387091733344582,
|
||||
"dual_0.7_2000": 0.18642830232473012,
|
||||
"dual_0.7_500": -0.05441615588199915,
|
||||
"dual_1.0_2000": 0.23254813837808486,
|
||||
"dual_1.0_500": -0.05551608061654063,
|
||||
"dual_1.5_2000": 0.3084791235429092,
|
||||
"dual_1.5_500": -0.057043442345604364,
|
||||
"dual_10.0_2000": 0.4377055060111567,
|
||||
"dual_10.0_500": 0.20966855072081148,
|
||||
"dual_2.0_2000": 0.3539423473152209,
|
||||
"dual_2.0_500": -0.0574902922094877,
|
||||
"dual_3.0_2000": 0.3970249956345811,
|
||||
"dual_3.0_500": -0.05247116979493978,
|
||||
"dual_5.0_2000": 0.42379656083975153,
|
||||
"dual_5.0_500": -0.010273028233688034,
|
||||
"res_300": 0.740945052805549,
|
||||
"res_400": 0.3420632609886434,
|
||||
"res_450": 0.14758272030066333,
|
||||
"res_475": 0.051083439669293566,
|
||||
"res_490": -0.007634204659142415,
|
||||
"res_500": -0.046625742175670996,
|
||||
"res_510": -0.08701341292655208,
|
||||
"res_525": -0.14710395674869248,
|
||||
"res_550": -0.2500832704002971,
|
||||
"res_600": -0.47125422339018486,
|
||||
"res_700": -1.0367358917741667,
|
||||
"t1k_1000": -0.5485503441283766,
|
||||
"t1k_1050": -0.3167571986018916,
|
||||
"t1k_1100": 0.17418996613796175,
|
||||
"t1k_1200": 0.16460873061812337,
|
||||
"t1k_1500": -0.2608862087302641,
|
||||
"t1k_2000": 0.4691205643000097,
|
||||
"t1k_500": -0.10662852895220419,
|
||||
"t1k_678.7611083984375": 0.027561491468636564,
|
||||
"t1k_800": 0.4822946693646578,
|
||||
"t1k_900": 0.30705968131126005,
|
||||
"t1k_950": -0.28077425246942284,
|
||||
"t1kq_1000": -0.21168039641542824,
|
||||
"t1kq_1050": -0.0011503254358071785,
|
||||
"t1kq_1100": 0.4344054852348732,
|
||||
"t1kq_1200": -0.11613738787651275,
|
||||
"t1kq_800": -0.05292751629888988,
|
||||
"t1kq_900": 0.54236470368085,
|
||||
"t1kq_950": 0.026290891468426707
|
||||
}
|
||||
+40
-1
@@ -62,6 +62,45 @@ def k_div_exact(a, b):
|
||||
return np.asarray(b, dtype=np.float64) / np.asarray(a, dtype=np.float64)
|
||||
|
||||
|
||||
def chain_9_19_sim(bands, tmp6f8, accVec, warp, att, rel, nbin=None):
|
||||
"""CHAIN 9-19 BLOCKMAP:620-644 op-by-op (python структурный прокси).
|
||||
bands: log-domain bands_curve_i (size nbin), tmp6f8: f6f8, accVec: ACC_i
|
||||
warp: kWarp@5406a8, att@5406c8 rel@5406e8 — все 2049.
|
||||
Возвращает bands mutated (log domain после IIR, перед FIR)."""
|
||||
if nbin is None:
|
||||
nbin = len(bands)
|
||||
bands = np.asarray(bands, dtype=np.float64)
|
||||
tmp6f8 = np.asarray(tmp6f8, dtype=np.float64)
|
||||
accVec = np.asarray(accVec, dtype=np.float64)
|
||||
# 9a zero already (vec698 irrelevant), 9b: tmp6f8 += 0.8 (p=1.0 *0.8)
|
||||
# 9c DIVIDE dst=bands A=bands B=tmp6f8+0.8? Actually B=tmp6f8, but step 9b already added 0.8
|
||||
# BLOCKMAP 24mm5: bands = tmp6f8 / bands (divide B/A)
|
||||
# Use k_div_exact
|
||||
b = tmp6f8 + 0.8 # proxy for step 9b effect (when tmp6f8 initially 0, b=0.8)
|
||||
# If tmp6f8 already has data, 9b is additive 0.8, so b = tmp6f8 +0.8
|
||||
# For calibration where tmp6f8 is 0, this yields 0.8/bands
|
||||
bands = k_div_exact(np.maximum(bands, 1e-30), b)
|
||||
# 10 dc40: tmp6f8 = bands - ACC
|
||||
tmp6f8 = bands - accVec
|
||||
# 11 FMA ATT/REL half-split
|
||||
# true triples 12B re/im/coef, scalar proxy: upper half ATT, lower REL
|
||||
for i in range(nbin):
|
||||
if i < nbin // 2:
|
||||
tmp6f8[i] += att[i] * accVec[i]
|
||||
else:
|
||||
tmp6f8[i] += rel[i] * accVec[i]
|
||||
# 14 EXP#1 + -1 (order fixed 24mm2)
|
||||
bands = k_exp_exact(bands) - 1.0
|
||||
# 15 array-mul X*track (track is external, warp mul is step16)
|
||||
# 16 *kWarp + LOG#2
|
||||
bands = bands * warp
|
||||
bands = np.log(np.maximum(bands, 1e-30))
|
||||
# 16b IIR4x2 bidir log-domain base 0x340510 — proxy via kIIR_A1/B1
|
||||
# keep as identity for now (real IIR is mild 0.003-0.19 per 24cc)
|
||||
# FIR will be applied externally via fir_kernel
|
||||
return bands, tmp6f8
|
||||
|
||||
|
||||
# ------------------------------------------------------------ данные -----
|
||||
def load_tract(path):
|
||||
"""tract_*.txt: k am res lvl_raw band_level prewarp w"""
|
||||
@@ -130,7 +169,7 @@ def win_periodic_hann(N):
|
||||
# ------------------------------------------------ FIR-цепь (24mm9) --------
|
||||
NFRAME = 4096 # n=[ctx+0x540534]
|
||||
NBINS_FIR = NFRAME // 2 + 1
|
||||
Q_EXP = 0.80 # скаляр аргумента EXP; источник в 1803831c0 (ОТКРЫТО)
|
||||
Q_EXP = 0.8002203702926636 # .rdata 1820013f0, refines 0.80 (BLOCKMAP 52b716)
|
||||
|
||||
|
||||
def winfreq_fall():
|
||||
|
||||
@@ -176,6 +176,25 @@ def main():
|
||||
store['lut_G'] = struct.unpack('<f', fb4[12:16])[0]
|
||||
store['lut_mode'] = fb4[16]
|
||||
for off in SLOTS[1:]:
|
||||
if off == 0x5407c8:
|
||||
# ACC is table of pointers per-band [5407c8 + i*8] -> buf 2049 f32 each
|
||||
for bi in range(4):
|
||||
q = rd(ctx + off + 8*bi, 8)
|
||||
if not q: continue
|
||||
ptr = struct.unpack('<Q', q)[0]
|
||||
if ptr < 0x10000: continue
|
||||
ab = rd(ptr, 2049 * 4)
|
||||
if ab:
|
||||
store[hex(off) + f"_{bi}"] = np.frombuffer(ab, dtype='<f4').astype(np.float32)
|
||||
# also store raw table pointer for debug
|
||||
q = rd(ctx + off, 8)
|
||||
if q:
|
||||
ptr = struct.unpack('<Q', q)[0]
|
||||
if ptr >= 0x10000:
|
||||
ab = rd(ptr, NARR * 4)
|
||||
if ab:
|
||||
store[hex(off)] = np.frombuffer(ab, dtype='<f4').astype(np.float32)
|
||||
continue
|
||||
q = rd(ctx + off, 8)
|
||||
if not q:
|
||||
continue
|
||||
|
||||
@@ -37,6 +37,11 @@ BP_CIN = 0x180529c60
|
||||
BP_COUT = 0x180529ee1
|
||||
BP_AIN = 0x180016140
|
||||
BP_AOUT = 0x18000332c
|
||||
# Chain 9-19 breakpoints (BLOCKMAP:620-644)
|
||||
BP_CHAIN_START = 0x18052a580 # band loop start (52a580)
|
||||
BP_CHAIN_LOG1 = 0x18052a63a # LOG#1 in chain (52a63a)
|
||||
BP_CHAIN_LOG2 = 0x18052aefd # LOG#2 in chain (52aefd)
|
||||
BP_CHAIN_END = 0x18052b3cd # chain end / FIR start (52b3cd)
|
||||
CTX_SLOTS = {'scr': 0x540628, 'trk': 0x540688, 'cur': 0x540678,
|
||||
'fir_ptr': 0x540668}
|
||||
|
||||
@@ -281,7 +286,12 @@ def main():
|
||||
('DIV', BP_DIV), ('DC40', BP_DC40),
|
||||
('EXPVAR', BP_EXPVAR), ('FN', BP_FN),
|
||||
('CIN', BP_CIN), ('COUT', BP_COUT),
|
||||
('AIN', BP_AIN), ('AOUT', BP_AOUT)):
|
||||
('AIN', BP_AIN), ('AOUT', BP_AOUT),
|
||||
# Chain 9-19 breakpoints
|
||||
('CHAIN_START', BP_CHAIN_START),
|
||||
('CHAIN_LOG1', BP_CHAIN_LOG1),
|
||||
('CHAIN_LOG2', BP_CHAIN_LOG2),
|
||||
('CHAIN_END', BP_CHAIN_END)):
|
||||
if not mapped(addr):
|
||||
print('!! %s@%#x не смапплен — пропуск' % (nm_ := name, addr), flush=True)
|
||||
continue
|
||||
@@ -299,9 +309,11 @@ def main():
|
||||
samples = []
|
||||
hits = {'COPY': 0, 'EXP': 0, 'DF0': 0, 'DF0RET': 0, 'TRACKSAVE': 0,
|
||||
'DIV': 0, 'DC40': 0, 'EXPVAR': 0, 'FN': 0,
|
||||
'CIN': 0, 'COUT': 0, 'AIN': 0, 'AOUT': 0}
|
||||
'CIN': 0, 'COUT': 0, 'AIN': 0, 'AOUT': 0,
|
||||
'CHAIN_START': 0, 'CHAIN_LOG1': 0, 'CHAIN_LOG2': 0, 'CHAIN_END': 0}
|
||||
track_by_tid = {}
|
||||
track_dumps = []
|
||||
chain_dumps = []
|
||||
regs_by_tid = {}
|
||||
t_start = time.time()
|
||||
|
||||
@@ -410,8 +422,35 @@ def main():
|
||||
continue
|
||||
if kind == 'DF0':
|
||||
track_by_tid[pid] = regs.rdx
|
||||
# Chain 9-19: dump buffers at key points
|
||||
if kind in ('CHAIN_START', 'CHAIN_LOG1', 'CHAIN_LOG2', 'CHAIN_END'):
|
||||
rec_c = {'kind': kind, 'tid': pid,
|
||||
't': round(time.time()-t_start, 4),
|
||||
'rcx': regs.rcx, 'rdx': regs.rdx,
|
||||
'r8': regs.r8, 'r9': regs.r9,
|
||||
'rsp': regs.rsp}
|
||||
try:
|
||||
# chain input: [678i] = rcx (bands_curve), [6f8] = rdx (tmp6f8)
|
||||
if regs.rcx > 0x10000 and regs.rdx > 0x10000:
|
||||
rec_c['bands_in'] = rd_f32(regs.rcx, min(2050, 512)) # first 512 vals
|
||||
rec_c['tmp6f8_in'] = rd_f32(regs.rdx, min(2050, 512))
|
||||
rec_c['acc_ptr'] = regs.r8 if regs.r8 > 0x10000 else 0
|
||||
if rec_c['acc_ptr']:
|
||||
rec_c['acc_in'] = rd_f32(rec_c['acc_ptr'], min(2050, 512))
|
||||
except OSError as e:
|
||||
rec_c['err'] = str(e)
|
||||
chain_dumps.append(rec_c)
|
||||
hits[kind] += 1
|
||||
# standard int3 handling
|
||||
poke(pid, site, (peek(pid, site) & ~0xFF) | obyte)
|
||||
regs.rip = site
|
||||
setregs(pid, regs)
|
||||
pt(PTRACE_SINGLESTEP, pid, 0, 0)
|
||||
os.waitpid(pid, __WALL)
|
||||
poke(pid, site, (peek(pid, site) & ~0xFF) | 0xCC)
|
||||
pt(PTRACE_CONT, pid, 0, 0)
|
||||
continue
|
||||
if kind in ('CIN','COUT'):
|
||||
key='cin_%d'%pid if kind=='CIN' else 'cout_%d'%pid
|
||||
if kind=='CIN':
|
||||
regs_by_tid[pid]=dict(rdx=regs.rdx,r12=regs.r12,
|
||||
rcx=regs.rcx)
|
||||
@@ -632,7 +671,7 @@ def main():
|
||||
snap_ptrs[nm] = p
|
||||
snap_arr[nm] = rd_f32(p, 4100)
|
||||
with open(os.path.join(outdir, 'chain_samples.pkl'), 'wb') as f:
|
||||
pickle.dump({'samples': samples, 'snap_ptrs': snap_ptrs, 'ctx': ctx}, f)
|
||||
pickle.dump({'samples': samples, 'chain_dumps': chain_dumps, 'snap_ptrs': snap_ptrs, 'ctx': ctx}, f)
|
||||
np.savez_compressed(os.path.join(outdir, 'ctx_snap.npz'), **snap_arr)
|
||||
print('saved %d -> %s' % (len(samples), outdir), flush=True)
|
||||
for _ in range(600):
|
||||
|
||||
Reference in New Issue
Block a user