summaryrefslogtreecommitdiff
path: root/src/audio.c
diff options
context:
space:
mode:
authorDoan Luu Duc Tai <doan@luuduct.ai>2026-09-08 14:46:47 -0500
committerDoan Luu Duc Tai <doan@luuduct.ai>2026-09-08 14:46:47 -0500
commitf34fb5042d40c186583f9e3009d8a1dd71d07d43 (patch)
tree3f494f3723ebe9f173d0702a97509794f0ef85a5 /src/audio.c
Initial commitmain
Diffstat (limited to 'src/audio.c')
-rw-r--r--src/audio.c126
1 files changed, 126 insertions, 0 deletions
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..3db0d70
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * vfetch - neofetch but the ascii part is a video
+ *
+ * Copyright (C) 2026 Doan Luu Duc Tai
+ *
+ */
+
+#include "audio.h"
+#include <stdatomic.h>
+#include <stdlib.h>
+#include <string.h>
+#include "audio_defs.h"
+#include "miniaudio.h"
+#include "pl_mpeg.h"
+
+/* This warning is suppressed because this function is a callback that
+ * implements a specific signature required by the miniaudio library
+ * (ma_data_callback). The library itself dictates the order of parameters
+ * (pOutput, pInput), and we cannot change it. Since this function is not called
+ * directly by our own code but by the audio library, the risk of a developer
+ * accidentally swapping the parameters is nonexistent. */
+static void
+data_callback(ma_device *pDevice,
+ /* NOLINTNEXTLINE(bugprone-easily-swappable-parameters) */
+ void *pOutput,
+ const void *pInput,
+ ma_uint32 frame_count) {
+ (void)pInput;
+ AudioState *audio = (AudioState *)pDevice->pUserData;
+ float *out = (float *)pOutput;
+ ma_uint32 total_frames_written = 0;
+
+ while (total_frames_written < frame_count) {
+ if (audio->buffer_index >= audio->buffer_count) {
+ plm_samples_t *samples = plm_decode_audio(audio->plm);
+
+ if (!samples) {
+ (void)memset(out + ((size_t)total_frames_written * 2),
+ 0,
+ (frame_count - total_frames_written) * sizeof(float) * 2);
+ return;
+ }
+
+ (void)memcpy(audio->sample_buffer,
+ samples->interleaved,
+ sizeof(float) * (size_t)samples->count * 2);
+ audio->buffer_index = 0;
+ audio->buffer_count = samples->count;
+ }
+
+ ma_uint32 frames_to_copy = frame_count - total_frames_written;
+ ma_uint32 frames_available = audio->buffer_count - audio->buffer_index;
+ if (frames_to_copy > frames_available) {
+ frames_to_copy = frames_available;
+ }
+
+ (void)memcpy(out + ((size_t)total_frames_written * 2),
+ audio->sample_buffer + ((size_t)audio->buffer_index * 2),
+ sizeof(float) * (size_t)frames_to_copy * 2);
+
+ audio->buffer_index += frames_to_copy;
+ total_frames_written += frames_to_copy;
+ }
+
+ atomic_fetch_add(&audio->frames_consumed, frame_count);
+}
+
+#define K_MPG_SAMPLES_PER_FRAME 1152
+VfetchError initialize_audio(AppState *state, const char *audio_source_file) {
+ AudioState *audio = &state->audio_state;
+ audio->state = state;
+ audio->buffer_index = 0;
+ audio->buffer_count = 0;
+ audio->sample_buffer = NULL;
+
+ audio->plm = plm_create_with_filename(audio_source_file);
+ if (!audio->plm || !plm_get_num_audio_streams(audio->plm)) {
+ return VFETCH_ERROR_MA_INIT;
+ }
+
+ ma_device_config config = ma_device_config_init(ma_device_type_playback);
+ config.playback.format = ma_format_f32;
+ config.playback.channels = 2;
+ config.sampleRate = (ma_uint32)plm_get_samplerate(audio->plm);
+ config.dataCallback = data_callback;
+ config.pUserData = audio;
+
+ if (ma_device_init(NULL, &config, &audio->device) != MA_SUCCESS) {
+ plm_destroy(audio->plm);
+ return VFETCH_ERROR_MA_INIT;
+ }
+
+ audio->sample_buffer = malloc(sizeof(float) * K_MPG_SAMPLES_PER_FRAME * 2);
+ if (!audio->sample_buffer) {
+ return VFETCH_ERROR_MEMORY_ALLOCATION;
+ }
+
+ ma_uint32 buffered_frames =
+ audio->device.playback.internalPeriodSizeInFrames *
+ audio->device.playback.internalPeriods;
+ if (audio->device.sampleRate > 0) {
+ audio->output_latency_seconds =
+ (double)buffered_frames / (double)audio->device.sampleRate;
+ } else {
+ audio->output_latency_seconds = 0.0;
+ }
+
+ if (ma_device_start(&audio->device) != MA_SUCCESS) {
+ shutdown_audio(state);
+ return VFETCH_ERROR_MA_INIT;
+ }
+
+ return VFETCH_SUCCESS;
+}
+#undef K_MPG_SAMPLES_PER_FRAME
+
+void shutdown_audio(AppState *state) {
+ ma_device_uninit(&state->audio_state.device);
+ if (state->audio_state.plm) {
+ plm_destroy(state->audio_state.plm);
+ }
+ if (state->audio_state.sample_buffer) {
+ free(state->audio_state.sample_buffer);
+ }
+}