LCOV - code coverage report
Current view: top level - src/gui/views - gui_midi.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 72.5 % 204 148
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 9 9

            Line data    Source code
       1              : #include "gui/views/gui_midi.h"
       2              : 
       3              : #include <imgui.h>
       4              : 
       5              : #include <cstdio>
       6              : 
       7              : #include "gui/theme/theme.h"
       8              : #include "midi/midi_manager.h"
       9              : 
      10              : namespace Amplitron {
      11              : 
      12          132 : GuiMidi::GuiMidi(IMidiManager& midi) : midi_(midi) {}
      13              : 
      14              : // ---------------------------------------------------------------------------
      15              : // MIDI Settings window
      16              : // ---------------------------------------------------------------------------
      17              : 
      18            6 : static const char* target_type_label(MidiTargetType type) {
      19            6 :     switch (type) {
      20            0 :         case MidiTargetType::EffectParam:
      21            0 :             return "Param";
      22            0 :         case MidiTargetType::EffectBypass:
      23            0 :             return "Bypass";
      24            2 :         case MidiTargetType::InputGain:
      25            3 :             return "Input Gain";
      26            2 :         case MidiTargetType::OutputGain:
      27            3 :             return "Output Gain";
      28            0 :         default:
      29            0 :             break;  // Fail at compile time if enum is extended
      30              :     }
      31            0 :     return "?";  // Unreachable if all cases handled
      32            2 : }
      33              : 
      34           12 : static const char* mode_label(MidiMappingMode mode) {
      35           12 :     switch (mode) {
      36            3 :         case MidiMappingMode::Continuous:
      37            6 :             return "Continuous";
      38            2 :         case MidiMappingMode::Toggle:
      39            3 :             return "Toggle";
      40            0 :         default:
      41            0 :             break;  // Fail at compile time if enum is extended
      42              :     }
      43            0 :     return "?";  // Unreachable if all cases handled
      44            4 : }
      45              : 
      46            6 : void GuiMidi::render(bool& show) {
      47            6 :     ImGui::SetNextWindowSize(ImVec2(620, 480), ImGuiCond_FirstUseEver);
      48            6 :     if (!ImGui::Begin("MIDI Settings", &show)) {
      49            0 :         ImGui::End();
      50            0 :         return;
      51              :     }
      52              : 
      53              :     // --- Port selection ---
      54            6 :     ImGui::TextColored(Theme::Gold(), "MIDI INPUT PORT");
      55            6 :     ImGui::BeginChild("PortSection", ImVec2(0, 70), true);
      56              : 
      57              :     // Refresh cached ports on first render or when user clicks refresh
      58            6 :     cached_ports_ = midi_.get_available_ports();
      59              : 
      60            6 :     if (cached_ports_.empty()) {
      61            6 :         ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "No MIDI input devices detected");
      62            2 :     } else {
      63            0 :         int current = midi_.current_port();
      64            0 :         std::string preview = (current >= 0) ? midi_.current_port_name() : "(none)";
      65              : 
      66            0 :         ImGui::SetNextItemWidth(400);
      67            0 :         if (ImGui::BeginCombo("##MidiPort", preview.c_str())) {
      68              :             // "(none)" option to close port
      69            0 :             if (ImGui::Selectable("(none)", current < 0)) {
      70            0 :                 midi_.close_port();
      71            0 :             }
      72            0 :             for (int i = 0; i < static_cast<int>(cached_ports_.size()); ++i) {
      73            0 :                 bool selected = (i == current);
      74            0 :                 if (ImGui::Selectable(cached_ports_[i].c_str(), selected)) {
      75            0 :                     midi_.open_port(i);
      76            0 :                 }
      77            0 :             }
      78            0 :             ImGui::EndCombo();
      79            0 :         }
      80            0 :     }
      81              : 
      82            6 :     ImGui::SameLine();
      83            6 :     if (ImGui::SmallButton("Refresh")) {
      84              :         // Re-query ports from RtMidi
      85            0 :         cached_ports_ = midi_.get_available_ports();
      86            0 :     }
      87              : 
      88            6 :     ImGui::EndChild();
      89              : 
      90            6 :     ImGui::Spacing();
      91              : 
      92              :     // --- Learn status ---
      93            6 :     if (midi_.is_learning()) {
      94            0 :         ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f), "%s", midi_.learn_status().c_str());
      95            0 :         ImGui::SameLine();
      96            0 :         if (ImGui::SmallButton("Cancel Learn")) {
      97            0 :             midi_.cancel_learn();
      98            0 :         }
      99            0 :         ImGui::Spacing();
     100            0 :     }
     101              : 
     102              :     // --- Mapping table ---
     103            6 :     ImGui::TextColored(Theme::Gold(), "CC MAPPINGS");
     104              : 
     105            6 :     if (ImGui::SmallButton("Add Defaults")) {
     106            0 :         midi_.install_default_mappings();
     107            0 :     }
     108            6 :     ImGui::SameLine();
     109            6 :     if (ImGui::SmallButton("Clear All")) {
     110            0 :         midi_.clear_mappings();
     111            0 :     }
     112              : 
     113            6 :     ImGui::Spacing();
     114              : 
     115            6 :     const auto& mappings = midi_.mappings();
     116            6 :     if (mappings.empty()) {
     117            3 :         ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
     118              :                            "No mappings. Right-click any knob to MIDI Learn, "
     119              :                            "or click \"Add Defaults\".");
     120            1 :     } else {
     121            3 :         ImGui::BeginChild("MappingTable", ImVec2(0, 0), true);
     122              : 
     123              :         // Table header
     124            3 :         if (ImGui::BeginTable("##MidiMappings", 6,
     125              :                               ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
     126              :                                   ImGuiTableFlags_SizingStretchProp)) {
     127            3 :             ImGui::TableSetupColumn("CC#", ImGuiTableColumnFlags_WidthFixed, 40);
     128            3 :             ImGui::TableSetupColumn("Ch", ImGuiTableColumnFlags_WidthFixed, 35);
     129            3 :             ImGui::TableSetupColumn("Target", ImGuiTableColumnFlags_WidthStretch);
     130            3 :             ImGui::TableSetupColumn("Param", ImGuiTableColumnFlags_WidthStretch);
     131            3 :             ImGui::TableSetupColumn("Mode", ImGuiTableColumnFlags_WidthFixed, 80);
     132            3 :             ImGui::TableSetupColumn("##Del", ImGuiTableColumnFlags_WidthFixed, 25);
     133            3 :             ImGui::TableHeadersRow();
     134              : 
     135            3 :             int remove_index = -1;
     136           15 :             for (int i = 0; i < static_cast<int>(mappings.size()); ++i) {
     137           12 :                 const auto& m = mappings[i];
     138           12 :                 ImGui::TableNextRow();
     139              : 
     140           12 :                 ImGui::TableNextColumn();
     141           12 :                 ImGui::Text("%d", m.cc_number);
     142              : 
     143           12 :                 ImGui::TableNextColumn();
     144           12 :                 if (m.midi_channel < 0) {
     145            3 :                     ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "All");
     146            1 :                 } else {
     147            9 :                     ImGui::Text("%d", m.midi_channel + 1);  // Display 1-based
     148              :                 }
     149              : 
     150           12 :                 ImGui::TableNextColumn();
     151           12 :                 switch (m.target_type) {
     152            4 :                     case MidiTargetType::EffectParam:
     153            2 :                     case MidiTargetType::EffectBypass:
     154            6 :                         ImGui::Text("%s", m.effect_name.c_str());
     155            4 :                         break;
     156            4 :                     default:
     157            6 :                         ImGui::Text("%s", target_type_label(m.target_type));
     158            4 :                         break;
     159              :                 }
     160              : 
     161           12 :                 ImGui::TableNextColumn();
     162           12 :                 switch (m.target_type) {
     163            2 :                     case MidiTargetType::EffectParam:
     164            3 :                         ImGui::Text("%s", m.param_name.c_str());
     165            2 :                         break;
     166            2 :                     case MidiTargetType::EffectBypass:
     167            3 :                         ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "(bypass)");
     168            3 :                         break;
     169            4 :                     default:
     170            6 :                         ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "-");
     171            6 :                         break;
     172              :                 }
     173              : 
     174           12 :                 ImGui::TableNextColumn();
     175           13 :                 ImGui::Text("%s", mode_label(m.mode));
     176              : 
     177           12 :                 ImGui::TableNextColumn();
     178            4 :                 char btn_id[16];
     179           12 :                 snprintf(btn_id, sizeof(btn_id), "X##%d", i);
     180           12 :                 if (ImGui::SmallButton(btn_id)) {
     181            0 :                     remove_index = i;
     182            0 :                 }
     183            4 :             }
     184              : 
     185            3 :             ImGui::EndTable();
     186              : 
     187            3 :             if (remove_index >= 0) {
     188            0 :                 midi_.remove_mapping(remove_index);
     189            0 :             }
     190            1 :         }
     191              : 
     192            3 :         ImGui::EndChild();
     193              :     }
     194              : 
     195            6 :     ImGui::End();
     196            2 : }
     197              : 
     198              : // ---------------------------------------------------------------------------
     199              : // MIDI Learn menu items (for knob right-click popups)
     200              : // ---------------------------------------------------------------------------
     201              : 
     202            6 : bool GuiMidi::render_learn_menu_item(const std::string& effect_name,
     203              :                                      const std::string& param_name) {
     204              :     // Check if this param already has a mapping
     205            6 :     bool has_mapping = false;
     206            6 :     int mapping_cc = -1;
     207            6 :     for (const auto& m : midi_.mappings()) {
     208            4 :         if (m.target_type == MidiTargetType::EffectParam && m.effect_name == effect_name &&
     209            3 :             m.param_name == param_name) {
     210            3 :             has_mapping = true;
     211            3 :             mapping_cc = m.cc_number;
     212            3 :             break;
     213              :         }
     214              :     }
     215              : 
     216            5 :     if (has_mapping) {
     217            1 :         char label[64];
     218            3 :         snprintf(label, sizeof(label), "MIDI: CC %d", mapping_cc);
     219            3 :         ImGui::TextColored(ImVec4(0.5f, 0.8f, 0.5f, 1.0f), "%s", label);
     220              : 
     221            3 :         if (ImGui::MenuItem("Remove MIDI Mapping")) {
     222            0 :             midi_.remove_mapping_for_param(effect_name, param_name);
     223            0 :             return true;
     224              :         }
     225            1 :     }
     226              : 
     227            6 :     if (ImGui::MenuItem("MIDI Learn")) {
     228            0 :         midi_.start_learn(MidiTargetType::EffectParam, effect_name, param_name);
     229            0 :         return true;
     230              :     }
     231            4 :     return false;
     232            2 : }
     233              : 
     234            3 : bool GuiMidi::render_learn_bypass_item(const std::string& effect_name) {
     235            3 :     if (ImGui::MenuItem("MIDI Learn (Bypass)")) {
     236            0 :         midi_.start_learn(MidiTargetType::EffectBypass, effect_name, "");
     237            0 :         return true;
     238              :     }
     239            2 :     return false;
     240            1 : }
     241              : 
     242            3 : bool GuiMidi::render_remove_mapping_item(const std::string& effect_name,
     243              :                                          const std::string& param_name) {
     244            3 :     int remove_idx = -1;
     245            3 :     const auto& mappings = midi_.mappings();
     246            3 :     for (int i = 0; i < static_cast<int>(mappings.size()); ++i) {
     247            5 :         if (mappings[i].target_type == MidiTargetType::EffectParam &&
     248            4 :             mappings[i].effect_name == effect_name && mappings[i].param_name == param_name) {
     249            2 :             remove_idx = i;
     250            2 :             break;
     251              :         }
     252            0 :     }
     253              : 
     254            3 :     if (remove_idx >= 0) {
     255            3 :         if (ImGui::MenuItem("Remove MIDI Mapping")) {
     256            0 :             midi_.remove_mapping(remove_idx);
     257            0 :             return true;
     258              :         }
     259            1 :     }
     260            2 :     return false;
     261            1 : }
     262              : 
     263            3 : bool GuiMidi::render_remove_bypass_item(const std::string& effect_name) {
     264            3 :     int remove_idx = -1;
     265            3 :     const auto& mappings = midi_.mappings();
     266            6 :     for (int i = 0; i < static_cast<int>(mappings.size()); ++i) {
     267            7 :         if (mappings[i].target_type == MidiTargetType::EffectBypass &&
     268            3 :             mappings[i].effect_name == effect_name) {
     269            2 :             remove_idx = i;
     270            2 :             break;
     271              :         }
     272            1 :     }
     273              : 
     274            3 :     if (remove_idx >= 0) {
     275            3 :         if (ImGui::MenuItem("Remove MIDI Bypass Mapping")) {
     276            0 :             midi_.remove_mapping(remove_idx);
     277            0 :             return true;
     278              :         }
     279            1 :     }
     280            2 :     return false;
     281            1 : }
     282              : 
     283          597 : std::string GuiMidi::get_mapping_info(const std::string& effect_name,
     284              :                                       const std::string& param_name) const {
     285          612 :     for (const auto& m : midi_.mappings()) {
     286           38 :         if (m.target_type == MidiTargetType::EffectParam && m.effect_name == effect_name &&
     287           24 :             m.param_name == param_name) {
     288           25 :             return "\n\n[MIDI: CC" + std::to_string(m.cc_number) +
     289           25 :                    (m.mode == MidiMappingMode::Toggle ? " Toggle]" : " Range]");
     290              :         }
     291              :     }
     292          776 :     return "";
     293          199 : }
     294              : 
     295              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1