Line data Source code
1 : #include "audio/engine/audio_graph_executor.h"
2 : #include <algorithm>
3 : #include <cstring>
4 :
5 : namespace Amplitron {
6 :
7 3374 : AudioGraphExecutor::AudioGraphExecutor() {}
8 :
9 2527 : void AudioGraphExecutor::prepare(int sample_rate, int max_block_size, int max_nodes) {
10 2527 : sample_rate_ = sample_rate;
11 2527 : max_block_size_ = max_block_size;
12 2527 : max_nodes_ = max_nodes;
13 :
14 : // Pre-allocate the memory pool to guarantee no allocations on the audio thread
15 4207 : buffer_pool_.resize(max_nodes_, std::vector<float>(max_block_size_, 0.0f));
16 2527 : sum_buffer_.resize(max_block_size_, 0.0f);
17 2527 : execution_plan_.reserve(max_nodes_);
18 2527 : }
19 :
20 2559 : void AudioGraphExecutor::compile(const AudioGraph& graph) {
21 2559 : execution_plan_.clear();
22 2559 : const auto& sorted_ids = graph.get_sorted_nodes();
23 2559 : const auto& nodes = graph.get_nodes();
24 2559 : const auto& links = graph.get_links();
25 :
26 3257 : if (sorted_ids.empty() || sorted_ids.size() > (size_t)max_nodes_) return;
27 :
28 : // Map each Node ID to a dedicated row in the buffer pool
29 2094 : std::unordered_map<int, int> node_to_buffer;
30 8370 : for (size_t i = 0; i < sorted_ids.size(); ++i) {
31 6276 : node_to_buffer[sorted_ids[i]] = static_cast<int>(i);
32 2092 : }
33 :
34 : // Determine if there are any explicit inputs or outputs
35 2094 : any_explicit_input_ = false;
36 2094 : bool any_explicit_output = false;
37 8370 : for (int node_id : sorted_ids) {
38 21744 : auto it = std::find_if(nodes.begin(), nodes.end(), [&](const DSPNode& n){ return n.id == node_id; });
39 6276 : if (it != nodes.end()) {
40 6276 : if (it->is_graph_input) any_explicit_input_ = true;
41 7860 : if (it->is_graph_output) any_explicit_output = true;
42 2092 : }
43 : }
44 :
45 2094 : fallback_input_node_id_ = -1;
46 2094 : if (!any_explicit_input_ && !sorted_ids.empty()) {
47 51 : fallback_input_node_id_ = sorted_ids[0];
48 17 : }
49 :
50 : // Build the flat execution array
51 8370 : for (int node_id : sorted_ids) {
52 21744 : auto it = std::find_if(nodes.begin(), nodes.end(), [&](const DSPNode& n){ return n.id == node_id; });
53 6276 : if (it == nodes.end() || !it->is_reachable) continue;
54 :
55 4344 : NodeExecutionStep step;
56 4344 : step.node_id = node_id;
57 4344 : step.buffer_index = node_to_buffer[node_id];
58 4344 : step.type = it->routing_type;
59 4344 : step.pedal = it->pedal;
60 4344 : step.is_graph_input = it->is_graph_input;
61 4344 : step.is_graph_output = it->is_graph_output;
62 :
63 4344 : if (any_explicit_output) {
64 4344 : step.is_sink = it->is_graph_output;
65 1448 : } else {
66 : // Check if this node has outgoing links
67 0 : bool has_outgoing = false;
68 0 : for (int out_pin : it->output_pin_ids) {
69 0 : for (const auto& link : links) {
70 0 : if (link.source_pin_id == out_pin) {
71 0 : has_outgoing = true;
72 0 : break;
73 : }
74 : }
75 0 : if (has_outgoing) break;
76 : }
77 0 : step.is_sink = !has_outgoing;
78 : }
79 :
80 : // Trace upstream connections to find which buffers to read from
81 9606 : for (size_t pin_idx = 0; pin_idx < it->input_pin_ids.size(); ++pin_idx) {
82 5262 : int in_pin = it->input_pin_ids[pin_idx];
83 5262 : float pin_gain = 1.0f;
84 5262 : if (pin_idx < it->input_gains.size()) {
85 1833 : pin_gain = it->input_gains[pin_idx];
86 611 : }
87 20103 : for (const auto& link : links) {
88 14841 : if (link.dest_pin_id == in_pin) {
89 3141 : int src_node_id = graph.get_node_from_pin(link.source_pin_id);
90 3141 : if (src_node_id != -1 && node_to_buffer.count(src_node_id)) {
91 3141 : step.input_sources.push_back({ node_to_buffer[src_node_id], pin_gain, static_cast<int>(pin_idx) });
92 1047 : }
93 1047 : }
94 : }
95 1754 : }
96 4344 : execution_plan_.push_back(step);
97 4344 : }
98 2249 : }
99 :
100 5160 : void AudioGraphExecutor::update_transport_state(float bpm) {
101 5178 : for (auto& step : execution_plan_) {
102 18 : if (step.pedal) {
103 6 : step.pedal->set_transport_state(bpm);
104 2 : }
105 : }
106 5160 : }
107 :
108 6108 : void AudioGraphExecutor::process(const float* input, float* output, int num_samples) {
109 6108 : if (num_samples > max_block_size_) {
110 9 : std::memcpy(output, input, static_cast<size_t>(max_block_size_) * sizeof(float));
111 9 : return;
112 : }
113 6099 : if (execution_plan_.empty()) {
114 5157 : std::memcpy(output, input, num_samples * sizeof(float));
115 5157 : return;
116 : }
117 :
118 4044 : for (const auto& step : execution_plan_) {
119 3102 : float* node_input = sum_buffer_.data();
120 :
121 3102 : bool is_input_source = false;
122 3102 : if (any_explicit_input_) {
123 3102 : is_input_source = step.is_graph_input;
124 1034 : } else {
125 0 : is_input_source = (step.node_id == fallback_input_node_id_);
126 : }
127 :
128 3102 : if (is_input_source) {
129 : // Feed the master guitar input
130 948 : std::memcpy(node_input, input, num_samples * sizeof(float));
131 316 : } else {
132 : // Summation: Additively mix all incoming paths
133 2154 : std::memset(node_input, 0, num_samples * sizeof(float));
134 4623 : for (const auto& src : step.input_sources) {
135 2469 : const float* src_buf = buffer_pool_[src.buffer_index].data();
136 2469 : if (src.gain == 1.0f) {
137 313881 : for (int i = 0; i < num_samples; ++i) {
138 311424 : node_input[i] += src_buf[i];
139 103808 : }
140 819 : } else {
141 780 : for (int i = 0; i < num_samples; ++i) {
142 768 : node_input[i] += src_buf[i] * src.gain;
143 256 : }
144 : }
145 : }
146 : }
147 :
148 3102 : float* node_output = buffer_pool_[step.buffer_index].data();
149 :
150 3102 : if (step.type == NodeRoutingType::StandardEffect && step.pedal) {
151 : // FIX: In-place processing!
152 : // 1. Copy the summed input data into our dedicated output buffer
153 6 : std::memcpy(node_output, node_input, num_samples * sizeof(float));
154 :
155 : // 2. Tell the pedal to process and overwrite that buffer directly
156 6 : step.pedal->process(node_output, num_samples);
157 2 : } else {
158 : // Merge nodes or empty wrappers just pass the summed input directly downstream
159 3096 : std::memcpy(node_output, node_input, num_samples * sizeof(float));
160 : }
161 : }
162 :
163 : // Accumulate/mix outputs from all explicit sink nodes into the final output buffer
164 942 : std::memset(output, 0, num_samples * sizeof(float));
165 4044 : for (const auto& step : execution_plan_) {
166 3102 : if (step.is_sink) {
167 945 : const float* sink_buf = buffer_pool_[step.buffer_index].data();
168 119409 : for (int i = 0; i < num_samples; ++i) {
169 118464 : output[i] += sink_buf[i];
170 39488 : }
171 315 : }
172 : }
173 3182 : }
174 :
175 3 : void AudioGraphExecutor::update_mixer_gain(int node_id, int pin_index, float gain) {
176 15 : for (auto& step : execution_plan_) {
177 15 : if (step.node_id == node_id && (step.type == NodeRoutingType::Mixer || step.type == NodeRoutingType::MergeSum)) {
178 3 : for (auto& src : step.input_sources) {
179 3 : if (src.pin_index == pin_index) {
180 3 : src.gain = std::clamp(gain, 0.0f, 2.0f);
181 3 : break;
182 : }
183 : }
184 2 : break;
185 : }
186 : }
187 3 : }
188 :
189 : } // namespace Amplitron
|