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
|