LCOV - code coverage report
Current view: top level - src/audio/dsp - spectrum_analyzer.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 100.0 % 85 85
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 7 7

            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
        

Generated by: LCOV version 2.0-1