summaryrefslogtreecommitdiffhomepage
path: root/src/audio.c
diff options
context:
space:
mode:
authorvictorfisac <[email protected]>2017-03-06 09:47:08 +0100
committervictorfisac <[email protected]>2017-03-06 09:47:08 +0100
commitf9277f216372179560c560427beccdd2e5c5d094 (patch)
tree8d3858c978f2b36ea8912f25e3cbe6fa56952aff /src/audio.c
parentce56fcb1eda06385b88c1a906f0968d742ff8130 (diff)
parentc05701253e0a4eda211a0d7ced74ae29d6585917 (diff)
downloadraylib-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.c1280
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, &currentMusic.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, &currentMusic.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