20 constexpr float PI = 3.14159265358979f;
27 for (
int i = 1, j = 0; i < size; ++i) {
35 std::swap(data[2 * i], data[2 * j]);
36 std::swap(data[2 * i + 1], data[2 * j + 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];
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;
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;
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;
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);
92 return RtAudio(RtAudio::LINUX_PULSE);
102 void process_samples(
const float *samples,
unsigned int frame_count,
unsigned int channels) {
103 if (samples ==
nullptr || frame_count == 0 || channels == 0)
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]);
113 const float sample = std::abs(samples[frame * channels]);
114 peak_value = std::max(peak_value, sample);
115 square_sum += sample * sample;
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);
123 constexpr float smooth_alpha = 0.15f;
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];
140 low_sum += low_sample * low_sample;
141 mid_sum += mid_sample * mid_sample;
142 high_sum += high_sample * high_sample;
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);
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))
155 frequency.store(
static_cast<float>(crossings) * rate / (2.0f *
static_cast<float>(frame_count)), std::memory_order_relaxed);
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)
169 std::atomic<float>
rms{0.0f};
171 std::atomic<float>
low{0.0f};
172 std::atomic<float>
mid{0.0f};
193 const unsigned int current_sample_rate =
sample_rate();
194 impl = std::make_unique<Impl>();
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),
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;
228 fft_radix2(complex.data(),
static_cast<int>(
FFT_SIZE));
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;
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)
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";
256 data_size.store(0, std::memory_order_relaxed);
258 ring_head.store(0, std::memory_order_relaxed);
259 ring_tail.store(0, std::memory_order_relaxed);
262 recording.store(
true, std::memory_order_release);
264 std::cout <<
"acmx2: Audio recording started: " << filepath <<
"\n";
269 recording.store(
false, std::memory_order_release);
271 std::this_thread::yield();
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()));
282 std::cout <<
"acmx2: Audio recording stopped (" << bytes <<
" bytes written)\n";
286 void capture(
const float *samples,
unsigned int frame_count,
unsigned int input_channels) {
287 if (samples ==
nullptr || !
recording.load(std::memory_order_acquire))
291 if (!
recording.load(std::memory_order_acquire)) {
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))
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);
310 ring[head & RING_MASK] =
static_cast<std::int16_t
>(sample * 32767.0f);
313 ring_head.store(head, std::memory_order_release);
319 std::array<std::int16_t, WRITE_BATCH_SIZE> batch{};
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);
324 std::this_thread::sleep_for(std::chrono::milliseconds(5));
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);
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);
351 std::array<std::int16_t, RING_CAPACITY>
ring{};
360 bool AudioRecorder::start(
const std::string &filepath,
unsigned int sample_rate,
unsigned int channels) {
return impl->start(filepath, sample_rate, channels); }
366 void AudioRecorder::capture(
const float *samples,
unsigned int frame_count,
unsigned int channels) {
impl->capture(samples, frame_count, channels); }
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)
377 return static_cast<double>(bytes) /
static_cast<double>(bytes_per_second);
389 const std::vector<unsigned int> device_ids =
stream.getDeviceIds();
390 if (device_ids.empty()) {
391 std::cerr <<
"acmx2: No audio devices found!\n";
394 std::cout <<
"acmx2: Audio device found...\n";
396 unsigned int input_device = 0;
398 input_device =
static_cast<unsigned int>(config.
input_device);
399 std::cout <<
"acmx2: Using specified input device: " << input_device <<
"\n";
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) {
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) {
420 std::cout <<
"acmx2: Using default input device: " << input_device <<
"\n";
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";
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";
439 RtAudio::StreamParameters input_parameters;
440 input_parameters.deviceId = input_device;
442 input_parameters.firstChannel = 0;
444 RtAudio::StreamParameters output_parameters;
445 RtAudio::StreamParameters *output_parameters_ptr =
nullptr;
449 std::cout <<
"acmx2: Using " << (config.
output_device >= 0 ?
"specified" :
"default") <<
" output device: " << output_device <<
"\n";
451 const RtAudio::DeviceInfo output_info =
stream.getDeviceInfo(output_device);
452 if (output_info.outputChannels > 0) {
454 output_parameters.deviceId = output_device;
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";
460 std::cerr <<
"acmx2: Output device has no output channels, "
461 "pass-through disabled.\n";
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()) {
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();
474 unsigned int buffer_frames =
static_cast<unsigned int>(
FFT_SIZE);
476 stream.openStream(output_parameters_ptr, &input_parameters, RTAUDIO_FLOAT32, sample_rate, &buffer_frames, &
Impl::audio_callback,
this);
478 }
catch (
const std::exception &error) {
479 std::cerr <<
"acmx2: Audio error: " << error.what() <<
"\n";
483 std::cerr <<
"acmx2: Unknown audio error occurred.\n";
488 std::cout <<
"acmx2: Audio input stream opened (rate=" << sample_rate <<
" Hz, channels=" <<
input_channels <<
", sensitivity=" <<
analyzer.
sensitivity() <<
")\n";
489 return stream.isStreamOpen();
494 if (!
stream.isStreamOpen())
497 if (
stream.isStreamRunning())
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";
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); }
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) {
518 for (
unsigned int frame = 0; frame < frame_count; ++frame) {
520 const unsigned int input_channel = channel <
input_channels ? channel : 0;
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]";
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 <<
" ";
std::atomic< float > peak
std::atomic< float > amplitude
void process_samples(const float *samples, unsigned int frame_count, unsigned int channels)
std::atomic< float > smooth
std::vector< float > spectrum_magnitudes
std::array< std::array< std::atomic< float >, FFT_SIZE >, 2 > spectrum_samples
std::atomic< float > frequency
std::atomic< float > high
std::atomic< unsigned int > sample_rate
std::atomic< float > sensitivity
std::atomic< int > spectrum_front
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
float sensitivity() 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)
unsigned int input_channels
unsigned int output_channels
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
static void list_devices()
std::array< std::int16_t, RING_CAPACITY > ring
std::atomic< bool > recording
void capture(const float *samples, unsigned int frame_count, unsigned int input_channels)
std::atomic< bool > disk_running
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< float > gain
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)
void set_gain(float gain)
double duration_seconds() const
std::unique_ptr< Impl > impl
bool is_recording() const
bool start(const std::string &filepath, unsigned int sample_rate, unsigned int channels)
constexpr std::size_t RING_MASK
void fft_radix2(float *data, int size)
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 WRITE_BATCH_SIZE
constexpr std::uint32_t RECORD_FADE_SAMPLES
constexpr std::size_t RING_CAPACITY
constexpr std::size_t FFT_SIZE