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
|