ACMX 2.139.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
ACMX2/audio.cpp
Go to the documentation of this file.
1#include "audio.hpp"
2
3#include <RtAudio.h>
4
5#include <algorithm>
6#include <array>
7#include <atomic>
8#include <chrono>
9#include <cmath>
10#include <cstdint>
11#include <fstream>
12#include <iostream>
13#include <limits>
14#include <thread>
15#include <vector>
16
17namespace acmx2::audio {
18 namespace {
19
20 constexpr float PI = 3.14159265358979f;
21 constexpr std::size_t RING_CAPACITY = 1U << 20;
22 constexpr std::size_t RING_MASK = RING_CAPACITY - 1;
23 constexpr std::size_t WRITE_BATCH_SIZE = 4096;
24 constexpr std::uint32_t RECORD_FADE_SAMPLES = 2048;
25
26 void fft_radix2(float *data, int size) {
27 for (int i = 1, j = 0; i < size; ++i) {
28 int bit = size >> 1;
29 while (j & bit) {
30 j ^= bit;
31 bit >>= 1;
32 }
33 j ^= bit;
34 if (i < j) {
35 std::swap(data[2 * i], data[2 * j]);
36 std::swap(data[2 * i + 1], data[2 * j + 1]);
37 }
38 }
39
40 for (int length = 2; length <= size; length <<= 1) {
41 const float angle = -2.0f * PI / static_cast<float>(length);
42 const float rotation_real = std::cos(angle);
43 const float rotation_imaginary = std::sin(angle);
44 for (int i = 0; i < size; i += length) {
45 float current_real = 1.0f;
46 float current_imaginary = 0.0f;
47 for (int j = 0; j < length / 2; ++j) {
48 const int even = i + j;
49 const int odd = even + length / 2;
50 const float odd_real = current_real * data[2 * odd] - current_imaginary * data[2 * odd + 1];
51 const float odd_imaginary = current_real * data[2 * odd + 1] + current_imaginary * data[2 * odd];
52
53 data[2 * odd] = data[2 * even] - odd_real;
54 data[2 * odd + 1] = data[2 * even + 1] - odd_imaginary;
55 data[2 * even] += odd_real;
56 data[2 * even + 1] += odd_imaginary;
57
58 const float next_real = current_real * rotation_real - current_imaginary * rotation_imaginary;
59 current_imaginary = current_real * rotation_imaginary + current_imaginary * rotation_real;
60 current_real = next_real;
61 }
62 }
63 }
64 }
65
66 void write_wav_header(std::ofstream &file, std::uint32_t data_size, std::uint32_t sample_rate, std::uint16_t channels) {
67 constexpr std::uint16_t bits_per_sample = 16;
68 const std::uint32_t byte_rate = sample_rate * channels * bits_per_sample / 8;
69 const std::uint16_t block_align = channels * bits_per_sample / 8;
70 const std::uint32_t chunk_size = 36 + data_size;
71 constexpr std::uint32_t format_chunk_size = 16;
72 constexpr std::uint16_t pcm_format = 1;
73
74 file.seekp(0);
75 file.write("RIFF", 4);
76 file.write(reinterpret_cast<const char *>(&chunk_size), 4);
77 file.write("WAVE", 4);
78 file.write("fmt ", 4);
79 file.write(reinterpret_cast<const char *>(&format_chunk_size), 4);
80 file.write(reinterpret_cast<const char *>(&pcm_format), 2);
81 file.write(reinterpret_cast<const char *>(&channels), 2);
82 file.write(reinterpret_cast<const char *>(&sample_rate), 4);
83 file.write(reinterpret_cast<const char *>(&byte_rate), 4);
84 file.write(reinterpret_cast<const char *>(&block_align), 2);
85 file.write(reinterpret_cast<const char *>(&bits_per_sample), 2);
86 file.write("data", 4);
87 file.write(reinterpret_cast<const char *>(&data_size), 4);
88 }
89
90 RtAudio make_rt_audio() {
91#ifdef __linux__
92 return RtAudio(RtAudio::LINUX_PULSE);
93#else
94 return RtAudio();
95#endif
96 }
97
98 } // namespace
99
101 public:
102 void process_samples(const float *samples, unsigned int frame_count, unsigned int channels) {
103 if (samples == nullptr || frame_count == 0 || channels == 0)
104 return;
105
106 float amplitude_sum = 0.0f;
107 float peak_value = 0.0f;
108 float square_sum = 0.0f;
109 for (unsigned int frame = 0; frame < frame_count; ++frame) {
110 for (unsigned int channel = 0; channel < channels; ++channel)
111 amplitude_sum += std::abs(samples[frame * channels + channel]);
112
113 const float sample = std::abs(samples[frame * channels]);
114 peak_value = std::max(peak_value, sample);
115 square_sum += sample * sample;
116 }
117
118 const float amplitude_value = amplitude_sum / static_cast<float>(frame_count * channels);
119 amplitude.store(amplitude_value, std::memory_order_relaxed);
120 peak.store(peak_value, std::memory_order_relaxed);
121 rms.store(std::sqrt(square_sum / static_cast<float>(frame_count)), std::memory_order_relaxed);
122
123 constexpr float smooth_alpha = 0.15f;
124 smooth_value += smooth_alpha * (amplitude_value - smooth_value);
125 smooth.store(smooth_value, std::memory_order_relaxed);
126
127 const float rate = static_cast<float>(sample_rate.load(std::memory_order_relaxed));
128 const float low_coefficient = 1.0f - std::exp(-2.0f * PI * 300.0f / rate);
129 const float mid_coefficient = 1.0f - std::exp(-2.0f * PI * 3000.0f / rate);
130 float low_sum = 0.0f;
131 float mid_sum = 0.0f;
132 float high_sum = 0.0f;
133 for (unsigned int frame = 0; frame < frame_count; ++frame) {
134 const float sample = samples[frame * channels];
135 low_pass_state += low_coefficient * (sample - low_pass_state);
136 mid_pass_state += mid_coefficient * (sample - mid_pass_state);
137 const float low_sample = low_pass_state;
138 const float mid_sample = mid_pass_state - low_pass_state;
139 const float high_sample = sample - mid_pass_state;
140 low_sum += low_sample * low_sample;
141 mid_sum += mid_sample * mid_sample;
142 high_sum += high_sample * high_sample;
143 }
144 low.store(std::sqrt(low_sum / static_cast<float>(frame_count)), std::memory_order_relaxed);
145 mid.store(std::sqrt(mid_sum / static_cast<float>(frame_count)), std::memory_order_relaxed);
146 high.store(std::sqrt(high_sum / static_cast<float>(frame_count)), std::memory_order_relaxed);
147
148 unsigned int crossings = 0;
149 for (unsigned int frame = 1; frame < frame_count; ++frame) {
150 const float previous = samples[(frame - 1) * channels];
151 const float current = samples[frame * channels];
152 if ((previous >= 0.0f && current < 0.0f) || (previous < 0.0f && current >= 0.0f))
153 ++crossings;
154 }
155 frequency.store(static_cast<float>(crossings) * rate / (2.0f * static_cast<float>(frame_count)), std::memory_order_relaxed);
156
157 const int back = 1 - spectrum_front.load(std::memory_order_acquire);
158 const unsigned int copied_frames = std::min(static_cast<unsigned int>(FFT_SIZE), frame_count);
159 for (unsigned int i = 0; i < copied_frames; ++i)
160 spectrum_samples[back][i].store(samples[i * channels], std::memory_order_relaxed);
161 for (unsigned int i = copied_frames; i < FFT_SIZE; ++i)
162 spectrum_samples[back][i].store(0.0f, std::memory_order_relaxed);
163 spectrum_front.store(back, std::memory_order_release);
164 }
165
166 std::atomic<float> amplitude{0.0f};
167 std::atomic<float> frequency{0.0f};
168 std::atomic<float> peak{0.0f};
169 std::atomic<float> rms{0.0f};
170 std::atomic<float> smooth{0.0f};
171 std::atomic<float> low{0.0f};
172 std::atomic<float> mid{0.0f};
173 std::atomic<float> high{0.0f};
174 std::atomic<float> sensitivity{1.0f};
175 std::atomic<unsigned int> sample_rate{44100};
176
177 float smooth_value = 0.0f;
178 float low_pass_state = 0.0f;
179 float mid_pass_state = 0.0f;
180
181 std::array<std::array<std::atomic<float>, FFT_SIZE>, 2> spectrum_samples{};
182 std::atomic<int> spectrum_front{0};
183 std::vector<float> spectrum_magnitudes = std::vector<float>(FFT_SIZE / 2, 0.0f);
184 };
185
186 AudioAnalyzer::AudioAnalyzer() : impl(std::make_unique<Impl>()) {}
188
189 void AudioAnalyzer::process_samples(const float *samples, unsigned int frame_count, unsigned int channels) { impl->process_samples(samples, frame_count, channels); }
190
192 const float current_sensitivity = sensitivity();
193 const unsigned int current_sample_rate = sample_rate();
194 impl = std::make_unique<Impl>();
195 set_sensitivity(current_sensitivity);
196 set_sample_rate(current_sample_rate);
197 }
198
199 void AudioAnalyzer::set_sample_rate(unsigned int sample_rate) { impl->sample_rate.store(std::max(sample_rate, 1U), std::memory_order_relaxed); }
200
201 unsigned int AudioAnalyzer::sample_rate() const { return impl->sample_rate.load(std::memory_order_relaxed); }
202
203 void AudioAnalyzer::set_sensitivity(float sensitivity) { impl->sensitivity.store(std::clamp(sensitivity, 0.1f, 5.0f), std::memory_order_relaxed); }
204
205 float AudioAnalyzer::sensitivity() const { return impl->sensitivity.load(std::memory_order_relaxed); }
206
208 return {
209 impl->amplitude.load(std::memory_order_relaxed),
210 impl->frequency.load(std::memory_order_relaxed),
211 impl->peak.load(std::memory_order_relaxed),
212 impl->rms.load(std::memory_order_relaxed),
213 impl->smooth.load(std::memory_order_relaxed),
214 impl->low.load(std::memory_order_relaxed),
215 impl->mid.load(std::memory_order_relaxed),
216 impl->high.load(std::memory_order_relaxed),
217 };
218 }
219
221 const int front = impl->spectrum_front.load(std::memory_order_acquire);
222 std::array<float, FFT_SIZE * 2> complex{};
223 for (std::size_t i = 0; i < FFT_SIZE; ++i) {
224 const float hann = 0.5f * (1.0f - std::cos(2.0f * PI * static_cast<float>(i) / static_cast<float>(FFT_SIZE - 1)));
225 complex[2 * i] = impl->spectrum_samples[front][i].load(std::memory_order_relaxed) * hann;
226 }
227
228 fft_radix2(complex.data(), static_cast<int>(FFT_SIZE));
229
230 constexpr float inverse = 2.0f / static_cast<float>(FFT_SIZE);
231 for (std::size_t i = 0; i < FFT_SIZE / 2; ++i) {
232 const float real = complex[2 * i];
233 const float imaginary = complex[2 * i + 1];
234 impl->spectrum_magnitudes[i] = std::sqrt(real * real + imaginary * imaginary) * inverse;
235 }
236 }
237
238 const std::vector<float> &AudioAnalyzer::spectrum() const { return impl->spectrum_magnitudes; }
239
241 public:
242 ~Impl() { stop(); }
243
244 bool start(const std::string &filepath, unsigned int new_sample_rate, unsigned int new_channels) {
245 if (recording.load(std::memory_order_acquire) || new_channels == 0)
246 return false;
247
248 file.open(filepath, std::ios::binary | std::ios::trunc);
249 if (!file.is_open()) {
250 std::cerr << "acmx2: Failed to open audio recording file: " << filepath << "\n";
251 return false;
252 }
253
254 sample_rate = new_sample_rate;
255 channels = new_channels;
256 data_size.store(0, std::memory_order_relaxed);
257 fade_position.store(0, std::memory_order_relaxed);
258 ring_head.store(0, std::memory_order_relaxed);
259 ring_tail.store(0, std::memory_order_relaxed);
260 write_wav_header(file, 0, sample_rate, static_cast<std::uint16_t>(channels));
261 disk_running.store(true, std::memory_order_relaxed);
262 recording.store(true, std::memory_order_release);
263 disk_thread = std::thread(&Impl::write_to_disk, this);
264 std::cout << "acmx2: Audio recording started: " << filepath << "\n";
265 return true;
266 }
267
268 void stop() {
269 recording.store(false, std::memory_order_release);
270 while (active_captures.load(std::memory_order_acquire) != 0)
271 std::this_thread::yield();
272
273 disk_running.store(false, std::memory_order_release);
274 if (disk_thread.joinable())
275 disk_thread.join();
276
277 if (file.is_open()) {
278 const std::uint64_t bytes = data_size.load(std::memory_order_relaxed);
279 const std::uint32_t wav_bytes = static_cast<std::uint32_t>(std::min<std::uint64_t>(bytes, std::numeric_limits<std::uint32_t>::max()));
280 write_wav_header(file, wav_bytes, sample_rate, static_cast<std::uint16_t>(channels));
281 file.close();
282 std::cout << "acmx2: Audio recording stopped (" << bytes << " bytes written)\n";
283 }
284 }
285
286 void capture(const float *samples, unsigned int frame_count, unsigned int input_channels) {
287 if (samples == nullptr || !recording.load(std::memory_order_acquire))
288 return;
289
290 active_captures.fetch_add(1, std::memory_order_acq_rel);
291 if (!recording.load(std::memory_order_acquire)) {
292 active_captures.fetch_sub(1, std::memory_order_release);
293 return;
294 }
295
296 const unsigned int total_samples = frame_count * input_channels;
297 const float current_gain = gain.load(std::memory_order_relaxed);
298 std::uint32_t fade = fade_position.load(std::memory_order_relaxed);
299 std::size_t head = ring_head.load(std::memory_order_relaxed);
300 const std::size_t tail = ring_tail.load(std::memory_order_acquire);
301 for (unsigned int i = 0; i < total_samples; ++i) {
302 if (((head + 1) & RING_MASK) == (tail & RING_MASK))
303 break;
304
305 float sample = std::clamp(samples[i] * current_gain, -1.0f, 1.0f);
306 if (fade < RECORD_FADE_SAMPLES) {
307 sample *= static_cast<float>(fade) / static_cast<float>(RECORD_FADE_SAMPLES);
308 ++fade;
309 }
310 ring[head & RING_MASK] = static_cast<std::int16_t>(sample * 32767.0f);
311 ++head;
312 }
313 ring_head.store(head, std::memory_order_release);
314 fade_position.store(fade, std::memory_order_relaxed);
315 active_captures.fetch_sub(1, std::memory_order_release);
316 }
317
319 std::array<std::int16_t, WRITE_BATCH_SIZE> batch{};
320 while (disk_running.load(std::memory_order_acquire) || ring_tail.load(std::memory_order_relaxed) != ring_head.load(std::memory_order_acquire)) {
321 const std::size_t tail = ring_tail.load(std::memory_order_relaxed);
322 const std::size_t head = ring_head.load(std::memory_order_acquire);
323 if (tail == head) {
324 std::this_thread::sleep_for(std::chrono::milliseconds(5));
325 continue;
326 }
327
328 const std::size_t count = std::min<std::size_t>(head - tail, WRITE_BATCH_SIZE);
329 for (std::size_t i = 0; i < count; ++i)
330 batch[i] = ring[(tail + i) & RING_MASK];
331 ring_tail.store(tail + count, std::memory_order_release);
332
333 if (file.is_open()) {
334 const auto bytes = count * sizeof(std::int16_t);
335 file.write(reinterpret_cast<const char *>(batch.data()), static_cast<std::streamsize>(bytes));
336 data_size.fetch_add(bytes, std::memory_order_relaxed);
337 }
338 }
339 }
340
341 std::ofstream file;
342 std::atomic<bool> recording{false};
343 std::atomic<bool> disk_running{false};
344 std::atomic<unsigned int> active_captures{0};
345 std::atomic<std::uint64_t> data_size{0};
346 std::atomic<std::uint32_t> fade_position{0};
347 std::atomic<float> gain{1.0f};
348 unsigned int sample_rate = 44100;
349 unsigned int channels = 2;
350
351 std::array<std::int16_t, RING_CAPACITY> ring{};
352 std::atomic<std::size_t> ring_head{0};
353 std::atomic<std::size_t> ring_tail{0};
354 std::thread disk_thread;
355 };
356
357 AudioRecorder::AudioRecorder() : impl(std::make_unique<Impl>()) {}
359
360 bool AudioRecorder::start(const std::string &filepath, unsigned int sample_rate, unsigned int channels) { return impl->start(filepath, sample_rate, channels); }
361
362 void AudioRecorder::stop() { impl->stop(); }
363
364 bool AudioRecorder::is_recording() const { return impl->recording.load(std::memory_order_relaxed); }
365
366 void AudioRecorder::capture(const float *samples, unsigned int frame_count, unsigned int channels) { impl->capture(samples, frame_count, channels); }
367
368 void AudioRecorder::set_gain(float gain) { impl->gain.store(std::clamp(gain, 0.0f, 2.0f), std::memory_order_relaxed); }
369
370 float AudioRecorder::gain() const { return impl->gain.load(std::memory_order_relaxed); }
371
373 const std::uint64_t bytes = impl->data_size.load(std::memory_order_relaxed);
374 const std::uint64_t bytes_per_second = static_cast<std::uint64_t>(impl->sample_rate) * impl->channels * sizeof(std::int16_t);
375 if (bytes_per_second == 0)
376 return 0.0;
377 return static_cast<double>(bytes) / static_cast<double>(bytes_per_second);
378 }
379
381 public:
382 Impl() : stream(make_rt_audio()) {}
383
384 ~Impl() { close(); }
385
386 bool open(const AudioStreamConfig &config) {
387 close();
388
389 const std::vector<unsigned int> device_ids = stream.getDeviceIds();
390 if (device_ids.empty()) {
391 std::cerr << "acmx2: No audio devices found!\n";
392 return false;
393 }
394 std::cout << "acmx2: Audio device found...\n";
395
396 unsigned int input_device = 0;
397 if (config.input_device >= 0) {
398 input_device = static_cast<unsigned int>(config.input_device);
399 std::cout << "acmx2: Using specified input device: " << input_device << "\n";
400 } else {
401 input_device = stream.getDefaultInputDevice();
402 if (input_device == 0) {
403 for (const unsigned int id : device_ids) {
404 const RtAudio::DeviceInfo info = stream.getDeviceInfo(id);
405 if (info.isDefaultInput && info.inputChannels > 0) {
406 input_device = id;
407 break;
408 }
409 }
410 }
411 if (input_device == 0) {
412 for (const unsigned int id : device_ids) {
413 const RtAudio::DeviceInfo info = stream.getDeviceInfo(id);
414 if (info.inputChannels > 0) {
415 input_device = id;
416 break;
417 }
418 }
419 }
420 std::cout << "acmx2: Using default input device: " << input_device << "\n";
421 }
422
423 const RtAudio::DeviceInfo input_info = stream.getDeviceInfo(input_device);
424 if (input_info.inputChannels == 0) {
425 std::cerr << "acmx2: Input device has no input channels.\n";
426 return false;
427 }
428
429 std::cout << "acmx2: Selected input device " << input_device << ": " << input_info.name << "\n";
430 std::cout << "acmx2: Input channels: " << input_info.inputChannels << "\n";
431 if (input_info.isDefaultInput)
432 std::cout << "acmx2: [DEFAULT INPUT]\n";
433
434 input_channels = std::min(std::max(config.channels, 1U), input_info.inputChannels);
435 pass_through = config.pass_through;
436 analyzer.reset();
438
439 RtAudio::StreamParameters input_parameters;
440 input_parameters.deviceId = input_device;
441 input_parameters.nChannels = input_channels;
442 input_parameters.firstChannel = 0;
443
444 RtAudio::StreamParameters output_parameters;
445 RtAudio::StreamParameters *output_parameters_ptr = nullptr;
446 output_channels = 0;
447 if (pass_through) {
448 const unsigned int output_device = config.output_device >= 0 ? static_cast<unsigned int>(config.output_device) : stream.getDefaultOutputDevice();
449 std::cout << "acmx2: Using " << (config.output_device >= 0 ? "specified" : "default") << " output device: " << output_device << "\n";
450
451 const RtAudio::DeviceInfo output_info = stream.getDeviceInfo(output_device);
452 if (output_info.outputChannels > 0) {
453 output_channels = std::min(2U, output_info.outputChannels);
454 output_parameters.deviceId = output_device;
455 output_parameters.nChannels = output_channels;
456 output_parameters.firstChannel = 0;
457 output_parameters_ptr = &output_parameters;
458 std::cout << "acmx2: Audio pass-through enabled on device " << output_device << ": " << output_info.name << " (" << output_channels << " ch)\n";
459 } else {
460 std::cerr << "acmx2: Output device has no output channels, "
461 "pass-through disabled.\n";
462 pass_through = false;
463 }
464 }
465
466 unsigned int sample_rate = 44100;
467 if (!input_info.sampleRates.empty() && std::find(input_info.sampleRates.begin(), input_info.sampleRates.end(), sample_rate) == input_info.sampleRates.end()) {
468 sample_rate = 48000;
469 if (std::find(input_info.sampleRates.begin(), input_info.sampleRates.end(), sample_rate) == input_info.sampleRates.end())
470 sample_rate = input_info.sampleRates.front();
471 }
472 analyzer.set_sample_rate(sample_rate);
473
474 unsigned int buffer_frames = static_cast<unsigned int>(FFT_SIZE);
475 try {
476 stream.openStream(output_parameters_ptr, &input_parameters, RTAUDIO_FLOAT32, sample_rate, &buffer_frames, &Impl::audio_callback, this);
477 stream.startStream();
478 } catch (const std::exception &error) {
479 std::cerr << "acmx2: Audio error: " << error.what() << "\n";
480 close();
481 return false;
482 } catch (...) {
483 std::cerr << "acmx2: Unknown audio error occurred.\n";
484 close();
485 return false;
486 }
487
488 std::cout << "acmx2: Audio input stream opened (rate=" << sample_rate << " Hz, channels=" << input_channels << ", sensitivity=" << analyzer.sensitivity() << ")\n";
489 return stream.isStreamOpen();
490 }
491
492 void close() {
493 recorder.stop();
494 if (!stream.isStreamOpen())
495 return;
496 try {
497 if (stream.isStreamRunning())
498 stream.stopStream();
499 stream.closeStream();
500 std::cout << "acmx2: Audio stream closed.\n";
501 } catch (const std::exception &error) {
502 std::cerr << "acmx2: Error closing audio stream: " << error.what() << "\n";
503 }
504 }
505
506 static int audio_callback(void *output_buffer, void *input_buffer, unsigned int frame_count, double, RtAudioStreamStatus status, void *user_data) { return static_cast<Impl *>(user_data)->process_callback(output_buffer, input_buffer, frame_count, status); }
507
508 int process_callback(void *output_buffer, void *input_buffer, unsigned int frame_count, RtAudioStreamStatus status) {
509 auto *input = static_cast<float *>(input_buffer);
510 auto *output = static_cast<float *>(output_buffer);
511 if (status || input == nullptr) {
512 if (output != nullptr && output_channels > 0)
513 std::fill_n(output, frame_count * output_channels, 0.0f);
514 return 0;
515 }
516
517 if (output != nullptr && output_channels > 0) {
518 for (unsigned int frame = 0; frame < frame_count; ++frame) {
519 for (unsigned int channel = 0; channel < output_channels; ++channel) {
520 const unsigned int input_channel = channel < input_channels ? channel : 0;
521 output[frame * output_channels + channel] = pass_through ? input[frame * input_channels + input_channel] : 0.0f;
522 }
523 }
524 }
525
526 analyzer.process_samples(input, frame_count, input_channels);
527 recorder.capture(input, frame_count, input_channels);
528 return 0;
529 }
530
531 RtAudio stream;
534 unsigned int input_channels = 2;
535 unsigned int output_channels = 0;
536 bool pass_through = false;
537 };
538
539 AudioEngine::AudioEngine() : impl(std::make_unique<Impl>()) {}
540 AudioEngine::~AudioEngine() = default;
541
542 bool AudioEngine::open(const AudioStreamConfig &config) { return impl->open(config); }
543
544 void AudioEngine::close() { impl->close(); }
545
546 bool AudioEngine::is_open() const { return impl->stream.isStreamOpen(); }
547
548 unsigned int AudioEngine::input_channels() const { return impl->input_channels; }
549
550 AudioAnalyzer &AudioEngine::analyzer() { return impl->analyzer; }
551
552 const AudioAnalyzer &AudioEngine::analyzer() const { return impl->analyzer; }
553
554 AudioRecorder &AudioEngine::recorder() { return impl->recorder; }
555
556 const AudioRecorder &AudioEngine::recorder() const { return impl->recorder; }
557
559 RtAudio stream = make_rt_audio();
560 const std::vector<unsigned int> device_ids = stream.getDeviceIds();
561 std::cout << "acmx2: Found " << device_ids.size() << " audio device(s):\n";
562 for (const unsigned int id : device_ids) {
563 const RtAudio::DeviceInfo info = stream.getDeviceInfo(id);
564 std::cout << " Device " << id << ": " << info.name;
565 if (info.isDefaultInput)
566 std::cout << " [DEFAULT INPUT]";
567 if (info.isDefaultOutput)
568 std::cout << " [DEFAULT OUTPUT]";
569 std::cout << "\n";
570 std::cout << " Input channels: " << info.inputChannels << "\n";
571 std::cout << " Output channels: " << info.outputChannels << "\n";
572 std::cout << " Sample rates: ";
573 for (const unsigned int rate : info.sampleRates)
574 std::cout << rate << " ";
575 std::cout << "\n";
576 }
577 }
578
579} // namespace acmx2::audio
GLsizei GLsizei * length
void process_samples(const float *samples, unsigned int frame_count, unsigned int channels)
std::vector< float > spectrum_magnitudes
std::array< std::array< std::atomic< float >, FFT_SIZE >, 2 > spectrum_samples
std::atomic< unsigned int > sample_rate
Owns the audio-reactive analysis state shared by live and file audio.
const std::vector< float > & spectrum() const
void set_sensitivity(float sensitivity)
unsigned int sample_rate() const
void process_samples(const float *samples, unsigned int frame_count, unsigned int channels)
AudioMetrics metrics() const
std::unique_ptr< Impl > impl
void set_sample_rate(unsigned int sample_rate)
int process_callback(void *output_buffer, void *input_buffer, unsigned int frame_count, RtAudioStreamStatus status)
bool open(const AudioStreamConfig &config)
static int audio_callback(void *output_buffer, void *input_buffer, unsigned int frame_count, double, RtAudioStreamStatus status, void *user_data)
AudioRecorder & recorder()
AudioAnalyzer & analyzer()
unsigned int input_channels() const
bool open(const AudioStreamConfig &config)
std::unique_ptr< Impl > impl
std::array< std::int16_t, RING_CAPACITY > ring
void capture(const float *samples, unsigned int frame_count, unsigned int input_channels)
std::atomic< std::uint64_t > data_size
std::atomic< std::size_t > ring_head
std::atomic< std::size_t > ring_tail
std::atomic< std::uint32_t > fade_position
std::atomic< unsigned int > active_captures
bool start(const std::string &filepath, unsigned int new_sample_rate, unsigned int new_channels)
Asynchronously records interleaved float PCM samples to a 16-bit WAV file.
void capture(const float *samples, unsigned int frame_count, unsigned int channels)
std::unique_ptr< Impl > impl
bool start(const std::string &filepath, unsigned int sample_rate, unsigned int channels)
void write_wav_header(std::ofstream &file, std::uint32_t data_size, std::uint32_t sample_rate, std::uint16_t channels)
constexpr std::size_t FFT_SIZE