LCOV - code coverage report
Current view: top level - src/audio/engine - audio_engine.h (source / functions) Coverage Total Hit
Test: merged.info Lines: 100.0 % 37 37
Test Date: 2026-06-03 09:13:19 Functions: 100.0 % 30 30

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include "common.h"
       4              : #include "audio/effects/effect.h"
       5              : #include "audio/recorder/recorder.h"
       6              : #include "audio/utils/spsc_queue.h"
       7              : #include "audio/dsp/level_analyzer.h"
       8              : #include "audio/dsp/spectrum_analyzer.h"
       9              : #include <chrono>
      10              : 
      11              : #include "audio/engine/audio_graph.h"
      12              : #include "audio/engine/audio_graph_executor.h"
      13              : #include <memory>
      14              : 
      15              : #include <nlohmann/json.hpp>
      16              : // FORWARD DECLARATIONS
      17              : namespace Amplitron {
      18              : 
      19            4 : struct AudioDeviceInfo {
      20              :     int index;
      21              :     std::string name;
      22              :     int max_input_channels;
      23              :     int max_output_channels;
      24              :     double default_sample_rate;
      25              :     bool is_usb_device;
      26              : };
      27              : 
      28              : struct AudioBackendState;
      29              : 
      30              : /**
      31              :  * @brief Core audio processing engine.
      32              :  *
      33              :  * Manages the audio stream (via a platform backend), the effect chain,
      34              :  * master gain controls, CPU load monitoring, and a lock-free SPSC command
      35              :  * queue for thread-safe GUI-to-audio parameter updates.
      36              :  *
      37              :  * All platform-specific code (PortAudio / SDL) lives in separate
      38              :  * compilation units; the engine itself is platform-agnostic.
      39              :  */
      40              : class AudioEngine {
      41              : public:
      42              :     friend class PortAudioTestSaboteur;
      43              :     
      44              :     /** @brief Construct the engine with default settings. */
      45              :     AudioEngine();
      46              : 
      47              :     /** @brief Destructor — shuts down the audio stream if still running. */
      48              :     ~AudioEngine();
      49              : 
      50              :     void commit_graph_changes();
      51              : 
      52              :     nlohmann::json serialize();
      53              :     void deserialize(const nlohmann::json& j);
      54              : 
      55              :     /** @brief Initialize the audio back-end. @return true on success. */
      56              :     bool initialize();
      57              : 
      58              :     /** @brief Release audio back-end resources. */
      59              :     void shutdown();
      60              : 
      61              :     /** @brief Open and start the audio stream. @return true on success. */
      62              :     bool start();
      63              : 
      64              :     /** @brief Stop the audio stream. */
      65              :     void stop();
      66              : 
      67              :     /** @brief Stop and restart the stream (manual recovery). @return true on success. */
      68              :     bool restart();
      69              : 
      70              :     /** @brief Return the last error message, or empty string. */
      71           15 :     std::string get_last_error() const { return last_error_; }
      72              : 
      73              :     /** @brief Clear the stored error message. */
      74            3 :     void clear_error() { last_error_.clear(); }
      75              : 
      76              : #ifdef AMPLITRON_ANDROID_OBOE
      77              :     /**
      78              :      * @brief Return a human-readable label for the Oboe sharing mode negotiated at runtime.
      79              :      * "AAudio exclusive mode" when AAudio exclusive path is active; "OpenSL ES (shared)" otherwise.
      80              :      * Used by the Android settings UI to display the actual backend, not a hardcoded string.
      81              :      */
      82              :     const char* get_oboe_sharing_mode_label() const;
      83              : #endif
      84              : 
      85              :     /** @brief Enumerate available audio input devices. */
      86              :     std::vector<AudioDeviceInfo> get_input_devices() const;
      87              : 
      88              :     /** @brief Enumerate available audio output devices. */
      89              :     std::vector<AudioDeviceInfo> get_output_devices() const;
      90              : 
      91              :     /**
      92              :      * @brief Select the input device by index.
      93              :      * @return true if the device was set successfully.
      94              :      */
      95              :     bool set_input_device(int device_index);
      96              : 
      97              :     /**
      98              :      * @brief Select the output device by index.
      99              :      * @return true if the device was set successfully.
     100              :      */
     101              :     bool set_output_device(int device_index);
     102              : 
     103              :     /** @brief Return the current input device index. */
     104           12 :     int get_input_device() const { return input_device_; }
     105              : 
     106              :     /** @brief Return the current output device index. */
     107           12 :     int get_output_device() const { return output_device_; }
     108              : 
     109              :     /** @brief Return the human-readable input device name. */
     110              :     std::string get_input_device_name() const;
     111              : 
     112              :     /** @brief Return the human-readable output device name. */
     113              :     std::string get_output_device_name() const;
     114              : 
     115              :     /** @brief Direct access to the effect chain vector (GUI thread only). */
     116          735 :     AudioGraph& graph() { return main_graph_; }
     117              :     const AudioGraph& graph() const { return main_graph_; }
     118              : 
     119              : #ifdef AMPLITRON_TESTS
     120              :     /** @brief Replace the platform backend in tests. */
     121              :     void replace_backend_for_test(AudioBackendState* backend);
     122              : #endif
     123              : 
     124              :     // =========================================================================
     125              :     // Compatibility bridge: flat effects_ vector for Undo/Redo/Snapshot systems
     126              :     // while the DAG-based AudioGraph is being migrated.
     127              :     // =========================================================================
     128              :     std::vector<std::shared_ptr<Effect>> dummy_effects_;
     129         1785 :     std::vector<std::shared_ptr<Effect>>& effects() { return dummy_effects_; }
     130              : 
     131              :     void add_effect(std::shared_ptr<Effect> fx);
     132           42 :     void add_initial_effects(const std::vector<std::shared_ptr<Effect>>& fxs) {
     133           42 :         dummy_effects_.clear();
     134          306 :         for (const auto& fx : fxs)
     135          264 :             dummy_effects_.push_back(fx);
     136           42 :         sync_graph_with_dummy_effects(true);
     137           42 :     }
     138              :     void insert_effect(int index, std::shared_ptr<Effect> fx);
     139              :     void remove_effect(int index);
     140              :     void clear_effects();
     141              :     void move_effect(int from, int to);
     142              :     void restore_effects_state(std::vector<std::shared_ptr<Effect>> state);
     143              : 
     144              : 
     145              :     /**
     146              :      * @brief Set the audio buffer size (takes effect on next stream restart).
     147              :      * @param size Buffer size in samples.
     148              :      */
     149              :     void set_buffer_size(int size);
     150              : 
     151              :     /**
     152              :      * @brief Set the audio sample rate (takes effect on next stream restart).
     153              :      * @param rate Sample rate in Hz.
     154              :      */
     155              :     void set_sample_rate(int rate);
     156              : 
     157              :     /** @brief Return the current buffer size in samples. */
     158           24 :     int get_buffer_size() const { return buffer_size_; }
     159              : 
     160              :     /** @brief Return the current sample rate in Hz. */
     161           50 :     int get_sample_rate() const { return sample_rate_; }
     162              : 
     163              :     /** @brief Return true if the audio stream is actively running. */
     164          127 :     bool is_running() const { return running_; }
     165              : 
     166              :     /** @brief Return the most recent input peak level (0.0–1.0, atomic). */
     167            3 :     float get_input_level() const { return input_level_.load(); }
     168              : 
     169              :     /** @brief Return the most recent output peak level (0.0–1.0, atomic). */
     170          153 :     float get_output_level() const { return output_level_.load(); }
     171              : 
     172              :     /** @brief Return the most recent input RMS level (0.0–1.0, atomic). */
     173            9 :     float get_input_rms() const { return input_rms_.load(std::memory_order_relaxed); }
     174              : 
     175              :     /** @brief Return the most recent output RMS level (0.0–1.0, atomic). */
     176            9 :     float get_output_rms() const { return output_rms_.load(std::memory_order_relaxed); }
     177              : 
     178              :     /** @brief Consume one-shot input clipping flag set by audio thread. */
     179            6 :     bool consume_input_clipped() { return input_clipped_.exchange(false, std::memory_order_acq_rel); }
     180              : 
     181              :     /** @brief Consume one-shot output clipping flag set by audio thread. */
     182            6 :     bool consume_output_clipped() { return output_clipped_.exchange(false, std::memory_order_acq_rel); }
     183              : 
     184              :     /** @brief FFT size used for GUI analyzer snapshots. */
     185              :     static constexpr int ANALYZER_FFT_SIZE = 2048;
     186              :     static constexpr int ANALYZER_FFT_MASK = ANALYZER_FFT_SIZE - 1;
     187              : 
     188              :     /** @brief Enable/disable analyzer capture in the audio callback (GUI thread). */
     189           12 :     void set_analyzer_enabled(bool enabled) { analyzer_enabled_.store(enabled, std::memory_order_release); }
     190              : 
     191              :     /** @brief Return true if analyzer capture is active. */
     192           15 :     bool is_analyzer_enabled() const { return analyzer_enabled_.load(std::memory_order_acquire); }
     193              : 
     194              :     /** @brief Snapshot sequence counter; increments when new analyzer data is published. */
     195            3 :     uint64_t get_analyzer_sequence() const {
     196            4 :         return analyzer_sequence_.load(std::memory_order_acquire);
     197              :     }
     198              : 
     199              :     /**
     200              :      * @brief Copy latest pre/post-chain analyzer snapshots (GUI thread).
     201              :      * @param input_dest  Destination buffer for pre-chain samples.
     202              :      * @param output_dest Destination buffer for post-chain samples.
     203              :      * @param sample_count Number of samples to copy (clamped to ANALYZER_FFT_SIZE).
     204              :      * @return true if at least one snapshot has been published.
     205              :      */
     206              :     bool copy_analyzer_snapshot(float* input_dest, float* output_dest, int sample_count) const;
     207              : 
     208              :     /** @brief Drive smoothed VU level metrics (GUI thread only). */
     209              :     void update_level_analyzer(float dt);
     210            3 :     const LevelAnalyzer& level_analyzer() const { return level_analyzer_; }
     211              : 
     212              :     /** @brief Drive FFT spectrum analysis (GUI thread only). */
     213              :     void update_spectrum_analyzer(float dt);
     214            5 :     const SpectrumAnalyzer& spectrum_analyzer() const { return spectrum_analyzer_; }
     215              : 
     216              :     /**
     217              :      * @brief Set the master input gain (enqueued to audio thread via SPSC queue).
     218              :      * @param gain Linear gain multiplier.
     219              :      */
     220              :     void set_input_gain(float gain);
     221              : 
     222              :     /**
     223              :      * @brief Set the master output gain (enqueued to audio thread via SPSC queue).
     224              :      * @param gain Linear gain multiplier.
     225              :      */
     226              :     void set_output_gain(float gain);
     227              : 
     228              :     
     229              :     /** @brief Return the current input gain (atomic relaxed read). */
     230          538 :     float get_input_gain() const { return input_gain_.load(std::memory_order_relaxed); }
     231              : 
     232              :     /** @brief Return the current output gain (atomic relaxed read). */
     233          488 :     float get_output_gain() const { return output_gain_.load(std::memory_order_relaxed); }
     234              : 
     235              :     /** @brief Toggle the metronome on/off (atomic update). */
     236              :     void toggle_metronome();
     237              : 
     238              :     /** @brief Set the metronome BPM (atomic update). */
     239              :     void set_metronome_bpm(int bpm);
     240              : 
     241              :     /** @brief Set the metronome click volume (atomic update). */
     242              :     void set_metronome_volume(float volume);
     243              : 
     244              :     /** @brief Return the current metronome enabled state (atomic relaxed read). */
     245           18 :     bool get_metronome_enabled() const { return metronome_enabled_state_.load(std::memory_order_relaxed); }
     246              : 
     247              :     /** @brief Return the current metronome BPM (atomic relaxed read). */
     248           28 :     int get_metronome_bpm() const { return metronome_bpm_state_.load(std::memory_order_relaxed); }
     249              : 
     250              :     /** @brief Return the current metronome volume (atomic relaxed read). */
     251           15 :     float get_metronome_volume() const { return metronome_volume_state_.load(std::memory_order_relaxed); }
     252              : 
     253              :     /**
     254              :      * @brief Enqueue a parameter value change from the GUI thread (lock-free).
     255              :      * @param effect_index Index of the effect in the chain.
     256              :      * @param param_index  Index of the parameter within the effect.
     257              :      * @param value        New parameter value.
     258              :      */
     259              :     void push_param_change(int effect_index, int param_index, float value);
     260              : 
     261              :     /**
     262              :      * @brief Enqueue a mixer input gain change from the GUI thread.
     263              :      * @param node_id      ID of the Mixer node.
     264              :      * @param pin_index    Index of the input pin on the mixer.
     265              :      * @param gain         New gain multiplier (0.0–2.0).
     266              :      */
     267              :     void push_mixer_gain_change(int node_id, int pin_index, float gain);
     268              : 
     269              :     /**
     270              :      * @brief Enqueue an effect enabled/disabled change from the GUI thread.
     271              :      * @param effect_index Index of the effect in the chain.
     272              :      * @param enabled      >0.5 means enabled.
     273              :      */
     274              :     void push_effect_enabled(int effect_index, float enabled);
     275              : 
     276              :     /**
     277              :      * @brief Enqueue a dry/wet mix change from the GUI thread.
     278              :      * @param effect_index Index of the effect in the chain.
     279              :      * @param mix          New mix value (0.0–1.0).
     280              :      */
     281              :     void push_effect_mix(int effect_index, float mix);
     282              : 
     283              :     /** @brief Return the current CPU load fraction (0.0–1.0, atomic). */
     284            6 :     float get_cpu_load() const { return cpu_load_.load(std::memory_order_relaxed); }
     285              : 
     286              :     /** @brief Suggest a new buffer size based on current CPU load. */
     287              :     int get_suggested_buffer_size() const;
     288              : 
     289              :     /** @brief Return true if automatic buffer-size tuning is enabled. */
     290            6 :     bool is_auto_buffer_enabled() const { return auto_buffer_enabled_; }
     291              : 
     292              :     /** @brief Enable or disable automatic buffer-size tuning. */
     293            3 :     void set_auto_buffer_enabled(bool enabled) { auto_buffer_enabled_ = enabled; }
     294              : 
     295              :     /** @brief Access the built-in audio recorder. */
     296           25 :     Recorder& recorder() { return recorder_; }
     297              : 
     298              :     /**
     299              :      * @brief Set a tuner tap that receives pre-chain audio each callback.
     300              :      *
     301              :      * The tap is processed before the effect chain. If its mute param is
     302              :      * active it will zero the buffer, silencing the downstream chain.
     303              :      * Protected by effect_mutex_.
     304              :      */
     305              :     void set_tuner_tap(std::shared_ptr<Effect> tap);
     306              : 
     307              :     /** @brief Remove the tuner tap. */
     308              :     void clear_tuner_tap();
     309              : 
     310              :     /** @brief Return true if a tuner tap is currently installed. */
     311              :     bool has_tuner_tap() const;
     312              : 
     313              :     /**
     314              :      * @brief Run the DSP pipeline on a block of audio samples.
     315              :      *
     316              :      * Called by the platform backend's audio callback. Public so that
     317              :      * backend compilation units (which are not class members) can invoke it.
     318              :      */
     319              :     void process_audio(const float* input, float* output, int frame_count);
     320              : 
     321              :     // MIDI instance is managed by the GUI thread's MidiManager.
     322              : 
     323              : private:
     324              :     // Platform backend state (defined in the backend .cpp that is compiled)
     325              :     AudioBackendState* backend_ = nullptr;
     326              : 
     327              :     bool initialized_ = false;
     328              :     bool running_ = false;
     329              : 
     330              :     int input_device_ = -1;
     331              :     int output_device_ = -1;
     332              :     int sample_rate_ = DEFAULT_SAMPLE_RATE;
     333              :     int buffer_size_ = DEFAULT_BUFFER_SIZE;
     334              :     //global transport
     335              :     std::atomic<float> input_gain_{1.0f};
     336              :     std::atomic<float> output_gain_{0.8f};
     337              :     std::atomic<bool> metronome_enabled_state_{false};
     338              :     std::atomic<int> metronome_bpm_state_{120};
     339              :     std::atomic<float> metronome_volume_state_{0.5f};
     340              : 
     341              :     std::atomic<float> input_level_{0.0f};
     342              :     std::atomic<float> output_level_{0.0f};
     343              :     std::atomic<float> input_rms_{0.0f};
     344              :     std::atomic<float> output_rms_{0.0f};
     345              :     std::atomic<bool> input_clipped_{false};
     346              :     std::atomic<bool> output_clipped_{false};
     347              :     std::atomic<bool> analyzer_enabled_{true};
     348              : 
     349              :     // std::vector<std::shared_ptr<Effect>> effects_;
     350              :     std::vector<float>     process_buffer_;
     351              :     std::vector<float> process_buffer_right_;
     352              :     std::mutex effect_mutex_;
     353              :     Recorder recorder_;
     354              :     std::shared_ptr<Effect> tuner_tap_;
     355              :     std::string last_error_;
     356              : 
     357              :     std::shared_ptr<Effect>      audio_shadow_tuner_;
     358              :     std::atomic<bool>            topology_dirty_{true};
     359              : 
     360              :     // The main graph data model (Edited by the GUI/Main thread)
     361              :     AudioGraph main_graph_;
     362              :     
     363              :     // The compiled executor (Built by the GUI thread)
     364              :     std::shared_ptr<AudioGraphExecutor> main_executor_;
     365              :     
     366              :     // The shadow executor (Safely copied by the Audio thread)
     367              :     std::shared_ptr<AudioGraphExecutor> audio_shadow_executor_;
     368              : 
     369              :     void sync_graph_with_dummy_effects(bool reset_graph = false);
     370              : 
     371              :     // Lock-free GUI -> Audio command queue (256 slots)
     372              :     SPSCQueue<AudioCommand, 256> command_queue_;
     373              :     void drain_commands();        // Must be called while holding effect_mutex_
     374              :     void drain_gain_commands();   // Safe to call without effect_mutex_
     375              :     void update_metronome_timing();
     376              : 
     377              :     // CPU load watchdog for buffer auto-tuning
     378              :     std::atomic<float> cpu_load_{0.0f};
     379              :     std::atomic<float> callback_duration_us_{0.0f};
     380              :     bool auto_buffer_enabled_ = false;
     381              : 
     382              :     // Audio-thread capture for GUI analyzer snapshots.
     383              :     static constexpr int ANALYZER_HOP_SIZE = 1024;
     384              :     std::array<float, ANALYZER_FFT_SIZE> analyzer_capture_input_{};
     385              :     std::array<float, ANALYZER_FFT_SIZE> analyzer_capture_output_{};
     386              :     int analyzer_capture_index_ = 0;
     387              :     int analyzer_samples_since_publish_ = 0;
     388              : 
     389              :     // Shared snapshot buffers (audio thread writes with try_lock, GUI reads with lock).
     390              :     mutable std::mutex analyzer_mutex_;
     391              :     std::array<float, ANALYZER_FFT_SIZE> analyzer_snapshot_input_{};
     392              :     std::array<float, ANALYZER_FFT_SIZE> analyzer_snapshot_output_{};
     393              :     std::atomic<uint64_t> analyzer_sequence_{0};
     394              : 
     395              :     // DSP analyzer instances (GUI thread only — driven by update_*_analyzer())
     396              :     LevelAnalyzer   level_analyzer_;
     397              :     SpectrumAnalyzer spectrum_analyzer_;
     398              :     uint64_t         analyzer_last_sequence_ = 0;
     399              :     std::array<float, ANALYZER_FFT_SIZE> analyzer_input_buf_{};
     400              :     std::array<float, ANALYZER_FFT_SIZE> analyzer_output_buf_{};
     401              : 
     402              :     // Metronome state (audio thread only)
     403              :     bool metronome_enabled_ = false;
     404              :     int metronome_bpm_ = 120;
     405              :     float metronome_volume_ = 0.5f;
     406              : 
     407              :     float metronome_volume_smoothed_ = 0.0f;
     408              :     float metronome_volume_smooth_alpha_ = 0.05f;
     409              :     float metronome_bpm_smoothed_ = 120.0f;
     410              :     float metronome_bpm_smooth_alpha_ = 0.05f;
     411              : 
     412              :     int metronome_sample_rate_ = 0;
     413              :     double metronome_samples_per_beat_ = 0.0;
     414              :     double metronome_sample_counter_ = 0.0;
     415              :     int metronome_click_samples_total_ = 0;
     416              :     int metronome_click_samples_remaining_ = 0;
     417              :     float metronome_click_phase_ = 0.0f;
     418              :     float metronome_click_phase_inc_ = 0.0f;
     419              :     float metronome_click_env_ = 0.0f;
     420              :     float metronome_click_decay_ = 0.0f;
     421              :     // (MIDI instance removed - use MidiManager)
     422              : };
     423              : 
     424              : } // namespace Amplitron
        

Generated by: LCOV version 2.0-1