ACMX 2.136.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
ACMXVK/audio.cpp
Go to the documentation of this file.
1#include "audio.hpp"
2
4
5#include <rtaudio/RtAudio.h>
6
7#include <algorithm>
8#include <array>
9#include <atomic>
10#include <cmath>
11#include <fstream>
12#include <iostream>
13#include <limits>
14#include <mutex>
15#include <string>
16#include <vector>
17
18namespace acmxvk::audio {
19 namespace {
20
21 constexpr float PI = 3.14159265358979F;
22
23 void write_u16_le(std::ostream &output, std::uint16_t value) {
24 const std::array<char, 2> bytes{
25 static_cast<char>(value & 0xFFU),
26 static_cast<char>((value >> 8U) & 0xFFU),
27 };
28 output.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
29 }
30
31 void write_u32_le(std::ostream &output, std::uint32_t value) {
32 const std::array<char, 4> bytes{
33 static_cast<char>(value & 0xFFU),
34 static_cast<char>((value >> 8U) & 0xFFU),
35 static_cast<char>((value >> 16U) & 0xFFU),
36 static_cast<char>((value >> 24U) & 0xFFU),
37 };
38 output.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
39 }
40
41 void fft_radix2(float *data, int size) {
42 for (int index = 1, reversed = 0; index < size; ++index) {
43 int bit = size >> 1;
44 while ((reversed & bit) != 0) {
45 reversed ^= bit;
46 bit >>= 1;
47 }
48 reversed ^= bit;
49 if (index < reversed) {
50 std::swap(data[2 * index], data[2 * reversed]);
51 std::swap(data[2 * index + 1], data[2 * reversed + 1]);
52 }
53 }
54
55 for (int length = 2; length <= size; length <<= 1) {
56 const float angle = -2.0F * PI / static_cast<float>(length);
57 const float rotation_real = std::cos(angle);
58 const float rotation_imaginary = std::sin(angle);
59 for (int start = 0; start < size; start += length) {
60 float current_real = 1.0F;
61 float current_imaginary = 0.0F;
62 for (int offset = 0; offset < length / 2; ++offset) {
63 const int even = start + offset;
64 const int odd = even + length / 2;
65 const float odd_real =
66 current_real * data[2 * odd] -
67 current_imaginary * data[2 * odd + 1];
68 const float odd_imaginary =
69 current_real * data[2 * odd + 1] +
70 current_imaginary * data[2 * odd];
71
72 data[2 * odd] = data[2 * even] - odd_real;
73 data[2 * odd + 1] = data[2 * even + 1] - odd_imaginary;
74 data[2 * even] += odd_real;
75 data[2 * even + 1] += odd_imaginary;
76
77 const float next_real =
78 current_real * rotation_real -
79 current_imaginary * rotation_imaginary;
80 current_imaginary =
81 current_real * rotation_imaginary +
82 current_imaginary * rotation_real;
83 current_real = next_real;
84 }
85 }
86 }
87 }
88
89 [[nodiscard]] RtAudio makeRtAudio() {
90#ifdef __linux__
91 return RtAudio(RtAudio::LINUX_PULSE);
92#else
93 return RtAudio();
94#endif
95 }
96
97 } // namespace
98
100 public:
101 Impl() : stream(makeRtAudio()) {}
102 ~Impl() { close(); }
103
104 bool open(const AudioStreamConfig &config) {
105 close();
106
107 try {
108 const std::vector<unsigned int> device_ids = stream.getDeviceIds();
109 if (device_ids.empty()) {
110 std::cerr << "acmxvk: no audio devices found\n";
111 return false;
112 }
113
114 unsigned int input_device = 0;
115 if (config.input_device >= 0) {
116 input_device = static_cast<unsigned int>(config.input_device);
117 } else {
118 input_device = stream.getDefaultInputDevice();
119 }
120
121 const auto selected = std::find(device_ids.begin(), device_ids.end(),
122 input_device);
123 if (selected == device_ids.end()) {
124 std::cerr << "acmxvk: audio input device " << input_device
125 << " was not found\n";
126 return false;
127 }
128
129 const RtAudio::DeviceInfo input_info =
130 stream.getDeviceInfo(input_device);
131 input::validate_string(input_info.name,
133 "audio input device name");
134 if (input_info.inputChannels == 0) {
135 std::cerr << "acmxvk: audio device " << input_device
136 << " has no input channels\n";
137 return false;
138 }
139
140 input_channels = std::min(std::max(config.channels, 1U),
141 input_info.inputChannels);
142 pass_through = config.pass_through;
144 std::clamp(config.pass_through_gain, 0.0F, 4.0F);
145 recording_gain = std::clamp(config.recording_gain, 0.0F, 2.0F);
146 output_channels = 0;
147 sensitivity_value.store(std::clamp(config.sensitivity, 0.1F, 5.0F),
148 std::memory_order_relaxed);
149 resetMetrics();
150
151 unsigned int selected_rate = 44100;
152 if (!input_info.sampleRates.empty() &&
153 std::find(input_info.sampleRates.begin(), input_info.sampleRates.end(),
154 selected_rate) == input_info.sampleRates.end()) {
155 selected_rate = 48000;
156 if (std::find(input_info.sampleRates.begin(),
157 input_info.sampleRates.end(), selected_rate) ==
158 input_info.sampleRates.end()) {
159 selected_rate = input_info.sampleRates.front();
160 }
161 }
162
163 RtAudio::StreamParameters input_parameters;
164 input_parameters.deviceId = input_device;
165 input_parameters.nChannels = input_channels;
166 input_parameters.firstChannel = 0;
167
168 RtAudio::StreamParameters output_parameters;
169 RtAudio::StreamParameters *output_parameters_ptr = nullptr;
170 unsigned int output_device = 0;
171 std::string output_name;
172 std::vector<unsigned int> output_sample_rates;
173 if (pass_through) {
174 output_device =
175 config.output_device >= 0
176 ? static_cast<unsigned int>(config.output_device)
177 : stream.getDefaultOutputDevice();
178 const auto output_selected =
179 std::find(device_ids.begin(), device_ids.end(), output_device);
180 if (output_selected == device_ids.end()) {
181 std::cerr << "acmxvk: audio output device " << output_device
182 << " was not found; live pass-through disabled\n";
183 pass_through = false;
184 } else {
185 const RtAudio::DeviceInfo output_info =
186 stream.getDeviceInfo(output_device);
187 input::validate_string(output_info.name,
189 "audio output device name");
190 if (output_info.outputChannels == 0) {
191 std::cerr << "acmxvk: audio device " << output_device
192 << " has no output channels; live "
193 "pass-through disabled\n";
194 pass_through = false;
195 } else {
197 std::min(2U, output_info.outputChannels);
198 output_name = output_info.name;
199 output_sample_rates = output_info.sampleRates;
200 output_parameters.deviceId = output_device;
201 output_parameters.nChannels = output_channels;
202 output_parameters.firstChannel = 0;
203 output_parameters_ptr = &output_parameters;
204 }
205 }
206 }
207
208 const auto supports_rate = [](const std::vector<unsigned int> &rates,
209 unsigned int rate) {
210 return rates.empty() ||
211 std::find(rates.begin(), rates.end(), rate) != rates.end();
212 };
213 if (pass_through &&
214 !supports_rate(output_sample_rates, selected_rate)) {
215 std::vector<unsigned int> candidates{44100, 48000};
216 candidates.insert(candidates.end(), input_info.sampleRates.begin(),
217 input_info.sampleRates.end());
218 candidates.insert(candidates.end(), output_sample_rates.begin(),
219 output_sample_rates.end());
220 const auto common_rate = std::find_if(
221 candidates.begin(), candidates.end(), [&](unsigned int rate) {
222 return supports_rate(input_info.sampleRates, rate) &&
223 supports_rate(output_sample_rates, rate);
224 });
225 if (common_rate == candidates.end()) {
226 std::cerr << "acmxvk: input and output devices have no "
227 "common sample rate; live pass-through disabled\n";
228 pass_through = false;
229 output_channels = 0;
230 output_parameters_ptr = nullptr;
231 } else {
232 selected_rate = *common_rate;
233 }
234 }
235 sample_rate.store(selected_rate, std::memory_order_relaxed);
236
237 unsigned int buffer_frames = 512;
238 stream.openStream(output_parameters_ptr, &input_parameters,
239 RTAUDIO_FLOAT32, selected_rate, &buffer_frames,
240 &Impl::audioCallback, this);
241 stream.startStream();
242
243 std::cout << "acmxvk: audio input " << input_device << ": "
244 << input_info.name << " (" << selected_rate << " Hz, "
245 << input_channels << " channel"
246 << (input_channels == 1 ? "" : "s") << ")\n";
247 if (pass_through) {
248 std::cout << "acmxvk: live audio pass-through " << output_device
249 << ": " << output_name << " (" << output_channels
250 << " channel"
251 << (output_channels == 1 ? "" : "s")
252 << ", gain " << pass_through_gain << ")\n";
253 }
254 return stream.isStreamOpen();
255 } catch (const std::exception &error) {
256 std::cerr << "acmxvk: audio error: " << error.what() << '\n';
257 close();
258 return false;
259 }
260 }
261
262 void close() {
263 static_cast<void>(stopRecording());
264 if (!stream.isStreamOpen()) {
265 return;
266 }
267 try {
268 if (stream.isStreamRunning()) {
269 stream.stopStream();
270 }
271 stream.closeStream();
272 std::cout << "acmxvk: audio input closed\n";
273 } catch (const std::exception &error) {
274 std::cerr << "acmxvk: error closing audio input: " << error.what()
275 << '\n';
276 }
277 }
278
279 [[nodiscard]] AudioMetrics metrics() const {
280 return {
281 amplitude.load(std::memory_order_relaxed),
282 frequency.load(std::memory_order_relaxed),
283 peak.load(std::memory_order_relaxed),
284 rms.load(std::memory_order_relaxed),
285 smooth.load(std::memory_order_relaxed),
286 low.load(std::memory_order_relaxed),
287 mid.load(std::memory_order_relaxed),
288 high.load(std::memory_order_relaxed),
289 };
290 }
291
292 [[nodiscard]] std::vector<float> spectrum() const {
293 const int front = spectrum_front.load(std::memory_order_acquire);
294 std::array<float, AudioEngine::FFT_SIZE * 2> complex{};
295 for (std::size_t index = 0; index < AudioEngine::FFT_SIZE; ++index) {
296 const float hann =
297 0.5F *
298 (1.0F -
299 std::cos(2.0F * PI * static_cast<float>(index) /
300 static_cast<float>(AudioEngine::FFT_SIZE - 1)));
301 complex[2 * index] =
302 spectrum_samples[front][index].load(std::memory_order_relaxed) *
303 hann;
304 }
305
306 fft_radix2(complex.data(), static_cast<int>(AudioEngine::FFT_SIZE));
307
308 std::vector<float> magnitudes(AudioEngine::spectrum_bin_count());
309 constexpr float INVERSE = 2.0F / static_cast<float>(AudioEngine::FFT_SIZE);
310 for (std::size_t index = 0; index < magnitudes.size(); ++index) {
311 const float real = complex[2 * index];
312 const float imaginary = complex[2 * index + 1];
313 magnitudes[index] =
314 std::sqrt(real * real + imaginary * imaginary) * INVERSE;
315 }
316 return magnitudes;
317 }
318
320 amplitude.store(0.0F, std::memory_order_relaxed);
321 frequency.store(0.0F, std::memory_order_relaxed);
322 peak.store(0.0F, std::memory_order_relaxed);
323 rms.store(0.0F, std::memory_order_relaxed);
324 smooth.store(0.0F, std::memory_order_relaxed);
325 low.store(0.0F, std::memory_order_relaxed);
326 mid.store(0.0F, std::memory_order_relaxed);
327 high.store(0.0F, std::memory_order_relaxed);
328 smooth_value = 0.0F;
329 low_pass_state = 0.0F;
330 mid_pass_state = 0.0F;
331 for (auto &buffer : spectrum_samples) {
332 for (std::atomic<float> &sample : buffer) {
333 sample.store(0.0F, std::memory_order_relaxed);
334 }
335 }
336 spectrum_front.store(0, std::memory_order_release);
337 }
338
339 static int audioCallback(void *output_buffer, void *input_buffer,
340 unsigned int frame_count, double,
341 RtAudioStreamStatus status, void *user_data) {
342 return static_cast<Impl *>(user_data)
343 ->processCallback(static_cast<float *>(output_buffer),
344 static_cast<const float *>(input_buffer),
345 frame_count, status);
346 }
347
348 int processCallback(float *output, const float *samples,
349 unsigned int frame_count, RtAudioStreamStatus status) {
350 static_cast<void>(status);
351 if (output != nullptr && output_channels > 0) {
352 if (samples == nullptr || !pass_through) {
353 std::fill_n(output, frame_count * output_channels, 0.0F);
354 } else {
355 for (unsigned int frame = 0; frame < frame_count; ++frame) {
356 for (unsigned int channel = 0; channel < output_channels;
357 ++channel) {
358 const unsigned int input_channel =
359 channel < input_channels ? channel : 0;
360 output[frame * output_channels + channel] =
361 std::clamp(
362 samples[frame * input_channels + input_channel] *
364 -1.0F, 1.0F);
365 }
366 }
367 }
368 }
369 return processSamples(samples, frame_count);
370 }
371
372 int processSamples(const float *samples, unsigned int frame_count) {
373 if (samples == nullptr || frame_count == 0 || input_channels == 0) {
374 return 0;
375 }
376
377 float amplitude_sum = 0.0F;
378 float peak_value = 0.0F;
379 float square_sum = 0.0F;
380 float low_sum = 0.0F;
381 float mid_sum = 0.0F;
382 float high_sum = 0.0F;
383 unsigned int crossings = 0;
384 float previous = samples[0];
385 const int spectrum_back =
386 1 - spectrum_front.load(std::memory_order_acquire);
387 const float rate = static_cast<float>(
388 std::max(sample_rate.load(std::memory_order_relaxed), 1U));
389 const float low_coefficient = 1.0F - std::exp(-2.0F * PI * 300.0F / rate);
390 const float mid_coefficient =
391 1.0F - std::exp(-2.0F * PI * 3000.0F / rate);
392
393 std::unique_lock<std::mutex> recording_lock;
394 bool capture_recording = recording.load(std::memory_order_acquire);
395 if (capture_recording) {
396 recording_lock = std::unique_lock<std::mutex>(recording_mutex);
397 capture_recording =
398 recording.load(std::memory_order_acquire);
399 }
400
401 for (unsigned int frame = 0; frame < frame_count; ++frame) {
402 float mono = 0.0F;
403 for (unsigned int channel = 0; channel < input_channels; ++channel) {
404 const float sample = samples[frame * input_channels + channel];
405 amplitude_sum += std::abs(sample);
406 mono += sample;
407 }
408 mono /= static_cast<float>(input_channels);
409 if (capture_recording) {
410 recorded_samples.push_back(
411 std::clamp(mono * recording_gain, -1.0F, 1.0F));
412 }
413 low_pass_state += low_coefficient * (mono - low_pass_state);
414 mid_pass_state += mid_coefficient * (mono - mid_pass_state);
415 const float low_sample = low_pass_state;
416 const float mid_sample = mid_pass_state - low_pass_state;
417 const float high_sample = mono - mid_pass_state;
418 low_sum += low_sample * low_sample;
419 mid_sum += mid_sample * mid_sample;
420 high_sum += high_sample * high_sample;
421 if (frame < AudioEngine::FFT_SIZE) {
422 spectrum_samples[spectrum_back][frame].store(
423 mono, std::memory_order_relaxed);
424 }
425 peak_value = std::max(peak_value, std::abs(mono));
426 square_sum += mono * mono;
427 if (frame > 0 && ((previous >= 0.0F && mono < 0.0F) ||
428 (previous < 0.0F && mono >= 0.0F))) {
429 ++crossings;
430 }
431 previous = mono;
432 }
433 if (capture_recording) {
434 recorded_sample_count.fetch_add(frame_count,
435 std::memory_order_release);
436 }
437 for (std::size_t frame =
438 std::min<std::size_t>(frame_count, AudioEngine::FFT_SIZE);
439 frame < AudioEngine::FFT_SIZE; ++frame) {
440 spectrum_samples[spectrum_back][frame].store(
441 0.0F, std::memory_order_relaxed);
442 }
443 spectrum_front.store(spectrum_back, std::memory_order_release);
444
445 const float amplitude_value =
446 amplitude_sum /
447 static_cast<float>(frame_count * input_channels);
448 constexpr float SMOOTH_ALPHA = 0.15F;
449 smooth_value += SMOOTH_ALPHA * (amplitude_value - smooth_value);
450
451 amplitude.store(amplitude_value, std::memory_order_relaxed);
452 peak.store(peak_value, std::memory_order_relaxed);
453 rms.store(std::sqrt(square_sum / static_cast<float>(frame_count)),
454 std::memory_order_relaxed);
455 smooth.store(smooth_value, std::memory_order_relaxed);
456 low.store(std::sqrt(low_sum / static_cast<float>(frame_count)),
457 std::memory_order_relaxed);
458 mid.store(std::sqrt(mid_sum / static_cast<float>(frame_count)),
459 std::memory_order_relaxed);
460 high.store(std::sqrt(high_sum / static_cast<float>(frame_count)),
461 std::memory_order_relaxed);
462 frequency.store(
463 static_cast<float>(crossings) *
464 rate /
465 (2.0F * static_cast<float>(frame_count)),
466 std::memory_order_relaxed);
467 return 0;
468 }
469
471 if (!stream.isStreamOpen() ||
472 recording.load(std::memory_order_acquire)) {
473 return false;
474 }
475
476 std::lock_guard<std::mutex> lock(recording_mutex);
477 recorded_samples.clear();
479 std::max(sample_rate.load(std::memory_order_relaxed), 1U);
480 recorded_sample_count.store(0, std::memory_order_relaxed);
481 constexpr unsigned int RESERVE_SECONDS = 60;
482 recorded_samples.reserve(static_cast<std::size_t>(recording_sample_rate) *
483 RESERVE_SECONDS);
484 recording.store(true, std::memory_order_release);
485 std::cout << "acmxvk: live audio recording started ("
486 << recording_sample_rate << " Hz, mono, gain "
487 << recording_gain << ")\n";
488 return true;
489 }
490
491 [[nodiscard]] double recordingTime() const {
492 const unsigned int rate = std::max(recording_sample_rate, 1U);
493 return static_cast<double>(
494 recorded_sample_count.load(std::memory_order_acquire)) /
495 static_cast<double>(rate);
496 }
497
499 recording.store(false, std::memory_order_release);
500 std::lock_guard<std::mutex> lock(recording_mutex);
501
502 AudioRecording result;
503 result.samples = std::move(recorded_samples);
505 if (!result.empty()) {
506 std::cout << "acmxvk: live audio recording stopped ("
507 << result.duration_seconds() << " seconds)\n";
508 }
509 recorded_samples.clear();
510 return result;
511 }
512
513 RtAudio stream;
514 std::atomic<float> amplitude{0.0F};
515 std::atomic<float> frequency{0.0F};
516 std::atomic<float> peak{0.0F};
517 std::atomic<float> rms{0.0F};
518 std::atomic<float> smooth{0.0F};
519 std::atomic<float> low{0.0F};
520 std::atomic<float> mid{0.0F};
521 std::atomic<float> high{0.0F};
522 std::atomic<float> sensitivity_value{1.0F};
523 std::atomic<unsigned int> sample_rate{44100};
524 std::atomic<bool> recording{false};
525 std::atomic<std::uint64_t> recorded_sample_count{0};
526 std::array<std::array<std::atomic<float>, AudioEngine::FFT_SIZE>, 2>
528 std::atomic<int> spectrum_front{0};
529 std::mutex recording_mutex;
530 std::vector<float> recorded_samples;
531 unsigned int recording_sample_rate = 44100;
532 unsigned int input_channels = 0;
533 unsigned int output_channels = 0;
534 bool pass_through = false;
535 float pass_through_gain = 1.0F;
536 float recording_gain = 1.0F;
537 float smooth_value = 0.0F;
538 float low_pass_state = 0.0F;
539 float mid_pass_state = 0.0F;
540 };
541
542 AudioEngine::AudioEngine() : impl(std::make_unique<Impl>()) {}
543 AudioEngine::~AudioEngine() = default;
544
546 return impl->open(config);
547 }
548
550 impl->close();
551 }
552
553 bool AudioEngine::is_open() const {
554 return impl->stream.isStreamOpen();
555 }
556
558 return impl->metrics();
559 }
560
561 unsigned int AudioEngine::sample_rate() const {
562 return impl->sample_rate.load(std::memory_order_relaxed);
563 }
564
566 impl->sensitivity_value.store(std::clamp(sensitivity, 0.1F, 5.0F),
567 std::memory_order_relaxed);
568 }
569
571 return impl->sensitivity_value.load(std::memory_order_relaxed);
572 }
573
574 std::vector<float> AudioEngine::spectrum() const {
575 return impl->spectrum();
576 }
577
578 void AudioEngine::process_samples(const float *samples,
579 unsigned int frame_count,
580 unsigned int channels,
581 unsigned int sample_rate) {
582 impl->input_channels = channels;
583 impl->sample_rate.store(std::max(sample_rate, 1U),
584 std::memory_order_relaxed);
585 impl->processSamples(samples, frame_count);
586 }
587
589 return impl->startRecording();
590 }
591
593 return impl->stopRecording();
594 }
595
597 return impl->recording.load(std::memory_order_acquire);
598 }
599
601 return impl->recordingTime();
602 }
603
604 bool write_wav_file(const AudioRecording &recording,
605 const std::string &filename) {
606 constexpr std::uint16_t CHANNEL_COUNT = 1;
607 constexpr std::uint16_t BITS_PER_SAMPLE = 16;
608 constexpr std::uint16_t PCM_FORMAT = 1;
609 constexpr std::uint32_t FORMAT_CHUNK_SIZE = 16;
610 constexpr std::size_t SAMPLE_BUFFER_SIZE = 4096;
611
612 if (recording.empty() || recording.sample_rate == 0 || filename.empty()) {
613 return false;
614 }
615
616 const std::uint64_t data_size_64 =
617 static_cast<std::uint64_t>(recording.samples.size()) *
618 sizeof(std::int16_t);
619 if (data_size_64 > std::numeric_limits<std::uint32_t>::max() - 36U) {
620 std::cerr << "acmxvk: WAV recording exceeds the RIFF size limit\n";
621 return false;
622 }
623 const auto data_size = static_cast<std::uint32_t>(data_size_64);
624
625 std::ofstream output(filename, std::ios::binary | std::ios::trunc);
626 if (!output) {
627 return false;
628 }
629
630 output.write("RIFF", 4);
631 write_u32_le(output, 36U + data_size);
632 output.write("WAVE", 4);
633 output.write("fmt ", 4);
634 write_u32_le(output, FORMAT_CHUNK_SIZE);
635 write_u16_le(output, PCM_FORMAT);
636 write_u16_le(output, CHANNEL_COUNT);
637 write_u32_le(output, recording.sample_rate);
638 write_u32_le(output, recording.sample_rate * sizeof(std::int16_t));
639 write_u16_le(output, sizeof(std::int16_t));
640 write_u16_le(output, BITS_PER_SAMPLE);
641 output.write("data", 4);
642 write_u32_le(output, data_size);
643
644 std::array<char, SAMPLE_BUFFER_SIZE * sizeof(std::int16_t)>
645 sample_buffer{};
646 std::size_t offset = 0;
647 while (offset < recording.samples.size()) {
648 const std::size_t sample_count =
649 std::min(SAMPLE_BUFFER_SIZE, recording.samples.size() - offset);
650 for (std::size_t index = 0; index < sample_count; ++index) {
651 const float recorded_sample = recording.samples[offset + index];
652 const float sample = std::isfinite(recorded_sample)
653 ? std::clamp(recorded_sample, -1.0F, 1.0F)
654 : 0.0F;
655 const auto pcm_sample = static_cast<std::int16_t>(
656 std::lround(sample * 32767.0F));
657 const auto sample_bits = static_cast<std::uint16_t>(pcm_sample);
658 sample_buffer[index * 2U] =
659 static_cast<char>(sample_bits & 0xFFU);
660 sample_buffer[index * 2U + 1U] =
661 static_cast<char>((sample_bits >> 8U) & 0xFFU);
662 }
663 output.write(sample_buffer.data(),
664 static_cast<std::streamsize>(sample_count *
665 sizeof(std::int16_t)));
666 if (!output) {
667 return false;
668 }
669 offset += sample_count;
670 }
671 output.flush();
672 return output.good();
673 }
674
676 impl->resetMetrics();
677 }
678
680 try {
681 RtAudio stream = makeRtAudio();
682 const std::vector<unsigned int> device_ids = stream.getDeviceIds();
683 std::cout << "acmxvk: found " << device_ids.size()
684 << " audio device(s)\n";
685 for (const unsigned int id : device_ids) {
686 const RtAudio::DeviceInfo info = stream.getDeviceInfo(id);
687 input::validate_string(info.name,
689 "audio device name");
690 std::cout << " Device " << id << ": " << info.name;
691 if (info.isDefaultInput) {
692 std::cout << " [DEFAULT INPUT]";
693 }
694 if (info.isDefaultOutput) {
695 std::cout << " [DEFAULT OUTPUT]";
696 }
697 std::cout << "\n Input channels: " << info.inputChannels
698 << "\n Output channels: " << info.outputChannels
699 << "\n";
700 }
701 } catch (const std::exception &error) {
702 throw std::runtime_error(std::string("could not enumerate audio devices: ") +
703 error.what());
704 }
705 }
706
707} // namespace acmxvk::audio
GLsizei GLsizei * length
std::vector< float > spectrum() const
std::atomic< std::uint64_t > recorded_sample_count
int processCallback(float *output, const float *samples, unsigned int frame_count, RtAudioStreamStatus status)
int processSamples(const float *samples, unsigned int frame_count)
bool open(const AudioStreamConfig &config)
std::vector< float > recorded_samples
std::atomic< float > sensitivity_value
static int audioCallback(void *output_buffer, void *input_buffer, unsigned int frame_count, double, RtAudioStreamStatus status, void *user_data)
std::array< std::array< std::atomic< float >, AudioEngine::FFT_SIZE >, 2 > spectrum_samples
std::atomic< unsigned int > sample_rate
std::unique_ptr< Impl > impl
std::vector< float > spectrum() const
static constexpr std::uint32_t spectrum_bin_count()
AudioRecording stop_recording()
static constexpr std::size_t FFT_SIZE
void process_samples(const float *samples, unsigned int frame_count, unsigned int channels, unsigned int sample_rate)
unsigned int sample_rate() const
AudioMetrics metrics() const
bool open(const AudioStreamConfig &config)
void set_sensitivity(float sensitivity)
void write_u16_le(std::ostream &output, std::uint16_t value)
void write_u32_le(std::ostream &output, std::uint32_t value)
bool write_wav_file(const AudioRecording &recording, const std::string &filename)
void validate_string(std::string_view value, StringKind kind, std::string_view context, bool allow_empty)
std::vector< float > samples