LCOV - code coverage report
Current view: top level - src/audio/engine - audio_engine_process.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 86.4 % 258 223
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 6 6

            Line data    Source code
       1              : #include "audio/engine/audio_engine.h"
       2              : #include <cstring>
       3              : #include <chrono>
       4              : #include <algorithm>
       5              : #include <cmath>
       6              : 
       7              : namespace Amplitron {
       8              : 
       9           59 : void AudioEngine::update_metronome_timing() {
      10           59 :     const int bpm = std::max(40, std::min(metronome_bpm_, 240));
      11           59 :     metronome_bpm_ = bpm;
      12           59 :     if (sample_rate_ <= 0) {
      13            0 :         metronome_samples_per_beat_ = 0.0;
      14            0 :         metronome_click_phase_inc_ = 0.0f;
      15            0 :         metronome_click_samples_total_ = 0;
      16            0 :         metronome_click_decay_ = 0.0f;
      17            0 :         return;
      18              :     }
      19              : 
      20           83 :     metronome_samples_per_beat_ = (static_cast<double>(sample_rate_) * 60.0)
      21           59 :                                  / static_cast<double>(bpm);
      22           59 :     if (metronome_samples_per_beat_ < 1.0) {
      23            0 :         metronome_samples_per_beat_ = 1.0;
      24            0 :     }
      25              : 
      26           59 :     constexpr float kClickLengthSec = 0.01f;
      27           59 :     const int click_samples = std::max(1, static_cast<int>(sample_rate_ * kClickLengthSec + 0.5f));
      28           59 :     metronome_click_samples_total_ = click_samples;
      29              : 
      30           59 :     constexpr float kTwoPi = 6.28318530718f;
      31           59 :     constexpr float kClickFreq = 1000.0f;
      32           59 :     metronome_click_phase_inc_ = (kTwoPi * kClickFreq) / static_cast<float>(sample_rate_);
      33              : 
      34           59 :     const float target = 0.001f;
      35           59 :     metronome_click_decay_ = std::exp(std::log(target) / static_cast<float>(click_samples));
      36           24 : }
      37              : 
      38         5160 : void AudioEngine::process_audio(const float* input, float* output, int frame_count) {
      39         5160 :     auto t_start = std::chrono::steady_clock::now();
      40              : 
      41         5160 :     if (frame_count > static_cast<int>(process_buffer_.size())) {
      42            3 :         process_buffer_.resize(frame_count, 0.0f);
      43            3 :         process_buffer_right_.resize(frame_count, 0.0f);
      44            1 :     }
      45              : 
      46         5160 :     const bool analyzer_on = analyzer_enabled_.load(std::memory_order_relaxed);
      47              : 
      48         5160 :     float in_gain = input_gain_.load(std::memory_order_relaxed);
      49         5160 :     float peak_in = 0.0f;
      50         5160 :     if (analyzer_on) {
      51         5160 :         float sum_sq_in = 0.0f;
      52         5160 :         bool clipped_in = false;
      53         5160 :         int cap = analyzer_capture_index_;
      54       341224 :         for (int i = 0; i < frame_count; ++i) {
      55       336064 :             process_buffer_[i] = input[i] * in_gain;
      56       336064 :             float abs_val = std::fabs(process_buffer_[i]);
      57       336064 :             if (abs_val > peak_in) peak_in = abs_val;
      58       336064 :             if (abs_val >= 1.0f) clipped_in = true;
      59       336064 :             sum_sq_in += process_buffer_[i] * process_buffer_[i];
      60       336064 :             analyzer_capture_input_[cap] = process_buffer_[i];
      61       336064 :             cap = (cap + 1) & ANALYZER_FFT_MASK;
      62       185344 :         }
      63         5160 :         input_rms_.store(std::sqrt(sum_sq_in / std::max(frame_count, 1)), std::memory_order_relaxed);
      64         5160 :         if (clipped_in) input_clipped_.store(true, std::memory_order_release);
      65         5160 :         analyzer_capture_index_ = cap;
      66         2866 :     } else {
      67            0 :         for (int i = 0; i < frame_count; ++i) {
      68            0 :             process_buffer_[i] = input[i] * in_gain;
      69            0 :             float abs_val = std::fabs(process_buffer_[i]);
      70            0 :             if (abs_val > peak_in) peak_in = abs_val;
      71            0 :         }
      72              :     }
      73         5160 :     input_level_.store(peak_in);
      74              : 
      75         8026 :     std::memcpy(process_buffer_right_.data(), process_buffer_.data(),
      76         5160 :                 static_cast<size_t>(frame_count) * sizeof(float));
      77              : 
      78         5160 :     drain_gain_commands();
      79              : 
      80         5160 :     const bool metronome_target = metronome_enabled_state_.load(std::memory_order_relaxed);
      81         5160 :     if (metronome_target != metronome_enabled_) {
      82            9 :         metronome_enabled_ = metronome_target;
      83            9 :         metronome_sample_counter_ = 0.0;
      84            9 :         metronome_click_samples_remaining_ = 0;
      85            9 :         metronome_click_env_ = 0.0f;
      86            9 :         metronome_click_phase_ = 0.0f;
      87            3 :     }
      88              : 
      89         5160 :     const int bpm_state = metronome_bpm_state_.load(std::memory_order_relaxed);
      90         5160 :     const bool bpm_changed = (bpm_state != metronome_bpm_);
      91         5160 :     if (bpm_changed) {
      92            6 :         metronome_bpm_ = bpm_state;
      93            2 :     }
      94              : 
      95         5160 :     const float volume_state = metronome_volume_state_.load(std::memory_order_relaxed);
      96         5160 :     if (volume_state != metronome_volume_) {
      97            6 :         metronome_volume_ = clamp(volume_state, 0.0f, 1.0f);
      98            2 :     }
      99              : 
     100         5160 :     const bool sample_rate_changed = (metronome_sample_rate_ != sample_rate_);
     101         5160 :     if (sample_rate_changed) {
     102           50 :         metronome_sample_rate_ = sample_rate_;
     103           20 :     }
     104              : 
     105         5160 :     const bool timing_dirty = sample_rate_changed || bpm_changed;
     106              : 
     107         5160 :     if (timing_dirty) {
     108           53 :         update_metronome_timing();
     109           53 :         if (metronome_enabled_) {
     110            6 :             if (metronome_sample_counter_ <= 0.0 ||
     111            0 :                 metronome_sample_counter_ > metronome_samples_per_beat_) {
     112            6 :                 metronome_sample_counter_ = metronome_samples_per_beat_;
     113            2 :             }
     114            2 :         }
     115           21 :     }
     116              : 
     117         5160 :     if (effect_mutex_.try_lock()) {
     118         5160 :         drain_commands();
     119         5160 :         if (topology_dirty_.exchange(false, std::memory_order_acq_rel)) {
     120           50 :             audio_shadow_executor_ = main_executor_;
     121           50 :             audio_shadow_tuner_   = tuner_tap_;
     122           20 :         }
     123         5160 :         effect_mutex_.unlock();
     124         2866 :     }
     125              : 
     126         5160 :     if (audio_shadow_tuner_ && audio_shadow_tuner_->is_enabled()) {
     127            3 :         audio_shadow_tuner_->process(process_buffer_.data(), frame_count);
     128            4 :         std::memcpy(process_buffer_right_.data(), process_buffer_.data(),
     129            3 :                     static_cast<size_t>(frame_count) * sizeof(float));
     130            1 :     }
     131              :     // The executor handles all the looping, routing, and processing internally!
     132         5160 :     if (audio_shadow_executor_) {
     133              :         // Broadcast tempo/bpm
     134         5160 :         audio_shadow_executor_->update_transport_state(static_cast<float>(metronome_bpm_));
     135              :         
     136              :         // Pass your mono/stereo buffers to the executor we built
     137         5160 :         audio_shadow_executor_->process(process_buffer_.data(), process_buffer_right_.data(), frame_count);
     138         8026 :         std::memcpy(process_buffer_.data(), process_buffer_right_.data(),
     139         5160 :                     static_cast<size_t>(frame_count) * sizeof(float));
     140         2866 :     }
     141              : 
     142         5160 :     float out_gain = output_gain_.load(std::memory_order_relaxed);
     143         5160 :     float peak_out = 0.0f;
     144       340077 :     auto next_metronome_sample = [this]() -> float {
     145              :         
     146       336064 :         metronome_bpm_smoothed_ += metronome_bpm_smooth_alpha_ * (metronome_bpm_ - metronome_bpm_smoothed_);
     147       336064 :         metronome_volume_smoothed_ += metronome_volume_smooth_alpha_ * (metronome_volume_ - metronome_volume_smoothed_);
     148              :         
     149       336064 :         if (metronome_bpm_smoothed_ > 0.0f) {
     150       336064 :             metronome_samples_per_beat_ = (static_cast<double>(sample_rate_) * 60.0) / metronome_bpm_smoothed_;
     151       185344 :         }
     152              :         
     153       336064 :         if (!metronome_enabled_ || metronome_samples_per_beat_ <= 0.0) {
     154       116480 :             return 0.0f;
     155              :         }
     156       216384 :         metronome_sample_counter_ -= 1.0;
     157       216384 :         if (metronome_sample_counter_ <= 0.0) {
     158            3 :             metronome_sample_counter_ += metronome_samples_per_beat_;
     159            3 :             metronome_click_samples_remaining_ = metronome_click_samples_total_;
     160            3 :             metronome_click_env_ = 1.0f;
     161            3 :             metronome_click_phase_ = 0.0f;
     162            1 :         }
     163       216384 :         if (metronome_click_samples_remaining_ <= 0) {
     164       144126 :             return 0.0f;
     165              :         }
     166           65 :         static constexpr float kTwoPi = 6.28318530718f;
     167          195 :         float click = std::sin(metronome_click_phase_) * metronome_click_env_ * metronome_volume_smoothed_;
     168          195 :         metronome_click_phase_ += metronome_click_phase_inc_;
     169          195 :         if (metronome_click_phase_ >= kTwoPi) {
     170            3 :             metronome_click_phase_ -= kTwoPi;
     171            1 :         }
     172          195 :         metronome_click_env_ *= metronome_click_decay_;
     173          195 :         --metronome_click_samples_remaining_;
     174          195 :         return click;
     175       187638 :     };
     176         5160 :     if (analyzer_on) {
     177         5160 :         float sum_sq_out = 0.0f;
     178         5160 :         bool clipped_out = false;
     179         5160 :         int cap = (analyzer_capture_index_ - frame_count) & ANALYZER_FFT_MASK;
     180       341224 :         for (int i = 0; i < frame_count; ++i) {
     181       336064 :             float click = next_metronome_sample();
     182       336064 :             float out_l = clamp(process_buffer_[i]       * out_gain + click, -1.0f, 1.0f);
     183       336064 :             float out_r = clamp(process_buffer_right_[i] * out_gain + click, -1.0f, 1.0f);
     184       336064 :             if (std::fabs(out_l) >= 1.0f || std::fabs(out_r) >= 1.0f) clipped_out = true;
     185       336064 :             output[i * 2]     = out_l;
     186       336064 :             output[i * 2 + 1] = out_r;
     187       336064 :             process_buffer_[i] = out_l;
     188       336064 :             float abs_val = std::fabs(out_l);
     189       336064 :             if (abs_val > peak_out) peak_out = abs_val;
     190       336064 :             sum_sq_out += out_l * out_l;
     191       336064 :             analyzer_capture_output_[cap] = out_l;
     192       336064 :             cap = (cap + 1) & ANALYZER_FFT_MASK;
     193       185344 :         }
     194         5160 :         output_rms_.store(std::sqrt(sum_sq_out / std::max(frame_count, 1)), std::memory_order_relaxed);
     195         5160 :         if (clipped_out) output_clipped_.store(true, std::memory_order_release);
     196              : 
     197         5160 :         analyzer_samples_since_publish_ += frame_count;
     198         5160 :         if (analyzer_samples_since_publish_ >= ANALYZER_HOP_SIZE) {
     199          321 :             if (analyzer_mutex_.try_lock()) {
     200          321 :                 const int start = analyzer_capture_index_;
     201          321 :                 const int first_chunk = ANALYZER_FFT_SIZE - start;
     202          642 :                 std::memcpy(analyzer_snapshot_input_.data(),
     203          321 :                             analyzer_capture_input_.data() + start,
     204          321 :                             static_cast<size_t>(first_chunk) * sizeof(float));
     205          642 :                 std::memcpy(analyzer_snapshot_input_.data() + first_chunk,
     206          321 :                             analyzer_capture_input_.data(),
     207          249 :                             static_cast<size_t>(start) * sizeof(float));
     208          642 :                 std::memcpy(analyzer_snapshot_output_.data(),
     209          321 :                             analyzer_capture_output_.data() + start,
     210          249 :                             static_cast<size_t>(first_chunk) * sizeof(float));
     211          642 :                 std::memcpy(analyzer_snapshot_output_.data() + first_chunk,
     212          321 :                             analyzer_capture_output_.data(),
     213          249 :                             static_cast<size_t>(start) * sizeof(float));
     214          321 :                 analyzer_sequence_.fetch_add(1, std::memory_order_release);
     215          321 :                 analyzer_samples_since_publish_ = 0;
     216          321 :                 analyzer_mutex_.unlock();
     217          177 :             }
     218          177 :         }
     219         2866 :     } else {
     220            0 :         for (int i = 0; i < frame_count; ++i) {
     221            0 :             float click = next_metronome_sample();
     222            0 :             float out_l = clamp(process_buffer_[i]       * out_gain + click, -1.0f, 1.0f);
     223            0 :             float out_r = clamp(process_buffer_right_[i] * out_gain + click, -1.0f, 1.0f);
     224            0 :             output[i * 2]     = out_l;
     225            0 :             output[i * 2 + 1] = out_r;
     226            0 :             process_buffer_[i] = out_l;
     227            0 :             float abs_val = std::fabs(out_l);
     228            0 :             if (abs_val > peak_out) peak_out = abs_val;
     229            0 :         }
     230              :     }
     231         5160 :     output_level_.store(peak_out);
     232              : 
     233         5160 :     if (recorder_.is_recording()) {
     234            4 :     recorder_.write_samples_stereo(process_buffer_.data(),
     235            3 :                                    process_buffer_right_.data(),
     236            1 :                                    frame_count);
     237            1 : }
     238              : 
     239         5160 :     auto t_end = std::chrono::steady_clock::now();
     240         5160 :     float duration_us = std::chrono::duration<float, std::micro>(t_end - t_start).count();
     241         5160 :     callback_duration_us_.store(duration_us, std::memory_order_relaxed);
     242         5160 :     float budget_us = (static_cast<float>(frame_count) / sample_rate_) * 1e6f;
     243         5160 :     cpu_load_.store(duration_us / budget_us, std::memory_order_relaxed);
     244         5160 : }
     245              : 
     246         5160 : void AudioEngine::drain_gain_commands() {
     247         1147 :     AudioCommand cmd;
     248         5211 :     while (command_queue_.try_peek(cmd)) {
     249           60 :         if (cmd.type == AudioCommand::SetInputGain) {
     250           27 :             command_queue_.try_pop(cmd);
     251           27 :             input_gain_.store(cmd.value, std::memory_order_relaxed);
     252           42 :         } else if (cmd.type == AudioCommand::SetOutputGain) {
     253           24 :             command_queue_.try_pop(cmd);
     254           24 :             output_gain_.store(cmd.value, std::memory_order_relaxed);
     255           17 :         } else if (cmd.type == AudioCommand::SetMixerGain) {
     256            0 :             command_queue_.try_pop(cmd);
     257            0 :             if (audio_shadow_executor_) {
     258            0 :                 audio_shadow_executor_->update_mixer_gain(cmd.effect_index, cmd.param_index, cmd.value);
     259            0 :             }
     260            0 :         } else {
     261            6 :             break;
     262              :         }
     263              :     }
     264         5160 : }
     265              : 
     266         5160 : void AudioEngine::drain_commands() {
     267         1147 :     AudioCommand cmd;
     268         5184 :     while (command_queue_.try_pop(cmd)) {
     269              :         
     270              :         // Helper to find the effect pointer safely inside the new Graph architecture by chain index
     271           37 :         auto get_effect = [&](int effect_index) -> std::shared_ptr<Effect> {
     272           21 :             if (effect_index >= 0 && effect_index < static_cast<int>(dummy_effects_.size())) {
     273           12 :                 return dummy_effects_[effect_index];
     274              :             }
     275            9 :             return nullptr;
     276           23 :         };
     277              : 
     278           24 :         switch (cmd.type) {
     279            4 :             case AudioCommand::SetEffectParam: {
     280            7 :                 if (auto fx = get_effect(cmd.effect_index)) {
     281            3 :                     auto& params = fx->params();
     282            4 :                     if (cmd.param_index >= 0 &&
     283            3 :                         cmd.param_index < static_cast<int>(params.size())) {
     284            3 :                         params[cmd.param_index].value = cmd.value;
     285            1 :                     }
     286            5 :                 }
     287            6 :                 break;
     288              :             }
     289            6 :             case AudioCommand::SetEffectEnabled: {
     290           11 :                 if (auto fx = get_effect(cmd.effect_index)) {
     291            6 :                     fx->set_enabled(cmd.value > 0.5f);
     292            8 :                 }
     293            9 :                 break;
     294              :             }
     295            4 :             case AudioCommand::SetEffectMix: {
     296            7 :                 if (auto fx = get_effect(cmd.effect_index)) {
     297            3 :                     fx->set_mix(cmd.value);
     298            5 :                 }
     299            6 :                 break;
     300              :             }
     301            0 :             case AudioCommand::SetMixerGain:
     302              :                 // Skip SetMixerGain in the mutex-gated path, it is handled lock-free
     303            0 :                 break;
     304            2 :             case AudioCommand::SetInputGain:
     305            3 :                 input_gain_.store(cmd.value, std::memory_order_relaxed);
     306            3 :                 break;
     307            0 :             case AudioCommand::SetOutputGain:
     308            0 :                 output_gain_.store(cmd.value, std::memory_order_relaxed);
     309            0 :                 break;
     310            0 :             default:
     311            0 :                 break;
     312              :         }
     313              :     }
     314         5160 : }
     315              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1