//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 #include #include #include #include #include #include #include #include // 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 #define AUDIO_DEVICE_SAMPLE_RATE 44100 // Device output sample rate // 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 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); 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) { // 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; // Open the audio stream err = Pa_OpenStream(&AUDIO.System.stream, NULL, // No input &AUDIO.System.outputParameters, // Output parameters AUDIO_DEVICE_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: %d", AUDIO_DEVICE_SAMPLE_RATE); 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_DEVICE_SAMPLE_RATE) { TRACELOG(LOG_INFO, "AUDIO: Resampling wave from %d Hz to %d Hz", wave.sampleRate, AUDIO_DEVICE_SAMPLE_RATE); SRC_DATA src_data; src_data.data_in = wave.data; src_data.input_frames = wave.frameCount; src_data.src_ratio = (double)AUDIO_DEVICE_SAMPLE_RATE / 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_DEVICE_SAMPLE_RATE; 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_DEVICE_SAMPLE_RATE, 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_DEVICE_SAMPLE_RATE) { TRACELOG(LOG_INFO, "AUDIO: Resampling music from %d Hz to %d Hz", sf_info.samplerate, AUDIO_DEVICE_SAMPLE_RATE); 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 = (double)AUDIO_DEVICE_SAMPLE_RATE / 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_DEVICE_SAMPLE_RATE, 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; }