Files
PyTaiko/libs/audio/audio.c
Anthony Samms 02dc2fac95 minor fixes
2025-09-16 00:26:06 -04:00

1087 lines
38 KiB
C

//WARNING: This is ported code from raylib's rAudio. It's also been mangled
// 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
#include "portaudio.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sndfile.h>
#include <samplerate.h>
#include <string.h>
#include <unistd.h>
// Logging macros
#define LOG_INFO 0
#define LOG_WARNING 1
#define LOG_ERROR 2
#define TRACELOG(level, ...) do { \
const char* level_str = (level == LOG_INFO) ? "INFO" : \
(level == LOG_WARNING) ? "WARNING" : "ERROR"; \
printf("[%s] AUDIO: ", level_str); \
printf(__VA_ARGS__); \
printf("\n"); \
fflush(stdout); \
} while(0)
// Memory management macros
#define FREE(ptr) do { if (ptr) { free(ptr); (ptr) = NULL; } } while(0)
#define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo
// Forward declarations of structures
struct audio_buffer;
// Type definitions
typedef struct wave {
unsigned int frameCount; // Total number of frames (considering channels)
unsigned int sampleRate; // Frequency (samples per second)
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
void *data; // Buffer data pointer
} wave;
// AudioStream, custom audio stream
typedef struct audio_stream {
struct audio_buffer *buffer; // Pointer to internal data used by the audio system
unsigned int sampleRate; // Frequency (samples per second)
unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported)
unsigned int channels; // Number of channels (1-mono, 2-stereo, ...)
} audio_stream;
// Sound
typedef struct sound {
audio_stream stream; // Audio stream
unsigned int frameCount; // Total number of frames (considering channels)
} sound;
// Music, audio stream, anything longer than ~10 seconds should be streamed
typedef struct music {
audio_stream stream; // Audio stream
unsigned int frameCount; // Total number of frames (considering channels)
void *ctxData;
} music;
// Music context data, required for music streaming
typedef struct music_ctx {
SNDFILE *snd_file;
SRC_STATE *resampler;
double src_ratio;
} music_ctx;
// Audio buffer structure
struct audio_buffer {
float volume; // Audio buffer volume
float pitch; // Audio buffer pitch
float pan; // Audio buffer pan (0.0f to 1.0f)
bool playing; // Audio buffer state: AUDIO_PLAYING
bool paused;
bool isStreaming; // Audio buffer state: AUDIO_PAUSED
bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer)
unsigned int sizeInFrames; // Total buffer size in frames
unsigned int frameCursorPos; // Frame cursor position
unsigned int framesProcessed; // Total frames processed in this buffer (required for play timing)
unsigned char *data; // Data buffer, on music stream keeps filling
struct audio_buffer *next; // Next audio buffer on the list
struct audio_buffer *prev; // Previous audio buffer on the list
};
// Audio data context
typedef struct AudioData {
struct {
PaStream *stream; // PortAudio stream
PaStreamParameters outputParameters; // Output stream parameters
pthread_mutex_t lock; // Mutex lock for thread synchronization
bool isReady; // Check if audio device is ready
double sampleRate;
size_t pcmBufferSize; // Pre-allocated buffer size
void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory
float masterVolume; // Master volume control
} System;
struct {
struct audio_buffer *first; // Pointer to first audio_buffer in the list
struct audio_buffer *last; // Pointer to last audio_buffer in the list
} Buffer;
} AudioData;
// Function declarations
// Device management
void list_host_apis(void);
void init_audio_device(PaHostApiIndex host_api, double sample_rate);
void close_audio_device(void);
bool is_audio_device_ready(void);
void set_master_volume(float volume);
float get_master_volume(void);
// Audio buffer management
struct audio_buffer *load_audio_buffer(uint32_t channels, uint32_t size_in_frames, int usage);
void unload_audio_buffer(struct audio_buffer *buffer);
bool is_audio_buffer_playing(struct audio_buffer *buffer);
void play_audio_buffer(struct audio_buffer *buffer);
void stop_audio_buffer(struct audio_buffer *buffer);
void pause_audio_buffer(struct audio_buffer *buffer);
void resume_audio_buffer(struct audio_buffer *buffer);
void set_audio_buffer_volume(struct audio_buffer *buffer, float volume);
void set_audio_buffer_pitch(struct audio_buffer *buffer, float pitch);
void set_audio_buffer_pan(struct audio_buffer *buffer, float pan);
void track_audio_buffer(struct audio_buffer *buffer);
void untrack_audio_buffer(struct audio_buffer *buffer);
// Wave management
wave load_wave(const char* filename);
bool is_wave_valid(wave wave);
void unload_wave(wave wave);
// Sound management
sound load_sound_from_wave(wave wave);
sound load_sound(const char* filename);
bool is_sound_valid(sound sound);
void unload_sound(sound sound);
void play_sound(sound sound);
void pause_sound(sound sound);
void resume_sound(sound sound);
void stop_sound(sound sound);
bool is_sound_playing(sound sound);
void set_sound_volume(sound sound, float volume);
void set_sound_pitch(sound sound, float pitch);
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);
void unload_audio_stream(audio_stream stream);
void play_audio_stream(audio_stream stream);
void pause_audio_stream(audio_stream stream);
void resume_audio_stream(audio_stream stream);
bool is_audio_stream_playing(audio_stream stream);
void stop_audio_stream(audio_stream stream);
void set_audio_stream_volume(audio_stream stream, float volume);
void set_audio_stream_pitch(audio_stream stream, float pitch);
void set_audio_stream_pan(audio_stream stream, float pan);
void update_audio_stream(audio_stream stream, const void *data, int frame_count);
// Music management
music load_music_stream(const char* filename);
bool is_music_valid(music music);
void unload_music_stream(music music);
void play_music_stream(music music);
void pause_music_stream(music music);
void resume_music_stream(music music);
void stop_music_stream(music music);
void seek_music_stream(music music, float position);
void update_music_stream(music music);
bool is_music_stream_playing(music music);
void set_music_volume(music music, float volume);
void set_music_pitch(music music, float pitch);
void set_music_pan(music music, float pan);
float get_music_time_length(music music);
float get_music_time_played(music music);
// Internal callback
static int port_audio_callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData);
// Global audio data
static AudioData AUDIO = {
.System.masterVolume = 1.0f
};
// PortAudio callback implementation
static int port_audio_callback(const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
(void) inputBuffer;
(void) timeInfo;
(void) statusFlags;
(void) userData;
float *out = (float*)outputBuffer;
pthread_mutex_lock(&AUDIO.System.lock);
// Initialize output buffer with silence
for (unsigned long i = 0; i < framesPerBuffer * AUDIO_DEVICE_CHANNELS; i++) {
out[i] = 0.0f;
}
struct audio_buffer *audio_buffer = AUDIO.Buffer.first;
int active_buffers = 0;
while (audio_buffer != NULL) {
if (audio_buffer->playing && !audio_buffer->paused && audio_buffer->data != NULL) {
active_buffers++;
unsigned int subBufferSizeFrames = audio_buffer->sizeInFrames / 2;
unsigned long framesToMix = framesPerBuffer;
float *buffer_data = (float *)audio_buffer->data;
while (framesToMix > 0) {
unsigned int currentSubBufferIndex = (audio_buffer->frameCursorPos / subBufferSizeFrames) % 2;
unsigned int frameOffsetInSubBuffer = audio_buffer->frameCursorPos % subBufferSizeFrames;
unsigned int framesLeftInSubBuffer = subBufferSizeFrames - frameOffsetInSubBuffer;
unsigned int framesThisPass = (framesToMix < framesLeftInSubBuffer) ? framesToMix : framesLeftInSubBuffer;
if (audio_buffer->isSubBufferProcessed[currentSubBufferIndex]) {
// This part of the buffer is not ready, output silence
} else {
// Mix the audio data
for (unsigned long i = 0; i < framesThisPass; i++) {
unsigned long buffer_pos = ((audio_buffer->frameCursorPos + i) % audio_buffer->sizeInFrames) * AUDIO_DEVICE_CHANNELS;
unsigned long output_pos = (framesPerBuffer - framesToMix + i) * AUDIO_DEVICE_CHANNELS;
for (int ch = 0; ch < AUDIO_DEVICE_CHANNELS; ch++) {
float sample = buffer_data[buffer_pos + ch] * audio_buffer->volume;
out[output_pos + ch] += sample;
}
}
}
audio_buffer->frameCursorPos += framesThisPass;
audio_buffer->framesProcessed += framesThisPass;
framesToMix -= framesThisPass;
unsigned int newSubBufferIndex = (audio_buffer->frameCursorPos / subBufferSizeFrames) % 2;
if (newSubBufferIndex != currentSubBufferIndex) {
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) {
audio_buffer->playing = false;
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;
}
// Apply master volume
for (unsigned long i = 0; i < framesPerBuffer * AUDIO_DEVICE_CHANNELS; i++) {
out[i] *= AUDIO.System.masterVolume;
}
pthread_mutex_unlock(&AUDIO.System.lock);
return paContinue;
}
void list_host_apis(void)
{
PaHostApiIndex hostApiCount = Pa_GetHostApiCount();
if (hostApiCount < 0) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to get host API count: %s", Pa_GetErrorText(hostApiCount));
return;
}
TRACELOG(LOG_INFO, "AUDIO: Available host APIs:");
for (PaHostApiIndex i = 0; i < hostApiCount; i++) {
const PaHostApiInfo *info = Pa_GetHostApiInfo(i);
if (info) {
TRACELOG(LOG_INFO, " [%d] %s (%d devices)", i, info->name, info->deviceCount);
}
}
}
PaDeviceIndex get_best_output_device_for_host_api(PaHostApiIndex hostApi)
{
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(hostApi);
if (!hostApiInfo) {
return paNoDevice;
}
// First try the default output device for this host API
if (hostApiInfo->defaultOutputDevice != paNoDevice) {
return hostApiInfo->defaultOutputDevice;
}
// If no default, find the first available output device for this host API
for (int i = 0; i < hostApiInfo->deviceCount; i++) {
PaDeviceIndex deviceIndex = Pa_HostApiDeviceIndexToDeviceIndex(hostApi, i);
if (deviceIndex >= 0) {
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(deviceIndex);
if (deviceInfo && deviceInfo->maxOutputChannels > 0) {
return deviceIndex;
}
}
}
return paNoDevice;
}
// Device management implementations
void init_audio_device(PaHostApiIndex host_api, double sample_rate)
{
// Initialize PortAudio
PaError err = Pa_Initialize();
if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize PortAudio: %s", Pa_GetErrorText(err));
return;
}
// Initialize mutex for thread synchronization
if (pthread_mutex_init(&AUDIO.System.lock, NULL) != 0) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing");
Pa_Terminate();
return;
}
// Set up output parameters
AUDIO.System.outputParameters.device = get_best_output_device_for_host_api(host_api);
if (AUDIO.System.outputParameters.device == paNoDevice) {
TRACELOG(LOG_WARNING, "AUDIO: No usable output device found");
pthread_mutex_destroy(&AUDIO.System.lock);
Pa_Terminate();
return;
}
AUDIO.System.outputParameters.channelCount = AUDIO_DEVICE_CHANNELS;
AUDIO.System.outputParameters.sampleFormat = paFloat32; // Using float format like miniaudio version
AUDIO.System.outputParameters.suggestedLatency = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device)->defaultLowOutputLatency;
AUDIO.System.outputParameters.hostApiSpecificStreamInfo = NULL;
AUDIO.System.sampleRate = sample_rate;
// Open the audio stream
err = Pa_OpenStream(&AUDIO.System.stream,
NULL, // No input
&AUDIO.System.outputParameters, // Output parameters
sample_rate, // Sample rate
paFramesPerBufferUnspecified, // Frames per buffer (let PortAudio decide)
paClipOff, // No clipping
port_audio_callback, // Callback function
NULL); // User data
if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to open audio stream: %s", Pa_GetErrorText(err));
pthread_mutex_destroy(&AUDIO.System.lock);
Pa_Terminate();
return;
}
// Start the audio stream
err = Pa_StartStream(AUDIO.System.stream);
if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to start audio stream: %s", Pa_GetErrorText(err));
Pa_CloseStream(AUDIO.System.stream);
pthread_mutex_destroy(&AUDIO.System.lock);
Pa_Terminate();
return;
}
AUDIO.System.isReady = true;
// Log device information
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device);
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully");
TRACELOG(LOG_INFO, " > Backend: PortAudio | %s", hostApiInfo->name);
TRACELOG(LOG_INFO, " > Device: %s", deviceInfo->name);
TRACELOG(LOG_INFO, " > Format: %s", "Float32");
TRACELOG(LOG_INFO, " > Channels: %d", AUDIO_DEVICE_CHANNELS);
TRACELOG(LOG_INFO, " > Sample rate: %f", AUDIO.System.sampleRate);
TRACELOG(LOG_INFO, " > Latency: %f ms", AUDIO.System.outputParameters.suggestedLatency * 1000.0);
}
void close_audio_device(void)
{
if (AUDIO.System.isReady) {
// Stop the stream
PaError err = Pa_StopStream(AUDIO.System.stream);
if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Error stopping stream: %s", Pa_GetErrorText(err));
}
// Close the stream
err = Pa_CloseStream(AUDIO.System.stream);
if (err != paNoError) {
TRACELOG(LOG_WARNING, "AUDIO: Error closing stream: %s", Pa_GetErrorText(err));
}
// Cleanup
pthread_mutex_destroy(&AUDIO.System.lock);
Pa_Terminate();
AUDIO.System.isReady = false;
FREE(AUDIO.System.pcmBuffer);
AUDIO.System.pcmBuffer = NULL;
AUDIO.System.pcmBufferSize = 0;
TRACELOG(LOG_INFO, "AUDIO: Device closed successfully");
}
else {
TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized");
}
}
bool is_audio_device_ready(void)
{
return AUDIO.System.isReady;
}
void set_master_volume(float volume)
{
pthread_mutex_lock(&AUDIO.System.lock);
AUDIO.System.masterVolume = volume;
pthread_mutex_unlock(&AUDIO.System.lock);
}
float get_master_volume(void)
{
pthread_mutex_lock(&AUDIO.System.lock);
float volume = AUDIO.System.masterVolume;
pthread_mutex_unlock(&AUDIO.System.lock);
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 *buffer = (struct audio_buffer*)calloc(1, sizeof(struct audio_buffer));
if (buffer == NULL) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer");
return NULL;
}
buffer->data = calloc(size_in_frames*channels*sizeof(float), 1);
if (buffer->data == NULL) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer data");
FREE(buffer);
return NULL;
}
buffer->volume = 1.0f;
buffer->pitch = 1.0f;
buffer->pan = 0.5f;
buffer->playing = false;
buffer->paused = false;
buffer->frameCursorPos = 0;
buffer->framesProcessed = 0;
buffer->sizeInFrames = size_in_frames;
if (usage == 0) { // Static buffer
buffer->isSubBufferProcessed[0] = false;
buffer->isSubBufferProcessed[1] = false;
} else { // Streaming buffer
buffer->isSubBufferProcessed[0] = true;
buffer->isSubBufferProcessed[1] = true;
}
// Set streaming flag based on usage parameter
// 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);
return buffer;
}
void unload_audio_buffer(struct audio_buffer *buffer)
{
if (buffer == NULL) return;
untrack_audio_buffer(buffer);
FREE(buffer->data);
FREE(buffer);
}
bool is_audio_buffer_playing(struct audio_buffer *buffer)
{
if (buffer == NULL) return false;
pthread_mutex_lock(&AUDIO.System.lock);
bool result = (buffer->playing && !buffer->paused);
pthread_mutex_unlock(&AUDIO.System.lock);
return result;
}
void play_audio_buffer(struct audio_buffer *buffer) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->playing = true;
buffer->paused = false;
buffer->frameCursorPos = 0;
buffer->framesProcessed = 0;
if (!buffer->isStreaming) {
buffer->isSubBufferProcessed[0] = false;
buffer->isSubBufferProcessed[1] = false;
}
pthread_mutex_unlock(&AUDIO.System.lock);
}
void stop_audio_buffer(struct audio_buffer* buffer) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->playing = false;
buffer->paused = false;
buffer->frameCursorPos = 0;
buffer->framesProcessed = 0;
buffer->isSubBufferProcessed[0] = true;
buffer->isSubBufferProcessed[1] = true;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void pause_audio_buffer(struct audio_buffer* buffer) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->paused = true;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void resume_audio_buffer(struct audio_buffer* buffer) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->paused = false;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void set_audio_buffer_volume(struct audio_buffer* buffer, float volume) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->volume = volume;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void set_audio_buffer_pitch(struct audio_buffer* buffer, float pitch) {
if ((buffer == NULL) || (pitch < 0.0f)) return;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->pitch = pitch;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void set_audio_buffer_pan(struct audio_buffer* buffer, float pan) {
if (buffer == NULL) return;
if (pan < 0.0f) pan = 0.0f;
else if (pan > 1.0f) pan = 1.0f;
pthread_mutex_lock(&AUDIO.System.lock);
buffer->pan = pan;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void track_audio_buffer(struct audio_buffer* buffer) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer;
else {
AUDIO.Buffer.last->next = buffer;
buffer->prev = AUDIO.Buffer.last;
}
AUDIO.Buffer.last = buffer;
pthread_mutex_unlock(&AUDIO.System.lock);
}
void untrack_audio_buffer(struct audio_buffer* buffer) {
if (buffer == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next;
else buffer->prev->next = buffer->next;
if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev;
else buffer->next->prev = buffer->prev;
buffer->prev = NULL;
buffer->next = NULL;
pthread_mutex_unlock(&AUDIO.System.lock);
}
// Wave management implementations
wave load_wave(const char* filename) {
wave wave = { 0 };
SNDFILE *snd_file;
SF_INFO sf_info;
memset(&sf_info, 0, sizeof(sf_info));
snd_file = sf_open(filename, SFM_READ, &sf_info);
if (snd_file == NULL) {
TRACELOG(LOG_ERROR, "AUDIO: Failed to open file '%s'\n", filename);
return wave;
}
wave.frameCount = (unsigned int)sf_info.frames;
wave.sampleRate = (unsigned int)sf_info.samplerate;
wave.channels = (unsigned int)sf_info.channels;
wave.sampleSize = 32; // Using 32-bit float samples
size_t total_samples = sf_info.frames * sf_info.channels;
wave.data = malloc(total_samples * sizeof(float));
if (wave.data == NULL) {
TRACELOG(LOG_ERROR, "AUDIO: Failed to allocate memory for wave data");
sf_close(snd_file);
return wave;
}
sf_readf_float(snd_file, wave.data, sf_info.frames);
sf_close(snd_file);
return wave;
}
bool is_wave_valid(wave wave) {
bool result = false;
if ((wave.data != NULL) && // Validate wave data available
(wave.frameCount > 0) && // Validate frame count
(wave.sampleRate > 0) && // Validate sample rate is supported
(wave.sampleSize > 0) && // Validate sample size is supported
(wave.channels > 0)) result = true; // Validate number of channels supported
return result;
}
void unload_wave(wave wave) {
FREE(wave.data);
}
// Sound management implementations
sound load_sound_from_wave(wave wave) {
sound sound = { 0 };
if (wave.data == NULL) return sound;
struct wave resampled_wave = { 0 };
bool is_resampled = false;
if (wave.sampleRate != AUDIO.System.sampleRate) {
TRACELOG(LOG_INFO, "AUDIO: Resampling wave from %d Hz to %f Hz", wave.sampleRate, AUDIO.System.sampleRate);
SRC_DATA src_data;
src_data.data_in = wave.data;
src_data.input_frames = wave.frameCount;
src_data.src_ratio = AUDIO.System.sampleRate / wave.sampleRate;
src_data.output_frames = (sf_count_t)(wave.frameCount * src_data.src_ratio);
resampled_wave.data = calloc(src_data.output_frames * wave.channels, sizeof(float));
if (resampled_wave.data == NULL) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for resampling");
return sound;
}
src_data.data_out = resampled_wave.data;
int error = src_simple(&src_data, SRC_SINC_BEST_QUALITY, wave.channels);
if (error) {
TRACELOG(LOG_WARNING, "AUDIO: Resampling failed: %s", src_strerror(error));
FREE(resampled_wave.data);
return sound;
}
resampled_wave.frameCount = src_data.output_frames_gen;
resampled_wave.sampleRate = AUDIO.System.sampleRate;
resampled_wave.channels = wave.channels;
resampled_wave.sampleSize = wave.sampleSize;
is_resampled = true;
}
struct wave *wave_to_load = is_resampled ? &resampled_wave : &wave;
struct audio_buffer *buffer = load_audio_buffer(AUDIO_DEVICE_CHANNELS, wave_to_load->frameCount, 0);
if (buffer != NULL && buffer->data != NULL) {
size_t samples_to_copy = wave_to_load->frameCount * wave_to_load->channels;
size_t buffer_samples = wave_to_load->frameCount * AUDIO_DEVICE_CHANNELS;
float *wave_data = (float *)wave_to_load->data;
float *buffer_data = (float *)buffer->data;
if (wave_to_load->channels == 1 && AUDIO_DEVICE_CHANNELS == 2) {
for (unsigned int i = 0; i < wave_to_load->frameCount; i++) {
buffer_data[i * 2] = wave_data[i]; // Left channel
buffer_data[i * 2 + 1] = wave_data[i]; // Right channel
}
} else if (wave_to_load->channels == 2 && AUDIO_DEVICE_CHANNELS == 2) {
memcpy(buffer_data, wave_data, samples_to_copy * sizeof(float));
} else {
size_t min_samples = (samples_to_copy < buffer_samples) ? samples_to_copy : buffer_samples;
memcpy(buffer_data, wave_data, min_samples * sizeof(float));
}
}
sound.frameCount = wave_to_load->frameCount;
sound.stream.sampleRate = wave_to_load->sampleRate;
sound.stream.sampleSize = wave_to_load->sampleSize;
sound.stream.channels = wave_to_load->channels;
sound.stream.buffer = buffer;
if (is_resampled) {
FREE(resampled_wave.data);
}
return sound;
}
sound load_sound(const char* filename) {
wave wave = load_wave(filename);
sound sound = load_sound_from_wave(wave);
unload_wave(wave);
return sound;
}
bool is_sound_valid(sound sound) {
bool result = false;
if ((sound.stream.buffer != NULL) && // Validate wave data available
(sound.frameCount > 0) && // Validate frame count
(sound.stream.sampleRate > 0) && // Validate sample rate is supported
(sound.stream.sampleSize > 0) && // Validate sample size is supported
(sound.stream.channels > 0)) result = true; // Validate number of channels supported
return result;
}
void unload_sound(sound sound) {
unload_audio_buffer(sound.stream.buffer);
}
void play_sound(sound sound) {
play_audio_buffer(sound.stream.buffer);
}
void pause_sound(sound sound) {
pause_audio_buffer(sound.stream.buffer);
}
void resume_sound(sound sound) {
resume_audio_buffer(sound.stream.buffer);
}
void stop_sound(sound sound) {
stop_audio_buffer(sound.stream.buffer);
}
bool is_sound_playing(sound sound) {
return is_audio_buffer_playing(sound.stream.buffer);
}
void set_sound_volume(sound sound, float volume) {
set_audio_buffer_volume(sound.stream.buffer, volume);
}
void set_sound_pitch(sound sound, float pitch) {
set_audio_buffer_pitch(sound.stream.buffer, pitch);
}
void set_sound_pan(sound sound, float 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 stream = { 0 };
stream.sampleRate = sample_rate;
stream.sampleSize = sample_size;
stream.channels = channels;
// Pass 1 to indicate this is a streaming buffer
stream.buffer = load_audio_buffer(AUDIO_DEVICE_CHANNELS, AUDIO.System.sampleRate, 1);
return stream;
}
void unload_audio_stream(audio_stream stream)
{
unload_audio_buffer(stream.buffer);
}
void play_audio_stream(audio_stream stream)
{
play_audio_buffer(stream.buffer);
}
void pause_audio_stream(audio_stream stream)
{
pause_audio_buffer(stream.buffer);
}
void resume_audio_stream(audio_stream stream)
{
resume_audio_buffer(stream.buffer);
}
bool is_audio_stream_playing(audio_stream stream)
{
return is_audio_buffer_playing(stream.buffer);
}
void stop_audio_stream(audio_stream stream)
{
stop_audio_buffer(stream.buffer);
}
void set_audio_stream_volume(audio_stream stream, float volume)
{
set_audio_buffer_volume(stream.buffer, volume);
}
void set_audio_stream_pitch(audio_stream stream, float pitch)
{
set_audio_buffer_pitch(stream.buffer, pitch);
}
void set_audio_stream_pan(audio_stream stream, float pan)
{
set_audio_buffer_pan(stream.buffer, pan);
}
void update_audio_stream(audio_stream stream, const void *data, int frame_count)
{
if (stream.buffer == NULL || data == NULL) return;
pthread_mutex_lock(&AUDIO.System.lock);
// For streaming, we directly update the buffer data
if (stream.buffer->data != NULL) {
float *buffer_data = (float *)stream.buffer->data;
const float *input_data = (const float *)data;
unsigned int samples_to_copy = frame_count * AUDIO_DEVICE_CHANNELS;
unsigned int max_samples = stream.buffer->sizeInFrames * AUDIO_DEVICE_CHANNELS;
if (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));
// Update the buffer size to match actual data
stream.buffer->sizeInFrames = frame_count;
// Don't reset cursor - let the callback manage it
}
pthread_mutex_unlock(&AUDIO.System.lock);
}
// Music management implementations
music load_music_stream(const char* filename) {
music music = { 0 };
bool music_loaded = false;
SF_INFO sf_info = { 0 };
SNDFILE *snd_file = sf_open(filename, SFM_READ, &sf_info);
if (snd_file != NULL) {
music_ctx *ctx = calloc(1, sizeof(music_ctx));
if (ctx == NULL) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for music context");
sf_close(snd_file);
return music;
}
ctx->snd_file = snd_file;
if (sf_info.samplerate != AUDIO.System.sampleRate) {
TRACELOG(LOG_INFO, "AUDIO: Resampling music from %d Hz to %f Hz", sf_info.samplerate, AUDIO.System.sampleRate);
int error;
ctx->resampler = src_new(SRC_SINC_FASTEST, sf_info.channels, &error);
if (ctx->resampler == NULL) {
TRACELOG(LOG_WARNING, "AUDIO: Failed to create resampler: %s", src_strerror(error));
free(ctx);
sf_close(snd_file);
return music;
}
ctx->src_ratio = AUDIO.System.sampleRate / sf_info.samplerate;
} else {
ctx->resampler = NULL;
ctx->src_ratio = 1.0;
}
music.ctxData = ctx;
int sample_size = 32; // We will work with floats internally
music.stream = load_audio_stream(AUDIO.System.sampleRate, sample_size, sf_info.channels);
music.frameCount = (unsigned int)(sf_info.frames * ctx->src_ratio);
music_loaded = true;
}
if (!music_loaded)
{
TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", filename);
if (snd_file) sf_close(snd_file);
}
else
{
TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", filename);
TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate);
TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize);
TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels,
(music.stream.channels == 1) ? "Mono" :
(music.stream.channels == 2) ? "Stereo" : "Multi");
TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount);
}
return music;
}
bool is_music_valid(music music)
{
return ((music.frameCount > 0) && // Validate audio frame count
(music.stream.sampleRate > 0) && // Validate sample rate is supported
(music.stream.sampleSize > 0) && // Validate sample size is supported
(music.stream.channels > 0)); // Validate number of channels supported
}
void unload_music_stream(music music) {
if (music.ctxData) {
music_ctx *ctx = (music_ctx *)music.ctxData;
if (ctx->snd_file) sf_close(ctx->snd_file);
if (ctx->resampler) src_delete(ctx->resampler);
free(ctx);
}
unload_audio_stream(music.stream);
}
void play_music_stream(music music) {
play_audio_stream(music.stream);
}
void pause_music_stream(music music) {
pause_audio_stream(music.stream);
}
void resume_music_stream(music music) {
resume_audio_stream(music.stream);
}
void stop_music_stream(music music) {
stop_audio_stream(music.stream);
}
void seek_music_stream(music music, float position) {
if (music.stream.buffer == NULL || music.ctxData == NULL) return;
music_ctx *ctx = (music_ctx *)music.ctxData;
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);
// Seek the file to the new position
sf_count_t seek_result = sf_seek(sndFile, position_in_frames, SEEK_SET);
if (seek_result < 0) return; // Seek failed
pthread_mutex_lock(&AUDIO.System.lock);
music.stream.buffer->framesProcessed = position_in_frames;
music.stream.buffer->frameCursorPos = 0; // Reset cursor
music.stream.buffer->isSubBufferProcessed[0] = true; // Force reload
music.stream.buffer->isSubBufferProcessed[1] = true; // Force reload
pthread_mutex_unlock(&AUDIO.System.lock);
}
void update_music_stream(music music) {
if (music.stream.buffer == NULL || music.ctxData == NULL) return;
music_ctx *ctx = (music_ctx *)music.ctxData;
SNDFILE *sndFile = ctx->snd_file;
if (sndFile == NULL) return;
for (int i = 0; i < 2; i++) {
pthread_mutex_lock(&AUDIO.System.lock);
bool needs_refill = music.stream.buffer->isSubBufferProcessed[i];
pthread_mutex_unlock(&AUDIO.System.lock);
if (needs_refill) {
unsigned int subBufferSizeFrames = music.stream.buffer->sizeInFrames / 2;
unsigned int frames_to_read = subBufferSizeFrames;
if (ctx->resampler) {
frames_to_read = (unsigned int)(subBufferSizeFrames / ctx->src_ratio) + 1;
}
if (AUDIO.System.pcmBufferSize < frames_to_read * music.stream.channels * sizeof(float)) {
FREE(AUDIO.System.pcmBuffer);
AUDIO.System.pcmBuffer = calloc(1, frames_to_read * music.stream.channels * sizeof(float));
AUDIO.System.pcmBufferSize = frames_to_read * music.stream.channels * sizeof(float);
}
sf_count_t frames_read = sf_readf_float(sndFile, (float*)AUDIO.System.pcmBuffer, frames_to_read);
unsigned int subBufferOffset = i * subBufferSizeFrames * AUDIO_DEVICE_CHANNELS;
float *buffer_data = (float *)music.stream.buffer->data;
float *input_ptr = (float *)AUDIO.System.pcmBuffer;
sf_count_t frames_written = 0;
if (ctx->resampler) {
SRC_DATA src_data;
src_data.data_in = input_ptr;
src_data.input_frames = frames_read;
src_data.data_out = buffer_data + subBufferOffset;
src_data.output_frames = subBufferSizeFrames;
src_data.src_ratio = ctx->src_ratio;
src_data.end_of_input = (frames_read < frames_to_read);
int error = src_process(ctx->resampler, &src_data);
if (error) {
TRACELOG(LOG_WARNING, "AUDIO: Resampling failed: %s", src_strerror(error));
}
frames_written = src_data.output_frames_gen;
} else {
if (music.stream.channels == 1 && AUDIO_DEVICE_CHANNELS == 2) {
for (int j = 0; j < frames_read; j++) {
buffer_data[subBufferOffset + j*2] = input_ptr[j];
buffer_data[subBufferOffset + j*2 + 1] = input_ptr[j];
}
} else {
memcpy(buffer_data + subBufferOffset, input_ptr, frames_read * music.stream.channels * sizeof(float));
}
frames_written = frames_read;
}
if (frames_written < subBufferSizeFrames) {
unsigned int offset = subBufferOffset + (frames_written * AUDIO_DEVICE_CHANNELS);
unsigned int size = (subBufferSizeFrames - frames_written) * AUDIO_DEVICE_CHANNELS * sizeof(float);
memset(buffer_data + offset, 0, size);
}
pthread_mutex_lock(&AUDIO.System.lock);
music.stream.buffer->isSubBufferProcessed[i] = false;
pthread_mutex_unlock(&AUDIO.System.lock);
}
}
}
bool is_music_stream_playing(music music) {
return is_audio_stream_playing(music.stream);
}
void set_music_volume(music music, float volume) {
set_audio_stream_volume(music.stream, volume);
}
void set_music_pitch(music music, float pitch) {
set_audio_buffer_pitch(music.stream.buffer, pitch);
}
void set_music_pan(music music, float pan) {
set_audio_buffer_pan(music.stream.buffer, pan);
}
float get_music_time_length(music music) {
float total_seconds = 0.0f;
total_seconds = (float)music.frameCount/music.stream.sampleRate;
return total_seconds;
}
float get_music_time_played(music music) {
float seconds_played = 0.0f;
if (music.stream.buffer != NULL) {
pthread_mutex_lock(&AUDIO.System.lock);
seconds_played = (float)music.stream.buffer->framesProcessed / music.stream.sampleRate;
pthread_mutex_unlock(&AUDIO.System.lock);
}
return seconds_played;
}