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
|