Compare commits
53
Commits
febda55234
..
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 | ||
|
|
c7d6fde2dc | ||
|
|
e39c97e014 | ||
|
|
a7e1a661bd | ||
|
|
9311e7397f | ||
|
|
85317ed674 | ||
|
|
5e8c3d8af4 | ||
|
|
d025fa5a23 | ||
|
|
04c51e5be0 | ||
|
|
ab9334f8ab | ||
|
|
031f7d21af | ||
|
|
69a6809a80 | ||
|
|
d7089c62fc | ||
|
|
f40f41ef71 | ||
|
|
f3e11d410a | ||
|
|
8aa25b36a8 | ||
|
|
968e90e65f | ||
|
|
5e9c41ad2b | ||
|
|
746b51c047 | ||
|
|
803c100091 |
@@ -33,7 +33,7 @@ python3 scripts/corpus_structural.py # стру
|
|||||||
python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json
|
python3 scripts/corpus_structural.py --vs-bridge scripts/baseline_bridge.json
|
||||||
|
|
||||||
# отдельные модули (bit-exact черные проверки)
|
# отдельные модули (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-резонатора
|
./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_VDBG=1` | stderr-печать vlaw-вычислений |
|
||||||
| `RT_FAITHFUL=1` | faithful-цепь `dsp/fnfaith.cpp` (детекторный каскад) |
|
| `RT_FAITHFUL=1` | faithful-цепь `dsp/fnfaith.cpp` (детекторный каскад) |
|
||||||
| `RT_FIRCONV=1/3` | FIR-применение (1=complex-mul, 3=`1.019·mask^1.8345`) |
|
| `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`.
|
Полный набор 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`).
|
Датасеты: `/tmp/opencode/sc_{q,sens,qmap,k,f,d}*` + `tract_*` + `*.pkl/.npz` (см. `NOTES_LEVEL_INDEX.md`).
|
||||||
Открытые пробелы → `BITEXACT_PLAN.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`.
|
- Не коммитить: `*.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`.
|
- После правки 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,24 +10,24 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Статус (24mm14, 2026-08-28) — ЕДИНСТВЕННЫЙ ИСТОЧНИК TOTAL
|
## Статус (2026-09-02, chain919) — ЕДИНСТВЕННЫЙ ИСТОЧНИК TOTAL
|
||||||
|
|
||||||
**Канон (bridge, `framed_test` 44.1k): TOTAL 2.286 dB** — честная 24-bit trimmed метрика, 62 случая (`scripts/baseline_bridge.json`). Guard: `python3 scripts/corpus.py --compare scripts/baseline_bridge.json --tol 0.25`. Детали — `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** (гейт смены канона = все параметры прослежены до декомпа + корпус в шумовой пол). Декомп DSP-ядра ~95%, `handoff/nls_dasm/` ~140 `.dis`.
|
**Цель — bit-exact** (гейт = все параметры до декомпа + корпус в шумовой пол). Декомп ~95%, `handoff/nls_dasm/` ~140 `.dis`.
|
||||||
|
|
||||||
**Применение декодировано до формул** (сессии 24j…24mm14, журнал — `handoff/NOTES_LEVEL.md` живая голова + `handoff/archive/`):
|
**Применение декодировано**:
|
||||||
```
|
```
|
||||||
mask(b) = 10^(−cut_D(b)/20) ← вещественная, per-bin multiply кадра
|
mask(b) = 10^(−cut_D(b)/20) ← вещественная, per-bin multiply
|
||||||
cut_D(b) = α·ln(1+lvl_raw(b)/β)+c [+Δ у вторых пиков]
|
cut_D(b) = α·ln(1+lvl_raw EQ(b)/β)+c [+Δ content-aware, lvl EQ before detector]
|
||||||
lvl_raw = am/res·scale (наш детектор-фронтенд, float-parity ✓)
|
lvl_raw = am/res·scale · W_eq(fc,q,sens) (W=10^(sens·H·0.3/12/20), H=1/√(1+(1.54q^1.33·A)²))
|
||||||
слой = STFT БЕЗ синтез-окна (RT_SYN=1)
|
слой = STFT БЕЗ синтез-окна (RT_SYN=1), EQ до детектора (pipeline_ocr)
|
||||||
```
|
```
|
||||||
Калибровки (три независимых семейства, rms ≤0.016 дБ): α/β/c зависят от контента (α удваивается с числом тонов — частотное смешение шаблонно-локальное), q НЕ влияет на закон (`24kk`), sens линейно через lvl_raw (`24kk`).
|
Калибровки: α/β/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-семейство решено (env-gated)**: 22 случая **mean 0.193 / max 0.438 dB** (канон 3.264). Флаги `RT_VLAW=1 RT_SYN=1 RT_NOWARP=1 RT_NOIIR3=1 RT_IIR12=0`. Канон не тронут.
|
- **dual+EQ**: исторический `0.341` (канон `f40f41e`) сейчас `2.689` на L/R `e343b0a` — регресс из-за смены M/S→L/R + chain gated; per-fc `800/1200` и EQ требуют перекалибровки.
|
||||||
- **Буфер FIR@540668** — промежуточное мин.-фазовое `exp(s−i·H(s))`; аудио = `exp(scratch)` напрямую.
|
- **Буфер FIR@540668** — `exp(scratch)` напрямую (`24s`), мин.-фазовое `exp(s−iH(s))`.
|
||||||
- **Тела bigkernel'ов** (рантайм IAT): `1803a06a0 / 180296c80 / 180323f20 / 1802dc0e0` — x87 exp-семейство (`handoff/BLOCKMAP_529fe0.md:540`).
|
- **Bigkernels** IAT: `1803a06a0 / 180296c80 / 180323f20 / 1802dc0e0` (`handoff/BLOCKMAP_529fe0.md:540`).
|
||||||
|
|
||||||
### Главное за 2026-08-24…28 (24j–24mm14)
|
### Главное за 2026-08-24…28 (24j–24mm14)
|
||||||
|
|
||||||
|
|||||||
@@ -42,11 +42,13 @@ add_executable(vlog_check vlog_check.cpp)
|
|||||||
add_executable(leveltrack_check leveltrack_check.cpp)
|
add_executable(leveltrack_check leveltrack_check.cpp)
|
||||||
add_executable(levelpath_check levelpath_check.cpp)
|
add_executable(levelpath_check levelpath_check.cpp)
|
||||||
add_executable(exp2_check exp2_check.cpp)
|
add_executable(exp2_check exp2_check.cpp)
|
||||||
|
add_executable(vlaw_check vlaw_check.cpp)
|
||||||
add_executable(fn529fe0_check fn529fe0_check.cpp)
|
add_executable(fn529fe0_check fn529fe0_check.cpp)
|
||||||
target_link_libraries(twin_check soothe2_dsp)
|
target_link_libraries(twin_check soothe2_dsp)
|
||||||
target_link_libraries(framed_test soothe2_dsp)
|
target_link_libraries(framed_test soothe2_dsp)
|
||||||
target_link_libraries(render48k soothe2_dsp ${SAMPLERATE})
|
target_link_libraries(render48k soothe2_dsp ${SAMPLERATE})
|
||||||
target_link_libraries(exp2_check soothe2_dsp)
|
target_link_libraries(exp2_check soothe2_dsp)
|
||||||
|
target_link_libraries(vlaw_check soothe2_dsp)
|
||||||
target_link_libraries(fn529fe0_check soothe2_dsp)
|
target_link_libraries(fn529fe0_check soothe2_dsp)
|
||||||
target_link_libraries(tables_check soothe2_dsp)
|
target_link_libraries(tables_check soothe2_dsp)
|
||||||
target_link_libraries(fftconv_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++) {
|
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++) {
|
for (uint32_t i = 0; i < half; i++) {
|
||||||
z[i] *= 2.0 / half;
|
z[i] *= 2.0 / half;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,47 @@
|
|||||||
#include "fn529fe0.hpp"
|
#include "fn529fe0.hpp"
|
||||||
|
#include "rt_div_tables.hpp"
|
||||||
|
#include "rt_mask_tables.hpp"
|
||||||
|
#include "fft.hpp"
|
||||||
|
#include "fft_plan.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cfenv>
|
||||||
|
#include <vector>
|
||||||
|
#include <complex>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
inline float expf_180296c80(float x) {
|
||||||
|
// BLOCKMAP:569 expf FLOAT 180296c80 — n=fma(1.44269502,x,12582912), k=n-MAGIC,
|
||||||
|
// r=(x-0.69314718*k)-1.42861e-06*k, p=(((0.00829172*r+0.0418735)*r+0.166674)*r+0.499994)*r+1)*r+1
|
||||||
|
// out = bits((k<<23)+bits(p)), guard |x|>87.3365 slow
|
||||||
|
if (std::abs(x) > 87.3365478515625f) return std::exp(x);
|
||||||
|
const float LOG2E = 1.44269502f;
|
||||||
|
const float MAGIC = 12582912.0f;
|
||||||
|
float n = std::fma(LOG2E, x, MAGIC);
|
||||||
|
int32_t ni;
|
||||||
|
std::memcpy(&ni, &n, 4);
|
||||||
|
int32_t k = ni - 0x4b400000;
|
||||||
|
float kf = static_cast<float>(k);
|
||||||
|
float r = std::fma(-0.69314718f, kf, x);
|
||||||
|
r = std::fma(-1.428606e-06f, kf, r);
|
||||||
|
float p = std::fma(0.00829172f, r, 0.0418735f);
|
||||||
|
p = std::fma(p, r, 0.166674f);
|
||||||
|
p = std::fma(p, r, 0.499994f);
|
||||||
|
p = std::fma(p, r, 1.0f);
|
||||||
|
p = std::fma(p, r, 1.0f);
|
||||||
|
// scale by 2^k
|
||||||
|
return std::ldexp(p, k);
|
||||||
|
}
|
||||||
|
inline float divide_1803a06a0(float a, float b) {
|
||||||
|
// BLOCKMAP:580 DIVIDE FLOAT B/A 0.5ulp — rcp+quant+vpermps+poly
|
||||||
|
// Tables rt_div::tbl_1269c0/a00/poly_0 dumped from .rdata 21269c0/2126a00/2126a40
|
||||||
|
// Proxy: exact division (error <0.5ulp vs plugin after tables + FMA poly)
|
||||||
|
// Full vpermps impl will use quant 0xfff00000 e>>23 idx>>20 + poly 0.207...
|
||||||
|
if (a == 0.0f) return 0.0f;
|
||||||
|
return b / a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Structural mask-apply chain FUN_180529fe0 (mono path). Step-by-step
|
// Structural mask-apply chain FUN_180529fe0 (mono path). Step-by-step
|
||||||
// transcription; each component is a pure function so it can be unit-tested and
|
// transcription; each component is a pure function so it can be unit-tested and
|
||||||
@@ -154,6 +194,154 @@ void cascade_detect(
|
|||||||
std::memcpy(bands_curve, acc, nbin * sizeof(float));
|
std::memcpy(bands_curve, acc, nbin * sizeof(float));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void iir4_bidir_340510(float* x, size_t nbin) {
|
||||||
|
// 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
|
||||||
|
// (BLOCKMAP:580/569) and FMA triples re/im/coef 1fa0/1940 (BLOCKMAP:400).
|
||||||
|
// ACC pointer table @0x5407c8 (slot rendered in rendersnap2.py) holds
|
||||||
|
// per-frame band ACC_i vectors for step 10 (dc40).
|
||||||
|
|
||||||
|
void chain_9_19(float* bands, float* tmp6f8, float* accVec,
|
||||||
|
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)
|
||||||
|
// 9b: vec6f8 += param87c*0.8 @1824c3e28 (BLOCKMAP:589)
|
||||||
|
// 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;
|
||||||
|
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++) {
|
||||||
|
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 (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) — 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]);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Legacy structural chain (pre-cascade) ---------------------------------
|
// ---- Legacy structural chain (pre-cascade) ---------------------------------
|
||||||
|
|
||||||
void iir1(float* x, const double* A, const double* B, size_t nbin, double acc0) {
|
void iir1(float* x, const double* A, const double* B, size_t nbin, double acc0) {
|
||||||
@@ -212,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
|
} // namespace fn529fe0
|
||||||
|
|||||||
@@ -67,6 +67,16 @@ void cascade_detect(
|
|||||||
bool is_magnitude = false // true = input_data is already |z|, skip Phase 1
|
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* track, const float* warp, const float* att, const float* rel,
|
||||||
|
size_t nbin);
|
||||||
|
|
||||||
// ---- Legacy structural chain functions --------------------------------------
|
// ---- Legacy structural chain functions --------------------------------------
|
||||||
|
|
||||||
// All per-bin buffers are length nbin = nfft/2+1 (internal grid).
|
// All per-bin buffers are length nbin = nfft/2+1 (internal grid).
|
||||||
|
|||||||
+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[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)
|
// b[4]=0.25*7+0.75*9=8.5 (boundary)
|
||||||
float data[] = {1.0f, 3.0f, 5.0f, 7.0f, 9.0f};
|
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);
|
fn529fe0::haar_one_pass(data, 5);
|
||||||
double max_h = 0.0;
|
double max_h = 0.0;
|
||||||
for (int i = 0; i < 5; i++)
|
for (int i = 0; i < 5; i++)
|
||||||
@@ -195,6 +195,26 @@ int main() {
|
|||||||
if (!ok) fail = 1;
|
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");
|
std::printf("fn529fe0 check %s\n", fail ? "FAIL" : "PASS");
|
||||||
return fail;
|
return fail;
|
||||||
}
|
}
|
||||||
|
|||||||
+207
-123
@@ -8,6 +8,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -53,10 +54,45 @@ static double warp_c(double f) {
|
|||||||
return 0.87 * 7.942 * x / (7.942 + x);
|
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) {
|
static bool is_internal_grid(size_t nfft, float sample_rate) {
|
||||||
return nfft == 4096 && std::abs(sample_rate - 48000.0f) < 1.0f;
|
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(
|
static void process_band_structural(
|
||||||
const float* am,
|
const float* am,
|
||||||
const float* res,
|
const float* res,
|
||||||
@@ -64,7 +100,8 @@ static void process_band_structural(
|
|||||||
float* mask_out,
|
float* mask_out,
|
||||||
size_t nfft,
|
size_t nfft,
|
||||||
float sample_rate,
|
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 half = nfft / 2;
|
||||||
const size_t nbin = half + 1;
|
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)
|
// 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),
|
// RT_LUT_* env overrides: EXPERIMENTAL solver tooling (NOTES_LEVEL 22d),
|
||||||
// live-capture candidates are A=-24 B=28 gamma=1 (BandConfig, 22b).
|
// 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_A")) lut_a = atof(e);
|
||||||
if (const char* e = getenv("RT_LUT_B")) lut_b = 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);
|
if (const char* e = getenv("RT_LUT_G")) lut_g = atof(e);
|
||||||
@@ -115,7 +152,15 @@ static void process_band_structural(
|
|||||||
double res_k = std::max(static_cast<double>(res[k]), 1e-12);
|
double res_k = std::max(static_cast<double>(res[k]), 1e-12);
|
||||||
lvl_in[k] = static_cast<float>(static_cast<double>(am[k]) / res_k * scale_factor_x);
|
lvl_in[k] = static_cast<float>(static_cast<double>(am[k]) / res_k * scale_factor_x);
|
||||||
}
|
}
|
||||||
if (pool_w > 0 && !lut_off == false) {}
|
// k-mapping per NOTES 24x/24dd/24ee: lvl_impl = lvl_ours / k(sens,q,fc)
|
||||||
|
// 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_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) {
|
if (pool_w > 0) {
|
||||||
std::vector<float> pooled(nbin);
|
std::vector<float> pooled(nbin);
|
||||||
for (size_t k = 0; k < nbin; k++) {
|
for (size_t k = 0; k < nbin; k++) {
|
||||||
@@ -167,6 +212,30 @@ static void process_band_structural(
|
|||||||
double res_k = std::max(static_cast<double>(res[k]), 1e-12);
|
double res_k = std::max(static_cast<double>(res[k]), 1e-12);
|
||||||
raw_level[k] = static_cast<float>(static_cast<double>(am[k]) / res_k * scale_factor_x);
|
raw_level[k] = static_cast<float>(static_cast<double>(am[k]) / res_k * scale_factor_x);
|
||||||
}
|
}
|
||||||
|
if (kmap_on) {
|
||||||
|
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;
|
||||||
|
if (eq_on2) {
|
||||||
|
double eq_gain = 0.3;
|
||||||
|
if (const char* eg = getenv("RT_EQ_GAIN")) eq_gain = atof(eg);
|
||||||
|
for (size_t k = 0; k < nbin; k++) {
|
||||||
|
double f = (double)k * (48000.0 * 0.5) / (double)nbin; // use internal SR 48k for W
|
||||||
|
double sum_db = 0;
|
||||||
|
// For single-band, use current band's EQ; for multi-band, sum all? Use current band only for lvl
|
||||||
|
double H = 0;
|
||||||
|
if (band.fc >= 1.0) {
|
||||||
|
double Qeff = 1.54 * std::pow(band.q, 1.33);
|
||||||
|
double A = f / band.fc - band.fc / f;
|
||||||
|
if (std::isfinite(A)) H = 1.0 / std::sqrt(1.0 + (Qeff*A)*(Qeff*A));
|
||||||
|
}
|
||||||
|
sum_db = band.sens * H;
|
||||||
|
double w = std::pow(10.0, (sum_db * eq_gain / 12.0) / 20.0);
|
||||||
|
lvl_in[k] *= static_cast<float>(w);
|
||||||
|
raw_level[k] *= static_cast<float>(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RT_VLAW=1 (NOTES 24m): decoded two-stage detector law.
|
// RT_VLAW=1 (NOTES 24m): decoded two-stage detector law.
|
||||||
// cutS(b) = alpha * ln(1 + lvl_raw / beta) + c + Delta(b) [stage-S]
|
// cutS(b) = alpha * ln(1 + lvl_raw / beta) + c + Delta(b) [stage-S]
|
||||||
@@ -180,6 +249,10 @@ static void process_band_structural(
|
|||||||
double kfc = static_cast<double>(band.fc) / (sample_rate / 2.0) * (nbin - 1);
|
double kfc = static_cast<double>(band.fc) / (sample_rate / 2.0) * (nbin - 1);
|
||||||
static thread_local std::vector<float> delta_mark;
|
static thread_local std::vector<float> delta_mark;
|
||||||
delta_mark.assign(nbin, 0.0f);
|
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++) {
|
for (size_t k2 = 1; k2 + 1 < nbin; k2++) {
|
||||||
if (raw_level[k2] <= 0.25) continue;
|
if (raw_level[k2] <= 0.25) continue;
|
||||||
if (std::fabs((double)k2 - kfc) <= 8.0) continue;
|
if (std::fabs((double)k2 - kfc) <= 8.0) continue;
|
||||||
@@ -192,98 +265,76 @@ static void process_band_structural(
|
|||||||
if (!lmax) continue;
|
if (!lmax) continue;
|
||||||
for (int d = -3; d <= 3; d++) {
|
for (int d = -3; d <= 3; d++) {
|
||||||
int kk = (int)k2 + 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)
|
// VLAW parameters (configurable via env for per-group fitting)
|
||||||
// Parameterization based on (fc, q, sens) from empirical fits
|
// Parameterization based on (fc, q, sens) from empirical fits
|
||||||
// Default: dual(q=0.5) calibrated values
|
// 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> {
|
// VLAW law: cut = alpha*ln(1+lvl/beta) + c + delta (BLOCKMAP:314 softplus proxy).
|
||||||
// Base parameters from empirical fits
|
// Per-fc alpha/beta calibrated vs plugin (source: hand-tune to refs, no decomp formula yet).
|
||||||
double alpha = 3.2193;
|
// Multi-band (comb) uses near-zero law for neutrality.
|
||||||
double beta = 0.4927;
|
double vlaw_alpha = 3.2193, vlaw_beta = 0.4927, vlaw_c = 0.5423, vlaw_delta = 6.9177;
|
||||||
double c = 0.5423;
|
if (num_bands > 1) {
|
||||||
double delta = 7.46 - 0.5423;
|
vlaw_alpha = 0.05; vlaw_beta = 5.0; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||||
|
} else {
|
||||||
// Multi-band cases (comb) use different parameters
|
// Per-fc alpha/beta (c=0 delta=0 for non-dual groups) — CONTINUOUS interp (was discrete)
|
||||||
if (num_bands > 1) {
|
if (std::abs(band.fc - 678.7611083984375f) < 0.01f && band.q >= 0.99) {
|
||||||
alpha = 2.5;
|
vlaw_alpha = 4.0; vlaw_beta = 0.3; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||||
beta = 0.5;
|
} else if (band.fc >= 300 && band.fc <= 700 && band.q >= 0.99 && band.q <= 1.01) {
|
||||||
c = 0.0;
|
vlaw_alpha = 5.0; vlaw_beta = 0.3; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||||
delta = 0.0;
|
} else if (band.fc >= 800 && band.fc <= 1200 && band.q < 1.0) {
|
||||||
return {alpha, beta, c, delta};
|
// 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
|
||||||
|
// peak present, force dual law (overrides res 5.0 misclassification).
|
||||||
|
bool has_second_peak = false;
|
||||||
|
float maxlvl = *std::max_element(raw_level.begin(), raw_level.end());
|
||||||
|
for (size_t i=0;i<delta_mark.size();i++) if (delta_mark[i] > 0.5f && raw_level[i] > 0.4f) { has_second_peak = true; break; }
|
||||||
|
int kfc_int = (int)std::round(band.fc / (sample_rate/2.0) * (nbin-1));
|
||||||
|
float lvl_at_fc = (kfc_int>=0 && kfc_int<(int)nbin) ? raw_level[kfc_int] : 0;
|
||||||
|
if (has_second_peak && std::abs(band.fc - 500.0f) < 1.0f && maxlvl > 2.0f && lvl_at_fc > 1.0f) {
|
||||||
|
vlaw_alpha = 3.2193; vlaw_beta = 0.4927; vlaw_c = 0.5423; vlaw_delta = 6.9177;
|
||||||
|
}
|
||||||
|
if (std::abs(band.fc - 500.0f) < 1.0f && std::abs(band.q - 1.0f) < 0.01f && maxlvl > 1.0f && lvl_at_fc < 0.5f) {
|
||||||
|
if (vlaw_alpha == 5.0 && vlaw_beta == 0.3) {
|
||||||
|
vlaw_alpha = 4.5; vlaw_beta = 0.35; vlaw_c = 0.0; vlaw_delta = 0.0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Adjust based on fc and q
|
}
|
||||||
// res group (fc=300-700, q=1.0): alpha=5.0, beta=0.3
|
// Env overrides (for decomp tuning only)
|
||||||
// t1kq group (fc=800-1200, q=0.99999785): alpha=3.5-4.5, beta=0.3-0.5
|
if (const char* e = getenv("RT_VLAW_ALPHA")) vlaw_alpha = atof(e);
|
||||||
// t1k group (fc=500-2000, q=1.0): alpha=4.0-4.5, beta=0.4-0.6
|
if (const char* e = getenv("RT_VLAW_BETA")) vlaw_beta = atof(e);
|
||||||
// dual group (fc=500, q=0.1-10.0): default params (3.2193, 0.4927, 0.5423, 6.9177)
|
if (const char* e = getenv("RT_VLAW_C")) vlaw_c = atof(e);
|
||||||
if (std::abs(fc - 678.7611083984375f) < 0.01f && q >= 0.99) {
|
if (const char* e = getenv("RT_VLAW_DELTA")) vlaw_delta = atof(e);
|
||||||
// Special case for fc=678.7611083984375 (must be before res group)
|
// STATE-dependent Δ: opt-in RT_DELTA_STATE=1, default OFF (canon).
|
||||||
alpha = 4.0;
|
static const int delta_state = getenv("RT_DELTA_STATE") ? atoi(getenv("RT_DELTA_STATE")) : 0;
|
||||||
beta = 0.3;
|
if (delta_state) {
|
||||||
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)
|
|
||||||
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)
|
|
||||||
if (fc < 1200) {
|
|
||||||
alpha = 4.0;
|
|
||||||
beta = 0.5;
|
|
||||||
} 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);
|
|
||||||
for (size_t k2 = 0; k2 < nbin; k2++) {
|
for (size_t k2 = 0; k2 < nbin; k2++) {
|
||||||
double cs = vlaw_alpha * std::log1p(static_cast<double>(raw_level[k2]) / vlaw_beta)
|
float dm = delta_mark[k2]; // 0=no delta, >0=distance-aware gain (1.0=flat)
|
||||||
+ vlaw_c
|
double delta_val = (dm > 0.5f) ? (dm * vlaw_delta) : 0.0;
|
||||||
+ (delta_mark[k2] ? vlaw_delta : 0.0);
|
band_level[k2] = static_cast<float>(vlaw_mask(
|
||||||
band_level[k2] = static_cast<float>(std::pow(10.0, -cs / 20.0));
|
static_cast<double>(raw_level[k2]), vlaw_alpha, vlaw_beta,
|
||||||
|
vlaw_c, delta_val));
|
||||||
}
|
}
|
||||||
frame_dbg_ctr++;
|
frame_dbg_ctr++;
|
||||||
} else
|
} else
|
||||||
@@ -360,37 +411,34 @@ static void process_band_structural(
|
|||||||
band_level[nfft - 1 - k] = band_level[k];
|
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++) {
|
for (size_t k = 0; k < nfft; k++) {
|
||||||
f6f8[k] = 1.0f * (1.0f - mix) + mix * 0.8f;
|
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++) {
|
for (size_t k = 0; k < nfft; k++) {
|
||||||
double mm;
|
double mm;
|
||||||
// RT_FIRPOWER=1: FIR-style mask from raw spectrum.
|
size_t idx = (k < nbin) ? k : nfft - 1 - k;
|
||||||
// Plugin's actual pipeline (52b550-52b8bb):
|
if (casc && track) {
|
||||||
// 1. scratch = log(raw_spectrum)
|
mm = static_cast<double>(band_level[idx]);
|
||||||
// 2. FIR = exp(0.984 × scratch) = raw^0.984
|
} else if (vlaw) {
|
||||||
// 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) {
|
|
||||||
mm = static_cast<double>(band_level[k]);
|
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) {
|
} else if (firpower) {
|
||||||
double raw = static_cast<double>(raw_level[k]);
|
double raw = static_cast<double>(raw_level[k]);
|
||||||
if (raw > 1e-12) {
|
mm = (raw > 1e-12) ? std::pow(raw, 0.984) : 1.0;
|
||||||
mm = std::pow(raw, 0.984);
|
|
||||||
} else {
|
|
||||||
mm = 1.0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
mm = std::exp2(-static_cast<double>(band_level[k]));
|
mm = std::exp2(-static_cast<double>(band_level[k]));
|
||||||
static const int noblend = getenv("RT_NOBLEND") ? atoi(getenv("RT_NOBLEND")) : 0;
|
static const int noblend = getenv("RT_NOBLEND") ? atoi(getenv("RT_NOBLEND")) : 0;
|
||||||
@@ -524,17 +572,16 @@ static void process_band_structural_am(
|
|||||||
float sample_rate,
|
float sample_rate,
|
||||||
size_t num_bands = 1,
|
size_t num_bands = 1,
|
||||||
const float* casc_curve = nullptr,
|
const float* casc_curve = nullptr,
|
||||||
bool use_cascade = false
|
bool use_cascade = false,
|
||||||
|
float* track = nullptr
|
||||||
) {
|
) {
|
||||||
if (use_cascade && casc_curve) {
|
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;
|
static thread_local std::vector<float> one_res;
|
||||||
size_t nbin = nfft/2 + 1;
|
size_t nbin = nfft/2 + 1;
|
||||||
one_res.assign(nbin, 1.0f);
|
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 {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,6 +654,34 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
|
|||||||
cascade_states_.assign(bands_.size(), fn529fe0::CascadeState());
|
cascade_states_.assign(bands_.size(), fn529fe0::CascadeState());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline double eq_bell(double f, double fc, double q, double sens_db) {
|
||||||
|
if (fc < 1.0 || sens_db == 0) return 1.0;
|
||||||
|
// RBJ peaking EQ magnitude (FilterGraph) - more accurate than 1/sqrt(1+(Q*A)^2)
|
||||||
|
double w0 = 2.0 * M_PI * fc / 48000.0;
|
||||||
|
double alpha = std::sin(w0) / (2.0 * q);
|
||||||
|
double A = std::pow(10.0, sens_db / 40.0); // linear amplitude (sens is in dB, 40 = 20*2)
|
||||||
|
double cosw0 = std::cos(w0);
|
||||||
|
double cosw = std::cos(2.0 * M_PI * f / 48000.0);
|
||||||
|
// Peaking EQ magnitude squared from RBJ: |H|^2 = (1 + ...)/...
|
||||||
|
// Simplified: use classic peaking magnitude formula
|
||||||
|
double alphaA = alpha * A;
|
||||||
|
double alphaDivA = alpha / A;
|
||||||
|
double b0 = 1.0 + alphaA, b1 = -2.0*cosw0, b2 = 1.0 - alphaA;
|
||||||
|
double a0 = 1.0 + alphaDivA, a1 = -2.0*cosw0, a2 = 1.0 - alphaDivA;
|
||||||
|
// Evaluate at frequency f: z = exp(j*w), w=2pi*f/48000
|
||||||
|
double cos_w = cosw, sin_w = std::sin(2.0 * M_PI * f / 48000.0);
|
||||||
|
// Use magnitude of biquad: |H| = |b0+b1*z^-1+b2*z^-2| / |a0+a1*z^-1+a2*z^-2|
|
||||||
|
std::complex<double> z = std::exp(std::complex<double>(0, 2*M_PI*f/48000.0));
|
||||||
|
std::complex<double> z1 = 1.0 / z, z2 = z1*z1;
|
||||||
|
std::complex<double> num = b0 + b1*z1 + b2*z2;
|
||||||
|
std::complex<double> den = a0 + a1*z1 + a2*z2;
|
||||||
|
double mag = std::abs(num/den);
|
||||||
|
// Normalize to 0dB at DC? RBJ peaking is 0dB at Nyquist, gain at fc
|
||||||
|
// For detector EQ, we want bell that is 1 at far frequencies, gain at fc
|
||||||
|
// So mag is already correct (1 at far, A at fc)
|
||||||
|
return mag;
|
||||||
|
}
|
||||||
|
|
||||||
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
||||||
size_t half = nfft_ / 2;
|
size_t half = nfft_ / 2;
|
||||||
if (wsum_ == 0.0) {
|
if (wsum_ == 0.0) {
|
||||||
@@ -626,6 +701,7 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
// the slow adaptation the real plugin exhibits on sustained content.
|
// the slow adaptation the real plugin exhibits on sustained content.
|
||||||
static const int env_live = getenv("RT_ENV") ? atoi(getenv("RT_ENV")) : 0;
|
static const int env_live = getenv("RT_ENV") ? atoi(getenv("RT_ENV")) : 0;
|
||||||
|
|
||||||
|
assert(spectrum != nullptr);
|
||||||
for (size_t k = 0; k <= half; k++) {
|
for (size_t k = 0; k <= half; k++) {
|
||||||
double a_cur = 2.0 * std::abs(spectrum[k]) / wsum_;
|
double a_cur = 2.0 * std::abs(spectrum[k]) / wsum_;
|
||||||
if (env_live) {
|
if (env_live) {
|
||||||
@@ -640,6 +716,7 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
am_[k] = static_cast<float>(am);
|
am_[k] = static_cast<float>(am);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// EQ before detector moved to lvl calc in process_band_structural (not am_ state)
|
||||||
|
|
||||||
// Detector cascade 529c60: per-band pre-processor on complex twin-filtered
|
// Detector cascade 529c60: per-band pre-processor on complex twin-filtered
|
||||||
// spectrum. Computes magnitudes, Haar-smooths, applies sin-peak floor.
|
// spectrum. Computes magnitudes, Haar-smooths, applies sin-peak floor.
|
||||||
@@ -665,6 +742,7 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
// Cascade computes: |audio_spectrum × twin_response| → Haar smooth → sin-peak floor
|
// Cascade computes: |audio_spectrum × twin_response| → Haar smooth → sin-peak floor
|
||||||
// Output replaces am/res in the structural chain.
|
// Output replaces am/res in the structural chain.
|
||||||
static thread_local std::vector<float> casc_curve;
|
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) {
|
if (casc_on && nfft_ == 4096 && twin_resp_complex_.size() > b) {
|
||||||
size_t nbin = half + 1;
|
size_t nbin = half + 1;
|
||||||
std::vector<float> complex_input(2 * nbin);
|
std::vector<float> complex_input(2 * nbin);
|
||||||
@@ -689,16 +767,22 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
4, // ctx[0x1ac] = 4 (quality default)
|
4, // ctx[0x1ac] = 4 (quality default)
|
||||||
false // is_magnitude = false (input is complex)
|
false // is_magnitude = false (input is complex)
|
||||||
);
|
);
|
||||||
|
// chain_9_19: transforms level curve AFTER cascade, BEFORE VLAW law.
|
||||||
// Cascade output IS the level curve (Haar-smoothed magnitude).
|
// ACC state (track_[b]) persists across frames (FramedDetector member).
|
||||||
// Use it directly as am_ replacement — pass res=1.0 so level = am*1
|
// f6f8_ is shared IIR1 output buffer.
|
||||||
// (twin response already baked into cascade output).
|
{
|
||||||
process_band_structural_am(am_.data(), res_[b].data(), bands_[b],
|
// Cascade output IS the level curve (Haar-smoothed magnitude).
|
||||||
band_mask.data(), nfft_, sample_rate_,
|
// Use it directly as am_ replacement — pass res=1.0 so level = am*1
|
||||||
bands_.size(), casc_curve.data(), true);
|
// (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 {
|
} else {
|
||||||
process_band_structural(am_.data(), res_[b].data(), bands_[b],
|
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++) {
|
for (size_t k = 0; k <= half; k++) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <cmath>
|
||||||
#include <complex>
|
#include <complex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "fn529fe0.hpp"
|
#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);
|
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
|
// FramedDetector — C++ transcription of the real soothe2 mask-apply chain
|
||||||
|
|
||||||
// (FUN_180529fe0 mono path, 0x5408b8==0), bit-exact structure.
|
// (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
|
// 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:
|
// 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)
|
// 2. resample 44100 -> 48000 (libsamplerate, SINC best)
|
||||||
// 3. SpectralProcessor(4096, 1024, 48000) with the given bands
|
// 3. Process L and R channels (stereo link=100%: same processing for both)
|
||||||
// 4. resample 48000 -> 44100
|
// 4. Apply balance: scale reduction for R channel
|
||||||
// 5. write 24-bit output WAV (matches reference format)
|
// 5. Apply mix: wet-dry mix
|
||||||
// Usage: render48k <in.wav> <out.wav> [fc,q,sens[,scale] ...] (comma bands, like framed_test)
|
// 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 "spectral.hpp"
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -18,7 +28,7 @@
|
|||||||
|
|
||||||
static int g_in_ch = 1;
|
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");
|
FILE* f = fopen(path, "rb");
|
||||||
if (!f) return false;
|
if (!f) return false;
|
||||||
char hdr[44];
|
char hdr[44];
|
||||||
@@ -26,7 +36,6 @@ static bool load_wav(const char* path, std::vector<float>& out, int& sr) {
|
|||||||
sr = *(int*)(hdr + 24);
|
sr = *(int*)(hdr + 24);
|
||||||
int ch = *(short*)(hdr + 22);
|
int ch = *(short*)(hdr + 22);
|
||||||
int bits = *(short*)(hdr + 34);
|
int bits = *(short*)(hdr + 34);
|
||||||
// scan chunks to find data chunk size (hdr[40] may be bext/junk size)
|
|
||||||
int data = 0;
|
int data = 0;
|
||||||
int64_t pos = 12;
|
int64_t pos = 12;
|
||||||
fseek(f, 12, SEEK_SET);
|
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; }
|
if (memcmp(cid, "data", 4) == 0) { data = csize; break; }
|
||||||
pos += csize;
|
pos += csize;
|
||||||
int skip = csize;
|
int skip = csize;
|
||||||
if (csize % 2) skip++; // odd chunk size padded
|
if (csize % 2) skip++;
|
||||||
fseek(f, skip, SEEK_CUR);
|
fseek(f, skip, SEEK_CUR);
|
||||||
}
|
}
|
||||||
if (!data) { fclose(f); return false; }
|
if (!data) { fclose(f); return false; }
|
||||||
int n = data / (ch * (bits / 8));
|
int n = data / (ch * (bits / 8));
|
||||||
g_in_ch = ch;
|
g_in_ch = ch;
|
||||||
out.resize(n);
|
channels = 2;
|
||||||
|
out.resize(n * 2);
|
||||||
if (bits == 16) {
|
if (bits == 16) {
|
||||||
std::vector<short> raw(n * ch);
|
std::vector<short> raw(n * ch);
|
||||||
fread(raw.data(), 2, n * ch, f);
|
fread(raw.data(), 2, n * ch, f);
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
long long v = 0;
|
float v = 0.0f;
|
||||||
for (int c = 0; c < ch; c++) v += raw[i * ch + c];
|
if (ch == 1) {
|
||||||
out[i] = (float)((v / ch) / 32768.0);
|
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) {
|
} else if (bits == 24) {
|
||||||
std::vector<unsigned char> raw(n * ch * 3);
|
std::vector<unsigned char> raw(n * ch * 3);
|
||||||
fread(raw.data(), 1, n * ch * 3, f);
|
fread(raw.data(), 1, n * ch * 3, f);
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
long long v = 0;
|
auto read24 = [&](int idx) -> float {
|
||||||
for (int c = 0; c < ch; c++) {
|
int32_t s = (raw[idx] | (raw[idx+1] << 8) | (raw[idx+2] << 16));
|
||||||
int idx = (i * ch + c) * 3;
|
|
||||||
int32_t s = (raw[idx] | (raw[idx + 1] << 8) | (raw[idx + 2] << 16));
|
|
||||||
if (s & 0x800000) s |= 0xFF000000;
|
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;
|
} else return false;
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return true;
|
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");
|
FILE* f = fopen(path, "wb");
|
||||||
if (!f) return false;
|
if (!f) return false;
|
||||||
int ch = 2, bits = 24;
|
int ch = 2, bits = 24;
|
||||||
// x is already stereo interleaved (size = mono_samples * 2)
|
size_t n = std::min(L.size(), R.size());
|
||||||
int data = (int)(x.size() * (bits / 8));
|
int data = (int)(n * ch * (bits / 8));
|
||||||
char hdr[44]; memset(hdr, 0, 44);
|
char hdr[44]; memset(hdr, 0, 44);
|
||||||
memcpy(hdr, "RIFF", 4); *(int*)(hdr + 4) = 36 + data;
|
memcpy(hdr, "RIFF", 4); *(int*)(hdr + 4) = 36 + data;
|
||||||
memcpy(hdr + 8, "WAVE", 4); memcpy(hdr + 12, "fmt ", 4);
|
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;
|
*(short*)(hdr + 32) = (short)ch; *(short*)(hdr + 34) = (short)bits;
|
||||||
memcpy(hdr + 36, "data", 4); *(int*)(hdr + 40) = data;
|
memcpy(hdr + 36, "data", 4); *(int*)(hdr + 40) = data;
|
||||||
fwrite(hdr, 1, 44, f);
|
fwrite(hdr, 1, 44, f);
|
||||||
for (size_t i = 0; i < x.size(); i++) {
|
for (size_t i = 0; i < n; i++) {
|
||||||
int32_t v = (int32_t)(std::max(-1.0f, std::min(1.0f, x[i])) * 8388607.0f);
|
int32_t vl = (int32_t)(std::max(-1.0f, std::min(1.0f, L[i])) * 8388607.0f);
|
||||||
unsigned char b0 = v & 0xFF, b1 = (v >> 8) & 0xFF, b2 = (v >> 16) & 0xFF;
|
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);
|
fwrite(&b0, 1, 1, f); fwrite(&b1, 1, 1, f); fwrite(&b2, 1, 1, f);
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return true;
|
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;
|
double frac = (double)dst_sr / src_sr;
|
||||||
int out_len = (int)(in.size() * frac) + 16;
|
int out_len = (int)(in.size() * frac) + 16;
|
||||||
std::vector<float> buf(out_len);
|
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;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
static std::vector<float> resample_stereo(const std::vector<float>& in, int src_sr, int dst_sr) {
|
||||||
if (argc < 3) { fprintf(stderr, "usage: %s in.wav out.wav [fc,q,sens[,scale] ...]\n", argv[0]); return 1; }
|
size_t n = in.size() / 2;
|
||||||
std::vector<float> x; int sr;
|
double frac = (double)dst_sr / src_sr;
|
||||||
if (!load_wav(argv[1], x, sr)) { fprintf(stderr, "cannot load %s\n", argv[1]); return 1; }
|
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;
|
std::vector<DetectorBand> bands;
|
||||||
for (int i = 3; i < argc; i++) {
|
for (int i = 3; i < argc; i++) {
|
||||||
if (!strchr(argv[i], ',')) continue;
|
if (!strchr(argv[i], ',')) continue;
|
||||||
float fc, q, sens, scl = 1.0f;
|
float fc, q, sens, scl = 1.0f;
|
||||||
if (sscanf(argv[i], "%f,%f,%f,%f", &fc, &q, &sens, &scl) < 3) continue;
|
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);
|
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;
|
if (x48.empty()) return 1;
|
||||||
|
|
||||||
SpectralProcessor sp(4096, 1024, 48000.0f);
|
size_t n = x48.size() / 2;
|
||||||
sp.setDetectorParams(bands);
|
|
||||||
std::vector<float> y48(x48.size());
|
// 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;
|
const size_t BLK = 1 << 16;
|
||||||
std::vector<float> inb(BLK), outb(BLK);
|
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
|
// Process L channel
|
||||||
std::vector<float> yst(y.size() * 2);
|
for (size_t s = 0; s < n; s += BLK) {
|
||||||
for (size_t i = 0; i < y.size(); i++) { yst[i * 2] = y[i]; yst[i * 2 + 1] = y[i]; }
|
size_t blk = std::min(BLK, n - s);
|
||||||
save_wav24(argv[2], yst, 44100);
|
memcpy(inb.data(), x48.data() + 2*s, blk * sizeof(float));
|
||||||
printf("render48k: %zu hostsamps -> %zu (48k) -> %zu (out), %zu bands\n",
|
for (size_t i = blk; i < BLK; i++) inb[i] = 0.0f;
|
||||||
x.size(), x48.size(), y.size(), bands.size());
|
procL.processBlock(inb.data(), outb.data(), BLK, 1);
|
||||||
(void)g_in_ch;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
// DIVIDE 1803a06a0 tables from .rdata RVA 0x21269c0/0x2126a00 poly 0.207
|
||||||
|
// BLOCKMAP:580 vpermps+poly 1c dump
|
||||||
|
// extracted via PE .rdata .rdata VA 0x1baa000 raw 0x1ba8400
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
namespace rt_div {
|
||||||
|
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,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)
|
SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate)
|
||||||
: nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0),
|
: nfft_(nfft), hop_(hop), frame_count_(0), output_pos_(0),
|
||||||
detector_(nfft, sample_rate) {
|
detector_(nfft, sample_rate) {
|
||||||
window_ = new double[nfft_];
|
window_.resize(nfft_);
|
||||||
computeWindow();
|
computeWindow();
|
||||||
fft::init_plan(&plan_, static_cast<uint32_t>(std::log2(nfft_)));
|
fft::init_plan(&plan_, static_cast<uint32_t>(std::log2(nfft_)));
|
||||||
buf_ = new std::complex<double>[nfft_];
|
buf_.resize(nfft_);
|
||||||
tmp_buf_ = new std::complex<double>[nfft_];
|
tmp_buf_.resize(nfft_);
|
||||||
fir_buf_ = new std::complex<double>[nfft_];
|
fir_buf_.resize(nfft_);
|
||||||
fir_freq_ = new std::complex<double>[nfft_];
|
fir_freq_.resize(nfft_);
|
||||||
overlap_.resize(nfft_, 0.0f);
|
overlap_.resize(nfft_, 0.0f);
|
||||||
mask_.resize(nfft_, 1.0f);
|
mask_.resize(nfft_, 1.0f);
|
||||||
|
|
||||||
@@ -30,13 +30,7 @@ SpectralProcessor::SpectralProcessor(size_t nfft, size_t hop, float sample_rate)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SpectralProcessor::~SpectralProcessor() {
|
SpectralProcessor::~SpectralProcessor() = default;
|
||||||
delete[] window_;
|
|
||||||
delete[] buf_;
|
|
||||||
delete[] tmp_buf_;
|
|
||||||
delete[] fir_buf_;
|
|
||||||
delete[] fir_freq_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpectralProcessor::setDetectorParams(const std::vector<DetectorBand>& bands) {
|
void SpectralProcessor::setDetectorParams(const std::vector<DetectorBand>& bands) {
|
||||||
detector_.setParams(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) {
|
void SpectralProcessor::istftFrame(std::complex<double>* in, float* out, float* overlap) {
|
||||||
memcpy(tmp_buf_, in, nfft_ * sizeof(std::complex<double>));
|
memcpy(tmp_buf_.data(), in, nfft_ * sizeof(std::complex<double>));
|
||||||
fft::execute_inverse(&plan_, tmp_buf_);
|
fft::execute_inverse(&plan_, tmp_buf_.data());
|
||||||
static bool wola_computed = false;
|
static bool wola_computed = false;
|
||||||
static float wola_norm = 1.0f;
|
static float wola_norm = 1.0f;
|
||||||
// RT_SYN: 0=synthesis window = analysis window (WOLA), 1=none
|
// 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++) {
|
for (size_t i = half + 1; i < nfft; i++) {
|
||||||
time_domain[i] = 0.0; // xmm9 = 0 zeros upper half
|
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
|
// Step 4: opB = fwd-RFFT (th1a90): time_domain (real) → complex
|
||||||
std::vector<std::complex<double>> freq_domain(half + 1);
|
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++) {
|
for (size_t f = 0; f < nframes; f++) {
|
||||||
size_t offset = f * hop_;
|
size_t offset = f * hop_;
|
||||||
if (offset + nfft_ > num_samples) break;
|
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) {
|
if (firconv == 3) {
|
||||||
// RT_FIRCONV=3 (NOTES 24k): plugin application law decoded live:
|
// 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).
|
// RT_FIRCONV=2: Full FIR construction pipeline (52b550-52b8bb).
|
||||||
// mask → reciprocal (1/mask) → window → normalize → complex multiply.
|
// mask → reciprocal (1/mask) → window → normalize → complex multiply.
|
||||||
// This replicates the plugin's FFT-conv FIR design path.
|
// 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
|
// Complex multiply FIR × audio spectrum
|
||||||
for (size_t i = 0; i < nfft_; i++) {
|
for (size_t i = 0; i < nfft_; i++) {
|
||||||
buf_[i] *= fir_freq_[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:
|
private:
|
||||||
size_t nfft_;
|
size_t nfft_;
|
||||||
size_t hop_;
|
size_t hop_;
|
||||||
double* window_;
|
std::vector<double> window_;
|
||||||
FFTPlan plan_;
|
FFTPlan plan_;
|
||||||
std::complex<double>* buf_;
|
std::vector<std::complex<double>> buf_;
|
||||||
std::complex<double>* tmp_buf_;
|
std::vector<std::complex<double>> tmp_buf_;
|
||||||
std::complex<double>* fir_buf_;
|
std::vector<std::complex<double>> fir_buf_;
|
||||||
std::complex<double>* fir_freq_;
|
std::vector<std::complex<double>> fir_freq_;
|
||||||
std::vector<double> fir_window_;
|
std::vector<double> fir_window_;
|
||||||
std::vector<float> overlap_;
|
std::vector<float> overlap_;
|
||||||
std::vector<float> mask_;
|
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).**
|
> **Архив 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) (единственный навигатор).
|
> Оглавление по датам/темам → [`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)
|
- w ≈ 0.015 (scalar, НЕ 0.977)
|
||||||
- Каскад STATEFUL: bands_curve сохраняется между кадрами
|
- Каскад STATEFUL: bands_curve сохраняется между кадрами
|
||||||
- 5407a8 = accumulator, НЕ нулевой при рекуррентности
|
- 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 — оглавление журнала
|
||||||
|
|
||||||
> **Навигатор:** `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`)
|
## Живая голова (`handoff/NOTES_LEVEL.md`)
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
| 24mm12 | Детекторный каскад vt+0x28=180529c60 | Оркестратор 5300f0, vtable карта, 0x281 байт |
|
| 24mm12 | Детекторный каскад vt+0x28=180529c60 | Оркестратор 5300f0, vtable карта, 0x281 байт |
|
||||||
| 24mm13 (+доп) | Хелперы каскада 529c60 | 5355d0→16140, 530080, 20f0/1850/1a00, рекуррентия Haar |
|
| 24mm13 (+доп) | Хелперы каскада 529c60 | 5355d0→16140, 530080, 20f0/1850/1a00, рекуррентия Haar |
|
||||||
| 24mm14 | Каскад декодирован — 3 фазы | `|z|` → Haar×2 → peak/sin/w/blend 5407a8 |
|
| 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`) — по периодам
|
## Архив (`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
|
||||||
|
}
|
||||||
@@ -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": 1.1724811230541312,
|
||||||
|
"t1kq_800": 0.6775178072048177,
|
||||||
|
"t1kq_900": 0.54236470368085,
|
||||||
|
"t1kq_950": 0.026290891468426707
|
||||||
|
}
|
||||||
+54
-1
@@ -42,12 +42,65 @@ def k_exp(x):
|
|||||||
return np.exp(x)
|
return np.exp(x)
|
||||||
|
|
||||||
|
|
||||||
|
def k_exp_exact(x):
|
||||||
|
"""Bit-exact expf 180296c80 (BLOCKMAP:569) — float32 FMA poly, guard 87.3365.
|
||||||
|
Python структурный прокси: np.exp (float64) — точный C++ порт в dsp/exp2.cpp
|
||||||
|
через fmaf+бит-манипуляции (1824c...), ошибка <0.5 ulp vs плагин."""
|
||||||
|
return np.exp(np.asarray(x, dtype=np.float64)).astype(np.float32)
|
||||||
|
|
||||||
|
|
||||||
def k_div(a, b):
|
def k_div(a, b):
|
||||||
"""divide-ядро 1803a06a0: dst = B/A (~0.5 ulp, rcp+таблицы+полином).
|
"""divide-ядро 1803a06a0: dst = B/A (~0.5 ulp, rcp+таблицы+полином).
|
||||||
Структурная фаза: точное деление."""
|
Структурная фаза: точное деление."""
|
||||||
return b / a
|
return b / a
|
||||||
|
|
||||||
|
|
||||||
|
def k_div_exact(a, b):
|
||||||
|
"""Bit-exact DIVIDE 1803a06a0 (BLOCKMAP:580) — rcp+quant+vpermps+poly.
|
||||||
|
Структурный прокси: точное деление; C++ порт копирует 0.5ulp полином
|
||||||
|
0.207515 -0.241687 0.288535 -0.360671 ... 0.240264 через vpermps tbl@1821269c0."""
|
||||||
|
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):
|
def load_tract(path):
|
||||||
"""tract_*.txt: k am res lvl_raw band_level prewarp w"""
|
"""tract_*.txt: k am res lvl_raw band_level prewarp w"""
|
||||||
@@ -116,7 +169,7 @@ def win_periodic_hann(N):
|
|||||||
# ------------------------------------------------ FIR-цепь (24mm9) --------
|
# ------------------------------------------------ FIR-цепь (24mm9) --------
|
||||||
NFRAME = 4096 # n=[ctx+0x540534]
|
NFRAME = 4096 # n=[ctx+0x540534]
|
||||||
NBINS_FIR = NFRAME // 2 + 1
|
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():
|
def winfreq_fall():
|
||||||
|
|||||||
@@ -176,6 +176,25 @@ def main():
|
|||||||
store['lut_G'] = struct.unpack('<f', fb4[12:16])[0]
|
store['lut_G'] = struct.unpack('<f', fb4[12:16])[0]
|
||||||
store['lut_mode'] = fb4[16]
|
store['lut_mode'] = fb4[16]
|
||||||
for off in SLOTS[1:]:
|
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)
|
q = rd(ctx + off, 8)
|
||||||
if not q:
|
if not q:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ BP_CIN = 0x180529c60
|
|||||||
BP_COUT = 0x180529ee1
|
BP_COUT = 0x180529ee1
|
||||||
BP_AIN = 0x180016140
|
BP_AIN = 0x180016140
|
||||||
BP_AOUT = 0x18000332c
|
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,
|
CTX_SLOTS = {'scr': 0x540628, 'trk': 0x540688, 'cur': 0x540678,
|
||||||
'fir_ptr': 0x540668}
|
'fir_ptr': 0x540668}
|
||||||
|
|
||||||
@@ -281,7 +286,12 @@ def main():
|
|||||||
('DIV', BP_DIV), ('DC40', BP_DC40),
|
('DIV', BP_DIV), ('DC40', BP_DC40),
|
||||||
('EXPVAR', BP_EXPVAR), ('FN', BP_FN),
|
('EXPVAR', BP_EXPVAR), ('FN', BP_FN),
|
||||||
('CIN', BP_CIN), ('COUT', BP_COUT),
|
('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):
|
if not mapped(addr):
|
||||||
print('!! %s@%#x не смапплен — пропуск' % (nm_ := name, addr), flush=True)
|
print('!! %s@%#x не смапплен — пропуск' % (nm_ := name, addr), flush=True)
|
||||||
continue
|
continue
|
||||||
@@ -299,9 +309,11 @@ def main():
|
|||||||
samples = []
|
samples = []
|
||||||
hits = {'COPY': 0, 'EXP': 0, 'DF0': 0, 'DF0RET': 0, 'TRACKSAVE': 0,
|
hits = {'COPY': 0, 'EXP': 0, 'DF0': 0, 'DF0RET': 0, 'TRACKSAVE': 0,
|
||||||
'DIV': 0, 'DC40': 0, 'EXPVAR': 0, 'FN': 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_by_tid = {}
|
||||||
track_dumps = []
|
track_dumps = []
|
||||||
|
chain_dumps = []
|
||||||
regs_by_tid = {}
|
regs_by_tid = {}
|
||||||
t_start = time.time()
|
t_start = time.time()
|
||||||
|
|
||||||
@@ -410,8 +422,35 @@ def main():
|
|||||||
continue
|
continue
|
||||||
if kind == 'DF0':
|
if kind == 'DF0':
|
||||||
track_by_tid[pid] = regs.rdx
|
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'):
|
if kind in ('CIN','COUT'):
|
||||||
key='cin_%d'%pid if kind=='CIN' else 'cout_%d'%pid
|
|
||||||
if kind=='CIN':
|
if kind=='CIN':
|
||||||
regs_by_tid[pid]=dict(rdx=regs.rdx,r12=regs.r12,
|
regs_by_tid[pid]=dict(rdx=regs.rdx,r12=regs.r12,
|
||||||
rcx=regs.rcx)
|
rcx=regs.rcx)
|
||||||
@@ -632,7 +671,7 @@ def main():
|
|||||||
snap_ptrs[nm] = p
|
snap_ptrs[nm] = p
|
||||||
snap_arr[nm] = rd_f32(p, 4100)
|
snap_arr[nm] = rd_f32(p, 4100)
|
||||||
with open(os.path.join(outdir, 'chain_samples.pkl'), 'wb') as f:
|
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)
|
np.savez_compressed(os.path.join(outdir, 'ctx_snap.npz'), **snap_arr)
|
||||||
print('saved %d -> %s' % (len(samples), outdir), flush=True)
|
print('saved %d -> %s' % (len(samples), outdir), flush=True)
|
||||||
for _ in range(600):
|
for _ in range(600):
|
||||||
|
|||||||
Reference in New Issue
Block a user