// Keyword-filler Viterbi spotter over streaming logits - C port of // phoneme_engine/decoder.py (KeywordSpotter). Same states, gates and // scoring; consumes raw logits (only within-frame differences matter). #pragma once #include #ifdef __cplusplus extern "C" { #endif #define PWW_DEC_MAX_PHONES 24 #define PWW_DEC_MAX_STATES (3 * PWW_DEC_MAX_PHONES + 1) #define PWW_DEC_MAX_ALLOWED 3 typedef struct { int n_phones; int n_states; uint8_t labels[PWW_DEC_MAX_STATES]; // class id per state uint8_t n_allowed[PWW_DEC_MAX_STATES]; uint8_t allowed[PWW_DEC_MAX_STATES][PWW_DEC_MAX_ALLOWED]; int8_t preds[PWW_DEC_MAX_STATES][2]; // predecessor ids, -1 unused uint8_t entry[PWW_DEC_MAX_STATES]; int min_dur, max_dur; float threshold; float strong_margin; // 1.5 float strong_ratio; // 0.5 int refractory; // frames // runtime state float rel[PWW_DEC_MAX_STATES]; int32_t start[PWW_DEC_MAX_STATES]; int32_t pframes[PWW_DEC_MAX_STATES]; int32_t strong[PWW_DEC_MAX_STATES]; int cooldown; int32_t t; float best_seen; // best gate-passing score since last clear (diag) } pww_spotter_t; // phone_ids: sequence of class ids (1..39, from PWW_PHONES order +1). // Returns 0 on success, -1 if too long/short. int pww_spotter_init(pww_spotter_t *sp, const uint8_t *phone_ids, int n_phones, float threshold); void pww_spotter_reset(pww_spotter_t *sp); // One frame of logits (PWW_NUM_CLASSES floats). Returns 1 and fills // score/dur when the keyword fires, else 0. int pww_spotter_step(pww_spotter_t *sp, const float *logits, float *score_out, int *dur_out); #ifdef __cplusplus } #endif