Line data Source code
1 : #include "audio/dsp/spectrum_analyzer.h"
2 :
3 : #include <algorithm>
4 : #include <cmath>
5 :
6 : namespace Amplitron {
7 :
8 : namespace {
9 :
10 : constexpr float kMinHz = 20.0f;
11 : constexpr float kMaxHz = 20000.0f;
12 : constexpr float kMinDb = -90.0f;
13 : constexpr float kMaxDb = 0.0f;
14 :
15 27840 : inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
16 :
17 : } // namespace
18 :
19 60 : SpectrumAnalyzer::SpectrumAnalyzer() {
20 92205 : for (int i = 0; i < FFT_SIZE; ++i) {
21 92160 : const float phase = (TWO_PI * static_cast<float>(i)) / static_cast<float>(FFT_SIZE - 1);
22 92160 : window_[i] = 0.5f * (1.0f - std::cos(phase));
23 30720 : }
24 :
25 45 : smoothed_input_db_.fill(kMinDb);
26 45 : smoothed_output_db_.fill(kMinDb);
27 45 : input_peak_db_.fill(kMinDb);
28 45 : output_peak_db_.fill(kMinDb);
29 45 : }
30 :
31 108 : void SpectrumAnalyzer::run_fft(std::array<std::complex<float>, FFT_SIZE>& data) const {
32 221184 : for (int i = 1, j = 0; i < FFT_SIZE; ++i) {
33 147384 : int bit = FFT_SIZE >> 1;
34 440964 : for (; (j & bit) != 0; bit >>= 1) {
35 219888 : j ^= bit;
36 73296 : }
37 221076 : j ^= bit;
38 221076 : if (i < j) {
39 107136 : std::swap(data[i], data[j]);
40 35712 : }
41 73692 : }
42 :
43 1296 : for (int len = 2; len <= FFT_SIZE; len <<= 1) {
44 1188 : const float angle = -TWO_PI / static_cast<float>(len);
45 1188 : const std::complex<float> wlen(std::cos(angle), std::sin(angle));
46 222264 : for (int i = 0; i < FFT_SIZE; i += len) {
47 221076 : std::complex<float> w(1.0f, 0.0f);
48 221076 : const int half = len >> 1;
49 1437588 : for (int j = 0; j < half; ++j) {
50 1216512 : const std::complex<float> u = data[i + j];
51 1216512 : const std::complex<float> v = data[i + j + half] * w;
52 1216512 : data[i + j] = u + v;
53 1216512 : data[i + j + half] = u - v;
54 1216512 : w *= wlen;
55 405504 : }
56 73692 : }
57 396 : }
58 108 : }
59 :
60 132 : void SpectrumAnalyzer::compute_spectrum_bars(const float* samples, int sample_rate,
61 : std::array<float, DISPLAY_BARS>& bars_db) {
62 132 : if (!samples || sample_rate <= 0) {
63 24 : bars_db.fill(kMinDb);
64 24 : return;
65 : }
66 :
67 221292 : for (int i = 0; i < FFT_SIZE; ++i) {
68 221184 : fft_work_[i] = std::complex<float>(samples[i] * window_[i], 0.0f);
69 73728 : }
70 :
71 108 : run_fft(fft_work_);
72 :
73 108 : std::array<float, FFT_BINS> mags_db{};
74 108 : mags_db.fill(kMinDb);
75 :
76 72 : const float norm = 2.0f / static_cast<float>(FFT_SIZE);
77 110592 : for (int i = 1; i < FFT_BINS; ++i) {
78 110484 : const float mag = std::abs(fft_work_[i]) * norm;
79 115718 : mags_db[i] = clamp(20.0f * std::log10(std::max(mag, 1e-8f)), kMinDb, kMaxDb);
80 36828 : }
81 :
82 10476 : for (int bar = 0; bar < DISPLAY_BARS; ++bar) {
83 10368 : const float t0 = static_cast<float>(bar) / static_cast<float>(DISPLAY_BARS);
84 10368 : const float t1 = static_cast<float>(bar + 1) / static_cast<float>(DISPLAY_BARS);
85 10368 : const float hz0 = std::pow(10.0f, lerp(std::log10(kMinHz), std::log10(kMaxHz), t0));
86 10368 : const float hz1 = std::pow(10.0f, lerp(std::log10(kMinHz), std::log10(kMaxHz), t1));
87 :
88 10368 : int bin0 = static_cast<int>(hz0 * FFT_SIZE / static_cast<float>(sample_rate));
89 10368 : int bin1 = static_cast<int>(hz1 * FFT_SIZE / static_cast<float>(sample_rate));
90 10368 : bin0 = std::max(1, std::min(bin0, FFT_BINS - 1));
91 10368 : bin1 = std::max(bin0 + 1, std::min(bin1, FFT_BINS));
92 :
93 10368 : float peak_db = kMinDb;
94 103578 : for (int b = bin0; b < bin1; ++b) {
95 93210 : peak_db = std::max(peak_db, mags_db[b]);
96 31070 : }
97 10368 : bars_db[bar] = peak_db;
98 3456 : }
99 44 : }
100 :
101 66 : void SpectrumAnalyzer::update(const float* input_samples, const float* output_samples,
102 : int sample_rate, float dt_seconds) {
103 66 : std::array<float, DISPLAY_BARS> input_db{};
104 66 : std::array<float, DISPLAY_BARS> output_db{};
105 66 : compute_spectrum_bars(input_samples, sample_rate, input_db);
106 66 : compute_spectrum_bars(output_samples, sample_rate, output_db);
107 :
108 66 : const float smooth_alpha = 0.26f;
109 66 : const float peak_decay_db_per_sec = 30.0f;
110 66 : const float decay = peak_decay_db_per_sec * std::max(dt_seconds, 1.0f / 240.0f);
111 :
112 6402 : for (int i = 0; i < DISPLAY_BARS; ++i) {
113 6336 : smoothed_input_db_[i] = lerp(smoothed_input_db_[i], input_db[i], smooth_alpha);
114 6336 : smoothed_output_db_[i] = lerp(smoothed_output_db_[i], output_db[i], smooth_alpha);
115 :
116 6336 : input_peak_db_[i] = std::max(smoothed_input_db_[i], input_peak_db_[i] - decay);
117 6336 : output_peak_db_[i] = std::max(smoothed_output_db_[i], output_peak_db_[i] - decay);
118 2112 : }
119 66 : }
120 :
121 : } // namespace Amplitron
|