#if defined(RAUDIO_STANDALONE)
#include "raudio.h"
#else
#include "raylib.h"
#if !defined(EXTERNAL_CONFIG_FLAGS)
#include "config.h"
#endif
#include "utils.h"
#endif
#if defined(SUPPORT_MODULE_RAUDIO)
#if defined(_WIN32)
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define OEMRESOURCE
#define NOATOM
#define NOCLIPBOARD
#define NOCOLOR
#define NOCTLMGR
#define NODRAWTEXT
#define NOGDI
#define NOKERNEL
#define NOUSER
#define NOMB
#define NOMEMMGR
#define NOMETAFILE
#define NOMINMAX
#define NOMSG
#define NOOPENFILE
#define NOSCROLL
#define NOSERVICE
#define NOSOUND
#define NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOCOMM
#define NOKANJI
#define NOHELP
#define NOPROFILER
#define NODEFERWINDOWPOS
#define NOMCX
typedef struct tagMSG *LPMSG;
#include <windows.h>
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
#include <objbase.h>
#include <mmreg.h>
#include <mmsystem.h>
#if defined(_MSC_VER) || defined(__TINYC__)
#include "propidl.h"
#endif
#endif
#define MA_MALLOC RL_MALLOC
#define MA_FREE RL_FREE
#define MA_NO_JACK
#define MA_NO_WAV
#define MA_NO_FLAC
#define MA_NO_MP3
#define MA_NO_RESOURCE_MANAGER
#define MA_NO_NODE_GRAPH
#define MA_NO_ENGINE
#define MA_NO_GENERATION
#define MA_COINIT_VALUE 2
#define MINIAUDIO_IMPLEMENTATION
#include "external/miniaudio.h"
#undef PlaySound
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#if defined(RAUDIO_STANDALONE)
#ifndef TRACELOG
#define TRACELOG(level, ...) printf(__VA_ARGS__)
#endif
#ifndef RL_MALLOC
#define RL_MALLOC(sz) malloc(sz)
#endif
#ifndef RL_CALLOC
#define RL_CALLOC(n,sz) calloc(n,sz)
#endif
#ifndef RL_REALLOC
#define RL_REALLOC(ptr,sz) realloc(ptr,sz)
#endif
#ifndef RL_FREE
#define RL_FREE(ptr) free(ptr)
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_WAV)
#define DRWAV_MALLOC RL_MALLOC
#define DRWAV_REALLOC RL_REALLOC
#define DRWAV_FREE RL_FREE
#define DR_WAV_IMPLEMENTATION
#include "external/dr_wav.h"
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
#include "external/stb_vorbis.c"
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
#define DRMP3_MALLOC RL_MALLOC
#define DRMP3_REALLOC RL_REALLOC
#define DRMP3_FREE RL_FREE
#define DR_MP3_IMPLEMENTATION
#include "external/dr_mp3.h"
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
#define QOA_MALLOC RL_MALLOC
#define QOA_FREE RL_FREE
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4018)
#pragma warning(disable : 4267)
#pragma warning(disable : 4244)
#endif
#define QOA_IMPLEMENTATION
#include "external/qoa.h"
#include "external/qoaplay.c"
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
#define DRFLAC_MALLOC RL_MALLOC
#define DRFLAC_REALLOC RL_REALLOC
#define DRFLAC_FREE RL_FREE
#define DR_FLAC_IMPLEMENTATION
#define DR_FLAC_NO_WIN32_IO
#include "external/dr_flac.h"
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
#define JARXM_MALLOC RL_MALLOC
#define JARXM_FREE RL_FREE
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4244)
#endif
#define JAR_XM_IMPLEMENTATION
#include "external/jar_xm.h"
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
#define JARMOD_MALLOC RL_MALLOC
#define JARMOD_FREE RL_FREE
#define JAR_MOD_IMPLEMENTATION
#include "external/jar_mod.h"
#endif
#ifndef AUDIO_DEVICE_FORMAT
#define AUDIO_DEVICE_FORMAT ma_format_f32
#endif
#ifndef AUDIO_DEVICE_CHANNELS
#define AUDIO_DEVICE_CHANNELS 2
#endif
#ifndef AUDIO_DEVICE_SAMPLE_RATE
#define AUDIO_DEVICE_SAMPLE_RATE 0
#endif
#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS
#define MAX_AUDIO_BUFFER_POOL_CHANNELS 16
#endif
#if defined(RAUDIO_STANDALONE)
typedef enum {
LOG_ALL = 0, LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_FATAL, LOG_NONE } TraceLogLevel;
#endif
typedef enum {
MUSIC_AUDIO_NONE = 0, MUSIC_AUDIO_WAV, MUSIC_AUDIO_OGG, MUSIC_AUDIO_FLAC, MUSIC_AUDIO_MP3, MUSIC_AUDIO_QOA, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType;
typedef enum {
AUDIO_BUFFER_USAGE_STATIC = 0,
AUDIO_BUFFER_USAGE_STREAM
} AudioBufferUsage;
struct rAudioBuffer {
ma_data_converter converter;
AudioCallback callback; rAudioProcessor *processor;
float volume; float pitch; float pan;
bool playing; bool paused; bool looping; int usage;
bool isSubBufferProcessed[2]; unsigned int sizeInFrames; unsigned int frameCursorPos; unsigned int framesProcessed;
unsigned char *data;
rAudioBuffer *next; rAudioBuffer *prev; };
struct rAudioProcessor {
AudioCallback process; rAudioProcessor *next; rAudioProcessor *prev; };
#define AudioBuffer rAudioBuffer
typedef struct AudioData {
struct {
ma_context context; ma_device device; ma_mutex lock; bool isReady; size_t pcmBufferSize; void *pcmBuffer; } System;
struct {
AudioBuffer *first; AudioBuffer *last; int defaultSize; } Buffer;
rAudioProcessor *mixedProcessor;
} AudioData;
static AudioData AUDIO = {
.Buffer.defaultSize = 0,
.mixedProcessor = NULL
};
static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage);
static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount);
static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount);
static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount);
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer);
static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer);
static void StopAudioBufferInLockedState(AudioBuffer *buffer);
static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount);
#if defined(RAUDIO_STANDALONE)
static bool IsFileExtension(const char *fileName, const char *ext); static const char *GetFileExtension(const char *fileName); static const char *GetFileName(const char *filePath); static const char *GetFileNameWithoutExt(const char *filePath);
static unsigned char *LoadFileData(const char *fileName, int *dataSize); static bool SaveFileData(const char *fileName, void *data, int dataSize); static bool SaveFileText(const char *fileName, char *text); #endif
AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage);
void UnloadAudioBuffer(AudioBuffer *buffer);
bool IsAudioBufferPlaying(AudioBuffer *buffer);
void PlayAudioBuffer(AudioBuffer *buffer);
void StopAudioBuffer(AudioBuffer *buffer);
void PauseAudioBuffer(AudioBuffer *buffer);
void ResumeAudioBuffer(AudioBuffer *buffer);
void SetAudioBufferVolume(AudioBuffer *buffer, float volume);
void SetAudioBufferPitch(AudioBuffer *buffer, float pitch);
void SetAudioBufferPan(AudioBuffer *buffer, float pan);
void TrackAudioBuffer(AudioBuffer *buffer);
void UntrackAudioBuffer(AudioBuffer *buffer);
void InitAudioDevice(void)
{
ma_context_config ctxConfig = ma_context_config_init();
ma_log_callback_init(OnLog, NULL);
ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context");
return;
}
ma_device_config config = ma_device_config_init(ma_device_type_playback);
config.playback.pDeviceID = NULL; config.playback.format = AUDIO_DEVICE_FORMAT;
config.playback.channels = AUDIO_DEVICE_CHANNELS;
config.capture.pDeviceID = NULL; config.capture.format = ma_format_s16;
config.capture.channels = 1;
config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE;
config.dataCallback = OnSendAudioDataToDevice;
config.pUserData = NULL;
result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device");
ma_context_uninit(&AUDIO.System.context);
return;
}
if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing");
ma_device_uninit(&AUDIO.System.device);
ma_context_uninit(&AUDIO.System.context);
return;
}
result = ma_device_start(&AUDIO.System.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device");
ma_device_uninit(&AUDIO.System.device);
ma_context_uninit(&AUDIO.System.context);
return;
}
TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully");
TRACELOG(LOG_INFO, " > Backend: miniaudio | %s", ma_get_backend_name(AUDIO.System.context.backend));
TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat));
TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels);
TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate);
TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods);
AUDIO.System.isReady = true;
}
void CloseAudioDevice(void)
{
if (AUDIO.System.isReady)
{
ma_mutex_uninit(&AUDIO.System.lock);
ma_device_uninit(&AUDIO.System.device);
ma_context_uninit(&AUDIO.System.context);
AUDIO.System.isReady = false;
RL_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 IsAudioDeviceReady(void)
{
return AUDIO.System.isReady;
}
void SetMasterVolume(float volume)
{
ma_device_set_master_volume(&AUDIO.System.device, volume);
}
float GetMasterVolume(void)
{
float volume = 0.0f;
ma_device_get_master_volume(&AUDIO.System.device, &volume);
return volume;
}
AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage)
{
AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer));
if (audioBuffer == NULL)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer");
return NULL;
}
if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1);
ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate);
converterConfig.allowDynamicSampleRate = true;
ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline");
RL_FREE(audioBuffer);
return NULL;
}
audioBuffer->volume = 1.0f;
audioBuffer->pitch = 1.0f;
audioBuffer->pan = 0.5f;
audioBuffer->callback = NULL;
audioBuffer->processor = NULL;
audioBuffer->playing = false;
audioBuffer->paused = false;
audioBuffer->looping = false;
audioBuffer->usage = usage;
audioBuffer->frameCursorPos = 0;
audioBuffer->sizeInFrames = sizeInFrames;
audioBuffer->isSubBufferProcessed[0] = true;
audioBuffer->isSubBufferProcessed[1] = true;
TrackAudioBuffer(audioBuffer);
return audioBuffer;
}
void UnloadAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
UntrackAudioBuffer(buffer);
ma_data_converter_uninit(&buffer->converter, NULL);
RL_FREE(buffer->data);
RL_FREE(buffer);
}
}
bool IsAudioBufferPlaying(AudioBuffer *buffer)
{
bool result = false;
ma_mutex_lock(&AUDIO.System.lock);
result = IsAudioBufferPlayingInLockedState(buffer);
ma_mutex_unlock(&AUDIO.System.lock);
return result;
}
void PlayAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->playing = true;
buffer->paused = false;
buffer->frameCursorPos = 0;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void StopAudioBuffer(AudioBuffer *buffer)
{
ma_mutex_lock(&AUDIO.System.lock);
StopAudioBufferInLockedState(buffer);
ma_mutex_unlock(&AUDIO.System.lock);
}
void PauseAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->paused = true;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void ResumeAudioBuffer(AudioBuffer *buffer)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->paused = false;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void SetAudioBufferVolume(AudioBuffer *buffer, float volume)
{
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->volume = volume;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void SetAudioBufferPitch(AudioBuffer *buffer, float pitch)
{
if ((buffer != NULL) && (pitch > 0.0f))
{
ma_mutex_lock(&AUDIO.System.lock);
ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.sampleRateOut/pitch);
ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate);
buffer->pitch = pitch;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void SetAudioBufferPan(AudioBuffer *buffer, float pan)
{
if (pan < 0.0f) pan = 0.0f;
else if (pan > 1.0f) pan = 1.0f;
if (buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
buffer->pan = pan;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void TrackAudioBuffer(AudioBuffer *buffer)
{
ma_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;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
void UntrackAudioBuffer(AudioBuffer *buffer)
{
ma_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;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
Wave LoadWave(const char *fileName)
{
Wave wave = { 0 };
int dataSize = 0;
unsigned char *fileData = LoadFileData(fileName, &dataSize);
if (fileData != NULL) wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, dataSize);
UnloadFileData(fileData);
return wave;
}
Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize)
{
Wave wave = { 0 };
if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV)
else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0))
{
drwav wav = { 0 };
bool success = drwav_init_memory(&wav, fileData, dataSize, NULL);
if (success)
{
wave.frameCount = (unsigned int)wav.totalPCMFrameCount;
wave.sampleRate = wav.sampleRate;
wave.sampleSize = 16;
wave.channels = wav.channels;
wave.data = (short *)RL_MALLOC((size_t)wave.frameCount*wave.channels*sizeof(short));
drwav_read_pcm_frames_s16(&wav, wave.frameCount, wave.data);
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data");
drwav_uninit(&wav);
}
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0))
{
stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL);
if (oggData != NULL)
{
stb_vorbis_info info = stb_vorbis_get_info(oggData);
wave.sampleRate = info.sample_rate;
wave.sampleSize = 16; wave.channels = info.channels;
wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short));
stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels);
stb_vorbis_close(oggData);
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data");
}
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0))
{
drmp3_config config = { 0 };
unsigned long long int totalFrameCount = 0;
wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, dataSize, &config, &totalFrameCount, NULL);
wave.sampleSize = 32;
if (wave.data != NULL)
{
wave.channels = config.channels;
wave.sampleRate = config.sampleRate;
wave.frameCount = (int)totalFrameCount;
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data");
}
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0))
{
qoa_desc qoa = { 0 };
wave.data = qoa_decode(fileData, dataSize, &qoa);
wave.sampleSize = 16;
if (wave.data != NULL)
{
wave.channels = qoa.channels;
wave.sampleRate = qoa.samplerate;
wave.frameCount = qoa.samples;
}
else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data");
}
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0))
{
unsigned long long int totalFrameCount = 0;
wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL);
wave.sampleSize = 16;
if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount;
else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data");
}
#endif
else TRACELOG(LOG_WARNING, "WAVE: Data format not supported");
TRACELOG(LOG_INFO, "WAVE: Data loaded successfully (%i Hz, %i bit, %i channels)", wave.sampleRate, wave.sampleSize, wave.channels);
return wave;
}
bool IsWaveValid(Wave wave)
{
bool result = false;
if ((wave.data != NULL) && (wave.frameCount > 0) && (wave.sampleRate > 0) && (wave.sampleSize > 0) && (wave.channels > 0)) result = true;
return result;
}
Sound LoadSound(const char *fileName)
{
Wave wave = LoadWave(fileName);
Sound sound = LoadSoundFromWave(wave);
UnloadWave(wave);
return sound;
}
Sound LoadSoundFromWave(Wave wave)
{
Sound sound = { 0 };
if (wave.data != NULL)
{
ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_uint32 frameCountIn = wave.frameCount;
ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate);
if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion");
AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC);
if (audioBuffer == NULL)
{
TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer");
return sound; }
frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate);
if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion");
sound.frameCount = frameCount;
sound.stream.sampleRate = AUDIO.System.device.sampleRate;
sound.stream.sampleSize = 32;
sound.stream.channels = AUDIO_DEVICE_CHANNELS;
sound.stream.buffer = audioBuffer;
}
return sound;
}
Sound LoadSoundAlias(Sound source)
{
Sound sound = { 0 };
if (source.stream.buffer->data != NULL)
{
AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC);
if (audioBuffer == NULL)
{
TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer");
return sound; }
audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames;
audioBuffer->volume = source.stream.buffer->volume;
audioBuffer->data = source.stream.buffer->data;
sound.frameCount = source.frameCount;
sound.stream.sampleRate = AUDIO.System.device.sampleRate;
sound.stream.sampleSize = 32;
sound.stream.channels = AUDIO_DEVICE_CHANNELS;
sound.stream.buffer = audioBuffer;
}
return sound;
}
bool IsSoundValid(Sound sound)
{
bool result = false;
if ((sound.frameCount > 0) && (sound.stream.buffer != NULL) && (sound.stream.sampleRate > 0) && (sound.stream.sampleSize > 0) && (sound.stream.channels > 0)) result = true;
return result;
}
void UnloadWave(Wave wave)
{
RL_FREE(wave.data);
}
void UnloadSound(Sound sound)
{
UnloadAudioBuffer(sound.stream.buffer);
}
void UnloadSoundAlias(Sound alias)
{
if (alias.stream.buffer != NULL)
{
UntrackAudioBuffer(alias.stream.buffer);
ma_data_converter_uninit(&alias.stream.buffer->converter, NULL);
RL_FREE(alias.stream.buffer);
}
}
void UpdateSound(Sound sound, const void *data, int frameCount)
{
if (sound.stream.buffer != NULL)
{
StopAudioBuffer(sound.stream.buffer);
memcpy(sound.stream.buffer->data, data, frameCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.formatIn, sound.stream.buffer->converter.channelsIn));
}
}
bool ExportWave(Wave wave, const char *fileName)
{
bool success = false;
if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV)
else if (IsFileExtension(fileName, ".wav"))
{
drwav wav = { 0 };
drwav_data_format format = { 0 };
format.container = drwav_container_riff;
if (wave.sampleSize == 32) format.format = DR_WAVE_FORMAT_IEEE_FLOAT;
else format.format = DR_WAVE_FORMAT_PCM;
format.channels = wave.channels;
format.sampleRate = wave.sampleRate;
format.bitsPerSample = wave.sampleSize;
void *fileData = NULL;
size_t fileDataSize = 0;
success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL);
if (success) success = (int)drwav_write_pcm_frames(&wav, wave.frameCount, wave.data);
drwav_result result = drwav_uninit(&wav);
if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize);
drwav_free(fileData, NULL);
}
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
else if (IsFileExtension(fileName, ".qoa"))
{
if (wave.sampleSize == 16)
{
qoa_desc qoa = { 0 };
qoa.channels = wave.channels;
qoa.samplerate = wave.sampleRate;
qoa.samples = wave.frameCount;
int bytesWritten = qoa_write(fileName, wave.data, &qoa);
if (bytesWritten > 0) success = true;
}
else TRACELOG(LOG_WARNING, "AUDIO: Wave data must be 16 bit per sample for QOA format export");
}
#endif
else if (IsFileExtension(fileName, ".raw"))
{
success = SaveFileData(fileName, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8);
}
if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName);
else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName);
return success;
}
bool ExportWaveAsCode(Wave wave, const char *fileName)
{
bool success = false;
#ifndef TEXT_BYTES_PER_LINE
#define TEXT_BYTES_PER_LINE 20
#endif
int waveDataSize = wave.frameCount*wave.channels*wave.sampleSize/8;
char *txtData = (char *)RL_CALLOC(waveDataSize*12 + 2000, sizeof(char));
int byteCount = 0;
byteCount += sprintf(txtData + byteCount, "\n//////////////////////////////////////////////////////////////////////////////////\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.1 - Wave data exported as an array of bytes //\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n");
byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2024 Ramon Santamaria (@raysan5) //\n");
byteCount += sprintf(txtData + byteCount, "// //\n");
byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n");
char varFileName[256] = { 0 };
strcpy(varFileName, GetFileNameWithoutExt(fileName));
for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; }
byteCount += sprintf(txtData + byteCount, "// Wave data information\n");
byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount);
byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate);
byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize);
byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels);
if (wave.sampleSize == 32)
{
byteCount += sprintf(txtData + byteCount, "static float %s_DATA[%i] = {\n", varFileName, waveDataSize/4);
for (int i = 1; i < waveDataSize/4; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.4ff,\n " : "%.4ff, "), ((float *)wave.data)[i - 1]);
byteCount += sprintf(txtData + byteCount, "%.4ff };\n", ((float *)wave.data)[waveDataSize/4 - 1]);
}
else
{
byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize);
for (int i = 1; i < waveDataSize; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n " : "0x%x, "), ((unsigned char *)wave.data)[i - 1]);
byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]);
}
success = SaveFileText(fileName, txtData);
RL_FREE(txtData);
if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave as code exported successfully", fileName);
else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave as code", fileName);
return success;
}
void PlaySound(Sound sound)
{
PlayAudioBuffer(sound.stream.buffer);
}
void PauseSound(Sound sound)
{
PauseAudioBuffer(sound.stream.buffer);
}
void ResumeSound(Sound sound)
{
ResumeAudioBuffer(sound.stream.buffer);
}
void StopSound(Sound sound)
{
StopAudioBuffer(sound.stream.buffer);
}
bool IsSoundPlaying(Sound sound)
{
bool result = false;
if (IsAudioBufferPlaying(sound.stream.buffer)) result = true;
return result;
}
void SetSoundVolume(Sound sound, float volume)
{
SetAudioBufferVolume(sound.stream.buffer, volume);
}
void SetSoundPitch(Sound sound, float pitch)
{
SetAudioBufferPitch(sound.stream.buffer, pitch);
}
void SetSoundPan(Sound sound, float pan)
{
SetAudioBufferPan(sound.stream.buffer, pan);
}
void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels)
{
ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32));
ma_uint32 frameCountIn = wave->frameCount;
ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate);
if (frameCount == 0)
{
TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion");
return;
}
void *data = RL_MALLOC(frameCount*channels*(sampleSize/8));
frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate);
if (frameCount == 0)
{
TRACELOG(LOG_WARNING, "WAVE: Failed format conversion");
return;
}
wave->frameCount = frameCount;
wave->sampleSize = sampleSize;
wave->sampleRate = sampleRate;
wave->channels = channels;
RL_FREE(wave->data);
wave->data = data;
}
Wave WaveCopy(Wave wave)
{
Wave newWave = { 0 };
newWave.data = RL_MALLOC(wave.frameCount*wave.channels*wave.sampleSize/8);
if (newWave.data != NULL)
{
memcpy(newWave.data, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8);
newWave.frameCount = wave.frameCount;
newWave.sampleRate = wave.sampleRate;
newWave.sampleSize = wave.sampleSize;
newWave.channels = wave.channels;
}
return newWave;
}
void WaveCrop(Wave *wave, int initFrame, int finalFrame)
{
if ((initFrame >= 0) && (initFrame < finalFrame) && ((unsigned int)finalFrame <= wave->frameCount))
{
int frameCount = finalFrame - initFrame;
void *data = RL_MALLOC(frameCount*wave->channels*wave->sampleSize/8);
memcpy(data, (unsigned char *)wave->data + (initFrame*wave->channels*wave->sampleSize/8), frameCount*wave->channels*wave->sampleSize/8);
RL_FREE(wave->data);
wave->data = data;
wave->frameCount = (unsigned int)frameCount;
}
else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds");
}
float *LoadWaveSamples(Wave wave)
{
float *samples = (float *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(float));
for (unsigned int i = 0; i < wave.frameCount*wave.channels; i++)
{
if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 128)/128.0f;
else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32768.0f;
else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i];
}
return samples;
}
void UnloadWaveSamples(float *samples)
{
RL_FREE(samples);
}
Music LoadMusicStream(const char *fileName)
{
Music music = { 0 };
bool musicLoaded = false;
if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV)
else if (IsFileExtension(fileName, ".wav"))
{
drwav *ctxWav = RL_CALLOC(1, sizeof(drwav));
bool success = drwav_init_file(ctxWav, fileName, NULL);
if (success)
{
music.ctxType = MUSIC_AUDIO_WAV;
music.ctxData = ctxWav;
int sampleSize = ctxWav->bitsPerSample;
if (ctxWav->bitsPerSample == 24) sampleSize = 16;
music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels);
music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount;
music.looping = true; musicLoaded = true;
}
else
{
RL_FREE(ctxWav);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
else if (IsFileExtension(fileName, ".ogg"))
{
stb_vorbis *ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL);
if (ctxOgg != NULL)
{
music.ctxType = MUSIC_AUDIO_OGG;
music.ctxData = ctxOgg;
stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData);
music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.looping = true; musicLoaded = true;
}
else
{
stb_vorbis_close(ctxOgg);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
else if (IsFileExtension(fileName, ".mp3"))
{
drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3));
int result = drmp3_init_file(ctxMp3, fileName, NULL);
if (result > 0)
{
music.ctxType = MUSIC_AUDIO_MP3;
music.ctxData = ctxMp3;
music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels);
music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3);
music.looping = true; musicLoaded = true;
}
else
{
RL_FREE(ctxMp3);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
else if (IsFileExtension(fileName, ".qoa"))
{
qoaplay_desc *ctxQoa = qoaplay_open(fileName);
if (ctxQoa != NULL)
{
music.ctxType = MUSIC_AUDIO_QOA;
music.ctxData = ctxQoa;
music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels);
music.frameCount = ctxQoa->info.samples;
music.looping = true; musicLoaded = true;
}
else{} }
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
else if (IsFileExtension(fileName, ".flac"))
{
drflac *ctxFlac = drflac_open_file(fileName, NULL);
if (ctxFlac != NULL)
{
music.ctxType = MUSIC_AUDIO_FLAC;
music.ctxData = ctxFlac;
int sampleSize = ctxFlac->bitsPerSample;
if (ctxFlac->bitsPerSample == 24) sampleSize = 16; music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels);
music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount;
music.looping = true; musicLoaded = true;
}
else
{
drflac_free(ctxFlac, NULL);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
else if (IsFileExtension(fileName, ".xm"))
{
jar_xm_context_t *ctxXm = NULL;
int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName);
if (result == 0) {
music.ctxType = MUSIC_MODULE_XM;
music.ctxData = ctxXm;
jar_xm_set_max_loop_count(ctxXm, 0);
unsigned int bits = 32;
if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16;
else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8;
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS);
music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); music.looping = true; jar_xm_reset(ctxXm); musicLoaded = true;
}
else
{
jar_xm_free_context(ctxXm);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
else if (IsFileExtension(fileName, ".mod"))
{
jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t));
jar_mod_init(ctxMod);
int result = jar_mod_load_file(ctxMod, fileName);
if (result > 0)
{
music.ctxType = MUSIC_MODULE_MOD;
music.ctxData = ctxMod;
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS);
music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); music.looping = true; musicLoaded = true;
}
else
{
jar_mod_unload(ctxMod);
RL_FREE(ctxMod);
}
}
#endif
else TRACELOG(LOG_WARNING, "STREAM: [%s] File format not supported", fileName);
if (!musicLoaded)
{
TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName);
}
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;
}
Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize)
{
Music music = { 0 };
bool musicLoaded = false;
if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV)
else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0))
{
drwav *ctxWav = RL_CALLOC(1, sizeof(drwav));
bool success = drwav_init_memory(ctxWav, (const void *)data, dataSize, NULL);
if (success)
{
music.ctxType = MUSIC_AUDIO_WAV;
music.ctxData = ctxWav;
int sampleSize = ctxWav->bitsPerSample;
if (ctxWav->bitsPerSample == 24) sampleSize = 16;
music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels);
music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount;
music.looping = true; musicLoaded = true;
}
else {
drwav_uninit(ctxWav);
RL_FREE(ctxWav);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0))
{
stb_vorbis* ctxOgg = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL);
if (ctxOgg != NULL)
{
music.ctxType = MUSIC_AUDIO_OGG;
music.ctxData = ctxOgg;
stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData);
music.stream = LoadAudioStream(info.sample_rate, 16, info.channels);
music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData);
music.looping = true; musicLoaded = true;
}
else
{
stb_vorbis_close(ctxOgg);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0))
{
drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3));
int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL);
if (success)
{
music.ctxType = MUSIC_AUDIO_MP3;
music.ctxData = ctxMp3;
music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels);
music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3);
music.looping = true; musicLoaded = true;
}
else
{
drmp3_uninit(ctxMp3);
RL_FREE(ctxMp3);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0))
{
qoaplay_desc *ctxQoa = NULL;
if ((data != NULL) && (dataSize > 0))
{
ctxQoa = qoaplay_open_memory(data, dataSize);
}
if (ctxQoa != NULL)
{
music.ctxType = MUSIC_AUDIO_QOA;
music.ctxData = ctxQoa;
music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels);
music.frameCount = ctxQoa->info.samples;
music.looping = true; musicLoaded = true;
}
else{} }
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0))
{
drflac *ctxFlac = drflac_open_memory((const void*)data, dataSize, NULL);
if (ctxFlac != NULL)
{
music.ctxType = MUSIC_AUDIO_FLAC;
music.ctxData = ctxFlac;
int sampleSize = ctxFlac->bitsPerSample;
if (ctxFlac->bitsPerSample == 24) sampleSize = 16; music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels);
music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount;
music.looping = true; musicLoaded = true;
}
else
{
drflac_free(ctxFlac, NULL);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
else if ((strcmp(fileType, ".xm") == 0) || (strcmp(fileType, ".XM") == 0))
{
jar_xm_context_t *ctxXm = NULL;
int result = jar_xm_create_context_safe(&ctxXm, (const char *)data, dataSize, AUDIO.System.device.sampleRate);
if (result == 0) {
music.ctxType = MUSIC_MODULE_XM;
music.ctxData = ctxXm;
jar_xm_set_max_loop_count(ctxXm, 0);
unsigned int bits = 32;
if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16;
else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8;
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2);
music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); music.looping = true; jar_xm_reset(ctxXm);
musicLoaded = true;
}
else
{
jar_xm_free_context(ctxXm);
}
}
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
else if ((strcmp(fileType, ".mod") == 0) || (strcmp(fileType, ".MOD") == 0))
{
jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_MALLOC(sizeof(jar_mod_context_t));
int result = 0;
jar_mod_init(ctxMod);
unsigned char *newData = (unsigned char *)RL_MALLOC(dataSize);
int it = dataSize/sizeof(unsigned char);
for (int i = 0; i < it; i++) newData[i] = data[i];
if (dataSize && (dataSize < 32*1024*1024))
{
ctxMod->modfilesize = dataSize;
ctxMod->modfile = newData;
if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize;
}
if (result > 0)
{
music.ctxType = MUSIC_MODULE_MOD;
music.ctxData = ctxMod;
music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2);
music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); music.looping = true; musicLoaded = true;
}
else
{
jar_mod_unload(ctxMod);
RL_FREE(ctxMod);
}
}
#endif
else TRACELOG(LOG_WARNING, "STREAM: Data format not supported");
if (!musicLoaded)
{
TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded");
}
else
{
TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully");
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 IsMusicValid(Music music)
{
return ((music.ctxData != NULL) && (music.frameCount > 0) && (music.stream.sampleRate > 0) && (music.stream.sampleSize > 0) && (music.stream.channels > 0)); }
void UnloadMusicStream(Music music)
{
UnloadAudioStream(music.stream);
if (music.ctxData != NULL)
{
if (false) { }
#if defined(SUPPORT_FILEFORMAT_WAV)
else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData);
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData);
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); }
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData);
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL);
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData);
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); }
#endif
}
}
void PlayMusicStream(Music music)
{
PlayAudioStream(music.stream);
}
void PauseMusicStream(Music music)
{
PauseAudioStream(music.stream);
}
void ResumeMusicStream(Music music)
{
ResumeAudioStream(music.stream);
}
void StopMusicStream(Music music)
{
StopAudioStream(music.stream);
switch (music.ctxType)
{
#if defined(SUPPORT_FILEFORMAT_WAV)
case MUSIC_AUDIO_WAV: drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); break;
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break;
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break;
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
case MUSIC_AUDIO_QOA: qoaplay_rewind((qoaplay_desc *)music.ctxData); break;
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break;
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break;
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break;
#endif
default: break;
}
}
void SeekMusicStream(Music music, float position)
{
if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return;
unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate);
switch (music.ctxType)
{
#if defined(SUPPORT_FILEFORMAT_WAV)
case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, positionInFrames); break;
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break;
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break;
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
case MUSIC_AUDIO_QOA:
{
int qoaFrame = positionInFrames/QOA_FRAME_LEN;
qoaplay_seek_frame((qoaplay_desc *)music.ctxData, qoaFrame);
positionInFrames = ((qoaplay_desc *)music.ctxData)->sample_position;
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break;
#endif
default: break;
}
ma_mutex_lock(&AUDIO.System.lock);
music.stream.buffer->framesProcessed = positionInFrames;
music.stream.buffer->isSubBufferProcessed[0] = true;
music.stream.buffer->isSubBufferProcessed[1] = true;
ma_mutex_unlock(&AUDIO.System.lock);
}
void UpdateMusicStream(Music music)
{
if (music.stream.buffer == NULL) return;
ma_mutex_lock(&AUDIO.System.lock);
unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2;
int frameSize = music.stream.channels*music.stream.sampleSize/8;
unsigned int pcmSize = subBufferSizeInFrames*frameSize;
if (AUDIO.System.pcmBufferSize < pcmSize)
{
RL_FREE(AUDIO.System.pcmBuffer);
AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize);
AUDIO.System.pcmBufferSize = pcmSize;
}
for (int i = 0; i < 2; i++)
{
if (!music.stream.buffer->isSubBufferProcessed[i]) continue;
unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; unsigned int framesToStream = 0;
if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames;
else framesToStream = framesLeft;
int frameCountStillNeeded = framesToStream;
int frameCountReadTotal = 0;
switch (music.ctxType)
{
#if defined(SUPPORT_FILEFORMAT_WAV)
case MUSIC_AUDIO_WAV:
{
if (music.stream.sampleSize == 16)
{
while (true)
{
int frameCountRead = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break;
else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
}
}
else if (music.stream.sampleSize == 32)
{
while (true)
{
int frameCountRead = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break;
else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData);
}
}
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_OGG)
case MUSIC_AUDIO_OGG:
{
while (true)
{
int frameCountRead = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels);
frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break;
else stb_vorbis_seek_start((stb_vorbis *)music.ctxData);
}
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_MP3)
case MUSIC_AUDIO_MP3:
{
while (true)
{
int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break;
else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData);
}
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_QOA)
case MUSIC_AUDIO_QOA:
{
unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream);
frameCountReadTotal += frameCountRead;
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_FLAC)
case MUSIC_AUDIO_FLAC:
{
while (true)
{
int frameCountRead = (int)drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize));
frameCountReadTotal += frameCountRead;
frameCountStillNeeded -= frameCountRead;
if (frameCountStillNeeded == 0) break;
else drflac__seek_to_first_frame((drflac *)music.ctxData);
}
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_XM)
case MUSIC_MODULE_XM:
{
if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream);
else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream);
else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream);
} break;
#endif
#if defined(SUPPORT_FILEFORMAT_MOD)
case MUSIC_MODULE_MOD:
{
jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0);
} break;
#endif
default: break;
}
UpdateAudioStreamInLockedState(music.stream, AUDIO.System.pcmBuffer, framesToStream);
music.stream.buffer->framesProcessed = music.stream.buffer->framesProcessed%music.frameCount;
if (framesLeft <= subBufferSizeInFrames)
{
if (!music.looping)
{
ma_mutex_unlock(&AUDIO.System.lock);
StopMusicStream(music);
return;
}
}
}
ma_mutex_unlock(&AUDIO.System.lock);
}
bool IsMusicStreamPlaying(Music music)
{
return IsAudioStreamPlaying(music.stream);
}
void SetMusicVolume(Music music, float volume)
{
SetAudioStreamVolume(music.stream, volume);
}
void SetMusicPitch(Music music, float pitch)
{
SetAudioBufferPitch(music.stream.buffer, pitch);
}
void SetMusicPan(Music music, float pan)
{
SetAudioBufferPan(music.stream.buffer, pan);
}
float GetMusicTimeLength(Music music)
{
float totalSeconds = 0.0f;
totalSeconds = (float)music.frameCount/music.stream.sampleRate;
return totalSeconds;
}
float GetMusicTimePlayed(Music music)
{
float secondsPlayed = 0.0f;
if (music.stream.buffer != NULL)
{
#if defined(SUPPORT_FILEFORMAT_XM)
if (music.ctxType == MUSIC_MODULE_XM)
{
uint64_t framesPlayed = 0;
jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &framesPlayed);
secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
}
else
#endif
{
ma_mutex_lock(&AUDIO.System.lock);
int framesProcessed = (int)music.stream.buffer->framesProcessed;
int subBufferSize = (int)music.stream.buffer->sizeInFrames/2;
int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize;
int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize;
int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize;
int framesPlayed = (framesProcessed - framesInFirstBuffer - framesInSecondBuffer + framesSentToMix)%(int)music.frameCount;
if (framesPlayed < 0) framesPlayed += music.frameCount;
secondsPlayed = (float)framesPlayed/music.stream.sampleRate;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
return secondsPlayed;
}
AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels)
{
AudioStream stream = { 0 };
stream.sampleRate = sampleRate;
stream.sampleSize = sampleSize;
stream.channels = channels;
ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32));
unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames;
unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? AUDIO.System.device.sampleRate/30 : AUDIO.Buffer.defaultSize;
if (subBufferSize < periodSize) subBufferSize = periodSize;
stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM);
if (stream.buffer != NULL)
{
stream.buffer->looping = true; TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo");
}
else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created");
return stream;
}
bool IsAudioStreamValid(AudioStream stream)
{
return ((stream.buffer != NULL) && (stream.sampleRate > 0) && (stream.sampleSize > 0) && (stream.channels > 0)); }
void UnloadAudioStream(AudioStream stream)
{
UnloadAudioBuffer(stream.buffer);
TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM");
}
void UpdateAudioStream(AudioStream stream, const void *data, int frameCount)
{
ma_mutex_lock(&AUDIO.System.lock);
UpdateAudioStreamInLockedState(stream, data, frameCount);
ma_mutex_unlock(&AUDIO.System.lock);
}
bool IsAudioStreamProcessed(AudioStream stream)
{
if (stream.buffer == NULL) return false;
bool result = false;
ma_mutex_lock(&AUDIO.System.lock);
result = stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1];
ma_mutex_unlock(&AUDIO.System.lock);
return result;
}
void PlayAudioStream(AudioStream stream)
{
PlayAudioBuffer(stream.buffer);
}
void PauseAudioStream(AudioStream stream)
{
PauseAudioBuffer(stream.buffer);
}
void ResumeAudioStream(AudioStream stream)
{
ResumeAudioBuffer(stream.buffer);
}
bool IsAudioStreamPlaying(AudioStream stream)
{
return IsAudioBufferPlaying(stream.buffer);
}
void StopAudioStream(AudioStream stream)
{
StopAudioBuffer(stream.buffer);
}
void SetAudioStreamVolume(AudioStream stream, float volume)
{
SetAudioBufferVolume(stream.buffer, volume);
}
void SetAudioStreamPitch(AudioStream stream, float pitch)
{
SetAudioBufferPitch(stream.buffer, pitch);
}
void SetAudioStreamPan(AudioStream stream, float pan)
{
SetAudioBufferPan(stream.buffer, pan);
}
void SetAudioStreamBufferSizeDefault(int size)
{
AUDIO.Buffer.defaultSize = size;
}
void SetAudioStreamCallback(AudioStream stream, AudioCallback callback)
{
if (stream.buffer != NULL)
{
ma_mutex_lock(&AUDIO.System.lock);
stream.buffer->callback = callback;
ma_mutex_unlock(&AUDIO.System.lock);
}
}
void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process)
{
ma_mutex_lock(&AUDIO.System.lock);
rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor));
processor->process = process;
rAudioProcessor *last = stream.buffer->processor;
while (last && last->next)
{
last = last->next;
}
if (last)
{
processor->prev = last;
last->next = processor;
}
else stream.buffer->processor = processor;
ma_mutex_unlock(&AUDIO.System.lock);
}
void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process)
{
ma_mutex_lock(&AUDIO.System.lock);
rAudioProcessor *processor = stream.buffer->processor;
while (processor)
{
rAudioProcessor *next = processor->next;
rAudioProcessor *prev = processor->prev;
if (processor->process == process)
{
if (stream.buffer->processor == processor) stream.buffer->processor = next;
if (prev) prev->next = next;
if (next) next->prev = prev;
RL_FREE(processor);
}
processor = next;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
void AttachAudioMixedProcessor(AudioCallback process)
{
ma_mutex_lock(&AUDIO.System.lock);
rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor));
processor->process = process;
rAudioProcessor *last = AUDIO.mixedProcessor;
while (last && last->next)
{
last = last->next;
}
if (last)
{
processor->prev = last;
last->next = processor;
}
else AUDIO.mixedProcessor = processor;
ma_mutex_unlock(&AUDIO.System.lock);
}
void DetachAudioMixedProcessor(AudioCallback process)
{
ma_mutex_lock(&AUDIO.System.lock);
rAudioProcessor *processor = AUDIO.mixedProcessor;
while (processor)
{
rAudioProcessor *next = processor->next;
rAudioProcessor *prev = processor->prev;
if (processor->process == process)
{
if (AUDIO.mixedProcessor == processor) AUDIO.mixedProcessor = next;
if (prev) prev->next = next;
if (next) next->prev = prev;
RL_FREE(processor);
}
processor = next;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage)
{
TRACELOG(LOG_WARNING, "miniaudio: %s", pMessage); }
static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount)
{
if (audioBuffer->callback)
{
audioBuffer->callback(framesOut, frameCount);
audioBuffer->framesProcessed += frameCount;
return frameCount;
}
ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames;
ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames;
if (currentSubBufferIndex > 1) return 0;
bool isSubBufferProcessed[2] = { 0 };
isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0];
isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1];
ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn);
ma_uint32 framesRead = 0;
while (1)
{
if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC)
{
if (framesRead >= frameCount) break;
}
else
{
if (isSubBufferProcessed[currentSubBufferIndex]) break;
}
ma_uint32 totalFramesRemaining = (frameCount - framesRead);
if (totalFramesRemaining == 0) break;
ma_uint32 framesRemainingInOutputBuffer;
if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC)
{
framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos;
}
else
{
ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex;
framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer);
}
ma_uint32 framesToRead = totalFramesRemaining;
if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer;
memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes);
audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames;
framesRead += framesToRead;
if (framesToRead == framesRemainingInOutputBuffer)
{
audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true;
isSubBufferProcessed[currentSubBufferIndex] = true;
currentSubBufferIndex = (currentSubBufferIndex + 1)%2;
if (!audioBuffer->looping)
{
StopAudioBufferInLockedState(audioBuffer);
break;
}
}
}
ma_uint32 totalFramesRemaining = (frameCount - framesRead);
if (totalFramesRemaining > 0)
{
memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes);
if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining;
}
return framesRead;
}
static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount)
{
ma_uint8 inputBuffer[4096] = { 0 };
ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn);
ma_uint32 totalOutputFramesProcessed = 0;
while (totalOutputFramesProcessed < frameCount)
{
ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed;
ma_uint64 inputFramesToProcessThisIteration = 0;
(void)ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration, &inputFramesToProcessThisIteration);
if (inputFramesToProcessThisIteration > inputBufferFrameCap)
{
inputFramesToProcessThisIteration = inputBufferFrameCap;
}
float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut);
ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration);
ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration;
ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration);
totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration;
if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) break;
if ((inputFramesProcessedThisIteration == 0) && (outputFramesProcessedThisIteration == 0)) break;
}
return totalOutputFramesProcessed;
}
static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount)
{
(void)pDevice;
memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format));
ma_mutex_lock(&AUDIO.System.lock);
{
for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next)
{
if (!audioBuffer->playing || audioBuffer->paused) continue;
ma_uint32 framesRead = 0;
while (1)
{
if (framesRead >= frameCount) break;
ma_uint32 framesToRead = (frameCount - framesRead);
while (framesToRead > 0)
{
float tempBuffer[1024] = { 0 };
ma_uint32 framesToReadRightNow = framesToRead;
if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS)
{
framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS;
}
ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow);
if (framesJustRead > 0)
{
float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels);
float *framesIn = tempBuffer;
rAudioProcessor *processor = audioBuffer->processor;
while (processor)
{
processor->process(framesIn, framesJustRead);
processor = processor->next;
}
MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer);
framesToRead -= framesJustRead;
framesRead += framesJustRead;
}
if (!audioBuffer->playing)
{
framesRead = frameCount;
break;
}
if (framesJustRead < framesToReadRightNow)
{
if (!audioBuffer->looping)
{
StopAudioBufferInLockedState(audioBuffer);
break;
}
else
{
audioBuffer->frameCursorPos = 0;
continue;
}
}
}
if (framesToRead > 0) break;
}
}
}
rAudioProcessor *processor = AUDIO.mixedProcessor;
while (processor)
{
processor->process(pFramesOut, frameCount);
processor = processor->next;
}
ma_mutex_unlock(&AUDIO.System.lock);
}
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer)
{
const float localVolume = buffer->volume;
const ma_uint32 channels = AUDIO.System.device.playback.channels;
if (channels == 2) {
const float left = buffer->pan;
const float right = 1.0f - left;
const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) };
float *frameOut = framesOut;
const float *frameIn = framesIn;
for (ma_uint32 frame = 0; frame < frameCount; frame++)
{
frameOut[0] += (frameIn[0]*levels[0]);
frameOut[1] += (frameIn[1]*levels[1]);
frameOut += 2;
frameIn += 2;
}
}
else {
for (ma_uint32 frame = 0; frame < frameCount; frame++)
{
for (ma_uint32 c = 0; c < channels; c++)
{
float *frameOut = framesOut + (frame*channels);
const float *frameIn = framesIn + (frame*channels);
frameOut[c] += (frameIn[c]*localVolume);
}
}
}
}
static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer)
{
bool result = false;
if (buffer != NULL) result = (buffer->playing && !buffer->paused);
return result;
}
static void StopAudioBufferInLockedState(AudioBuffer *buffer)
{
if (buffer != NULL)
{
if (IsAudioBufferPlayingInLockedState(buffer))
{
buffer->playing = false;
buffer->paused = false;
buffer->frameCursorPos = 0;
buffer->framesProcessed = 0;
buffer->isSubBufferProcessed[0] = true;
buffer->isSubBufferProcessed[1] = true;
}
}
}
static void UpdateAudioStreamInLockedState(AudioStream stream, const void *data, int frameCount)
{
if (stream.buffer != NULL)
{
if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1])
{
ma_uint32 subBufferToUpdate = 0;
if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1])
{
subBufferToUpdate = 0;
stream.buffer->frameCursorPos = 0;
}
else
{
subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1;
}
ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2;
unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate);
stream.buffer->framesProcessed += subBufferSizeInFrames;
if (subBufferSizeInFrames >= (ma_uint32)frameCount)
{
ma_uint32 framesToWrite = (ma_uint32)frameCount;
ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8);
memcpy(subBuffer, data, bytesToWrite);
ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite;
if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8));
stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false;
}
else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer");
}
else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating");
}
}
#if defined(RAUDIO_STANDALONE)
static bool IsFileExtension(const char *fileName, const char *ext)
{
bool result = false;
const char *fileExt;
if ((fileExt = strrchr(fileName, '.')) != NULL)
{
if (strcmp(fileExt, ext) == 0) result = true;
}
return result;
}
static const char *GetFileExtension(const char *fileName)
{
const char *dot = strrchr(fileName, '.');
if (!dot || dot == fileName) return NULL;
return dot;
}
static const char *strprbrk(const char *s, const char *charset)
{
const char *latestMatch = NULL;
for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { }
return latestMatch;
}
static const char *GetFileName(const char *filePath)
{
const char *fileName = NULL;
if (filePath != NULL) fileName = strprbrk(filePath, "\\/");
if (!fileName) return filePath;
return fileName + 1;
}
static const char *GetFileNameWithoutExt(const char *filePath)
{
#define MAX_FILENAMEWITHOUTEXT_LENGTH 256
static char fileName[MAX_FILENAMEWITHOUTEXT_LENGTH] = { 0 };
memset(fileName, 0, MAX_FILENAMEWITHOUTEXT_LENGTH);
if (filePath != NULL) strcpy(fileName, GetFileName(filePath));
int size = (int)strlen(fileName);
for (int i = 0; (i < size) && (i < MAX_FILENAMEWITHOUTEXT_LENGTH); i++)
{
if (fileName[i] == '.')
{
fileName[i] = '\0';
break;
}
}
return fileName;
}
static unsigned char *LoadFileData(const char *fileName, int *dataSize)
{
unsigned char *data = NULL;
*dataSize = 0;
if (fileName != NULL)
{
FILE *file = fopen(fileName, "rb");
if (file != NULL)
{
fseek(file, 0, SEEK_END);
int size = ftell(file);
fseek(file, 0, SEEK_SET);
if (size > 0)
{
data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char));
unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file);
*dataSize = count;
if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName);
else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName);
}
else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName);
fclose(file);
}
else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName);
}
else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid");
return data;
}
static bool SaveFileData(const char *fileName, void *data, int dataSize)
{
if (fileName != NULL)
{
FILE *file = fopen(fileName, "wb");
if (file != NULL)
{
unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), dataSize, file);
if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName);
else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName);
else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName);
fclose(file);
}
else
{
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName);
return false;
}
}
else
{
TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid");
return false;
}
return true;
}
static bool SaveFileText(const char *fileName, char *text)
{
if (fileName != NULL)
{
FILE *file = fopen(fileName, "wt");
if (file != NULL)
{
int count = fprintf(file, "%s", text);
if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName);
else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName);
fclose(file);
}
else
{
TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName);
return false;
}
}
else
{
TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid");
return false;
}
return true;
}
#endif
#undef AudioBuffer
#endif