Enhance buzzer detection logic to group intermittent matches with a 500 ms quiet tail, updating relevant configurations and documentation.
This commit is contained in:
@@ -63,7 +63,7 @@ idf.py menuconfig # "Door Buzzer Detector"
|
|||||||
* **ntfy**: server (default `https://ntfy.sh`), **topic** (pick something unguessable: anyone who knows it can read it),
|
* **ntfy**: server (default `https://ntfy.sh`), **topic** (pick something unguessable: anyone who knows it can read it),
|
||||||
optional heartbeat topic and interval (hours, 0 = off). Subscribe to the topic in the ntfy phone app.
|
optional heartbeat topic and interval (hours, 0 = off). Subscribe to the topic in the ntfy phone app.
|
||||||
* **Hardware**: pins, I2S slot, `Right shift` (default 14 = 4x digital gain for the quiet INMP441).
|
* **Hardware**: pins, I2S slot, `Right shift` (default 14 = 4x digital gain for the quiet INMP441).
|
||||||
* **Detector defaults**: threshold, loudness gate, N/M, cooldown (default 5 ms). The existing N-of-M sliding window smooths frame matches; because frames arrive every 32 ms, cooldown/re-arm timing is effectively frame-granular. All can also be changed live with `set` and persist in NVS.
|
* **Detector defaults**: threshold, loudness gate, N/M, and a 500 ms quiet tail for grouping intermittent matching frames into one buzz. Each new match within that tail extends the event; its reported duration runs from the first qualifying match through the last, excluding the final quiet tail. Duration is frame-granular (32 ms hops), and is included in the alert notification and `status`. Settings can also be changed live with `set` and persist in NVS.
|
||||||
* Console: defaults to the **UART** USB port. For the native USB port set *Component config > ESP System Settings > Channel for console output*
|
* Console: defaults to the **UART** USB port. For the native USB port set *Component config > ESP System Settings > Channel for console output*
|
||||||
to *USB Serial/JTAG*.
|
to *USB Serial/JTAG*.
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,8 @@ menu "Door Buzzer Detector"
|
|||||||
default 10
|
default 10
|
||||||
range 1 32
|
range 1 32
|
||||||
config BUZZ_COOLDOWN_MS
|
config BUZZ_COOLDOWN_MS
|
||||||
int "Cooldown after an alert (milliseconds)"
|
int "Quiet tail to group buzz frames into one event (milliseconds)"
|
||||||
default 5
|
default 500
|
||||||
range 0 86400000
|
range 0 86400000
|
||||||
config BUZZ_NOISE_MULT_X10
|
config BUZZ_NOISE_MULT_X10
|
||||||
int "Learn/adapt gate: noise floor multiplier x10"
|
int "Learn/adapt gate: noise floor multiplier x10"
|
||||||
|
|||||||
+13
-3
@@ -13,7 +13,8 @@
|
|||||||
static const char *TAG = "config";
|
static const char *TAG = "config";
|
||||||
#define NVS_NS "buzz"
|
#define NVS_NS "buzz"
|
||||||
#define CFG_KEY "cfg"
|
#define CFG_KEY "cfg"
|
||||||
#define CFG_MAGIC 0xB0220002u
|
#define CFG_MAGIC 0xB0220003u
|
||||||
|
#define CFG_MAGIC_PRE_500MS 0xB0220002u
|
||||||
#define CFG_MAGIC_PRE_COOLDOWN_MS 0xB0220001u
|
#define CFG_MAGIC_PRE_COOLDOWN_MS 0xB0220001u
|
||||||
|
|
||||||
typedef struct { uint32_t magic; app_config_t cfg; } cfg_blob_t;
|
typedef struct { uint32_t magic; app_config_t cfg; } cfg_blob_t;
|
||||||
@@ -33,7 +34,7 @@ static const param_t s_params[] = {
|
|||||||
P(min_rms, PT_FLOAT, 0.0, 32767.0, "loudness gate (int16 RMS)"),
|
P(min_rms, PT_FLOAT, 0.0, 32767.0, "loudness gate (int16 RMS)"),
|
||||||
P(win_n, PT_UINT, 1, 32, "sliding window length N (frames)"),
|
P(win_n, PT_UINT, 1, 32, "sliding window length N (frames)"),
|
||||||
P(win_m, PT_UINT, 1, 32, "matches M needed in window (<= N)"),
|
P(win_m, PT_UINT, 1, 32, "matches M needed in window (<= N)"),
|
||||||
P(cooldown_ms, PT_UINT, 0, 86400000, "cooldown after alert (ms)"),
|
P(cooldown_ms, PT_UINT, 0, 86400000, "quiet tail used to group buzzer frames (ms)"),
|
||||||
P(noise_mult, PT_FLOAT, 1.0, 100.0, "learn/adapt gate = noise floor x this"),
|
P(noise_mult, PT_FLOAT, 1.0, 100.0, "learn/adapt gate = noise floor x this"),
|
||||||
P(adapt_weight, PT_FLOAT, 0.0, 1.0, "weight of new template in adapt"),
|
P(adapt_weight, PT_FLOAT, 0.0, 1.0, "weight of new template in adapt"),
|
||||||
P(monitor_ms, PT_UINT, 10, 60000, "min interval between monitor lines (ms)"),
|
P(monitor_ms, PT_UINT, 10, 60000, "min interval between monitor lines (ms)"),
|
||||||
@@ -102,7 +103,16 @@ esp_err_t config_init(void)
|
|||||||
} else if (blob.magic == CFG_MAGIC_PRE_COOLDOWN_MS) {
|
} else if (blob.magic == CFG_MAGIC_PRE_COOLDOWN_MS) {
|
||||||
s_cfg = blob.cfg;
|
s_cfg = blob.cfg;
|
||||||
s_cfg.cooldown_ms = CONFIG_BUZZ_COOLDOWN_MS;
|
s_cfg.cooldown_ms = CONFIG_BUZZ_COOLDOWN_MS;
|
||||||
ESP_LOGI(TAG, "migrating saved config to millisecond cooldown");
|
ESP_LOGI(TAG, "migrating saved config to event-grouping cooldown");
|
||||||
|
err = save_locked();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
nvs_close(h);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
} else if (blob.magic == CFG_MAGIC_PRE_500MS) {
|
||||||
|
s_cfg = blob.cfg;
|
||||||
|
s_cfg.cooldown_ms = CONFIG_BUZZ_COOLDOWN_MS;
|
||||||
|
ESP_LOGI(TAG, "updating saved cooldown for buzz-event grouping");
|
||||||
err = save_locked();
|
err = save_locked();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
nvs_close(h);
|
nvs_close(h);
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ typedef struct {
|
|||||||
float min_rms; // loudness gate (int16 RMS); quieter frames never match
|
float min_rms; // loudness gate (int16 RMS); quieter frames never match
|
||||||
uint32_t win_n; // sliding window length (frames), 1..32
|
uint32_t win_n; // sliding window length (frames), 1..32
|
||||||
uint32_t win_m; // matches needed inside the window, 1..win_n
|
uint32_t win_m; // matches needed inside the window, 1..win_n
|
||||||
uint32_t cooldown_ms; // quiet period after an alert
|
uint32_t cooldown_ms; // quiet tail that groups matches into one buzz event
|
||||||
float noise_mult; // learn/adapt gate = noise floor * this
|
float noise_mult; // learn/adapt gate = noise floor * this
|
||||||
float adapt_weight; // weight of the NEW template in `adapt` (0..1)
|
float adapt_weight; // weight of the NEW template in `adapt` (0..1)
|
||||||
uint32_t monitor_ms; // min interval between `monitor` lines
|
uint32_t monitor_ms; // min interval between `monitor` lines
|
||||||
|
|||||||
+2
-1
@@ -128,10 +128,11 @@ static int cmd_status(int argc, char **argv)
|
|||||||
printf("template: %s, %lu frames%s\n", matcher_source_name(), (unsigned long)matcher_frame_count(),
|
printf("template: %s, %lu frames%s\n", matcher_source_name(), (unsigned long)matcher_frame_count(),
|
||||||
matcher_has_template() ? "" : " <-- run tools/make_template.py + rebuild, or `learn`");
|
matcher_has_template() ? "" : " <-- run tools/make_template.py + rebuild, or `learn`");
|
||||||
if (matcher_suggested_threshold(&sug)) printf(" template.h suggests threshold %.3f\n", sug);
|
if (matcher_suggested_threshold(&sug)) printf(" template.h suggests threshold %.3f\n", sug);
|
||||||
printf("detector: threshold %.3f, min_rms %.0f, window %lu/%lu, cooldown %lu ms\n", cfg.threshold, cfg.min_rms,
|
printf("detector: threshold %.3f, min_rms %.0f, window %lu/%lu, buzz quiet tail %lu ms\n", cfg.threshold, cfg.min_rms,
|
||||||
(unsigned long)cfg.win_m, (unsigned long)cfg.win_n, (unsigned long)cfg.cooldown_ms);
|
(unsigned long)cfg.win_m, (unsigned long)cfg.win_n, (unsigned long)cfg.cooldown_ms);
|
||||||
printf("last frame: rms %.1f, score %+.3f, match %d\n", ps.last_rms, ps.last_score, ps.last_match);
|
printf("last frame: rms %.1f, score %+.3f, match %d\n", ps.last_rms, ps.last_score, ps.last_match);
|
||||||
printf("triggers: %lu (last at uptime %lu s)\n", (unsigned long)ps.triggers, (unsigned long)ps.last_trigger_s);
|
printf("triggers: %lu (last at uptime %lu s)\n", (unsigned long)ps.triggers, (unsigned long)ps.last_trigger_s);
|
||||||
|
printf("last buzz: %lu ms (final quiet tail excluded)\n", (unsigned long)ps.last_buzz_ms);
|
||||||
printf("frames: processed %lu, captured %lu, dropped %lu\n", (unsigned long)ps.frames,
|
printf("frames: processed %lu, captured %lu, dropped %lu\n", (unsigned long)ps.frames,
|
||||||
(unsigned long)audio_in_frames_captured(), (unsigned long)audio_in_frames_dropped());
|
(unsigned long)audio_in_frames_captured(), (unsigned long)audio_in_frames_dropped());
|
||||||
printf("proc time: avg %lu us, max %lu us (budget %d us per frame)\n", (unsigned long)ps.proc_avg_us,
|
printf("proc time: avg %lu us, max %lu us (budget %d us per frame)\n", (unsigned long)ps.proc_avg_us,
|
||||||
|
|||||||
+49
-17
@@ -1,39 +1,71 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "detector.h"
|
#include "detector.h"
|
||||||
|
#include "feature_params.h"
|
||||||
|
|
||||||
void detector_init(detector_t *d) { memset(d, 0, sizeof(*d)); d->state = DET_IDLE; }
|
void detector_init(detector_t *d) { memset(d, 0, sizeof(*d)); d->state = DET_IDLE; }
|
||||||
void detector_reset(detector_t *d) { d->state = DET_IDLE; d->hist = 0; }
|
void detector_reset(detector_t *d)
|
||||||
|
{
|
||||||
|
d->state = DET_IDLE;
|
||||||
|
d->hist = 0;
|
||||||
|
d->frame_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool detector_is_match(float rms, float score, float min_rms, float threshold)
|
bool detector_is_match(float rms, float score, float min_rms, float threshold)
|
||||||
{
|
{
|
||||||
return rms >= min_rms && score >= threshold;
|
return rms >= min_rms && score >= threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg)
|
static void update_idle(detector_t *d, bool match, uint32_t now_ms,
|
||||||
|
uint32_t n, uint32_t m)
|
||||||
|
{
|
||||||
|
uint32_t slot = d->frame_index % 32;
|
||||||
|
d->match_time_ms[slot] = now_ms;
|
||||||
|
d->hist = (d->hist << 1) | (match ? 1u : 0u);
|
||||||
|
|
||||||
|
uint32_t mask = (n == 32) ? 0xFFFFFFFFu : ((1u << n) - 1u);
|
||||||
|
if ((uint32_t)__builtin_popcount(d->hist & mask) >= m) {
|
||||||
|
uint32_t matches_seen = 0;
|
||||||
|
for (uint32_t offset = 0; offset < n; offset++) {
|
||||||
|
if ((d->hist & (1u << offset)) && ++matches_seen == m) {
|
||||||
|
uint32_t first_slot = (d->frame_index - offset) % 32;
|
||||||
|
d->buzz_start_ms = d->match_time_ms[first_slot];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->last_match_ms = now_ms;
|
||||||
|
d->state = DET_TRIGGERED;
|
||||||
|
d->triggers++;
|
||||||
|
}
|
||||||
|
d->frame_index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg,
|
||||||
|
uint32_t *buzz_duration_ms)
|
||||||
{
|
{
|
||||||
uint32_t n = cfg->win_n < 1 ? 1 : (cfg->win_n > 32 ? 32 : cfg->win_n);
|
uint32_t n = cfg->win_n < 1 ? 1 : (cfg->win_n > 32 ? 32 : cfg->win_n);
|
||||||
uint32_t m = cfg->win_m < 1 ? 1 : (cfg->win_m > n ? n : cfg->win_m);
|
uint32_t m = cfg->win_m < 1 ? 1 : (cfg->win_m > n ? n : cfg->win_m);
|
||||||
|
|
||||||
switch (d->state) {
|
switch (d->state) {
|
||||||
case DET_IDLE: {
|
case DET_IDLE:
|
||||||
d->hist = (d->hist << 1) | (match ? 1u : 0u);
|
update_idle(d, match, now_ms, n, m);
|
||||||
uint32_t mask = (n == 32) ? 0xFFFFFFFFu : ((1u << n) - 1u);
|
return false;
|
||||||
if ((uint32_t)__builtin_popcount(d->hist & mask) >= m) {
|
|
||||||
d->state = DET_TRIGGERED;
|
|
||||||
d->triggers++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DET_TRIGGERED:
|
case DET_TRIGGERED:
|
||||||
d->state = DET_COOLDOWN;
|
|
||||||
d->cooldown_start_ms = now_ms;
|
|
||||||
d->hist = 0;
|
|
||||||
break;
|
|
||||||
case DET_COOLDOWN:
|
case DET_COOLDOWN:
|
||||||
if ((uint32_t)(now_ms - d->cooldown_start_ms) >= cfg->cooldown_ms) {
|
if (match && (uint32_t)(now_ms - d->last_match_ms) < cfg->cooldown_ms) {
|
||||||
|
d->last_match_ms = now_ms;
|
||||||
|
d->state = DET_COOLDOWN;
|
||||||
|
} else {
|
||||||
|
d->state = DET_COOLDOWN;
|
||||||
|
if ((uint32_t)(now_ms - d->last_match_ms) < cfg->cooldown_ms) break;
|
||||||
|
d->last_completed_start_ms = d->buzz_start_ms;
|
||||||
|
if (buzz_duration_ms) {
|
||||||
|
*buzz_duration_ms = (uint32_t)(d->last_match_ms - d->buzz_start_ms) +
|
||||||
|
(1000u * HOP_LEN / FS_HZ);
|
||||||
|
}
|
||||||
d->state = DET_IDLE;
|
d->state = DET_IDLE;
|
||||||
d->hist = 0;
|
d->hist = 0;
|
||||||
|
if (match) update_idle(d, true, now_ms, n, m);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-5
@@ -14,7 +14,11 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
det_state_t state;
|
det_state_t state;
|
||||||
uint32_t hist; // bit 0 = newest frame, 1 = matched
|
uint32_t hist; // bit 0 = newest frame, 1 = matched
|
||||||
uint32_t cooldown_start_ms;
|
uint32_t frame_index;
|
||||||
|
uint32_t match_time_ms[32];
|
||||||
|
uint32_t buzz_start_ms;
|
||||||
|
uint32_t last_match_ms;
|
||||||
|
uint32_t last_completed_start_ms;
|
||||||
uint32_t triggers;
|
uint32_t triggers;
|
||||||
} detector_t;
|
} detector_t;
|
||||||
|
|
||||||
@@ -24,8 +28,9 @@ void detector_reset(detector_t *d); // back to IDLE with empty history
|
|||||||
// Loudness gate AND score >= threshold.
|
// Loudness gate AND score >= threshold.
|
||||||
bool detector_is_match(float rms, float score, float min_rms, float threshold);
|
bool detector_is_match(float rms, float score, float min_rms, float threshold);
|
||||||
|
|
||||||
// Feed one frame. Returns true exactly once per trigger (IDLE -> TRIGGERED).
|
// Feed one frame. Returns true when a buzz event ends and writes its duration.
|
||||||
// TRIGGERED lasts one frame, then COOLDOWN until cooldown_ms elapsed, then IDLE
|
// Matches within cooldown_ms extend the same event; the final quiet tail is
|
||||||
// with a cleared history (the buzzer's tail cannot re-trigger).
|
// excluded from its duration.
|
||||||
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg);
|
bool detector_update(detector_t *d, bool match, uint32_t now_ms, const detector_cfg_t *cfg,
|
||||||
|
uint32_t *buzz_duration_ms);
|
||||||
const char *detector_state_name(det_state_t s);
|
const char *detector_state_name(det_state_t s);
|
||||||
|
|||||||
+15
-6
@@ -92,11 +92,12 @@ static void processing_task(void *arg)
|
|||||||
const app_mode_t mode = calibration_get_mode();
|
const app_mode_t mode = calibration_get_mode();
|
||||||
const bool match = has_tpl && detector_is_match(rms, score, cfg.min_rms, cfg.threshold);
|
const bool match = has_tpl && detector_is_match(rms, score, cfg.min_rms, cfg.threshold);
|
||||||
|
|
||||||
bool trig = false;
|
bool buzz_ended = false;
|
||||||
|
uint32_t buzz_duration_ms = 0;
|
||||||
if (mode == MODE_NORMAL) {
|
if (mode == MODE_NORMAL) {
|
||||||
if (has_tpl) {
|
if (has_tpl) {
|
||||||
detector_cfg_t dc = { cfg.win_n, cfg.win_m, cfg.cooldown_ms };
|
detector_cfg_t dc = { cfg.win_n, cfg.win_m, cfg.cooldown_ms };
|
||||||
trig = detector_update(&det, match, now_ms, &dc);
|
buzz_ended = detector_update(&det, match, now_ms, &dc, &buzz_duration_ms);
|
||||||
} else {
|
} else {
|
||||||
detector_reset(&det);
|
detector_reset(&det);
|
||||||
}
|
}
|
||||||
@@ -104,9 +105,13 @@ static void processing_task(void *arg)
|
|||||||
calibration_on_frame(fp, valid, rms, score); // detector paused: no alerts
|
calibration_on_frame(fp, valid, rms, score); // detector paused: no alerts
|
||||||
detector_reset(&det);
|
detector_reset(&det);
|
||||||
}
|
}
|
||||||
if (trig) {
|
if (buzz_ended) {
|
||||||
ESP_LOGW(TAG, "BUZZER DETECTED (rms %.0f, score %.3f)", rms, score);
|
char message[96];
|
||||||
notifier_send(NOTIF_ALERT, "Buzzer detected");
|
snprintf(message, sizeof(message), "Buzzer detected: %lu ms",
|
||||||
|
(unsigned long)buzz_duration_ms);
|
||||||
|
ESP_LOGW(TAG, "BUZZER EVENT ended, detected duration %lu ms",
|
||||||
|
(unsigned long)buzz_duration_ms);
|
||||||
|
notifier_send(NOTIF_ALERT, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
portENTER_CRITICAL(&s_lock);
|
portENTER_CRITICAL(&s_lock);
|
||||||
@@ -118,7 +123,11 @@ static void processing_task(void *arg)
|
|||||||
s_stats.last_match = match;
|
s_stats.last_match = match;
|
||||||
s_stats.state = det.state;
|
s_stats.state = det.state;
|
||||||
s_stats.triggers = det.triggers;
|
s_stats.triggers = det.triggers;
|
||||||
if (trig) s_stats.last_trigger_s = now_ms / 1000 ? now_ms / 1000 : 1;
|
if (buzz_ended) {
|
||||||
|
s_stats.last_trigger_s = det.last_completed_start_ms / 1000;
|
||||||
|
if (s_stats.last_trigger_s == 0) s_stats.last_trigger_s = 1;
|
||||||
|
s_stats.last_buzz_ms = buzz_duration_ms;
|
||||||
|
}
|
||||||
memcpy(s_last_fp, fp, sizeof(s_last_fp));
|
memcpy(s_last_fp, fp, sizeof(s_last_fp));
|
||||||
portEXIT_CRITICAL(&s_lock);
|
portEXIT_CRITICAL(&s_lock);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ typedef struct {
|
|||||||
det_state_t state;
|
det_state_t state;
|
||||||
uint32_t triggers;
|
uint32_t triggers;
|
||||||
uint32_t last_trigger_s; // uptime seconds of the last trigger, 0 = never
|
uint32_t last_trigger_s; // uptime seconds of the last trigger, 0 = never
|
||||||
|
uint32_t last_buzz_ms; // detected duration, excluding final quiet tail
|
||||||
} proc_stats_t;
|
} proc_stats_t;
|
||||||
|
|
||||||
esp_err_t processing_start(void);
|
esp_err_t processing_start(void);
|
||||||
|
|||||||
+37
-14
@@ -24,21 +24,44 @@ int main(void)
|
|||||||
CHECK(!features_compute(&c2, z, f2, &r2) && r2 == 0.0f, "silence -> invalid, rms 0");
|
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");
|
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 starts after N-of-M matches, groups matches within 500 ms,
|
||||||
|
// includes intermittent gaps, and excludes the final quiet tail.
|
||||||
detector_t d; detector_init(&d);
|
detector_t d; detector_init(&d);
|
||||||
detector_cfg_t cfg = { 15, 10, 20000 };
|
detector_cfg_t cfg = { 1, 1, 500 };
|
||||||
uint32_t t = 0; int trig = 0, first = -1;
|
uint32_t duration = 0;
|
||||||
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(!detector_update(&d, true, 0, &cfg, &duration), "event remains open after first match");
|
||||||
CHECK(trig == 1 && first == 14, "single trigger at 10th match (got %d trig, first %d)", trig, first);
|
CHECK(d.state == DET_TRIGGERED && d.triggers == 1, "event starts on match");
|
||||||
CHECK(d.state == DET_COOLDOWN, "cooldown after trigger");
|
CHECK(!detector_update(&d, false, 100, &cfg, &duration), "intermittent gap remains in event");
|
||||||
for (int i = 0; i < 100; i++, t += 32) CHECK(!detector_update(&d, true, t, &cfg), "no trigger in cooldown");
|
CHECK(!detector_update(&d, true, 400, &cfg, &duration), "match within quiet tail extends event");
|
||||||
t += 20000; detector_update(&d, false, t, &cfg);
|
CHECK(!detector_update(&d, false, 600, &cfg, &duration), "event remains open before tail expires");
|
||||||
CHECK(d.state == DET_IDLE, "back to idle");
|
CHECK(detector_update(&d, false, 900, &cfg, &duration), "event closes at 500 ms after last match");
|
||||||
int n = 0; for (int i = 0; i < 12; i++, t += 32) n += detector_update(&d, true, t, &cfg);
|
CHECK(duration == 432, "duration includes gap, excludes final quiet tail (got %lu ms)",
|
||||||
CHECK(n == 1, "re-arms and triggers again");
|
(unsigned long)duration);
|
||||||
// sparse matches never trigger
|
CHECK(d.state == DET_IDLE, "returns to idle after event closes");
|
||||||
detector_reset(&d); n = 0;
|
CHECK(!detector_update(&d, true, 932, &cfg, &duration), "next event starts after prior event closes");
|
||||||
for (int i = 0; i < 300; i++, t += 32) n += detector_update(&d, i % 3 == 0, t, &cfg);
|
CHECK(detector_update(&d, false, 1432, &cfg, &duration), "single-frame event closes");
|
||||||
|
CHECK(duration == (1000u * HOP_LEN / FS_HZ), "single-frame duration is one hop (got %lu ms)",
|
||||||
|
(unsigned long)duration);
|
||||||
|
|
||||||
|
detector_reset(&d);
|
||||||
|
cfg = (detector_cfg_t){ 5, 3, 500 };
|
||||||
|
CHECK(!detector_update(&d, true, 0, &cfg, &duration), "first of three matches");
|
||||||
|
CHECK(!detector_update(&d, false, 32, &cfg, &duration), "intervening non-match");
|
||||||
|
CHECK(!detector_update(&d, true, 64, &cfg, &duration), "second match");
|
||||||
|
CHECK(!detector_update(&d, true, 96, &cfg, &duration), "third match starts event");
|
||||||
|
CHECK(d.buzz_start_ms == 0, "event start is the earliest qualifying match");
|
||||||
|
CHECK(detector_update(&d, false, 608, &cfg, &duration), "N-of-M event ends after quiet tail");
|
||||||
|
CHECK(duration == 128, "N-of-M duration includes one-hop extent (got %lu ms)",
|
||||||
|
(unsigned long)duration);
|
||||||
|
|
||||||
|
// Sparse matches must not satisfy 10-of-15.
|
||||||
|
cfg = (detector_cfg_t){ 15, 10, 500 };
|
||||||
|
int n = 0;
|
||||||
|
detector_reset(&d);
|
||||||
|
uint32_t t = 0;
|
||||||
|
for (int i = 0; i < 300; i++, t += 32) {
|
||||||
|
n += detector_update(&d, i % 3 == 0, t, &cfg, &duration);
|
||||||
|
}
|
||||||
CHECK(n == 0, "1-in-3 matches must not trigger 10/15");
|
CHECK(n == 0, "1-in-3 matches must not trigger 10/15");
|
||||||
printf(fails ? "HOST TEST FAILED\n" : "HOST TEST PASSED\n");
|
printf(fails ? "HOST TEST FAILED\n" : "HOST TEST PASSED\n");
|
||||||
return fails != 0;
|
return fails != 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user