6#include "AudioTools/CoreAudio/AudioOutput.h"
7#include "AudioTools/CoreAudio/AudioBasic/Collections/Vector.h"
8#include "AudioTools/CoreAudio/Buffers.h"
9#include "AudioTools/AudioLibs/AudioFFT.h"
50template <
typename T =
int16_t,
size_t N = 3>
63 using WakeWordCallback = void (*)(
const char* name);
68 auto& fft_cfg = fft.
config();
70 fft_cfg.callback = fftResult;
73 void startRecording() {
78 Vector<FrequencyFrame<N>> stopRecording() {
85 void addTemplate(
const Vector<FrequencyFrame<N>>& frames,
86 float threshold_percent,
const char* name) {
89 t.threshold_percent = threshold_percent;
91 t.last_match_percent = 0.0f;
96 void setWakeWordCallback(WakeWordCallback cb) { _callback = cb; }
98 size_t write(
const uint8_t* buf,
size_t size)
override {
99 return p_fft->
write(buf, size);
102 static void fftResult(AudioFFTBase& fft) {
104 auto* self =
static_cast<WakeWordDetector<T,N>*
>(fft.config().ref);
106 FrequencyFrame<N> frame;
107 AudioFFTResult result[N];
108 fft.resultArray(result);
109 for (
size_t j = 0; j < N; j++) {
110 frame.top_freqs[j] = result[j].frequency;
112 self->_recent_frames.push_back(frame);
114 if (self->_is_recording) {
118 if (self->_recent_frames.size() > self->_max_template_len)
119 self->_recent_frames.erase(self->_recent_frames.begin());
120 for (
size_t i = 0; i < self->_templates.size(); ++i) {
121 Template& tmpl = self->_templates[i];
122 if (self->_recent_frames.size() >= tmpl.frames.size()) {
123 float percent = self->matchTemplate(tmpl);
124 if (percent >= tmpl.threshold_percent) {
125 if (self->_callback) self->_callback(tmpl.name);
139 WakeWordCallback _callback =
nullptr;
141 float matchTemplate(
Template& tmpl) {
144 for (
size_t i = 0; i < tmpl.
frames.size(); ++i) {
145 size_t frame_matches = 0;
146 for (
size_t j = 0; j < N; ++j) {
147 if (tmpl.
frames[i].top_freqs[j] ==
151 if (frame_matches >= (N >= 2 ? N - 1 : 1))
154 float percent = (tmpl.
frames.size() > 0)
155 ? (100.0f * matches / tmpl.
frames.size())