Line data Source code
1 : #include "audio/engine/audio_engine.h"
2 : #include "audio/backend/audio_backend.h"
3 : #include <iostream>
4 : #include <algorithm>
5 : #include <nlohmann/json.hpp>
6 :
7 : namespace Amplitron
8 : {
9 :
10 1154 : AudioEngine::AudioEngine()
11 292 : {
12 862 : process_buffer_.resize(MAX_BUFFER_SIZE, 0.0f);
13 862 : process_buffer_right_.resize(MAX_BUFFER_SIZE, 0.0f);
14 862 : backend_ = create_audio_backend();
15 :
16 : // Pre-allocate the graph memory pools immediately on startup
17 862 : main_executor_ = std::make_shared<AudioGraphExecutor>();
18 : // Assuming standard values, use your engine's actual sample rate / block size variables here
19 862 : main_executor_->prepare(48000, 512, 32);
20 :
21 : // Seed the shadow executor so the audio thread has something safe to read instantly
22 862 : audio_shadow_executor_ = main_executor_;
23 862 : }
24 :
25 1154 : AudioEngine::~AudioEngine()
26 292 : {
27 862 : shutdown();
28 :
29 862 : destroy_audio_backend(backend_);
30 862 : backend_ = nullptr;
31 1737 : }
32 :
33 : // --- Serialization Methods ---
34 :
35 9 : nlohmann::json AudioEngine::serialize()
36 : {
37 9 : std::lock_guard<std::mutex> lock(effect_mutex_);
38 9 : nlohmann::json j;
39 :
40 : // Read atomic variables safely
41 9 : j["input_gain"] = input_gain_.load(std::memory_order_relaxed);
42 :
43 9 : auto effects_array = nlohmann::json::array();
44 15 : for (const auto &fx : dummy_effects_)
45 : {
46 6 : if (fx)
47 : {
48 6 : nlohmann::json fx_json;
49 6 : fx_json["name"] = fx->name();
50 6 : fx_json["params"] = fx->get_params();
51 6 : effects_array.push_back(fx_json);
52 6 : }
53 : }
54 9 : j["effects"] = effects_array;
55 12 : return j;
56 9 : }
57 :
58 6 : void AudioEngine::deserialize(const nlohmann::json &j)
59 : {
60 6 : std::lock_guard<std::mutex> lock(effect_mutex_);
61 :
62 6 : if (j.contains("input_gain"))
63 : {
64 6 : set_input_gain(j["input_gain"]);
65 2 : }
66 :
67 6 : if (j.contains("effects"))
68 : {
69 16 : for (const auto &fx_data : j["effects"])
70 : {
71 6 : std::string name = fx_data["name"];
72 18 : for (auto &fx : dummy_effects_)
73 : {
74 20 : if (fx && std::string(fx->name()) == name)
75 : {
76 6 : fx->set_params(fx_data["params"]);
77 2 : }
78 : }
79 6 : }
80 2 : }
81 6 : }
82 :
83 : // --- Existing Methods ---
84 :
85 162 : void AudioEngine::set_buffer_size(int size)
86 : {
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)
91 0 : stop();
92 162 : buffer_size_ = size;
93 162 : if (was_running)
94 : {
95 0 : if (!start())
96 : {
97 0 : last_error_ = "Failed with buffer size " + std::to_string(size) + ". Reverting.";
98 0 : std::cerr << "[Amplitron] " << last_error_ << std::endl;
99 0 : buffer_size_ = prev_size;
100 0 : start();
101 0 : }
102 : else
103 : {
104 0 : last_error_.clear();
105 : }
106 0 : }
107 162 : }
108 :
109 41 : void AudioEngine::set_sample_rate(int rate)
110 : {
111 41 : int prev_rate = sample_rate_;
112 41 : bool was_running = running_;
113 41 : if (was_running)
114 0 : 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 53 : for (const auto &node : main_graph_.get_nodes())
121 : {
122 12 : if (node.pedal)
123 : { // Check if it's a standard effect and not a bare merge node
124 9 : node.pedal->set_sample_rate(rate);
125 9 : node.pedal->reset();
126 3 : }
127 : }
128 41 : if (tuner_tap_)
129 : {
130 0 : tuner_tap_->set_sample_rate(rate);
131 0 : tuner_tap_->reset();
132 0 : }
133 27 : }
134 :
135 41 : if (was_running)
136 : {
137 0 : if (!start())
138 : {
139 0 : last_error_ = "Failed with sample rate " + std::to_string(rate) + " Hz. Reverting.";
140 0 : std::cerr << "[Amplitron] " << last_error_ << std::endl;
141 0 : sample_rate_ = prev_rate;
142 :
143 0 : std::lock_guard<std::mutex> lock(effect_mutex_);
144 : // FIX: Revert the sample rates using the graph nodes
145 0 : for (const auto &node : main_graph_.get_nodes())
146 : {
147 0 : if (node.pedal)
148 : {
149 0 : node.pedal->set_sample_rate(prev_rate);
150 0 : node.pedal->reset();
151 0 : }
152 : }
153 0 : if (tuner_tap_)
154 : {
155 0 : tuner_tap_->set_sample_rate(prev_rate);
156 0 : tuner_tap_->reset();
157 0 : }
158 0 : start();
159 0 : }
160 : else
161 : {
162 0 : last_error_.clear();
163 : }
164 0 : }
165 41 : }
166 1620 : void AudioEngine::commit_graph_changes()
167 : {
168 1620 : std::lock_guard<std::mutex> lock(effect_mutex_);
169 :
170 : // 1. Create a brand new executor (so we don't mutate memory the audio thread is currently reading)
171 1620 : auto new_executor = std::make_shared<AudioGraphExecutor>();
172 1620 : new_executor->prepare(sample_rate_, buffer_size_, 32);
173 :
174 : // 2. Compile the latest UI graph into the new executor
175 1620 : new_executor->compile(main_graph_);
176 :
177 : // 3. Promote it to the main slot. The audio thread will grab it on the next try_lock!
178 1620 : main_executor_ = new_executor;
179 1620 : topology_dirty_.store(true, std::memory_order_release);
180 1620 : }
181 :
182 : #ifdef AMPLITRON_TESTS
183 1 : void AudioEngine::replace_backend_for_test(AudioBackendState *backend)
184 : {
185 1 : if (backend_ == backend)
186 : {
187 0 : return;
188 : }
189 :
190 1 : destroy_audio_backend(backend_);
191 1 : backend_ = backend;
192 0 : }
193 : #endif
194 : }
|