LCOV - code coverage report
Current view: top level - src/audio/engine - audio_engine.h (source / functions) Coverage Total Hit
Test: merged.info Lines: 97.0 % 33 32
Test Date: 2026-06-07 15:51:50 Functions: 96.3 % 27 26

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <chrono>
       4              : #include <memory>
       5              : #include <nlohmann/json.hpp>
       6              : 
       7              : #include "audio/backend/audio_backend.h"
       8              : #include "audio/dsp/level_analyzer.h"
       9              : #include "audio/dsp/spectrum_analyzer.h"
      10              : #include "audio/effects/core/effect.h"
      11              : #include "audio/engine/audio_command_dispatcher.h"
      12              : #include "audio/engine/audio_graph.h"
      13              : #include "audio/engine/audio_graph_executor.h"
      14              : #include "audio/engine/i_audio_engine.h"
      15              : #include "audio/engine/i_metronome.h"
      16              : #include "audio/engine/metronome.h"
      17              : #include "audio/recorder/i_recorder.h"
      18              : #include "audio/recorder/recorder.h"
      19              : #include "common.h"
      20              : 
      21              : namespace Amplitron {
      22              : 
      23              : class AnalyzerCapture;
      24              : 
      25              : /**
      26              :  * @brief Core audio processing engine.
      27              :  *
      28              :  * Manages the audio stream (via a platform backend), the effect chain,
      29              :  * master gain controls, CPU load monitoring, and a lock-free SPSC command
      30              :  * queue for thread-safe GUI-to-audio parameter updates.
      31              :  *
      32              :  * All platform-specific code (PortAudio / SDL) lives in separate
      33              :  * compilation units; the engine itself is platform-agnostic.
      34              :  *
      35              :  * @note Backend Precedence and Ownership:
      36              :  * - Precedence: poly_backend_ (if non-null) takes operational precedence over the native backend_.
      37              :  * - Ownership: AudioEngine owns the opaque backend_ pointer (manages its creation/destruction).
      38              :  *   However, AudioEngine does NOT take ownership of the polymorphic poly_backend_ pointer;
      39              :  *   the caller or test fixture is responsible for managing its lifetime.
      40              :  */
      41              : class AudioEngine : public IAudioEngine {
      42              :    public:
      43              :     friend class PortAudioTestSaboteur;
      44              : 
      45              :     /** @brief Construct the engine with default settings. */
      46              :     AudioEngine(std::unique_ptr<IRecorder> recorder = nullptr,
      47              :                 std::unique_ptr<IMetronome> metronome = nullptr);
      48              : 
      49              :     /** @brief Destructor — shuts down the audio stream if still running. */
      50              :     ~AudioEngine() override;
      51              : 
      52              :     void commit_graph_changes() override;
      53              : 
      54              :     nlohmann::json serialize() override;
      55              :     void deserialize(const nlohmann::json& j) override;
      56              : 
      57              :     /** @brief Initialize the audio back-end. @return true on success. */
      58              :     bool initialize() override;
      59              : 
      60              :     /** @brief Release audio back-end resources. */
      61              :     void shutdown() override;
      62              : 
      63              :     /** @brief Open and start the audio stream. @return true on success. */
      64              :     bool start() override;
      65              : 
      66              :     /** @brief Stop the audio stream. */
      67              :     void stop() override;
      68              : 
      69              :     /** @brief Stop and restart the stream (manual recovery). @return true on success. */
      70              :     bool restart() override;
      71              : 
      72              :     /** @brief Return the last error message, or empty string. */
      73           13 :     std::string get_last_error() const override { return last_error_; }
      74              : 
      75              :     /** @brief Clear the stored error message. */
      76            3 :     void clear_error() override { last_error_.clear(); }
      77              : 
      78              : #ifdef AMPLITRON_ANDROID_OBOE
      79              :     /**
      80              :      * @brief Return a human-readable label for the Oboe sharing mode negotiated at runtime.
      81              :      * "AAudio exclusive mode" when AAudio exclusive path is active; "OpenSL ES (shared)" otherwise.
      82              :      * Used by the Android settings UI to display the actual backend, not a hardcoded string.
      83              :      */
      84              :     const char* get_oboe_sharing_mode_label() const override;
      85              : #endif
      86              : 
      87              :     /** @brief Enumerate available audio input devices. */
      88              :     std::vector<AudioDeviceInfo> get_input_devices() const override;
      89              : 
      90              :     /** @brief Enumerate available audio output devices. */
      91              :     std::vector<AudioDeviceInfo> get_output_devices() const override;
      92              : 
      93              :     /**
      94              :      * @brief Select the input device by index.
      95              :      * @return true if the device was set successfully.
      96              :      */
      97              :     bool set_input_device(int device_index) override;
      98              : 
      99              :     /**
     100              :      * @brief Select the output device by index.
     101              :      * @return true if the device was set successfully.
     102              :      */
     103              :     bool set_output_device(int device_index) override;
     104              : 
     105              :     /** @brief Return the current input device index (delegates to backend so auto-detection is
     106              :      * reflected). */
     107           12 :     int get_input_device() const override { return backend_ ? backend_->get_input_device() : -1; }
     108              : 
     109              :     /** @brief Return the current output device index (delegates to backend so auto-detection is
     110              :      * reflected). */
     111           12 :     int get_output_device() const override { return backend_ ? backend_->get_output_device() : -1; }
     112              : 
     113              :     /** @brief Return the human-readable input device name. */
     114              :     std::string get_input_device_name() const override;
     115              : 
     116              :     /** @brief Return the human-readable output device name. */
     117              :     std::string get_output_device_name() const override;
     118              : 
     119              :     /** @brief Direct access to the effect chain vector (GUI thread only). */
     120          889 :     AudioGraph& graph() override { return main_graph_; }
     121            0 :     const AudioGraph& graph() const override { return main_graph_; }
     122              : 
     123              : #ifdef AMPLITRON_TESTS
     124              :     /** @brief Replace the platform backend in tests. */
     125              :     void replace_backend_state_for_test(std::unique_ptr<IAudioBackend> backend);
     126              :     void replace_backend_for_test(IAudioBackend* backend);
     127              :     void clear_backend_for_test();
     128              : #endif
     129              : 
     130              :     // =========================================================================
     131              :     // Compatibility bridge: flat effects_ vector for Undo/Redo/Snapshot systems
     132              :     // while the DAG-based AudioGraph is being migrated.
     133              :     // =========================================================================
     134              :     std::vector<std::shared_ptr<Effect>> dummy_effects_;
     135         1707 :     std::vector<std::shared_ptr<Effect>>& effects() override { return dummy_effects_; }
     136              : 
     137              :     void add_effect(std::shared_ptr<Effect> fx) override;
     138           42 :     void add_initial_effects(const std::vector<std::shared_ptr<Effect>>& fxs) override {
     139           42 :         dummy_effects_.clear();
     140          306 :         for (const auto& fx : fxs) dummy_effects_.push_back(fx);
     141           42 :         sync_graph_with_dummy_effects(true);
     142           42 :     }
     143              :     void insert_effect(int index, std::shared_ptr<Effect> fx) override;
     144              :     void remove_effect(int index) override;
     145              :     void clear_effects() override;
     146              :     void move_effect(int from, int to) override;
     147              :     void restore_effects_state(std::vector<std::shared_ptr<Effect>> state) override;
     148              : 
     149              :     /**
     150              :      * @brief Set the audio buffer size (takes effect on next stream restart).
     151              :      * @param size Buffer size in samples.
     152              :      */
     153              :     void set_buffer_size(int size) override;
     154              : 
     155              :     /**
     156              :      * @brief Set the audio sample rate (takes effect on next stream restart).
     157              :      * @param rate Sample rate in Hz.
     158              :      */
     159              :     void set_sample_rate(int rate) override;
     160              : 
     161              :     /** @brief Return the current buffer size in samples. */
     162           88 :     int get_buffer_size() const override { return buffer_size_; }
     163              : 
     164              :     /** @brief Return the current sample rate in Hz. */
     165           86 :     int get_sample_rate() const override { return sample_rate_; }
     166              : 
     167              :     /** @brief Return true if the audio stream is actively running. */
     168          177 :     bool is_running() const override { return running_; }
     169              : 
     170              :     /** @brief Test-only helper to bypass hardware startup constraints */
     171            6 :     void set_running_for_testing(bool running) { running_ = running; }
     172              : 
     173              :     /** @brief Return the most recent input peak level (0.0–1.0, atomic). */
     174            3 :     float get_input_level() const override { return input_level_.load(); }
     175              : 
     176              :     /** @brief Return the most recent output peak level (0.0–1.0, atomic). */
     177          153 :     float get_output_level() const override { return output_level_.load(); }
     178              : 
     179              :     /** @brief Return the most recent input RMS level (0.0–1.0, atomic). */
     180            9 :     float get_input_rms() const override { return input_rms_.load(std::memory_order_relaxed); }
     181              : 
     182              :     /** @brief Return the most recent output RMS level (0.0–1.0, atomic). */
     183            9 :     float get_output_rms() const override { return output_rms_.load(std::memory_order_relaxed); }
     184              : 
     185              :     /** @brief Consume one-shot input clipping flag set by audio thread. */
     186            6 :     bool consume_input_clipped() override {
     187            6 :         return input_clipped_.exchange(false, std::memory_order_acq_rel);
     188              :     }
     189              : 
     190              :     /** @brief Consume one-shot output clipping flag set by audio thread. */
     191            6 :     bool consume_output_clipped() override {
     192            6 :         return output_clipped_.exchange(false, std::memory_order_acq_rel);
     193              :     }
     194              : 
     195              :     /** @brief Enable/disable analyzer capture in the audio callback (GUI thread). */
     196              :     void set_analyzer_enabled(bool enabled) override;
     197              : 
     198              :     /** @brief Return true if analyzer capture is active. */
     199              :     bool is_analyzer_enabled() const override;
     200              : 
     201              :     /** @brief Snapshot sequence counter; increments when new analyzer data is published. */
     202              :     uint64_t get_analyzer_sequence() const override;
     203              : 
     204              :     /**
     205              :      * @brief Copy latest pre/post-chain analyzer snapshots (GUI thread).
     206              :      * @param input_dest  Destination buffer for pre-chain samples.
     207              :      * @param output_dest Destination buffer for post-chain samples.
     208              :      * @param sample_count Number of samples to copy (clamped to ANALYZER_FFT_SIZE).
     209              :      * @return true if at least one snapshot has been published.
     210              :      */
     211              :     bool copy_analyzer_snapshot(float* input_dest, float* output_dest,
     212              :                                 int sample_count) const override;
     213              : 
     214              :     /**
     215              :      * @brief Set the master input gain (enqueued to audio thread via SPSC queue).
     216              :      * @param gain Linear gain multiplier.
     217              :      */
     218              :     void set_input_gain(float gain) override;
     219              : 
     220              :     /**
     221              :      * @brief Set the master output gain (enqueued to audio thread via SPSC queue).
     222              :      * @param gain Linear gain multiplier.
     223              :      */
     224              :     void set_output_gain(float gain) override;
     225              : 
     226              :     /** @brief Return the current input gain (atomic relaxed read). */
     227          533 :     float get_input_gain() const override { return input_gain_.load(std::memory_order_relaxed); }
     228              : 
     229              :     /** @brief Return the current output gain (atomic relaxed read). */
     230          477 :     float get_output_gain() const override { return output_gain_.load(std::memory_order_relaxed); }
     231              : 
     232              :     /** @brief Toggle the metronome on/off (atomic update). */
     233              :     void toggle_metronome() override;
     234              : 
     235              :     /** @brief Set the metronome BPM (atomic update). */
     236              :     void set_metronome_bpm(int bpm) override;
     237              : 
     238              :     /** @brief Set the metronome click volume (atomic update). */
     239              :     void set_metronome_volume(float volume) override;
     240              : 
     241              :     /** @brief Return the current metronome enabled state (atomic relaxed read). */
     242           18 :     bool get_metronome_enabled() const override { return metronome_->is_enabled(); }
     243              : 
     244              :     /** @brief Return the current metronome BPM (atomic relaxed read). */
     245           21 :     int get_metronome_bpm() const override { return metronome_->get_bpm(); }
     246              : 
     247              :     /** @brief Return the current metronome volume (atomic relaxed read). */
     248           15 :     float get_metronome_volume() const override { return metronome_->get_volume(); }
     249              : 
     250              :     /**
     251              :      * @brief Enqueue a parameter value change from the GUI thread (lock-free).
     252              :      * @param effect_index Index of the effect in the chain.
     253              :      * @param param_index  Index of the parameter within the effect.
     254              :      * @param value        New parameter value.
     255              :      */
     256              :     void push_param_change(int effect_index, int param_index, float value) override;
     257              : 
     258              :     /**
     259              :      * @brief Enqueue a mixer input gain change from the GUI thread.
     260              :      * @param node_id      ID of the Mixer node.
     261              :      * @param pin_index    Index of the input pin on the mixer.
     262              :      * @param gain         New gain multiplier (0.0–2.0).
     263              :      */
     264              :     void push_mixer_gain_change(int node_id, int pin_index, float gain) override;
     265              : 
     266              :     /**
     267              :      * @brief Enqueue an effect enabled/disabled change from the GUI thread.
     268              :      * @param effect_index Index of the effect in the chain.
     269              :      * @param enabled      >0.5 means enabled.
     270              :      */
     271              :     void push_effect_enabled(int effect_index, float enabled) override;
     272              : 
     273              :     /**
     274              :      * @brief Enqueue a dry/wet mix change from the GUI thread.
     275              :      * @param effect_index Index of the effect in the chain.
     276              :      * @param mix          New mix value (0.0–1.0).
     277              :      */
     278              :     void push_effect_mix(int effect_index, float mix) override;
     279              : 
     280              :     /** @brief Return the current CPU load fraction (0.0–1.0, atomic). */
     281            6 :     float get_cpu_load() const override { return cpu_load_.load(std::memory_order_relaxed); }
     282              : 
     283              :     /** @brief Suggest a new buffer size based on current CPU load. */
     284              :     int get_suggested_buffer_size() const override;
     285              : 
     286              :     /** @brief Return true if automatic buffer-size tuning is enabled. */
     287            6 :     bool is_auto_buffer_enabled() const override { return auto_buffer_enabled_; }
     288              : 
     289              :     /** @brief Enable or disable automatic buffer-size tuning. */
     290            3 :     void set_auto_buffer_enabled(bool enabled) override { auto_buffer_enabled_ = enabled; }
     291              : 
     292              :     /** @brief Access the built-in audio recorder. */
     293           25 :     IRecorder& recorder() override { return *recorder_; }
     294              : 
     295              :     /**
     296              :      * @brief Set a tuner tap that receives pre-chain audio each callback.
     297              :      *
     298              :      * The tap is processed before the effect chain. If its mute param is
     299              :      * active it will zero the buffer, silencing the downstream chain.
     300              :      * Protected by effect_mutex_.
     301              :      */
     302              :     void set_tuner_tap(std::shared_ptr<Effect> tap) override;
     303              : 
     304              :     /** @brief Remove the tuner tap. */
     305              :     void clear_tuner_tap() override;
     306              : 
     307              :     /** @brief Return true if a tuner tap is currently installed. */
     308              :     bool has_tuner_tap() const override;
     309              : 
     310              :     /**
     311              :      * @brief Run the DSP pipeline on a block of audio samples.
     312              :      *
     313              :      * Called by the platform backend's audio callback. Public so that
     314              :      * backend compilation units (which are not class members) can invoke it.
     315              :      */
     316              :     void process_audio(const float* input, float* output, int frame_count) override;
     317              : 
     318              :     // MIDI instance is managed by the GUI thread's MidiManager.
     319              : 
     320              :    private:
     321              :     // Platform backend state
     322              :     std::unique_ptr<IAudioBackend> backend_;
     323              : 
     324              :     bool initialized_ = false;
     325              :     bool running_ = false;
     326              : 
     327              :     int input_device_ = -1;
     328              :     int output_device_ = -1;
     329              :     int sample_rate_ = DEFAULT_SAMPLE_RATE;
     330              :     int buffer_size_ = DEFAULT_BUFFER_SIZE;
     331              :     // global transport
     332              :     std::atomic<float> input_gain_{1.0f};
     333              :     std::atomic<float> output_gain_{0.8f};
     334              :     std::unique_ptr<IMetronome> metronome_;
     335              : 
     336              :     std::atomic<float> input_level_{0.0f};
     337              :     std::atomic<float> output_level_{0.0f};
     338              :     std::atomic<float> input_rms_{0.0f};
     339              :     std::atomic<float> output_rms_{0.0f};
     340              :     std::atomic<bool> input_clipped_{false};
     341              :     std::atomic<bool> output_clipped_{false};
     342              : 
     343              :     // std::vector<std::shared_ptr<Effect>> effects_;
     344              :     std::vector<float> process_buffer_;
     345              :     std::vector<float> process_buffer_right_;
     346              :     std::mutex effect_mutex_;
     347              :     std::unique_ptr<IRecorder> recorder_;
     348              :     std::shared_ptr<Effect> tuner_tap_;
     349              :     std::string last_error_;
     350              : 
     351              :     std::shared_ptr<Effect> audio_shadow_tuner_;
     352              :     std::atomic<bool> topology_dirty_{true};
     353              : 
     354              :     // The main graph data model (Edited by the GUI/Main thread)
     355              :     AudioGraph main_graph_;
     356              : 
     357              :     // The compiled executor (Built by the GUI thread)
     358              :     std::shared_ptr<AudioGraphExecutor> main_executor_;
     359              : 
     360              :     // The shadow executor (Safely copied by the Audio thread)
     361              :     std::shared_ptr<AudioGraphExecutor> audio_shadow_executor_;
     362              : 
     363              :     void sync_graph_with_dummy_effects(bool reset_graph = false);
     364              : 
     365              :     AudioCommandDispatcher command_dispatcher_;
     366              : 
     367              :     // CPU load watchdog for buffer auto-tuning
     368              :     std::atomic<float> cpu_load_{0.0f};
     369              :     std::atomic<float> callback_duration_us_{0.0f};
     370              :     bool auto_buffer_enabled_ = false;
     371              : 
     372              :     std::unique_ptr<AnalyzerCapture> analyzer_capture_;
     373              : 
     374              :     // (MIDI instance removed - use MidiManager)
     375              : };
     376              : 
     377              : }  // namespace Amplitron
        

Generated by: LCOV version 2.0-1