Line data Source code
1 : #include "audio/dsp/spectrum_analyzer.h"
2 : #include <algorithm>
3 : #include <cmath>
4 :
5 : namespace Amplitron {
6 :
7 : namespace {
8 :
9 : constexpr float kMinHz = 20.0f;
10 : constexpr float kMaxHz = 20000.0f;
11 : constexpr float kMinDb = -90.0f;
12 : constexpr float kMaxDb = 0.0f;
13 :
14 29280 : inline float lerp(float a, float b, float t) {
15 29280 : return a + (b - a) * t;
16 : }
17 :
18 : } // namespace
19 :
20 1194 : SpectrumAnalyzer::SpectrumAnalyzer() {
21 1827708 : for (int i = 0; i < FFT_SIZE; ++i) {
22 1826816 : const float phase = (TWO_PI * static_cast<float>(i)) / static_cast<float>(FFT_SIZE - 1);
23 1826816 : window_[i] = 0.5f * (1.0f - std::cos(phase));
24 618496 : }
25 :
26 892 : smoothed_input_db_.fill(kMinDb);
27 892 : smoothed_output_db_.fill(kMinDb);
28 892 : input_peak_db_.fill(kMinDb);
29 892 : output_peak_db_.fill(kMinDb);
30 892 : }
31 :
32 114 : void SpectrumAnalyzer::run_fft(std::array<std::complex<float>, FFT_SIZE>& data) const {
33 233472 : for (int i = 1, j = 0; i < FFT_SIZE; ++i) {
34 155572 : int bit = FFT_SIZE >> 1;
35 465462 : for (; (j & bit) != 0; bit >>= 1) {
36 232104 : j ^= bit;
37 77368 : }
38 233358 : j ^= bit;
39 233358 : if (i < j) {
40 113088 : std::swap(data[i], data[j]);
41 37696 : }
42 77786 : }
43 :
44 1368 : for (int len = 2; len <= FFT_SIZE; len <<= 1) {
45 1254 : const float angle = -TWO_PI / static_cast<float>(len);
46 1254 : const std::complex<float> wlen(std::cos(angle), std::sin(angle));
47 234612 : for (int i = 0; i < FFT_SIZE; i += len) {
48 233358 : std::complex<float> w(1.0f, 0.0f);
49 233358 : const int half = len >> 1;
50 1517454 : for (int j = 0; j < half; ++j) {
51 1284096 : const std::complex<float> u = data[i + j];
52 1284096 : const std::complex<float> v = data[i + j + half] * w;
53 1284096 : data[i + j] = u + v;
54 1284096 : data[i + j + half] = u - v;
55 1284096 : w *= wlen;
56 428032 : }
57 77786 : }
58 418 : }
59 114 : }
60 :
61 138 : void SpectrumAnalyzer::compute_spectrum_bars(const float* samples,
62 : int sample_rate,
63 : std::array<float, DISPLAY_BARS>& bars_db) {
64 138 : if (!samples || sample_rate <= 0) {
65 24 : bars_db.fill(kMinDb);
66 24 : return;
67 : }
68 :
69 233586 : for (int i = 0; i < FFT_SIZE; ++i) {
70 233472 : fft_work_[i] = std::complex<float>(samples[i] * window_[i], 0.0f);
71 77824 : }
72 :
73 114 : run_fft(fft_work_);
74 :
75 114 : std::array<float, FFT_BINS> mags_db{};
76 114 : mags_db.fill(kMinDb);
77 :
78 76 : const float norm = 2.0f / static_cast<float>(FFT_SIZE);
79 116736 : for (int i = 1; i < FFT_BINS; ++i) {
80 116622 : const float mag = std::abs(fft_work_[i]) * norm;
81 121856 : mags_db[i] = clamp(20.0f * std::log10(std::max(mag, 1e-8f)), kMinDb, kMaxDb);
82 38874 : }
83 :
84 11058 : for (int bar = 0; bar < DISPLAY_BARS; ++bar) {
85 10944 : const float t0 = static_cast<float>(bar) / static_cast<float>(DISPLAY_BARS);
86 10944 : const float t1 = static_cast<float>(bar + 1) / static_cast<float>(DISPLAY_BARS);
87 10944 : const float hz0 = std::pow(10.0f, lerp(std::log10(kMinHz), std::log10(kMaxHz), t0));
88 10944 : const float hz1 = std::pow(10.0f, lerp(std::log10(kMinHz), std::log10(kMaxHz), t1));
89 :
90 10944 : int bin0 = static_cast<int>(hz0 * FFT_SIZE / static_cast<float>(sample_rate));
91 10944 : int bin1 = static_cast<int>(hz1 * FFT_SIZE / static_cast<float>(sample_rate));
92 10944 : bin0 = std::max(1, std::min(bin0, FFT_BINS - 1));
93 10944 : bin1 = std::max(bin0 + 1, std::min(bin1, FFT_BINS));
94 :
95 10944 : float peak_db = kMinDb;
96 109422 : for (int b = bin0; b < bin1; ++b) {
97 98478 : peak_db = std::max(peak_db, mags_db[b]);
98 32826 : }
99 10944 : bars_db[bar] = peak_db;
100 3648 : }
101 46 : }
102 :
103 69 : void SpectrumAnalyzer::update(const float* input_samples,
104 : const float* output_samples,
105 : int sample_rate,
106 : float dt_seconds) {
107 69 : std::array<float, DISPLAY_BARS> input_db{};
108 69 : std::array<float, DISPLAY_BARS> output_db{};
109 69 : compute_spectrum_bars(input_samples, sample_rate, input_db);
110 69 : compute_spectrum_bars(output_samples, sample_rate, output_db);
111 :
112 69 : const float smooth_alpha = 0.26f;
113 69 : const float peak_decay_db_per_sec = 30.0f;
114 69 : const float decay = peak_decay_db_per_sec * std::max(dt_seconds, 1.0f / 240.0f);
115 :
116 6693 : for (int i = 0; i < DISPLAY_BARS; ++i) {
117 6624 : smoothed_input_db_[i] = lerp(smoothed_input_db_[i], input_db[i], smooth_alpha);
118 6624 : smoothed_output_db_[i] = lerp(smoothed_output_db_[i], output_db[i], smooth_alpha);
119 :
120 6624 : input_peak_db_[i] = std::max(smoothed_input_db_[i], input_peak_db_[i] - decay);
121 6624 : output_peak_db_[i] = std::max(smoothed_output_db_[i], output_peak_db_[i] - decay);
122 2208 : }
123 69 : }
124 :
125 : } // namespace Amplitron
|