vibe code the initial impl

This commit is contained in:
2026-10-13 19:47:46 -05:00
parent 7da1c2119d
commit 7862a09858
39 changed files with 2867 additions and 157 deletions
+9
View File
@@ -0,0 +1,9 @@
# Host test (optional, no ESP-IDF needed)
Compiles the real `main/fingerprint.c` and `main/detector.c` on your PC with a stub FFT and checks
(1) C-vs-Python fingerprint parity on `test_vector.h`, (2) silence/NaN safety, (3) the N-of-M + cooldown state machine.
python tools/make_template.py --buzz your.wav # writes main/test_vector.h
cd tools/host_test
gcc -Wall -I stubs -iquote ../../main host_test.c ../../main/fingerprint.c ../../main/detector.c -lm -o host_test
./host_test
+45
View File
@@ -0,0 +1,45 @@
// PC test of the REAL fingerprint.c and detector.c.
// gcc -I stubs -iquote ../../main host_test.c ../../main/fingerprint.c ../../main/detector.c -lm -o host_test
#include <math.h>
#include <stdio.h>
#include "fingerprint.h"
#include "detector.h"
#include "test_vector.h"
static int fails;
#define CHECK(c, ...) do { if (!(c)) { printf("FAIL: " __VA_ARGS__); printf("\n"); fails++; } } while (0)
int main(void)
{
CHECK(features_init() == ESP_OK, "features_init");
#if TEST_VECTOR_VALID
static features_ctx_t ctx; float fp[NUM_BANDS], rms;
bool ok = features_compute(&ctx, TEST_VECTOR_PCM, fp, &rms);
float me = 0; for (int b = 0; b < NUM_BANDS; b++) { float e = fabsf(fp[b] - TEST_VECTOR_FP[b]); if (e > me) me = e; }
printf("parity: ok=%d max|fp err|=%.2e rms C=%.4f py=%.4f\n", ok, me, rms, TEST_VECTOR_RMS);
CHECK(ok && me < 1e-3f, "fingerprint parity");
#endif
// degenerate inputs must not produce NaN
static int16_t z[FRAME_LEN]; static features_ctx_t c2; float f2[NUM_BANDS], r2;
CHECK(!features_compute(&c2, z, f2, &r2) && r2 == 0.0f, "silence -> invalid, rms 0");
for (int b = 0; b < NUM_BANDS; b++) CHECK(f2[b] == 0.0f, "silence fp zero");
// detector: 10-of-15 triggers once, cooldown 20 s, then re-arms
detector_t d; detector_init(&d);
detector_cfg_t cfg = { 15, 10, 20000 };
uint32_t t = 0; int trig = 0, first = -1;
for (int i = 0; i < 40; i++, t += 32) if (detector_update(&d, i >= 5 && i < 30, t, &cfg)) { trig++; if (first < 0) first = i; }
CHECK(trig == 1 && first == 14, "single trigger at 10th match (got %d trig, first %d)", trig, first);
CHECK(d.state == DET_COOLDOWN, "cooldown after trigger");
for (int i = 0; i < 100; i++, t += 32) CHECK(!detector_update(&d, true, t, &cfg), "no trigger in cooldown");
t += 20000; detector_update(&d, false, t, &cfg);
CHECK(d.state == DET_IDLE, "back to idle");
int n = 0; for (int i = 0; i < 12; i++, t += 32) n += detector_update(&d, true, t, &cfg);
CHECK(n == 1, "re-arms and triggers again");
// sparse matches never trigger
detector_reset(&d); n = 0;
for (int i = 0; i < 300; i++, t += 32) n += detector_update(&d, i % 3 == 0, t, &cfg);
CHECK(n == 0, "1-in-3 matches must not trigger 10/15");
printf(fails ? "HOST TEST FAILED\n" : "HOST TEST PASSED\n");
return fails != 0;
}
+26
View File
@@ -0,0 +1,26 @@
// Host stand-in for esp-dsp: float32 radix-2 FFT with the same call sequence
// (fft2r then bit_rev). Output order is natural, so bit_rev is a no-op here.
#pragma once
#include <math.h>
#include "esp_err.h"
static inline esp_err_t dsps_fft2r_init_fc32(float *t, int n) { (void)t; (void)n; return ESP_OK; }
static inline esp_err_t dsps_fft2r_fc32(float *d, int n)
{
for (int i = 1, j = 0; i < n; i++) { // bit-reverse permutation
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j ^= bit;
if (i < j) { float a = d[2*i], b = d[2*i+1]; d[2*i] = d[2*j]; d[2*i+1] = d[2*j+1]; d[2*j] = a; d[2*j+1] = b; }
}
for (int len = 2; len <= n; len <<= 1) {
float ang = -2.0f * (float)M_PI / (float)len;
for (int i = 0; i < n; i += len) for (int k = 0; k < len / 2; k++) {
float wr = cosf(ang * k), wi = sinf(ang * k);
int a = i + k, b = i + k + len / 2;
float xr = d[2*b] * wr - d[2*b+1] * wi, xi = d[2*b] * wi + d[2*b+1] * wr;
d[2*b] = d[2*a] - xr; d[2*b+1] = d[2*a+1] - xi; d[2*a] += xr; d[2*a+1] += xi;
}
}
return ESP_OK;
}
static inline esp_err_t dsps_bit_rev_fc32(float *d, int n) { (void)d; (void)n; return ESP_OK; }
+6
View File
@@ -0,0 +1,6 @@
#pragma once
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_INVALID_ARG 0x102
static inline const char *esp_err_to_name(esp_err_t e) { return e == ESP_OK ? "ESP_OK" : "ERR"; }
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <stdio.h>
#define ESP_LOGE(t, f, ...) fprintf(stderr, "E %s: " f "\n", t, ##__VA_ARGS__)
#define ESP_LOGW(t, f, ...) fprintf(stderr, "W %s: " f "\n", t, ##__VA_ARGS__)
#define ESP_LOGI(t, f, ...) fprintf(stderr, "I %s: " f "\n", t, ##__VA_ARGS__)