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