mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 19:50:12 +01:00
1112 lines
39 KiB
C
1112 lines
39 KiB
C
|
|
// Thank you raysan for data structures
|
|
// 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"
|
|
#ifdef _WIN32
|
|
#include "pa_asio.h"
|
|
#endif
|
|
#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>
|
|
#include <math.h>
|
|
|
|
#define LOG_INFO 0
|
|
#define LOG_WARNING 1
|
|
#define LOG_ERROR 2
|
|
|
|
static int CURRENT_LOG_LEVEL = LOG_INFO;
|
|
|
|
void set_log_level(int level) {
|
|
CURRENT_LOG_LEVEL = level;
|
|
}
|
|
|
|
#define TRACELOG(level, ...) do { \
|
|
if (level >= CURRENT_LOG_LEVEL) { \
|
|
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)
|
|
|
|
#define FREE(ptr) do { if (ptr) { free(ptr); (ptr) = NULL; } } while(0)
|
|
|
|
#define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo
|
|
|
|
struct audio_buffer;
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
typedef struct sound {
|
|
audio_stream stream; // Audio stream
|
|
unsigned int frameCount; // Total number of frames (considering channels)
|
|
} sound;
|
|
|
|
//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;
|
|
|
|
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
|
|
};
|
|
|
|
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;
|
|
|
|
void list_host_apis(void);
|
|
const char* get_host_api_name(PaHostApiIndex hostApi);
|
|
void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned long buffer_size);
|
|
void close_audio_device(void);
|
|
bool is_audio_device_ready(void);
|
|
void set_master_volume(float volume);
|
|
float get_master_volume(void);
|
|
|
|
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 load_wave(const char* filename);
|
|
bool is_wave_valid(wave wave);
|
|
void unload_wave(wave wave);
|
|
|
|
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 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 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);
|
|
|
|
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
|
|
};
|
|
|
|
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 {
|
|
// Calculate pan gains (0.0 = full left, 0.5 = center, 1.0 = full right)
|
|
float left_gain = sqrtf(1.0f - audio_buffer->pan);
|
|
float right_gain = sqrtf(audio_buffer->pan);
|
|
|
|
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;
|
|
float gain = (ch == 0) ? left_gain : right_gain;
|
|
out[output_pos + ch] += sample * gain;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!audio_buffer->isStreaming && audio_buffer->frameCursorPos >= audio_buffer->sizeInFrames) {
|
|
audio_buffer->playing = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
audio_buffer = audio_buffer->next;
|
|
}
|
|
|
|
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, "Failed to get host API count: %s", Pa_GetErrorText(hostApiCount));
|
|
return;
|
|
}
|
|
|
|
TRACELOG(LOG_INFO, "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);
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* get_host_api_name(PaHostApiIndex hostApi)
|
|
{
|
|
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(hostApi);
|
|
if (!hostApiInfo) {
|
|
return NULL;
|
|
}
|
|
|
|
return hostApiInfo->name;
|
|
}
|
|
|
|
PaDeviceIndex get_best_output_device_for_host_api(PaHostApiIndex hostApi)
|
|
{
|
|
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(hostApi);
|
|
if (!hostApiInfo) {
|
|
return paNoDevice;
|
|
}
|
|
|
|
if (hostApiInfo->defaultOutputDevice != paNoDevice) {
|
|
return hostApiInfo->defaultOutputDevice;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void init_audio_device(PaHostApiIndex host_api, double sample_rate, unsigned long buffer_size)
|
|
{
|
|
PaError err = Pa_Initialize();
|
|
if (err != paNoError) {
|
|
TRACELOG(LOG_WARNING, "Failed to initialize PortAudio: %s", Pa_GetErrorText(err));
|
|
return;
|
|
}
|
|
|
|
if (pthread_mutex_init(&AUDIO.System.lock, NULL) != 0) {
|
|
TRACELOG(LOG_WARNING, "Failed to create mutex for mixing");
|
|
Pa_Terminate();
|
|
return;
|
|
}
|
|
|
|
AUDIO.System.outputParameters.device = get_best_output_device_for_host_api(host_api);
|
|
if (AUDIO.System.outputParameters.device == paNoDevice) {
|
|
TRACELOG(LOG_WARNING, "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;
|
|
|
|
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(AUDIO.System.outputParameters.device);
|
|
const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
|
|
|
#ifdef _WIN32
|
|
if (hostApiInfo->type == paASIO) {
|
|
long minSize, maxSize, preferredSize, granularity;
|
|
PaError asioErr = PaAsio_GetAvailableBufferSizes(AUDIO.System.outputParameters.device,
|
|
&minSize, &maxSize, &preferredSize, &granularity);
|
|
|
|
if (asioErr == paNoError) {
|
|
TRACELOG(LOG_INFO, "ASIO buffer size constraints:");
|
|
TRACELOG(LOG_INFO, " > Minimum: %ld samples", minSize);
|
|
TRACELOG(LOG_INFO, " > Maximum: %ld samples", maxSize);
|
|
TRACELOG(LOG_INFO, " > Preferred: %ld samples", preferredSize);
|
|
if (granularity == -1) {
|
|
TRACELOG(LOG_INFO, " > Granularity: Powers of 2 only");
|
|
} else if (granularity == 0) {
|
|
TRACELOG(LOG_INFO, " > Granularity: Fixed size (min=max=preferred)");
|
|
} else {
|
|
TRACELOG(LOG_INFO, " > Granularity: %ld samples", granularity);
|
|
}
|
|
|
|
// Warn if requested buffer size is out of range
|
|
if (buffer_size > 0 && buffer_size < minSize) {
|
|
TRACELOG(LOG_WARNING, "Requested buffer size (%lu) is below ASIO minimum (%ld)", buffer_size, minSize);
|
|
TRACELOG(LOG_WARNING, "Driver will use %ld samples instead", minSize);
|
|
} else if (buffer_size > maxSize) {
|
|
TRACELOG(LOG_WARNING, "Requested buffer size (%lu) exceeds ASIO maximum (%ld)", buffer_size, maxSize);
|
|
TRACELOG(LOG_WARNING, "Driver will use %ld samples instead", maxSize);
|
|
} else if (buffer_size == 0) {
|
|
TRACELOG(LOG_INFO, "Buffer size not specified, driver will choose (likely %ld samples)", preferredSize);
|
|
}
|
|
} else {
|
|
TRACELOG(LOG_WARNING, "Failed to query ASIO buffer sizes: %s", Pa_GetErrorText(asioErr));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
err = Pa_OpenStream(&AUDIO.System.stream,
|
|
NULL, // No input
|
|
&AUDIO.System.outputParameters, // Output parameters
|
|
sample_rate, // Sample rate
|
|
buffer_size, // Frames per buffer
|
|
paNoFlag, // No clipping
|
|
port_audio_callback, // Callback function
|
|
NULL); // User data
|
|
|
|
if (err != paNoError) {
|
|
TRACELOG(LOG_WARNING, "Failed to open audio stream: %s", Pa_GetErrorText(err));
|
|
pthread_mutex_destroy(&AUDIO.System.lock);
|
|
Pa_Terminate();
|
|
return;
|
|
}
|
|
|
|
err = Pa_StartStream(AUDIO.System.stream);
|
|
if (err != paNoError) {
|
|
TRACELOG(LOG_WARNING, "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;
|
|
|
|
TRACELOG(LOG_INFO, "Device initialized successfully");
|
|
TRACELOG(LOG_INFO, " > Backend: PortAudio | %s", hostApiInfo->name);
|
|
TRACELOG(LOG_INFO, " > Device: %s", deviceInfo->name);
|
|
TRACELOG(LOG_INFO, " > Format: %s", "Float32");
|
|
const PaStreamInfo *streamInfo = Pa_GetStreamInfo(AUDIO.System.stream);
|
|
TRACELOG(LOG_INFO, " > Channels: %d", AUDIO_DEVICE_CHANNELS);
|
|
TRACELOG(LOG_INFO, " > Sample rate: %f", AUDIO.System.sampleRate);
|
|
TRACELOG(LOG_INFO, " > Buffer size: %lu (requested)", buffer_size);
|
|
TRACELOG(LOG_INFO, " > Latency: %f ms", streamInfo->outputLatency * 1000.0);
|
|
#ifdef _WIN32
|
|
if (hostApiInfo->type == paASIO) {
|
|
unsigned long estimatedBufferSize = (unsigned long)(streamInfo->outputLatency * AUDIO.System.sampleRate);
|
|
TRACELOG(LOG_INFO, " > Estimated actual buffer: ~%lu samples (based on latency)", estimatedBufferSize);
|
|
if (buffer_size > 0 && estimatedBufferSize != buffer_size) {
|
|
TRACELOG(LOG_INFO, " > Note: ASIO driver adjusted buffer size to meet its constraints");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void close_audio_device(void)
|
|
{
|
|
if (AUDIO.System.isReady) {
|
|
PaError err = Pa_StopStream(AUDIO.System.stream);
|
|
if (err != paNoError) {
|
|
TRACELOG(LOG_WARNING, "Error stopping stream: %s", Pa_GetErrorText(err));
|
|
}
|
|
|
|
err = Pa_CloseStream(AUDIO.System.stream);
|
|
if (err != paNoError) {
|
|
TRACELOG(LOG_WARNING, "Error closing stream: %s", Pa_GetErrorText(err));
|
|
}
|
|
|
|
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, "Device closed successfully");
|
|
}
|
|
else {
|
|
TRACELOG(LOG_WARNING, "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;
|
|
}
|
|
|
|
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, "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, "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;
|
|
}
|
|
|
|
buffer->isStreaming = (usage == 1); //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 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, "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, "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 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, "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, "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, "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 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;
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
memcpy(buffer_data, input_data, samples_to_copy * sizeof(float));
|
|
|
|
stream.buffer->sizeInFrames = frame_count;
|
|
}
|
|
|
|
pthread_mutex_unlock(&AUDIO.System.lock);
|
|
}
|
|
|
|
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, "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, "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, "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;
|
|
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;
|
|
unsigned int position_in_frames = (unsigned int)(position * music.stream.sampleRate / ctx->src_ratio);
|
|
|
|
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, "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/AUDIO.System.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 / AUDIO.System.sampleRate;
|
|
pthread_mutex_unlock(&AUDIO.System.lock);
|
|
}
|
|
return seconds_played;
|
|
}
|