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