From 8328353204212f754c7ef004a18de7161d91bb89 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 17 Jul 2016 17:27:49 +0200 Subject: New example: XM module playing --- examples/audio_module_playing.c | 138 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 examples/audio_module_playing.c (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c new file mode 100644 index 00000000..6189b866 --- /dev/null +++ b/examples/audio_module_playing.c @@ -0,0 +1,138 @@ +/******************************************************************************************* +* +* raylib [audio] example - Module playing (streaming) +* +* NOTE: This example requires OpenAL Soft library installed +* +* This example has been created using raylib 1.5 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2016 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +#define MAX_CIRCLES 64 + +typedef struct { + Vector2 position; + float radius; + float alpha; + float speed; + Color color; +} CircleWave; + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [audio] example - module playing (streaming)"); + + InitAudioDevice(); // Initialize audio device + + Color colors[14] = { ORANGE, RED, GOLD, LIME, BLUE, VIOLET, BROWN, LIGHTGRAY, PINK, + YELLOW, GREEN, SKYBLUE, PURPLE, BEIGE }; + + // Creates ome circles for visual effect + CircleWave circles[MAX_CIRCLES]; + + for (int i = MAX_CIRCLES - 1; i >= 0; i--) + { + circles[i].alpha = 0.0f; + circles[i].radius = GetRandomValue(10, 40); + circles[i].position.x = GetRandomValue(circles[i].radius, screenWidth - circles[i].radius); + circles[i].position.y = GetRandomValue(circles[i].radius, screenHeight - circles[i].radius); + circles[i].speed = (float)GetRandomValue(1, 100)/20000.0f; + circles[i].color = colors[GetRandomValue(0, 13)]; + } + + // Load postprocessing bloom shader + Shader shader = LoadShader("resources/shaders/glsl330/base.vs", + "resources/shaders/glsl330/bloom.fs"); + + // Create a RenderTexture2D to be used for render to texture + RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight); + + PlayMusicStream(0, "resources/audio/2t2m_spa.xm"); // Play module stream + + float timePlayed = 0.0f; + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + for (int i = MAX_CIRCLES - 1; i >= 0; i--) + { + circles[i].alpha += circles[i].speed; + circles[i].radius += circles[i].speed*10.0f; + + if (circles[i].alpha > 1.0f) circles[i].speed *= -1; + + if (circles[i].alpha <= 0.0f) + { + circles[i].alpha = 0.0f; + circles[i].radius = GetRandomValue(10, 40); + circles[i].position.x = GetRandomValue(circles[i].radius, screenWidth - circles[i].radius); + circles[i].position.y = GetRandomValue(circles[i].radius, screenHeight - circles[i].radius); + circles[i].color = colors[GetRandomValue(0, 13)]; + circles[i].speed = (float)GetRandomValue(1, 100)/20000.0f; + } + } + + // Get timePlayed scaled to bar dimensions + timePlayed = (GetMusicTimePlayed(0)/GetMusicTimeLength(0)*(screenWidth - 40))*2; + + UpdateMusicStream(0); // Update music buffer with new stream data + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(BLACK); + + BeginTextureMode(target); // Enable drawing to texture + + for (int i = MAX_CIRCLES - 1; i >= 0; i--) + { + DrawCircleV(circles[i].position, circles[i].radius, Fade(circles[i].color, circles[i].alpha)); + } + + EndTextureMode(); // End drawing to texture (now we have a texture available for next passes) + + BeginShaderMode(shader); + + // NOTE: Render texture must be y-flipped due to default OpenGL coordinates (left-bottom) + DrawTextureRec(target.texture, (Rectangle){ 0, 0, target.texture.width, -target.texture.height }, (Vector2){ 0, 0 }, WHITE); + + EndShaderMode(); + + // Draw time bar + DrawRectangle(20, screenHeight - 20 - 12, screenWidth - 40, 12, LIGHTGRAY); + DrawRectangle(20, screenHeight - 20 - 12, (int)timePlayed, 12, MAROON); + DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, WHITE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadShader(shader); // Unload shader + UnloadRenderTexture(target); // Unload render texture + + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file -- cgit v1.2.3 From 02c456432d7f284c41519f6d540ad6c4dfb4a065 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Mon, 1 Aug 2016 12:49:17 +0200 Subject: Complete review of audio system Still some work left... --- examples/audio_module_playing.c | 10 +- examples/audio_music_stream.c | 20 +- src/audio.c | 1091 ++++++++++++++------------------------- src/audio.h | 36 +- src/external/jar_xm.h | 60 ++- src/raylib.h | 32 +- 6 files changed, 464 insertions(+), 785 deletions(-) (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c index 6189b866..07165c76 100644 --- a/examples/audio_module_playing.c +++ b/examples/audio_module_playing.c @@ -57,7 +57,9 @@ int main() // Create a RenderTexture2D to be used for render to texture RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight); - PlayMusicStream(0, "resources/audio/2t2m_spa.xm"); // Play module stream + Music xm = LoadMusicStream("resources/audio/2t2m_spa.xm"); + + PlayMusicStream(xm); float timePlayed = 0.0f; @@ -88,9 +90,9 @@ int main() } // Get timePlayed scaled to bar dimensions - timePlayed = (GetMusicTimePlayed(0)/GetMusicTimeLength(0)*(screenWidth - 40))*2; + timePlayed = (GetMusicTimePlayed(xm)/GetMusicTimeLength(xm)*(screenWidth - 40))*2; - UpdateMusicStream(0); // Update music buffer with new stream data + UpdateMusicStream(xm); // Update music buffer with new stream data //---------------------------------------------------------------------------------- // Draw @@ -129,6 +131,8 @@ int main() UnloadShader(shader); // Unload shader UnloadRenderTexture(target); // Unload render texture + UnloadMusicStream(xm); // Unload music stream buffers from RAM + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) CloseWindow(); // Close window and OpenGL context diff --git a/examples/audio_music_stream.c b/examples/audio_music_stream.c index e135a6e4..b96b85f7 100644 --- a/examples/audio_music_stream.c +++ b/examples/audio_music_stream.c @@ -24,7 +24,9 @@ int main() InitAudioDevice(); // Initialize audio device - PlayMusicStream(0, "resources/audio/guitar_noodling.ogg"); // Play music stream + Music music = LoadMusicStream("resources/audio/guitar_noodling.ogg"); + + PlayMusicStream(music); int framesCounter = 0; float timePlayed = 0.0f; @@ -58,12 +60,12 @@ int main() SetMusicVolume(volume); } */ - if (IsWindowMinimized()) PauseMusicStream(0); - else ResumeMusicStream(0); + if (IsWindowMinimized()) PauseMusicStream(music); + else ResumeMusicStream(music); - timePlayed = GetMusicTimePlayed(0)/GetMusicTimeLength(0)*100*4; // We scale by 4 to fit 400 pixels - - UpdateMusicStream(0); // Update music buffer with new stream data + timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*100*4; // We scale by 4 to fit 400 pixels + + UpdateMusicStream(music); // Update music buffer with new stream data //---------------------------------------------------------------------------------- // Draw @@ -83,9 +85,11 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- - CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + UnloadMusicStream(music); // Unload music stream buffers from RAM + + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) - CloseWindow(); // Close window and OpenGL context + CloseWindow(); // Close window and OpenGL context //-------------------------------------------------------------------------------------- return 0; diff --git a/src/audio.c b/src/audio.c index 93987c6e..94bbb376 100644 --- a/src/audio.c +++ b/src/audio.c @@ -55,6 +55,7 @@ #include // Required for: strcmp(), strncmp() #include // Required for: FILE, fopen(), fclose(), fread() +// Tokens defined by OpenAL extension: AL_EXT_float32 #ifndef AL_FORMAT_MONO_FLOAT32 #define AL_FORMAT_MONO_FLOAT32 0x10010 #endif @@ -85,55 +86,47 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MAX_STREAM_BUFFERS 2 // Number of buffers for each source -#define MAX_MUSIC_STREAMS 2 // Number of simultanious music sources -#define MAX_MIX_CHANNELS 4 // Number of mix channels (OpenAL sources) - -#if defined(PLATFORM_RPI) || defined(PLATFORM_ANDROID) - // NOTE: On RPI and Android should be lower to avoid frame-stalls - #define MUSIC_BUFFER_SIZE_SHORT 4096*2 // PCM data buffer (short) - 16Kb (RPI) - #define MUSIC_BUFFER_SIZE_FLOAT 4096 // PCM data buffer (float) - 16Kb (RPI) -#else - // NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care... - #define MUSIC_BUFFER_SIZE_SHORT 4096*8 // PCM data buffer (short) - 64Kb - #define MUSIC_BUFFER_SIZE_FLOAT 4096*4 // PCM data buffer (float) - 64Kb -#endif +#define MAX_STREAM_BUFFERS 2 // Number of buffers for each audio stream + +// NOTE: Music buffer size is defined by number of samples, independent of sample size +// After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds +// and double-buffering system, I concluded that a 4096 samples buffer should be enough +// In case of music-stalls, just inclease this number +#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. short: 32Kb) //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- +typedef enum { MUSIC_AUDIO_OGG = 0, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType; + // Used to create custom audio streams that are not bound to a specific file. -// There can be no more than 4 concurrent mixchannels in use. -// This is due to each active mixc being tied to a dedicated mix channel. -typedef struct MixChannel { - unsigned short sampleRate; // default is 48000 - unsigned char channels; // 1=mono,2=stereo - unsigned char mixChannel; // 0-3 or mixA-mixD, each mix channel can receive up to one dedicated audio stream - bool floatingPoint; // if false then the short datatype is used instead - bool playing; // false if paused - - ALenum alFormat; // OpenAL format specifier - ALuint alSource; // OpenAL source - ALuint alBuffer[MAX_STREAM_BUFFERS]; // OpenAL sample buffer -} MixChannel; +typedef struct AudioStream { + unsigned int sampleRate; // Frequency (samples per second): default is 48000 + unsigned int sampleSize; // BitDepth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels + + ALenum format; // OpenAL format specifier + ALuint source; // OpenAL source + ALuint buffers[MAX_STREAM_BUFFERS]; // OpenAL buffers (double buffering) +} AudioStream; // Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed into a mix channel... typedef struct Music { - stb_vorbis *stream; - jar_xm_context_t *xmctx; // XM chiptune context - jar_mod_context_t modctx; // MOD chiptune context - MixChannel *mixc; // Mix channel - - unsigned int totalSamplesLeft; - float totalLengthSeconds; - bool loop; - bool chipTune; // chiptune is loaded? - bool enabled; -} Music; - -// Audio errors registered + MusicContextType ctxType; // Type of music context (OGG, XM, MOD) + stb_vorbis *ctxOgg; // OGG audio context + jar_xm_context_t *ctxXm; // XM chiptune context + jar_mod_context_t ctxMod; // MOD chiptune context + + AudioStream stream; // Audio stream + + bool loop; // Repeat music after finish (loop) + unsigned int totalSamples; // Total number of samples + unsigned int samplesLeft; // Number of samples left to end +} MusicData, *Music; + +// Audio errors to register +/* typedef enum { ERROR_RAW_CONTEXT_CREATION = 1, ERROR_XM_CONTEXT_CREATION = 2, @@ -152,6 +145,7 @@ typedef enum { ERROR_UNINITIALIZED_CHANNELS = 16384, ERROR_UNINTIALIZED_MUSIC_BUFFER = 32768 } AudioError; +*/ #if defined(AUDIO_STANDALONE) typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; @@ -160,10 +154,7 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static Music musicStreams[MAX_MUSIC_STREAMS]; // Current music loaded, up to two can play at the same time -static MixChannel *mixChannels[MAX_MIX_CHANNELS]; // Mix channels currently active (from music streams) - -static int lastAudioError = 0; // Registers last audio error +static int lastAudioError = 0; // Registers last audio error //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -172,14 +163,11 @@ static Wave LoadWAV(const char *fileName); // Load WAV file static Wave LoadOGG(char *fileName); // Load OGG file static void UnloadWave(Wave wave); // Unload wave data -static bool BufferMusicStream(int index, int numBuffers); // Fill music buffers with data -static void EmptyMusicStream(int index); // Empty music buffers +static AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); +static void CloseAudioStream(AudioStream stream); // Frees mix channel +static int BufferAudioStream(AudioStream stream, void *data, int numberElements); // Pushes more audio data into mix channel -static MixChannel *InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint); -static void CloseMixChannel(MixChannel *mixc); // Frees mix channel -static int BufferMixChannel(MixChannel *mixc, void *data, int numberElements); // Pushes more audio data into mix channel -//static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len); // Pass two arrays of the same legnth in -//static void ResampleByteToFloat(char *chars, float *floats, unsigned short len); // Pass two arrays of same length in +static bool BufferMusicStream(Music music, int numBuffersToProcess); // Fill music buffers with data #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -190,45 +178,44 @@ void TraceLog(int msgType, const char *text, ...); // Outputs a trace log messa // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- -// Initialize audio device and mixc +// Initialize audio device void InitAudioDevice(void) { // Open and initialize a device with default settings ALCdevice *device = alcOpenDevice(NULL); if (!device) TraceLog(ERROR, "Audio device could not be opened"); - - ALCcontext *context = alcCreateContext(device, NULL); - - if ((context == NULL) || (alcMakeContextCurrent(context) == ALC_FALSE)) + else { - if (context != NULL) alcDestroyContext(context); + ALCcontext *context = alcCreateContext(device, NULL); - alcCloseDevice(device); + if ((context == NULL) || (alcMakeContextCurrent(context) == ALC_FALSE)) + { + if (context != NULL) alcDestroyContext(context); - TraceLog(ERROR, "Could not setup mix channel"); - } + alcCloseDevice(device); - TraceLog(INFO, "Audio device and context initialized successfully: %s", alcGetString(device, ALC_DEVICE_SPECIFIER)); + TraceLog(ERROR, "Could not initialize audio context"); + } + else + { + TraceLog(INFO, "Audio device and context initialized successfully: %s", alcGetString(device, ALC_DEVICE_SPECIFIER)); - // Listener definition (just for 2D) - alListener3f(AL_POSITION, 0, 0, 0); - alListener3f(AL_VELOCITY, 0, 0, 0); - alListener3f(AL_ORIENTATION, 0, 0, -1); + // Listener definition (just for 2D) + alListener3f(AL_POSITION, 0, 0, 0); + alListener3f(AL_VELOCITY, 0, 0, 0); + alListener3f(AL_ORIENTATION, 0, 0, -1); + } + } } // Close the audio device for all contexts void CloseAudioDevice(void) { - for (int index = 0; index < MAX_MUSIC_STREAMS; index++) - { - if (musicStreams[index].mixc) StopMusicStreamEx(index); // Stop music streaming and close current stream - } - ALCdevice *device; ALCcontext *context = alcGetCurrentContext(); - if (context == NULL) TraceLog(WARNING, "Could not get current mix channel for closing"); + if (context == NULL) TraceLog(WARNING, "Could not get current audio context for closing"); device = alcGetContextsDevice(context); @@ -252,300 +239,30 @@ bool IsAudioDeviceReady(void) } } -//---------------------------------------------------------------------------------- -// Module Functions Definition - Custom audio output -//---------------------------------------------------------------------------------- - -// Init mix channel for streaming -// The mixChannel is what audio muxing channel you want to operate on, 0-3 are the ones available. -// Each mix channel can only be used one at a time. -static MixChannel *InitMixChannel(unsigned short sampleRate, unsigned char mixChannel, unsigned char channels, bool floatingPoint) -{ - if (mixChannel >= MAX_MIX_CHANNELS) return NULL; - if (!IsAudioDeviceReady()) InitAudioDevice(); - - if (!mixChannels[mixChannel]) - { - MixChannel *mixc = (MixChannel *)malloc(sizeof(MixChannel)); - mixc->sampleRate = sampleRate; - mixc->channels = channels; - mixc->mixChannel = mixChannel; - mixc->floatingPoint = floatingPoint; - mixChannels[mixChannel] = mixc; - - // Setup OpenAL format - if (channels == 1) - { - if (floatingPoint) mixc->alFormat = AL_FORMAT_MONO_FLOAT32; - else mixc->alFormat = AL_FORMAT_MONO16; - } - else if (channels == 2) - { - if (floatingPoint) mixc->alFormat = AL_FORMAT_STEREO_FLOAT32; - else mixc->alFormat = AL_FORMAT_STEREO16; - } - - // Create an audio source - alGenSources(1, &mixc->alSource); - alSourcef(mixc->alSource, AL_PITCH, 1); - alSourcef(mixc->alSource, AL_GAIN, 1); - alSource3f(mixc->alSource, AL_POSITION, 0, 0, 0); - alSource3f(mixc->alSource, AL_VELOCITY, 0, 0, 0); - - // Create Buffer - alGenBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer); - - // Fill buffers - for (int i = 0; i < MAX_STREAM_BUFFERS; i++) - { - // Initialize buffer with zeros by default - if (mixc->floatingPoint) - { - float pcm[MUSIC_BUFFER_SIZE_FLOAT] = { 0.0f }; - alBufferData(mixc->alBuffer[i], mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_FLOAT*sizeof(float), mixc->sampleRate); - } - else - { - short pcm[MUSIC_BUFFER_SIZE_SHORT] = { 0 }; - alBufferData(mixc->alBuffer[i], mixc->alFormat, pcm, MUSIC_BUFFER_SIZE_SHORT*sizeof(short), mixc->sampleRate); - } - } - - alSourceQueueBuffers(mixc->alSource, MAX_STREAM_BUFFERS, mixc->alBuffer); - mixc->playing = true; - alSourcePlay(mixc->alSource); - - return mixc; - } - - return NULL; -} - -// Frees buffer in mix channel -static void CloseMixChannel(MixChannel *mixc) -{ - if (mixc) - { - alSourceStop(mixc->alSource); - mixc->playing = false; - - // Flush out all queued buffers - ALuint buffer = 0; - int queued = 0; - alGetSourcei(mixc->alSource, AL_BUFFERS_QUEUED, &queued); - - while (queued > 0) - { - alSourceUnqueueBuffers(mixc->alSource, 1, &buffer); - queued--; - } - - // Delete source and buffers - alDeleteSources(1, &mixc->alSource); - alDeleteBuffers(MAX_STREAM_BUFFERS, mixc->alBuffer); - mixChannels[mixc->mixChannel] = NULL; - free(mixc); - mixc = NULL; - } -} - -// Pushes more audio data into mix channel, only one buffer per call -// Call "BufferMixChannel(mixc, NULL, 0)" if you want to pause the audio. -// Returns number of samples that where processed. -static int BufferMixChannel(MixChannel *mixc, void *data, int numberElements) -{ - if (!mixc || (mixChannels[mixc->mixChannel] != mixc)) return 0; // When there is two channels there must be an even number of samples - - if (!data || !numberElements) - { - // Pauses audio until data is given - if (mixc->playing) - { - alSourcePause(mixc->alSource); - mixc->playing = false; - } - - return 0; - } - else if (!mixc->playing) - { - // Restart audio otherwise - alSourcePlay(mixc->alSource); - mixc->playing = true; - } - - ALuint buffer = 0; - - alSourceUnqueueBuffers(mixc->alSource, 1, &buffer); - if (!buffer) return 0; - - if (mixc->floatingPoint) - { - // Process float buffers - float *ptr = (float *)data; - alBufferData(buffer, mixc->alFormat, ptr, numberElements*sizeof(float), mixc->sampleRate); - } - else - { - // Process short buffers - short *ptr = (short *)data; - alBufferData(buffer, mixc->alFormat, ptr, numberElements*sizeof(short), mixc->sampleRate); - } - - alSourceQueueBuffers(mixc->alSource, 1, &buffer); - - return numberElements; -} - -/* -// Convert data from short to float -// example usage: -// short sh[3] = {1,2,3};float fl[3]; -// ResampleShortToFloat(sh,fl,3); -static void ResampleShortToFloat(short *shorts, float *floats, unsigned short len) -{ - for (int i = 0; i < len; i++) - { - if (shorts[i] < 0) floats[i] = (float)shorts[i]/32766.0f; - else floats[i] = (float)shorts[i]/32767.0f; - } -} - -// Convert data from float to short -// example usage: -// char ch[3] = {1,2,3};float fl[3]; -// ResampleByteToFloat(ch,fl,3); -static void ResampleByteToFloat(char *chars, float *floats, unsigned short len) -{ - for (int i = 0; i < len; i++) - { - if (chars[i] < 0) floats[i] = (float)chars[i]/127.0f; - else floats[i] = (float)chars[i]/128.0f; - } -} -*/ - -// Initialize raw audio mix channel for audio buffering -// NOTE: Returns mix channel index or -1 if it fails (errors are registered on lastAudioError) -int InitRawMixChannel(int sampleRate, int channels, bool floatingPoint) -{ - int mixIndex; - - for (mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot - { - if (mixChannels[mixIndex] == NULL) break; - else if (mixIndex == (MAX_MIX_CHANNELS - 1)) - { - lastAudioError = ERROR_OUT_OF_MIX_CHANNELS; - return -1; - } - } - - if (InitMixChannel(sampleRate, mixIndex, channels, floatingPoint)) return mixIndex; - else - { - lastAudioError = ERROR_RAW_CONTEXT_CREATION; - return -1; - } -} - -// Buffers data directly to raw mix channel -// if 0 is returned, buffers are still full and you need to keep trying with the same data -// otherwise it will return number of samples buffered. -// NOTE: Data could be either be an array of floats or shorts, depending on the created context -int BufferRawAudioContext(int ctx, void *data, unsigned short numberElements) -{ - int numBuffered = 0; - - if (ctx >= 0) - { - MixChannel *mixc = mixChannels[ctx]; - numBuffered = BufferMixChannel(mixc, data, numberElements); - } - - return numBuffered; -} - -// Closes and frees raw mix channel -void CloseRawAudioContext(int ctx) -{ - if (mixChannels[ctx]) CloseMixChannel(mixChannels[ctx]); -} - //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- // Load sound to memory +// NOTE: The entire file is loaded to memory to be played (no-streaming) Sound LoadSound(char *fileName) { - Sound sound = { 0 }; Wave wave = { 0 }; - // NOTE: The entire file is loaded to memory to play it all at once (no-streaming) - - // Audio file loading - // NOTE: Buffer space is allocated inside function, Wave must be freed - - if (strcmp(GetExtension(fileName),"wav") == 0) wave = LoadWAV(fileName); - else if (strcmp(GetExtension(fileName),"ogg") == 0) wave = LoadOGG(fileName); - else - { - TraceLog(WARNING, "[%s] Sound extension not recognized, it can't be loaded", fileName); - - // TODO: Find a better way to register errors (similar to glGetError()) - lastAudioError = ERROR_EXTENSION_NOT_RECOGNIZED; - } - - if (wave.data != NULL) - { - ALenum format = 0; - // The OpenAL format is worked out by looking at the number of channels and the bits per sample - if (wave.channels == 1) - { - if (wave.bitsPerSample == 8 ) format = AL_FORMAT_MONO8; - else if (wave.bitsPerSample == 16) format = AL_FORMAT_MONO16; - } - else if (wave.channels == 2) - { - if (wave.bitsPerSample == 8 ) format = AL_FORMAT_STEREO8; - else if (wave.bitsPerSample == 16) format = AL_FORMAT_STEREO16; - } - - // Create an audio source - ALuint source; - alGenSources(1, &source); // Generate pointer to audio source + if (strcmp(GetExtension(fileName), "wav") == 0) wave = LoadWAV(fileName); + else if (strcmp(GetExtension(fileName), "ogg") == 0) wave = LoadOGG(fileName); + else TraceLog(WARNING, "[%s] Sound extension not recognized, it can't be loaded", fileName); - alSourcef(source, AL_PITCH, 1); - alSourcef(source, AL_GAIN, 1); - alSource3f(source, AL_POSITION, 0, 0, 0); - alSource3f(source, AL_VELOCITY, 0, 0, 0); - alSourcei(source, AL_LOOPING, AL_FALSE); - - // Convert loaded data to OpenAL buffer - //---------------------------------------- - ALuint buffer; - alGenBuffers(1, &buffer); // Generate pointer to buffer - - // Upload sound data to buffer - alBufferData(buffer, format, wave.data, wave.dataSize, wave.sampleRate); - - // Attach sound buffer to source - alSourcei(source, AL_BUFFER, buffer); - - TraceLog(INFO, "[%s] Sound file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); - - // Unallocate WAV data - UnloadWave(wave); - - sound.source = source; - sound.buffer = buffer; - } + Sound sound = LoadSoundFromWave(wave); + + // Sound is loaded, we can unload wave + UnloadWave(wave); return sound; } // Load sound from wave data +// NOTE: Wave data must be unallocated manually Sound LoadSoundFromWave(Wave wave) { Sound sound = { 0 }; @@ -586,10 +303,7 @@ Sound LoadSoundFromWave(Wave wave) // Attach sound buffer to source alSourcei(source, AL_BUFFER, buffer); - // Unallocate WAV data - UnloadWave(wave); - - TraceLog(INFO, "[Wave] Sound file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", wave.sampleRate, wave.bitsPerSample, wave.channels); + TraceLog(INFO, "[SND ID %i][BUFR ID %i] Sound data loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", source, buffer, wave.sampleRate, wave.bitsPerSample, wave.channels); sound.source = source; sound.buffer = buffer; @@ -619,11 +333,7 @@ Sound LoadSoundFromRES(const char *rresName, int resId) FILE *rresFile = fopen(rresName, "rb"); - if (rresFile == NULL) - { - TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); - lastAudioError = ERROR_UNABLE_TO_OPEN_RRES_FILE; - } + if (rresFile == NULL) TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); else { // Read rres file (basic file check - id) @@ -637,7 +347,6 @@ Sound LoadSoundFromRES(const char *rresName, int resId) if ((id[0] != 'r') && (id[1] != 'R') && (id[2] != 'E') &&(id[3] != 'S')) { TraceLog(WARNING, "[%s] This is not a valid raylib resource file", rresName); - lastAudioError = ERROR_INVALID_RRES_FILE; } else { @@ -681,55 +390,12 @@ Sound LoadSoundFromRES(const char *rresName, int resId) free(data); - // Convert wave to Sound (OpenAL) - ALenum format = 0; - - // The OpenAL format is worked out by looking at the number of channels and the bits per sample - if (wave.channels == 1) - { - if (wave.bitsPerSample == 8 ) format = AL_FORMAT_MONO8; - else if (wave.bitsPerSample == 16) format = AL_FORMAT_MONO16; - } - else if (wave.channels == 2) - { - if (wave.bitsPerSample == 8 ) format = AL_FORMAT_STEREO8; - else if (wave.bitsPerSample == 16) format = AL_FORMAT_STEREO16; - } - - // Create an audio source - ALuint source; - alGenSources(1, &source); // Generate pointer to audio source - - alSourcef(source, AL_PITCH, 1); - alSourcef(source, AL_GAIN, 1); - alSource3f(source, AL_POSITION, 0, 0, 0); - alSource3f(source, AL_VELOCITY, 0, 0, 0); - alSourcei(source, AL_LOOPING, AL_FALSE); - - // Convert loaded data to OpenAL buffer - //---------------------------------------- - ALuint buffer; - alGenBuffers(1, &buffer); // Generate pointer to buffer - - // Upload sound data to buffer - alBufferData(buffer, format, (void*)wave.data, wave.dataSize, wave.sampleRate); - - // Attach sound buffer to source - alSourcei(source, AL_BUFFER, buffer); - - TraceLog(INFO, "[%s] Sound loaded successfully from resource (SampleRate: %i, BitRate: %i, Channels: %i)", rresName, wave.sampleRate, wave.bitsPerSample, wave.channels); - - // Unallocate WAV data + sound = LoadSoundFromWave(wave); + + // Sound is loaded, we can unload wave data UnloadWave(wave); - - sound.source = source; - sound.buffer = buffer; - } - else - { - TraceLog(WARNING, "[%s] Required resource do not seem to be a valid SOUND resource", rresName); - lastAudioError = ERROR_INVALID_RRES_RESOURCE; } + else TraceLog(WARNING, "[%s] Required resource do not seem to be a valid SOUND resource", rresName); } else { @@ -764,7 +430,7 @@ void UnloadSound(Sound sound) alDeleteSources(1, &sound.source); alDeleteBuffers(1, &sound.buffer); - TraceLog(INFO, "Unloaded sound data"); + TraceLog(INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); } // Play a sound @@ -794,6 +460,16 @@ void PauseSound(Sound sound) alSourcePause(sound.source); } +// Resume a paused sound +void ResumeSound(Sound sound) +{ + ALenum state; + + alGetSourcei(sound.source, AL_SOURCE_STATE, &state); + + if (state == AL_PAUSED) alSourcePlay(sound.source); +} + // Stop reproducing a sound void StopSound(Sound sound) { @@ -828,409 +504,426 @@ void SetSoundPitch(Sound sound, float pitch) // Module Functions Definition - Music loading and stream playing (.OGG) //---------------------------------------------------------------------------------- -MusicBuffer LoadMusicBufferStream(char *fileName, int index) +// Load music stream from file +Music LoadMusicStream(char *fileName) { - MusicBuffer buffer = { 0 }; + Music music = (MusicData *)malloc(sizeof(MusicData)); - if(index > MAX_MUSIC_STREAMS) + if (strcmp(GetExtension(fileName), "ogg") == 0) { - TraceLog("[%s] index is greater than MAX_MUSIC_STREAMS", ERROR); - return; // error - } - - buffer.fileName = fileName; - buffer.index = index; - - - if (musicStreams[buffer.index].stream || musicStreams[buffer.index].xmctx) return; // error + // Open ogg audio stream + music->ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); - return buffer; -} - -// Start music playing (open stream) -// returns 0 on success or error code -int PlayMusicStream(MusicBuffer musicBuffer) -{ - if(musicBuffer.fileName == 0) - { - return ERROR_UNINTIALIZED_MUSIC_BUFFER; - } - int mixIndex; - for (mixIndex = 0; mixIndex < MAX_MIX_CHANNELS; mixIndex++) // find empty mix channel slot - { - if (mixChannels[mixIndex] == NULL) break; - else if (mixIndex == (MAX_MIX_CHANNELS - 1)) return ERROR_OUT_OF_MIX_CHANNELS; // error - } - - if (strcmp(GetExtension(musicBuffer.fileName),"ogg") == 0) - { - // Open audio stream - musicStreams[musicBuffer.index].stream = stb_vorbis_open_filename(musicBuffer.fileName, NULL, NULL); - - if (musicStreams[musicBuffer.index].stream == NULL) + if (music->ctxOgg == NULL) { - TraceLog(WARNING, "[%s] OGG audio file could not be opened", musicBuffer.fileName); - return ERROR_LOADING_OGG; // error + TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); } else { - // Get file info - stb_vorbis_info info = stb_vorbis_get_info(musicStreams[musicBuffer.index].stream); - - TraceLog(INFO, "[%s] Ogg sample rate: %i", musicBuffer.fileName, info.sample_rate); - TraceLog(INFO, "[%s] Ogg channels: %i", musicBuffer.fileName, info.channels); - TraceLog(DEBUG, "[%s] Temp memory required: %i", musicBuffer.fileName, info.temp_memory_required); + stb_vorbis_info info = stb_vorbis_get_info(music->ctxOgg); // Get Ogg file info - musicStreams[musicBuffer.index].loop = true; // We loop by default - musicStreams[musicBuffer.index].enabled = true; + TraceLog(DEBUG, "[%s] Ogg sample rate: %i", fileName, info.sample_rate); + TraceLog(DEBUG, "[%s] Ogg channels: %i", fileName, info.channels); + TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required); + // TODO: Support 32-bit sampleSize OGGs + music->stream = InitAudioStream(info.sample_rate, 16, info.channels); + + music->totalSamples = (unsigned int)stb_vorbis_stream_length_in_samples(music->ctxOgg)*info.channels; + music->samplesLeft = music->totalSamples; + //float totalLengthSeconds = stb_vorbis_stream_length_in_seconds(music->ctxOgg); - musicStreams[musicBuffer.index].totalSamplesLeft = (unsigned int)stb_vorbis_stream_length_in_samples(musicStreams[musicBuffer.index].stream) * info.channels; - musicStreams[musicBuffer.index].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(musicStreams[musicBuffer.index].stream); - - if (info.channels == 2) - { - musicStreams[musicBuffer.index].mixc = InitMixChannel(info.sample_rate, mixIndex, 2, false); - musicStreams[musicBuffer.index].mixc->playing = true; - } - else - { - musicStreams[musicBuffer.index].mixc = InitMixChannel(info.sample_rate, mixIndex, 1, false); - musicStreams[musicBuffer.index].mixc->playing = true; - } - - if (!musicStreams[musicBuffer.index].mixc) return ERROR_LOADING_OGG; // error + music->ctxType = MUSIC_AUDIO_OGG; + music->loop = true; // We loop by default } } - else if (strcmp(GetExtension(musicBuffer.fileName),"xm") == 0) + else if (strcmp(GetExtension(fileName), "xm") == 0) { - // only stereo is supported for xm - if (!jar_xm_create_context_from_file(&musicStreams[musicBuffer.index].xmctx, 48000, musicBuffer.fileName)) + int result = jar_xm_create_context_from_file(&music->ctxXm, 48000, fileName); + + if (!result) // XM context created successfully { - musicStreams[musicBuffer.index].chipTune = true; - musicStreams[musicBuffer.index].loop = true; - jar_xm_set_max_loop_count(musicStreams[musicBuffer.index].xmctx, 0); // infinite number of loops - musicStreams[musicBuffer.index].totalSamplesLeft = (unsigned int)jar_xm_get_remaining_samples(musicStreams[musicBuffer.index].xmctx); - musicStreams[musicBuffer.index].totalLengthSeconds = ((float)musicStreams[musicBuffer.index].totalSamplesLeft)/48000.0f; - musicStreams[musicBuffer.index].enabled = true; - - TraceLog(INFO, "[%s] XM number of samples: %i", musicBuffer.fileName, musicStreams[musicBuffer.index].totalSamplesLeft); - TraceLog(INFO, "[%s] XM track length: %11.6f sec", musicBuffer.fileName, musicStreams[musicBuffer.index].totalLengthSeconds); - - musicStreams[musicBuffer.index].mixc = InitMixChannel(48000, mixIndex, 2, true); - - if (!musicStreams[musicBuffer.index].mixc) return ERROR_XM_CONTEXT_CREATION; // error - - musicStreams[musicBuffer.index].mixc->playing = true; - } - else - { - TraceLog(WARNING, "[%s] XM file could not be opened", musicBuffer.fileName); - return ERROR_LOADING_XM; // error + jar_xm_set_max_loop_count(music->ctxXm, 0); // Set infinite number of loops + + music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); + music->samplesLeft = music->totalSamples; + + TraceLog(INFO, "[%s] XM number of samples: %i", fileName, music->totalSamples); + TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + + // NOTE: Only stereo is supported for XM + music->stream = InitAudioStream(48000, 32, 2); + + music->ctxType = MUSIC_MODULE_XM; + music->loop = true; } + else TraceLog(WARNING, "[%s] XM file could not be opened", fileName); } - else if (strcmp(GetExtension(musicBuffer.fileName),"mod") == 0) + else if (strcmp(GetExtension(fileName), "mod") == 0) { - jar_mod_init(&musicStreams[musicBuffer.index].modctx); + jar_mod_init(&music->ctxMod); - if (jar_mod_load_file(&musicStreams[musicBuffer.index].modctx, musicBuffer.fileName)) + if (jar_mod_load_file(&music->ctxMod, fileName)) { - musicStreams[musicBuffer.index].chipTune = true; - musicStreams[musicBuffer.index].loop = true; - musicStreams[musicBuffer.index].totalSamplesLeft = (unsigned int)jar_mod_max_samples(&musicStreams[musicBuffer.index].modctx); - musicStreams[musicBuffer.index].totalLengthSeconds = ((float)musicStreams[musicBuffer.index].totalSamplesLeft)/48000.0f; - musicStreams[musicBuffer.index].enabled = true; + music->totalSamples = (unsigned int)jar_mod_max_samples(&music->ctxMod); + music->samplesLeft = music->totalSamples; - TraceLog(INFO, "[%s] MOD number of samples: %i", musicBuffer.fileName, musicStreams[musicBuffer.index].totalSamplesLeft); - TraceLog(INFO, "[%s] MOD track length: %11.6f sec", musicBuffer.fileName, musicStreams[musicBuffer.index].totalLengthSeconds); + TraceLog(INFO, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); + TraceLog(INFO, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); - musicStreams[musicBuffer.index].mixc = InitMixChannel(48000, mixIndex, 2, false); + music->stream = InitAudioStream(48000, 16, 2); - if (!musicStreams[musicBuffer.index].mixc) return ERROR_MOD_CONTEXT_CREATION; // error - - musicStreams[musicBuffer.index].mixc->playing = true; - } - else - { - TraceLog(WARNING, "[%s] MOD file could not be opened", musicBuffer.fileName); - return ERROR_LOADING_MOD; // error + music->ctxType = MUSIC_MODULE_MOD; + music->loop = true; } + else TraceLog(WARNING, "[%s] MOD file could not be opened", fileName); } - else - { - TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", musicBuffer.fileName); - return ERROR_EXTENSION_NOT_RECOGNIZED; // error - } + else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); - return 0; // normal return + return music; } -// Stop music playing for individual music index of musicStreams array (close stream) -void StopMusicStream(MusicBuffer musicBuffer) +// Unload music stream +void UnloadMusicStream(Music music) { - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc) - { - CloseMixChannel(musicStreams[musicBuffer.index].mixc); - - if (musicStreams[musicBuffer.index].xmctx) - jar_xm_free_context(musicStreams[musicBuffer.index].xmctx); - else if (musicStreams[musicBuffer.index].modctx.mod_loaded) - jar_mod_unload(&musicStreams[musicBuffer.index].modctx); - else - stb_vorbis_close(musicStreams[musicBuffer.index].stream); - - musicStreams[musicBuffer.index].enabled = false; + CloseAudioStream(music->stream); + + if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); + else if (music->ctxType == MUSIC_MODULE_XM) jar_xm_free_context(music->ctxXm); + else if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_unload(&music->ctxMod); + + free(music); +} - if (musicStreams[musicBuffer.index].stream || musicStreams[musicBuffer.index].xmctx) - { - musicStreams[musicBuffer.index].stream = NULL; - musicStreams[musicBuffer.index].xmctx = NULL; - } - } +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + alSourcePlay(music->stream.source); } -void StopMusicStreamEx(int index) +// Pause music playing +void PauseMusicStream(Music music) { - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) - { - CloseMixChannel(musicStreams[index].mixc); + alSourcePause(music->stream.source); +} - if (musicStreams[index].xmctx) - jar_xm_free_context(musicStreams[index].xmctx); - else if (musicStreams[index].modctx.mod_loaded) - jar_mod_unload(&musicStreams[index].modctx); - else - stb_vorbis_close(musicStreams[index].stream); +// Resume music playing +void ResumeMusicStream(Music music) +{ + ALenum state; + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); - musicStreams[index].enabled = false; + if (state == AL_PAUSED) alSourcePlay(music->stream.source); +} - if (musicStreams[index].stream || musicStreams[index].xmctx) - { - musicStreams[index].stream = NULL; - musicStreams[index].xmctx = NULL; - } - } +// Stop music playing (close stream) +void StopMusicStream(Music music) +{ + alSourceStop(music->stream.source); } // Update (re-fill) music buffers if data already processed -void UpdateMusicStream(MusicBuffer musicBuffer) +void UpdateMusicStream(Music music) { ALenum state; bool active = true; ALint processed = 0; // Determine if music stream is ready to be written - alGetSourcei(musicStreams[musicBuffer.index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); - if (musicStreams[musicBuffer.index].mixc->playing && (musicBuffer.index < MAX_MUSIC_STREAMS) && musicStreams[musicBuffer.index].enabled && musicStreams[musicBuffer.index].mixc && (processed > 0)) + if (processed > 0) { - active = BufferMusicStream(musicBuffer.index, processed); + active = BufferMusicStream(music, processed); - if (!active && musicStreams[musicBuffer.index].loop) + if (!active && music->loop) { - if (musicStreams[musicBuffer.index].chipTune) - { - if(musicStreams[musicBuffer.index].modctx.mod_loaded) jar_mod_seek_start(&musicStreams[musicBuffer.index].modctx); - - musicStreams[musicBuffer.index].totalSamplesLeft = musicStreams[musicBuffer.index].totalLengthSeconds*48000.0f; - } - else - { - stb_vorbis_seek_start(musicStreams[musicBuffer.index].stream); - musicStreams[musicBuffer.index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(musicStreams[musicBuffer.index].stream)*musicStreams[musicBuffer.index].mixc->channels; - } + // Restart music context (if required) + if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_seek_start(&music->ctxMod); + else if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_seek_start(music->ctxOgg); + + music->samplesLeft = music->totalSamples; // Determine if music stream is ready to be written - alGetSourcei(musicStreams[musicBuffer.index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); - active = BufferMusicStream(musicBuffer.index, processed); + active = BufferMusicStream(music, processed); } if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); - alGetSourcei(musicStreams[musicBuffer.index].mixc->alSource, AL_SOURCE_STATE, &state); - - if (state != AL_PLAYING && active) alSourcePlay(musicStreams[musicBuffer.index].mixc->alSource); + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); - if (!active) StopMusicStream(musicBuffer); + if (state != AL_PLAYING && active) alSourcePlay(music->stream.source); - } -} - -//get number of music channels active at this time, this does not mean they are playing -int GetMusicStreamCount(void) -{ - int musicCount = 0; - - // Find empty music slot - for (int musicIndex = 0; musicIndex < MAX_MUSIC_STREAMS; musicIndex++) - { - if(musicStreams[musicIndex].stream != NULL || musicStreams[musicIndex].chipTune) musicCount++; - } - - return musicCount; -} - -// Pause music playing -void PauseMusicStream(MusicBuffer musicBuffer) -{ - // Pause music stream if music available! - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc && musicStreams[musicBuffer.index].enabled) - { - TraceLog(INFO, "Pausing music stream"); - alSourcePause(musicStreams[musicBuffer.index].mixc->alSource); - musicStreams[musicBuffer.index].mixc->playing = false; - } -} - -// Resume music playing -void ResumeMusicStream(MusicBuffer musicBuffer) -{ - // Resume music playing... if music available! - ALenum state; - - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc) - { - alGetSourcei(musicStreams[musicBuffer.index].mixc->alSource, AL_SOURCE_STATE, &state); - - if (state == AL_PAUSED) - { - TraceLog(INFO, "Resuming music stream"); - alSourcePlay(musicStreams[musicBuffer.index].mixc->alSource); - musicStreams[musicBuffer.index].mixc->playing = true; - } + if (!active) StopMusicStream(music); } } // Check if any music is playing -bool IsMusicPlaying(MusicBuffer musicBuffer) +bool IsMusicPlaying(Music music) { bool playing = false; ALint state; - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc) - { - alGetSourcei(musicStreams[musicBuffer.index].mixc->alSource, AL_SOURCE_STATE, &state); + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); - if (state == AL_PLAYING) playing = true; - } + if (state == AL_PLAYING) playing = true; return playing; } // Set volume for music -void SetMusicVolume(MusicBuffer musicBuffer, float volume) +void SetMusicVolume(Music music, float volume) { - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc) - { - alSourcef(musicStreams[musicBuffer.index].mixc->alSource, AL_GAIN, volume); - } + alSourcef(music->stream.source, AL_GAIN, volume); } // Set pitch for music -void SetMusicPitch(MusicBuffer musicBuffer, float pitch) +void SetMusicPitch(Music music, float pitch) { - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc) - { - alSourcef(musicStreams[musicBuffer.index].mixc->alSource, AL_PITCH, pitch); - } + alSourcef(music->stream.source, AL_PITCH, pitch); } // Get music time length (in seconds) -float GetMusicTimeLength(MusicBuffer musicBuffer) +float GetMusicTimeLength(Music music) { - float totalSeconds; - - if (musicStreams[musicBuffer.index].chipTune) totalSeconds = (float)musicStreams[musicBuffer.index].totalLengthSeconds; - else totalSeconds = stb_vorbis_stream_length_in_seconds(musicStreams[musicBuffer.index].stream); - + float totalSeconds = (float)music->totalSamples/music->stream.sampleRate; + return totalSeconds; } // Get current music time played (in seconds) -float GetMusicTimePlayed(MusicBuffer musicBuffer) +float GetMusicTimePlayed(Music music) { float secondsPlayed = 0.0f; - if (musicBuffer.index < MAX_MUSIC_STREAMS && musicStreams[musicBuffer.index].mixc) + if (music->ctxType == MUSIC_MODULE_XM) { - if (musicStreams[musicBuffer.index].chipTune && musicStreams[musicBuffer.index].xmctx) + uint64_t samplesPlayed; + jar_xm_get_position(music->ctxXm, NULL, NULL, NULL, &samplesPlayed); + + // TODO: Not sure if this is the correct value + secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); + } + else if (music->ctxType == MUSIC_MODULE_MOD) + { + long samplesPlayed = jar_mod_current_samples(&music->ctxMod); + + secondsPlayed = (float)samplesPlayed/music->stream.sampleRate; + } + else if (music->ctxType == MUSIC_AUDIO_OGG) + { + unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; + + secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); + } + + return secondsPlayed; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Init audio stream (to stream audio pcm data) +static AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +{ + AudioStream stream = { 0 }; + + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; + stream.channels = channels; + + // Setup OpenAL format + if (channels == 1) + { + switch (sampleSize) { - uint64_t samples; - jar_xm_get_position(musicStreams[musicBuffer.index].xmctx, NULL, NULL, NULL, &samples); - secondsPlayed = (float)samples/(48000.0f*musicStreams[musicBuffer.index].mixc->channels); // Not sure if this is the correct value + case 8: stream.format = AL_FORMAT_MONO8; break; + case 16: stream.format = AL_FORMAT_MONO16; break; + case 32: stream.format = AL_FORMAT_MONO_FLOAT32; break; + default: TraceLog(WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; } - else if(musicStreams[musicBuffer.index].chipTune && musicStreams[musicBuffer.index].modctx.mod_loaded) + } + else if (channels == 2) + { + switch (sampleSize) { - long numsamp = jar_mod_current_samples(&musicStreams[musicBuffer.index].modctx); - secondsPlayed = (float)numsamp/(48000.0f); + case 8: stream.format = AL_FORMAT_STEREO8; break; + case 16: stream.format = AL_FORMAT_STEREO16; break; + case 32: stream.format = AL_FORMAT_STEREO_FLOAT32; break; + default: TraceLog(WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; } - else + } + else TraceLog(WARNING, "Init audio stream: Number of channels not supported: %i", channels); + + // Create an audio source + alGenSources(1, &stream.source); + alSourcef(stream.source, AL_PITCH, 1); + alSourcef(stream.source, AL_GAIN, 1); + alSource3f(stream.source, AL_POSITION, 0, 0, 0); + alSource3f(stream.source, AL_VELOCITY, 0, 0, 0); + + // Create Buffers + alGenBuffers(MAX_STREAM_BUFFERS, stream.buffers); + + // Initialize buffer with zeros by default + for (int i = 0; i < MAX_STREAM_BUFFERS; i++) + { + if (stream.sampleSize == 8) { - int totalSamples = stb_vorbis_stream_length_in_samples(musicStreams[musicBuffer.index].stream)*musicStreams[musicBuffer.index].mixc->channels; - int samplesPlayed = totalSamples - musicStreams[musicBuffer.index].totalSamplesLeft; - secondsPlayed = (float)samplesPlayed/(musicStreams[musicBuffer.index].mixc->sampleRate*musicStreams[musicBuffer.index].mixc->channels); + unsigned char pcm[AUDIO_BUFFER_SIZE] = { 0 }; + alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(unsigned char), stream.sampleRate); + } + else if (stream.sampleSize == 16) + { + short pcm[AUDIO_BUFFER_SIZE] = { 0 }; + alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(short), stream.sampleRate); + } + else if (stream.sampleSize == 32) + { + float pcm[AUDIO_BUFFER_SIZE] = { 0.0f }; + alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(float), stream.sampleRate); } } - return secondsPlayed; + alSourceQueueBuffers(stream.source, MAX_STREAM_BUFFERS, stream.buffers); + + TraceLog(INFO, "[AUD ID %i] Audio stream loaded successfully", stream.source); + + return stream; } -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- +// Close audio stream and free memory +static void CloseAudioStream(AudioStream stream) +{ + // Stop playing channel + alSourceStop(stream.source); + + // Flush out all queued buffers + int queued = 0; + alGetSourcei(stream.source, AL_BUFFERS_QUEUED, &queued); + + ALuint buffer = 0; + + while (queued > 0) + { + alSourceUnqueueBuffers(stream.source, 1, &buffer); + queued--; + } + + // Delete source and buffers + alDeleteSources(1, &stream.source); + alDeleteBuffers(MAX_STREAM_BUFFERS, stream.buffers); + + TraceLog(INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); +} + +// Push more audio data into audio stream, only one buffer per call +// NOTE: Returns number of samples that were processed +static int BufferAudioStream(AudioStream stream, void *data, int numberElements) +{ + if (!data || !numberElements) + { + // Pauses audio until data is given + alSourcePause(stream.source); + return 0; + } + + ALuint buffer = 0; + alSourceUnqueueBuffers(stream.source, 1, &buffer); + + if (!buffer) return 0; + + // Reference + //void alBufferData(ALuint bufferName, ALenum format, const ALvoid *data, ALsizei size, ALsizei frequency); + + // ALuint bufferName: buffer id + // ALenum format: Valid formats are + // AL_FORMAT_MONO8, // unsigned char + // AL_FORMAT_MONO16, // short + // AL_FORMAT_STEREO8, + // AL_FORMAT_STEREO16 // stereo data is interleaved: left+right channels sample + // AL_FORMAT_MONO_FLOAT32 (extension) + // AL_FORMAT_STEREO_FLOAT32 (extension) + // ALsizei size: Number of bytes, must be coherent with format + // ALsizei frequency: sample rate + + if (stream.sampleSize == 8) alBufferData(buffer, stream.format, (unsigned char *)data, numberElements*sizeof(unsigned char), stream.sampleRate); + else if (stream.sampleSize == 16) alBufferData(buffer, stream.format, (short *)data, numberElements*sizeof(short), stream.sampleRate); + else if (stream.sampleSize == 32) alBufferData(buffer, stream.format, (float *)data, numberElements*sizeof(float), stream.sampleRate); + + alSourceQueueBuffers(stream.source, 1, &buffer); + + return numberElements; +} // Fill music buffers with new data from music stream -static bool BufferMusicStream(int index, int numBuffers) +static bool BufferMusicStream(Music music, int numBuffersToProcess) { - short pcm[MUSIC_BUFFER_SIZE_SHORT]; - float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; + short pcm[AUDIO_BUFFER_SIZE]; + float pcmf[AUDIO_BUFFER_SIZE]; int size = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts bool active = true; // We can get more data from stream (not finished) - if (musicStreams[index].chipTune) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. + if (music->ctxType == MUSIC_MODULE_XM) // There is no end of stream for xmfiles, once the end is reached zeros are generated for non looped chiptunes. { - for (int i = 0; i < numBuffers; i++) + for (int i = 0; i < numBuffersToProcess; i++) { - if (musicStreams[index].modctx.mod_loaded) - { - if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT/2; - else size = musicStreams[index].totalSamplesLeft/2; + if (music->samplesLeft >= AUDIO_BUFFER_SIZE) size = AUDIO_BUFFER_SIZE/2; + else size = music->samplesLeft/2; - jar_mod_fillbuffer(&musicStreams[index].modctx, pcm, size, 0 ); - BufferMixChannel(musicStreams[index].mixc, pcm, size*2); - } - else if (musicStreams[index].xmctx) - { - if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_FLOAT) size = MUSIC_BUFFER_SIZE_FLOAT/2; - else size = musicStreams[index].totalSamplesLeft/2; + // Read 2*shorts and moves them to buffer+size memory location + jar_xm_generate_samples(music->ctxXm, pcmf, size); + + BufferAudioStream(music->stream, pcmf, size*2); + + music->samplesLeft -= size; - jar_xm_generate_samples(musicStreams[index].xmctx, pcmf, size); // reads 2*readlen shorts and moves them to buffer+size memory location - BufferMixChannel(musicStreams[index].mixc, pcmf, size*2); + if (music->samplesLeft <= 0) + { + active = false; + break; } + } + } + else if (music->ctxType == MUSIC_MODULE_MOD) + { + for (int i = 0; i < numBuffersToProcess; i++) + { + if (music->samplesLeft >= AUDIO_BUFFER_SIZE) size = AUDIO_BUFFER_SIZE/2; + else size = music->samplesLeft/2; + + jar_mod_fillbuffer(&music->ctxMod, pcm, size, 0); + + BufferAudioStream(music->stream, pcm, size*2); - musicStreams[index].totalSamplesLeft -= size; + music->samplesLeft -= size; - if (musicStreams[index].totalSamplesLeft <= 0) + if (music->samplesLeft <= 0) { active = false; break; } } } - else + else if (music->ctxType == MUSIC_AUDIO_OGG) { - if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT; - else size = musicStreams[index].totalSamplesLeft; + if (music->samplesLeft >= AUDIO_BUFFER_SIZE) size = AUDIO_BUFFER_SIZE; + else size = music->samplesLeft; - for (int i = 0; i < numBuffers; i++) + for (int i = 0; i < numBuffersToProcess; i++) { - int streamedBytes = stb_vorbis_get_samples_short_interleaved(musicStreams[index].stream, musicStreams[index].mixc->channels, pcm, size); - BufferMixChannel(musicStreams[index].mixc, pcm, streamedBytes * musicStreams[index].mixc->channels); - musicStreams[index].totalSamplesLeft -= streamedBytes * musicStreams[index].mixc->channels; - - if (musicStreams[index].totalSamplesLeft <= 0) + // NOTE: Returns the number of samples stored per channel + int numSamples = stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, pcm, size); + + BufferAudioStream(music->stream, pcm, numSamples*music->stream.channels); + + music->samplesLeft -= (numSamples*music->stream.channels); + + if (music->samplesLeft <= 0) { active = false; break; @@ -1241,22 +934,6 @@ static bool BufferMusicStream(int index, int numBuffers) return active; } -// Empty music buffers -static void EmptyMusicStream(int index) -{ - ALuint buffer = 0; - int queued = 0; - - alGetSourcei(musicStreams[index].mixc->alSource, AL_BUFFERS_QUEUED, &queued); - - while (queued > 0) - { - alSourceUnqueueBuffers(musicStreams[index].mixc->alSource, 1, &buffer); - - queued--; - } -} - // Load WAV file into Wave structure static Wave LoadWAV(const char *fileName) { @@ -1382,7 +1059,7 @@ static Wave LoadOGG(char *fileName) TraceLog(DEBUG, "[%s] Ogg sample rate: %i", fileName, info.sample_rate); TraceLog(DEBUG, "[%s] Ogg channels: %i", fileName, info.channels); - int totalSamplesLength = (stb_vorbis_stream_length_in_samples(oggFile) * info.channels); + int totalSamplesLength = (stb_vorbis_stream_length_in_samples(oggFile)*info.channels); wave.dataSize = totalSamplesLength*sizeof(short); // Size must be in bytes @@ -1417,7 +1094,7 @@ static void UnloadWave(Wave wave) { free(wave.data); - TraceLog(INFO, "Unloaded wave data"); + TraceLog(INFO, "Unloaded wave data from RAM"); } // Some required functions for audio standalone module version diff --git a/src/audio.h b/src/audio.h index d39162b5..c9171339 100644 --- a/src/audio.h +++ b/src/audio.h @@ -75,10 +75,9 @@ typedef struct Wave { short channels; } Wave; -typedef struct MusicBuffer { - char *fileName; - int index; // index in musicStreams -} MusicBuffer; +// Music type (file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed into a mix channel... +typedef struct Music *Music; #ifdef __cplusplus extern "C" { // Prevents name mangling of functions @@ -102,27 +101,24 @@ Sound LoadSoundFromRES(const char *rresName, int resId); // Load sound to void UnloadSound(Sound sound); // Unload sound void PlaySound(Sound sound); // Play a sound void PauseSound(Sound sound); // Pause a sound +void ResumeSound(Sound sound); // Resume a paused sound void StopSound(Sound sound); // Stop playing a sound bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -MusicBuffer LoadMusicBufferStream(char *fileName, int index); -int PlayMusicStream(MusicBuffer buffer); // Start music playing (open stream) -void UpdateMusicStream(MusicBuffer buffer); // Updates buffers for music streaming -void StopMusicStream(MusicBuffer buffer); // Stop music playing (close stream) -void PauseMusicStream(MusicBuffer buffer); // Pause music playing -void ResumeMusicStream(MusicBuffer buffer); // Resume playing paused music -bool IsMusicPlaying(MusicBuffer buffer); // Check if music is playing -void SetMusicVolume(MusicBuffer buffer float volume); // Set volume for music (1.0 is max level) -void SetMusicPitch(MusicBuffer buffer, float pitch); // Set pitch for a music (1.0 is base level) -float GetMusicTimeLength(MusicBuffer buffer); // Get music time length (in seconds) -float GetMusicTimePlayed(MusicBuffer buffer); // Get current music time played (in seconds) -int GetMusicStreamCount(void); // Get number of streams loaded - -int InitRawMixChannel(int sampleRate, int channels, bool floatingPoint); // Initialize raw audio mix channel for audio buffering -int BufferRawMixChannel(int mixc, void *data, unsigned short numberElements); // Buffers data directly to raw mix channel -void CloseRawMixChannel(int mixc); // Closes and frees raw mix channel +Music LoadMusicStream(char *fileName); // Load music stream from file +void UnloadMusicStream(Music music); // Unload music stream +void PlayMusicStream(Music music); // Start music playing (open stream) +void UpdateMusicStream(Music music); // Updates buffers for music streaming +void StopMusicStream(Music music); // Stop music playing (close stream) +void PauseMusicStream(Music music); // Pause music playing +void ResumeMusicStream(Music music); // Resume playing paused music +bool IsMusicPlaying(Music music); // Check if music is playing +void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +float GetMusicTimeLength(Music music); // Get music time length (in seconds) +float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) #ifdef __cplusplus } diff --git a/src/external/jar_xm.h b/src/external/jar_xm.h index 02463e08..7f0517df 100644 --- a/src/external/jar_xm.h +++ b/src/external/jar_xm.h @@ -102,7 +102,7 @@ int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const * @deprecated This function is unsafe! * @see jar_xm_create_context_safe() */ -int jar_xm_create_context(jar_xm_context_t**, const char* moddata, uint32_t rate); +int jar_xm_create_context(jar_xm_context_t** ctx, const char* moddata, uint32_t rate); /** Create a XM context. * @@ -114,17 +114,17 @@ int jar_xm_create_context(jar_xm_context_t**, const char* moddata, uint32_t rate * @returns 1 if module data is not sane * @returns 2 if memory allocation failed */ -int jar_xm_create_context_safe(jar_xm_context_t**, const char* moddata, size_t moddata_length, uint32_t rate); +int jar_xm_create_context_safe(jar_xm_context_t** ctx, const char* moddata, size_t moddata_length, uint32_t rate); /** Free a XM context created by jar_xm_create_context(). */ -void jar_xm_free_context(jar_xm_context_t*); +void jar_xm_free_context(jar_xm_context_t* ctx); /** Play the module and put the sound samples in an output buffer. * * @param output buffer of 2*numsamples elements (A left and right value for each sample) * @param numsamples number of samples to generate */ -void jar_xm_generate_samples(jar_xm_context_t*, float* output, size_t numsamples); +void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples); /** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer. * @@ -173,12 +173,12 @@ void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t nu * * @param loopcnt maximum number of loops. Use 0 to loop * indefinitely. */ -void jar_xm_set_max_loop_count(jar_xm_context_t*, uint8_t loopcnt); +void jar_xm_set_max_loop_count(jar_xm_context_t* ctx, uint8_t loopcnt); /** Get the loop count of the currently playing module. This value is * 0 when the module is still playing, 1 when the module has looped * once, etc. */ -uint8_t jar_xm_get_loop_count(jar_xm_context_t*); +uint8_t jar_xm_get_loop_count(jar_xm_context_t* ctx); @@ -188,7 +188,7 @@ uint8_t jar_xm_get_loop_count(jar_xm_context_t*); * * @return whether the channel was muted. */ -bool jar_xm_mute_channel(jar_xm_context_t*, uint16_t, bool); +bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t, bool); /** Mute or unmute an instrument. * @@ -197,43 +197,43 @@ bool jar_xm_mute_channel(jar_xm_context_t*, uint16_t, bool); * * @return whether the instrument was muted. */ -bool jar_xm_mute_instrument(jar_xm_context_t*, uint16_t, bool); +bool jar_xm_mute_instrument(jar_xm_context_t* ctx, uint16_t, bool); /** Get the module name as a NUL-terminated string. */ -const char* jar_xm_get_module_name(jar_xm_context_t*); +const char* jar_xm_get_module_name(jar_xm_context_t* ctx); /** Get the tracker name as a NUL-terminated string. */ -const char* jar_xm_get_tracker_name(jar_xm_context_t*); +const char* jar_xm_get_tracker_name(jar_xm_context_t* ctx); /** Get the number of channels. */ -uint16_t jar_xm_get_number_of_channels(jar_xm_context_t*); +uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx); /** Get the module length (in patterns). */ uint16_t jar_xm_get_module_length(jar_xm_context_t*); /** Get the number of patterns. */ -uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t*); +uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx); /** Get the number of rows of a pattern. * * @note Pattern numbers go from 0 to * jar_xm_get_number_of_patterns(...)-1. */ -uint16_t jar_xm_get_number_of_rows(jar_xm_context_t*, uint16_t); +uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, uint16_t); /** Get the number of instruments. */ -uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t*); +uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx); /** Get the number of samples of an instrument. * * @note Instrument numbers go from 1 to * jar_xm_get_number_of_instruments(...). */ -uint16_t jar_xm_get_number_of_samples(jar_xm_context_t*, uint16_t); +uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, uint16_t); @@ -242,7 +242,7 @@ uint16_t jar_xm_get_number_of_samples(jar_xm_context_t*, uint16_t); * @param bpm will receive the current BPM * @param tempo will receive the current tempo (ticks per line) */ -void jar_xm_get_playing_speed(jar_xm_context_t*, uint16_t* bpm, uint16_t* tempo); +void jar_xm_get_playing_speed(jar_xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo); /** Get the current position in the module being played. * @@ -257,7 +257,7 @@ void jar_xm_get_playing_speed(jar_xm_context_t*, uint16_t* bpm, uint16_t* tempo) * generated samples (divide by sample rate to get seconds of * generated audio) */ -void jar_xm_get_position(jar_xm_context_t*, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples); +void jar_xm_get_position(jar_xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples); /** Get the latest time (in number of generated samples) when a * particular instrument was triggered in any channel. @@ -265,7 +265,7 @@ void jar_xm_get_position(jar_xm_context_t*, uint8_t* pattern_index, uint8_t* pat * @note Instrument numbers go from 1 to * jar_xm_get_number_of_instruments(...). */ -uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t*, uint16_t); +uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, uint16_t); /** Get the latest time (in number of generated samples) when a * particular sample was triggered in any channel. @@ -276,21 +276,21 @@ uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t*, uint16_t); * @note Sample numbers go from 0 to * jar_xm_get_nubmer_of_samples(...,instr)-1. */ -uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t*, uint16_t instr, uint16_t sample); +uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, uint16_t instr, uint16_t sample); /** Get the latest time (in number of generated samples) when any * instrument was triggered in a given channel. * * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). */ -uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t*, uint16_t); +uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, uint16_t); /** Get the number of remaining samples. Divide by 2 to get the number of individual LR data samples. * * @note This is the remaining number of samples before the loop starts module again, or halts if on last pass. * @note This function is very slow and should only be run once, if at all. */ -uint64_t jar_xm_get_remaining_samples(jar_xm_context_t*); +uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx); #ifdef __cplusplus } @@ -308,7 +308,7 @@ uint64_t jar_xm_get_remaining_samples(jar_xm_context_t*); #include #include -#if JAR_XM_DEBUG +#ifdef JAR_XM_DEBUG #include #define DEBUG(fmt, ...) do { \ fprintf(stderr, "%s(): " fmt "\n", __func__, __VA_ARGS__); \ @@ -638,7 +638,7 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz /* Initialize most of the fields to 0, 0.f, NULL or false depending on type */ memset(mempool, 0, bytes_needed); - ctx = (*ctxp = (jar_xm_context_t*)mempool); + ctx = (*ctxp = (jar_xm_context_t *)mempool); ctx->allocated_memory = mempool; /* Keep original pointer for free() */ mempool += sizeof(jar_xm_context_t); @@ -685,20 +685,18 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz return 0; } -void jar_xm_free_context(jar_xm_context_t* context) { - free(context->allocated_memory); +void jar_xm_free_context(jar_xm_context_t* ctx) { + free(ctx->allocated_memory); } -void jar_xm_set_max_loop_count(jar_xm_context_t* context, uint8_t loopcnt) { - context->max_loop_count = loopcnt; +void jar_xm_set_max_loop_count(jar_xm_context_t* ctx, uint8_t loopcnt) { + ctx->max_loop_count = loopcnt; } -uint8_t jar_xm_get_loop_count(jar_xm_context_t* context) { - return context->loop_count; +uint8_t jar_xm_get_loop_count(jar_xm_context_t* ctx) { + return ctx->loop_count; } - - bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t channel, bool mute) { bool old = ctx->channels[channel - 1].muted; ctx->channels[channel - 1].muted = mute; diff --git a/src/raylib.h b/src/raylib.h index 1966f75e..f8dd8359 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -478,10 +478,9 @@ typedef struct Wave { short channels; } Wave; -typedef struct MusicBuffer { - char *fileName; - int index; // index in musicStreams -} MusicBuffer; +// Music type (file streaming from memory) +// NOTE: Anything longer than ~10 seconds should be streamed +typedef struct Music *Music; // Texture formats // NOTE: Support depends on OpenGL version and platform @@ -897,23 +896,24 @@ Sound LoadSoundFromRES(const char *rresName, int resId); // Load sound to void UnloadSound(Sound sound); // Unload sound void PlaySound(Sound sound); // Play a sound void PauseSound(Sound sound); // Pause a sound +void ResumeSound(Sound sound); // Resume a paused sound void StopSound(Sound sound); // Stop playing a sound bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -MusicBuffer LoadMusicBufferStream(char *fileName, int index); -int PlayMusicStream(MusicBuffer buffer); // Start music playing (open stream) -void UpdateMusicStream(MusicBuffer buffer); // Updates buffers for music streaming -void StopMusicStream(MusicBuffer buffer); // Stop music playing (close stream) -void PauseMusicStream(MusicBuffer buffer); // Pause music playing -void ResumeMusicStream(MusicBuffer buffer); // Resume playing paused music -bool IsMusicPlaying(MusicBuffer buffer); // Check if music is playing -void SetMusicVolume(MusicBuffer buffer, float volume); // Set volume for music (1.0 is max level) -void SetMusicPitch(MusicBuffer buffer, float pitch); // Set pitch for a music (1.0 is base level) -float GetMusicTimeLength(MusicBuffer buffer); // Get music time length (in seconds) -float GetMusicTimePlayed(MusicBuffer buffer); // Get current music time played (in seconds) -int GetMusicStreamCount(void); // Get number of streams loaded +Music LoadMusicStream(char *fileName); // Load music stream from file +void UnloadMusicStream(Music music); // Unload music stream +void PlayMusicStream(Music music); // Start music playing (open stream) +void UpdateMusicStream(Music music); // Updates buffers for music streaming +void StopMusicStream(Music music); // Stop music playing (close stream) +void PauseMusicStream(Music music); // Pause music playing +void ResumeMusicStream(Music music); // Resume playing paused music +bool IsMusicPlaying(Music music); // Check if music is playing +void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +float GetMusicTimeLength(Music music); // Get music time length (in seconds) +float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) #ifdef __cplusplus } -- cgit v1.2.3 From 68d647c1af1b9f0479f680dbd7c4f93586cd51a2 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 2 Aug 2016 17:32:24 +0200 Subject: Complete review and update Simplified module for Music and AudioStream Added support for raw audio streaming (with example) --- examples/audio_module_playing.c | 2 +- examples/audio_music_stream.c | 3 - examples/audio_raw_stream.c | 106 ++++++++++++++++ examples/audio_sound_loading.c | 1 - examples/resources/audio/2t2m_spa.xm | Bin 2287400 -> 0 bytes examples/resources/audio/chiptun1.mod | Bin 0 -> 2142 bytes examples/resources/audio/mini1111.xm | Bin 0 -> 25676 bytes src/audio.c | 230 ++++++++++++++++++---------------- src/audio.h | 27 +++- src/raylib.h | 29 ++++- 10 files changed, 277 insertions(+), 121 deletions(-) create mode 100644 examples/audio_raw_stream.c delete mode 100644 examples/resources/audio/2t2m_spa.xm create mode 100644 examples/resources/audio/chiptun1.mod create mode 100644 examples/resources/audio/mini1111.xm (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c index 07165c76..fe9ea15c 100644 --- a/examples/audio_module_playing.c +++ b/examples/audio_module_playing.c @@ -57,7 +57,7 @@ int main() // Create a RenderTexture2D to be used for render to texture RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight); - Music xm = LoadMusicStream("resources/audio/2t2m_spa.xm"); + Music xm = LoadMusicStream("resources/audio/mini1111.xm"); PlayMusicStream(xm); diff --git a/examples/audio_music_stream.c b/examples/audio_music_stream.c index 1d86bd1a..c552d030 100644 --- a/examples/audio_music_stream.c +++ b/examples/audio_music_stream.c @@ -59,9 +59,6 @@ int main() SetMusicVolume(volume); } */ - if (IsWindowMinimized()) PauseMusicStream(music); - else ResumeMusicStream(music); - timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*100*4; // We scale by 4 to fit 400 pixels UpdateMusicStream(music); // Update music buffer with new stream data diff --git a/examples/audio_raw_stream.c b/examples/audio_raw_stream.c new file mode 100644 index 00000000..37a5b4ff --- /dev/null +++ b/examples/audio_raw_stream.c @@ -0,0 +1,106 @@ +/******************************************************************************************* +* +* raylib [audio] example - Raw audio streaming +* +* NOTE: This example requires OpenAL Soft library installed +* +* This example has been created using raylib 1.6 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2015 Ramon Santamaria (@raysan5) +* +********************************************************************************************/ + +#include "raylib.h" + +#include // Required for: malloc(), free() +#include // Required for: sinf() + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 800; + int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [audio] example - raw audio streaming"); + + InitAudioDevice(); // Initialize audio device + + AudioStream stream = InitAudioStream(44100, 32, 1); // Init raw audio stream + + // Fill audio stream with some samples (sine wave) + float *data = (float *)malloc(sizeof(float)*44100); + + for (int i = 0; i < 44100; i++) + { + data[i] = sinf(2*PI*(float)i*DEG2RAD); + } + + PlayAudioStream(stream); + + int totalSamples = 44100; + int samplesLeft = totalSamples; + + Vector2 position = { 0, 0 }; + + SetTargetFPS(30); // Set our game to run at 30 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + + // Refill audio stream if required + if (IsAudioBufferProcessed(stream)) + { + int numSamples = 0; + if (samplesLeft >= 4096) numSamples = 4096; + else numSamples = samplesLeft; + + UpdateAudioStream(stream, data + (totalSamples - samplesLeft), numSamples); + + samplesLeft -= numSamples; + + // Reset samples feeding (loop audio) + if (samplesLeft <= 0) samplesLeft = totalSamples; + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + DrawText("SINE WAVE SHOULD BE PLAYING!", 240, 140, 20, LIGHTGRAY); + + // NOTE: Draw a part of the sine wave (only screen width) + for (int i = 0; i < GetScreenWidth(); i++) + { + position.x = i; + position.y = 250 + 50*data[i]; + + DrawPixelV(position, RED); + } + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + free(data); // Unload sine wave data + + CloseAudioStream(stream); // Close raw audio stream and delete buffers from RAM + + CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/audio_sound_loading.c b/examples/audio_sound_loading.c index 8819aad1..f081e8ed 100644 --- a/examples/audio_sound_loading.c +++ b/examples/audio_sound_loading.c @@ -36,7 +36,6 @@ int main() // Update //---------------------------------------------------------------------------------- if (IsKeyPressed(KEY_SPACE)) PlaySound(fxWav); // Play WAV sound - if (IsKeyPressed(KEY_ENTER)) PlaySound(fxOgg); // Play OGG sound //---------------------------------------------------------------------------------- diff --git a/examples/resources/audio/2t2m_spa.xm b/examples/resources/audio/2t2m_spa.xm deleted file mode 100644 index fa416ef2..00000000 Binary files a/examples/resources/audio/2t2m_spa.xm and /dev/null differ diff --git a/examples/resources/audio/chiptun1.mod b/examples/resources/audio/chiptun1.mod new file mode 100644 index 00000000..00d16885 Binary files /dev/null and b/examples/resources/audio/chiptun1.mod differ diff --git a/examples/resources/audio/mini1111.xm b/examples/resources/audio/mini1111.xm new file mode 100644 index 00000000..a185c1a2 Binary files /dev/null and b/examples/resources/audio/mini1111.xm differ diff --git a/src/audio.c b/src/audio.c index d1c425d5..befed61c 100644 --- a/src/audio.c +++ b/src/audio.c @@ -100,17 +100,6 @@ typedef enum { MUSIC_AUDIO_OGG = 0, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType; -// Used to create custom audio streams that are not bound to a specific file. -typedef struct AudioStream { - unsigned int sampleRate; // Frequency (samples per second): default is 48000 - unsigned int sampleSize; // BitDepth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels - - ALenum format; // OpenAL format specifier - ALuint source; // OpenAL source - ALuint buffers[MAX_STREAM_BUFFERS]; // OpenAL buffers (double buffering) -} AudioStream; - // Music type (file streaming from memory) typedef struct Music { MusicContextType ctxType; // Type of music context (OGG, XM, MOD) @@ -118,7 +107,7 @@ typedef struct Music { jar_xm_context_t *ctxXm; // XM chiptune context jar_mod_context_t ctxMod; // MOD chiptune context - AudioStream stream; // Audio stream + AudioStream stream; // Audio stream (double buffering) bool loop; // Repeat music after finish (loop) unsigned int totalSamples; // Total number of samples @@ -141,12 +130,6 @@ static Wave LoadWAV(const char *fileName); // Load WAV file static Wave LoadOGG(char *fileName); // Load OGG file static void UnloadWave(Wave wave); // Unload wave data -static bool BufferMusicStream(Music music, int numBuffersToProcess); // Fill music buffers with data - -static AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); -static void BufferAudioStream(AudioStream stream, void *data, int numSamples); -static void CloseAudioStream(AudioStream stream); - #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename void TraceLog(int msgType, const char *text, ...); // Outputs a trace log message (INFO, ERROR, WARNING) @@ -595,33 +578,89 @@ void StopMusicStream(Music music) // Update (re-fill) music buffers if data already processed void UpdateMusicStream(Music music) { - ALenum state; - bool active = true; ALint processed = 0; // Determine if music stream is ready to be written alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); - + + int numBuffersToProcess = processed; + if (processed > 0) { - active = BufferMusicStream(music, processed); + bool active = true; + short pcm[AUDIO_BUFFER_SIZE]; + float pcmf[AUDIO_BUFFER_SIZE]; + + int numSamples = 0; // Total size of data steamed in L+R samples for xm floats, + // individual L or R for ogg shorts + + for (int i = 0; i < numBuffersToProcess; i++) + { + switch (music->ctxType) + { + case MUSIC_AUDIO_OGG: + { + if (music->samplesLeft >= AUDIO_BUFFER_SIZE) numSamples = AUDIO_BUFFER_SIZE; + else numSamples = music->samplesLeft; + + // NOTE: Returns the number of samples to process (should be the same as numSamples -> it is) + int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, pcm, numSamples); + + // TODO: Review stereo channels Ogg, not enough samples served! + UpdateAudioStream(music->stream, pcm, numSamples*music->stream.channels); + music->samplesLeft -= (numSamples*music->stream.channels); + + } break; + case MUSIC_MODULE_XM: + { + if (music->samplesLeft >= AUDIO_BUFFER_SIZE/2) numSamples = AUDIO_BUFFER_SIZE/2; + else numSamples = music->samplesLeft; + + // NOTE: Output buffer is 2*numsamples elements (left and right value for each sample) + jar_xm_generate_samples(music->ctxXm, pcmf, numSamples); + UpdateAudioStream(music->stream, pcmf, numSamples*2); // Using 32bit PCM data + music->samplesLeft -= numSamples; + + //TraceLog(INFO, "Samples left: %i", music->samplesLeft); + + } break; + case MUSIC_MODULE_MOD: + { + if (music->samplesLeft >= AUDIO_BUFFER_SIZE/2) numSamples = AUDIO_BUFFER_SIZE/2; + else numSamples = music->samplesLeft; + + // NOTE: Output buffer size is nbsample*channels (default: 48000Hz, 16bit, Stereo) + jar_mod_fillbuffer(&music->ctxMod, pcm, numSamples, 0); + UpdateAudioStream(music->stream, pcm, numSamples*2); + music->samplesLeft -= numSamples; + + } break; + default: break; + } + if (music->samplesLeft <= 0) + { + active = false; + break; + } + } + + // Reset audio stream for looping if (!active && music->loop) { // Restart music context (if required) + //if (music->ctxType == MUSIC_MODULE_XM) if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_seek_start(&music->ctxMod); else if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_seek_start(music->ctxOgg); + // Reset samples left to total samples music->samplesLeft = music->totalSamples; - - // Determine if music stream is ready to be written - alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); - - active = BufferMusicStream(music, processed); } - if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); + // This error is registered when UpdateAudioStream() fails + if (alGetError() == AL_INVALID_VALUE) TraceLog(WARNING, "OpenAL: Error buffering data..."); + ALenum state; alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); if (state != AL_PLAYING && active) alSourcePlay(music->stream.source); @@ -668,36 +707,14 @@ float GetMusicTimePlayed(Music music) { float secondsPlayed = 0.0f; - if (music->ctxType == MUSIC_MODULE_XM) - { - uint64_t samplesPlayed; - jar_xm_get_position(music->ctxXm, NULL, NULL, NULL, &samplesPlayed); - - // TODO: Not sure if this is the correct value - secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); - } - else if (music->ctxType == MUSIC_MODULE_MOD) - { - long samplesPlayed = jar_mod_current_samples(&music->ctxMod); - - secondsPlayed = (float)samplesPlayed/music->stream.sampleRate; - } - else if (music->ctxType == MUSIC_AUDIO_OGG) - { - unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; - - secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); - } + unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; + secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); return secondsPlayed; } -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - // Init audio stream (to stream audio pcm data) -static AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) { AudioStream stream = { 0 }; @@ -735,7 +752,7 @@ static AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleS alSource3f(stream.source, AL_POSITION, 0, 0, 0); alSource3f(stream.source, AL_VELOCITY, 0, 0, 0); - // Create Buffers + // Create Buffers (double buffering) alGenBuffers(MAX_STREAM_BUFFERS, stream.buffers); // Initialize buffer with zeros by default @@ -766,7 +783,7 @@ static AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleS } // Close audio stream and free memory -static void CloseAudioStream(AudioStream stream) +void CloseAudioStream(AudioStream stream) { // Stop playing channel alSourceStop(stream.source); @@ -790,75 +807,66 @@ static void CloseAudioStream(AudioStream stream) TraceLog(INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); } -// Push more audio data into audio stream, only one buffer per call -static void BufferAudioStream(AudioStream stream, void *data, int numSamples) -{ +// Update audio stream buffers with data +// NOTE: Only one buffer per call +void UpdateAudioStream(AudioStream stream, void *data, int numSamples) +{ ALuint buffer = 0; alSourceUnqueueBuffers(stream.source, 1, &buffer); - //TraceLog(DEBUG, "Buffer to refill: %i", buffer); + // Check if any buffer was available for unqueue + if (alGetError() != AL_INVALID_VALUE) + { + if (stream.sampleSize == 8) alBufferData(buffer, stream.format, (unsigned char *)data, numSamples*sizeof(unsigned char), stream.sampleRate); + else if (stream.sampleSize == 16) alBufferData(buffer, stream.format, (short *)data, numSamples*sizeof(short), stream.sampleRate); + else if (stream.sampleSize == 32) alBufferData(buffer, stream.format, (float *)data, numSamples*sizeof(float), stream.sampleRate); + + alSourceQueueBuffers(stream.source, 1, &buffer); + } +} + +// Check if any audio stream buffers requires refill +bool IsAudioBufferProcessed(AudioStream stream) +{ + ALint processed = 0; - if (stream.sampleSize == 8) alBufferData(buffer, stream.format, (unsigned char *)data, numSamples*sizeof(unsigned char), stream.sampleRate); - else if (stream.sampleSize == 16) alBufferData(buffer, stream.format, (short *)data, numSamples*sizeof(short), stream.sampleRate); - else if (stream.sampleSize == 32) alBufferData(buffer, stream.format, (float *)data, numSamples*sizeof(float), stream.sampleRate); + // Determine if music stream is ready to be written + alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed); - alSourceQueueBuffers(stream.source, 1, &buffer); + return (processed > 0); } -// Fill music buffers with new data from music stream -static bool BufferMusicStream(Music music, int numBuffersToProcess) +// Play audio stream +void PlayAudioStream(AudioStream stream) { - short pcm[AUDIO_BUFFER_SIZE]; - float pcmf[AUDIO_BUFFER_SIZE]; + alSourcePlay(stream.source); +} - int size = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts - bool active = true; // We can get more data from stream (not finished) - - for (int i = 0; i < numBuffersToProcess; i++) - { - if (music->samplesLeft >= AUDIO_BUFFER_SIZE) size = AUDIO_BUFFER_SIZE; - else size = music->samplesLeft; +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + alSourcePause(stream.source); +} - switch (music->ctxType) - { - case MUSIC_AUDIO_OGG: - { - // NOTE: Returns the number of samples to process (should be the same as size) - int numSamples = stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, pcm, size); - - BufferAudioStream(music->stream, pcm, numSamples*music->stream.channels); - music->samplesLeft -= (numSamples*music->stream.channels); - - } break; - case MUSIC_MODULE_XM: - { - // NOTE: Output buffer is 2*numsamples elements (left and right value for each sample) - jar_xm_generate_samples(music->ctxXm, pcmf, size/2); - BufferAudioStream(music->stream, pcmf, size); // Using 32bit PCM data - music->samplesLeft -= (size/2); - - } break; - case MUSIC_MODULE_MOD: - { - // NOTE: Output buffer size is nbsample*channels (default: 48000Hz, 16bit, Stereo) - jar_mod_fillbuffer(&music->ctxMod, pcm, size/2, 0); - BufferAudioStream(music->stream, pcm, size); - music->samplesLeft -= (size/2); - - } break; - default: break; - } +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ALenum state; + alGetSourcei(stream.source, AL_SOURCE_STATE, &state); - if (music->samplesLeft <= 0) - { - active = false; - break; - } - } - - return active; + if (state == AL_PAUSED) alSourcePlay(stream.source); } +// Stop audio stream +void StopAudioStream(AudioStream stream) +{ + alSourceStop(stream.source); +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + // Load WAV file into Wave structure static Wave LoadWAV(const char *fileName) { diff --git a/src/audio.h b/src/audio.h index c9171339..dbd88939 100644 --- a/src/audio.h +++ b/src/audio.h @@ -76,9 +76,21 @@ typedef struct Wave { } Wave; // Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed into a mix channel... +// NOTE: Anything longer than ~10 seconds should be streamed typedef struct Music *Music; +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + 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) + + int format; // OpenAL audio format specifier + unsigned int source; // OpenAL audio source id + unsigned int buffers[2]; // OpenAL audio buffers (double buffering) +} AudioStream; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -93,7 +105,7 @@ extern "C" { // Prevents name mangling of functions //---------------------------------------------------------------------------------- void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) -bool IsAudioDeviceReady(void); // Check if device has been initialized successfully +bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data @@ -120,6 +132,17 @@ void SetMusicPitch(Music music, float pitch); // Set pitch for float GetMusicTimeLength(Music music); // Get music time length (in seconds) float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) +AudioStream InitAudioStream(unsigned int sampleRate, + unsigned int sampleSize, + unsigned int channels); // Init audio stream (to stream audio pcm data) +void UpdateAudioStream(AudioStream stream, void *data, int numSamples); // Update audio stream buffers with data +void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +bool IsAudioBufferProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +void PlayAudioStream(AudioStream stream); // Play audio stream +void PauseAudioStream(AudioStream stream); // Pause audio stream +void ResumeAudioStream(AudioStream stream); // Resume audio stream +void StopAudioStream(AudioStream stream); // Stop audio stream + #ifdef __cplusplus } #endif diff --git a/src/raylib.h b/src/raylib.h index 4b9f6ca0..3ee7a793 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -499,8 +499,8 @@ typedef struct Ray { // Sound source type typedef struct Sound { - unsigned int source; // Sound audio source id - unsigned int buffer; // Sound audio buffer id + unsigned int source; // OpenAL audio source id + unsigned int buffer; // OpenAL audio buffer id } Sound; // Wave type, defines audio wave data @@ -516,6 +516,18 @@ typedef struct Wave { // NOTE: Anything longer than ~10 seconds should be streamed typedef struct Music *Music; +// Audio stream type +// NOTE: Useful to create custom audio streams not bound to a specific file +typedef struct AudioStream { + 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) + + int format; // OpenAL audio format specifier + unsigned int source; // OpenAL audio source id + unsigned int buffers[2]; // OpenAL audio buffers (double buffering) +} AudioStream; + // Texture formats // NOTE: Support depends on OpenGL version and platform typedef enum { @@ -923,7 +935,7 @@ void ToggleVrMode(void); // Enable/Disable VR experience (dev //------------------------------------------------------------------------------------ void InitAudioDevice(void); // Initialize audio device and context void CloseAudioDevice(void); // Close the audio device and context (and music stream) -bool IsAudioDeviceReady(void); // True if call to InitAudioDevice() was successful and CloseAudioDevice() has not been called yet +bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully Sound LoadSound(char *fileName); // Load sound to memory Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data @@ -950,6 +962,17 @@ void SetMusicPitch(Music music, float pitch); // Set pitch for float GetMusicTimeLength(Music music); // Get music time length (in seconds) float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) +AudioStream InitAudioStream(unsigned int sampleRate, + unsigned int sampleSize, + unsigned int channels); // Init audio stream (to stream audio pcm data) +void UpdateAudioStream(AudioStream stream, void *data, int numSamples); // Update audio stream buffers with data +void CloseAudioStream(AudioStream stream); // Close audio stream and free memory +bool IsAudioBufferProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +void PlayAudioStream(AudioStream stream); // Play audio stream +void PauseAudioStream(AudioStream stream); // Pause audio stream +void ResumeAudioStream(AudioStream stream); // Resume audio stream +void StopAudioStream(AudioStream stream); // Stop audio stream + #ifdef __cplusplus } #endif -- cgit v1.2.3 From 79c8eb543ef93fbfbc4073c6c4ea71e22e7e02c4 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Thu, 15 Sep 2016 11:53:16 +0200 Subject: Corrected audio bugs and improved examples --- examples/audio_module_playing.c | 30 ++++++++++++++---- examples/audio_music_stream.c | 51 ++++++++++++++++--------------- src/audio.c | 68 +++++++++++++++++++++++++---------------- 3 files changed, 91 insertions(+), 58 deletions(-) (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c index fe9ea15c..7da3579c 100644 --- a/examples/audio_module_playing.c +++ b/examples/audio_module_playing.c @@ -62,6 +62,7 @@ int main() PlayMusicStream(xm); float timePlayed = 0.0f; + bool pause = false; SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -71,7 +72,29 @@ int main() { // Update //---------------------------------------------------------------------------------- - for (int i = MAX_CIRCLES - 1; i >= 0; i--) + UpdateMusicStream(xm); // Update music buffer with new stream data + + // Restart music playing (stop and play) + if (IsKeyPressed(KEY_SPACE)) + { + StopMusicStream(xm); + PlayMusicStream(xm); + } + + // Pause/Resume music playing + if (IsKeyPressed(KEY_P)) + { + pause = !pause; + + if (pause) PauseMusicStream(xm); + else ResumeMusicStream(xm); + } + + // Get timePlayed scaled to bar dimensions + timePlayed = (GetMusicTimePlayed(xm)/GetMusicTimeLength(xm)*(screenWidth - 40))*2; + + // Color circles animation + for (int i = MAX_CIRCLES - 1; (i >= 0) && !pause; i--) { circles[i].alpha += circles[i].speed; circles[i].radius += circles[i].speed*10.0f; @@ -88,11 +111,6 @@ int main() circles[i].speed = (float)GetRandomValue(1, 100)/20000.0f; } } - - // Get timePlayed scaled to bar dimensions - timePlayed = (GetMusicTimePlayed(xm)/GetMusicTimeLength(xm)*(screenWidth - 40))*2; - - UpdateMusicStream(xm); // Update music buffer with new stream data //---------------------------------------------------------------------------------- // Draw diff --git a/examples/audio_music_stream.c b/examples/audio_music_stream.c index c552d030..dc9d4355 100644 --- a/examples/audio_music_stream.c +++ b/examples/audio_music_stream.c @@ -28,9 +28,8 @@ int main() PlayMusicStream(music); - int framesCounter = 0; float timePlayed = 0.0f; - //float volume = 1.0; + bool pause = false; SetTargetFPS(60); // Set our game to run at 60 frames-per-second //-------------------------------------------------------------------------------------- @@ -40,28 +39,26 @@ int main() { // Update //---------------------------------------------------------------------------------- - framesCounter++; - - // Testing music fading from one file to another -/* - if (framesCounter > 600) // Wait for 10 seconds (600 frames) + UpdateMusicStream(music); // Update music buffer with new stream data + + // Restart music playing (stop and play) + if (IsKeyPressed(KEY_SPACE)) { - volume -= 0.01; // Decrement music volume level - - // When music volume level equal or lower than 0, - // restore volume level and init another music file - if (volume <= 0) - { - volume = 1.0; - framesCounter = 0; - } - - SetMusicVolume(volume); + StopMusicStream(music); + PlayMusicStream(music); } -*/ - timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*100*4; // We scale by 4 to fit 400 pixels - - UpdateMusicStream(music); // Update music buffer with new stream data + + // Pause/Resume music playing + if (IsKeyPressed(KEY_P)) + { + pause = !pause; + + if (pause) PauseMusicStream(music); + else ResumeMusicStream(music); + } + + // Get timePlayed scaled to bar dimensions (400 pixels) + timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*100*4; //---------------------------------------------------------------------------------- // Draw @@ -70,10 +67,14 @@ int main() ClearBackground(RAYWHITE); - DrawText("MUSIC SHOULD BE PLAYING!", 255, 200, 20, LIGHTGRAY); + DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, LIGHTGRAY); - DrawRectangle(200, 250, 400, 12, LIGHTGRAY); - DrawRectangle(200, 250, (int)timePlayed, 12, MAROON); + DrawRectangle(200, 200, 400, 12, LIGHTGRAY); + DrawRectangle(200, 200, (int)timePlayed, 12, MAROON); + DrawRectangleLines(200, 200, 400, 12, GRAY); + + DrawText("PRESS SPACE TO RESTART MUSIC", 215, 250, 20, LIGHTGRAY); + DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 280, 20, LIGHTGRAY); EndDrawing(); //---------------------------------------------------------------------------------- diff --git a/src/audio.c b/src/audio.c index 06bf55d4..90bf4968 100644 --- a/src/audio.c +++ b/src/audio.c @@ -225,12 +225,16 @@ Wave LoadWaveEx(float *data, int sampleCount, int sampleRate, int sampleSize, in wave.data = data; wave.sampleCount = sampleCount; wave.sampleRate = sampleRate; - wave.sampleSize = sampleSize; + wave.sampleSize = 32; wave.channels = channels; - WaveFormat(&wave, sampleRate, sampleSize, channels); + // NOTE: Copy wave data to work with, + // user is responsible of input data to free + Wave cwave = WaveCopy(wave); - return wave; + WaveFormat(&cwave, sampleRate, sampleSize, channels); + + return cwave; } // Load sound to memory @@ -578,6 +582,8 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) } } else TraceLog(WARNING, "Wave formatting: Sample size not supported"); + + free(samples); } // NOTE: Only supported 1 or 2 channels (mono or stereo) @@ -615,7 +621,8 @@ Wave WaveCopy(Wave wave) // NOTE: Security check in case of out-of-range void WaveCrop(Wave *wave, int initSample, int finalSample) { - if ((initSample >= 0) && (finalSample > 0) && (finalSample < wave->sampleCount)) + if ((initSample >= 0) && (initSample < finalSample) && + (finalSample > 0) && (finalSample < wave->sampleCount)) { // TODO: Review cropping (it could be simplified...) @@ -636,6 +643,7 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) // Get samples data from wave as a floats array // NOTE: Returned sample values are normalized to range [-1..1] +// TODO: Consider multiple channels (mono - stereo) float *GetWaveData(Wave wave) { float *samples = (float *)malloc(wave.sampleCount*sizeof(float)); @@ -759,26 +767,37 @@ void ResumeMusicStream(Music music) } // Stop music playing (close stream) +// TODO: Restart XM context void StopMusicStream(Music music) { alSourceStop(music->stream.source); + + switch (music->ctxType) + { + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break; + case MUSIC_MODULE_XM: break; + case MUSIC_MODULE_MOD: jar_mod_seek_start(&music->ctxMod); break; + default: break; + } + + music->samplesLeft = music->totalSamples; } // Update (re-fill) music buffers if data already processed void UpdateMusicStream(Music music) { + ALenum state; ALint processed = 0; - - // Determine if music stream is ready to be written - alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); - - int numBuffersToProcess = processed; + + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); // Get music stream state + alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); // Get processed buffers if (processed > 0) { bool active = true; short pcm[AUDIO_BUFFER_SIZE]; float pcmf[AUDIO_BUFFER_SIZE]; + int numBuffersToProcess = processed; int numSamples = 0; // Total size of data steamed in L+R samples for xm floats, // individual L or R for ogg shorts @@ -833,28 +852,23 @@ void UpdateMusicStream(Music music) break; } } + + // This error is registered when UpdateAudioStream() fails + if (alGetError() == AL_INVALID_VALUE) TraceLog(WARNING, "OpenAL: Error buffering data..."); // Reset audio stream for looping - if (!active && music->loop) + if (!active) { - // Restart music context (if required) - //if (music->ctxType == MUSIC_MODULE_XM) - if (music->ctxType == MUSIC_MODULE_MOD) jar_mod_seek_start(&music->ctxMod); - else if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_seek_start(music->ctxOgg); - - // Reset samples left to total samples - music->samplesLeft = music->totalSamples; + StopMusicStream(music); // Stop music (and reset) + + if (music->loop) PlayMusicStream(music); // Play again + } + else + { + // NOTE: In case window is minimized, music stream is stopped, + // just make sure to play again on window restore + if (state != AL_PLAYING) PlayMusicStream(music); } - - // This error is registered when UpdateAudioStream() fails - if (alGetError() == AL_INVALID_VALUE) TraceLog(WARNING, "OpenAL: Error buffering data..."); - - ALenum state; - alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); - - if (state != AL_PLAYING && active) alSourcePlay(music->stream.source); - - if (!active) StopMusicStream(music); } } -- cgit v1.2.3 From 918fc002d0e75f5ea15036634edf8aa3fba9bedc Mon Sep 17 00:00:00 2001 From: raysan5 Date: Mon, 21 Nov 2016 19:50:31 +0100 Subject: Simplified audio example --- examples/audio_module_playing.c | 35 ++++++++--------------------------- examples/audio_module_playing.png | Bin 215508 -> 47970 bytes 2 files changed, 8 insertions(+), 27 deletions(-) (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c index 7da3579c..a9ee4619 100644 --- a/examples/audio_module_playing.c +++ b/examples/audio_module_playing.c @@ -30,6 +30,8 @@ int main() int screenWidth = 800; int screenHeight = 450; + SetConfigFlags(FLAG_MSAA_4X_HINT); // NOTE: Try to enable MSAA 4X + InitWindow(screenWidth, screenHeight, "raylib [audio] example - module playing (streaming)"); InitAudioDevice(); // Initialize audio device @@ -49,13 +51,6 @@ int main() circles[i].speed = (float)GetRandomValue(1, 100)/20000.0f; circles[i].color = colors[GetRandomValue(0, 13)]; } - - // Load postprocessing bloom shader - Shader shader = LoadShader("resources/shaders/glsl330/base.vs", - "resources/shaders/glsl330/bloom.fs"); - - // Create a RenderTexture2D to be used for render to texture - RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight); Music xm = LoadMusicStream("resources/audio/mini1111.xm"); @@ -117,28 +112,17 @@ int main() //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(BLACK); + ClearBackground(WHITE); - BeginTextureMode(target); // Enable drawing to texture - - for (int i = MAX_CIRCLES - 1; i >= 0; i--) - { - DrawCircleV(circles[i].position, circles[i].radius, Fade(circles[i].color, circles[i].alpha)); - } - - EndTextureMode(); // End drawing to texture (now we have a texture available for next passes) + for (int i = MAX_CIRCLES - 1; i >= 0; i--) + { + DrawCircleV(circles[i].position, circles[i].radius, Fade(circles[i].color, circles[i].alpha)); + } - BeginShaderMode(shader); - - // NOTE: Render texture must be y-flipped due to default OpenGL coordinates (left-bottom) - DrawTextureRec(target.texture, (Rectangle){ 0, 0, target.texture.width, -target.texture.height }, (Vector2){ 0, 0 }, WHITE); - - EndShaderMode(); - // Draw time bar DrawRectangle(20, screenHeight - 20 - 12, screenWidth - 40, 12, LIGHTGRAY); DrawRectangle(20, screenHeight - 20 - 12, (int)timePlayed, 12, MAROON); - DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, WHITE); + DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, GRAY); EndDrawing(); //---------------------------------------------------------------------------------- @@ -146,9 +130,6 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- - UnloadShader(shader); // Unload shader - UnloadRenderTexture(target); // Unload render texture - UnloadMusicStream(xm); // Unload music stream buffers from RAM CloseAudioDevice(); // Close audio device (music streaming is automatically stopped) diff --git a/examples/audio_module_playing.png b/examples/audio_module_playing.png index 7c2e469f..8bde9879 100644 Binary files a/examples/audio_module_playing.png and b/examples/audio_module_playing.png differ -- cgit v1.2.3 From b9310f7d3a58ab3c581cd1b7995877813ac2d300 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 24 Nov 2016 22:41:22 +0100 Subject: Updated background color --- examples/audio_module_playing.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c index a9ee4619..4582a561 100644 --- a/examples/audio_module_playing.c +++ b/examples/audio_module_playing.c @@ -112,7 +112,7 @@ int main() //---------------------------------------------------------------------------------- BeginDrawing(); - ClearBackground(WHITE); + ClearBackground(RAYWHITE); for (int i = MAX_CIRCLES - 1; i >= 0; i--) { -- cgit v1.2.3 From 5de597579feff50ab63ba4285984b64473241c46 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 25 Dec 2016 01:58:56 +0100 Subject: Complete review of audio module --- examples/audio_module_playing.c | 2 +- examples/audio_music_stream.c | 2 +- examples/audio_raw_stream.c | 12 +- src/audio.c | 375 ++++++++++++++++++++-------------------- src/audio.h | 11 +- 5 files changed, 202 insertions(+), 200 deletions(-) (limited to 'examples/audio_module_playing.c') diff --git a/examples/audio_module_playing.c b/examples/audio_module_playing.c index 4582a561..08ae2b05 100644 --- a/examples/audio_module_playing.c +++ b/examples/audio_module_playing.c @@ -86,7 +86,7 @@ int main() } // Get timePlayed scaled to bar dimensions - timePlayed = (GetMusicTimePlayed(xm)/GetMusicTimeLength(xm)*(screenWidth - 40))*2; + timePlayed = GetMusicTimePlayed(xm)/GetMusicTimeLength(xm)*(screenWidth - 40); // Color circles animation for (int i = MAX_CIRCLES - 1; (i >= 0) && !pause; i--) diff --git a/examples/audio_music_stream.c b/examples/audio_music_stream.c index dc9d4355..9c1ca4df 100644 --- a/examples/audio_music_stream.c +++ b/examples/audio_music_stream.c @@ -58,7 +58,7 @@ int main() } // Get timePlayed scaled to bar dimensions (400 pixels) - timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*100*4; + timePlayed = GetMusicTimePlayed(music)/GetMusicTimeLength(music)*400; //---------------------------------------------------------------------------------- // Draw diff --git a/examples/audio_raw_stream.c b/examples/audio_raw_stream.c index c044a7e0..d1fd1794 100644 --- a/examples/audio_raw_stream.c +++ b/examples/audio_raw_stream.c @@ -16,7 +16,7 @@ #include // Required for: malloc(), free() #include // Required for: sinf() -#define MAX_SAMPLES 20000 +#define MAX_SAMPLES 22050 int main() { @@ -29,15 +29,15 @@ int main() InitAudioDevice(); // Initialize audio device - // Init raw audio stream (sample rate: 22050, sample size: 32bit-float, channels: 1-mono) - AudioStream stream = InitAudioStream(22050, 32, 1); + // Init raw audio stream (sample rate: 22050, sample size: 16bit-short, channels: 1-mono) + AudioStream stream = InitAudioStream(22050, 16, 1); // Fill audio stream with some samples (sine wave) - float *data = (float *)malloc(sizeof(float)*MAX_SAMPLES); + short *data = (short *)malloc(sizeof(short)*MAX_SAMPLES); for (int i = 0; i < MAX_SAMPLES; i++) { - data[i] = sinf(((2*PI*(float)i)/2)*DEG2RAD); + data[i] = (short)(sinf(((2*PI*(float)i)/2)*DEG2RAD)*32000); } // NOTE: The generated MAX_SAMPLES do not fit to close a perfect loop @@ -87,7 +87,7 @@ int main() for (int i = 0; i < GetScreenWidth(); i++) { position.x = i; - position.y = 250 + 50*data[i]; + position.y = 250 + 50*data[i]/32000; DrawPixelV(position, RED); } diff --git a/src/audio.c b/src/audio.c index aa89de02..04ff90da 100644 --- a/src/audio.c +++ b/src/audio.c @@ -19,6 +19,10 @@ * Module Configuration Flags: * AUDIO_STANDALONE - Use this module as standalone library (independently of raylib) * +* Some design decisions: +* Support only up to two channels: MONO and STEREO (for additional channels, AL_EXT_MCFORMATS) +* Support only the following sample sizes: 8bit PCM and 16bit PCM (for additional size, AL_EXT_FLOAT32) +* * Many thanks to Joshua Reisenauer (github: @kd7tck) for the following additions: * XM audio module support (jar_xm) * MOD audio module support (jar_mod) @@ -57,19 +61,15 @@ #include "AL/al.h" // OpenAL basic header #include "AL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work) +//#include "AL/alext.h" // OpenAL extensions header, required for AL_EXT_FLOAT32 and AL_EXT_MCFORMATS + +// OpenAL extension: AL_EXT_FLOAT32 - Support for 32bit float samples +// OpenAL extension: AL_EXT_MCFORMATS - Support for multi-channel formats (Quad, 5.1, 6.1, 7.1) #include // Required for: malloc(), free() #include // Required for: strcmp(), strncmp() #include // Required for: FILE, fopen(), fclose(), fread() -// Tokens defined by OpenAL extension: AL_EXT_float32 -#ifndef AL_FORMAT_MONO_FLOAT32 - #define AL_FORMAT_MONO_FLOAT32 0x10010 -#endif -#ifndef AL_FORMAT_STEREO_FLOAT32 - #define AL_FORMAT_STEREO_FLOAT32 0x10011 -#endif - //#define STB_VORBIS_HEADER_ONLY #include "external/stb_vorbis.h" // OGG loading functions @@ -92,11 +92,11 @@ //---------------------------------------------------------------------------------- #define MAX_STREAM_BUFFERS 2 // Number of buffers for each audio stream -// NOTE: Music buffer size is defined by number of samples, independent of sample size +// NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds // and double-buffering system, I concluded that a 4096 samples buffer should be enough // In case of music-stalls, just increase this number -#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. short: 32Kb) +#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. 16bit, Mono: 8Kb) //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -211,7 +211,7 @@ bool IsAudioDeviceReady(void) // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- -// Load wave data from file into RAM +// Load wave data from file Wave LoadWave(const char *fileName) { Wave wave = { 0 }; @@ -224,19 +224,18 @@ Wave LoadWave(const char *fileName) return wave; } -// Load wave data from float array data (32bit) -Wave LoadWaveEx(float *data, int sampleCount, int sampleRate, int sampleSize, int channels) +// Load wave data from raw array data +Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels) { Wave wave; wave.data = data; wave.sampleCount = sampleCount; wave.sampleRate = sampleRate; - wave.sampleSize = 32; + wave.sampleSize = sampleSize; wave.channels = channels; - // NOTE: Copy wave data to work with, - // user is responsible of input data to free + // NOTE: Copy wave data to work with, user is responsible of input data to free Wave cwave = WaveCopy(wave); WaveFormat(&cwave, sampleRate, sampleSize, channels); @@ -244,7 +243,7 @@ Wave LoadWaveEx(float *data, int sampleCount, int sampleRate, int sampleSize, in return cwave; } -// Load sound to memory +// Load sound from file // NOTE: The entire file is loaded to memory to be played (no-streaming) Sound LoadSound(const char *fileName) { @@ -274,7 +273,7 @@ Sound LoadSoundFromWave(Wave wave) { case 8: format = AL_FORMAT_MONO8; break; case 16: format = AL_FORMAT_MONO16; break; - case 32: format = AL_FORMAT_MONO_FLOAT32; break; + case 32: //format = AL_FORMAT_MONO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 default: TraceLog(WARNING, "Wave sample size not supported: %i", wave.sampleSize); break; } } @@ -284,7 +283,7 @@ Sound LoadSoundFromWave(Wave wave) { case 8: format = AL_FORMAT_STEREO8; break; case 16: format = AL_FORMAT_STEREO16; break; - case 32: format = AL_FORMAT_STEREO_FLOAT32; break; + case 32: //format = AL_FORMAT_STEREO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 default: TraceLog(WARNING, "Wave sample size not supported: %i", wave.sampleSize); break; } } @@ -305,7 +304,7 @@ Sound LoadSoundFromWave(Wave wave) ALuint buffer; alGenBuffers(1, &buffer); // Generate pointer to buffer - unsigned int dataSize = wave.sampleCount*wave.sampleSize/8; // Size in bytes + unsigned int dataSize = wave.sampleCount*wave.sampleSize/8*wave.channels; // Size in bytes // Upload sound data to buffer alBufferData(buffer, format, wave.data, dataSize, wave.sampleRate); @@ -313,7 +312,7 @@ Sound LoadSoundFromWave(Wave wave) // Attach sound buffer to source alSourcei(source, AL_BUFFER, buffer); - TraceLog(INFO, "[SND ID %i][BUFR ID %i] Sound data loaded successfully (SampleRate: %i, SampleSize: %i, Channels: %i)", source, buffer, wave.sampleRate, wave.sampleSize, wave.channels); + TraceLog(INFO, "[SND ID %i][BUFR ID %i] Sound data loaded successfully (%i Hz, %i bit, %s)", source, buffer, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); sound.source = source; sound.buffer = buffer; @@ -323,7 +322,7 @@ Sound LoadSoundFromWave(Wave wave) return sound; } -// Unload Wave data +// Unload wave data void UnloadWave(Wave wave) { free(wave.data); @@ -346,14 +345,14 @@ void UpdateSound(Sound sound, const void *data, int numSamples) { ALint sampleRate, sampleSize, channels; alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate); - alGetBufferi(sound.buffer, AL_BITS, &sampleSize); // It could also be retrieved from sound.format - alGetBufferi(sound.buffer, AL_CHANNELS, &channels); // It could also be retrieved from sound.format + alGetBufferi(sound.buffer, AL_BITS, &sampleSize); // It could also be retrieved from sound.format + alGetBufferi(sound.buffer, AL_CHANNELS, &channels); // It could also be retrieved from sound.format TraceLog(DEBUG, "UpdateSound() : AL_FREQUENCY: %i", sampleRate); TraceLog(DEBUG, "UpdateSound() : AL_BITS: %i", sampleSize); TraceLog(DEBUG, "UpdateSound() : AL_CHANNELS: %i", channels); - unsigned int dataSize = numSamples*sampleSize/8; // Size of data in bytes + unsigned int dataSize = numSamples*sampleSize/8*channels; // Size of data in bytes alSourceStop(sound.source); // Stop sound alSourcei(sound.source, AL_BUFFER, 0); // Unbind buffer from sound to update @@ -435,69 +434,86 @@ void SetSoundPitch(Sound sound, float pitch) } // Convert wave data to desired format -// TODO: Consider channels (mono - stereo) void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { + // Format sample rate + if (wave->sampleRate != sampleRate) wave->sampleRate = sampleRate; + + // Format sample size + // NOTE: Only supported 8 bit <--> 16 bit <--> 32 bit if (wave->sampleSize != sampleSize) { - float *samples = GetWaveData(*wave); //Color *pixels = GetImageData(*image); - - free(wave->data); + void *data = malloc(wave->sampleCount*wave->channels*sampleSize/8); - wave->sampleSize = sampleSize; - - //sample *= 4.0f; // Arbitrary gain to get reasonable output volume... - //if (sample > 1.0f) sample = 1.0f; - //if (sample < -1.0f) sample = -1.0f; - - if (sampleSize == 8) + for (int i = 0; i < wave->sampleCount; i++) { - wave->data = (unsigned char *)malloc(wave->sampleCount*sizeof(unsigned char)); - - for (int i = 0; i < wave->sampleCount; i++) + for (int j = 0; j < wave->channels; j++) { - ((unsigned char *)wave->data)[i] = (unsigned char)((float)samples[i]*127 + 128); + if (sampleSize == 8) + { + if (wave->sampleSize == 16) ((unsigned char *)data)[wave->channels*i + j] = (unsigned char)(((float)(((short *)wave->data)[wave->channels*i + j])/32767.0f)*256); + else if (wave->sampleSize == 32) ((unsigned char *)data)[wave->channels*i + j] = (unsigned char)(((float *)wave->data)[wave->channels*i + j]*127.0f + 127); + } + else if (sampleSize == 16) + { + if (wave->sampleSize == 8) ((short *)data)[wave->channels*i + j] = (short)(((float)(((unsigned char *)wave->data)[wave->channels*i + j] - 127)/256.0f)*32767); + else if (wave->sampleSize == 32) ((short *)data)[wave->channels*i + j] = (short)((((float *)wave->data)[wave->channels*i + j])*32767); + } + else if (sampleSize == 32) + { + if (wave->sampleSize == 8) ((float *)data)[wave->channels*i + j] = (float)(((unsigned char *)wave->data)[wave->channels*i + j] - 127)/256.0f; + else if (wave->sampleSize == 16) ((float *)data)[wave->channels*i + j] = (float)(((short *)wave->data)[wave->channels*i + j])/32767.0f; + } } } - else if (sampleSize == 16) + + wave->sampleSize = sampleSize; + free(wave->data); + wave->data = data; + } + + // Format channels (interlaced mode) + // NOTE: Only supported mono <--> stereo + if (wave->channels != channels) + { + void *data = malloc(wave->sampleCount*channels*wave->sampleSize/8); + + if ((wave->channels == 1) && (channels == 2)) // mono ---> stereo (duplicate mono information) { - wave->data = (short *)malloc(wave->sampleCount*sizeof(short)); - for (int i = 0; i < wave->sampleCount; i++) { - ((short *)wave->data)[i] = (short)((float)samples[i]*32000); // SHRT_MAX = 32767 + for (int j = 0; j < channels; j++) + { + if (wave->sampleSize == 8) ((unsigned char *)data)[channels*i + j] = ((unsigned char *)wave->data)[i]; + else if (wave->sampleSize == 16) ((short *)data)[channels*i + j] = ((short *)wave->data)[i]; + else if (wave->sampleSize == 32) ((float *)data)[channels*i + j] = ((float *)wave->data)[i]; + } } } - else if (sampleSize == 32) + else if ((wave->channels == 2) && (channels == 1)) // stereo ---> mono (mix stereo channels) { - wave->data = (float *)malloc(wave->sampleCount*sizeof(float)); - - for (int i = 0; i < wave->sampleCount; i++) + for (int i = 0, j = 0; i < wave->sampleCount; i++, j += 2) { - ((float *)wave->data)[i] = (float)samples[i]; + if (wave->sampleSize == 8) ((unsigned char *)data)[i] = (((unsigned char *)wave->data)[j] + ((unsigned char *)wave->data)[j + 1])/2; + else if (wave->sampleSize == 16) ((short *)data)[i] = (((short *)wave->data)[j] + ((short *)wave->data)[j + 1])/2; + else if (wave->sampleSize == 32) ((float *)data)[i] = (((float *)wave->data)[j] + ((float *)wave->data)[j + 1])/2.0f; } } - else TraceLog(WARNING, "Wave formatting: Sample size not supported"); - free(samples); - } - - // NOTE: Only supported 1 or 2 channels (mono or stereo) - if ((channels > 0) && (channels < 3) && (wave->channels != channels)) - { - // TODO: Add/remove channels interlaced data if required... + // TODO: Add/remove additional interlaced channels + + wave->channels = channels; + free(wave->data); + wave->data = data; } } // Copy a wave to a new wave Wave WaveCopy(Wave wave) { - Wave newWave = { 0 }; + Wave newWave = { 0 }; - if (wave.sampleSize == 8) newWave.data = (unsigned char *)malloc(wave.sampleCount*wave.channels*sizeof(unsigned char)); - else if (wave.sampleSize == 16) newWave.data = (short *)malloc(wave.sampleCount*wave.channels*sizeof(short)); - else if (wave.sampleSize == 32) newWave.data = (float *)malloc(wave.sampleCount*wave.channels*sizeof(float)); - else TraceLog(WARNING, "Wave sample size not supported for copy"); + newWave.data = malloc(wave.sampleCount*wave.channels*wave.sampleSize/8); if (newWave.data != NULL) { @@ -520,35 +536,32 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) if ((initSample >= 0) && (initSample < finalSample) && (finalSample > 0) && (finalSample < wave->sampleCount)) { - // TODO: Review cropping (it could be simplified...) + int sampleCount = finalSample - initSample; - float *samples = GetWaveData(*wave); - float *cropSamples = (float *)malloc((finalSample - initSample)*sizeof(float)); + void *data = malloc(sampleCount*wave->channels*wave->sampleSize/8); + + memcpy(data, wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); - for (int i = initSample; i < finalSample; i++) cropSamples[i] = samples[i]; - free(wave->data); - wave->data = cropSamples; - int sampleSize = wave->sampleSize; - wave->sampleSize = 32; - - WaveFormat(wave, wave->sampleRate, sampleSize, wave->channels); + wave->data = data; } else TraceLog(WARNING, "Wave crop range out of bounds"); } // Get samples data from wave as a floats array // NOTE: Returned sample values are normalized to range [-1..1] -// TODO: Consider multiple channels (mono - stereo) float *GetWaveData(Wave wave) { - float *samples = (float *)malloc(wave.sampleCount*sizeof(float)); + float *samples = (float *)malloc(wave.sampleCount*wave.channels*sizeof(float)); for (int i = 0; i < wave.sampleCount; i++) { - if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; - else if (wave.sampleSize == 16) samples[i] = (float)((short *)wave.data)[i]/32767.0f; - else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; + for (int j = 0; j < wave.channels; j++) + { + if (wave.sampleSize == 8) samples[wave.channels*i + j] = (float)(((unsigned char *)wave.data)[wave.channels*i + j] - 127)/256.0f; + else if (wave.sampleSize == 16) samples[wave.channels*i + j] = (float)((short *)wave.data)[wave.channels*i + j]/32767.0f; + else if (wave.sampleSize == 32) samples[wave.channels*i + j] = ((float *)wave.data)[wave.channels*i + j]; + } } return samples; @@ -572,11 +585,10 @@ Music LoadMusicStream(const char *fileName) else { stb_vorbis_info info = stb_vorbis_get_info(music->ctxOgg); // Get Ogg file info - //float totalLengthSeconds = stb_vorbis_stream_length_in_seconds(music->ctxOgg); - // TODO: Support 32-bit sampleSize OGGs + // OGG bit rate defaults to 16 bit, it's enough for compressed format music->stream = InitAudioStream(info.sample_rate, 16, info.channels); - music->totalSamples = (unsigned int)stb_vorbis_stream_length_in_samples(music->ctxOgg)*info.channels; + music->totalSamples = (unsigned int)stb_vorbis_stream_length_in_samples(music->ctxOgg); music->samplesLeft = music->totalSamples; music->ctxType = MUSIC_AUDIO_OGG; music->loop = true; // We loop by default @@ -584,7 +596,6 @@ Music LoadMusicStream(const char *fileName) TraceLog(DEBUG, "[%s] OGG sample rate: %i", fileName, info.sample_rate); TraceLog(DEBUG, "[%s] OGG channels: %i", fileName, info.channels); TraceLog(DEBUG, "[%s] OGG memory required: %i", fileName, info.temp_memory_required); - } } else if (strcmp(GetExtension(fileName), "flac") == 0) @@ -614,7 +625,7 @@ Music LoadMusicStream(const char *fileName) jar_xm_set_max_loop_count(music->ctxXm, 0); // Set infinite number of loops // NOTE: Only stereo is supported for XM - music->stream = InitAudioStream(48000, 32, 2); + music->stream = InitAudioStream(48000, 16, 2); music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); music->samplesLeft = music->totalSamples; music->ctxType = MUSIC_MODULE_XM; @@ -637,8 +648,8 @@ Music LoadMusicStream(const char *fileName) music->ctxType = MUSIC_MODULE_MOD; music->loop = true; - TraceLog(INFO, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); - TraceLog(INFO, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + TraceLog(DEBUG, "[%s] MOD number of samples: %i", fileName, music->samplesLeft); + TraceLog(DEBUG, "[%s] MOD track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); } else TraceLog(WARNING, "[%s] MOD file could not be opened", fileName); } @@ -682,7 +693,6 @@ void ResumeMusicStream(Music music) } // Stop music playing (close stream) -// TODO: Restart XM context void StopMusicStream(Music music) { alSourceStop(music->stream.source); @@ -690,7 +700,7 @@ void StopMusicStream(Music music) switch (music->ctxType) { case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break; - case MUSIC_MODULE_XM: break; + case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break; case MUSIC_MODULE_MOD: jar_mod_seek_start(&music->ctxMod); break; default: break; } @@ -710,70 +720,43 @@ void UpdateMusicStream(Music music) if (processed > 0) { bool active = true; - short pcm[AUDIO_BUFFER_SIZE]; // TODO: Dynamic allocation (uses more than 16KB of stack) - float pcmf[AUDIO_BUFFER_SIZE]; // TODO: Dynamic allocation (uses more than 16KB of stack) - int numBuffersToProcess = processed; + + // NOTE: Using dynamic allocation because it could require more than 16KB + void *pcm = calloc(AUDIO_BUFFER_SIZE*music->stream.channels*music->stream.sampleSize/8, 1); + int numBuffersToProcess = processed; int numSamples = 0; // Total size of data steamed in L+R samples for xm floats, // individual L or R for ogg shorts for (int i = 0; i < numBuffersToProcess; i++) { + if (music->samplesLeft >= AUDIO_BUFFER_SIZE) numSamples = AUDIO_BUFFER_SIZE; + else numSamples = music->samplesLeft; + + // TODO: Really don't like ctxType thingy... switch (music->ctxType) { case MUSIC_AUDIO_OGG: { - if (music->samplesLeft >= AUDIO_BUFFER_SIZE) numSamples = AUDIO_BUFFER_SIZE; - else numSamples = music->samplesLeft; - - // NOTE: Returns the number of samples to process (should be the same as numSamples -> it is) - int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, pcm, numSamples); - - // TODO: Review stereo channels Ogg, not enough samples served! - UpdateAudioStream(music->stream, pcm, numSamplesOgg*music->stream.channels); - music->samplesLeft -= (numSamplesOgg*music->stream.channels); + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, numSamples*music->stream.channels); } break; case MUSIC_AUDIO_FLAC: { - if (music->samplesLeft >= AUDIO_BUFFER_SIZE) numSamples = AUDIO_BUFFER_SIZE; - else numSamples = music->samplesLeft; - - int pcmi[AUDIO_BUFFER_SIZE]; - - // NOTE: Returns the number of samples to process (should be the same as numSamples) - unsigned int numSamplesFlac = (unsigned int)drflac_read_s32(music->ctxFlac, numSamples, pcmi); + // NOTE: Returns the number of samples to process + unsigned int numSamplesFlac = (unsigned int)drflac_read_s32(music->ctxFlac, numSamples/2, (int *)pcm); - UpdateAudioStream(music->stream, pcmi, numSamplesFlac*music->stream.channels); - music->samplesLeft -= (numSamples*music->stream.channels); - - } break; - case MUSIC_MODULE_XM: - { - if (music->samplesLeft >= AUDIO_BUFFER_SIZE/2) numSamples = AUDIO_BUFFER_SIZE/2; - else numSamples = music->samplesLeft; - - // NOTE: Output buffer is 2*numsamples elements (left and right value for each sample) - jar_xm_generate_samples(music->ctxXm, pcmf, numSamples); - UpdateAudioStream(music->stream, pcmf, numSamples*2); // Using 32bit PCM data - music->samplesLeft -= numSamples; - - //TraceLog(INFO, "Samples left: %i", music->samplesLeft); - - } break; - case MUSIC_MODULE_MOD: - { - if (music->samplesLeft >= AUDIO_BUFFER_SIZE/2) numSamples = AUDIO_BUFFER_SIZE/2; - else numSamples = music->samplesLeft; - - // NOTE: Output buffer size is nbsample*channels (default: 48000Hz, 16bit, Stereo) - jar_mod_fillbuffer(&music->ctxMod, pcm, numSamples, 0); - UpdateAudioStream(music->stream, pcm, numSamples*2); - music->samplesLeft -= numSamples; + // TODO: Samples should be provided as 16 bit instead of 32 bit! } break; + case MUSIC_MODULE_XM: jar_xm_generate_samples_16bit(music->ctxXm, pcm, numSamples); break; + case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, numSamples, 0); break; default: break; } + + UpdateAudioStream(music->stream, pcm, numSamples); + music->samplesLeft -= numSamples; if (music->samplesLeft <= 0) { @@ -789,7 +772,6 @@ void UpdateMusicStream(Music music) if (!active) { StopMusicStream(music); // Stop music (and reset) - if (music->loop) PlayMusicStream(music); // Play again } else @@ -798,6 +780,8 @@ void UpdateMusicStream(Music music) // just make sure to play again on window restore if (state != AL_PLAYING) PlayMusicStream(music); } + + free(pcm); } } @@ -840,7 +824,7 @@ float GetMusicTimePlayed(Music music) float secondsPlayed = 0.0f; unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; - secondsPlayed = (float)(samplesPlayed/(music->stream.sampleRate*music->stream.channels)); + secondsPlayed = (float)samplesPlayed/music->stream.sampleRate; return secondsPlayed; } @@ -852,30 +836,36 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un stream.sampleRate = sampleRate; stream.sampleSize = sampleSize; - stream.channels = channels; + + // Only mono and stereo channels are supported, more channels require AL_EXT_MCFORMATS extension + if ((channels > 0) && (channels < 3)) stream.channels = channels; + else + { + TraceLog(WARNING, "Init audio stream: Number of channels not supported: %i", channels); + stream.channels = 1; // Fallback to mono channel + } // Setup OpenAL format - if (channels == 1) + if (stream.channels == 1) { switch (sampleSize) { case 8: stream.format = AL_FORMAT_MONO8; break; case 16: stream.format = AL_FORMAT_MONO16; break; - case 32: stream.format = AL_FORMAT_MONO_FLOAT32; break; + case 32: //stream.format = AL_FORMAT_MONO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 default: TraceLog(WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; } } - else if (channels == 2) + else if (stream.channels == 2) { switch (sampleSize) { case 8: stream.format = AL_FORMAT_STEREO8; break; case 16: stream.format = AL_FORMAT_STEREO16; break; - case 32: stream.format = AL_FORMAT_STEREO_FLOAT32; break; + case 32: //stream.format = AL_FORMAT_STEREO_FLOAT32; break; // Requires OpenAL extension: AL_EXT_FLOAT32 default: TraceLog(WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; } } - else TraceLog(WARNING, "Init audio stream: Number of channels not supported: %i", channels); // Create an audio source alGenSources(1, &stream.source); @@ -888,28 +878,19 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un alGenBuffers(MAX_STREAM_BUFFERS, stream.buffers); // Initialize buffer with zeros by default + // NOTE: Using dynamic allocation because it requires more than 16KB + void *pcm = calloc(AUDIO_BUFFER_SIZE*stream.sampleSize/8*stream.channels, 1); + for (int i = 0; i < MAX_STREAM_BUFFERS; i++) { - if (stream.sampleSize == 8) - { - unsigned char pcm[AUDIO_BUFFER_SIZE] = { 0 }; // TODO: Dynamic allocation (uses more than 16KB of stack) - alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(unsigned char), stream.sampleRate); - } - else if (stream.sampleSize == 16) - { - short pcm[AUDIO_BUFFER_SIZE] = { 0 }; // TODO: Dynamic allocation (uses more than 16KB of stack) - alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(short), stream.sampleRate); - } - else if (stream.sampleSize == 32) - { - float pcm[AUDIO_BUFFER_SIZE] = { 0.0f }; // TODO: Dynamic allocation (uses more than 16KB of stack) - alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(float), stream.sampleRate); - } + alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*stream.sampleSize/8*stream.channels, stream.sampleRate); } + + free(pcm); alSourceQueueBuffers(stream.source, MAX_STREAM_BUFFERS, stream.buffers); - TraceLog(INFO, "[AUD ID %i] Audio stream loaded successfully", stream.source); + TraceLog(INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo"); return stream; } @@ -940,8 +921,8 @@ void CloseAudioStream(AudioStream stream) } // Update audio stream buffers with data -// NOTE: Only one buffer per call -void UpdateAudioStream(AudioStream stream, void *data, int numSamples) +// NOTE: Only updates one buffer per call +void UpdateAudioStream(AudioStream stream, const void *data, int numSamples) { ALuint buffer = 0; alSourceUnqueueBuffers(stream.source, 1, &buffer); @@ -949,10 +930,7 @@ void UpdateAudioStream(AudioStream stream, void *data, int numSamples) // Check if any buffer was available for unqueue if (alGetError() != AL_INVALID_VALUE) { - if (stream.sampleSize == 8) alBufferData(buffer, stream.format, (unsigned char *)data, numSamples*sizeof(unsigned char), stream.sampleRate); - else if (stream.sampleSize == 16) alBufferData(buffer, stream.format, (short *)data, numSamples*sizeof(short), stream.sampleRate); - else if (stream.sampleSize == 32) alBufferData(buffer, stream.format, (float *)data, numSamples*sizeof(float), stream.sampleRate); - + alBufferData(buffer, stream.format, data, numSamples*stream.channels*stream.sampleSize/8, stream.sampleRate); alSourceQueueBuffers(stream.source, 1, &buffer); } } @@ -1007,7 +985,7 @@ static Wave LoadWAV(const char *fileName) char chunkID[4]; int chunkSize; char format[4]; - } RiffHeader; + } WavRiffHeader; typedef struct { char subChunkID[4]; @@ -1018,16 +996,16 @@ static Wave LoadWAV(const char *fileName) int byteRate; short blockAlign; short bitsPerSample; - } WaveFormat; + } WavFormat; typedef struct { char subChunkID[4]; int subChunkSize; - } WaveData; + } WavData; - RiffHeader riffHeader; - WaveFormat waveFormat; - WaveData waveData; + WavRiffHeader wavRiffHeader; + WavFormat wavFormat; + WavData wavData; Wave wave = { 0 }; FILE *wavFile; @@ -1042,56 +1020,70 @@ static Wave LoadWAV(const char *fileName) else { // Read in the first chunk into the struct - fread(&riffHeader, sizeof(RiffHeader), 1, wavFile); + fread(&wavRiffHeader, sizeof(WavRiffHeader), 1, wavFile); // Check for RIFF and WAVE tags - if (strncmp(riffHeader.chunkID, "RIFF", 4) || - strncmp(riffHeader.format, "WAVE", 4)) + if (strncmp(wavRiffHeader.chunkID, "RIFF", 4) || + strncmp(wavRiffHeader.format, "WAVE", 4)) { TraceLog(WARNING, "[%s] Invalid RIFF or WAVE Header", fileName); } else { // Read in the 2nd chunk for the wave info - fread(&waveFormat, sizeof(WaveFormat), 1, wavFile); + fread(&wavFormat, sizeof(WavFormat), 1, wavFile); // Check for fmt tag - if ((waveFormat.subChunkID[0] != 'f') || (waveFormat.subChunkID[1] != 'm') || - (waveFormat.subChunkID[2] != 't') || (waveFormat.subChunkID[3] != ' ')) + if ((wavFormat.subChunkID[0] != 'f') || (wavFormat.subChunkID[1] != 'm') || + (wavFormat.subChunkID[2] != 't') || (wavFormat.subChunkID[3] != ' ')) { TraceLog(WARNING, "[%s] Invalid Wave format", fileName); } else { // Check for extra parameters; - if (waveFormat.subChunkSize > 16) fseek(wavFile, sizeof(short), SEEK_CUR); + if (wavFormat.subChunkSize > 16) fseek(wavFile, sizeof(short), SEEK_CUR); // Read in the the last byte of data before the sound file - fread(&waveData, sizeof(WaveData), 1, wavFile); + fread(&wavData, sizeof(WavData), 1, wavFile); // Check for data tag - if ((waveData.subChunkID[0] != 'd') || (waveData.subChunkID[1] != 'a') || - (waveData.subChunkID[2] != 't') || (waveData.subChunkID[3] != 'a')) + if ((wavData.subChunkID[0] != 'd') || (wavData.subChunkID[1] != 'a') || + (wavData.subChunkID[2] != 't') || (wavData.subChunkID[3] != 'a')) { TraceLog(WARNING, "[%s] Invalid data header", fileName); } else { // Allocate memory for data - wave.data = (unsigned char *)malloc(sizeof(unsigned char)*waveData.subChunkSize); + wave.data = (unsigned char *)malloc(sizeof(unsigned char)*wavData.subChunkSize); // Read in the sound data into the soundData variable - fread(wave.data, waveData.subChunkSize, 1, wavFile); + fread(wave.data, wavData.subChunkSize, 1, wavFile); // Store wave parameters - wave.sampleRate = waveFormat.sampleRate; - wave.sampleSize = waveFormat.bitsPerSample; - wave.channels = waveFormat.numChannels; + wave.sampleRate = wavFormat.sampleRate; + wave.sampleSize = wavFormat.bitsPerSample; + wave.channels = wavFormat.numChannels; + + // NOTE: Only support up to 16 bit sample sizes + if (wave.sampleSize > 16) + { + WaveFormat(&wave, wave.sampleRate, 16, wave.channels); + TraceLog(WARNING, "[%s] WAV sample size (%ibit) not supported, converted to 16bit", fileName, wave.sampleSize); + } + + // NOTE: Only support up to 2 channels (mono, stereo) + if (wave.channels > 2) + { + WaveFormat(&wave, wave.sampleRate, wave.sampleSize, 2); + TraceLog(WARNING, "[%s] WAV channels number (%i) not supported, converted to 2 channels", fileName, wave.channels); + } // NOTE: subChunkSize comes in bytes, we need to translate it to number of samples - wave.sampleCount = waveData.subChunkSize/(waveFormat.bitsPerSample/8); + wave.sampleCount = (wavData.subChunkSize/(wave.sampleSize/8))/wave.channels; - TraceLog(INFO, "[%s] WAV file loaded successfully (SampleRate: %i, SampleSize: %i, Channels: %i)", fileName, wave.sampleRate, wave.sampleSize, wave.channels); + TraceLog(INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); } } } @@ -1137,7 +1129,7 @@ static Wave LoadOGG(const char *fileName) TraceLog(DEBUG, "[%s] Samples obtained: %i", fileName, samplesObtained); - TraceLog(INFO, "[%s] OGG file loaded successfully (SampleRate: %i, SampleSize: %i, Channels: %i)", fileName, wave.sampleRate, wave.sampleSize, wave.channels); + TraceLog(INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); stb_vorbis_close(oggFile); } @@ -1156,9 +1148,20 @@ static Wave LoadFLAC(const char *fileName) wave.data = drflac_open_and_decode_file_s32(fileName, &wave.channels, &wave.sampleRate, &totalSampleCount); wave.sampleCount = (int)totalSampleCount; - wave.sampleSize = 32; + wave.sampleSize = 32; // 32 bit per sample (float) + + // NOTE: By default, dr_flac returns 32bit float samples, needs to be converted to 16bit + WaveFormat(&wave, wave.sampleRate, 16, wave.channels); + + // NOTE: Only support up to 2 channels (mono, stereo) + if (wave.channels > 2) + { + WaveFormat(&wave, wave.sampleRate, wave.sampleSize, 2); + TraceLog(WARNING, "[%s] FLAC channels number (%i) not supported, converted to 2 channels", fileName, wave.channels); + } if (wave.data == NULL) TraceLog(WARNING, "[%s] FLAC data could not be loaded", fileName); + else TraceLog(INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); return wave; } diff --git a/src/audio.h b/src/audio.h index db1bb694..6f0c235a 100644 --- a/src/audio.h +++ b/src/audio.h @@ -110,12 +110,11 @@ void InitAudioDevice(void); // Initialize au void CloseAudioDevice(void); // Close the audio device and context bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully -Wave LoadWave(const char *fileName); // Load wave data from file into RAM -Wave LoadWaveEx(float *data, int sampleCount, int sampleRate, int sampleSize, int channels); // Load wave data from float array data (32bit) -Sound LoadSound(const char *fileName); // Load sound to memory -Sound LoadSoundFromWave(Wave wave); // Load sound to memory from wave data -Sound LoadSoundFromRES(const char *rresName, int resId); // Load sound to memory from rRES file (raylib Resource) -void UpdateSound(Sound sound, const void *data, int numSamples); // Update sound buffer with new data +Wave LoadWave(const char *fileName); // Load wave data from file +Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels); // Load wave data from raw array data +Sound LoadSound(const char *fileName); // Load sound from file +Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +void UpdateSound(Sound sound, const void *data, int numSamples);// Update sound buffer with new data void UnloadWave(Wave wave); // Unload wave data void UnloadSound(Sound sound); // Unload sound void PlaySound(Sound sound); // Play a sound -- cgit v1.2.3