diff --git a/dsp/CMakeLists.txt b/dsp/CMakeLists.txt index 02470cd..44ee14c 100644 --- a/dsp/CMakeLists.txt +++ b/dsp/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(soothe2_dsp SHARED phase_table.cpp fftconv.cpp vlog.cpp + exp2_tables.cpp + exp2.cpp leveltrack.cpp framed_model.cpp rt_weights.cpp @@ -34,8 +36,10 @@ add_executable(fftconv_check fftconv_check.cpp) add_executable(vlog_check vlog_check.cpp) add_executable(leveltrack_check leveltrack_check.cpp) add_executable(levelpath_check levelpath_check.cpp) +add_executable(exp2_check exp2_check.cpp) target_link_libraries(twin_check 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(fftconv_check soothe2_dsp) target_link_libraries(vlog_check soothe2_dsp) diff --git a/dsp/exp2.cpp b/dsp/exp2.cpp new file mode 100644 index 0000000..320c39d --- /dev/null +++ b/dsp/exp2.cpp @@ -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 +#include +#include +#include + +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::infinity()) return 0.0; + if (x == std::numeric_limits::infinity()) return x; + return std::exp2(x); +} + +} // namespace exp2d \ No newline at end of file diff --git a/dsp/exp2.hpp b/dsp/exp2.hpp new file mode 100644 index 0000000..68834c8 --- /dev/null +++ b/dsp/exp2.hpp @@ -0,0 +1,17 @@ +#pragma once +#include + +// 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 \ No newline at end of file diff --git a/dsp/exp2_check.cpp b/dsp/exp2_check.cpp new file mode 100644 index 0000000..0e48329 --- /dev/null +++ b/dsp/exp2_check.cpp @@ -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 +#include +#include +#include +#include + +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 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; +} \ No newline at end of file diff --git a/dsp/exp2_tables.cpp b/dsp/exp2_tables.cpp new file mode 100644 index 0000000..51b6fd4 --- /dev/null +++ b/dsp/exp2_tables.cpp @@ -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, +}; diff --git a/dsp/exp2_tables.hpp b/dsp/exp2_tables.hpp new file mode 100644 index 0000000..139d9d2 --- /dev/null +++ b/dsp/exp2_tables.hpp @@ -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]; + diff --git a/handoff/NOTES_LEVEL.md b/handoff/NOTES_LEVEL.md index b179b35..df748af 100644 --- a/handoff/NOTES_LEVEL.md +++ b/handoff/NOTES_LEVEL.md @@ -1090,3 +1090,16 @@ window (step5), combine 0x5407c8 accumulator, internal 48000/4096 geometry. The 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).