Line data Source code
1 : #include "audio/engine/audio_engine.h"
2 : #include <algorithm>
3 :
4 : namespace Amplitron {
5 :
6 1092 : void AudioEngine::sync_graph_with_dummy_effects(bool reset_graph) {
7 364 : {
8 1092 : std::lock_guard<std::mutex> lock(effect_mutex_);
9 1092 : if (reset_graph) {
10 114 : main_graph_ = AudioGraph();
11 38 : }
12 :
13 1092 : if (main_graph_.get_nodes().empty()) {
14 : // 1. INITIAL SETUP: Reset the main graph model completely and auto-wire
15 351 : main_graph_ = AudioGraph();
16 :
17 351 : float cursor_x = 40.0f;
18 351 : float cursor_y = 60.0f;
19 :
20 585 : int input_node_id = main_graph_.add_node("Input", NodeRoutingType::StandardEffect, nullptr);
21 351 : main_graph_.set_node_as_input(input_node_id, true);
22 351 : main_graph_.set_node_position(input_node_id, cursor_x, cursor_y);
23 351 : cursor_x += 160.0f; // Input node is narrower
24 :
25 351 : int prev_output_pin = main_graph_.get_nodes().back().output_pin_ids.empty() ? -1 : main_graph_.get_nodes().back().output_pin_ids[0];
26 :
27 : // Loop through the linear pedals and wire them back-to-back in the DAG
28 852 : for (auto& fx : dummy_effects_) {
29 501 : fx->set_sample_rate(sample_rate_);
30 501 : fx->reset();
31 835 : int node_id = main_graph_.add_node(fx->name(), NodeRoutingType::StandardEffect, fx);
32 501 : main_graph_.set_node_position(node_id, cursor_x, cursor_y);
33 501 : cursor_x += 230.0f; // Standard pedals width + comfortable gap
34 :
35 : // Output routing will be handled dynamically at the end
36 :
37 501 : const auto& nodes = main_graph_.get_nodes();
38 501 : if (nodes.empty()) continue;
39 501 : const auto& current_node = nodes.back();
40 : // Connect the previous pedal's output pin to this pedal's input pin
41 501 : if (prev_output_pin != -1 && !current_node.input_pin_ids.empty()) {
42 501 : main_graph_.add_link(prev_output_pin, current_node.input_pin_ids[0]);
43 167 : }
44 :
45 501 : if (!current_node.output_pin_ids.empty()) {
46 501 : prev_output_pin = current_node.output_pin_ids[0];
47 167 : }
48 : }
49 :
50 : // Mark the last node in the linear chain as the Output so sound reaches the speakers
51 351 : if (!main_graph_.get_nodes().empty()) {
52 351 : main_graph_.set_node_as_output(main_graph_.get_nodes().back().id, true);
53 117 : }
54 117 : } else {
55 : // 2. MODULAR MODE: Remove standard nodes that are no longer in dummy_effects_
56 741 : std::vector<int> nodes_to_remove;
57 2859 : for (const auto& node : main_graph_.get_nodes()) {
58 2118 : if (node.routing_type == NodeRoutingType::StandardEffect && node.pedal != nullptr) {
59 1371 : auto it = std::find(dummy_effects_.begin(), dummy_effects_.end(), node.pedal);
60 1371 : if (it == dummy_effects_.end()) {
61 315 : nodes_to_remove.push_back(node.id);
62 105 : }
63 457 : }
64 : }
65 1056 : for (int nid : nodes_to_remove) {
66 315 : main_graph_.remove_node(nid);
67 : }
68 :
69 : // Add standard nodes for effects in dummy_effects_ that are not yet in the graph
70 2196 : for (int i = 0; i < static_cast<int>(dummy_effects_.size()); ++i) {
71 1455 : auto& fx = dummy_effects_[i];
72 : // Find if a node already exists for this effect
73 1455 : bool exists = false;
74 5130 : for (const auto& node : main_graph_.get_nodes()) {
75 4731 : if (node.pedal == fx) {
76 704 : exists = true;
77 704 : break;
78 : }
79 : }
80 :
81 1455 : if (!exists) {
82 399 : fx->set_sample_rate(sample_rate_);
83 399 : fx->reset();
84 665 : int node_id = main_graph_.add_node(fx->name(), NodeRoutingType::StandardEffect, fx);
85 532 : if (std::string(fx->name()) == "Amp Sim") {
86 6 : main_graph_.set_node_as_output(node_id, true);
87 2 : }
88 133 : }
89 485 : }
90 741 : }
91 1092 : }
92 :
93 : // 3. Compile the topology plan and push it to the hot audio thread safely
94 1092 : commit_graph_changes();
95 1092 : }
96 :
97 561 : void AudioEngine::add_effect(std::shared_ptr<Effect> fx) {
98 561 : dummy_effects_.push_back(fx);
99 561 : sync_graph_with_dummy_effects();
100 561 : }
101 :
102 3 : void AudioEngine::insert_effect(int index, std::shared_ptr<Effect> fx) {
103 3 : if (index >= 0 && index <= static_cast<int>(dummy_effects_.size())) {
104 3 : dummy_effects_.insert(dummy_effects_.begin() + index, fx);
105 3 : sync_graph_with_dummy_effects();
106 1 : }
107 3 : }
108 :
109 252 : void AudioEngine::remove_effect(int index) {
110 252 : if (index >= 0 && index < static_cast<int>(dummy_effects_.size())) {
111 246 : dummy_effects_.erase(dummy_effects_.begin() + index);
112 246 : sync_graph_with_dummy_effects();
113 82 : }
114 252 : }
115 :
116 72 : void AudioEngine::clear_effects() {
117 72 : dummy_effects_.clear();
118 72 : sync_graph_with_dummy_effects(true);
119 72 : }
120 :
121 24 : void AudioEngine::move_effect(int from, int to) {
122 24 : int size = static_cast<int>(dummy_effects_.size());
123 24 : if (from < 0 || from >= size || to < 0 || to >= size) {
124 8 : return;
125 : }
126 :
127 18 : if (from == to) return;
128 :
129 15 : auto fx = dummy_effects_[from];
130 15 : dummy_effects_.erase(dummy_effects_.begin() + from);
131 15 : dummy_effects_.insert(dummy_effects_.begin() + to, fx);
132 :
133 15 : sync_graph_with_dummy_effects();
134 18 : }
135 :
136 153 : void AudioEngine::restore_effects_state(std::vector<std::shared_ptr<Effect>> state) {
137 153 : dummy_effects_ = state;
138 153 : sync_graph_with_dummy_effects();
139 153 : }
140 :
141 3 : void AudioEngine::set_tuner_tap(std::shared_ptr<Effect> tap) {
142 3 : std::lock_guard<std::mutex> lock(effect_mutex_);
143 3 : tuner_tap_ = std::move(tap);
144 3 : if (tuner_tap_) {
145 3 : tuner_tap_->set_sample_rate(sample_rate_);
146 3 : tuner_tap_->reset();
147 1 : }
148 3 : topology_dirty_.store(true, std::memory_order_release);
149 3 : }
150 :
151 3 : void AudioEngine::clear_tuner_tap() {
152 3 : std::lock_guard<std::mutex> lock(effect_mutex_);
153 3 : tuner_tap_.reset();
154 3 : topology_dirty_.store(true, std::memory_order_release);
155 3 : }
156 :
157 9 : bool AudioEngine::has_tuner_tap() const {
158 9 : return tuner_tap_ != nullptr;
159 : }
160 :
161 : } // namespace Amplitron
|