Line data Source code
1 : #include "gui/pedalboard/pedal_board.h"
2 : #include "gui/pedalboard/pedal_widget.h"
3 : #include "gui/theme/theme.h"
4 : #include "gui/commands/command.h"
5 : #include "audio/effects/amp_simulator.h"
6 : #include "gui/views/gui_midi.h"
7 : #include "midi/midi_manager.h"
8 :
9 : #include <cstring>
10 : #include <imgui.h>
11 : #include <set>
12 : #include <algorithm>
13 :
14 : namespace Amplitron {
15 :
16 : /** @brief Construct PedalBoard and build initial widget list from engine state. */
17 95 : PedalBoard::PedalBoard(AudioEngine& engine, CommandHistory& history, GuiMidi* gui_midi)
18 76 : : engine_(engine), history_(history), gui_midi_(gui_midi) {
19 57 : rebuild_widgets();
20 57 : }
21 :
22 : /** @brief Default destructor. */
23 76 : PedalBoard::~PedalBoard() = default;
24 :
25 : /** @brief Recreate PedalWidget list to match the engine's current effect chain. */
26 132 : void PedalBoard::rebuild_widgets() {
27 132 : std::set<Effect*> prev_visible;
28 168 : for (int idx : visible_indices_) {
29 36 : if (idx >= 0 && idx < static_cast<int>(widgets_.size())) {
30 48 : prev_visible.insert(widgets_[idx]->get_effect().get());
31 12 : }
32 : }
33 :
34 132 : widgets_.clear();
35 132 : visible_indices_.clear();
36 132 : auto& effects = engine_.effects();
37 :
38 132 : int amp_idx = find_amp_index();
39 :
40 213 : for (int i = 0; i < static_cast<int>(effects.size()); ++i) {
41 81 : auto w = std::make_unique<PedalWidget>(engine_, effects[i], i);
42 81 : w->set_history(&history_);
43 81 : w->set_gui_midi(gui_midi_);
44 81 : widgets_.push_back(std::move(w));
45 :
46 81 : Effect* ptr = effects[i].get();
47 81 : bool is_amp = (amp_idx >= 0 && i == amp_idx);
48 63 : bool is_post_amp = (amp_idx >= 0 && i > amp_idx);
49 :
50 81 : if (is_post_amp) continue;
51 :
52 78 : if (prev_visible.count(ptr)) {
53 36 : visible_indices_.insert(i);
54 54 : } else if (effects[i]->is_enabled() || is_amp) {
55 42 : visible_indices_.insert(i);
56 14 : }
57 81 : }
58 132 : }
59 :
60 : /** @brief Find the index of the current AmpSimulator in the effect chain (-1 if none). */
61 156 : int PedalBoard::find_amp_index() const {
62 156 : auto& fx = engine_.effects();
63 225 : for (int i = 0; i < static_cast<int>(fx.size()); ++i) {
64 99 : if (std::strcmp(fx[i]->name(), "Amp Sim") == 0) return i;
65 23 : }
66 84 : return -1;
67 52 : }
68 :
69 : /** @brief Render the toolbar (add/reset) and the scrollable signal chain area. */
70 6 : void PedalBoard::render() {
71 6 : float bar_height = ImGui::GetFrameHeight() + ImGui::GetStyle().WindowPadding.y * 2.0f + ImGui::GetStyle().WindowBorderSize * 2.0f;
72 6 : ImGui::BeginChild("PedalToolbar", ImVec2(0, bar_height), true,
73 : ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
74 :
75 2 : {
76 6 : float avail = ImGui::GetContentRegionAvail().y;
77 6 : float row_h = ImGui::GetFrameHeight();
78 6 : float offset = std::max(0.0f, (avail - row_h) * 0.5f);
79 6 : ImGui::SetCursorPosY(ImGui::GetCursorPosY() + offset);
80 : }
81 :
82 6 : render_add_pedal_menu();
83 6 : ImGui::SameLine();
84 :
85 6 : if (ImGui::Button("Reset All")) {
86 0 : show_confirm_reset_ = true;
87 0 : }
88 6 : ImGui::SameLine();
89 :
90 6 : if (ImGui::Button("Clear All")) {
91 0 : show_confirm_clear_ = true;
92 0 : }
93 6 : ImGui::SameLine();
94 :
95 6 : render_midi_menu();
96 6 : ImGui::SameLine();
97 :
98 6 : if (show_confirm_reset_) {
99 3 : ImGui::OpenPopup("Confirm Reset##Modal");
100 3 : show_confirm_reset_ = false;
101 1 : }
102 6 : if (show_confirm_clear_) {
103 3 : ImGui::OpenPopup("Confirm Clear##Modal");
104 3 : show_confirm_clear_ = false;
105 1 : }
106 :
107 6 : if (ImGui::BeginPopupModal("Confirm Reset##Modal", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
108 0 : ImGui::Text("Are you sure you want to reset ALL parameters to their default values?\nThis will affect every pedal on the board.");
109 0 : ImGui::Separator();
110 0 : if (ImGui::Button("Reset", ImVec2(120, 0))) {
111 0 : history_.execute(std::make_unique<ResetAllCommand>(engine_));
112 0 : ImGui::CloseCurrentPopup();
113 0 : }
114 0 : ImGui::SetItemDefaultFocus();
115 0 : ImGui::SameLine();
116 0 : if (ImGui::Button("Cancel", ImVec2(120, 0))) {
117 0 : ImGui::CloseCurrentPopup();
118 0 : }
119 0 : ImGui::EndPopup();
120 0 : }
121 :
122 6 : if (ImGui::BeginPopupModal("Confirm Clear##Modal", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
123 3 : ImGui::Text("Are you sure you want to remove ALL pedals from the signal chain?\nThis cannot be undone easily if you have many complex settings.");
124 3 : ImGui::Separator();
125 3 : if (ImGui::Button("Clear All", ImVec2(120, 0))) {
126 0 : history_.execute(std::make_unique<ClearAllCommand>(engine_));
127 0 : rebuild_widgets();
128 0 : ImGui::CloseCurrentPopup();
129 0 : }
130 3 : ImGui::SetItemDefaultFocus();
131 3 : ImGui::SameLine();
132 3 : if (ImGui::Button("Cancel", ImVec2(120, 0))) {
133 0 : ImGui::CloseCurrentPopup();
134 0 : }
135 3 : ImGui::EndPopup();
136 1 : }
137 :
138 6 : if (show_confirm_midi_clear_) {
139 3 : ImGui::OpenPopup("Confirm MIDI Clear##Modal");
140 3 : show_confirm_midi_clear_ = false;
141 1 : }
142 :
143 6 : if (ImGui::BeginPopupModal("Confirm MIDI Clear##Modal", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
144 3 : ImGui::Text("Are you sure you want to clear ALL MIDI CC mappings?");
145 3 : ImGui::TextColored(Theme::Gold(), "This action cannot be undone.");
146 3 : ImGui::Spacing();
147 :
148 3 : if (ImGui::Button("Clear All", ImVec2(120, 0))) {
149 0 : if (gui_midi_) {
150 0 : gui_midi_->manager().clear_mappings();
151 0 : }
152 0 : ImGui::CloseCurrentPopup();
153 0 : }
154 3 : ImGui::SetItemDefaultFocus();
155 3 : ImGui::SameLine();
156 3 : if (ImGui::Button("Cancel", ImVec2(120, 0))) {
157 0 : ImGui::CloseCurrentPopup();
158 0 : }
159 3 : ImGui::EndPopup();
160 1 : }
161 :
162 6 : ImGui::SameLine();
163 :
164 6 : render_amp_selector();
165 :
166 6 : ImGui::SameLine();
167 6 : int pedal_count = static_cast<int>(engine_.effects().size());
168 8 : ImGui::TextColored(Theme::TextSecondary(),
169 2 : " %d effects | Drag headers to route", pedal_count);
170 :
171 6 : ImGui::EndChild();
172 :
173 : // Canvas Window Viewport Layout Setup
174 6 : ImGui::BeginChild("PedalArea", ImVec2(0, 0), true,
175 : ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
176 :
177 6 : render_signal_chain();
178 :
179 6 : ImGui::EndChild();
180 6 : }
181 :
182 : /** @brief Add an effect to the chain via undo system, rebuild widgets, and make it visible. */
183 3 : void PedalBoard::add_effect_and_show(std::shared_ptr<Effect> effect) {
184 3 : history_.execute(std::make_unique<AddEffectCommand>(engine_, std::move(effect)));
185 3 : rebuild_widgets();
186 3 : }
187 :
188 : } // namespace Amplitron
|