diff options
| author | victorfisac <[email protected]> | 2016-10-06 20:57:31 +0200 |
|---|---|---|
| committer | victorfisac <[email protected]> | 2016-10-06 20:57:31 +0200 |
| commit | 2a158c47955d2d849e939152e0c174b02c500104 (patch) | |
| tree | fbf515597add99255f0e980d7a12ea33a3aa5585 /src/audio.c | |
| parent | 3e1321ac24743cf3df1c2bb923023f9c222aa250 (diff) | |
| parent | db6538859cd2fabb44f1f29cd87f5b498ca0c2c8 (diff) | |
| download | raylib-2a158c47955d2d849e939152e0c174b02c500104.tar.gz raylib-2a158c47955d2d849e939152e0c174b02c500104.zip | |
Merge remote-tracking branch 'refs/remotes/raysan5/develop' into develop
Diffstat (limited to 'src/audio.c')
| -rw-r--r-- | src/audio.c | 1340 |
1 files changed, 591 insertions, 749 deletions
diff --git a/src/audio.c b/src/audio.c index 38fefd12..90bf4968 100644 --- a/src/audio.c +++ b/src/audio.c @@ -2,7 +2,7 @@ * * raylib.audio * -* Basic functions to manage Audio: +* Basic functions to manage Audio: * Manage audio device (init/close) * Load and Unload audio files * Play/Stop/Pause/Resume loaded audio @@ -55,6 +55,7 @@ #include <string.h> // Required for: strcmp(), strncmp() #include <stdio.h> // 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 @@ -78,75 +79,40 @@ #define JAR_MOD_IMPLEMENTATION #include "external/jar_mod.h" // MOD loading functions +#ifdef _MSC_VER + #undef bool +#endif + //---------------------------------------------------------------------------------- // 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 increase this number +#define AUDIO_BUFFER_SIZE 4096 // PCM data samples (i.e. short: 32Kb) //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -// 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 enum { MUSIC_AUDIO_OGG = 0, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType; // 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 -typedef enum { - ERROR_RAW_CONTEXT_CREATION = 1, - ERROR_XM_CONTEXT_CREATION = 2, - ERROR_MOD_CONTEXT_CREATION = 4, - ERROR_MIX_CHANNEL_CREATION = 8, - ERROR_MUSIC_CHANNEL_CREATION = 16, - ERROR_LOADING_XM = 32, - ERROR_LOADING_MOD = 64, - ERROR_LOADING_WAV = 128, - ERROR_LOADING_OGG = 256, - ERROR_OUT_OF_MIX_CHANNELS = 512, - ERROR_EXTENSION_NOT_RECOGNIZED = 1024, - ERROR_UNABLE_TO_OPEN_RRES_FILE = 2048, - ERROR_INVALID_RRES_FILE = 4096, - ERROR_INVALID_RRES_RESOURCE = 8192, - ERROR_UNINITIALIZED_CHANNELS = 16384 -} AudioError; +typedef struct MusicData { + 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 (double buffering) + + 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; #if defined(AUDIO_STANDALONE) typedef enum { INFO = 0, ERROR, WARNING, DEBUG, OTHER } TraceLogType; @@ -155,26 +121,13 @@ 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 +// ... //---------------------------------------------------------------------------------- // 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(int index, int numBuffers); // Fill music buffers with data -static void EmptyMusicStream(int index); // Empty music buffers - -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 Wave LoadWAV(const char *fileName); // Load WAV file +static Wave LoadOGG(const char *fileName); // Load OGG file #if defined(AUDIO_STANDALONE) const char *GetExtension(const char *fileName); // Get the extension for a filename @@ -185,362 +138,120 @@ 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) StopMusicStream(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); 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; } } //---------------------------------------------------------------------------------- -// Module Functions Definition - Custom audio output +// Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- -// 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) +// Load wave data from file into RAM +Wave LoadWave(const char *fileName) { - 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; -} + Wave wave = { 0 }; -// 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; - } + if (strcmp(GetExtension(fileName), "wav") == 0) wave = LoadWAV(fileName); + else if (strcmp(GetExtension(fileName), "ogg") == 0) wave = LoadOGG(fileName); + else TraceLog(WARNING, "[%s] File extension not recognized, it can't be loaded", fileName); + + return wave; } -// 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) +// Load wave data from float array data (32bit) +Wave LoadWaveEx(float *data, int sampleCount, int sampleRate, int sampleSize, int channels) { - 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; + Wave wave; - alSourceUnqueueBuffers(mixc->alSource, 1, &buffer); - if (!buffer) return 0; + wave.data = data; + wave.sampleCount = sampleCount; + wave.sampleRate = sampleRate; + wave.sampleSize = 32; + wave.channels = channels; - 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); - } + // NOTE: Copy wave data to work with, + // user is responsible of input data to free + Wave cwave = WaveCopy(wave); - alSourceQueueBuffers(mixc->alSource, 1, &buffer); + WaveFormat(&cwave, sampleRate, sampleSize, channels); - 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; - } + return cwave; } -// 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) +// Load sound to memory +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) { - int numBuffered = 0; + Wave wave = LoadWave(fileName); - if (ctx >= 0) - { - MixChannel *mixc = mixChannels[ctx]; - numBuffered = BufferMixChannel(mixc, data, numberElements); - } + Sound sound = LoadSoundFromWave(wave); - 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 -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 - - 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; - } + 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 }; @@ -548,17 +259,29 @@ 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; + 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; + 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; @@ -575,19 +298,19 @@ Sound LoadSoundFromWave(Wave wave) ALuint buffer; alGenBuffers(1, &buffer); // Generate pointer to buffer + unsigned int dataSize = wave.sampleCount*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 (SampleRate: %i, SampleSize: %i, Channels: %i)", source, buffer, wave.sampleRate, wave.sampleSize, wave.channels); sound.source = source; sound.buffer = buffer; + sound.format = format; } return sound; @@ -602,9 +325,9 @@ Sound LoadSoundFromRES(const char *rresName, int resId) #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 @@ -614,11 +337,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) @@ -632,7 +351,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 { @@ -664,8 +382,7 @@ Sound LoadSoundFromRES(const char *rresName, int resId) fread(&reserved, 1, 1, rresFile); // <reserved> wave.sampleRate = sampleRate; - wave.dataSize = infoHeader.srcSize; - wave.bitsPerSample = bps; + wave.sampleSize = bps; wave.channels = (short)channels; unsigned char *data = malloc(infoHeader.size); @@ -676,55 +393,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 - UnloadWave(wave); + sound = LoadSoundFromWave(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; + // Sound is loaded, we can unload wave data + UnloadWave(wave); } + else TraceLog(WARNING, "[%s] Required resource do not seem to be a valid SOUND resource", rresName); } else { @@ -753,13 +427,48 @@ Sound LoadSoundFromRES(const char *rresName, int resId) return sound; } +// Unload Wave data +void UnloadWave(Wave wave) +{ + free(wave.data); + + TraceLog(INFO, "Unloaded wave data from RAM"); +} + // Unload sound void UnloadSound(Sound sound) { alDeleteSources(1, &sound.source); alDeleteBuffers(1, &sound.buffer); + + TraceLog(INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); +} + +// Update sound buffer with new data +// NOTE: data must match sound.format +void UpdateSound(Sound sound, 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 - TraceLog(INFO, "Unloaded sound data"); + 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 + + 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 + + // Upload new data to sound buffer + alBufferData(sound.buffer, sound.format, data, dataSize, sampleRate); + + // Attach sound buffer to source again + alSourcei(sound.source, AL_BUFFER, sound.buffer); } // Play a sound @@ -777,7 +486,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 @@ -789,6 +498,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) { @@ -819,396 +538,538 @@ 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) -// returns 0 on success or error code -int PlayMusicStream(int index, char *fileName) +// Convert wave data to desired format +// TODO: Consider channels (mono - stereo) +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { - int mixIndex; - - if (musicStreams[index].stream || musicStreams[index].xmctx) return ERROR_UNINITIALIZED_CHANNELS; // error - - 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(fileName),"ogg") == 0) + if (wave->sampleSize != sampleSize) { - // Open audio stream - musicStreams[index].stream = stb_vorbis_open_filename(fileName, NULL, NULL); + float *samples = GetWaveData(*wave); //Color *pixels = GetImageData(*image); - if (musicStreams[index].stream == NULL) - { - TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); - return ERROR_LOADING_OGG; // error - } - else - { - // Get file info - stb_vorbis_info info = stb_vorbis_get_info(musicStreams[index].stream); + free(wave->data); + + wave->sampleSize = sampleSize; - 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); + //sample *= 4.0f; // Arbitrary gain to get reasonable output volume... + //if (sample > 1.0f) sample = 1.0f; + //if (sample < -1.0f) sample = -1.0f; - musicStreams[index].loop = true; // We loop by default - musicStreams[index].enabled = true; - + if (sampleSize == 8) + { + wave->data = (unsigned char *)malloc(wave->sampleCount*sizeof(unsigned char)); - musicStreams[index].totalSamplesLeft = (unsigned int)stb_vorbis_stream_length_in_samples(musicStreams[index].stream) * info.channels; - musicStreams[index].totalLengthSeconds = stb_vorbis_stream_length_in_seconds(musicStreams[index].stream); - - if (info.channels == 2) - { - musicStreams[index].mixc = InitMixChannel(info.sample_rate, mixIndex, 2, false); - musicStreams[index].mixc->playing = true; - } - else + for (int i = 0; i < wave->sampleCount; i++) { - musicStreams[index].mixc = InitMixChannel(info.sample_rate, mixIndex, 1, false); - musicStreams[index].mixc->playing = true; + ((unsigned char *)wave->data)[i] = (unsigned char)((float)samples[i]*127 + 128); } - - if (!musicStreams[index].mixc) return ERROR_LOADING_OGG; // error } - } - else if (strcmp(GetExtension(fileName),"xm") == 0) - { - // only stereo is supported for xm - if (!jar_xm_create_context_from_file(&musicStreams[index].xmctx, 48000, fileName)) + else if (sampleSize == 16) { - musicStreams[index].chipTune = true; - musicStreams[index].loop = true; - jar_xm_set_max_loop_count(musicStreams[index].xmctx, 0); // infinite number of loops - musicStreams[index].totalSamplesLeft = (unsigned int)jar_xm_get_remaining_samples(musicStreams[index].xmctx); - musicStreams[index].totalLengthSeconds = ((float)musicStreams[index].totalSamplesLeft)/48000.0f; - musicStreams[index].enabled = true; + wave->data = (short *)malloc(wave->sampleCount*sizeof(short)); - TraceLog(INFO, "[%s] XM number of samples: %i", fileName, musicStreams[index].totalSamplesLeft); - TraceLog(INFO, "[%s] XM track length: %11.6f sec", fileName, musicStreams[index].totalLengthSeconds); - - musicStreams[index].mixc = InitMixChannel(48000, mixIndex, 2, true); - - if (!musicStreams[index].mixc) return ERROR_XM_CONTEXT_CREATION; // error - - musicStreams[index].mixc->playing = true; + for (int i = 0; i < wave->sampleCount; i++) + { + ((short *)wave->data)[i] = (short)((float)samples[i]*32000); // SHRT_MAX = 32767 + } } - else + else if (sampleSize == 32) { - TraceLog(WARNING, "[%s] XM file could not be opened", fileName); - return ERROR_LOADING_XM; // error + wave->data = (float *)malloc(wave->sampleCount*sizeof(float)); + + for (int i = 0; i < wave->sampleCount; i++) + { + ((float *)wave->data)[i] = (float)samples[i]; + } } + else TraceLog(WARNING, "Wave formatting: Sample size not supported"); + + free(samples); } - else if (strcmp(GetExtension(fileName),"mod") == 0) + + // NOTE: Only supported 1 or 2 channels (mono or stereo) + if ((channels > 0) && (channels < 3) && (wave->channels != channels)) { - jar_mod_init(&musicStreams[index].modctx); - - if (jar_mod_load_file(&musicStreams[index].modctx, fileName)) - { - musicStreams[index].chipTune = true; - musicStreams[index].loop = true; - musicStreams[index].totalSamplesLeft = (unsigned int)jar_mod_max_samples(&musicStreams[index].modctx); - musicStreams[index].totalLengthSeconds = ((float)musicStreams[index].totalSamplesLeft)/48000.0f; - musicStreams[index].enabled = true; - - TraceLog(INFO, "[%s] MOD number of samples: %i", fileName, musicStreams[index].totalSamplesLeft); - TraceLog(INFO, "[%s] MOD track length: %11.6f sec", fileName, musicStreams[index].totalLengthSeconds); - - musicStreams[index].mixc = InitMixChannel(48000, mixIndex, 2, false); - - if (!musicStreams[index].mixc) return ERROR_MOD_CONTEXT_CREATION; // error - - musicStreams[index].mixc->playing = true; - } - else - { - TraceLog(WARNING, "[%s] MOD file could not be opened", fileName); - return ERROR_LOADING_MOD; // error - } + // TODO: Add/remove channels interlaced data if required... } - else +} + +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave; + + 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"); + + if (newWave.data != NULL) { - TraceLog(WARNING, "[%s] Music extension not recognized, it can't be loaded", fileName); - return ERROR_EXTENSION_NOT_RECOGNIZED; // error + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8); + + newWave.sampleCount = wave.sampleCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; } - - return 0; // normal return + + return newWave; } -// Stop music playing for individual music index of musicStreams array (close stream) -void StopMusicStream(int index) +// 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 (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) + if ((initSample >= 0) && (initSample < finalSample) && + (finalSample > 0) && (finalSample < wave->sampleCount)) { - CloseMixChannel(musicStreams[index].mixc); - - 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); + // TODO: Review cropping (it could be simplified...) - musicStreams[index].enabled = false; + float *samples = GetWaveData(*wave); + float *cropSamples = (float *)malloc((finalSample - initSample)*sizeof(float)); - if (musicStreams[index].stream || musicStreams[index].xmctx) - { - musicStreams[index].stream = NULL; - musicStreams[index].xmctx = NULL; - } + 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); } + else TraceLog(WARNING, "Wave crop range out of bounds"); } -// Update (re-fill) music buffers if data already processed -void UpdateMusicStream(int index) +// 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) { - ALenum state; - bool active = true; - ALint processed = 0; + float *samples = (float *)malloc(wave.sampleCount*sizeof(float)); - // Determine if music stream is ready to be written - alGetSourcei(musicStreams[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); + 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]; + } - if (musicStreams[index].mixc->playing && (index < MAX_MUSIC_STREAMS) && musicStreams[index].enabled && musicStreams[index].mixc && (processed > 0)) + return samples; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing (.OGG) +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) +{ + Music music = (MusicData *)malloc(sizeof(MusicData)); + + if (strcmp(GetExtension(fileName), "ogg") == 0) { - active = BufferMusicStream(index, processed); - - if (!active && musicStreams[index].loop) + // Open ogg audio stream + music->ctxOgg = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (music->ctxOgg == NULL) TraceLog(WARNING, "[%s] OGG audio file could not be opened", fileName); + else { - if (musicStreams[index].chipTune) - { - if(musicStreams[index].modctx.mod_loaded) jar_mod_seek_start(&musicStreams[index].modctx); - - musicStreams[index].totalSamplesLeft = musicStreams[index].totalLengthSeconds*48000.0f; - } - else - { - stb_vorbis_seek_start(musicStreams[index].stream); - musicStreams[index].totalSamplesLeft = stb_vorbis_stream_length_in_samples(musicStreams[index].stream)*musicStreams[index].mixc->channels; - } - - // Determine if music stream is ready to be written - alGetSourcei(musicStreams[index].mixc->alSource, AL_BUFFERS_PROCESSED, &processed); - - active = BufferMusicStream(index, processed); + 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 + 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; + music->ctxType = MUSIC_AUDIO_OGG; + music->loop = true; // We loop by default + + 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), "xm") == 0) + { + int result = jar_xm_create_context_from_file(&music->ctxXm, 48000, fileName); - if (alGetError() != AL_NO_ERROR) TraceLog(WARNING, "Error buffering data..."); - - alGetSourcei(musicStreams[index].mixc->alSource, AL_SOURCE_STATE, &state); + if (!result) // XM context created successfully + { + jar_xm_set_max_loop_count(music->ctxXm, 0); // Set infinite number of loops - if (state != AL_PLAYING && active) alSourcePlay(musicStreams[index].mixc->alSource); + // NOTE: Only stereo is supported for XM + music->stream = InitAudioStream(48000, 32, 2); + music->totalSamples = (unsigned int)jar_xm_get_remaining_samples(music->ctxXm); + music->samplesLeft = music->totalSamples; + music->ctxType = MUSIC_MODULE_XM; + music->loop = true; - if (!active) StopMusicStream(index); - + 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->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); + } + 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; } -//get number of music channels active at this time, this does not mean they are playing -int GetMusicStreamCount(void) +// Unload music stream +void UnloadMusicStream(Music music) { - 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; + 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); +} + +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + alSourcePlay(music->stream.source); } // Pause music playing -void PauseMusicStream(int index) +void PauseMusicStream(Music music) +{ + alSourcePause(music->stream.source); +} + +// Resume music playing +void ResumeMusicStream(Music music) +{ + ALenum state; + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); + + if (state == AL_PAUSED) alSourcePlay(music->stream.source); +} + +// Stop music playing (close stream) +// TODO: Restart XM context +void StopMusicStream(Music music) { - // Pause music stream if music available! - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc && musicStreams[index].enabled) + alSourceStop(music->stream.source); + + switch (music->ctxType) { - TraceLog(INFO, "Pausing music stream"); - alSourcePause(musicStreams[index].mixc->alSource); - musicStreams[index].mixc->playing = false; + 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; } -// Resume music playing -void ResumeMusicStream(int index) +// Update (re-fill) music buffers if data already processed +void UpdateMusicStream(Music music) { - // Resume music playing... if music available! ALenum state; + ALint processed = 0; - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) + 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) { - alGetSourcei(musicStreams[index].mixc->alSource, AL_SOURCE_STATE, &state); + 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 + + 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, numSamplesOgg*music->stream.channels); + music->samplesLeft -= (numSamplesOgg*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; + } + } - if (state == AL_PAUSED) + // 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) + + if (music->loop) PlayMusicStream(music); // Play again + } + else { - TraceLog(INFO, "Resuming music stream"); - alSourcePlay(musicStreams[index].mixc->alSource); - musicStreams[index].mixc->playing = true; + // 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); } } } // Check if any music is playing -bool IsMusicPlaying(int index) +bool IsMusicPlaying(Music music) { bool playing = false; ALint state; - - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) - { - alGetSourcei(musicStreams[index].mixc->alSource, AL_SOURCE_STATE, &state); - - if (state == AL_PLAYING) playing = true; - } + + alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); + + if (state == AL_PLAYING) playing = true; return playing; } // Set volume for music -void SetMusicVolume(int index, float volume) +void SetMusicVolume(Music music, float volume) { - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) - { - alSourcef(musicStreams[index].mixc->alSource, AL_GAIN, volume); - } + alSourcef(music->stream.source, AL_GAIN, volume); } // Set pitch for music -void SetMusicPitch(int index, float pitch) +void SetMusicPitch(Music music, float pitch) { - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) - { - alSourcef(musicStreams[index].mixc->alSource, AL_PITCH, pitch); - } + alSourcef(music->stream.source, AL_PITCH, pitch); } // Get music time length (in seconds) -float GetMusicTimeLength(int index) +float GetMusicTimeLength(Music music) { - float totalSeconds; - - if (musicStreams[index].chipTune) totalSeconds = (float)musicStreams[index].totalLengthSeconds; - else totalSeconds = stb_vorbis_stream_length_in_seconds(musicStreams[index].stream); + float totalSeconds = (float)music->totalSamples/music->stream.sampleRate; return totalSeconds; } // Get current music time played (in seconds) -float GetMusicTimePlayed(int index) +float GetMusicTimePlayed(Music music) { float secondsPlayed = 0.0f; - - if (index < MAX_MUSIC_STREAMS && musicStreams[index].mixc) + + unsigned int samplesPlayed = music->totalSamples - music->samplesLeft; + secondsPlayed = (float)samplesPlayed/(music->stream.sampleRate*music->stream.channels); + + return secondsPlayed; +} + +// Init audio stream (to stream audio pcm data) +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) { - if (musicStreams[index].chipTune && musicStreams[index].xmctx) - { - uint64_t samples; - jar_xm_get_position(musicStreams[index].xmctx, NULL, NULL, NULL, &samples); - secondsPlayed = (float)samples/(48000.0f*musicStreams[index].mixc->channels); // Not sure if this is the correct value - } - else if(musicStreams[index].chipTune && musicStreams[index].modctx.mod_loaded) + switch (sampleSize) { - long numsamp = jar_mod_current_samples(&musicStreams[index].modctx); - secondsPlayed = (float)numsamp/(48000.0f); + 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 + } + else if (channels == 2) + { + switch (sampleSize) { - int totalSamples = stb_vorbis_stream_length_in_samples(musicStreams[index].stream)*musicStreams[index].mixc->channels; - int samplesPlayed = totalSamples - musicStreams[index].totalSamplesLeft; - secondsPlayed = (float)samplesPlayed/(musicStreams[index].mixc->sampleRate*musicStreams[index].mixc->channels); + 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 TraceLog(WARNING, "Init audio stream: Number of channels not supported: %i", channels); - return secondsPlayed; -} + // 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); -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- + // Create Buffers (double buffering) + alGenBuffers(MAX_STREAM_BUFFERS, stream.buffers); -// Fill music buffers with new data from music stream -static bool BufferMusicStream(int index, int numBuffers) -{ - short pcm[MUSIC_BUFFER_SIZE_SHORT]; - float pcmf[MUSIC_BUFFER_SIZE_FLOAT]; - - 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. + // Initialize buffer with zeros by default + for (int i = 0; i < MAX_STREAM_BUFFERS; i++) { - for (int i = 0; i < numBuffers; i++) + if (stream.sampleSize == 8) { - 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; - - 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; - - 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); - } - - musicStreams[index].totalSamplesLeft -= size; - - if (musicStreams[index].totalSamplesLeft <= 0) - { - active = false; - break; - } + unsigned char pcm[AUDIO_BUFFER_SIZE] = { 0 }; + alBufferData(stream.buffers[i], stream.format, pcm, AUDIO_BUFFER_SIZE*sizeof(unsigned char), stream.sampleRate); } - } - else - { - if (musicStreams[index].totalSamplesLeft >= MUSIC_BUFFER_SIZE_SHORT) size = MUSIC_BUFFER_SIZE_SHORT; - else size = musicStreams[index].totalSamplesLeft; - - for (int i = 0; i < numBuffers; i++) + else if (stream.sampleSize == 16) { - 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) - { - active = false; - break; - } + 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 active; + alSourceQueueBuffers(stream.source, MAX_STREAM_BUFFERS, stream.buffers); + + TraceLog(INFO, "[AUD ID %i] Audio stream loaded successfully", stream.source); + + return stream; } -// Empty music buffers -static void EmptyMusicStream(int index) +// 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(musicStreams[index].mixc->alSource, AL_BUFFERS_QUEUED, &queued); + ALuint buffer = 0; while (queued > 0) { - alSourceUnqueueBuffers(musicStreams[index].mixc->alSource, 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 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); + + // 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; + + // Determine if music stream is ready to be written + alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed); + + return (processed > 0); +} + +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + alSourcePlay(stream.source); +} + +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + alSourcePause(stream.source); +} + +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ALenum state; + alGetSourcei(stream.source, AL_SOURCE_STATE, &state); + + 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) { @@ -1288,18 +1149,18 @@ static Wave LoadWAV(const char *fileName) else { // Allocate memory for data - wave.data = (unsigned char *)malloc(sizeof(unsigned char) * waveData.subChunkSize); + wave.data = (unsigned char *)malloc(sizeof(unsigned char)*waveData.subChunkSize); // Read in the sound data into the soundData variable fread(wave.data, waveData.subChunkSize, 1, wavFile); // Now we set the variables that we need later - wave.dataSize = waveData.subChunkSize; + wave.sampleCount = waveData.subChunkSize; wave.sampleRate = waveFormat.sampleRate; + wave.sampleSize = waveFormat.bitsPerSample; wave.channels = waveFormat.numChannels; - wave.bitsPerSample = waveFormat.bitsPerSample; - TraceLog(INFO, "[%s] WAV file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); + TraceLog(INFO, "[%s] WAV file loaded successfully (SampleRate: %i, SampleSize: %i, Channels: %i)", fileName, wave.sampleRate, wave.sampleSize, wave.channels); } } } @@ -1312,7 +1173,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; @@ -1328,35 +1189,24 @@ 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); - + int totalSamplesLength = (stb_vorbis_stream_length_in_samples(oggFile)*info.channels); 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.sampleCount = totalSamples; - TraceLog(DEBUG, "[%s] Total samples calculated: %i", fileName, totalSamples); + wave.data = (short *)malloc(totalSamplesLength*sizeof(short)); - wave.data = malloc(sizeof(short)*totalSamplesLength); - - int samplesObtained = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, wave.data, totalSamplesLength); + int samplesObtained = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)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 (SampleRate: %i, SampleSize: %i, Channels: %i)", fileName, wave.sampleRate, wave.sampleSize, wave.channels); stb_vorbis_close(oggFile); } @@ -1364,21 +1214,13 @@ static Wave LoadOGG(char *fileName) return wave; } -// Unload Wave data -static void UnloadWave(Wave wave) -{ - free(wave.data); - - TraceLog(INFO, "Unloaded wave data"); -} - // Some required functions for audio standalone module version #if defined(AUDIO_STANDALONE) // Get the extension for a filename const char *GetExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); - if(!dot || dot == fileName) return ""; + if (!dot || dot == fileName) return ""; return (dot + 1); } @@ -1393,7 +1235,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; |
