Line data Source code
1 : #include "audio/engine/audio_engine.h"
2 : #include <cstring>
3 : #include <chrono>
4 : #include <algorithm>
5 : #include <cmath>
6 :
7 : namespace Amplitron {
8 :
9 59 : void AudioEngine::update_metronome_timing() {
10 59 : const int bpm = std::max(40, std::min(metronome_bpm_, 240));
11 59 : metronome_bpm_ = bpm;
12 59 : if (sample_rate_ <= 0) {
13 0 : metronome_samples_per_beat_ = 0.0;
14 0 : metronome_click_phase_inc_ = 0.0f;
15 0 : metronome_click_samples_total_ = 0;
16 0 : metronome_click_decay_ = 0.0f;
17 0 : return;
18 : }
19 :
20 83 : metronome_samples_per_beat_ = (static_cast<double>(sample_rate_) * 60.0)
21 59 : / static_cast<double>(bpm);
22 59 : if (metronome_samples_per_beat_ < 1.0) {
23 0 : metronome_samples_per_beat_ = 1.0;
24 0 : }
25 :
26 59 : constexpr float kClickLengthSec = 0.01f;
27 59 : const int click_samples = std::max(1, static_cast<int>(sample_rate_ * kClickLengthSec + 0.5f));
28 59 : metronome_click_samples_total_ = click_samples;
29 :
30 59 : constexpr float kTwoPi = 6.28318530718f;
31 59 : constexpr float kClickFreq = 1000.0f;
32 59 : metronome_click_phase_inc_ = (kTwoPi * kClickFreq) / static_cast<float>(sample_rate_);
33 :
34 59 : const float target = 0.001f;
35 59 : metronome_click_decay_ = std::exp(std::log(target) / static_cast<float>(click_samples));
36 24 : }
37 :
38 5160 : void AudioEngine::process_audio(const float* input, float* output, int frame_count) {
39 5160 : auto t_start = std::chrono::steady_clock::now();
40 :
41 5160 : if (frame_count > static_cast<int>(process_buffer_.size())) {
42 3 : process_buffer_.resize(frame_count, 0.0f);
43 3 : process_buffer_right_.resize(frame_count, 0.0f);
44 1 : }
45 :
46 5160 : const bool analyzer_on = analyzer_enabled_.load(std::memory_order_relaxed);
47 :
48 5160 : float in_gain = input_gain_.load(std::memory_order_relaxed);
49 5160 : float peak_in = 0.0f;
50 5160 : if (analyzer_on) {
51 5160 : float sum_sq_in = 0.0f;
52 5160 : bool clipped_in = false;
53 5160 : int cap = analyzer_capture_index_;
54 341224 : for (int i = 0; i < frame_count; ++i) {
55 336064 : process_buffer_[i] = input[i] * in_gain;
56 336064 : float abs_val = std::fabs(process_buffer_[i]);
57 336064 : if (abs_val > peak_in) peak_in = abs_val;
58 336064 : if (abs_val >= 1.0f) clipped_in = true;
59 336064 : sum_sq_in += process_buffer_[i] * process_buffer_[i];
60 336064 : analyzer_capture_input_[cap] = process_buffer_[i];
61 336064 : cap = (cap + 1) & ANALYZER_FFT_MASK;
62 185344 : }
63 5160 : input_rms_.store(std::sqrt(sum_sq_in / std::max(frame_count, 1)), std::memory_order_relaxed);
64 5160 : if (clipped_in) input_clipped_.store(true, std::memory_order_release);
65 5160 : analyzer_capture_index_ = cap;
66 2866 : } else {
67 0 : for (int i = 0; i < frame_count; ++i) {
68 0 : process_buffer_[i] = input[i] * in_gain;
69 0 : float abs_val = std::fabs(process_buffer_[i]);
70 0 : if (abs_val > peak_in) peak_in = abs_val;
71 0 : }
72 : }
73 5160 : input_level_.store(peak_in);
74 :
75 8026 : std::memcpy(process_buffer_right_.data(), process_buffer_.data(),
76 5160 : static_cast<size_t>(frame_count) * sizeof(float));
77 :
78 5160 : drain_gain_commands();
79 :
80 5160 : const bool metronome_target = metronome_enabled_state_.load(std::memory_order_relaxed);
81 5160 : if (metronome_target != metronome_enabled_) {
82 9 : metronome_enabled_ = metronome_target;
83 9 : metronome_sample_counter_ = 0.0;
84 9 : metronome_click_samples_remaining_ = 0;
85 9 : metronome_click_env_ = 0.0f;
86 9 : metronome_click_phase_ = 0.0f;
87 3 : }
88 :
89 5160 : const int bpm_state = metronome_bpm_state_.load(std::memory_order_relaxed);
90 5160 : const bool bpm_changed = (bpm_state != metronome_bpm_);
91 5160 : if (bpm_changed) {
92 6 : metronome_bpm_ = bpm_state;
93 2 : }
94 :
95 5160 : const float volume_state = metronome_volume_state_.load(std::memory_order_relaxed);
96 5160 : if (volume_state != metronome_volume_) {
97 6 : metronome_volume_ = clamp(volume_state, 0.0f, 1.0f);
98 2 : }
99 :
100 5160 : const bool sample_rate_changed = (metronome_sample_rate_ != sample_rate_);
101 5160 : if (sample_rate_changed) {
102 50 : metronome_sample_rate_ = sample_rate_;
103 20 : }
104 :
105 5160 : const bool timing_dirty = sample_rate_changed || bpm_changed;
106 :
107 5160 : if (timing_dirty) {
108 53 : update_metronome_timing();
109 53 : if (metronome_enabled_) {
110 6 : if (metronome_sample_counter_ <= 0.0 ||
111 0 : metronome_sample_counter_ > metronome_samples_per_beat_) {
112 6 : metronome_sample_counter_ = metronome_samples_per_beat_;
113 2 : }
114 2 : }
115 21 : }
116 :
117 5160 : if (effect_mutex_.try_lock()) {
118 5160 : drain_commands();
119 5160 : if (topology_dirty_.exchange(false, std::memory_order_acq_rel)) {
120 50 : audio_shadow_executor_ = main_executor_;
121 50 : audio_shadow_tuner_ = tuner_tap_;
122 20 : }
123 5160 : effect_mutex_.unlock();
124 2866 : }
125 :
126 5160 : if (audio_shadow_tuner_ && audio_shadow_tuner_->is_enabled()) {
127 3 : audio_shadow_tuner_->process(process_buffer_.data(), frame_count);
128 4 : std::memcpy(process_buffer_right_.data(), process_buffer_.data(),
129 3 : static_cast<size_t>(frame_count) * sizeof(float));
130 1 : }
131 : // The executor handles all the looping, routing, and processing internally!
132 5160 : if (audio_shadow_executor_) {
133 : // Broadcast tempo/bpm
134 5160 : audio_shadow_executor_->update_transport_state(static_cast<float>(metronome_bpm_));
135 :
136 : // Pass your mono/stereo buffers to the executor we built
137 5160 : audio_shadow_executor_->process(process_buffer_.data(), process_buffer_right_.data(), frame_count);
138 8026 : std::memcpy(process_buffer_.data(), process_buffer_right_.data(),
139 5160 : static_cast<size_t>(frame_count) * sizeof(float));
140 2866 : }
141 :
142 5160 : float out_gain = output_gain_.load(std::memory_order_relaxed);
143 5160 : float peak_out = 0.0f;
144 340077 : auto next_metronome_sample = [this]() -> float {
145 :
146 336064 : metronome_bpm_smoothed_ += metronome_bpm_smooth_alpha_ * (metronome_bpm_ - metronome_bpm_smoothed_);
147 336064 : metronome_volume_smoothed_ += metronome_volume_smooth_alpha_ * (metronome_volume_ - metronome_volume_smoothed_);
148 :
149 336064 : if (metronome_bpm_smoothed_ > 0.0f) {
150 336064 : metronome_samples_per_beat_ = (static_cast<double>(sample_rate_) * 60.0) / metronome_bpm_smoothed_;
151 185344 : }
152 :
153 336064 : if (!metronome_enabled_ || metronome_samples_per_beat_ <= 0.0) {
154 116480 : return 0.0f;
155 : }
156 216384 : metronome_sample_counter_ -= 1.0;
157 216384 : if (metronome_sample_counter_ <= 0.0) {
158 3 : metronome_sample_counter_ += metronome_samples_per_beat_;
159 3 : metronome_click_samples_remaining_ = metronome_click_samples_total_;
160 3 : metronome_click_env_ = 1.0f;
161 3 : metronome_click_phase_ = 0.0f;
162 1 : }
163 216384 : if (metronome_click_samples_remaining_ <= 0) {
164 144126 : return 0.0f;
165 : }
166 65 : static constexpr float kTwoPi = 6.28318530718f;
167 195 : float click = std::sin(metronome_click_phase_) * metronome_click_env_ * metronome_volume_smoothed_;
168 195 : metronome_click_phase_ += metronome_click_phase_inc_;
169 195 : if (metronome_click_phase_ >= kTwoPi) {
170 3 : metronome_click_phase_ -= kTwoPi;
171 1 : }
172 195 : metronome_click_env_ *= metronome_click_decay_;
173 195 : --metronome_click_samples_remaining_;
174 195 : return click;
175 187638 : };
176 5160 : if (analyzer_on) {
177 5160 : float sum_sq_out = 0.0f;
178 5160 : bool clipped_out = false;
179 5160 : int cap = (analyzer_capture_index_ - frame_count) & ANALYZER_FFT_MASK;
180 341224 : for (int i = 0; i < frame_count; ++i) {
181 336064 : float click = next_metronome_sample();
182 336064 : float out_l = clamp(process_buffer_[i] * out_gain + click, -1.0f, 1.0f);
183 336064 : float out_r = clamp(process_buffer_right_[i] * out_gain + click, -1.0f, 1.0f);
184 336064 : if (std::fabs(out_l) >= 1.0f || std::fabs(out_r) >= 1.0f) clipped_out = true;
185 336064 : output[i * 2] = out_l;
186 336064 : output[i * 2 + 1] = out_r;
187 336064 : process_buffer_[i] = out_l;
188 336064 : float abs_val = std::fabs(out_l);
189 336064 : if (abs_val > peak_out) peak_out = abs_val;
190 336064 : sum_sq_out += out_l * out_l;
191 336064 : analyzer_capture_output_[cap] = out_l;
192 336064 : cap = (cap + 1) & ANALYZER_FFT_MASK;
193 185344 : }
194 5160 : output_rms_.store(std::sqrt(sum_sq_out / std::max(frame_count, 1)), std::memory_order_relaxed);
195 5160 : if (clipped_out) output_clipped_.store(true, std::memory_order_release);
196 :
197 5160 : analyzer_samples_since_publish_ += frame_count;
198 5160 : if (analyzer_samples_since_publish_ >= ANALYZER_HOP_SIZE) {
199 321 : if (analyzer_mutex_.try_lock()) {
200 321 : const int start = analyzer_capture_index_;
201 321 : const int first_chunk = ANALYZER_FFT_SIZE - start;
202 642 : std::memcpy(analyzer_snapshot_input_.data(),
203 321 : analyzer_capture_input_.data() + start,
204 321 : static_cast<size_t>(first_chunk) * sizeof(float));
205 642 : std::memcpy(analyzer_snapshot_input_.data() + first_chunk,
206 321 : analyzer_capture_input_.data(),
207 249 : static_cast<size_t>(start) * sizeof(float));
208 642 : std::memcpy(analyzer_snapshot_output_.data(),
209 321 : analyzer_capture_output_.data() + start,
210 249 : static_cast<size_t>(first_chunk) * sizeof(float));
211 642 : std::memcpy(analyzer_snapshot_output_.data() + first_chunk,
212 321 : analyzer_capture_output_.data(),
213 249 : static_cast<size_t>(start) * sizeof(float));
214 321 : analyzer_sequence_.fetch_add(1, std::memory_order_release);
215 321 : analyzer_samples_since_publish_ = 0;
216 321 : analyzer_mutex_.unlock();
217 177 : }
218 177 : }
219 2866 : } else {
220 0 : for (int i = 0; i < frame_count; ++i) {
221 0 : float click = next_metronome_sample();
222 0 : float out_l = clamp(process_buffer_[i] * out_gain + click, -1.0f, 1.0f);
223 0 : float out_r = clamp(process_buffer_right_[i] * out_gain + click, -1.0f, 1.0f);
224 0 : output[i * 2] = out_l;
225 0 : output[i * 2 + 1] = out_r;
226 0 : process_buffer_[i] = out_l;
227 0 : float abs_val = std::fabs(out_l);
228 0 : if (abs_val > peak_out) peak_out = abs_val;
229 0 : }
230 : }
231 5160 : output_level_.store(peak_out);
232 :
233 5160 : if (recorder_.is_recording()) {
234 4 : recorder_.write_samples_stereo(process_buffer_.data(),
235 3 : process_buffer_right_.data(),
236 1 : frame_count);
237 1 : }
238 :
239 5160 : auto t_end = std::chrono::steady_clock::now();
240 5160 : float duration_us = std::chrono::duration<float, std::micro>(t_end - t_start).count();
241 5160 : callback_duration_us_.store(duration_us, std::memory_order_relaxed);
242 5160 : float budget_us = (static_cast<float>(frame_count) / sample_rate_) * 1e6f;
243 5160 : cpu_load_.store(duration_us / budget_us, std::memory_order_relaxed);
244 5160 : }
245 :
246 5160 : void AudioEngine::drain_gain_commands() {
247 1147 : AudioCommand cmd;
248 5211 : while (command_queue_.try_peek(cmd)) {
249 60 : if (cmd.type == AudioCommand::SetInputGain) {
250 27 : command_queue_.try_pop(cmd);
251 27 : input_gain_.store(cmd.value, std::memory_order_relaxed);
252 42 : } else if (cmd.type == AudioCommand::SetOutputGain) {
253 24 : command_queue_.try_pop(cmd);
254 24 : output_gain_.store(cmd.value, std::memory_order_relaxed);
255 17 : } else if (cmd.type == AudioCommand::SetMixerGain) {
256 0 : command_queue_.try_pop(cmd);
257 0 : if (audio_shadow_executor_) {
258 0 : audio_shadow_executor_->update_mixer_gain(cmd.effect_index, cmd.param_index, cmd.value);
259 0 : }
260 0 : } else {
261 6 : break;
262 : }
263 : }
264 5160 : }
265 :
266 5160 : void AudioEngine::drain_commands() {
267 1147 : AudioCommand cmd;
268 5184 : while (command_queue_.try_pop(cmd)) {
269 :
270 : // Helper to find the effect pointer safely inside the new Graph architecture by chain index
271 37 : auto get_effect = [&](int effect_index) -> std::shared_ptr<Effect> {
272 21 : if (effect_index >= 0 && effect_index < static_cast<int>(dummy_effects_.size())) {
273 12 : return dummy_effects_[effect_index];
274 : }
275 9 : return nullptr;
276 23 : };
277 :
278 24 : switch (cmd.type) {
279 4 : case AudioCommand::SetEffectParam: {
280 7 : if (auto fx = get_effect(cmd.effect_index)) {
281 3 : auto& params = fx->params();
282 4 : if (cmd.param_index >= 0 &&
283 3 : cmd.param_index < static_cast<int>(params.size())) {
284 3 : params[cmd.param_index].value = cmd.value;
285 1 : }
286 5 : }
287 6 : break;
288 : }
289 6 : case AudioCommand::SetEffectEnabled: {
290 11 : if (auto fx = get_effect(cmd.effect_index)) {
291 6 : fx->set_enabled(cmd.value > 0.5f);
292 8 : }
293 9 : break;
294 : }
295 4 : case AudioCommand::SetEffectMix: {
296 7 : if (auto fx = get_effect(cmd.effect_index)) {
297 3 : fx->set_mix(cmd.value);
298 5 : }
299 6 : break;
300 : }
301 0 : case AudioCommand::SetMixerGain:
302 : // Skip SetMixerGain in the mutex-gated path, it is handled lock-free
303 0 : break;
304 2 : case AudioCommand::SetInputGain:
305 3 : input_gain_.store(cmd.value, std::memory_order_relaxed);
306 3 : break;
307 0 : case AudioCommand::SetOutputGain:
308 0 : output_gain_.store(cmd.value, std::memory_order_relaxed);
309 0 : break;
310 0 : default:
311 0 : break;
312 : }
313 : }
314 5160 : }
315 : } // namespace Amplitron
|