Compare commits
16
Commits
f1f06765f7
...
16853d7e0b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16853d7e0b | ||
|
|
2d69b85aa3 | ||
|
|
d802ee7aed | ||
|
|
4e9c1b1ed7 | ||
|
|
fa1ef205df | ||
|
|
1cfe013939 | ||
|
|
cea562b511 | ||
|
|
4014133d57 | ||
|
|
067d483f15 | ||
|
|
cc52461903 | ||
|
|
9a26532c60 | ||
|
|
94a6749c9a | ||
|
|
da63adc82c | ||
|
|
f8ecf6a1af | ||
|
|
8f64539328 | ||
|
|
ddf2ac2050 |
@@ -52,29 +52,30 @@ err = db(ta(out,1000)/ta(ref,1000)) # err в dB, цель <0.1
|
|||||||
|
|
||||||
## Текущее состояние (2026-08-20, P4)
|
## Текущее состояние (2026-08-20, P4)
|
||||||
|
|
||||||
**Декодировано и транскрибировано в `dsp/framed_model.cpp`** (mask-apply цепь
|
**Реализованная модель в `dsp/framed_model.cpp`** — BRIDGE-канон (NOTES:147, honest):
|
||||||
`FUN_180529fe0`, mono-path 0x5408b8==0):
|
|
||||||
```
|
```
|
||||||
level=am*res*level_scale -> scale(0x540870*0x54088c/0x1a0)
|
am_k: smoothed amp (2|X|/wsum, att~11ms/trel~80ms)
|
||||||
-> IIR1 leaky(A1/B1) -> IIR2(A2/B2) -> mask=exp2(-mask)*blend (bigkernel 0x26b820)
|
xv = log10(am_k / res_k) res_k = |2B/A| twin (min@band centre)
|
||||||
-> combine/acc(0x5407c8) -> warp(mask*=0x540768, *=warp) -> IIR3(A3/B3)x2
|
C = G·LUT(xv) + W·warp(f_k)^A G/W/A = 0.9963/0.3335/0.9807 (fit)
|
||||||
-> dry/wet(mask*(fVar30*0x540888)+(1-fVar30)) -> [FFT-conv]
|
gain_k = (1−C) · res_k^rp(Q) rp(Q) = 0.0275·Q^0.2159
|
||||||
|
LUT = monotone cubic (Fritsch–Carlson) через joint-fit узлы (al_* + B.11 anchors)
|
||||||
```
|
```
|
||||||
- **bigkernel 0x26b820 = векторизованный exp2** (log2e=1.4424, floor −708.9, N-таблицы);
|
- **METRIC CRITICAL**: рефы 24-bit НЕЛЬЗЯ читать 16-bit кодеком (даёт phantom −53 dB);
|
||||||
семантика залочена: `mask = exp2(-level) * blend`, blend=0.8 при mix=1.0.
|
`render_parity.load` (sw handling) + окно 3.5s — канон. dual honest ref = −10.2 dB FLAT.
|
||||||
- **Level-tracker = би-направленный leaky-IIR** `y=A[i]*acc+B[i]*x`, B=1−A
|
- **Broadband-hypothesis ОПРОВЕРГНУТА** (NOTES:2026-08-20s): редукция per-bin (tone-бины
|
||||||
(НЕ per-bin независимый трекер).
|
режутся, пустые bin ~1.0);
|
||||||
- **Live-таблицы** в `dsp/rt_mask_tables.{hpp,cpp}`: kIIR_A1/B1..A3/B3 (2049 doubles),
|
- **Честные результаты (24-bit метрика, окно 3.5s, trimmed)**: dual (band fc=500,
|
||||||
kWarp (8193), kBand768, kPRNGLut. `dsp/rt_weights.{hpp,cpp}` = acc коды весов.
|
q-sweep) err ≤0.72; t1kq fc-scan (only1 800..1200) err ≤0.59; al_* lv3-12 err ≤0.43;
|
||||||
- **Результаты**: t1kq err −0.43 dB (scale=600); comb per-tone err ≤6.4 dB.
|
полный корпус mean|err| 0.77, max 2.12 (t1k_b1f loud +2.1, al lv18/24 low-level −1.8).
|
||||||
|
- Эмпирика (G/W/A/rp/LUT-узлы) ЯВНО помечена в коде и NOTES; структурные лэкеры ниже.
|
||||||
|
|
||||||
### Открытые bit-exact пробелы (см. NOTES_LEVEL.md)
|
### Открытые bit-exact/структурные пробелы (см. NOTES_LEVEL.md)
|
||||||
1. PRNG-пролог (LCG+LUT → fVar30), per-frame рандомизация scale/dry-wet.
|
1. `combine/аккумулятор 0x5407c8` (аддитивные веса 0x5406c8/6e8) + `FFT-conv 0x535a70`
|
||||||
2. FFT-conv (0x535a70) сглаживание маски перед FIR.
|
(сглаживание маски) — НЕ замкнуты в цепь; структурно могут закрыть t1k_b1f/al хвосты.
|
||||||
3. Бит-экзактный exp2 (0x26b820) вместо std::exp2.
|
2. BandConfig `ctx+0x188` A/B/γ (параметрический LUT 0x563a60) — не live-захвачен;
|
||||||
4. combine/аккумулятор 0x5407c8 точная обратная связь.
|
писатели = JUCE param plumbing (статически исчерпаны).
|
||||||
5. **SR-mismatch**: внутренний DSP SR=48000/N=4096 (спейс 11.713 Гц) против
|
3. PRNG-пролог (LCG+LUT → fVar30) — залочен (fVar30=1 при live state 112), dry/wet rnd импорт.
|
||||||
хоста 44100/2048; twin per-bin IIR статически невидим (FUN_180535880 tail-call).
|
4. Бит-экзактный exp2 (0x26b820) вместо std::exp2; SR-mismatch 48000/4096 vs 44100/2048.
|
||||||
|
|
||||||
## Структура ключевых файлов
|
## Структура ключевых файлов
|
||||||
- `dsp/framed_model.{cpp,hpp}` — C++ порт mask-apply цепи (ГЛАВНЫЙ активный файл).
|
- `dsp/framed_model.{cpp,hpp}` — C++ порт mask-apply цепи (ГЛАВНЫЙ активный файл).
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ add_library(soothe2_dsp SHARED
|
|||||||
phase_table.cpp
|
phase_table.cpp
|
||||||
fftconv.cpp
|
fftconv.cpp
|
||||||
vlog.cpp
|
vlog.cpp
|
||||||
|
exp2_tables.cpp
|
||||||
|
exp2.cpp
|
||||||
leveltrack.cpp
|
leveltrack.cpp
|
||||||
framed_model.cpp
|
framed_model.cpp
|
||||||
rt_weights.cpp
|
rt_weights.cpp
|
||||||
@@ -34,8 +36,10 @@ add_executable(fftconv_check fftconv_check.cpp)
|
|||||||
add_executable(vlog_check vlog_check.cpp)
|
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)
|
||||||
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(exp2_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)
|
||||||
target_link_libraries(vlog_check soothe2_dsp)
|
target_link_libraries(vlog_check soothe2_dsp)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// exp2.cpp — numerical double exp2 (wiring fallback) + P3 asset note.
|
||||||
|
// The plugin's table-driven path (0x18026b820: kExp2_* irr tables + vfmadd213sd +
|
||||||
|
// Cody-Waite hi/lo) is bit-exact-remaining; this module provides the correct
|
||||||
|
// function value for structural wiring until the 1:1 transcription lands.
|
||||||
|
#include "exp2.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace exp2d {
|
||||||
|
|
||||||
|
double exp2_dsp(double x) {
|
||||||
|
if (std::isnan(x)) return x;
|
||||||
|
if (x == 0.0) return 1.0;
|
||||||
|
if (x == -std::numeric_limits<double>::infinity()) return 0.0;
|
||||||
|
if (x == std::numeric_limits<double>::infinity()) return x;
|
||||||
|
return std::exp2(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace exp2d
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
// Scalar double exp2 — structural sketch of the dump function at 0x18026b820
|
||||||
|
// (element kernel wrapped by bigkernel 0x18026c220). NOTES_LEVEL:854-860.
|
||||||
|
//
|
||||||
|
// NUMERIC STATUS: exp2_dsp() is a numerically-correct double exp2 (agrees with
|
||||||
|
// std::exp2 within ~1e-13 rel) used for wiring/tests NOW. BIT-EXACT parity with
|
||||||
|
// the plugin's table-driven path (8x16 irr tables kExp2_* + vfmadd213sd chain,
|
||||||
|
// Cody-Waite hi/lo splits) is P3 REMAINING: the tables are captured bit-exact
|
||||||
|
// (dsp/exp2_tables.*), the algorithm wiring is not yet transcribed 1:1.
|
||||||
|
namespace exp2d {
|
||||||
|
|
||||||
|
// 2^x. Numerically correct; matches std::exp2 for all finite x.
|
||||||
|
double exp2_dsp(double x);
|
||||||
|
|
||||||
|
} // namespace exp2d
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
// exp2_check.cpp — numeric gate for the exp2_dsp transcription + P3 asset check.
|
||||||
|
// 1) exp2_dsp vs std::exp2 over a dense grid (should agree within ~1-2 ULP for the
|
||||||
|
// dominant path — this is the achievable ceiling until the irr tables are wired).
|
||||||
|
// 2) sanity-print of the extracted table headers (bit-exact P3 inputs present).
|
||||||
|
#include "exp2.hpp"
|
||||||
|
#include "exp2_tables.hpp"
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
static double rel_err(double a, double b) {
|
||||||
|
return std::fabs(a - b) / std::max(std::fabs(b), 1e-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// table sanity
|
||||||
|
printf("kExp2_big[0..3] = %0.17g %0.17g %0.17g %0.17g\n",
|
||||||
|
kExp2_big[0], kExp2_big[1], kExp2_big[2], kExp2_big[3]);
|
||||||
|
printf("kExp2_f2f4e0[0..3] = %0.17g %0.17g %0.17g %0.17g\n",
|
||||||
|
kExp2_f2f4e0[0], kExp2_f2f4e0[1], kExp2_f2f4e0[2], kExp2_f2f4e0[3]);
|
||||||
|
|
||||||
|
// dense grid on [-1074, 1023]
|
||||||
|
double max_rel = 0.0, maxx = 0.0;
|
||||||
|
int bad = 0;
|
||||||
|
std::mt19937_64 rng(42);
|
||||||
|
std::uniform_real_distribution<double> u(-1074.0, 1023.999);
|
||||||
|
for (int i = 0; i < 2000000; i++) {
|
||||||
|
double x = u(rng);
|
||||||
|
double a = exp2d::exp2_dsp(x);
|
||||||
|
double b = std::exp2(x);
|
||||||
|
double e = rel_err(a, b);
|
||||||
|
if (e > max_rel) { max_rel = e; maxx = x; }
|
||||||
|
if (e > 1e-13) bad++;
|
||||||
|
}
|
||||||
|
// edge grid
|
||||||
|
double edges[] = {0.0, -0.0, 1.0, -1.0, 10.0, -10.0, 1023.0, -1073.0,
|
||||||
|
512.0, -512.0, 0.5, -0.5, 1e-3, -1e-3};
|
||||||
|
for (double x : edges) {
|
||||||
|
double a = exp2d::exp2_dsp(x), b = std::exp2(x);
|
||||||
|
if (rel_err(a, b) > 1e-12) { printf("edge fail %.17g: got %.17g want %.17g\n", x, a, b); bad++; }
|
||||||
|
}
|
||||||
|
printf("exp2 check: max_rel=%.3e @x=%.3f ; cells >1e-13: %d\n", max_rel, maxx, bad);
|
||||||
|
printf(bad == 0 ? "PASS (dominant-path numeric parity w/ std::exp2)\n"
|
||||||
|
: "FAIL\n");
|
||||||
|
return bad == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#include "exp2_tables.hpp"
|
||||||
|
|
||||||
|
const double kExp2_big[16] = {-708.4496630450985, -1.684386341407621e-09, -708.4515131843864, -1.6846944146916641e-09, -708.45335990698, -1.6842043988210445e-09, -708.4552032254742, -1.6843549704258747e-09, -708.4570431523962, -1.6843595268906607e-09, -708.4588797002034, -1.6844454361914893e-09, -708.460712881285, -1.6845411315873754e-09, -708.4625427079618, -1.6847775654777725e-09};
|
||||||
|
const double kExp2_f2f4e0[16] = {
|
||||||
|
1.4428269863128662, 1.4428050518035889,
|
||||||
|
1.4427828788757324, 1.442760944366455,
|
||||||
|
1.4427390098571777, 1.4427168369293213,
|
||||||
|
1.442694902420044, 0.0,
|
||||||
|
0.0, 4.4108115616836585e-05,
|
||||||
|
1.1367896310043682e-14, 8.797914665592543e-05,
|
||||||
|
1.5988983620902337e-14, 0.0001320899521033425,
|
||||||
|
1.4296626333272017e-13, 0.000176202106558776,
|
||||||
|
};
|
||||||
|
const double kExp2_f2f7f8[16] = {
|
||||||
|
0.0020239239952388743, 1.8741497305441306e-13,
|
||||||
|
0.002067855273025998, 7.192750688017651e-14,
|
||||||
|
0.0021117878884524544, 1.0220676705319255e-13,
|
||||||
|
0.002155721841745617, 1.3235740370168912e-13,
|
||||||
|
0.0021996571331328596, 1.6491510884591246e-14,
|
||||||
|
0.0022435937623868085, 6.347597838479135e-14,
|
||||||
|
0.0022875317297348374, 1.2743779084316537e-13,
|
||||||
|
0.00233147103540432, 6.251137955211938e-14,
|
||||||
|
};
|
||||||
|
const double kExp2_f2f5e8[16] = {
|
||||||
|
0.0005723181491248397, 1.376549281185315e-13,
|
||||||
|
0.0006162052457057143, 1.6759446167811947e-13,
|
||||||
|
0.000660332205143277, 1.6967863464240126e-13,
|
||||||
|
0.0007042219792765536, 1.7632410214517053e-13,
|
||||||
|
0.0007483516310458072, 9.18589132069676e-14,
|
||||||
|
0.0007922440829588595, 1.745659100765534e-13,
|
||||||
|
0.0008363764272871776, 1.0471272193277231e-13,
|
||||||
|
0.000880271557434753, 1.3552297619973825e-13,
|
||||||
|
};
|
||||||
|
const double kExp2_f2f900[16] = {
|
||||||
|
-1.222848299709874e-13, -0.0014317083230253047,
|
||||||
|
-6.872760839443708e-15, -0.001409557215993118,
|
||||||
|
-1.6088057051259155e-13, -0.0013876439584237232,
|
||||||
|
-1.5027471116381911e-13, -0.001365492174954852,
|
||||||
|
-1.6218976050293882e-13, -0.0013435782479973568,
|
||||||
|
-2.1550458851811851e-13, -0.0013214257880918012,
|
||||||
|
-4.3995053663865014e-14, -0.0012995111917462054,
|
||||||
|
-1.203413000559803e-13, -0.001277358054949218,
|
||||||
|
};
|
||||||
|
const double kExp2_f2ff18[16] = {
|
||||||
|
1.182784710984341, 1.542975430079076e-17,
|
||||||
|
1.189207115002721, 3.982015231465646e-17,
|
||||||
|
1.1956643920398273, 4.6166036704814814e-17,
|
||||||
|
1.202156731452703, 6.6449814992523e-17,
|
||||||
|
1.2086843236265816, -4.746725945228984e-17,
|
||||||
|
1.215247359980469, -7.712630692681487e-17,
|
||||||
|
1.2218460329727576, -1.061102121140269e-16,
|
||||||
|
1.22848053610687, -1.8987816313025296e-17,
|
||||||
|
};
|
||||||
|
const double kExp2_f2ff20[16] = {
|
||||||
|
1.542975430079076e-17, 1.189207115002721,
|
||||||
|
3.982015231465646e-17, 1.1956643920398273,
|
||||||
|
4.6166036704814814e-17, 1.202156731452703,
|
||||||
|
6.6449814992523e-17, 1.2086843236265816,
|
||||||
|
-4.746725945228984e-17, 1.215247359980469,
|
||||||
|
-7.712630692681487e-17, 1.2218460329727576,
|
||||||
|
-1.061102121140269e-16, 1.22848053610687,
|
||||||
|
-1.8987816313025296e-17, 1.2351510639369334,
|
||||||
|
};
|
||||||
|
const double kExp2_f2fb10[16] = {
|
||||||
|
-9.17010025169853e-14, -0.0007045933023164253,
|
||||||
|
-8.15362723390069e-14, -0.0006826693343100487,
|
||||||
|
-3.361971520911895e-14, -0.0006605067235341266,
|
||||||
|
-1.2931671548024404e-13, -0.0006385820854575286,
|
||||||
|
-1.7298500585521957e-13, -0.000616418797562801,
|
||||||
|
-1.0577760184105114e-13, -0.0005944934894159815,
|
||||||
|
-2.0008148363066578e-13, -0.0005725678481667273,
|
||||||
|
-1.8264405520037802e-13, -0.0005504035461854073,
|
||||||
|
};
|
||||||
|
const double kExp2_f30f88[16] = {
|
||||||
|
-1.6843595268906607e-09, -708.4588797002034,
|
||||||
|
-1.6844454361914893e-09, -708.460712881285,
|
||||||
|
-1.6845411315873754e-09, -708.4625427079618,
|
||||||
|
-1.6847775654777725e-09, -708.4643691924884,
|
||||||
|
-1.6841660943615788e-09, -708.4661923470494,
|
||||||
|
-1.6847288026066712e-09, -708.4680121837664,
|
||||||
|
-1.6847104098547488e-09, -708.469828714693,
|
||||||
|
-1.684518673135394e-09, -708.4716419518172,
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
// Bit-exact irrational tables of the soothe2 scalar exp2 (0x18026b820),
|
||||||
|
// extracted from soothe_mem.bin (VA-linear: file=RVA=VA-0x180000000).
|
||||||
|
// 8 tables x 16 doubles; interleaved (value, correction) pairs feeding the
|
||||||
|
// vfmadd213sd poly chain. P3 bit-exact transcription input.
|
||||||
|
extern const double kExp2_big[16];
|
||||||
|
extern const double kExp2_f2f4e0[16];
|
||||||
|
extern const double kExp2_f2f7f8[16];
|
||||||
|
extern const double kExp2_f2f5e8[16];
|
||||||
|
extern const double kExp2_f2f900[16];
|
||||||
|
extern const double kExp2_f2ff18[16];
|
||||||
|
extern const double kExp2_f2ff20[16];
|
||||||
|
extern const double kExp2_f2fb10[16];
|
||||||
|
extern const double kExp2_f30f88[16];
|
||||||
|
|
||||||
+77
-88
@@ -2,6 +2,7 @@
|
|||||||
#include "twin.hpp"
|
#include "twin.hpp"
|
||||||
#include "freqpath.hpp"
|
#include "freqpath.hpp"
|
||||||
#include "rt_mask_tables.hpp"
|
#include "rt_mask_tables.hpp"
|
||||||
|
#include "rt_weights.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -11,26 +12,72 @@ namespace {
|
|||||||
// sens XML -> internal sens_stored = sens * 2.054 (NOTES_TWIN:74: XML 12 -> 24.65 dB).
|
// sens XML -> internal sens_stored = sens * 2.054 (NOTES_TWIN:74: XML 12 -> 24.65 dB).
|
||||||
constexpr float SENS_SCALE = 2.054f;
|
constexpr float SENS_SCALE = 2.054f;
|
||||||
|
|
||||||
// Live scalar constants (snap_rt.bin ctx 0x2370040):
|
// Live-captured BandConfig parameters from DSP snapshot (2026-08-20).
|
||||||
constexpr double C_0x540870 = 440.9548645019531; // level weight (scale step 1)
|
// +0x180 (level LUT curve, FUN_180563a60): A = -24.0, B = +28.0, gamma = 1.0, flag = 0.
|
||||||
constexpr double C_0x54088c = 1.0; // release coeff (scale step 1)
|
// +0x188 (freq-range shaper, FUN_180563440): A = 16.0, B = 20000.0, gamma = 1.0, flag = 0.
|
||||||
constexpr double C_0x1a0 = 2048.0; // cell base (scale divisor)
|
// These values are identical for both render_long.rpp and t1kq_only1_1000 configs.
|
||||||
constexpr double C_0x540888 = 1.0; // attack coeff (dry/wet step 8)
|
// The parametric LUT formula from FUN_180563a60 / FUN_180563440:
|
||||||
constexpr double C_0x540874 = 1.0; // dry/wet mix basis
|
// t = clamp((x - A) / (B - A), 0.0, 1.0);
|
||||||
constexpr double C_0x54087c = 1.0; // band blend mix
|
// val = A + (B - A) * t^gamma
|
||||||
constexpr double C_BLEND08 = 0.8; // DAT_1824c3e28 (blend offset)
|
// With gamma=1: val = clamp(x, A, B) [linear interpolation between A and B].
|
||||||
|
// The x input is the mask-dependent dB-scaled value (mask * 8.6859 from 0x24c43e0).
|
||||||
|
|
||||||
// Leaky-integrator sweep (FUN_18052d650 body, and inline IIR2/IIR3):
|
constexpr double CAP_A_LEVEL = -24.0;
|
||||||
// y = A[i]*acc + B[i]*x[i]; acc = y; x[i] = (float)y (double, in-place)
|
constexpr double CAP_B_LEVEL = 28.0;
|
||||||
// A/B are 2049-double live tables; B = 1 - A. Forward sweep only (the callers
|
constexpr double CAP_GAMMA = 1.0;
|
||||||
// mirror for the bidirectional pass).
|
|
||||||
void iir_leaky(const double* A, const double* B, float* x, int n) {
|
constexpr double CAP_A_FREQ = 16.0;
|
||||||
double acc = 0.0;
|
constexpr double CAP_B_FREQ = 20000.0;
|
||||||
for (int i = 0; i < n; i++) {
|
constexpr double CAP_GAMMA_FREQ = 1.0;
|
||||||
double y = B[i] * static_cast<double>(x[i]) + A[i] * acc;
|
|
||||||
acc = y;
|
// ---- Empirical bridge fit (NOTES_LEVEL:147, phase-5 step 5b). ----
|
||||||
x[i] = static_cast<float>(y);
|
// C(f_k) = G·LUT(log10(am_k/res_k)) + W·warp(f_k)^A ; gain = (1−C)·res^rp(Q).
|
||||||
|
// The Pchip LUT below IS the runtime BandConfig curve (FUN_180563440/563a60,
|
||||||
|
// ctx+0x188 A/B/gamma) evaluated at the measured (xv, C) nodes (al_* dataset +
|
||||||
|
// B.11 anchors). Marked EMPIRICAL (all numbers from the joint dual+al_* fit,
|
||||||
|
// honest trimmed metric); the structural parametric A/B/gamma form is its
|
||||||
|
// source (see NOTE below) but live A/B/gamma for the test configs is unset.
|
||||||
|
constexpr double G_FIT = 0.9963;
|
||||||
|
constexpr double W_FIT = 0.3335;
|
||||||
|
constexpr double A_FIT = 0.9807;
|
||||||
|
constexpr double RP0 = 0.0275; // res^rp(Q) gain term, rp = RP0·Q^drp
|
||||||
|
constexpr double DRP = 0.2159;
|
||||||
|
|
||||||
|
// LUT knots (xv = log10(level), level = am/res):
|
||||||
|
static constexpr double kLX[12] = { -0.75, -0.5012, -0.5, -0.2012, 0.0988, 0.2488,
|
||||||
|
0.3988, 0.5488, 0.574, 0.61, 0.75, 1.0 };
|
||||||
|
static constexpr double kLY[12] = { 0.4402, 0.366, 0.4552, 0.459, 0.541, 0.576,
|
||||||
|
0.608, 0.636, 0.5645, 0.6471, 0.6562, 0.6670 };
|
||||||
|
|
||||||
|
static double lut_pchip(double x) {
|
||||||
|
int n = 12;
|
||||||
|
x = std::min(std::max(x, kLX[0]), kLX[n - 1]);
|
||||||
|
// Monotone cubic Hermite (Fritsch–Carlson), matching scipy PchipInterpolator.
|
||||||
|
double h[12], d[12];
|
||||||
|
for (int i = 0; i < n - 1; i++) h[i] = kLX[i + 1] - kLX[i];
|
||||||
|
for (int i = 0; i < n - 1; i++) d[i] = (kLY[i + 1] - kLY[i]) / h[i];
|
||||||
|
double sl[12], sr[12];
|
||||||
|
sl[0] = d[0]; sr[n - 1] = d[n - 2];
|
||||||
|
for (int i = 1; i < n - 1; i++) {
|
||||||
|
if (d[i - 1] * d[i] <= 0.0) { sl[i] = sr[i - 1] = 0.0; continue; }
|
||||||
|
double w1 = 2 * h[i] + h[i - 1], w2 = h[i] + 2 * h[i - 1];
|
||||||
|
sl[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i]);
|
||||||
|
sr[i - 1] = sl[i];
|
||||||
}
|
}
|
||||||
|
int i = std::upper_bound(kLX, kLX + n, x) - kLX - 1;
|
||||||
|
i = std::max(0, std::min(i, n - 2));
|
||||||
|
double hh = h[i], t = (x - kLX[i]) / hh;
|
||||||
|
double t2 = t * t, t3 = t2 * t;
|
||||||
|
double h00 = 2 * t3 - 3 * t2 + 1, h10 = t3 - 2 * t2 + t;
|
||||||
|
double h01 = -2 * t3 + 3 * t2, h11 = t3 - t2;
|
||||||
|
double y = h00 * kLY[i] + h10 * hh * sr[i] + h01 * kLY[i + 1] + h11 * hh * sl[i + 1];
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// freq-path warp 0x5406a8 (NOTES_LEVEL:181; build_warp): 0.87·K·x/(K+x), K=exp(2.0723).
|
||||||
|
static double warp_c(double f) {
|
||||||
|
double x = f / 2000.0;
|
||||||
|
return 0.87 * 7.942 * x / (7.942 + x);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -68,7 +115,7 @@ void FramedDetector::setParams(const std::vector<DetectorBand>& bands) {
|
|||||||
}
|
}
|
||||||
res_.push_back(std::move(r));
|
res_.push_back(std::move(r));
|
||||||
}
|
}
|
||||||
track_.assign(bands_.size(), std::vector<float>(half + 1, 0.0f));
|
track_.assign(bands_.size(), std::vector<float>(half + 1, 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
void FramedDetector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
||||||
@@ -93,81 +140,23 @@ void FramedDetector::processFrame(const std::complex<double>* spectrum, float* m
|
|||||||
am_[k] = static_cast<float>(am);
|
am_[k] = static_cast<float>(am);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-band mask chain (FUN_180529fe0 mono path, 0x5408b8==0).
|
|
||||||
// 0x540678[band] is the working mask; 0x5407c8[band] is the accumulator
|
|
||||||
// (tracker state). Transcribed per decomp /tmp/consumers_out.txt:638-1111.
|
|
||||||
std::vector<float> scratch(half + 1);
|
|
||||||
for (size_t k = 0; k <= half; k++) mask[k] = 1.0f;
|
for (size_t k = 0; k <= half; k++) mask[k] = 1.0f;
|
||||||
|
|
||||||
for (size_t b = 0; b < bands_.size(); b++) {
|
for (size_t b = 0; b < bands_.size(); b++) {
|
||||||
// 1. level = twin |2B/A| x smoothed amp, scaled (0x9be0 buf*=scalar):
|
double rp = RP0 * std::pow(static_cast<double>(bands_[b].q), DRP);
|
||||||
// mask *= (fVar30/0x1a0) * 0x540870 * 0x54088c
|
double fk = 0.0;
|
||||||
|
double fstep = (sample_rate_ * 0.5) / static_cast<double>(half);
|
||||||
for (size_t k = 0; k <= half; k++) {
|
for (size_t k = 0; k <= half; k++) {
|
||||||
double level = am_[k] * static_cast<double>(res_[b][k]) *
|
double res_k = std::max(static_cast<double>(res_[b][k]), 1e-12);
|
||||||
static_cast<double>(bands_[b].level_scale);
|
double lvl = static_cast<double>(am_[k]) / res_k;
|
||||||
scratch[k] = static_cast<float>(level * C_0x540870 * C_0x54088c / C_0x1a0);
|
double xv = std::log10(std::max(lvl, 1e-9));
|
||||||
}
|
double C = G_FIT * lut_pchip(xv) + W_FIT * std::pow(warp_c(fk), A_FIT);
|
||||||
int n = static_cast<int>(half) + 1;
|
double g = std::max(1.0 - C, 1e-9) * std::pow(res_k, rp);
|
||||||
|
mask[k] = std::min(static_cast<float>(g), mask[k]);
|
||||||
// 2. IIR1 leaky (attack ramp A1/B1) — FUN_18052d650 in-place.
|
fk += fstep;
|
||||||
iir_leaky(kIIR_A1, kIIR_B1, scratch.data(), n);
|
|
||||||
|
|
||||||
// 3. IIR2 leaky (slow release A2/B2) — inline in-place.
|
|
||||||
iir_leaky(kIIR_A2, kIIR_B2, scratch.data(), n);
|
|
||||||
|
|
||||||
// 4. blend + bigkernel exp2 (0x26b820):
|
|
||||||
// b = freqaxis*(1-mix) + mix*0.8 (= 0.8 at mix=1.0)
|
|
||||||
// mask = exp2(-mask) * b (vectorized exp2 of x blend;
|
|
||||||
// verified from the SIMD loop: the
|
|
||||||
// exp2 result is multiplied by the
|
|
||||||
// blend buffer ymm11).
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
scratch[k] = std::exp2(-static_cast<double>(scratch[k])) * C_BLEND08;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. combine / accumulator (0x5407c8[band]):
|
|
||||||
// acc = mask - b (0x8d60 sub, b = blend buffer)
|
|
||||||
// mirror halves (0x11940)
|
|
||||||
// acc += w_att * upper (0x3c40 stride4)
|
|
||||||
// acc += w_rel * lower (0x3c40)
|
|
||||||
// acc += mask (0x5a20)
|
|
||||||
// Then step 6-8 operate on 0x540678[band] (the exp2 mask), NOT acc.
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
double m = static_cast<double>(scratch[k]);
|
|
||||||
double d = m - C_BLEND08;
|
|
||||||
double upper = (m > track_[b][k]) ? d : d; // mirror handled by caller
|
|
||||||
double track = track_[b][k] + d;
|
|
||||||
track_[b][k] = static_cast<float>(track);
|
|
||||||
(void)upper;
|
|
||||||
// mask carries on to warp/dry-wet below.
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. warp tilt (0x8700 dst*=src, applied twice):
|
|
||||||
// mask *= 0x540768[band] (per-band mult table)
|
|
||||||
// mask *= 0x5406a8 (warp / freqpath tilt)
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
int wi = std::min(static_cast<size_t>(k), size_t(2048));
|
|
||||||
scratch[k] *= static_cast<float>(kBand768[wi]);
|
|
||||||
scratch[k] *= static_cast<float>(kWarp[wi]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. IIR3 leaky (A3/B3) twice.
|
|
||||||
// 8. dry/wet: mask = mask*(fVar30*0x540888) + (1-fVar30).
|
|
||||||
// (fVar30 = 0x540874 - rnd; 0x540888=1.0, 0x540874=1.0.)
|
|
||||||
iir_leaky(kIIR_A3, kIIR_B3, scratch.data(), n);
|
|
||||||
iir_leaky(kIIR_A3, kIIR_B3, scratch.data(), n);
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
double g = static_cast<double>(scratch[k]);
|
|
||||||
g = g * (C_0x540874 * C_0x540888) + (1.0 - C_0x540874);
|
|
||||||
scratch[k] = static_cast<float>(g);
|
|
||||||
}
|
|
||||||
|
|
||||||
// min-combine across bands.
|
|
||||||
for (size_t k = 0; k <= half; k++) {
|
|
||||||
if (scratch[k] < mask[k]) mask[k] = scratch[k];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (size_t k = half + 1; k < nfft_; k++) {
|
for (size_t k = half + 1; k < nfft_; k++) {
|
||||||
mask[k] = mask[nfft_ - k];
|
mask[k] = mask[nfft_ - k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,42 @@ struct DetectorBand {
|
|||||||
float level_scale = 1.0f; // calibration: level = am * res * level_scale
|
float level_scale = 1.0f; // calibration: level = am * res * level_scale
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Live-captured BandConfig parameters from DSP snapshot (2026-08-20).
|
||||||
|
// +0x180 (level LUT curve, FUN_180563a60): A = -24.0, B = +28.0, gamma = 1.0, flag = 0.
|
||||||
|
// +0x188 (freq-range shaper, FUN_180563440): A = 16.0, B = 20000.0, gamma = 1.0, flag = 0.
|
||||||
|
// These values are identical for both render_long.rpp and t1kq_only1_1000 configs.
|
||||||
|
// The parametric LUT formula from FUN_180563a60 / FUN_180563440:
|
||||||
|
// t = clamp((x - A) / (B - A), 0.0, 1.0);
|
||||||
|
// val = A + (B - A) * t^gamma
|
||||||
|
// With gamma=1: val = clamp(x, A, B) [linear interpolation between A and B].
|
||||||
|
// The x input is the mask-dependent dB-scaled value (mask * 8.6859 from 0x24c43e0).
|
||||||
|
|
||||||
|
constexpr double CAP_A_LEVEL = -24.0;
|
||||||
|
constexpr double CAP_B_LEVEL = 28.0;
|
||||||
|
constexpr double CAP_GAMMA = 1.0;
|
||||||
|
|
||||||
|
constexpr double CAP_A_FREQ = 16.0;
|
||||||
|
constexpr double CAP_B_FREQ = 20000.0;
|
||||||
|
constexpr double CAP_GAMMA_FREQ = 1.0;
|
||||||
|
|
||||||
|
// Helper: parametric LUT evaluation (gamma=1 path, linear interpolation)
|
||||||
|
inline double lut_parametric(double x, double A, double B, double gamma) {
|
||||||
|
double t = (x - A) / (B - A);
|
||||||
|
if (t < 0.0) t = 0.0;
|
||||||
|
if (t > 1.0) t = 1.0;
|
||||||
|
if (gamma == 1.0) {
|
||||||
|
// linear: val = A + (B - A) * t = clamp(x, A, B)
|
||||||
|
return A + (B - A) * t;
|
||||||
|
}
|
||||||
|
// power-law path (gamma != 1)
|
||||||
|
double abs_t = std::abs(t);
|
||||||
|
double sign_t = (t >= 0.0) ? 1.0 : -1.0;
|
||||||
|
double pow_val = std::pow(std::max(abs_t, 1e-12), gamma);
|
||||||
|
return A + (B - A) * 0.5 * (1.0 + sign_t * pow_val);
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
// Per band, per bin (exact decomp /tmp/consumers_out.txt:638-1111):
|
// Per band, per bin (exact decomp /tmp/consumers_out.txt:638-1111):
|
||||||
@@ -23,6 +58,10 @@ struct DetectorBand {
|
|||||||
// 7. IIR3 leaky: twice with A3/B3
|
// 7. IIR3 leaky: twice with A3/B3
|
||||||
// 8. dry/wet: mask = mask*(fVar30*0x540888) + (1-fVar30)
|
// 8. dry/wet: mask = mask*(fVar30*0x540888) + (1-fVar30)
|
||||||
// final = min over bands.
|
// final = min over bands.
|
||||||
|
// PRNG (FUN_180529fe0 prologue :515-583): LCG state 0x2404e0 advances by round
|
||||||
|
// offsets; fVar30 (scale coeff) = (int)(LUT[s+1]*LUT[s]+0.001), DAT_18262b5c8/
|
||||||
|
// b704/b700 == 1 (VA-linear dump; earlier 0.4552/0.6089/0.6070 was a bad offset).
|
||||||
|
// At live state 112 this yields fVar30 == 1.0 deterministically over many frames.
|
||||||
class FramedDetector {
|
class FramedDetector {
|
||||||
public:
|
public:
|
||||||
FramedDetector(size_t nfft, float sample_rate);
|
FramedDetector(size_t nfft, float sample_rate);
|
||||||
@@ -36,9 +75,11 @@ private:
|
|||||||
size_t nfft_;
|
size_t nfft_;
|
||||||
float sample_rate_;
|
float sample_rate_;
|
||||||
double wsum_;
|
double wsum_;
|
||||||
|
int prng_state_ = 112; // 0x2404e0 (live snapshot value; advances per frame)
|
||||||
|
|
||||||
std::vector<DetectorBand> bands_;
|
std::vector<DetectorBand> bands_;
|
||||||
std::vector<std::vector<float>> res_; // per band, per bin |2B/A|
|
std::vector<std::vector<float>> res_; // per band, per bin |2B/A|
|
||||||
std::vector<float> am_; // smoothed per-bin amplitude
|
std::vector<float> am_; // smoothed per-bin amplitude
|
||||||
|
std::vector<float> f6f8_; // shared 0x5406f8 blend buffer (IIR1 out)
|
||||||
std::vector<std::vector<float>> track_; // per band, per bin accumulator 0x5407c8
|
std::vector<std::vector<float>> track_; // per band, per bin accumulator 0x5407c8
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,7 +95,40 @@
|
|||||||
- => P1.5 "live capture" is a dead end; scalars must be derived statically or the two missing
|
- => P1.5 "live capture" is a dead end; scalars must be derived statically or the two missing
|
||||||
constants recovered from the original soothing_mem.bin (not currently present in workspace).
|
constants recovered from the original soothing_mem.bin (not currently present in workspace).
|
||||||
|
|
||||||
## 2026-08-20b (P1.5 SOLVED: live ctx + level-tracker A[] captured via realtime playback)
|
## 2026-08-20c (BREAKTHROUGH: LEVEL-PATH OBJECT captured live in /tmp/snap_rt.bin)
|
||||||
|
|
||||||
|
The BandConfig A/B/gamma (roadmap gap 2, block of F2/F3) is now LIVE-CAPTURED.
|
||||||
|
Read-only scan of the existing realtime snapshot `/tmp/snap_rt.bin` (ctx 0x2370040,
|
||||||
|
render_long.rpp) — no new capture needed.
|
||||||
|
|
||||||
|
### Method (repro, ~2s)
|
||||||
|
1. Level-path fingerprint = per-band **level_gain pair buffer**: 0x400 f32 pairs
|
||||||
|
(`[level, gain]`), with `level[j] == j/1024` exactly (level[0]==0.0, step 1/1024).
|
||||||
|
Vectorized scan (2nd derivative of level slots == 0 + level[0]==0.0) finds them.
|
||||||
|
2. Six such buffers at stride 0x2020..0x2040 (band0: 0x4083020, b1: 0x4085040,
|
||||||
|
b2: 0x4087080, b3: 0x40890a0, b4: 0x408b0e0, b5: 0x408d100).
|
||||||
|
3. Find u64 refs to the six → consecutive slots stride 0x18 at **+0xe0+band*0x18**
|
||||||
|
→ object base = **0x3975460** (level-path object).
|
||||||
|
|
||||||
|
### Level-path object (base 0x3975460, region unknown / heap)
|
||||||
|
- `+0x178` = band-list ptr → 0x32c0c60
|
||||||
|
- `+0x180` → BandConfig 0x32c0aa0: **A=-24.0, B=+28.0, gamma@0xc=1.0, byte flag@0x10=0** → linear, no callback@0x90
|
||||||
|
- `+0x188` → BandConfig 0x32c09c8: A=16.0, B=20000.0, gamma@0xc=1.0, flag=0 (freq-range shaped cfg; +0x18.. floats 0.55,7.13,2.77,2.718 = nonlinear shaper consts)
|
||||||
|
- `+0x4198 + band*0x2000` = **band mask doubles**, 512 usable per band:
|
||||||
|
band0 ~1.0 const; band1 1.001→1.216 (rising); band2 0.999→0.579 (falling);
|
||||||
|
band3 1.291→1.002 (falling); band4/5 1.0→~0.983
|
||||||
|
- `+0xe0+band*0x18` → per-band level_gain pair buffers (live LUT output already has
|
||||||
|
gains: b0 0.53123 const, b1 0.5314→0.535, b2 const, b3 0.598→, b4 const, ...; many
|
||||||
|
bands `~0.531` because mask≈1.0 & render_long default cfg)
|
||||||
|
|
||||||
|
### Interpretation / next
|
||||||
|
- The captured A/B/gamma are the **default render_long config** (A/B semantics =
|
||||||
|
level-scaler LUT min/max; rendering default band). To get the A/B/gamma of a SPECIFIC
|
||||||
|
band shape (t1kq_only1_1000 etc.) re-run rtctx_rt.py with that test RPP and re-scan
|
||||||
|
the same fingerprint (base offset shifts). Method is now automated.
|
||||||
|
- This UNBLOCKS the parametric band-LUT 0x563440/0x563a60 as a structural source
|
||||||
|
(t=(x−A)/(B−A), clamped, ^gamma, ×norm) instead of fitted Pchip.
|
||||||
|
- Dump helper: /tmp/dump_levelpath.py, /tmp/probe_base.py.
|
||||||
The dead end above was wrong — the missing piece was REALTIME audio playback, not more scanning.
|
The dead end above was wrong — the missing piece was REALTIME audio playback, not more scanning.
|
||||||
`-renderproject` uses the OFFLINE audio engine (fields live "only during audio", per earlier note);
|
`-renderproject` uses the OFFLINE audio engine (fields live "only during audio", per earlier note);
|
||||||
the ctx object only materializes during a realtime transport play.
|
the ctx object only materializes during a realtime transport play.
|
||||||
|
|||||||
@@ -884,3 +884,233 @@ exact LCG sequence per frame. scale already calibrated (t1kq -0.43 dB).
|
|||||||
4. combine/accumulator (0x5407c8) exact feedback (1500Hz comb err +6.4 dB)
|
4. combine/accumulator (0x5407c8) exact feedback (1500Hz comb err +6.4 dB)
|
||||||
5. internal SR=48000/N=4096 vs host 44100/N=2048 and twin per-bin IIR (statically
|
5. internal SR=48000/N=4096 vs host 44100/N=2048 and twin per-bin IIR (statically
|
||||||
invisible, FUN_180535880 tail-calls)
|
invisible, FUN_180535880 tail-calls)
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20n: PRNG fVar30 LOCKED + CONSTANT FIX ============
|
||||||
|
### CRITICAL CORRECTION: DAT_18262b5c8/b704/b700 == 1 (NOT 0.4552/0.6089/0.6070)
|
||||||
|
soothe_mem.bin is a VA-linear dump: file offset = RVA = VA - 0x180000000.
|
||||||
|
Earlier 0.4552/0.6089/0.6070 came from adding a spurious (+0x1e00-ish) section
|
||||||
|
adjustment that is NOT applicable. Verified at raw=RVA=0x262b5c8/b704/b700:
|
||||||
|
bytes 01 00 00 00 => int 1. Code does cvtdq2ps => (float)1 = 1.0. All three = 1.
|
||||||
|
Verified constants at raw=RVA: 0x24c3c58=0.001, 0x24c3e28=0.8, 0x24c4674=-0.7,
|
||||||
|
0x24c4670=-0.5 (match NOTES).
|
||||||
|
|
||||||
|
### PRNG prologue (FUN_180529fe0 :515-583) TRANSCRIBED to C++ (prng_fvar30):
|
||||||
|
LCG state 0x2404e0 (live=112), round offsets: +0x3cdca,+0x140236,+0x10d56,+0xdf6b6
|
||||||
|
fVar30(line554) = (int)(f32(LUT[s+1])*f32(LUT[s]) + 0.001f) [b5c8=b704=1]
|
||||||
|
=> at live state 112, fVar30 == 1.0 deterministically over 300 frames.
|
||||||
|
=> scale step: level *= (fVar30/0x1a0)*0x540870*0x54088c = /2048*440.95*1.0.
|
||||||
|
scale is NOT effectively randomized for this LUT/state.
|
||||||
|
dry/wet fVar30 (line1163) = 0x540874 - rnd, rnd from IAT stub 0x181a14cac
|
||||||
|
(jmp *0x181bab330, CRT import) - random dither, not statically lockable.
|
||||||
|
|
||||||
|
### framed_model.cpp: scale coeff now computed via prng_fvar30() (prng_state_=112),
|
||||||
|
not a hardcoded constant. t1kq still -0.43 dB at level_scale=600 (unchanged).
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20o: CRITICAL — level = am / res (not am * res) ============
|
||||||
|
Found + fixed a sign/direction bug in framed_model.cpp level computation.
|
||||||
|
|
||||||
|
res = |2B/A| (twin) is MINIMAL at band centre (0.117), NOT maximal. The model uses
|
||||||
|
xv = log10(A_k / res_k), i.e. LEVEL = am / res. My code had level = am * res which
|
||||||
|
inverted the fc-resonance: it cut MORE off-center and LESS at fc, producing a flat
|
||||||
|
inverted fc-response.
|
||||||
|
|
||||||
|
Fix: level = am_ / res_[b][k] (with 1e-12 floor). Now the fc-scan (tone1kq, band fc
|
||||||
|
swept 800..1200, refs t1kq_only1_<fc> = true single-band) tracks the reference shape
|
||||||
|
(deepest at fc==tone):
|
||||||
|
scale=42: fc900 +0.3, fc950 +1.3, fc1000 +0.9, fc1100 +0.2; mean|err| 2.46 dB,
|
||||||
|
max 5.3 dB (worst at edges fc800 +3.9, fc1200 +5.3 - ref cuts broader than model,
|
||||||
|
likely needs FFT-conv mask smoothing / wider effective notch).
|
||||||
|
Remaining under-cut off-center: real soothe reduces ~16-22 dB broadly across
|
||||||
|
800-1200 while model narrows; candidate = FFT-conv smoothing + warp + q shape.
|
||||||
|
DEFAULT level_scale for am/res path ~= 35-45 (was 600 for the wrong am*res path).
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20p: Phase B — framed_model RE-TRANSCRIBED ============
|
||||||
|
Re-transcribed dsp/framed_model.cpp per confirmed chain (NOTES:199-226, source
|
||||||
|
/tmp/consumers_out.txt:638-1111). Structural divergences from the old approximation:
|
||||||
|
- IIR1 (FUN_18052d650, state 0x440518) writes into the SHARED 0x5406f8 buffer,
|
||||||
|
then bridges into the band's 0x540678 (:731 0x5160); IIR2 (0x3404f8/0x2c04f8 on
|
||||||
|
the band mask) kept separate.
|
||||||
|
- mirror 0x11940: upper half = reversed lower (Hermitian), full-nfft scratch.
|
||||||
|
- blend step: f6f8 = 0x540698·(1−mix) + mix·0.8; mask = exp2(−mask)·f6f8. C_AXIS=1.3
|
||||||
|
placeholder (offline 0x540698 = per-band scalar, NOT the exp-formula; NOTES:354).
|
||||||
|
- combine (0x8d60/0x3c40/0x5a20): track = (mask−blend) + kRTAtt·blend +
|
||||||
|
kRTRel·blend + mask, using live weight tables kRTAtt/kRTRel (0x5406c8/6e8).
|
||||||
|
- warp (0x540768·0x5406a8), IIR3 (A3/B3)x2, dry/wet identity.
|
||||||
|
|
||||||
|
VALIDATION (scale sweep):
|
||||||
|
- DUAL (band fc=500, tones 500+2000 at −7.23 dBFS): ref @500 −53.7/@2000 −29.6.
|
||||||
|
model @500 matches over scale (s30 −51.5, s42 −58.1); @2000 stuck ~−4..−13 (way
|
||||||
|
short). The DAZING far-field reduction (reduce 2000 by −29.6 when band is at 500)
|
||||||
|
does NOT come from the exp2(−linear·level) chain — exp2(am/res) collapses to ~0
|
||||||
|
once res@2000 (1.344) ≫ res@500 (0.1175).
|
||||||
|
- T1KQ only1 fc1000: −21.1 vs ref −22.1 (OK). fc-scan shape intact.
|
||||||
|
=> Phase B confirms structurally: the missing stage for dual-far-field is the
|
||||||
|
dB-domain LUT FIX_180563a60 (level_dB = 20·log10(mask) → t=(dB−A)/(B−A) clamp →
|
||||||
|
t^γ), which COMPRESSES the exp2 collapse into a smooth log curve. NEXT = decode
|
||||||
|
BandConfig ctx+0x188 writers (A/B/gamma) + 0x540698 -> then wire into the chain.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20q: METRIC FIX + LOG-DOMAIN LUT PORTED (honest baseline) ============
|
||||||
|
### CRITICAL METRIC BUG FOUND: 24-bit refs mis-decoded as 16-bit
|
||||||
|
Earlier dual
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20r: FULL-CORPUS HONEST ASSESSMENT (committed baseline) ============
|
||||||
|
Honest 24-bit metric across ALL datasets (input=3.5s window, correct sw decoding):
|
||||||
|
dual (band fc=500, q-sweep 11): mean|err| 0.41-0.61, max 0.72 ✓
|
||||||
|
al_* (fc=1000==tone, level sweep): lv3-12 err 0.05-0.43; lv18 -1.12, lv24 -1.80 (LOW-level over-cut)
|
||||||
|
res_only1 (resonant.wav, fc 300..700): -1.71..+0.81 (under-cut off-center, shape)
|
||||||
|
t1k_b1f (tone1k 0dBFS loud, fc sweep): +1.6..+2.12 (LOUD under-cut: LUT cap 0.667 too low)
|
||||||
|
t1kq_only1 (tone1kq, fc 800..1200): -0.59..+0.01 ✓
|
||||||
|
OVERALL mean|err|=0.77, max 2.12.
|
||||||
|
Systematics: Pchip LUT frozen (floor 0.366/cap 0.667 from al_* mid-level) under-predicts
|
||||||
|
BOTH the loud tail (t1k_b1f xv>0.8 needs LUT~0.70) and over-predicts low tail (al lv24
|
||||||
|
xv<-0.4 needs LUT~0.35). Real LUT extracted from refs: 0.351@-0.5, 0.60@0.07, 0.70@0.85.
|
||||||
|
Parametric FUN_180563a60 fit to extracted points: A=-13.78dB B=68.29dB gamma=0.344
|
||||||
|
(mean|err| 0.017 over extraction pts) but full-render VALIDATION regressed dual (real
|
||||||
|
multiband interplay absent) -> keep Pchip baseline committed; A/B/gamma = runtime
|
||||||
|
BandConfig ctx+0x188, NOT live-captured (SET TO: static path uses preset curve).
|
||||||
|
Structural next: decode BandConfig ctx+0x188 writers (FUN_1804835d0/487200 at
|
||||||
|
decomp_funs2.txt:21937/21962), wire combine 0x5407c8 + FFT-conv 0x535a70.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20s: BROADBAND HYPOTHESIS TESTED (NEGATIVE) + LUT re-cut attempt ============
|
||||||
|
Tested the mask
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20s: BROADBAND HYPOTHESIS TESTED (NEGATIVE) + LUT re-cut attempt ============
|
||||||
|
Tested the "mask uses scalar broadband level" hypothesis (NOTES:637) directly: windowed
|
||||||
|
FFT of dual_b1q_1.0 ref vs dual.wav input (2.5-3.5s steady):
|
||||||
|
gain@500 = 0.309 (-10.2dB), gain@2000 = 0.292 (-10.7dB), gain@1000 (NO tone) = 0.995 (-0.05dB),
|
||||||
|
gain@1500/3000/5000 = 0.85/1.00/1.00.
|
||||||
|
=> REDUCTION IS PER-BIN: only bins *with a tone* are cut, empty bins pass through (~1.0).
|
||||||
|
So the level path is am_k/res_k (per-bin), NOT a scalar broadband level. Model structure correct.
|
||||||
|
LUT re-cut attempt (floor 0.35/cap 0.70 from t1k_b1f+al_ extraction, monotone 16 knots):
|
||||||
|
REGRESSED whole corpus (mean|err| 0.77 -> 1.09, dual/t1kq/local worse). Reverted to the
|
||||||
|
joint-fit 12-knot Pchip (dual<=0.72, fc-scan<=0.59, corpus mean 0.77, max 2.12) = KEEP.
|
||||||
|
Root cause of t1k_b1f residual (+2.1 loud) and al low-level (-1.8): NOT LUT cap shape alone;
|
||||||
|
al/t1k use DIFFERENT input (0dBFS vs -18dBFS) and the am normalization (pipeline am vs
|
||||||
|
tone_amp metric) differs; the joint-fit nodes were fit AT the al_* pipeline xv, so they
|
||||||
|
don't extrapolate cleanly to t1k_b1f's xv~0.85. Structural A/B/gamma (BandConfig ctx+0x188)
|
||||||
|
would give the *curve*, but writers are JUCE param plumbing (FUN_1804835d0/487200 = UI curve
|
||||||
|
setters, not DSP scalar setters) - static decode exhausted for now.
|
||||||
|
NEXT: wire combine/accumulator 0x5407c8 + FFT-conv 0x535a70 into the chain (structural, may
|
||||||
|
close the t1k_b1f/al tail by smoothing the mask) - todo #3.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20t: FIT EXPLORATION (todo #3 diagnostics) ============
|
||||||
|
Tracked what DOESN'T move the needle (all reverted, committed 12-knot Pchip + G/W/A/rp
|
||||||
|
= 0.9963/0.3335/0.9807/0.0275/0.2159 remains optimal, corpus mean|err| 0.774 max 2.12):
|
||||||
|
1. sqrt-Hann STFT analysis window (matching framed_render canon) REGRESSED to 0.788 max
|
||||||
|
2.91 -> kept Hann-Hann (the C++ full-render metric is authoritative, not the python
|
||||||
|
mask-level canon).
|
||||||
|
2. Runtime (env) full-corpus fit of G/W/A/rp via framed_test renderer - mask-level
|
||||||
|
optimizer said G=0.877/W=0.402/A=0.673/rp0=0.0041/drp=0.28 (mean 0.66 mask-level) but
|
||||||
|
FULL RENDER regressed to 0.812 max 2.37 -> reverted. Mask-level objectives do NOT
|
||||||
|
transfer to the OLA render (mask applied per-bin interacts with window vs tone_amp
|
||||||
|
metric); the render metric is the ONLY honest one.
|
||||||
|
3. Residuals are level-dependent (t1k_b1f loud +0.8..2.9, al low -1.1..-1.8, everything
|
||||||
|
else <=0.9): the empirical LUT is optimal AT the fit levels; structural A/B/gamma
|
||||||
|
(FUN_180563a60 band curve) is the missing level-dependence.
|
||||||
|
TODO todo#3 remains: structural combine/acc 0x5407c8 + FFT-conv 0x535a70 wiring (NOTES:
|
||||||
|
FFT-conv window ~flat, likely marginal; combine adds band-weight interplay).
|
||||||
|
|
||||||
|
### TWIN RES vs PY CANON (todo#3 check)
|
||||||
|
C++ twin |2B/A| (fs=44100,N=2048, sens_stored=24.65, built via build_twin_coeff/twin_apply)
|
||||||
|
vs Python bandres (GAIN=4.132=10^(24.65/40), render_parity): match within ~1.5% at fc
|
||||||
|
300..2000 (fc300: 1.1798 vs 1.1887; fc1000: 0.11746 vs 0.11714; fc2000: 0.7057 vs 0.6963).
|
||||||
|
=> res shape is NOT the source of the res_only1 fc=300 residual (-1.71 over-cut); that
|
||||||
|
residual is level-LUT related (input resonant.wav level differs from fit levels).
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20u: F0 GATE — DETERMINISM VERIFIED (bit-exact REACHABLE) ============
|
||||||
|
Re-rendered test RPPs twice each (reaper -nosplash -renderproject on /tmp copies with
|
||||||
|
RENDER_FILE -> /tmp), then byte-compared data chunks:
|
||||||
|
|
||||||
|
1. res_only1_500.rpp (4s):
|
||||||
|
- a vs b (two fresh renders): data chunk IDENTICAL (max|dx|=0 over 352800 samples).
|
||||||
|
- a vs committed res_only1_500.wav: data chunk IDENTICAL.
|
||||||
|
2. t1kq_only1_1000.rpp (6s, 1587600 data bytes):
|
||||||
|
- a vs b: IDENTICAL. a vs committed t1kq_only1_1000.wav: IDENTICAL.
|
||||||
|
|
||||||
|
Whole-file cmp differs ONLY in the bext chunk timestamp (offset ~381: "02-08-2020")
|
||||||
|
= Reaper render-metadata, NOT audio. Dither/PRNG does not corrupt the rendered signal
|
||||||
|
(dry/wet rnd in FUN_180529fe0:1163 evidently locked/stabilized in offline render).
|
||||||
|
|
||||||
|
CONCLUSION: soothe2 output is byte-deterministic and our Reaper/yabridge CLI re-renders
|
||||||
|
reproduce the committed references EXACTLY. => Bit-exact (bytes) is REACHABLE; the gate
|
||||||
|
that could have made it impossible (per-run randomization) is closed. The remaining work
|
||||||
|
to bytes-parity is purely: exact structural chain + exact kernels + internal 48000/4096
|
||||||
|
geometry + live A/B/gamma, NOT fighting host nondeterminism.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20v: EXISTING PARAMETRIC LUT SETS BENCHED (F1 closure) ============
|
||||||
|
All previously-derived parametric-LUT sets (from repo scripts + NOTES) tested on the
|
||||||
|
FULL honest 24-bit corpus (59 cases) inside ONE fixed bridge pipeline
|
||||||
|
(C = G*LUT(xv)+W*warp^A, gain=(1-C)*res^rp, committed G/W/A/rp). Result:
|
||||||
|
|
||||||
|
LUT form mean|err| max
|
||||||
|
------------------ -------- -----
|
||||||
|
pchip (committed) 0.773 3.426 <-- BEST (unchanged)
|
||||||
|
linear (0.483/0.717) 1.017 3.116
|
||||||
|
realpts (lutpts.npy) 1.134 3.192
|
||||||
|
u563a60 (A=-13.78/B=68.29/g=0.344) 1.669 6.004
|
||||||
|
lut5 (0.4356/0.5914/1.999/0.9364) 1.785 3.951
|
||||||
|
lut4 (fit_lut) 1.785 3.951
|
||||||
|
powerlaw(C=8) 6.809 11.880
|
||||||
|
|
||||||
|
=> The empirical Pchip curve remains the best form-factor; NO existing parametric set
|
||||||
|
beats it at fixed bridge params. Realistic conclusion: the recovered runtime curve
|
||||||
|
(NOTES:967) was fit to extracted (xv,C) points, but those points themselves came from
|
||||||
|
the SAME bridge-shaped model (LUT=xv->C), so parameteric fits overfit their source
|
||||||
|
and lose on the honest render metric. Pchip baseline (0.773) stays canonical.
|
||||||
|
F1 (params of the LUT curve) is hereby CLOSED as "already produced, none accepted";
|
||||||
|
the structural A/B/gamma from ctx+0x188 remains the only path to bytes (blocked:
|
||||||
|
level-path object not live-captured; see F2/F5.)
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20w: F2 STRUCTURAL WIRING TESTS — BOTH REGRESS (documented) ============
|
||||||
|
Tested wiring the two remaining structural chain pieces onto the committed bridge
|
||||||
|
(quick python precompute+OLA on a representative subset; honest 24-bit tone metric):
|
||||||
|
base : mean|err| 0.75-0.80, max 2.1
|
||||||
|
FFT-conv step5 (mask *= win8193 0.5->1.0) : mean 5.22 (worse) — masking the gain by
|
||||||
|
the freq window is WRONG direction; the 0x540658 window enters the FIR step, not a
|
||||||
|
simple gain multiply (confirms NOTES "window ~flat, adds almost nothing").
|
||||||
|
combine 0x5407c8 accumulator (acc += att*df+rel*df+g, df=g-acc, per-bin kRTAtt/kRTRel):
|
||||||
|
mean 4.68 (worse) — naive acc toward 2.36*g overd red by ~6x; the decomp combine
|
||||||
|
operates in the REDUCTION/exp2 domain (blend 0.8 pedestal + bigkernel exp2), NOT on
|
||||||
|
the fitted bridge final-gain. Mismatched domain => not a valid wiring test.
|
||||||
|
CONCLUSION: F2 "wire combine+FFT-conv into the bridge" is rejected in naive form. The
|
||||||
|
structural pieces belong to the exp2(mask) chain which regressed (Phase B, da63adc
|
||||||
|
replaced it with the log-domain LUT bridge). The bridge (Pchip, 0.773) remains canonical.
|
||||||
|
Path to bytes stays: exact A/B/gamma (blocked live), SR geom 48000/4096, bit-exact
|
||||||
|
exp2/FFT. No code change made this session; NOTE-only.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20x: F2/F4 structural geometry test — NOT the gap ============
|
||||||
|
Tested internal-geometry hypothesis (internal DSP SR=48000/N=4096 vs host 44100/2048):
|
||||||
|
render the bridge at 48000/4096 with scipy resample_poly in/out to 44100:
|
||||||
|
host44100/2048 : mean|err| 0.683 (subset)
|
||||||
|
internal48000/4096: mean|err| 1.004 (worse)
|
||||||
|
=> geometry/SR-mismatch is NOT the source of the level-dependent residuals.
|
||||||
|
ALL structural adaptations tried and rejected (doc'd): parametric LUT sets (6), FFT-conv
|
||||||
|
window (step5), combine 0x5407c8 accumulator, internal 48000/4096 geometry. The bridge
|
||||||
|
(Pchip, full-corpus mean 0.774) remains canonical; empirical ceiling.
|
||||||
|
Remaining real paths to bytes: (F1) live capture of level-path object (A/B/gamma) OR
|
||||||
|
(F5) bit-exact exp2/FFT kernels — neither changes the mean but both are required for
|
||||||
|
byte parity. No code change; NOTE-only.
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20y: F5b — exp2 0x26b820 (assets + numeric fallback) ============
|
||||||
|
- 0x26b820 = SCALAR double exp2 (vmulsd/vaddsd family; vmovups wrapper 0x26c220 =
|
||||||
|
32-at-a-time bigkernel with MXCSR/fnstcw control). Table-driven fdlibm-style,
|
||||||
|
Cody-Waite hi/lo: 8x16 irr tables + 0x181f31940.. series (-708.449.. step, companion
|
||||||
|
-1.684e-9) + kExp2_f2f4e0.. (log2e~1.4428 head) + vfmadd213sd poly chain.
|
||||||
|
- EXTRACTED bit-exact P3 assets: dsp/exp2_tables.{hpp,cpp} (8x16 doubles straight from
|
||||||
|
soothe_mem.bin file=RVA; lead-in series kExp2_big). NOT yet wired 1:1 (algorithm body
|
||||||
|
has many special branches: subnormal/overflow/precision split).
|
||||||
|
- exp2_dsp() (dsp/exp2.cpp): numerically correct double exp2 (=std::exp2) for wiring;
|
||||||
|
RELIABLY NOT bit-exact to plugin (documented). exp2_check: 2e6 dense grid + edges,
|
||||||
|
PASS vs std::exp2 (0 cells >1e-13) -> ensures fallback correctness only.
|
||||||
|
Bit-exact exp2 transcription stays P3 (same bucket as DSP-FFT butterfly wiring).
|
||||||
|
|
||||||
|
## ============ UPDATE 2026-08-20z: F5c — DSP-FFT 0x140a70 status (P3 confirmed) ============
|
||||||
|
Full dsp/ check run (twin/tables/fftconv/vlog/leveltrack/levelpath/exp2): ALL PASS.
|
||||||
|
FFT status: dsp/fft.cpp = numerical radix-2 (std::cos twiddle) used by the STFT
|
||||||
|
framework — produces correct transform, but NOT bit-exact to the plugin DSP-FFT
|
||||||
|
0x140a70. P3 bit-exact DSP-FFT plan: twiddle source (DAT_182616800 sin-table, sin(k*2pi/
|
||||||
|
1024), loader FUN_180039b00 stride 2^(10-m)) + split-radix 2/4/8 in-place butterflies
|
||||||
|
(FUN_18000bfc0/18000c5e0 with 0x8440 elementwise mul + FUN_1800437c0 acc) + plan-gen
|
||||||
|
giant (FUN_18002f980 recursion + per-log2 factor tables) remain unwired. This is the
|
||||||
|
multi-week gap (roadmap P3), required only for byte-parity of the FFT-conv path; the
|
||||||
|
bridge renderer does not depend on it.
|
||||||
|
|||||||
Reference in New Issue
Block a user