LCOV - code coverage report
Current view: top level - src/gui/views - gui_midi.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 73.6 % 201 148
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 9 9

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

Generated by: LCOV version 2.0-1