roadmap: detector decrypted in soothe_mem.bin, SNR 19.8dB; integrate twiddle loader
- fft.cpp: build_twiddle via soothe::twiddle_load (Cody-Waite sin/cos); drop dup init_plan - fft_stage.cpp: cplx_mul/stage_complex/stage_double kernels -> phase fixed (corr +0.995) - detect.cpp: level-dependent regional floor (no bell-boost), mask 10^(-1.041*depth*floor/20) - burst500 metrics: ref -26.07 / ours -26.13 dBFS, corr 0.99475, SNR 19.80 dB, diff -0.065 dB - KEY: FUN_180535880/536f90 bodies are decrypted real SSE in soothe_mem.bin; dispatch table 0x182616008[0]=idx=4 -> 0x180009860 -> FUN_180040d40; region 0x18004xxxx = full detector algorithm, absent from prior fun_map/decomp (Ghidra ran on encrypted file)
This commit is contained in:
+17
-18
@@ -38,36 +38,35 @@ void Detector::processFrame(const std::complex<double>* spectrum, float* mask) {
|
|||||||
|
|
||||||
// Compute magnitude per bin
|
// Compute magnitude per bin
|
||||||
std::vector<float> mag(half + 1);
|
std::vector<float> mag(half + 1);
|
||||||
double total_energy = 0.0;
|
|
||||||
for (size_t i = 0; i <= half; i++) {
|
for (size_t i = 0; i <= half; i++) {
|
||||||
mag[i] = static_cast<float>(std::sqrt(
|
mag[i] = static_cast<float>(std::sqrt(
|
||||||
spectrum[i].real() * spectrum[i].real() +
|
spectrum[i].real() * spectrum[i].real() +
|
||||||
spectrum[i].imag() * spectrum[i].imag()));
|
spectrum[i].imag() * spectrum[i].imag()));
|
||||||
total_energy += static_cast<double>(mag[i]) * mag[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overall signal level in dBFS (RMS of the spectrum)
|
// Regional level: RMS over a moving window of bins, calibrated to dBFS
|
||||||
// Calibrated offset: spectrum overall_db → time-domain dBFS
|
// Empirically: peek level -8 dBFS → floor 6.1; below ~-32 dBFS → no cut
|
||||||
// For Hann window + N=2048: offset ≈ 28.8 dB
|
const size_t reg = 16;
|
||||||
float overall_rms = std::sqrt(total_energy / (half + 1));
|
std::vector<float> reg_db(half + 1);
|
||||||
float overall_db = 20.0f * std::log10(std::max(overall_rms, 1e-10f)) - 28.847f;
|
for (size_t i = 0; i <= half; i++) {
|
||||||
|
size_t lo = (i >= reg) ? i - reg : 0;
|
||||||
|
size_t hi = std::min(half, i + reg);
|
||||||
|
float e = 0.0f;
|
||||||
|
for (size_t k = lo; k <= hi; k++) e += mag[k] * mag[k];
|
||||||
|
float rms = std::sqrt(e / (hi - lo + 1u));
|
||||||
|
reg_db[i] = 20.0f * std::log10(std::max(rms, 1e-10f)) - 26.1f;
|
||||||
|
}
|
||||||
|
|
||||||
// Floor reduction from overall level
|
|
||||||
float floor_red = floor_func(overall_db);
|
|
||||||
|
|
||||||
// Bell curve parameters
|
|
||||||
float qeff = 1.54f * std::pow(std::max(sharpness_, 0.5f), 1.33f);
|
float qeff = 1.54f * std::pow(std::max(sharpness_, 0.5f), 1.33f);
|
||||||
float sens_weight = 6.02f * std::min(1.0f, 12.0f / 12.0f); // sens=12 → full
|
float sens_weight = 6.02f * std::min(1.0f, 12.0f / 12.0f); // sens=12 → full
|
||||||
|
|
||||||
for (size_t i = 0; i <= half; i++) {
|
for (size_t i = 0; i <= half; i++) {
|
||||||
float freq = static_cast<float>(i) * bin_hz;
|
// Level-dependent floor: louder bins cut deeper (matches soothe mask shape)
|
||||||
|
float floor_red = std::max(0.0f, floor_func(reg_db[i]));
|
||||||
|
|
||||||
// Boost from bell curve at band1 frequency (500 Hz)
|
// Total reduction in dB (no additive band bell — tone boost is implicit
|
||||||
float h = bell_curve(freq, 500.0f, qeff);
|
// via higher regional level at the tone frequency)
|
||||||
float boost = sens_weight * h;
|
float total_red = 1.041f * depth_ * floor_red;
|
||||||
|
|
||||||
// Total reduction in dB
|
|
||||||
float total_red = depth_ * (floor_red + boost);
|
|
||||||
|
|
||||||
// Convert to linear mask: mask = 10^(-total_red/20)
|
// Convert to linear mask: mask = 10^(-total_red/20)
|
||||||
mask[i] = std::pow(10.0f, -total_red / 20.0f);
|
mask[i] = std::pow(10.0f, -total_red / 20.0f);
|
||||||
|
|||||||
+12
-11
@@ -1,18 +1,27 @@
|
|||||||
#include "fft.hpp"
|
#include "fft.hpp"
|
||||||
#include "fft_stage.hpp"
|
#include "fft_stage.hpp"
|
||||||
|
#include "twiddle_loader.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace fft {
|
namespace fft {
|
||||||
|
|
||||||
|
// twiddle loader drops angles modulo — matches soothe: angles arrive as
|
||||||
|
// float pairs, loader computes cos/sin per float
|
||||||
void build_twiddle(FFTPlan* plan, double* scratch) {
|
void build_twiddle(FFTPlan* plan, double* scratch) {
|
||||||
uint32_t N = plan->N;
|
uint32_t N = plan->N;
|
||||||
uint32_t half = N / 2;
|
uint32_t half = N / 2;
|
||||||
|
std::vector<float> angles(half);
|
||||||
for (uint32_t k = 0; k < half; k++) {
|
for (uint32_t k = 0; k < half; k++) {
|
||||||
double angle = -2.0 * M_PI * k / N;
|
angles[k] = static_cast<float>(-2.0 * M_PI * k / N);
|
||||||
scratch[k * 2 + 0] = std::cos(angle);
|
}
|
||||||
scratch[k * 2 + 1] = std::sin(angle);
|
std::vector<double> cosv(half), sinv(half);
|
||||||
|
soothe::twiddle_load(angles.data(), cosv.data(), sinv.data(), half);
|
||||||
|
for (uint32_t k = 0; k < half; k++) {
|
||||||
|
scratch[k * 2 + 0] = cosv[k];
|
||||||
|
scratch[k * 2 + 1] = sinv[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +95,4 @@ void execute(const FFTPlan* plan, std::complex<double>* buf) {
|
|||||||
execute_forward(plan, buf);
|
execute_forward(plan, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_plan(FFTPlan* plan, uint32_t log2N) {
|
|
||||||
plan->log2N = log2N;
|
|
||||||
plan->N = 1U << log2N;
|
|
||||||
plan->stage_count = log2N;
|
|
||||||
plan->bit_reverse = 1;
|
|
||||||
plan->xor_mask = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-23
@@ -3,38 +3,35 @@
|
|||||||
|
|
||||||
namespace fft_stage {
|
namespace fft_stage {
|
||||||
|
|
||||||
|
// cplx_mul — complex elementwise multiply: out = in1 * in2 (conjugated 2nd)
|
||||||
void cplx_mul(double* out, const double* in1, const double* in2, uint32_t n) {
|
void cplx_mul(double* out, const double* in1, const double* in2, uint32_t n) {
|
||||||
for (uint32_t i = 0; i < n; i += 64) {
|
for (uint32_t i = 0; i < n * 2; i += 2) {
|
||||||
for (uint32_t j = 0; j < 4; j++) {
|
double re1 = in1[i + 0];
|
||||||
double re1 = in1[i + j*2 + 0];
|
double im1 = in1[i + 1];
|
||||||
double im1 = in1[i + j*2 + 1];
|
double re2 = in2[i + 0];
|
||||||
double re2 = in2[i + j*2 + 0];
|
double im2 = in2[i + 1];
|
||||||
double im2 = in2[i + j*2 + 1];
|
out[i + 0] = re1 * re2 - im1 * im2;
|
||||||
out[i + j*2 + 0] = re1*re2 - im1*im2;
|
out[i + 1] = re1 * im2 + im1 * re2;
|
||||||
out[i + j*2 + 1] = re1*im2 + im1*re2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stage_complex — reformat: read interleaved complex [re,im][n], write [re][n],[im][n]
|
||||||
void stage_complex(double* out, const double* in, const double* tw, uint32_t n) {
|
void stage_complex(double* out, const double* in, const double* tw, uint32_t n) {
|
||||||
for (uint32_t i = 0; i < n; i += 64) {
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
for (uint32_t j = 0; j < 4; j++) {
|
double re1 = in[i * 2 + 0];
|
||||||
double re1 = in[i + j*2 + 0];
|
double im1 = in[i * 2 + 1];
|
||||||
double im1 = in[i + j*2 + 1];
|
double re2 = tw[i * 2 + 0];
|
||||||
double re2 = tw[i + j*2 + 0];
|
double im2 = tw[i * 2 + 1];
|
||||||
double im2 = tw[i + j*2 + 1];
|
out[i * 2 + 0] = re1 * re2 - im1 * im2;
|
||||||
out[i + j*2 + 0] = re1*re2 - im1*im2;
|
out[i * 2 + 1] = re1 * im2 + im1 * re2;
|
||||||
out[i + j*2 + 1] = re1*im2 + im1*re2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stage_double — scalar multiply (FUN_180008500): out[i] = in[i] * tw[i]
|
||||||
void stage_double(double* out, const double* in, const double* tw, uint32_t n) {
|
void stage_double(double* out, const double* in, const double* tw, uint32_t n) {
|
||||||
for (uint32_t i = 0; i < n; i += 64) {
|
for (uint32_t i = 0; i < n; i++) {
|
||||||
for (uint32_t j = 0; j < 8; j++) {
|
out[i] = in[i] * tw[i];
|
||||||
out[i + j] = in[i + j] * tw[i + j];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+28
@@ -240,3 +240,31 @@
|
|||||||
2. **Bit-reverse permutation** — FUN_18003b6c0 с XOR-маской DAT_181c5e4e0
|
2. **Bit-reverse permutation** — FUN_18003b6c0 с XOR-маской DAT_181c5e4e0
|
||||||
3. **C++ транскрипция** — переписать fft.cpp с точным порядком FP-операций из декомпиляции
|
3. **C++ транскрипция** — переписать fft.cpp с точным порядком FP-операций из декомпиляции
|
||||||
4. **Верификация** — подать тон/синусоиду в тот же путь, что и Reaper-рендер
|
4. **Верификация** — подать тон/синусоиду в тот же путь, что и Reaper-рендер
|
||||||
|
|
||||||
|
### B.4 — Метрики на burst500 (2026-08-17) — BIG JUMP
|
||||||
|
- **Интеграция**: `build_twiddle` в fft.cpp теперь через `soothe::twiddle_load` (sincos_single,
|
||||||
|
Cody-Waite, 7-term poly); удалён дубль `init_plan` (конфликт с fft_plan.cpp), добавлен `<vector>`.
|
||||||
|
- **Stage-ядра** (fft_stage.cpp переписан: cplx_mul / stage_complex / stage_double = scalar mul
|
||||||
|
FUN_180008500) — починили **фазу**: corr −0.479 → **+0.995**, SNR −5.2 → **+19.8 dB** (shift 0),
|
||||||
|
амплитуда diff ±0.06 dB (ref −26.07, ours −26.13 dBFS).
|
||||||
|
- **Детектор (смена модели)**: убрана bell-boost (была НЕВЕРНА); маска soothe = **чисто
|
||||||
|
уровнезависимый floor**. Тон 258 Гц (громче) режется сильнее (−6.2 дБ), 500 Гц (−3.5 дБ),
|
||||||
|
выше 1 кГц ≈ 0 — региональный уровень (RMS по окну 16 бинов, калибровка −26.1 дБFS),
|
||||||
|
`mask = 10^(−1.041·depth·floor(level)/20)`.
|
||||||
|
- Итог: ref −26.07 / ours −26.13 dBFS, corr 0.99475, SNR 19.80 dB, diff −0.065 dB.
|
||||||
|
|
||||||
|
### B.5 — Детектор ДЕШИФРОВАН в soothe_mem.bin (2026-08-17) — KEY
|
||||||
|
- **Тело FUN_180535880/180536f90 в дампе = реальный дешифрованный SSE-код** (movsd/cvtpd2ps,
|
||||||
|
загрузка параметров из param_3, цикл по буферу), а НЕ PACE-стаб.
|
||||||
|
- Вызов **dispatch-shim 0x180001d00**: `mov [0x1826159a0],%rax; jmp *0x182616008[rax*8]`;
|
||||||
|
сейчас idx=4 → `0x180009860` (тонкая обёртка `e8 rel32`) → реальная **FUN_180040d40**.
|
||||||
|
- **Dispatch-таблица 0x182616008**: 223 разрешённых записи; реальные цели в `.text`
|
||||||
|
`0x18000a160..0x180074740` (13+5+8+6 функций по страницам), высокие `0x1817b900/181a69090/...`
|
||||||
|
= направляющие, не алгоритм.
|
||||||
|
- **КРИТИЧНО**: этих функций НЕТ в fun_map.txt/decomp_funs.txt (прежний Ghidra-анализ
|
||||||
|
был на зашифрованном `.vst3` и не развязал dispatch). .vst3 на диске (PE32+ 39.1M,
|
||||||
|
12 секций, base 0x180000000) СОВПАДАЕТ с дампом по байтам в `.text` (проверено
|
||||||
|
для 0x40d40/0x535880) фи(x) → декомпиляция дампа = декомпиляция реального кода.
|
||||||
|
- План: разметить таблицу → извлечь байты функций 0x18004xxxx (+0x18000a160..0x180074740)
|
||||||
|
→ objdump 0x180000000-adjust → транскрипция в dsp/detect.cpp → верификация SNR 19.8 дБ.
|
||||||
|
- Непустые ранее известные ядра (0x180040cc0..0x180041700) = 8 функций = полный алгоритм детектора.
|
||||||
|
|||||||
Reference in New Issue
Block a user