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