diff options
| author | victorfisac <[email protected]> | 2017-03-06 09:47:08 +0100 |
|---|---|---|
| committer | victorfisac <[email protected]> | 2017-03-06 09:47:08 +0100 |
| commit | f9277f216372179560c560427beccdd2e5c5d094 (patch) | |
| tree | 8d3858c978f2b36ea8912f25e3cbe6fa56952aff /src/audio.c | |
| parent | ce56fcb1eda06385b88c1a906f0968d742ff8130 (diff) | |
| parent | c05701253e0a4eda211a0d7ced74ae29d6585917 (diff) | |
| download | raylib-f9277f216372179560c560427beccdd2e5c5d094.tar.gz raylib-f9277f216372179560c560427beccdd2e5c5d094.zip | |
Merge remote-tracking branch 'refs/remotes/raysan5/master'
Diffstat (limited to 'src/audio.c')
| -rw-r--r-- | src/audio.c | 1280 |
1 files changed, 804 insertions, 476 deletions
diff --git a/src/audio.c b/src/audio.c index 260f6778..659ead0f 100644 --- a/src/audio.c +++ b/src/audio.c @@ -2,13 +2,53 @@ * * raylib.audio * -* Basic functions to manage Audio: InitAudioDevice, LoadAudioFiles, PlayAudioFiles +* This module provides basic functionality to work with audio: +* Manage audio device (init/close) +* Load and Unload audio files (WAV, OGG, FLAC, XM, MOD) +* Play/Stop/Pause/Resume loaded audio +* Manage mixing channels +* Manage raw audio context * -* Uses external lib: -* OpenAL Soft - Audio device management lib (http://kcat.strangesoft.net/openal.html) -* stb_vorbis - Ogg audio files loading (http://www.nothings.org/stb_vorbis/) +* NOTES: * -* Copyright (c) 2014 Ramon Santamaria (@raysan5) +* Only up to two channels supported: MONO and STEREO (for additional channels, use AL_EXT_MCFORMATS) +* Only the following sample sizes supported: 8bit PCM, 16bit PCM, 32-bit float PCM (using AL_EXT_FLOAT32) +* +* CONFIGURATION: +* +* #define AUDIO_STANDALONE +* If defined, the module can be used as standalone library (independently of raylib). +* Required types and functions are defined in the same module. +* +* #define SUPPORT_FILEFORMAT_WAV / SUPPORT_LOAD_WAV / ENABLE_LOAD_WAV +* #define SUPPORT_FILEFORMAT_OGG +* #define SUPPORT_FILEFORMAT_XM +* #define SUPPORT_FILEFORMAT_MOD +* #define SUPPORT_FILEFORMAT_FLAC +* Selected desired fileformats to be supported for loading. Some of those formats are +* supported by default, to remove support, just comment unrequired #define in this module +* +* #define SUPPORT_RAW_AUDIO_BUFFERS +* +* DEPENDENCIES: +* OpenAL Soft - Audio device management (http://kcat.strangesoft.net/openal.html) +* stb_vorbis - OGG audio files loading (http://www.nothings.org/stb_vorbis/) +* jar_xm - XM module file loading +* jar_mod - MOD audio file loading +* dr_flac - FLAC audio file loading +* +* CONTRIBUTORS: +* +* Many thanks to Joshua Reisenauer (github: @kd7tck) for the following additions: +* XM audio module support (jar_xm) +* MOD audio module support (jar_mod) +* Mixing channels support +* Raw audio context support +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2016 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. @@ -31,59 +71,85 @@ #if defined(AUDIO_STANDALONE) #include "audio.h" + #include <stdarg.h> // Required for: va_list, va_start(), vfprintf(), va_end() #else #include "raylib.h" + #include "utils.h" // Required for: fopen() Android mapping, TraceLog() #endif -#include "AL/al.h" // OpenAL basic header -#include "AL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work) - -#include <stdlib.h> // Declares malloc() and free() for memory management -#include <string.h> // Required for strcmp() -#include <stdio.h> // Used for .WAV loading - -#if defined(AUDIO_STANDALONE) - #include <stdarg.h> // Used for functions with variable number of parameters (TraceLog()) +#ifdef __APPLE__ + #include "OpenAL/al.h" // OpenAL basic header + #include "OpenAL/alc.h" // OpenAL context header (like OpenGL, OpenAL requires a context to work) #else - #include "utils.h" // rRES data decompression utility function - // NOTE: Includes Android fopen function map + #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 #endif +// 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 <stdlib.h> // Required for: malloc(), free() +#include <string.h> // Required for: strcmp(), strncmp() +#include <stdio.h> // Required for: FILE, fopen(), fclose(), fread() + //#define STB_VORBIS_HEADER_ONLY -#include "stb_vorbis.h" // OGG loading functions +#include "external/stb_vorbis.h" // OGG loading functions + +#define JAR_XM_IMPLEMENTATION +#include "external/jar_xm.h" // XM loading functions + +#define JAR_MOD_IMPLEMENTATION +#include "external/jar_mod.h" // MOD loading functions + +#define DR_FLAC_IMPLEMENTATION +#define DR_FLAC_NO_WIN32_IO +#include "external/dr_flac.h" // FLAC loading functions + +#ifdef _MSC_VER + #undef bool +#endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#define MUSIC_STREAM_BUFFERS 2 - -#if defined(PLATFORM_RPI) - // NOTE: On RPI should be lower to avoid frame-stalls - #define MUSIC_BUFFER_SIZE 4096*2 // PCM data buffer (short) - 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 4096*8 // PCM data buffer (short) - 64Kb +#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 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. 16bit, Mono: 8Kb) + +// Support uncompressed PCM data in 32-bit float IEEE format +// NOTE: This definition is included in "AL/alext.h", but some OpenAL implementations +// could not provide the extensions header (Android), so its defined here +#if !defined(AL_EXT_float32) + #define AL_EXT_float32 1 + #define AL_FORMAT_MONO_FLOAT32 0x10010 + #define AL_FORMAT_STEREO_FLOAT32 0x10011 #endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -// Music type (file streaming from memory) -// NOTE: Anything longer than ~10 seconds should be streamed... -typedef struct Music { - stb_vorbis *stream; +typedef enum { MUSIC_AUDIO_OGG = 0, MUSIC_AUDIO_FLAC, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType; - ALuint buffers[MUSIC_STREAM_BUFFERS]; - ALuint source; - ALenum format; +// Music type (file streaming from memory) +typedef struct MusicData { + MusicContextType ctxType; // Type of music context (OGG, XM, MOD) + stb_vorbis *ctxOgg; // OGG audio context + drflac *ctxFlac; // FLAC audio context + jar_xm_context_t *ctxXm; // XM chiptune context + jar_mod_context_t ctxMod; // MOD chiptune context - int channels; - int sampleRate; - int totalSamplesLeft; - bool loop; + AudioStream stream; // Audio stream (double buffering) -} Music; + int loopCount; // Loops count (times music repeats), -1 means infinite loop + unsigned int totalSamples; // Total number of samples + unsigned int samplesLeft; // Number of samples left to end +} MusicData; #if defined(AUDIO_STANDALONE) typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; @@ -92,19 +158,14 @@ typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -static bool musicEnabled = false; -static Music currentMusic; // Current music loaded - // NOTE: Only one music file playing at a time +// ... //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- 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(ALuint buffer); // Fill music buffers with data -static void EmptyMusicStream(void); // Empty music buffers +static Wave LoadOGG(const char *fileName); // Load OGG file +static Wave LoadFLAC(const char *fileName); // Load FLAC file #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -115,38 +176,42 @@ void TraceLog(int msgType, const char *text, ...); // Outputs a trace log messa // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- -// Initialize audio device and context +// 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) + if (!device) TraceLog(ERROR, "Audio device could not be opened"); + else { - if(context != NULL) alcDestroyContext(context); + ALCcontext *context = alcCreateContext(device, NULL); - alcCloseDevice(device); - - TraceLog(ERROR, "Could not setup audio context"); - } + if ((context == NULL) || (alcMakeContextCurrent(context) == ALC_FALSE)) + { + if (context != NULL) alcDestroyContext(context); - TraceLog(INFO, "Audio device and context initialized successfully: %s", alcGetString(device, ALC_DEVICE_SPECIFIER)); + alcCloseDevice(device); - // Listener definition (just for 2D) - alListener3f(AL_POSITION, 0, 0, 0); - alListener3f(AL_VELOCITY, 0, 0, 0); - alListener3f(AL_ORIENTATION, 0, 0, -1); + 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.0f, 0.0f, 0.0f); + alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alListener3f(AL_ORIENTATION, 0.0f, 0.0f, -1.0f); + + alListenerf(AL_GAIN, 1.0f); + } + } } -// Close the audio device for the current context, and destroys the context +// Close the audio device for all contexts void CloseAudioDevice(void) { - StopMusicStream(); // Stop music streaming and close current stream - ALCdevice *device; ALCcontext *context = alcGetCurrentContext(); @@ -157,76 +222,96 @@ void CloseAudioDevice(void) alcMakeContextCurrent(NULL); alcDestroyContext(context); alcCloseDevice(device); + + TraceLog(INFO, "Audio device closed successfully"); +} + +// Check if device has been initialized successfully +bool IsAudioDeviceReady(void) +{ + ALCcontext *context = alcGetCurrentContext(); + + if (context == NULL) return false; + else + { + ALCdevice *device = alcGetContextsDevice(context); + + if (device == NULL) return false; + else return true; + } +} + +// Set master volume (listener) +void SetMasterVolume(float volume) +{ + if (volume < 0.0f) volume = 0.0f; + else if (volume > 1.0f) volume = 1.0f; + + alListenerf(AL_GAIN, volume); } //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- -// Load sound to memory -Sound LoadSound(char *fileName) +// Load wave data from file +Wave LoadWave(const char *fileName) { - Sound sound = { 0 }; Wave wave = { 0 }; - // NOTE: The entire file is loaded to memory to play it all at once (no-streaming) + if (strcmp(GetExtension(fileName), "wav") == 0) wave = LoadWAV(fileName); + else if (strcmp(GetExtension(fileName), "ogg") == 0) wave = LoadOGG(fileName); + else if (strcmp(GetExtension(fileName), "flac") == 0) wave = LoadFLAC(fileName); + else if (strcmp(GetExtension(fileName),"rres") == 0) + { + RRES rres = LoadResource(fileName, 0); - // Audio file loading - // NOTE: Buffer space is allocated inside function, Wave must be freed + // NOTE: Parameters for RRES_TYPE_WAVE are: sampleCount, sampleRate, sampleSize, channels - 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); + if (rres[0].type == RRES_TYPE_WAVE) wave = LoadWaveEx(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3, rres[0].param4); + else TraceLog(WARNING, "[%s] Resource file does not contain wave data", fileName); - 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; - } + UnloadResource(rres); + } + else TraceLog(WARNING, "[%s] File extension not recognized, it can't be loaded", fileName); - // Create an audio source - ALuint source; - alGenSources(1, &source); // Generate pointer to audio source + return wave; +} - 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); +// Load wave data from raw array data +Wave LoadWaveEx(void *data, int sampleCount, int sampleRate, int sampleSize, int channels) +{ + Wave wave; - // Convert loaded data to OpenAL buffer - //---------------------------------------- - ALuint buffer; - alGenBuffers(1, &buffer); // Generate pointer to buffer + wave.data = data; + wave.sampleCount = sampleCount; + wave.sampleRate = sampleRate; + wave.sampleSize = sampleSize; + wave.channels = channels; - // Upload sound data to buffer - alBufferData(buffer, format, wave.data, wave.dataSize, wave.sampleRate); + // NOTE: Copy wave data to work with, user is responsible of input data to free + Wave cwave = WaveCopy(wave); - // Attach sound buffer to source - alSourcei(source, AL_BUFFER, buffer); + WaveFormat(&cwave, sampleRate, sampleSize, channels); + + return cwave; +} - TraceLog(INFO, "[%s] Sound file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); +// Load sound from file +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) +{ + Wave wave = LoadWave(fileName); - // Unallocate WAV data - UnloadWave(wave); + Sound sound = LoadSoundFromWave(wave); - sound.source = source; - sound.buffer = buffer; - } + UnloadWave(wave); // Sound is loaded, we can unload wave return sound; } // Load sound from wave data +// NOTE: Wave data must be unallocated manually Sound LoadSoundFromWave(Wave wave) { Sound sound = { 0 }; @@ -234,26 +319,38 @@ Sound LoadSoundFromWave(Wave wave) 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 + + // The OpenAL format is worked out by looking at the number of channels and the sample size (bits per sample) if (wave.channels == 1) { - if (wave.bitsPerSample == 8 ) format = AL_FORMAT_MONO8; - else if (wave.bitsPerSample == 16) format = AL_FORMAT_MONO16; + switch (wave.sampleSize) + { + case 8: format = AL_FORMAT_MONO8; break; + case 16: format = AL_FORMAT_MONO16; 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; + } } else if (wave.channels == 2) { - if (wave.bitsPerSample == 8 ) format = AL_FORMAT_STEREO8; - else if (wave.bitsPerSample == 16) format = AL_FORMAT_STEREO16; + switch (wave.sampleSize) + { + case 8: format = AL_FORMAT_STEREO8; break; + case 16: format = AL_FORMAT_STEREO16; 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; + } } + else TraceLog(WARNING, "Wave number of channels not supported: %i", wave.channels); // 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); + alSourcef(source, AL_PITCH, 1.0f); + alSourcef(source, AL_GAIN, 1.0f); + alSource3f(source, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); alSourcei(source, AL_LOOPING, AL_FALSE); // Convert loaded data to OpenAL buffer @@ -261,188 +358,68 @@ Sound LoadSoundFromWave(Wave wave) ALuint buffer; alGenBuffers(1, &buffer); // Generate pointer to buffer + unsigned int dataSize = wave.sampleCount*wave.channels*wave.sampleSize/8; // Size in bytes + // Upload sound data to buffer - alBufferData(buffer, format, wave.data, wave.dataSize, wave.sampleRate); + alBufferData(buffer, format, wave.data, dataSize, wave.sampleRate); // 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 (%i Hz, %i bit, %s)", source, buffer, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); sound.source = source; sound.buffer = buffer; + sound.format = format; } return sound; } -// Load sound to memory from rRES file (raylib Resource) -// TODO: Maybe rresName could be directly a char array with all the data? -Sound LoadSoundFromRES(const char *rresName, int resId) +// Unload wave data +void UnloadWave(Wave wave) { - Sound sound = { 0 }; + if (wave.data != NULL) free(wave.data); -#if defined(AUDIO_STANDALONE) - TraceLog(WARNING, "Sound loading from rRES resource file not supported on standalone mode"); -#else - - bool found = false; - - char id[4]; // rRES file identifier - unsigned char version; // rRES file version and subversion - char useless; // rRES header reserved data - short numRes; - - ResInfoHeader infoHeader; - - FILE *rresFile = fopen(rresName, "rb"); - - if (rresFile == NULL) - { - TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); - } - else - { - // Read rres file (basic file check - id) - fread(&id[0], sizeof(char), 1, rresFile); - fread(&id[1], sizeof(char), 1, rresFile); - fread(&id[2], sizeof(char), 1, rresFile); - fread(&id[3], sizeof(char), 1, rresFile); - fread(&version, sizeof(char), 1, rresFile); - fread(&useless, sizeof(char), 1, rresFile); - - 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); - } - else - { - // Read number of resources embedded - fread(&numRes, sizeof(short), 1, rresFile); - - for (int i = 0; i < numRes; i++) - { - fread(&infoHeader, sizeof(ResInfoHeader), 1, rresFile); - - if (infoHeader.id == resId) - { - found = true; - - // Check data is of valid SOUND type - if (infoHeader.type == 1) // SOUND data type - { - // TODO: Check data compression type - // NOTE: We suppose compression type 2 (DEFLATE - default) - - // Reading SOUND parameters - Wave wave; - short sampleRate, bps; - char channels, reserved; - - fread(&sampleRate, sizeof(short), 1, rresFile); // Sample rate (frequency) - fread(&bps, sizeof(short), 1, rresFile); // Bits per sample - fread(&channels, 1, 1, rresFile); // Channels (1 - mono, 2 - stereo) - fread(&reserved, 1, 1, rresFile); // <reserved> - - wave.sampleRate = sampleRate; - wave.dataSize = infoHeader.srcSize; - wave.bitsPerSample = bps; - wave.channels = (short)channels; - - unsigned char *data = malloc(infoHeader.size); - - fread(data, infoHeader.size, 1, rresFile); - - wave.data = DecompressData(data, infoHeader.size, infoHeader.srcSize); - - 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 + TraceLog(INFO, "Unloaded wave data from RAM"); +} - // Upload sound data to buffer - alBufferData(buffer, format, (void*)wave.data, wave.dataSize, wave.sampleRate); +// Unload sound +void UnloadSound(Sound sound) +{ + alSourceStop(sound.source); - // Attach sound buffer to source - alSourcei(source, AL_BUFFER, buffer); + alDeleteSources(1, &sound.source); + alDeleteBuffers(1, &sound.buffer); - TraceLog(INFO, "[%s] Sound loaded successfully from resource (SampleRate: %i, BitRate: %i, Channels: %i)", rresName, wave.sampleRate, wave.bitsPerSample, wave.channels); + TraceLog(INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); +} - // Unallocate WAV data - UnloadWave(wave); +// Update sound buffer with new data +// NOTE: data must match sound.format +void UpdateSound(Sound sound, const void *data, int samplesCount) +{ + 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 - sound.source = source; - sound.buffer = buffer; - } - else - { - TraceLog(WARNING, "[%s] Required resource do not seem to be a valid SOUND resource", rresName); - } - } - else - { - // Depending on type, skip the right amount of parameters - switch (infoHeader.type) - { - case 0: fseek(rresFile, 6, SEEK_CUR); break; // IMAGE: Jump 6 bytes of parameters - case 1: fseek(rresFile, 6, SEEK_CUR); break; // SOUND: Jump 6 bytes of parameters - case 2: fseek(rresFile, 5, SEEK_CUR); break; // MODEL: Jump 5 bytes of parameters (TODO: Review) - case 3: break; // TEXT: No parameters - case 4: break; // RAW: No parameters - default: break; - } + TraceLog(DEBUG, "UpdateSound() : AL_FREQUENCY: %i", sampleRate); + TraceLog(DEBUG, "UpdateSound() : AL_BITS: %i", sampleSize); + TraceLog(DEBUG, "UpdateSound() : AL_CHANNELS: %i", channels); - // Jump DATA to read next infoHeader - fseek(rresFile, infoHeader.size, SEEK_CUR); - } - } - } + unsigned int dataSize = samplesCount*channels*sampleSize/8; // Size of data in bytes - fclose(rresFile); - } + alSourceStop(sound.source); // Stop sound + alSourcei(sound.source, AL_BUFFER, 0); // Unbind buffer from sound to update + //alDeleteBuffers(1, &sound.buffer); // Delete current buffer data + //alGenBuffers(1, &sound.buffer); // Generate new buffer - if (!found) TraceLog(WARNING, "[%s] Required resource id [%i] could not be found in the raylib resource file", rresName, resId); -#endif - return sound; -} + // Upload new data to sound buffer + alBufferData(sound.buffer, sound.format, data, dataSize, sampleRate); -// Unload sound -void UnloadSound(Sound sound) -{ - alDeleteSources(1, &sound.source); - alDeleteBuffers(1, &sound.buffer); - - TraceLog(INFO, "Unloaded sound data"); + // Attach sound buffer to source again + alSourcei(sound.source, AL_BUFFER, sound.buffer); } // Play a sound @@ -460,7 +437,7 @@ void PlaySound(Sound sound) //int sampleRate; //alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate); // AL_CHANNELS, AL_BITS (bps) - //float seconds = (float)byteOffset / sampleRate; // Number of seconds since the beginning of the sound + //float seconds = (float)byteOffset/sampleRate; // Number of seconds since the beginning of the sound //or //float result; //alGetSourcef(sound.source, AL_SEC_OFFSET, &result); // AL_SAMPLE_OFFSET @@ -472,6 +449,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) { @@ -479,7 +466,7 @@ void StopSound(Sound sound) } // Check if a sound is playing -bool SoundIsPlaying(Sound sound) +bool IsSoundPlaying(Sound sound) { bool playing = false; ALint state; @@ -502,258 +489,583 @@ void SetSoundPitch(Sound sound, float pitch) alSourcef(sound.source, AL_PITCH, pitch); } -//---------------------------------------------------------------------------------- -// Module Functions Definition - Music loading and stream playing (.OGG) -//---------------------------------------------------------------------------------- - -// Start music playing (open stream) -void PlayMusicStream(char *fileName) +// Convert wave data to desired format +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { - if (strcmp(GetExtension(fileName),"ogg") == 0) + // Format sample rate + // NOTE: Only supported 22050 <--> 44100 + if (wave->sampleRate != sampleRate) { - // Stop current music, clean buffers, unload current stream - StopMusicStream(); + // TODO: Resample wave data (upsampling or downsampling) + // NOTE 1: To downsample, you have to drop samples or average them. + // NOTE 2: To upsample, you have to interpolate new samples. - // Open audio stream - currentMusic.stream = stb_vorbis_open_filename(fileName, NULL, NULL); + wave->sampleRate = sampleRate; + } + + // Format sample size + // NOTE: Only supported 8 bit <--> 16 bit <--> 32 bit + if (wave->sampleSize != sampleSize) + { + void *data = malloc(wave->sampleCount*wave->channels*sampleSize/8); - if (currentMusic.stream == NULL) + for (int i = 0; i < wave->sampleCount; i++) { - TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); + for (int j = 0; j < wave->channels; j++) + { + 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 + + 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) + { + for (int i = 0; i < wave->sampleCount; i++) + { + 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 ((wave->channels == 2) && (channels == 1)) // stereo ---> mono (mix stereo channels) { - // Get file info - stb_vorbis_info info = stb_vorbis_get_info(currentMusic.stream); + for (int i = 0, j = 0; i < wave->sampleCount; i++, j += 2) + { + 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; + } + } + + // TODO: Add/remove additional interlaced channels + + wave->channels = channels; + free(wave->data); + wave->data = data; + } +} - currentMusic.channels = info.channels; - currentMusic.sampleRate = info.sample_rate; +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave = { 0 }; - TraceLog(INFO, "[%s] Ogg sample rate: %i", fileName, info.sample_rate); - TraceLog(INFO, "[%s] Ogg channels: %i", fileName, info.channels); - TraceLog(DEBUG, "[%s] Temp memory required: %i", fileName, info.temp_memory_required); + newWave.data = malloc(wave.sampleCount*wave.channels*wave.sampleSize/8); - if (info.channels == 2) currentMusic.format = AL_FORMAT_STEREO16; - else currentMusic.format = AL_FORMAT_MONO16; + if (newWave.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8); - currentMusic.loop = true; // We loop by default - musicEnabled = true; + newWave.sampleCount = wave.sampleCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; + } - // Create an audio source - alGenSources(1, ¤tMusic.source); // Generate pointer to audio source + return newWave; +} - alSourcef(currentMusic.source, AL_PITCH, 1); - alSourcef(currentMusic.source, AL_GAIN, 1); - alSource3f(currentMusic.source, AL_POSITION, 0, 0, 0); - alSource3f(currentMusic.source, AL_VELOCITY, 0, 0, 0); - //alSourcei(currentMusic.source, AL_LOOPING, AL_TRUE); // ERROR: Buffers do not queue! +// Crop a wave to defined samples range +// NOTE: Security check in case of out-of-range +void WaveCrop(Wave *wave, int initSample, int finalSample) +{ + if ((initSample >= 0) && (initSample < finalSample) && + (finalSample > 0) && (finalSample < wave->sampleCount)) + { + int sampleCount = finalSample - initSample; - // Generate two OpenAL buffers - alGenBuffers(2, currentMusic.buffers); + void *data = malloc(sampleCount*wave->channels*wave->sampleSize/8); - // Fill buffers with music... - BufferMusicStream(currentMusic.buffers[0]); - BufferMusicStream(currentMusic.buffers[1]); + memcpy(data, (unsigned char*)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); - // Queue buffers and start playing - alSourceQueueBuffers(currentMusic.source, 2, currentMusic.buffers); - alSourcePlay(currentMusic.source); + free(wave->data); + wave->data = data; + } + else TraceLog(WARNING, "Wave crop range out of bounds"); +} - // NOTE: Regularly, we must check if a buffer has been processed and refill it: UpdateMusicStream() +// Get samples data from wave as a floats array +// NOTE: Returned sample values are normalized to range [-1..1] +float *GetWaveData(Wave wave) +{ + float *samples = (float *)malloc(wave.sampleCount*wave.channels*sizeof(float)); - currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; + for (int i = 0; i < wave.sampleCount; 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]; } } - else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); + + return samples; } -// Stop music playing (close stream) -void StopMusicStream(void) +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing (.OGG) +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) { - if (musicEnabled) + Music music = (MusicData *)malloc(sizeof(MusicData)); + + if (strcmp(GetExtension(fileName), "ogg") == 0) { - alSourceStop(currentMusic.source); + // Open ogg audio stream + music->ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); - EmptyMusicStream(); // Empty music buffers + if (music->ctxOgg == NULL) TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); + else + { + stb_vorbis_info info = stb_vorbis_get_info(music->ctxOgg); // Get Ogg file info + + // 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); // Independent by channel + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_AUDIO_OGG; + music->loopCount = -1; // Infinite loop by default + + TraceLog(DEBUG, "[%s] FLAC total samples: %i", fileName, music->totalSamples); + 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) + { + music->ctxFlac = drflac_open_file(fileName); - alDeleteSources(1, ¤tMusic.source); - alDeleteBuffers(2, currentMusic.buffers); + if (music->ctxFlac == NULL) TraceLog(WARNING, "[%s] FLAC audio file could not be opened", fileName); + else + { + music->stream = InitAudioStream(music->ctxFlac->sampleRate, music->ctxFlac->bitsPerSample, music->ctxFlac->channels); + music->totalSamples = (unsigned int)music->ctxFlac->totalSampleCount/music->ctxFlac->channels; + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_AUDIO_FLAC; + music->loopCount = -1; // Infinite loop by default + + TraceLog(DEBUG, "[%s] FLAC total samples: %i", fileName, music->totalSamples); + TraceLog(DEBUG, "[%s] FLAC sample rate: %i", fileName, music->ctxFlac->sampleRate); + TraceLog(DEBUG, "[%s] FLAC bits per sample: %i", fileName, music->ctxFlac->bitsPerSample); + TraceLog(DEBUG, "[%s] FLAC channels: %i", fileName, music->ctxFlac->channels); + } + } + else if (strcmp(GetExtension(fileName), "xm") == 0) + { + int result = jar_xm_create_context_from_file(&music->ctxXm, 48000, fileName); - stb_vorbis_close(currentMusic.stream); + if (!result) // XM context created successfully + { + 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, 16, 2); + music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_MODULE_XM; + music->loopCount = -1; // Infinite loop by default + + TraceLog(DEBUG, "[%s] XM number of samples: %i", fileName, music->totalSamples); + TraceLog(DEBUG, "[%s] XM track length: %11.6f sec", fileName, (float)music->totalSamples/48000.0f); + } + else TraceLog(WARNING, "[%s] XM file could not be opened", fileName); } + else if (strcmp(GetExtension(fileName), "mod") == 0) + { + jar_mod_init(&music->ctxMod); + + if (jar_mod_load_file(&music->ctxMod, fileName)) + { + music->stream = InitAudioStream(48000, 16, 2); + music->totalSamples = (unsigned int)jar_mod_max_samples(&music->ctxMod); + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_MODULE_MOD; + music->loopCount = -1; // Infinite loop by default + + 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); + } + else TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); + + return music; +} + +// Unload music stream +void UnloadMusicStream(Music music) +{ + CloseAudioStream(music->stream); + + if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg); + else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac); + 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); +} - musicEnabled = false; +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + alSourcePlay(music->stream.source); } // Pause music playing -void PauseMusicStream(void) +void PauseMusicStream(Music music) +{ + alSourcePause(music->stream.source); +} + +// Resume music playing +void ResumeMusicStream(Music music) { - // Pause music stream if music available! - if (musicEnabled) + ALenum state; + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); + + if (state == AL_PAUSED) alSourcePlay(music->stream.source); +} + +// Stop music playing (close stream) +void StopMusicStream(Music music) +{ + alSourceStop(music->stream.source); + + // Clear stream buffers + void *pcm = calloc(AUDIO_BUFFER_SIZE*music->stream.sampleSize/8*music->stream.channels, 1); + + for (int i = 0; i < MAX_STREAM_BUFFERS; i++) { - TraceLog(INFO, "Pausing music stream"); - alSourcePause(currentMusic.source); - musicEnabled = false; + alBufferData(music->stream.buffers[i], music->stream.format, pcm, AUDIO_BUFFER_SIZE*music->stream.sampleSize/8*music->stream.channels, music->stream.sampleRate); } + + free(pcm); + + // Restart music context + switch (music->ctxType) + { + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break; + case MUSIC_MODULE_XM: /* TODO: Restart XM context */ break; + case MUSIC_MODULE_MOD: jar_mod_seek_start(&music->ctxMod); break; + default: break; + } + + music->samplesLeft = music->totalSamples; } -// Resume music playing -void ResumeMusicStream(void) +// Update (re-fill) music buffers if data already processed +// TODO: Make sure buffers are ready for update... check music state +void UpdateMusicStream(Music music) { - // Resume music playing... if music available! ALenum state; - alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); + ALint processed = 0; + + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); // Get music stream state + alGetSourcei(music->stream.source, AL_BUFFERS_PROCESSED, &processed); // Get processed buffers - if (state == AL_PAUSED) + if (processed > 0) { - TraceLog(INFO, "Resuming music stream"); - alSourcePlay(currentMusic.source); - musicEnabled = true; + bool active = true; + + // 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 samplesCount = 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) samplesCount = AUDIO_BUFFER_SIZE; + else samplesCount = music->samplesLeft; + + // TODO: Really don't like ctxType thingy... + switch (music->ctxType) + { + case MUSIC_AUDIO_OGG: + { + // 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, samplesCount*music->stream.channels); + + } break; + case MUSIC_AUDIO_FLAC: + { + // NOTE: Returns the number of samples to process + unsigned int numSamplesFlac = (unsigned int)drflac_read_s16(music->ctxFlac, samplesCount*music->stream.channels, (short *)pcm); + + } break; + case MUSIC_MODULE_XM: jar_xm_generate_samples_16bit(music->ctxXm, pcm, samplesCount); break; + case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break; + default: break; + } + + UpdateAudioStream(music->stream, pcm, samplesCount); + music->samplesLeft -= samplesCount; + + if (music->samplesLeft <= 0) + { + active = false; + 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) + { + StopMusicStream(music); // Stop music (and reset) + + // Decrease loopCount to stop when required + if (music->loopCount > 0) + { + music->loopCount--; // Decrease loop count + 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); + } + + free(pcm); } } -// Check if music is playing -bool MusicIsPlaying(void) +// Check if any music is playing +bool IsMusicPlaying(Music music) { bool playing = false; ALint state; - alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) playing = true; return playing; } // Set volume for music -void SetMusicVolume(float volume) +void SetMusicVolume(Music music, float volume) +{ + alSourcef(music->stream.source, AL_GAIN, volume); +} + +// Set pitch for music +void SetMusicPitch(Music music, float pitch) +{ + alSourcef(music->stream.source, AL_PITCH, pitch); +} + +// Set music loop count (loop repeats) +// NOTE: If set to -1, means infinite loop +void SetMusicLoopCount(Music music, float count) { - alSourcef(currentMusic.source, AL_GAIN, volume); + music->loopCount = count; } -// Get current music time length (in seconds) -float GetMusicTimeLength(void) +// Get music time length (in seconds) +float GetMusicTimeLength(Music music) { - float totalSeconds = stb_vorbis_stream_length_in_seconds(currentMusic.stream); + float totalSeconds = (float)music->totalSamples/music->stream.sampleRate; return totalSeconds; } // Get current music time played (in seconds) -float GetMusicTimePlayed(void) +float GetMusicTimePlayed(Music music) { - int totalSamples = stb_vorbis_stream_length_in_samples(currentMusic.stream) * currentMusic.channels; - - int samplesPlayed = totalSamples - currentMusic.totalSamplesLeft; + float secondsPlayed = 0.0f; - float secondsPlayed = (float)samplesPlayed / (currentMusic.sampleRate * currentMusic.channels); + unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; + secondsPlayed = (float)samplesPlayed/music->stream.sampleRate; return secondsPlayed; } -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -// Fill music buffers with new data from music stream -static bool BufferMusicStream(ALuint buffer) +// Init audio stream (to stream audio pcm data) +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) { - short pcm[MUSIC_BUFFER_SIZE]; + AudioStream stream = { 0 }; - int size = 0; // Total size of data steamed (in bytes) - int streamedBytes = 0; // Bytes of data obtained in one samples get + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; - bool active = true; // We can get more data from stream (not finished) + // 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 + } - if (musicEnabled) + // Setup OpenAL format + if (stream.channels == 1) { - while (size < MUSIC_BUFFER_SIZE) + switch (sampleSize) { - streamedBytes = stb_vorbis_get_samples_short_interleaved(currentMusic.stream, currentMusic.channels, pcm + size, MUSIC_BUFFER_SIZE - size); - - if (streamedBytes > 0) size += (streamedBytes*currentMusic.channels); - else break; + 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; // Requires OpenAL extension: AL_EXT_FLOAT32 + default: TraceLog(WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; } - - //TraceLog(DEBUG, "Streaming music data to buffer. Bytes streamed: %i", size); } - - if (size > 0) + else if (stream.channels == 2) { - alBufferData(buffer, currentMusic.format, pcm, size*sizeof(short), currentMusic.sampleRate); - - currentMusic.totalSamplesLeft -= size; + 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; // Requires OpenAL extension: AL_EXT_FLOAT32 + default: TraceLog(WARNING, "Init audio stream: Sample size not supported: %i", sampleSize); break; + } } - else + + // Create an audio source + alGenSources(1, &stream.source); + alSourcef(stream.source, AL_PITCH, 1.0f); + alSourcef(stream.source, AL_GAIN, 1.0f); + alSource3f(stream.source, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(stream.source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + + // Create Buffers (double buffering) + 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++) { - active = false; - TraceLog(WARNING, "No more data obtained from stream"); + alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*stream.sampleSize/8*stream.channels, stream.sampleRate); } - return active; + free(pcm); + + alSourceQueueBuffers(stream.source, MAX_STREAM_BUFFERS, stream.buffers); + + 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; } -// Empty music buffers -static void EmptyMusicStream(void) +// Close audio stream and free memory +void CloseAudioStream(AudioStream stream) { - ALuint buffer = 0; + // Stop playing channel + alSourceStop(stream.source); + + // Flush out all queued buffers int queued = 0; + alGetSourcei(stream.source, AL_BUFFERS_QUEUED, &queued); - alGetSourcei(currentMusic.source, AL_BUFFERS_QUEUED, &queued); + ALuint buffer = 0; while (queued > 0) { - alSourceUnqueueBuffers(currentMusic.source, 1, &buffer); - + 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); } -// Update (re-fill) music buffers if data already processed -void UpdateMusicStream(void) +// Update audio stream buffers with data +// NOTE: Only updates one buffer per call +void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) { ALuint buffer = 0; - ALint processed = 0; - bool active = true; + alSourceUnqueueBuffers(stream.source, 1, &buffer); - if (musicEnabled) + // Check if any buffer was available for unqueue + if (alGetError() != AL_INVALID_VALUE) { - // Get the number of already processed buffers (if any) - alGetSourcei(currentMusic.source, AL_BUFFERS_PROCESSED, &processed); - - while (processed > 0) - { - // Recover processed buffer for refill - alSourceUnqueueBuffers(currentMusic.source, 1, &buffer); - - // Refill buffer - active = BufferMusicStream(buffer); + alBufferData(buffer, stream.format, data, samplesCount*stream.channels*stream.sampleSize/8, stream.sampleRate); + alSourceQueueBuffers(stream.source, 1, &buffer); + } +} - // If no more data to stream, restart music (if loop) - if ((!active) && (currentMusic.loop)) - { - stb_vorbis_seek_start(currentMusic.stream); - currentMusic.totalSamplesLeft = stb_vorbis_stream_length_in_samples(currentMusic.stream)*currentMusic.channels; +// Check if any audio stream buffers requires refill +bool IsAudioBufferProcessed(AudioStream stream) +{ + ALint processed = 0; - active = BufferMusicStream(buffer); - } + // Determine if music stream is ready to be written + alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed); - // Add refilled buffer to queue again... don't let the music stop! - alSourceQueueBuffers(currentMusic.source, 1, &buffer); + return (processed > 0); +} - if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Ogg playing, error buffering data..."); +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + alSourcePlay(stream.source); +} - processed--; - } +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + alSourcePause(stream.source); +} - ALenum state; - alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ALenum state; + alGetSourcei(stream.source, AL_SOURCE_STATE, &state); - if ((state != AL_PLAYING) && active) alSourcePlay(currentMusic.source); + if (state == AL_PAUSED) alSourcePlay(stream.source); +} - if (!active) StopMusicStream(); - } +// 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) { @@ -762,7 +1074,7 @@ static Wave LoadWAV(const char *fileName) char chunkID[4]; int chunkSize; char format[4]; - } RiffHeader; + } WAVRiffHeader; typedef struct { char subChunkID[4]; @@ -773,16 +1085,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; @@ -797,54 +1109,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 = malloc(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); - // Now we set the variables that we need later - wave.dataSize = waveData.subChunkSize; - wave.sampleRate = waveFormat.sampleRate; - wave.channels = waveFormat.numChannels; - wave.bitsPerSample = waveFormat.bitsPerSample; + // Store wave parameters + wave.sampleRate = wavFormat.sampleRate; + wave.sampleSize = wavFormat.bitsPerSample; + wave.channels = wavFormat.numChannels; - TraceLog(INFO, "[%s] WAV file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); + // NOTE: Only support 8 bit, 16 bit and 32 bit sample sizes + if ((wave.sampleSize != 8) && (wave.sampleSize != 16) && (wave.sampleSize != 32)) + { + TraceLog(WARNING, "[%s] WAV sample size (%ibit) not supported, converted to 16bit", fileName, wave.sampleSize); + 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] 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 = (wavData.subChunkSize/(wave.sampleSize/8))/wave.channels; + + TraceLog(INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo"); } } } @@ -857,7 +1185,7 @@ static Wave LoadWAV(const char *fileName) // Load OGG file into Wave structure // NOTE: Using stb_vorbis library -static Wave LoadOGG(char *fileName) +static Wave LoadOGG(const char *fileName) { Wave wave; @@ -873,35 +1201,21 @@ static Wave LoadOGG(char *fileName) stb_vorbis_info info = stb_vorbis_get_info(oggFile); wave.sampleRate = info.sample_rate; - wave.bitsPerSample = 16; + wave.sampleSize = 16; // 16 bit per sample (short) wave.channels = info.channels; - - 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); - - wave.dataSize = totalSamplesLength*sizeof(short); // Size must be in bytes - - TraceLog(DEBUG, "[%s] Samples length: %i", fileName, totalSamplesLength); + wave.sampleCount = (int)stb_vorbis_stream_length_in_samples(oggFile); float totalSeconds = stb_vorbis_stream_length_in_seconds(oggFile); - - TraceLog(DEBUG, "[%s] Total seconds: %f", fileName, totalSeconds); - if (totalSeconds > 10) TraceLog(WARNING, "[%s] Ogg audio lenght is larger than 10 seconds (%f), that's a big file in memory, consider music streaming", fileName, totalSeconds); - int totalSamples = totalSeconds*info.sample_rate*info.channels; + wave.data = (short *)malloc(wave.sampleCount*wave.channels*sizeof(short)); - TraceLog(DEBUG, "[%s] Total samples calculated: %i", fileName, totalSamples); + // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) + int numSamplesOgg = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)wave.data, wave.sampleCount*wave.channels); - wave.data = malloc(sizeof(short)*totalSamplesLength); + TraceLog(DEBUG, "[%s] Samples obtained: %i", fileName, numSamplesOgg); - int samplesObtained = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, wave.data, totalSamplesLength); - - TraceLog(DEBUG, "[%s] Samples obtained: %i", fileName, samplesObtained); - - TraceLog(INFO, "[%s] OGG file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, 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); } @@ -909,12 +1223,26 @@ static Wave LoadOGG(char *fileName) return wave; } -// Unload Wave data -static void UnloadWave(Wave wave) +// Load FLAC file into Wave structure +// NOTE: Using dr_flac library +static Wave LoadFLAC(const char *fileName) { - free(wave.data); - - TraceLog(INFO, "Unloaded wave data"); + Wave wave; + + // Decode an entire FLAC file in one go + uint64_t totalSampleCount; + wave.data = drflac_open_and_decode_file_s16(fileName, &wave.channels, &wave.sampleRate, &totalSampleCount); + + wave.sampleCount = (int)totalSampleCount/wave.channels; + wave.sampleSize = 16; + + // NOTE: Only support up to 2 channels (mono, stereo) + if (wave.channels > 2) TraceLog(WARNING, "[%s] FLAC channels number (%i) not supported", 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; } // Some required functions for audio standalone module version @@ -923,7 +1251,7 @@ static void UnloadWave(Wave wave) const char *GetExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); - if(!dot || dot == fileName) return ""; + if (!dot || dot == fileName) return ""; return (dot + 1); } @@ -938,7 +1266,7 @@ void TraceLog(int msgType, const char *text, ...) traceDebugMsgs = 0; #endif - switch(msgType) + switch (msgType) { case INFO: fprintf(stdout, "INFO: "); break; case ERROR: fprintf(stdout, "ERROR: "); break; @@ -958,4 +1286,4 @@ void TraceLog(int msgType, const char *text, ...) if (msgType == ERROR) exit(1); // If ERROR message, exit program } -#endif
\ No newline at end of file +#endif |
