File size: 6,327 Bytes
139f25e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include "wake_word.hpp"

#include "model_runner.hpp"

#include <cmath>
#include <complex>
#include <cstring>
#include <utility>
#include <vector>

namespace {

constexpr int kNfft = 512;
constexpr int kFeatWidth = kNfft / 2 + 1;  // 257
constexpr int kNumMels = 26;
constexpr int kFrameLen = 512;
constexpr int kWindow = 64;
constexpr float kEps = 1.1920928955078125e-7f;  // reference log epsilon

// periodic-512 Hann (== reference window array)
std::vector<float> MakeHann() {
    std::vector<float> w(kFrameLen);
    for (int n = 0; n < kFrameLen; ++n) {
        w[n] = 0.5f * (1.0f - std::cos(2.0 * M_PI * n / kFrameLen));
    }
    return w;
}

float Hz2Mel(float hz) {
    return 2595.0f * std::log10(1.0f + hz / 700.0f);
}

struct MelBank {
    std::vector<float> coeff;
    std::vector<std::pair<int, int>> pos;  // (start, stop)
};

MelBank MakeMelBank(int nfilter, int low_freq, int high_freq, int samp_freq) {
    MelBank bank;
    const float lowmel = Hz2Mel(static_cast<float>(low_freq));
    const float highmel = Hz2Mel(static_cast<float>(high_freq));
    const float nyquist = samp_freq * 0.5f;
    std::vector<float> mel_points(nfilter + 2);
    for (int i = 0; i < nfilter + 2; ++i) {
        mel_points[i] = lowmel + i * (highmel - lowmel) / (nfilter + 1);
    }
    std::vector<float> bin_mels(kFeatWidth);
    for (int i = 0; i < kFeatWidth; ++i) {
        bin_mels[i] = Hz2Mel(i * nyquist / (kNfft / 2));
    }
    int off = 0;
    for (int i = 0; i < nfilter; ++i) {
        int start = -1, stop = -1;
        for (int j = 1; j < kFeatWidth; ++j) {  // bands_to_zero = 1
            const float lower =
                (bin_mels[j] - mel_points[i]) / (mel_points[i + 1] - mel_points[i]);
            const float upper =
                (mel_points[i + 2] - bin_mels[j]) / (mel_points[i + 2] - mel_points[i + 1]);
            const float temp = std::min(lower, upper);
            if (lower > 0 && start == -1) start = j;
            if (upper <= 0 && stop == -1) stop = j - 1;
            if (temp > 0.f) {
                bank.coeff.push_back(temp);
                ++off;
            }
        }
        bank.pos.emplace_back(start, stop);
    }
    (void)off;
    return bank;
}

// radix-2 iterative FFT (512 = 2^9)
void Fft(std::vector<std::complex<double>>& a) {
    const int n = static_cast<int>(a.size());
    for (int i = 1, j = 0; i < n; ++i) {
        int bit = n >> 1;
        for (; j & bit; bit >>= 1) j ^= bit;
        j ^= bit;
        if (i < j) std::swap(a[i], a[j]);
    }
    for (int len = 2; len <= n; len <<= 1) {
        const double ang = -2.0 * M_PI / len;
        const std::complex<double> wlen(std::cos(ang), std::sin(ang));
        for (int i = 0; i < n; i += len) {
            std::complex<double> w(1.0, 0.0);
            for (int k = 0; k < len / 2; ++k) {
                std::complex<double> u = a[i + k];
                std::complex<double> v = a[i + k + len / 2] * w;
                a[i + k] = u + v;
                a[i + k + len / 2] = u - v;
                w *= wlen;
            }
        }
    }
}

}  // namespace

struct WakeWordDetector::Impl {
    ModelRunner runner;
    std::vector<float> hann = MakeHann();
    MelBank mel = MakeMelBank(kNumMels, 80, 7000, 16000);
    std::vector<float> window;  // (kWindow, 26) ring, oldest first
    std::vector<float> scores;
    float threshold;
    int det_win;
    int mel_off = 0;

    Impl(const std::string& model_path, float th, int dw)
        : runner(model_path, "wakeup_axera"), threshold(th), det_win(dw),
          window(kWindow * kNumMels, 0.f) {
        for (const auto& p : mel.pos) mel_off += p.second - p.first + 1;
    }

    float FrameFbank(const int16_t* samples) {
        std::vector<std::complex<double>> buf(kNfft, 0.0);
        for (int i = 0; i < kFrameLen; ++i) {
            buf[i] = static_cast<double>(samples[i]) / 32768.0 * hann[i];
        }
        Fft(buf);
        std::vector<float> spec(kFeatWidth);
        spec[0] = static_cast<float>(std::abs(buf[0]));
        for (int i = 1; i < kFeatWidth - 1; ++i) {
            spec[i] = static_cast<float>(std::abs(buf[i]));
        }
        spec[kFeatWidth - 1] = static_cast<float>(std::abs(buf[kNfft / 2]));

        float out[kNumMels];
        int off = 0;
        for (int f = 0; f < kNumMels; ++f) {
            const int s = mel.pos[f].first, e = mel.pos[f].second;
            double acc = 0.0;
            for (int j = s; j <= e; ++j) {
                acc += spec[j] * mel.coeff[off++];
            }
            out[f] = std::log(static_cast<float>(acc) + kEps);
        }
        // Q6.10 reference quantization
        for (int f = 0; f < kNumMels; ++f) {
            const float c = std::max(-32.f, std::min(32.f, out[f]));
            const float q = std::trunc(c * 1024.0f + 0.5f) / 1024.0f;
            window[(kWindow - 1) * kNumMels + f] = q;
        }
        // model input: (1,26,64) last window
        std::vector<float> feed(kNumMels * kWindow);
        for (int t = 0; t < kWindow; ++t) {
            for (int f = 0; f < kNumMels; ++f) {
                feed[f * kWindow + t] = window[t * kNumMels + f];
            }
        }
        std::vector<std::vector<float>> inputs;
        inputs.push_back(std::move(feed));
        std::vector<std::vector<float>> outs = runner.Run(inputs);  // (1,2,64)
        return outs[0][1 * kWindow + (kWindow - 1)];  // logits[1, -1]
    }
};

WakeWordDetector::WakeWordDetector(const std::string& model_path, float threshold, int det_win)
    : impl_(new Impl(model_path, threshold, det_win)) {}

WakeWordDetector::~WakeWordDetector() { delete impl_; }

WakeWordDetector::FrameResult WakeWordDetector::ProcessFrame(const int16_t* samples) {
    std::memmove(impl_->window.data(), impl_->window.data() + kNumMels,
                 (kWindow - 1) * kNumMels * sizeof(float));
    impl_->scores.push_back(impl_->FrameFbank(samples));
    if (static_cast<int>(impl_->scores.size()) > impl_->det_win) {
        impl_->scores.erase(impl_->scores.begin());
    }
    FrameResult r;
    r.wake_score = impl_->scores.back();
    for (float s : impl_->scores) r.window_sum += s;
    r.triggered = r.window_sum > impl_->threshold;
    return r;
}

void WakeWordDetector::Reset() {
    std::fill(impl_->window.begin(), impl_->window.end(), 0.f);
    impl_->scores.clear();
}