LCOV - code coverage report
Current view: top level - src/audio/engine - audio_engine.cpp (source / functions) Coverage Total Hit
Test: merged.info Lines: 77.2 % 263 203
Test Date: 2026-06-07 15:51:50 Functions: 84.2 % 38 32

            Line data    Source code
       1              : #include "audio/engine/audio_engine.h"
       2              : 
       3              : #include "audio/backend/audio_backend.h"
       4              : #include "audio/engine/analyzer_capture.h"
       5              : #ifdef AMPLITRON_ANDROID_OBOE
       6              : #include "audio/backend/oboe_backend.h"
       7              : #endif
       8              : #include <algorithm>
       9              : #include <iostream>
      10              : #include <nlohmann/json.hpp>
      11              : 
      12              : namespace Amplitron {
      13              : 
      14         1366 : AudioEngine::AudioEngine(std::unique_ptr<IRecorder> recorder, std::unique_ptr<IMetronome> metronome)
      15          538 :     : metronome_(std::move(metronome)),
      16          538 :       recorder_(std::move(recorder)),
      17         1616 :       analyzer_capture_(std::make_unique<AnalyzerCapture>()) {
      18          814 :     if (!recorder_) recorder_ = std::make_unique<Recorder>();
      19          814 :     if (!metronome_) metronome_ = std::make_unique<Metronome>();
      20              : 
      21          814 :     process_buffer_.resize(16384, 0.0f);
      22          814 :     process_buffer_right_.resize(16384, 0.0f);
      23              : #if defined(AMPLITRON_ANDROID_OBOE)
      24              :     backend_ = AudioBackendFactory::create_backend("oboe");
      25              : #elif defined(__EMSCRIPTEN__) || (defined(__APPLE__) && TARGET_OS_IPHONE)
      26              :     backend_ = AudioBackendFactory::create_backend("sdl");
      27              : #else
      28          814 :     backend_ = AudioBackendFactory::create_backend("portaudio");
      29              : #endif
      30          814 :     metronome_->set_sample_rate(sample_rate_);
      31              : 
      32              :     // Pre-allocate the graph memory pools immediately on startup
      33          814 :     main_executor_ = std::make_shared<AudioGraphExecutor>();
      34              :     // Assuming standard values, use your engine's actual sample rate / block size variables here
      35          814 :     main_executor_->prepare(48000, 512, 32);
      36              : 
      37              :     // Seed the shadow executor so the audio thread has something safe to read instantly
      38          814 :     audio_shadow_executor_ = main_executor_;
      39          814 : }
      40              : 
      41         1662 : AudioEngine::~AudioEngine() { shutdown(); }
      42              : 
      43              : // --- Serialization Methods ---
      44              : 
      45            9 : nlohmann::json AudioEngine::serialize() {
      46            9 :     std::lock_guard<std::mutex> lock(effect_mutex_);
      47            9 :     nlohmann::json j;
      48              : 
      49              :     // Read atomic variables safely
      50            9 :     j["input_gain"] = input_gain_.load(std::memory_order_relaxed);
      51              : 
      52            9 :     auto effects_array = nlohmann::json::array();
      53           15 :     for (const auto& fx : dummy_effects_) {
      54            6 :         if (fx) {
      55            6 :             nlohmann::json fx_json;
      56            6 :             fx_json["name"] = fx->name();
      57            6 :             fx_json["params"] = fx->get_params();
      58            6 :             effects_array.push_back(fx_json);
      59            6 :         }
      60              :     }
      61            9 :     j["effects"] = effects_array;
      62           12 :     return j;
      63            9 : }
      64              : 
      65            6 : void AudioEngine::deserialize(const nlohmann::json& j) {
      66            6 :     std::lock_guard<std::mutex> lock(effect_mutex_);
      67              : 
      68            6 :     if (j.contains("input_gain")) {
      69            6 :         set_input_gain(j["input_gain"]);
      70            2 :     }
      71              : 
      72            6 :     if (j.contains("effects")) {
      73           16 :         for (const auto& fx_data : j["effects"]) {
      74            6 :             std::string name = fx_data["name"];
      75           18 :             for (auto& fx : dummy_effects_) {
      76           20 :                 if (fx && std::string(fx->name()) == name) {
      77            6 :                     fx->set_params(fx_data["params"]);
      78            2 :                 }
      79              :             }
      80            6 :         }
      81            2 :     }
      82            6 : }
      83              : 
      84              : // --- Existing Methods ---
      85              : 
      86          162 : void AudioEngine::set_buffer_size(int size) {
      87          162 :     size = std::max(MIN_BUFFER_SIZE, std::min(MAX_BUFFER_SIZE, size));
      88          162 :     int prev_size = buffer_size_;
      89          162 :     bool was_running = running_;
      90          162 :     if (was_running) stop();
      91          162 :     buffer_size_ = size;
      92              : 
      93              :     // Pre-allocate buffers for the new size
      94          162 :     if (static_cast<size_t>(size) > process_buffer_.size()) {
      95            0 :         process_buffer_.resize(size, 0.0f);
      96            0 :         process_buffer_right_.resize(size, 0.0f);
      97            0 :     }
      98              : 
      99          162 :     if (was_running) {
     100            0 :         if (!start()) {
     101            0 :             last_error_ = "Failed with buffer size " + std::to_string(size) + ". Reverting.";
     102            0 :             std::cerr << "[Amplitron] " << last_error_ << std::endl;
     103            0 :             buffer_size_ = prev_size;
     104            0 :             start();
     105            0 :         } else {
     106            0 :             last_error_.clear();
     107              :         }
     108            0 :     }
     109          162 : }
     110              : 
     111           41 : void AudioEngine::set_sample_rate(int rate) {
     112           41 :     int prev_rate = sample_rate_;
     113           41 :     bool was_running = running_;
     114           41 :     if (was_running) stop();
     115           41 :     sample_rate_ = rate;
     116              : 
     117           14 :     {
     118           41 :         std::lock_guard<std::mutex> lock(effect_mutex_);
     119              :         // FIX: Iterate over the nodes in the new AudioGraph
     120           65 :         for (const auto& node : main_graph_.get_nodes()) {
     121           24 :             if (node.pedal) {  // Check if it's a standard effect and not a bare merge node
     122           15 :                 node.pedal->set_sample_rate(rate);
     123           15 :                 node.pedal->reset();
     124            5 :             }
     125              :         }
     126           41 :         if (tuner_tap_) {
     127            0 :             tuner_tap_->set_sample_rate(rate);
     128            0 :             tuner_tap_->reset();
     129            0 :         }
     130           41 :         metronome_->set_sample_rate(rate);
     131           41 :         metronome_->reset();
     132           27 :     }
     133              : 
     134           41 :     if (was_running) {
     135            0 :         if (!start()) {
     136            0 :             last_error_ = "Failed with sample rate " + std::to_string(rate) + " Hz. Reverting.";
     137            0 :             std::cerr << "[Amplitron] " << last_error_ << std::endl;
     138            0 :             sample_rate_ = prev_rate;
     139              : 
     140            0 :             std::lock_guard<std::mutex> lock(effect_mutex_);
     141              :             // FIX: Revert the sample rates using the graph nodes
     142            0 :             for (const auto& node : main_graph_.get_nodes()) {
     143            0 :                 if (node.pedal) {
     144            0 :                     node.pedal->set_sample_rate(prev_rate);
     145            0 :                     node.pedal->reset();
     146            0 :                 }
     147              :             }
     148            0 :             if (tuner_tap_) {
     149            0 :                 tuner_tap_->set_sample_rate(prev_rate);
     150            0 :                 tuner_tap_->reset();
     151            0 :             }
     152            0 :             metronome_->set_sample_rate(prev_rate);
     153            0 :             metronome_->reset();
     154            0 :             start();
     155            0 :         } else {
     156            0 :             last_error_.clear();
     157              :         }
     158            0 :     }
     159           41 : }
     160         1557 : void AudioEngine::commit_graph_changes() {
     161         1557 :     std::lock_guard<std::mutex> lock(effect_mutex_);
     162              : 
     163              :     // 1. Create a brand new executor (so we don't mutate memory the audio thread is currently
     164              :     // reading)
     165         1557 :     auto new_executor = std::make_shared<AudioGraphExecutor>();
     166         1557 :     int safe_block_size = std::max(buffer_size_, 8192);
     167         1557 :     new_executor->prepare(sample_rate_, safe_block_size, 32);
     168              : 
     169              :     // 2. Compile the latest UI graph into the new executor
     170         1557 :     new_executor->compile(main_graph_);
     171              : 
     172              :     // 3. Promote it to the main slot. The audio thread will grab it on the next try_lock!
     173         1557 :     main_executor_ = new_executor;
     174         1557 :     topology_dirty_.store(true, std::memory_order_release);
     175         1557 : }
     176              : 
     177              : #ifdef AMPLITRON_TESTS
     178              : class NonOwningBackendWrapper : public IAudioBackend {
     179              :    public:
     180            4 :     explicit NonOwningBackendWrapper(IAudioBackend* delegate) : delegate_(delegate) {}
     181              : 
     182            3 :     bool initialize(IAudioEngine* engine) override { return delegate_->initialize(engine); }
     183            3 :     void shutdown() override { delegate_->shutdown(); }
     184            6 :     bool start() override { return delegate_->start(); }
     185            9 :     void stop() override { delegate_->stop(); }
     186              : 
     187            3 :     std::vector<AudioDeviceInfo> get_input_devices() const override {
     188            3 :         return delegate_->get_input_devices();
     189              :     }
     190            0 :     std::vector<AudioDeviceInfo> get_output_devices() const override {
     191            0 :         return delegate_->get_output_devices();
     192              :     }
     193              : 
     194            3 :     bool set_input_device(int index) override { return delegate_->set_input_device(index); }
     195            0 :     bool set_output_device(int index) override { return delegate_->set_output_device(index); }
     196              : 
     197            0 :     std::string get_input_device_name() const override {
     198            0 :         return delegate_->get_input_device_name();
     199              :     }
     200            0 :     std::string get_output_device_name() const override {
     201            0 :         return delegate_->get_output_device_name();
     202              :     }
     203              : 
     204            6 :     int get_sample_rate() const override { return delegate_->get_sample_rate(); }
     205            6 :     int get_buffer_size() const override { return delegate_->get_buffer_size(); }
     206              : 
     207            0 :     int get_input_device() const override { return delegate_->get_input_device(); }
     208            0 :     int get_output_device() const override { return delegate_->get_output_device(); }
     209              : 
     210              :    private:
     211              :     IAudioBackend* delegate_;
     212              : };
     213              : 
     214            5 : void AudioEngine::replace_backend_state_for_test(std::unique_ptr<IAudioBackend> backend) {
     215            5 :     backend_ = std::move(backend);
     216            5 : }
     217              : 
     218            3 : void AudioEngine::replace_backend_for_test(IAudioBackend* backend) {
     219            3 :     backend_ = std::make_unique<NonOwningBackendWrapper>(backend);
     220            3 : }
     221              : 
     222            3 : void AudioEngine::clear_backend_for_test() { backend_ = nullptr; }
     223              : #endif
     224              : 
     225          765 : bool AudioEngine::initialize() {
     226          765 :     if (backend_) {
     227          765 :         initialized_ = backend_->initialize(this);
     228          765 :         return initialized_;
     229              :     }
     230            0 :     return false;
     231          258 : }
     232              : 
     233         1552 : void AudioEngine::shutdown() {
     234         1552 :     stop();
     235         1552 :     if (backend_) {
     236         1549 :         backend_->shutdown();
     237          522 :     }
     238         1552 :     initialized_ = false;
     239         1552 : }
     240              : 
     241           58 : bool AudioEngine::start() {
     242           58 :     if (!initialized_ || running_) return false;
     243           51 :     if (backend_) {
     244           51 :         running_ = backend_->start();
     245           51 :         if (running_) {
     246           33 :             sample_rate_ = backend_->get_sample_rate();
     247           33 :             buffer_size_ = backend_->get_buffer_size();
     248              : 
     249              :             // Pre-allocate the audio thread buffers to avoid allocations in the realtime callback
     250           33 :             if (static_cast<size_t>(buffer_size_) > process_buffer_.size()) {
     251            0 :                 process_buffer_.resize(buffer_size_, 0.0f);
     252            0 :                 process_buffer_right_.resize(buffer_size_, 0.0f);
     253            0 :             }
     254              : 
     255           33 :             metronome_->set_sample_rate(sample_rate_);
     256           33 :             metronome_->reset();
     257           11 :             {
     258           33 :                 std::lock_guard<std::mutex> lock(effect_mutex_);
     259           33 :                 for (const auto& node : main_graph_.get_nodes()) {
     260            0 :                     if (node.pedal) {
     261            0 :                         node.pedal->set_sample_rate(sample_rate_);
     262            0 :                         node.pedal->reset();
     263            0 :                     }
     264              :                 }
     265           33 :                 if (tuner_tap_) {
     266            0 :                     tuner_tap_->set_sample_rate(sample_rate_);
     267            0 :                     tuner_tap_->reset();
     268            0 :                 }
     269           33 :             }
     270              : 
     271              :             // Recompile graph executor with actual sample_rate and buffer_size from audio hardware
     272           33 :             commit_graph_changes();
     273              : 
     274           33 :             last_error_.clear();
     275           18 :         } else {
     276           18 :             last_error_ = "Failed to start audio backend.";
     277              :         }
     278           51 :         return running_;
     279              :     }
     280            0 :     return false;
     281           27 : }
     282              : 
     283         1589 : void AudioEngine::stop() {
     284         1589 :     if (backend_) {
     285         1586 :         backend_->stop();
     286          539 :     }
     287         1589 :     running_ = false;
     288         1589 : }
     289              : 
     290            6 : bool AudioEngine::restart() {
     291            6 :     stop();
     292            6 :     bool ok = start();
     293            6 :     if (!ok) {
     294            3 :         last_error_ = "Failed to restart audio stream. Check device settings.";
     295            5 :         std::cerr << "[Amplitron] " << last_error_ << std::endl;
     296            0 :     } else {
     297            3 :         last_error_.clear();
     298              :     }
     299            6 :     return ok;
     300              : }
     301              : 
     302           20 : std::string AudioEngine::get_input_device_name() const {
     303           20 :     return backend_ ? backend_->get_input_device_name() : "None";
     304              : }
     305              : 
     306           20 : std::string AudioEngine::get_output_device_name() const {
     307           20 :     return backend_ ? backend_->get_output_device_name() : "None";
     308              : }
     309              : 
     310           18 : std::vector<AudioDeviceInfo> AudioEngine::get_input_devices() const {
     311           18 :     return backend_ ? backend_->get_input_devices() : std::vector<AudioDeviceInfo>();
     312              : }
     313              : 
     314           15 : std::vector<AudioDeviceInfo> AudioEngine::get_output_devices() const {
     315           15 :     return backend_ ? backend_->get_output_devices() : std::vector<AudioDeviceInfo>();
     316              : }
     317              : 
     318           21 : bool AudioEngine::set_input_device(int device_index) {
     319           21 :     if (!backend_) return false;
     320              : 
     321           21 :     int prev_device = input_device_;
     322           21 :     bool was_running = running_;
     323           21 :     if (was_running) stop();
     324              : 
     325           21 :     bool ok = backend_->set_input_device(device_index);
     326           21 :     if (!ok) {
     327            7 :         if (was_running) {
     328            0 :             start();
     329            0 :         }
     330            7 :         return false;
     331              :     }
     332              : 
     333           14 :     input_device_ = device_index;
     334           14 :     if (was_running) {
     335           10 :         if (!start()) {
     336            3 :             last_error_ = "Failed to start with new input device. Reverting.";
     337            4 :             std::cerr << "[Amplitron] " << last_error_ << std::endl;
     338              : 
     339            3 :             backend_->set_input_device(prev_device);
     340            3 :             input_device_ = prev_device;
     341              : 
     342            3 :             if (!start()) {
     343            1 :                 last_error_ = "Failed to revert to previous input device. Engine stopped.";
     344            1 :                 std::cerr << "[Amplitron] " << last_error_ << std::endl;
     345            1 :             }
     346            3 :             return false;
     347              :         }
     348            7 :         last_error_.clear();
     349            3 :     }
     350            7 :     return true;
     351           10 : }
     352              : 
     353           18 : bool AudioEngine::set_output_device(int device_index) {
     354           18 :     if (!backend_) return false;
     355              : 
     356           18 :     int prev_device = output_device_;
     357           18 :     bool was_running = running_;
     358           18 :     if (was_running) stop();
     359              : 
     360           18 :     bool ok = backend_->set_output_device(device_index);
     361           18 :     if (!ok) {
     362            6 :         if (was_running) {
     363            0 :             start();
     364            0 :         }
     365            6 :         return false;
     366              :     }
     367              : 
     368           12 :     output_device_ = device_index;
     369           12 :     if (was_running) {
     370            7 :         if (!start()) {
     371            3 :             last_error_ = "Failed to start with new output device. Reverting.";
     372            4 :             std::cerr << "[Amplitron] " << last_error_ << std::endl;
     373              : 
     374            3 :             backend_->set_output_device(prev_device);
     375            3 :             output_device_ = prev_device;
     376              : 
     377            3 :             if (!start()) {
     378            1 :                 last_error_ = "Failed to revert to previous output device. Engine stopped.";
     379            1 :                 std::cerr << "[Amplitron] " << last_error_ << std::endl;
     380            1 :             }
     381            3 :             return false;
     382              :         }
     383            4 :         last_error_.clear();
     384            2 :     }
     385            6 :     return true;
     386            9 : }
     387              : 
     388              : #ifdef AMPLITRON_ANDROID_OBOE
     389              : const char* AudioEngine::get_oboe_sharing_mode_label() const {
     390              :     // Try to dynamic cast if we know OboeBackend class
     391              :     // Oboe backend has get_oboe_sharing_mode_label method.
     392              :     // We can cast using static or dynamic cast.
     393              :     auto* oboe_be = dynamic_cast<OboeBackend*>(backend_.get());
     394              :     if (oboe_be) {
     395              :         return oboe_be->get_oboe_sharing_mode_label();
     396              :     }
     397              :     return "Oboe";
     398              : }
     399              : #endif
     400              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1