LCOV - code coverage report
Current view: top level - src/audio/engine - audio_graph_executor.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 92.5 % 134 124
Test Date: 2026-06-07 15:51:50 Functions: 100.0 % 8 8

            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
        

Generated by: LCOV version 2.0-1