Update audio.c

This commit is contained in:
Anthony Samms
2025-09-26 10:57:40 -04:00
parent 48fa513151
commit f4f586fde5

View File

@@ -1,7 +1,9 @@
//WARNING: This is ported code from raylib's rAudio. It's also been mangled // Thank you raysan for data structures
// with claude. I need a professional review. Thank you raysan for most of the code
// https://github.com/raysan5/raylib/blob/master/src/raudio.c // https://github.com/raysan5/raylib/blob/master/src/raudio.c
// This could be cleaned up significantly. I do not think
// the audio stream structure is necessary after converting the music
// stream to portaudio
#include "portaudio.h" #include "portaudio.h"
#include <pthread.h> #include <pthread.h>
@@ -14,7 +16,6 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
// Logging macros
#define LOG_INFO 0 #define LOG_INFO 0
#define LOG_WARNING 1 #define LOG_WARNING 1
#define LOG_ERROR 2 #define LOG_ERROR 2
@@ -28,15 +29,12 @@
fflush(stdout); \ fflush(stdout); \
} while(0) } while(0)
// Memory management macros
#define FREE(ptr) do { if (ptr) { free(ptr); (ptr) = NULL; } } while(0) #define FREE(ptr) do { if (ptr) { free(ptr); (ptr) = NULL; } } while(0)
#define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo
// Forward declarations of structures
struct audio_buffer; struct audio_buffer;
// Type definitions
typedef struct wave { typedef struct wave {
unsigned int frameCount; // Total number of frames (considering channels) unsigned int frameCount; // Total number of frames (considering channels)
unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleRate; // Frequency (samples per second)
@@ -45,7 +43,6 @@ typedef struct wave {
void *data; // Buffer data pointer void *data; // Buffer data pointer
} wave; } wave;
// AudioStream, custom audio stream
typedef struct audio_stream { typedef struct audio_stream {
struct audio_buffer *buffer; // Pointer to internal data used by the audio system struct audio_buffer *buffer; // Pointer to internal data used by the audio system
@@ -54,13 +51,12 @@ typedef struct audio_stream {
unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
} audio_stream; } audio_stream;
// Sound
typedef struct sound { typedef struct sound {
audio_stream stream; // Audio stream audio_stream stream; // Audio stream
unsigned int frameCount; // Total number of frames (considering channels) unsigned int frameCount; // Total number of frames (considering channels)
} sound; } sound;
// Music, audio stream, anything longer than ~10 seconds should be streamed //anything longer than ~10 seconds should be streamed
typedef struct music { typedef struct music {
audio_stream stream; // Audio stream audio_stream stream; // Audio stream
unsigned int frameCount; // Total number of frames (considering channels) unsigned int frameCount; // Total number of frames (considering channels)
@@ -74,7 +70,6 @@ typedef struct music_ctx {
double src_ratio; double src_ratio;
} music_ctx; } music_ctx;
// Audio buffer structure
struct audio_buffer { struct audio_buffer {
float volume; // Audio buffer volume float volume; // Audio buffer volume
float pitch; // Audio buffer pitch float pitch; // Audio buffer pitch
@@ -91,9 +86,6 @@ struct audio_buffer {
struct audio_buffer *prev; // Previous audio buffer on the list struct audio_buffer *prev; // Previous audio buffer on the list
}; };
// Audio data context
typedef struct AudioData { typedef struct AudioData {
struct { struct {
PaStream *stream; // PortAudio stream PaStream *stream; // PortAudio stream
@@ -111,8 +103,6 @@ typedef struct AudioData {
} Buffer; } Buffer;
} AudioData; } AudioData;
// Function declarations
// Device management
void list_host_apis(void); void list_host_apis(void);
void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned long buffer_size); void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned long buffer_size);
void close_audio_device(void); void close_audio_device(void);
@@ -120,7 +110,6 @@ bool is_audio_device_ready(void);
void set_master_volume(float volume); void set_master_volume(float volume);
float get_master_volume(void); float get_master_volume(void);
// Audio buffer management
struct audio_buffer *load_audio_buffer(uint32_t channels, uint32_t size_in_frames, int usage); struct audio_buffer *load_audio_buffer(uint32_t channels, uint32_t size_in_frames, int usage);
void unload_audio_buffer(struct audio_buffer *buffer); void unload_audio_buffer(struct audio_buffer *buffer);
bool is_audio_buffer_playing(struct audio_buffer *buffer); bool is_audio_buffer_playing(struct audio_buffer *buffer);
@@ -134,12 +123,10 @@ void set_audio_buffer_pan(struct audio_buffer *buffer, float pan);
void track_audio_buffer(struct audio_buffer *buffer); void track_audio_buffer(struct audio_buffer *buffer);
void untrack_audio_buffer(struct audio_buffer *buffer); void untrack_audio_buffer(struct audio_buffer *buffer);
// Wave management
wave load_wave(const char* filename); wave load_wave(const char* filename);
bool is_wave_valid(wave wave); bool is_wave_valid(wave wave);
void unload_wave(wave wave); void unload_wave(wave wave);
// Sound management
sound load_sound_from_wave(wave wave); sound load_sound_from_wave(wave wave);
sound load_sound(const char* filename); sound load_sound(const char* filename);
bool is_sound_valid(sound sound); bool is_sound_valid(sound sound);
@@ -153,7 +140,6 @@ void set_sound_volume(sound sound, float volume);
void set_sound_pitch(sound sound, float pitch); void set_sound_pitch(sound sound, float pitch);
void set_sound_pan(sound sound, float pan); void set_sound_pan(sound sound, float pan);
// Audio stream management
audio_stream load_audio_stream(unsigned int sample_rate, unsigned int sample_size, unsigned int channels); audio_stream load_audio_stream(unsigned int sample_rate, unsigned int sample_size, unsigned int channels);
void unload_audio_stream(audio_stream stream); void unload_audio_stream(audio_stream stream);
void play_audio_stream(audio_stream stream); void play_audio_stream(audio_stream stream);
@@ -166,7 +152,6 @@ void set_audio_stream_pitch(audio_stream stream, float pitch);
void set_audio_stream_pan(audio_stream stream, float pan); void set_audio_stream_pan(audio_stream stream, float pan);
void update_audio_stream(audio_stream stream, const void *data, int frame_count); void update_audio_stream(audio_stream stream, const void *data, int frame_count);
// Music management
music load_music_stream(const char* filename); music load_music_stream(const char* filename);
bool is_music_valid(music music); bool is_music_valid(music music);
void unload_music_stream(music music); void unload_music_stream(music music);
@@ -183,7 +168,6 @@ void set_music_pan(music music, float pan);
float get_music_time_length(music music); float get_music_time_length(music music);
float get_music_time_played(music music); float get_music_time_played(music music);
// Internal callback
static int port_audio_callback(const void *inputBuffer, void *outputBuffer, static int port_audio_callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo, const PaStreamCallbackTimeInfo* timeInfo,
@@ -195,7 +179,6 @@ static AudioData AUDIO = {
.System.masterVolume = 1.0f .System.masterVolume = 1.0f
}; };
// PortAudio callback implementation
static int port_audio_callback(const void *inputBuffer, void *outputBuffer, static int port_audio_callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo, const PaStreamCallbackTimeInfo* timeInfo,
@@ -234,7 +217,6 @@ static int port_audio_callback(const void *inputBuffer, void *outputBuffer,
if (audio_buffer->isSubBufferProcessed[currentSubBufferIndex]) { if (audio_buffer->isSubBufferProcessed[currentSubBufferIndex]) {
// This part of the buffer is not ready, output silence // This part of the buffer is not ready, output silence
} else { } else {
// Mix the audio data
for (unsigned long i = 0; i < framesThisPass; i++) { for (unsigned long i = 0; i < framesThisPass; i++) {
unsigned long buffer_pos = ((audio_buffer->frameCursorPos + i) % audio_buffer->sizeInFrames) * AUDIO_DEVICE_CHANNELS; unsigned long buffer_pos = ((audio_buffer->frameCursorPos + i) % audio_buffer->sizeInFrames) * AUDIO_DEVICE_CHANNELS;
unsigned long output_pos = (framesPerBuffer - framesToMix + i) * AUDIO_DEVICE_CHANNELS; unsigned long output_pos = (framesPerBuffer - framesToMix + i) * AUDIO_DEVICE_CHANNELS;
@@ -255,20 +237,15 @@ static int port_audio_callback(const void *inputBuffer, void *outputBuffer,
audio_buffer->isSubBufferProcessed[currentSubBufferIndex] = true; audio_buffer->isSubBufferProcessed[currentSubBufferIndex] = true;
} }
// FIXED: Only stop non-streaming buffers when they reach the end
if (!audio_buffer->isStreaming && audio_buffer->frameCursorPos >= audio_buffer->sizeInFrames) { if (!audio_buffer->isStreaming && audio_buffer->frameCursorPos >= audio_buffer->sizeInFrames) {
audio_buffer->playing = false; audio_buffer->playing = false;
break; break;
} }
// For streaming buffers, frameCursorPos can exceed sizeInFrames and that's OK
// The modulo operation handles the circular buffer access
} }
} }
audio_buffer = audio_buffer->next; audio_buffer = audio_buffer->next;
} }
// Apply master volume
for (unsigned long i = 0; i < framesPerBuffer * AUDIO_DEVICE_CHANNELS; i++) { for (unsigned long i = 0; i < framesPerBuffer * AUDIO_DEVICE_CHANNELS; i++) {
out[i] *= AUDIO.System.masterVolume; out[i] *= AUDIO.System.masterVolume;
} }
@@ -302,12 +279,10 @@ PaDeviceIndex get_best_output_device_for_host_api(PaHostApiIndex hostApi)
return paNoDevice; return paNoDevice;
} }
// First try the default output device for this host API
if (hostApiInfo->defaultOutputDevice != paNoDevice) { if (hostApiInfo->defaultOutputDevice != paNoDevice) {
return hostApiInfo->defaultOutputDevice; return hostApiInfo->defaultOutputDevice;
} }
// If no default, find the first available output device for this host API
for (int i = 0; i < hostApiInfo->deviceCount; i++) { for (int i = 0; i < hostApiInfo->deviceCount; i++) {
PaDeviceIndex deviceIndex = Pa_HostApiDeviceIndexToDeviceIndex(hostApi, i); PaDeviceIndex deviceIndex = Pa_HostApiDeviceIndexToDeviceIndex(hostApi, i);
if (deviceIndex >= 0) { if (deviceIndex >= 0) {
@@ -321,24 +296,20 @@ PaDeviceIndex get_best_output_device_for_host_api(PaHostApiIndex hostApi)
return paNoDevice; return paNoDevice;
} }
// Device management implementations
void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned long buffer_size) void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned long buffer_size)
{ {
// Initialize PortAudio
PaError err = Pa_Initialize(); PaError err = Pa_Initialize();
if (err != paNoError) { if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize PortAudio: %s", Pa_GetErrorText(err)); TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize PortAudio: %s", Pa_GetErrorText(err));
return; return;
} }
// Initialize mutex for thread synchronization
if (pthread_mutex_init(&AUDIO.System.lock, NULL) != 0) { if (pthread_mutex_init(&AUDIO.System.lock, NULL) != 0) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing");
Pa_Terminate(); Pa_Terminate();
return; return;
} }
// Set up output parameters
AUDIO.System.outputParameters.device = get_best_output_device_for_host_api(host_api); AUDIO.System.outputParameters.device = get_best_output_device_for_host_api(host_api);
if (AUDIO.System.outputParameters.device == paNoDevice) { if (AUDIO.System.outputParameters.device == paNoDevice) {
TRACELOG(LOG_WARNING, "AUDIO: No usable output device found"); TRACELOG(LOG_WARNING, "AUDIO: No usable output device found");
@@ -353,12 +324,11 @@ void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned lon
AUDIO.System.outputParameters.hostApiSpecificStreamInfo = NULL; AUDIO.System.outputParameters.hostApiSpecificStreamInfo = NULL;
AUDIO.System.sampleRate = sample_rate; AUDIO.System.sampleRate = sample_rate;
// Open the audio stream
err = Pa_OpenStream(&AUDIO.System.stream, err = Pa_OpenStream(&AUDIO.System.stream,
NULL, // No input NULL, // No input
&AUDIO.System.outputParameters, // Output parameters &AUDIO.System.outputParameters, // Output parameters
sample_rate, // Sample rate sample_rate, // Sample rate
buffer_size, // Frames per buffer (let PortAudio decide) buffer_size, // Frames per buffer
paClipOff, // No clipping paClipOff, // No clipping
port_audio_callback, // Callback function port_audio_callback, // Callback function
NULL); // User data NULL); // User data
@@ -370,7 +340,6 @@ void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned lon
return; return;
} }
// Start the audio stream
err = Pa_StartStream(AUDIO.System.stream); err = Pa_StartStream(AUDIO.System.stream);
if (err != paNoError) { if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to start audio stream: %s", Pa_GetErrorText(err)); TRACELOG(LOG_WARNING, "AUDIO: Failed to start audio stream: %s", Pa_GetErrorText(err));
@@ -382,7 +351,6 @@ void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned lon
AUDIO.System.isReady = true; AUDIO.System.isReady = true;
// Log device information
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device); const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device);
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi); const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
@@ -398,19 +366,16 @@ void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned lon
void close_audio_device(void) void close_audio_device(void)
{ {
if (AUDIO.System.isReady) { if (AUDIO.System.isReady) {
// Stop the stream
PaError err = Pa_StopStream(AUDIO.System.stream); PaError err = Pa_StopStream(AUDIO.System.stream);
if (err != paNoError) { if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Error stopping stream: %s", Pa_GetErrorText(err)); TRACELOG(LOG_WARNING, "AUDIO: Error stopping stream: %s", Pa_GetErrorText(err));
} }
// Close the stream
err = Pa_CloseStream(AUDIO.System.stream); err = Pa_CloseStream(AUDIO.System.stream);
if (err != paNoError) { if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Error closing stream: %s", Pa_GetErrorText(err)); TRACELOG(LOG_WARNING, "AUDIO: Error closing stream: %s", Pa_GetErrorText(err));
} }
// Cleanup
pthread_mutex_destroy(&AUDIO.System.lock); pthread_mutex_destroy(&AUDIO.System.lock);
Pa_Terminate(); Pa_Terminate();
@@ -446,7 +411,6 @@ float get_master_volume(void)
return volume; return volume;
} }
// Audio buffer management implementations
struct audio_buffer *load_audio_buffer(uint32_t channels, uint32_t size_in_frames, int usage) struct audio_buffer *load_audio_buffer(uint32_t channels, uint32_t size_in_frames, int usage)
{ {
struct audio_buffer *buffer = (struct audio_buffer*)calloc(1, sizeof(struct audio_buffer)); struct audio_buffer *buffer = (struct audio_buffer*)calloc(1, sizeof(struct audio_buffer));
@@ -479,9 +443,7 @@ struct audio_buffer *load_audio_buffer(uint32_t channels, uint32_t size_in_frame
buffer->isSubBufferProcessed[1] = true; buffer->isSubBufferProcessed[1] = true;
} }
// Set streaming flag based on usage parameter buffer->isStreaming = (usage == 1); //1 means streaming
// You can define constants like: #define AUDIO_BUFFER_USAGE_STATIC 0, #define AUDIO_BUFFER_USAGE_STREAM 1
buffer->isStreaming = (usage == 1); // Assuming 1 means streaming
track_audio_buffer(buffer); track_audio_buffer(buffer);
@@ -606,7 +568,6 @@ void untrack_audio_buffer(struct audio_buffer* buffer) {
pthread_mutex_unlock(&AUDIO.System.lock); pthread_mutex_unlock(&AUDIO.System.lock);
} }
// Wave management implementations
wave load_wave(const char* filename) { wave load_wave(const char* filename) {
wave wave = { 0 }; wave wave = { 0 };
SNDFILE *snd_file; SNDFILE *snd_file;
@@ -650,7 +611,6 @@ void unload_wave(wave wave) {
FREE(wave.data); FREE(wave.data);
} }
// Sound management implementations
sound load_sound_from_wave(wave wave) { sound load_sound_from_wave(wave wave) {
sound sound = { 0 }; sound sound = { 0 };
if (wave.data == NULL) return sound; if (wave.data == NULL) return sound;
@@ -781,7 +741,6 @@ void set_sound_pan(sound sound, float pan) {
set_audio_buffer_pan(sound.stream.buffer, pan); set_audio_buffer_pan(sound.stream.buffer, pan);
} }
// Audio stream management implementations
audio_stream load_audio_stream(unsigned int sample_rate, unsigned int sample_size, unsigned int channels) audio_stream load_audio_stream(unsigned int sample_rate, unsigned int sample_size, unsigned int channels)
{ {
audio_stream stream = { 0 }; audio_stream stream = { 0 };
@@ -790,7 +749,6 @@ audio_stream load_audio_stream(unsigned int sample_rate, unsigned int sample_siz
stream.sampleSize = sample_size; stream.sampleSize = sample_size;
stream.channels = channels; stream.channels = channels;
// Pass 1 to indicate this is a streaming buffer
stream.buffer = load_audio_buffer(AUDIO_DEVICE_CHANNELS, AUDIO.System.sampleRate, 1); stream.buffer = load_audio_buffer(AUDIO_DEVICE_CHANNELS, AUDIO.System.sampleRate, 1);
return stream; return stream;
} }
@@ -846,7 +804,6 @@ void update_audio_stream(audio_stream stream, const void *data, int frame_count)
pthread_mutex_lock(&AUDIO.System.lock); pthread_mutex_lock(&AUDIO.System.lock);
// For streaming, we directly update the buffer data
if (stream.buffer->data != NULL) { if (stream.buffer->data != NULL) {
float *buffer_data = (float *)stream.buffer->data; float *buffer_data = (float *)stream.buffer->data;
const float *input_data = (const float *)data; const float *input_data = (const float *)data;
@@ -858,18 +815,14 @@ void update_audio_stream(audio_stream stream, const void *data, int frame_count)
samples_to_copy = max_samples; samples_to_copy = max_samples;
} }
// Copy the data to the buffer
memcpy(buffer_data, input_data, samples_to_copy * sizeof(float)); memcpy(buffer_data, input_data, samples_to_copy * sizeof(float));
// Update the buffer size to match actual data
stream.buffer->sizeInFrames = frame_count; stream.buffer->sizeInFrames = frame_count;
// Don't reset cursor - let the callback manage it
} }
pthread_mutex_unlock(&AUDIO.System.lock); pthread_mutex_unlock(&AUDIO.System.lock);
} }
// Music management implementations
music load_music_stream(const char* filename) { music load_music_stream(const char* filename) {
music music = { 0 }; music music = { 0 };
bool music_loaded = false; bool music_loaded = false;
@@ -902,7 +855,7 @@ music load_music_stream(const char* filename) {
} }
music.ctxData = ctx; music.ctxData = ctx;
int sample_size = 32; // We will work with floats internally int sample_size = 32;
music.stream = load_audio_stream(AUDIO.System.sampleRate, sample_size, sf_info.channels); music.stream = load_audio_stream(AUDIO.System.sampleRate, sample_size, sf_info.channels);
music.frameCount = (unsigned int)(sf_info.frames * ctx->src_ratio); music.frameCount = (unsigned int)(sf_info.frames * ctx->src_ratio);
music_loaded = true; music_loaded = true;
@@ -964,10 +917,8 @@ void seek_music_stream(music music, float position) {
music_ctx *ctx = (music_ctx *)music.ctxData; music_ctx *ctx = (music_ctx *)music.ctxData;
SNDFILE *sndFile = ctx->snd_file; SNDFILE *sndFile = ctx->snd_file;
// Position is in output samples, so we need to convert back to input samples for seeking
unsigned int position_in_frames = (unsigned int)(position * music.stream.sampleRate / ctx->src_ratio); unsigned int position_in_frames = (unsigned int)(position * music.stream.sampleRate / ctx->src_ratio);
// Seek the file to the new position
sf_count_t seek_result = sf_seek(sndFile, position_in_frames, SEEK_SET); sf_count_t seek_result = sf_seek(sndFile, position_in_frames, SEEK_SET);
if (seek_result < 0) return; // Seek failed if (seek_result < 0) return; // Seek failed