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 % 84 84
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 7 7

            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
        

Generated by: LCOV version 2.0-1