Line data Source code
1 : #include "audio/engine/audio_graph_executor.h"
2 :
3 : #include <algorithm>
4 : #include <cstring>
5 :
6 : namespace Amplitron {
7 :
8 3237 : AudioGraphExecutor::AudioGraphExecutor() {}
9 :
10 2419 : void AudioGraphExecutor::prepare(int sample_rate, int max_block_size, int max_nodes) {
11 2419 : sample_rate_ = sample_rate;
12 2419 : max_block_size_ = max_block_size;
13 2419 : max_nodes_ = max_nodes;
14 :
15 : // Pre-allocate the memory pool to guarantee no allocations on the audio thread
16 4020 : buffer_pool_.resize(max_nodes_, std::vector<float>(max_block_size_, 0.0f));
17 2419 : sum_buffer_.resize(max_block_size_, 0.0f);
18 2419 : execution_plan_.reserve(max_nodes_);
19 2419 : }
20 :
21 2511 : void AudioGraphExecutor::compile(const AudioGraph& graph) {
22 2511 : execution_plan_.clear();
23 2511 : const auto& sorted_ids = graph.get_sorted_nodes();
24 2511 : const auto& nodes = graph.get_nodes();
25 2511 : const auto& links = graph.get_links();
26 :
27 3182 : if (sorted_ids.empty() || sorted_ids.size() > (size_t)max_nodes_) return;
28 :
29 : // Map each Node ID to a dedicated row in the buffer pool
30 2013 : std::unordered_map<int, int> node_to_buffer;
31 7956 : for (size_t i = 0; i < sorted_ids.size(); ++i) {
32 5943 : node_to_buffer[sorted_ids[i]] = static_cast<int>(i);
33 1981 : }
34 :
35 : // Determine if there are any explicit inputs or outputs
36 2013 : any_explicit_input_ = false;
37 2013 : bool any_explicit_output = false;
38 7956 : for (int node_id : sorted_ids) {
39 7924 : auto it = std::find_if(nodes.begin(), nodes.end(),
40 16153 : [&](const DSPNode& n) { return n.id == node_id; });
41 5943 : if (it != nodes.end()) {
42 5943 : if (it->is_graph_input) any_explicit_input_ = true;
43 7409 : if (it->is_graph_output) any_explicit_output = true;
44 1981 : }
45 : }
46 :
47 2013 : fallback_input_node_id_ = -1;
48 2013 : if (!any_explicit_input_ && !sorted_ids.empty()) {
49 51 : fallback_input_node_id_ = sorted_ids[0];
50 17 : }
51 :
52 : // Build the flat execution array
53 7956 : for (int node_id : sorted_ids) {
54 7924 : auto it = std::find_if(nodes.begin(), nodes.end(),
55 16153 : [&](const DSPNode& n) { return n.id == node_id; });
56 5943 : if (it == nodes.end() || !it->is_reachable) continue;
57 :
58 4404 : NodeExecutionStep step;
59 4404 : step.node_id = node_id;
60 4404 : step.buffer_index = node_to_buffer[node_id];
61 4404 : step.type = it->routing_type;
62 4404 : step.pedal = it->pedal;
63 4404 : step.is_graph_input = it->is_graph_input;
64 4404 : step.is_graph_output = it->is_graph_output;
65 :
66 4404 : if (any_explicit_output) {
67 4404 : step.is_sink = it->is_graph_output;
68 1468 : } else {
69 : // Check if this node has outgoing links
70 0 : bool has_outgoing = false;
71 0 : for (int out_pin : it->output_pin_ids) {
72 0 : for (const auto& link : links) {
73 0 : if (link.source_pin_id == out_pin) {
74 0 : has_outgoing = true;
75 0 : break;
76 : }
77 : }
78 0 : if (has_outgoing) break;
79 : }
80 0 : step.is_sink = !has_outgoing;
81 : }
82 :
83 : // Trace upstream connections to find which buffers to read from
84 9735 : for (size_t pin_idx = 0; pin_idx < it->input_pin_ids.size(); ++pin_idx) {
85 5331 : int in_pin = it->input_pin_ids[pin_idx];
86 5331 : float pin_gain = 1.0f;
87 5331 : if (pin_idx < it->input_gains.size()) {
88 1851 : pin_gain = it->input_gains[pin_idx];
89 617 : }
90 20490 : for (const auto& link : links) {
91 15159 : if (link.dest_pin_id == in_pin) {
92 3195 : int src_node_id = graph.get_node_from_pin(link.source_pin_id);
93 3195 : if (src_node_id != -1 && node_to_buffer.count(src_node_id)) {
94 4260 : step.input_sources.push_back(
95 3195 : {node_to_buffer[src_node_id], pin_gain, static_cast<int>(pin_idx)});
96 1065 : }
97 1065 : }
98 : }
99 1777 : }
100 :
101 4404 : if (it->routing_type == NodeRoutingType::StandardEffect && it->pedal) {
102 699 : step.processor = std::make_unique<StandardEffectProcessor>(it->pedal);
103 233 : } else {
104 3705 : step.processor = std::make_unique<PassthroughProcessor>();
105 : }
106 :
107 4404 : execution_plan_.push_back(std::move(step));
108 4404 : }
109 2186 : }
110 :
111 4625 : void AudioGraphExecutor::update_transport_state(float bpm) {
112 4709 : for (auto& step : execution_plan_) {
113 84 : if (step.pedal) {
114 39 : step.pedal->set_transport_state(bpm);
115 13 : }
116 : }
117 4625 : }
118 :
119 5588 : void AudioGraphExecutor::process(const float* input, float* output, int num_samples) {
120 5588 : if (num_samples > max_block_size_) {
121 9 : std::memset(output, 0, static_cast<size_t>(num_samples) * sizeof(float));
122 9 : return;
123 : }
124 5579 : if (execution_plan_.empty()) {
125 4595 : std::memset(output, 0, num_samples * sizeof(float));
126 4595 : return;
127 : }
128 :
129 4200 : for (const auto& step : execution_plan_) {
130 3216 : float* node_input = sum_buffer_.data();
131 :
132 3216 : bool is_input_source = false;
133 3216 : if (any_explicit_input_) {
134 3216 : is_input_source = step.is_graph_input;
135 1072 : } else {
136 0 : is_input_source = (step.node_id == fallback_input_node_id_);
137 : }
138 :
139 3216 : if (is_input_source) {
140 : // Feed the master guitar input
141 990 : std::memcpy(node_input, input, num_samples * sizeof(float));
142 330 : } else {
143 : // Summation: Additively mix all incoming paths
144 2226 : std::memset(node_input, 0, num_samples * sizeof(float));
145 4776 : for (const auto& src : step.input_sources) {
146 2550 : const float* src_buf = buffer_pool_[src.buffer_index].data();
147 2550 : if (src.gain == 1.0f) {
148 319146 : for (int i = 0; i < num_samples; ++i) {
149 316608 : node_input[i] += src_buf[i];
150 105536 : }
151 846 : } else {
152 780 : for (int i = 0; i < num_samples; ++i) {
153 768 : node_input[i] += src_buf[i] * src.gain;
154 256 : }
155 : }
156 : }
157 : }
158 :
159 3216 : float* node_output = buffer_pool_[step.buffer_index].data();
160 :
161 3216 : if (step.processor) {
162 3216 : step.processor->process(node_input, node_output, num_samples);
163 1072 : } else {
164 0 : std::memcpy(node_output, node_input, num_samples * sizeof(float));
165 : }
166 : }
167 :
168 : // Accumulate/mix outputs from all explicit sink nodes into the final output buffer
169 984 : std::memset(output, 0, num_samples * sizeof(float));
170 4200 : for (const auto& step : execution_plan_) {
171 3216 : if (step.is_sink) {
172 987 : const float* sink_buf = buffer_pool_[step.buffer_index].data();
173 122139 : for (int i = 0; i < num_samples; ++i) {
174 121152 : output[i] += sink_buf[i];
175 40384 : }
176 329 : }
177 : }
178 2652 : }
179 :
180 3 : void AudioGraphExecutor::update_mixer_gain(int node_id, int pin_index, float gain) {
181 15 : for (auto& step : execution_plan_) {
182 15 : if (step.node_id == node_id &&
183 3 : (step.type == NodeRoutingType::Mixer || step.type == NodeRoutingType::MergeSum)) {
184 3 : for (auto& src : step.input_sources) {
185 3 : if (src.pin_index == pin_index) {
186 3 : src.gain = std::clamp(gain, 0.0f, 2.0f);
187 3 : break;
188 : }
189 : }
190 2 : break;
191 : }
192 : }
193 3 : }
194 :
195 : } // namespace Amplitron
|