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.9 % 127 118
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 8 8

            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
        

Generated by: LCOV version 2.0-1