LCOV - code coverage report
Current view: top level - src/gui/views - gui_analyzer.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 82.5 % 120 99
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 7 7

            Line data    Source code
       1              : #include "gui/views/gui_analyzer.h"
       2              : 
       3              : #include <imgui.h>
       4              : 
       5              : #include <algorithm>
       6              : #include <cmath>
       7              : 
       8              : #include "common.h"
       9              : #include "gui/theme/theme.h"
      10              : 
      11              : namespace Amplitron {
      12              : 
      13              : // Display axis constants (for drawing only — DSP uses its own internal values)
      14              : namespace {
      15              : constexpr float kDisplayMinHz = 20.0f;
      16              : constexpr float kDisplayMaxHz = 20000.0f;
      17              : constexpr float kDisplayMinDb = -90.0f;
      18              : constexpr float kDisplayMaxDb = 0.0f;
      19              : 
      20           18 : inline float hz_to_log_norm(float hz) {
      21           18 :     const float lo = std::log10(kDisplayMinHz);
      22           18 :     const float hi = std::log10(kDisplayMaxHz);
      23           18 :     return clamp((std::log10(hz) - lo) / (hi - lo), 0.0f, 1.0f);
      24              : }
      25              : }  // namespace
      26              : 
      27              : // ─────────────────────────────────────────────────────────────────────────────
      28              : // VU bar — pure drawing, receives pre-computed scalars
      29              : // ─────────────────────────────────────────────────────────────────────────────
      30            6 : void GuiAnalyzer::render_vu_bar(const char* id, const char* label, float rms_level, float peak_hold,
      31              :                                 bool clip_active, float clip_flash, ImU32 base_color,
      32              :                                 ImU32 peak_color) {
      33            6 :     ImGui::PushID(id);
      34            6 :     ImGui::TextUnformatted(label);
      35            6 :     ImGui::SameLine();
      36            6 :     float db_value = (rms_level > 0.0001f) ? (20.0f * std::log10(rms_level)) : -96.0f;
      37            6 :     ImGui::TextColored(ImVec4(0.80f, 0.80f, 0.80f, 1.0f), "%.1f dB", db_value);
      38              : 
      39            6 :     ImVec2 pos = ImGui::GetCursorScreenPos();
      40            6 :     float width = ImGui::GetContentRegionAvail().x;
      41            6 :     float height = 18.0f;
      42            6 :     ImDrawList* dl = ImGui::GetWindowDrawList();
      43              : 
      44            6 :     ImU32 bg_col = Theme::METER_BG;
      45            6 :     if (clip_active || clip_flash > 0.01f) {
      46            0 :         const float flash = clamp(clip_flash, 0.0f, 1.0f);
      47            0 :         int alpha = static_cast<int>(90.0f + flash * 130.0f);
      48            0 :         bg_col = IM_COL32(180, 30, 30, alpha);
      49            0 :     }
      50              : 
      51            6 :     dl->AddRectFilled(pos, ImVec2(pos.x + width, pos.y + height), bg_col, Theme::ROUNDING_SM);
      52              : 
      53            6 :     float rms_fill = clamp(rms_level, 0.0f, 1.0f) * width;
      54            6 :     dl->AddRectFilled(pos, ImVec2(pos.x + rms_fill, pos.y + height), base_color,
      55              :                       Theme::ROUNDING_SM);
      56              : 
      57            6 :     float peak_x = pos.x + clamp(peak_hold, 0.0f, 1.0f) * width;
      58            6 :     dl->AddLine(ImVec2(peak_x, pos.y - 1.0f), ImVec2(peak_x, pos.y + height + 1.0f), peak_color,
      59              :                 2.0f);
      60              : 
      61            6 :     if (clip_active || clip_flash > 0.01f)
      62            0 :         dl->AddText(ImVec2(pos.x + width - 32.0f, pos.y - 1.0f), IM_COL32(255, 90, 90, 255),
      63              :                     "CLIP");
      64              : 
      65            6 :     ImGui::Dummy(ImVec2(width, height + 6.0f));
      66            6 :     ImGui::PopID();
      67            6 : }
      68              : 
      69              : // ─────────────────────────────────────────────────────────────────────────────
      70              : // Spectrum — pure drawing from pre-computed SpectrumAnalyzer arrays
      71              : // ─────────────────────────────────────────────────────────────────────────────
      72            3 : void GuiAnalyzer::draw_spectrum(ImDrawList* dl, const ImVec2& pos, const ImVec2& size) const {
      73            3 :     if (size.x <= 2.0f || size.y <= 2.0f) return;
      74              : 
      75            3 :     const auto& sa = props_.spectrum;
      76            3 :     const ImVec2 pmax(pos.x + size.x, pos.y + size.y);
      77              : 
      78            3 :     dl->AddRect(pos, pmax, IM_COL32(72, 78, 92, 220), Theme::ROUNDING_SM);
      79              : 
      80              :     // Reference dB lines
      81            3 :     const float ref_lines[] = {-60.0f, -48.0f, -36.0f, -24.0f, -12.0f};
      82           18 :     for (float db : ref_lines) {
      83           15 :         float t = (db - kDisplayMinDb) / (kDisplayMaxDb - kDisplayMinDb);
      84           15 :         float y = pmax.y - t * size.y;
      85           15 :         dl->AddLine(ImVec2(pos.x, y), ImVec2(pmax.x, y), IM_COL32(58, 64, 76, 180), 1.0f);
      86              :     }
      87              : 
      88            3 :     constexpr int BARS = SpectrumAnalyzer::DISPLAY_BARS;
      89            3 :     const ImU32 input_col = IM_COL32(82, 220, 135, 220);
      90            3 :     const ImU32 output_col = IM_COL32(92, 170, 255, 220);
      91            3 :     const ImU32 peak_col = IM_COL32(255, 240, 165, 255);
      92              : 
      93            5 :     const auto draw_set = [&](const std::array<float, BARS>& bars,
      94              :                               const std::array<float, BARS>& peaks, ImU32 bar_col,
      95              :                               float width_scale) {
      96          291 :         for (int i = 0; i < BARS; ++i) {
      97          288 :             const float x0 = pos.x + (static_cast<float>(i) / BARS) * size.x;
      98          288 :             const float x1 = pos.x + (static_cast<float>(i + 1) / BARS) * size.x;
      99          288 :             const float db = clamp(bars[i], kDisplayMinDb, kDisplayMaxDb);
     100          288 :             const float t = (db - kDisplayMinDb) / (kDisplayMaxDb - kDisplayMinDb);
     101          288 :             const float y = pmax.y - t * size.y;
     102              : 
     103          288 :             const float center = (x0 + x1) * 0.5f;
     104          288 :             const float half = (x1 - x0) * 0.5f * width_scale;
     105          288 :             dl->AddRectFilled(ImVec2(center - half, y), ImVec2(center + half, pmax.y), bar_col,
     106              :                               1.5f);
     107              : 
     108          288 :             const float peak_t = (clamp(peaks[i], kDisplayMinDb, kDisplayMaxDb) - kDisplayMinDb) /
     109          192 :                                  (kDisplayMaxDb - kDisplayMinDb);
     110          288 :             const float py = pmax.y - peak_t * size.y;
     111          288 :             dl->AddLine(ImVec2(center - half, py), ImVec2(center + half, py), peak_col, 1.0f);
     112           96 :         }
     113            4 :     };
     114              : 
     115            3 :     switch (mode_) {
     116            0 :         case SpectrumDisplayMode::Input:
     117            0 :             draw_set(sa.smoothed_input_db, sa.input_peak_db, input_col, 0.82f);
     118            0 :             break;
     119            2 :         case SpectrumDisplayMode::Output:
     120            3 :             draw_set(sa.smoothed_output_db, sa.output_peak_db, output_col, 0.82f);
     121            2 :             break;
     122            0 :         case SpectrumDisplayMode::Overlay:
     123            0 :             draw_set(sa.smoothed_input_db, sa.input_peak_db, input_col, 0.42f);
     124            0 :             draw_set(sa.smoothed_output_db, sa.output_peak_db, output_col, 0.42f);
     125            0 :             break;
     126              :     }
     127              : 
     128              :     // Frequency tick lines
     129            3 :     const float ticks[] = {20.0f, 100.0f, 1000.0f, 5000.0f, 10000.0f, 20000.0f};
     130           21 :     for (float hz : ticks) {
     131           18 :         float x = pos.x + hz_to_log_norm(hz) * size.x;
     132           18 :         dl->AddLine(ImVec2(x, pos.y), ImVec2(x, pmax.y), IM_COL32(52, 58, 72, 180), 1.0f);
     133              :     }
     134            1 : }
     135              : 
     136              : // ─────────────────────────────────────────────────────────────────────────────
     137              : // Main panel render
     138              : // ─────────────────────────────────────────────────────────────────────────────
     139            3 : void GuiAnalyzer::render() {
     140            3 :     const AnalyzerProps& p = props_;
     141              : 
     142            3 :     float panel_h = expanded_ ? 230.0f : 34.0f;
     143            3 :     ImGui::BeginChild("AnalyzerPanel", ImVec2(0, panel_h), true, ImGuiWindowFlags_NoScrollbar);
     144              : 
     145            3 :     ImGui::SetNextItemOpen(expanded_, ImGuiCond_Once);
     146            3 :     const bool expanded = ImGui::CollapsingHeader("Real-Time Analyzer");
     147            3 :     if (expanded != expanded_) {
     148            0 :         expanded_ = expanded;
     149            0 :         if (p.on_expanded_changed) p.on_expanded_changed(expanded_);
     150            0 :         if (p.on_set_analyzer_enabled) p.on_set_analyzer_enabled(expanded_);
     151            0 :     }
     152              : 
     153            3 :     if (!expanded_) {
     154            0 :         ImGui::EndChild();
     155            0 :         return;
     156              :     }
     157              : 
     158              :     // ── Mode selector ──
     159            3 :     int mode_index = static_cast<int>(mode_);
     160            3 :     ImGui::TextUnformatted("Spectrum:");
     161            3 :     ImGui::SameLine();
     162            3 :     ImGui::SetNextItemWidth(150.0f);
     163            3 :     if (ImGui::Combo("##AnalyzerMode", &mode_index, "Input\0Output\0Overlay\0")) {
     164            0 :         mode_ = static_cast<SpectrumDisplayMode>(mode_index);
     165            0 :         if (p.on_mode_changed) p.on_mode_changed(mode_);
     166            0 :     }
     167              : 
     168              :     // ── VU bars (pre-calculated values from props) ──
     169            3 :     ImGui::Columns(2, "analyzer_vu_cols", false);
     170            4 :     render_vu_bar("input_vu", "INPUT RMS", p.smoothed_input_rms, p.input_peak_hold,
     171            3 :                   p.input_clip_active, p.input_clip_flash, IM_COL32(60, 200, 110, 230),
     172              :                   IM_COL32(255, 230, 120, 255));
     173            3 :     ImGui::NextColumn();
     174            4 :     render_vu_bar("output_vu", "OUTPUT RMS", p.smoothed_output_rms, p.output_peak_hold,
     175            3 :                   p.output_clip_active, p.output_clip_flash, IM_COL32(80, 170, 245, 230),
     176              :                   IM_COL32(255, 230, 120, 255));
     177            3 :     ImGui::Columns(1);
     178              : 
     179              :     // ── Spectrum plot ──
     180            3 :     ImVec2 plot_pos(ImGui::GetCursorScreenPos());
     181            3 :     ImVec2 plot_size(ImGui::GetContentRegionAvail().x, 112.0f);
     182            3 :     ImDrawList* dl = ImGui::GetWindowDrawList();
     183              : 
     184            3 :     dl->AddRectFilled(plot_pos, ImVec2(plot_pos.x + plot_size.x, plot_pos.y + plot_size.y),
     185              :                       IM_COL32(20, 22, 28, 255), Theme::ROUNDING_SM);
     186              : 
     187            3 :     draw_spectrum(dl, plot_pos, plot_size);
     188            3 :     ImGui::Dummy(plot_size);
     189              : 
     190              :     // ── Frequency axis labels ──
     191            3 :     const float axis_left = ImGui::GetCursorPosX();
     192            3 :     const float axis_w = ImGui::GetContentRegionAvail().x;
     193            3 :     ImGui::TextColored(Theme::TextSecondary(), "20 Hz");
     194            3 :     ImGui::SameLine(axis_left + axis_w * 0.48f);
     195            3 :     ImGui::TextColored(Theme::TextSecondary(), "1 kHz");
     196            3 :     ImGui::SameLine(axis_left + axis_w - 52.0f);
     197            3 :     ImGui::TextColored(Theme::TextSecondary(), "20 kHz");
     198              : 
     199            3 :     ImGui::EndChild();
     200            1 : }
     201              : 
     202              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1