Line data Source code
1 : #pragma once
2 :
3 : #include "gui/commands/command_base.h"
4 : #include "audio/engine/audio_engine.h"
5 : #include "audio/engine/audio_graph.h"
6 : #include "gui/state/gui_graph_state.h"
7 :
8 : namespace Amplitron {
9 :
10 : using NodeId = int;
11 : using EffectType = NodeRoutingType;
12 :
13 : struct AddGraphNodeCommand : public Command {
14 : AudioEngine& engine_;
15 2 : NodeId node_id = -1; // Assigned on first execute
16 : std::string name;
17 : EffectType type;
18 : std::shared_ptr<Effect> pedal;
19 : ImVec2 position;
20 : DSPNode cached_node; // To remember exactly what was added for redo
21 :
22 : int num_inputs = 0;
23 :
24 8 : AddGraphNodeCommand(AudioEngine& engine, const std::string& name, EffectType type, std::shared_ptr<Effect> pedal, ImVec2 pos, int num_inputs = 0)
25 12 : : engine_(engine), name(name), type(type), pedal(pedal), position(pos), num_inputs(num_inputs) {}
26 :
27 9 : bool execute() override {
28 9 : if (node_id == -1) {
29 6 : node_id = engine_.graph().add_node(name, type, pedal, num_inputs);
30 6 : auto* added_node = engine_.graph().find_node(node_id);
31 6 : if (added_node) cached_node = *added_node;
32 2 : } else {
33 : // Re-adding the previously deleted/undone node
34 3 : engine_.graph().restore_node(cached_node);
35 : }
36 : // Only write a fixed position when one was explicitly requested;
37 : // if position is (0,0) the auto-placement logic in render_signal_chain
38 : // will assign the correct cascading position on the next frame.
39 9 : if (position.x != 0.0f || position.y != 0.0f) {
40 6 : GuiGraphState::get_instance().node_positions[node_id] = { position, false, ImVec2(0, 0) };
41 2 : }
42 9 : engine_.commit_graph_changes();
43 9 : return true;
44 0 : }
45 :
46 3 : void undo() override {
47 3 : engine_.graph().remove_node(node_id);
48 3 : GuiGraphState::get_instance().node_positions.erase(node_id);
49 3 : engine_.commit_graph_changes();
50 3 : }
51 :
52 0 : const char* description() const override { return "Add Node"; }
53 : };
54 :
55 : struct RemovedPinInfo {
56 : int node_id;
57 : int pin_id;
58 : int index;
59 : float gain;
60 : };
61 :
62 : struct RemoveGraphNodeCommand : public Command {
63 : AudioEngine& engine_;
64 : NodeId node_id;
65 : EffectType type;
66 : ImVec2 position;
67 : std::vector<GraphLink> severed_links; // cache for undo
68 : DSPNode cached_node; // full node data for exact restoration
69 : std::vector<RemovedPinInfo> auto_removed_pins; // cache for dynamically removed pins on other nodes
70 : std::vector<RemovedPinInfo> auto_removed_out_pins;
71 :
72 10 : RemoveGraphNodeCommand(AudioEngine& engine, NodeId id, EffectType t, ImVec2 pos)
73 10 : : engine_(engine), node_id(id), type(t), position(pos) {}
74 :
75 9 : bool execute() override {
76 9 : auto* node_to_remove = engine_.graph().find_node(node_id);
77 9 : if (!node_to_remove) return false;
78 :
79 6 : cached_node = *node_to_remove;
80 :
81 : // Cache severed links before removal
82 6 : severed_links.clear();
83 12 : for (const auto& link : engine_.graph().get_links()) {
84 10 : if (std::find(cached_node.input_pin_ids.begin(), cached_node.input_pin_ids.end(), link.dest_pin_id) != cached_node.input_pin_ids.end() ||
85 10 : std::find(cached_node.output_pin_ids.begin(), cached_node.output_pin_ids.end(), link.source_pin_id) != cached_node.output_pin_ids.end()) {
86 6 : severed_links.push_back(link);
87 2 : }
88 : }
89 :
90 6 : engine_.graph().remove_node(node_id);
91 6 : GuiGraphState::get_instance().node_positions.erase(node_id);
92 :
93 : // Clean up empty pins on mixers that were affected by severed links
94 12 : for (const auto& link : severed_links) {
95 6 : int dest_node_id = engine_.graph().get_node_from_pin(link.dest_pin_id);
96 6 : if (dest_node_id != -1) {
97 6 : const DSPNode* n = engine_.graph().find_node(dest_node_id);
98 6 : if (n && (n->routing_type == NodeRoutingType::Mixer || n->routing_type == NodeRoutingType::MergeSum)) {
99 6 : if (n->input_pin_ids.size() > 2) {
100 0 : int idx = -1; float gain = 1.0f;
101 0 : for (size_t i = 0; i < n->input_pin_ids.size(); ++i) {
102 0 : if (n->input_pin_ids[i] == link.dest_pin_id) {
103 0 : idx = i; gain = (i < n->input_gains.size()) ? n->input_gains[i] : 1.0f; break;
104 : }
105 0 : }
106 0 : if (engine_.graph().remove_input_pin(dest_node_id, link.dest_pin_id)) {
107 0 : auto_removed_pins.push_back({dest_node_id, link.dest_pin_id, idx, gain});
108 0 : }
109 0 : }
110 2 : }
111 2 : }
112 :
113 6 : int source_node_id = engine_.graph().get_node_from_pin(link.source_pin_id);
114 6 : if (source_node_id != -1) {
115 0 : const DSPNode* n = engine_.graph().find_node(source_node_id);
116 0 : if (n && n->routing_type == NodeRoutingType::Splitter) {
117 0 : if (n->output_pin_ids.size() > 2) {
118 0 : int idx = -1;
119 0 : for (size_t i = 0; i < n->output_pin_ids.size(); ++i) {
120 0 : if (n->output_pin_ids[i] == link.source_pin_id) {
121 0 : idx = i; break;
122 : }
123 0 : }
124 0 : if (engine_.graph().remove_output_pin(source_node_id, link.source_pin_id)) {
125 0 : auto_removed_out_pins.push_back({source_node_id, link.source_pin_id, idx, 1.0f});
126 0 : }
127 0 : }
128 0 : }
129 0 : }
130 : }
131 :
132 6 : engine_.commit_graph_changes();
133 6 : return true;
134 3 : }
135 :
136 3 : void undo() override {
137 3 : engine_.graph().restore_node(cached_node);
138 3 : GuiGraphState::get_instance().node_positions[node_id] = { position, false, ImVec2(0, 0) };
139 :
140 3 : for (auto it = auto_removed_pins.rbegin(); it != auto_removed_pins.rend(); ++it) {
141 0 : engine_.graph().restore_input_pin(it->node_id, it->pin_id, it->index, it->gain);
142 0 : }
143 3 : for (auto it = auto_removed_out_pins.rbegin(); it != auto_removed_out_pins.rend(); ++it) {
144 0 : engine_.graph().restore_output_pin(it->node_id, it->pin_id, it->index);
145 0 : }
146 6 : for (const auto& link : severed_links) {
147 3 : engine_.graph().restore_link(link);
148 : }
149 3 : engine_.commit_graph_changes();
150 3 : }
151 :
152 0 : const char* description() const override { return "Remove Node"; }
153 : };
154 :
155 : struct AddGraphLinkCommand : public Command {
156 : AudioEngine& engine_;
157 : GraphLink link;
158 3 : bool was_successful = false;
159 :
160 3 : int auto_added_pin_node_id = -1;
161 3 : int auto_added_pin_id = -1;
162 3 : int auto_added_pin_index = -1;
163 3 : float auto_added_pin_gain = 1.0f;
164 :
165 3 : int auto_added_out_pin_node_id = -1;
166 3 : int auto_added_out_pin_id = -1;
167 3 : int auto_added_out_pin_index = -1;
168 :
169 15 : AddGraphLinkCommand(AudioEngine& engine, int src_pin, int dst_pin)
170 12 : : engine_(engine) {
171 9 : link.source_pin_id = src_pin;
172 9 : link.dest_pin_id = dst_pin;
173 9 : link.id = -1; // Unknown until execute
174 12 : }
175 :
176 12 : bool execute() override {
177 12 : if (link.id == -1) {
178 9 : bool already_exists = false;
179 9 : for (const auto& l : engine_.graph().get_links()) {
180 3 : if (l.source_pin_id == link.source_pin_id && l.dest_pin_id == link.dest_pin_id) {
181 2 : already_exists = true;
182 2 : break;
183 : }
184 : }
185 9 : if (already_exists) return false;
186 :
187 6 : link.id = engine_.graph().add_link(link.source_pin_id, link.dest_pin_id);
188 6 : was_successful = (link.id != -1);
189 :
190 6 : if (was_successful) {
191 6 : int dest_node_id = engine_.graph().get_node_from_pin(link.dest_pin_id);
192 6 : if (dest_node_id != -1) {
193 6 : const DSPNode* node = engine_.graph().find_node(dest_node_id);
194 6 : if (node && (node->routing_type == NodeRoutingType::Mixer || node->routing_type == NodeRoutingType::MergeSum)) {
195 6 : size_t occupied_count = 0;
196 18 : for (int p : node->input_pin_ids) {
197 18 : for (const auto& l : engine_.graph().get_links()) {
198 12 : if (l.dest_pin_id == p) {
199 6 : occupied_count++; break;
200 : }
201 : }
202 : }
203 6 : if (occupied_count == node->input_pin_ids.size()) {
204 0 : if (engine_.graph().add_input_pin(dest_node_id)) {
205 0 : const DSPNode* updated_node = engine_.graph().find_node(dest_node_id);
206 0 : int new_pin = updated_node->input_pin_ids.back();
207 :
208 0 : auto_added_pin_node_id = dest_node_id;
209 0 : auto_added_pin_id = new_pin;
210 0 : auto_added_pin_index = updated_node->input_pin_ids.size() - 1;
211 0 : auto_added_pin_gain = 1.0f;
212 0 : }
213 0 : }
214 2 : }
215 2 : }
216 :
217 6 : int source_node_id = engine_.graph().get_node_from_pin(link.source_pin_id);
218 6 : if (source_node_id != -1) {
219 6 : const DSPNode* node = engine_.graph().find_node(source_node_id);
220 6 : if (node && node->routing_type == NodeRoutingType::Splitter) {
221 6 : size_t occupied_count = 0;
222 18 : for (int p : node->output_pin_ids) {
223 18 : for (const auto& l : engine_.graph().get_links()) {
224 12 : if (l.source_pin_id == p) {
225 6 : occupied_count++; break;
226 : }
227 : }
228 : }
229 6 : if (occupied_count == node->output_pin_ids.size()) {
230 0 : if (engine_.graph().add_output_pin(source_node_id)) {
231 0 : const DSPNode* updated_node = engine_.graph().find_node(source_node_id);
232 0 : int new_pin = updated_node->output_pin_ids.back();
233 :
234 0 : auto_added_out_pin_node_id = source_node_id;
235 0 : auto_added_out_pin_id = new_pin;
236 0 : auto_added_out_pin_index = updated_node->output_pin_ids.size() - 1;
237 0 : }
238 0 : }
239 2 : }
240 2 : }
241 2 : }
242 5 : } else if (was_successful) {
243 3 : if (auto_added_pin_node_id != -1) {
244 0 : engine_.graph().restore_input_pin(auto_added_pin_node_id, auto_added_pin_id, auto_added_pin_index, auto_added_pin_gain);
245 0 : }
246 3 : if (auto_added_out_pin_node_id != -1) {
247 0 : engine_.graph().restore_output_pin(auto_added_out_pin_node_id, auto_added_out_pin_id, auto_added_out_pin_index);
248 0 : }
249 3 : engine_.graph().restore_link(link);
250 1 : }
251 9 : if (was_successful) {
252 9 : engine_.commit_graph_changes();
253 3 : }
254 9 : return was_successful;
255 4 : }
256 :
257 3 : void undo() override {
258 3 : if (was_successful) {
259 3 : engine_.graph().remove_link(link.id);
260 3 : if (auto_added_pin_node_id != -1) {
261 0 : engine_.graph().remove_input_pin(auto_added_pin_node_id, auto_added_pin_id);
262 0 : }
263 3 : if (auto_added_out_pin_node_id != -1) {
264 0 : engine_.graph().remove_output_pin(auto_added_out_pin_node_id, auto_added_out_pin_id);
265 0 : }
266 3 : engine_.commit_graph_changes();
267 1 : }
268 3 : }
269 :
270 0 : const char* description() const override { return "Add Link"; }
271 : };
272 :
273 : struct RemoveGraphLinkCommand : public Command {
274 : AudioEngine& engine_;
275 : GraphLink link;
276 :
277 2 : int auto_removed_pin_node_id = -1;
278 2 : int auto_removed_pin_id = -1;
279 2 : int auto_removed_pin_index = -1;
280 2 : float auto_removed_pin_gain = 1.0f;
281 :
282 2 : int auto_removed_out_pin_node_id = -1;
283 2 : int auto_removed_out_pin_id = -1;
284 2 : int auto_removed_out_pin_index = -1;
285 :
286 8 : RemoveGraphLinkCommand(AudioEngine& engine, const GraphLink& l)
287 10 : : engine_(engine), link(l) {}
288 :
289 9 : bool execute() override {
290 9 : bool success = engine_.graph().remove_link(link.id);
291 9 : if (success) {
292 6 : int dest_node_id = engine_.graph().get_node_from_pin(link.dest_pin_id);
293 6 : if (dest_node_id != -1) {
294 6 : const DSPNode* node = engine_.graph().find_node(dest_node_id);
295 6 : if (node && (node->routing_type == NodeRoutingType::Mixer || node->routing_type == NodeRoutingType::MergeSum)) {
296 6 : if (node->input_pin_ids.size() > 2) {
297 0 : for (size_t i = 0; i < node->input_pin_ids.size(); ++i) {
298 0 : if (node->input_pin_ids[i] == link.dest_pin_id) {
299 0 : auto_removed_pin_index = i;
300 0 : auto_removed_pin_gain = (i < node->input_gains.size()) ? node->input_gains[i] : 1.0f;
301 0 : break;
302 : }
303 0 : }
304 0 : if (engine_.graph().remove_input_pin(dest_node_id, link.dest_pin_id)) {
305 0 : auto_removed_pin_node_id = dest_node_id;
306 0 : auto_removed_pin_id = link.dest_pin_id;
307 0 : }
308 0 : }
309 2 : }
310 2 : }
311 :
312 6 : int source_node_id = engine_.graph().get_node_from_pin(link.source_pin_id);
313 6 : if (source_node_id != -1) {
314 6 : const DSPNode* node = engine_.graph().find_node(source_node_id);
315 6 : if (node && node->routing_type == NodeRoutingType::Splitter) {
316 6 : if (node->output_pin_ids.size() > 2) {
317 0 : for (size_t i = 0; i < node->output_pin_ids.size(); ++i) {
318 0 : if (node->output_pin_ids[i] == link.source_pin_id) {
319 0 : auto_removed_out_pin_index = i;
320 0 : break;
321 : }
322 0 : }
323 0 : if (engine_.graph().remove_output_pin(source_node_id, link.source_pin_id)) {
324 0 : auto_removed_out_pin_node_id = source_node_id;
325 0 : auto_removed_out_pin_id = link.source_pin_id;
326 0 : }
327 0 : }
328 2 : }
329 2 : }
330 6 : engine_.commit_graph_changes();
331 2 : }
332 9 : return success;
333 : }
334 :
335 3 : void undo() override {
336 3 : if (auto_removed_pin_node_id != -1) {
337 0 : engine_.graph().restore_input_pin(auto_removed_pin_node_id, auto_removed_pin_id, auto_removed_pin_index, auto_removed_pin_gain);
338 0 : }
339 3 : if (auto_removed_out_pin_node_id != -1) {
340 0 : engine_.graph().restore_output_pin(auto_removed_out_pin_node_id, auto_removed_out_pin_id, auto_removed_out_pin_index);
341 0 : }
342 3 : engine_.graph().restore_link(link);
343 3 : engine_.commit_graph_changes();
344 3 : }
345 :
346 0 : const char* description() const override { return "Remove Link"; }
347 : };
348 :
349 : struct MoveGraphNodeCommand : public Command {
350 : NodeId node_id;
351 : ImVec2 old_pos;
352 : ImVec2 new_pos;
353 :
354 12 : MoveGraphNodeCommand(NodeId id, ImVec2 old_pos, ImVec2 new_pos)
355 12 : : node_id(id), old_pos(old_pos), new_pos(new_pos) {}
356 :
357 12 : bool execute() override {
358 12 : auto& positions = GuiGraphState::get_instance().node_positions;
359 12 : auto it = positions.find(node_id);
360 12 : if (it == positions.end()) return false;
361 :
362 9 : if (it->second.position.x == new_pos.x && it->second.position.y == new_pos.y) {
363 2 : return false;
364 : }
365 :
366 6 : it->second.position = new_pos;
367 6 : return true;
368 4 : }
369 :
370 3 : void undo() override {
371 3 : auto& positions = GuiGraphState::get_instance().node_positions;
372 3 : if (positions.count(node_id)) {
373 3 : positions[node_id].position = old_pos;
374 1 : }
375 3 : }
376 :
377 0 : const char* description() const override { return "Move Node"; }
378 : };
379 :
380 : } // namespace Amplitron
|