From 539a9ca50eda438df66e92327990b25e8c9c59fc Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 25 Oct 2017 01:24:17 +0200 Subject: Corrected ImageTextEx() - Added new function: GenImageColor() ImageDraw() should be reviewed... specially alpha blending... --- src/raylib.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 392e0a24..3decb464 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -940,6 +940,7 @@ RLAPI void ImageColorContrast(Image *image, float contrast); RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) // Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color RLAPI Image GenImageGradientV(int width, int height, Color top, Color bottom); // Generate image: vertical gradient RLAPI Image GenImageGradientH(int width, int height, Color left, Color right); // Generate image: horizontal gradient RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient -- cgit v1.2.3 From 9e0105a1a3c3107206c2940706470d66cf466447 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Nov 2017 16:57:20 +0100 Subject: Corrected function name --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 3decb464..aa15183c 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -764,7 +764,7 @@ RLAPI Color Fade(Color color, float alpha); // Color fade- RLAPI float *ColorToFloat(Color color); // Converts Color to float array and normalizes // Math useful functions (available from raymath.h) -RLAPI float *VectorToFloat(Vector3 vec); // Returns Vector3 as float array +RLAPI float *Vector3ToFloat(Vector3 vec); // Returns Vector3 as float array RLAPI float *MatrixToFloat(Matrix mat); // Returns Matrix as float array RLAPI Vector3 Vector3Zero(void); // Vector with components value 0.0f RLAPI Vector3 Vector3One(void); // Vector with components value 1.0f -- cgit v1.2.3 From 879c2f484ba575efd215767088b931c7806af434 Mon Sep 17 00:00:00 2001 From: Wilhem Barbier Date: Sun, 5 Nov 2017 21:57:29 +0100 Subject: SetShaderValue take const pointer --- src/raylib.h | 4 ++-- src/rlgl.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index aa15183c..e5b74743 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1075,8 +1075,8 @@ RLAPI Texture2D GetTextureDefault(void); // Get // Shader configuration functions RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); // Set shader uniform value (float) -RLAPI void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size); // Set shader uniform value (int) +RLAPI void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size); // Set shader uniform value (float) +RLAPI void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size); // Set shader uniform value (int) RLAPI void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) RLAPI void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) RLAPI void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) diff --git a/src/rlgl.c b/src/rlgl.c index 655b03ca..affe54ff 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -2452,7 +2452,7 @@ int GetShaderLocation(Shader shader, const char *uniformName) } // Set shader uniform value (float) -void SetShaderValue(Shader shader, int uniformLoc, float *value, int size) +void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glUseProgram(shader.id); @@ -2468,7 +2468,7 @@ void SetShaderValue(Shader shader, int uniformLoc, float *value, int size) } // Set shader uniform value (int) -void SetShaderValuei(Shader shader, int uniformLoc, int *value, int size) +void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) glUseProgram(shader.id); -- cgit v1.2.3 From 75433a670e0880c4d23d5178b073836de3628547 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 12 Nov 2017 14:17:05 +1000 Subject: Initial work on adding support for mini_al. --- src/audio.c | 414 +- src/external/mini_al.c | 2 + src/external/mini_al.h | 10437 +++++++++++++++++++++++++++++++++++++++++++++++ src/raylib.h | 2 + 4 files changed, 10843 insertions(+), 12 deletions(-) create mode 100644 src/external/mini_al.c create mode 100644 src/external/mini_al.h (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index 06af8ed4..a5f117b5 100644 --- a/src/audio.c +++ b/src/audio.c @@ -72,6 +72,8 @@ #define SUPPORT_FILEFORMAT_MOD //------------------------------------------------- +#define USE_MINI_AL 1 // Set to 1 to use mini_al; 0 to use OpenAL. + #if defined(AUDIO_STANDALONE) #include "audio.h" #include // Required for: va_list, va_start(), vfprintf(), va_end() @@ -80,17 +82,21 @@ #include "utils.h" // Required for: fopen() Android mapping #endif -#if defined(__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 "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) +//#if USE_MINI_AL + #include "external/mini_al.h" // Implemented in mini_al.c. Cannot implement this here because it conflicts with Win32 APIs such as CloseWindow(), etc. +//#else + #if defined(__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 "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) +//#endif #include // Required for: malloc(), free() #include // Required for: strcmp(), strncmp() @@ -200,10 +206,195 @@ void TraceLog(int msgType, const char *text, ...); // Show trace lo //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Device initialization and Closing //---------------------------------------------------------------------------------- +#if USE_MINI_AL +#define DEVICE_FORMAT mal_format_f32 +#define DEVICE_CHANNELS 2 +#define DEVICE_SAMPLE_RATE 44100 + +typedef struct SoundInternal SoundInternal; +struct SoundInternal +{ + mal_format format; + mal_uint32 channels; + mal_uint32 sampleRate; + mal_uint32 frameCount; + mal_uint32 frameCursorPos; // Keeps track of the next frame to read when mixing + float volume; + float pitch; + bool playing; + bool paused; + bool looping; + SoundInternal* next; + SoundInternal* prev; + mal_uint8 data[1]; // Raw audio data. +}; + +static mal_context context; +static mal_device device; +static mal_bool32 isAudioInitialized = MAL_FALSE; +static float masterVolume = 1; +static mal_mutex soundLock; +static SoundInternal* firstSound; // Sounds are tracked in a linked list. +static SoundInternal* lastSound; + +static void AppendSound(SoundInternal* internalSound) +{ + mal_mutex_lock(&context, &soundLock); + { + if (firstSound == NULL) { + firstSound = internalSound; + } else { + lastSound->next = internalSound; + internalSound->prev = lastSound; + } + + lastSound = internalSound; + } + mal_mutex_unlock(&context, &soundLock); +} + +static void RemoveSound(SoundInternal* internalSound) +{ + mal_mutex_lock(&context, &soundLock); + { + if (internalSound->prev == NULL) { + firstSound = internalSound->next; + } else { + internalSound->prev->next = internalSound->next; + } + + if (internalSound->next == NULL) { + lastSound = internalSound->prev; + } else { + internalSound->next->prev = internalSound->prev; + } + } + mal_mutex_unlock(&context, &soundLock); +} + +static void OnLog_MAL(mal_context* pContext, mal_device* pDevice, const char* message) +{ + (void)pContext; + (void)pDevice; + TraceLog(LOG_ERROR, message); // All log messages from mini_al are errors. +} + +static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameCount, void* pFramesOut) +{ + // This is where all of the mixing takes place. + (void)pDevice; + + // Mixing is basically just an accumulation. We need to initialize the output buffer to 0. + memset(pFramesOut, 0, frameCount*pDevice->channels*mal_get_sample_size_in_bytes(pDevice->format)); + + // Using a mutex here for thread-safety which makes things not real-time. This is unlikely to be necessary for this project, but may + // want to consider how you might want to avoid this. + mal_mutex_lock(&context, &soundLock); + { + float* pFramesOutF = (float*)pFramesOut; // <-- Just for convenience. + + // Sounds. + for (SoundInternal* internalSound = firstSound; internalSound != NULL; internalSound = internalSound->next) + { + // Ignore stopped or paused sounds. + if (!internalSound->playing || internalSound->paused) { + continue; + } + + mal_uint32 framesRead = 0; + for (;;) { + if (framesRead > frameCount) { + TraceLog(LOG_DEBUG, "Mixed too many frames from sound"); + break; + } + if (framesRead == frameCount) { + break; + } + + // Keep reading until the end of the buffer, or we've already read as much as is allowed. + mal_uint32 framesToRead = (frameCount - framesRead); + mal_uint32 framesRemaining = (internalSound->frameCount - internalSound->frameCursorPos); + if (framesToRead > framesRemaining) { + framesToRead = framesRemaining; + } + + // This is where the real mixing takes place. This can be optimized. This assumes the device and sound are of the same format. + // + // TODO: Implement pitching. + for (mal_uint32 iFrame = 0; iFrame < framesToRead; ++iFrame) { + float* pFrameOut = pFramesOutF + ((framesRead+iFrame) * device.channels); + float* pFrameIn = ((float*)internalSound->data) + ((internalSound->frameCursorPos+iFrame) * device.channels); + + for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) { + pFrameOut[iChannel] += pFrameIn[iChannel] * masterVolume * internalSound->volume; + } + } + + framesRead += framesToRead; + internalSound->frameCursorPos += framesToRead; + + // If we've reached the end of the sound's internal buffer we do one of two things: loop back to the start, or just stop. + if (framesToRead == framesRemaining) { + if (!internalSound->looping) { + break; + } + } + } + } + + // Music. + // TODO: Implement me. + } + mal_mutex_unlock(&context, &soundLock); + + return frameCount; // We always output the same number of frames that were originally requested. +} +#endif // Initialize audio device void InitAudioDevice(void) { +#if USE_MINI_AL + // Context. + mal_context_config contextConfig = mal_context_config_init(OnLog_MAL); + mal_result result = mal_context_init(NULL, 0, &contextConfig, &context); + if (result != MAL_SUCCESS) + { + return; + } + + // Device. Using the default device. Format is floating point because it simplifies mixing. + mal_device_config deviceConfig = mal_device_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, OnSendAudioDataToDevice); + result = mal_device_init(&context, mal_device_type_playback, NULL, &deviceConfig, NULL, &device); + if (result != MAL_SUCCESS) + { + mal_context_uninit(&context); + return; + } + + // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running + // while there's at least one sound being played. + result = mal_device_start(&device); + if (result != MAL_SUCCESS) + { + mal_device_uninit(&device); + mal_context_uninit(&context); + return; + } + + // Mixing happens on a seperate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may + // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. + if (!mal_mutex_create(&context, &soundLock)) + { + TraceLog(LOG_ERROR, "Failed to create mutex for audio mixing"); + mal_device_uninit(&device); + mal_context_uninit(&context); + return; + } + + + isAudioInitialized = MAL_TRUE; +#else // Open and initialize a device with default settings ALCdevice *device = alcOpenDevice(NULL); @@ -230,13 +421,30 @@ void InitAudioDevice(void) alListener3f(AL_ORIENTATION, 0.0f, 0.0f, -1.0f); alListenerf(AL_GAIN, 1.0f); + + if (alIsExtensionPresent("AL_EXT_float32")) { + TraceLog(LOG_INFO, "AL_EXT_float32 supported"); + } else { + TraceLog(LOG_INFO, "AL_EXT_float32 not supported"); + } } } +#endif } // Close the audio device for all contexts void CloseAudioDevice(void) { +#if USE_MINI_AL + if (!isAudioInitialized) { + TraceLog(LOG_WARNING, "Could not close audio device because it is not currently initialized"); + return; + } + + mal_mutex_delete(&context, &soundLock); + mal_device_uninit(&device); + mal_context_uninit(&context); +#else ALCdevice *device; ALCcontext *context = alcGetCurrentContext(); @@ -247,6 +455,7 @@ void CloseAudioDevice(void) alcMakeContextCurrent(NULL); alcDestroyContext(context); alcCloseDevice(device); +#endif TraceLog(LOG_INFO, "Audio device closed successfully"); } @@ -254,6 +463,9 @@ void CloseAudioDevice(void) // Check if device has been initialized successfully bool IsAudioDeviceReady(void) { +#if USE_MINI_AL + return isAudioInitialized; +#else ALCcontext *context = alcGetCurrentContext(); if (context == NULL) return false; @@ -264,6 +476,7 @@ bool IsAudioDeviceReady(void) if (device == NULL) return false; else return true; } +#endif } // Set master volume (listener) @@ -271,8 +484,12 @@ void SetMasterVolume(float volume) { if (volume < 0.0f) volume = 0.0f; else if (volume > 1.0f) volume = 1.0f; - + +#if USE_MINI_AL + masterVolume = 1; +#else alListenerf(AL_GAIN, volume); +#endif } //---------------------------------------------------------------------------------- @@ -349,6 +566,47 @@ Sound LoadSoundFromWave(Wave wave) if (wave.data != NULL) { +#if USE_MINI_AL + // When using mini_al we need to do our own mixing. To simplify this we need convert the format of each sound to be consistent with + // the format used to open the playback device. We can do this two ways: + // + // 1) Convert the whole sound in one go at load time (here). + // 2) Convert the audio data in chunks at mixing time. + // + // I have decided on the first option because it offloads work required for the format conversion to the to the loading stage. The + // downside to this is that it uses more memory if the original sound is u8 or s16. + mal_format formatIn = ((wave.sampleSize == 8) ? mal_format_u8 : ((wave.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + mal_uint32 frameCountIn = wave.sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. + + mal_uint32 frameCount = mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); + if (frameCount == 0) { + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to get frame count for format conversion."); + } + + SoundInternal* internalSound = (SoundInternal*)calloc(sizeof(*internalSound) + (frameCount*DEVICE_CHANNELS*4), 1); // <-- Make sure this is initialized to zero for safety. + if (internalSound == NULL) { + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to allocate memory for internal buffer"); + } + + frameCount = mal_convert_frames(internalSound->data, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); + if (frameCount == 0) { + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Format conversion failed."); + } + + internalSound->format = DEVICE_FORMAT; + internalSound->channels = DEVICE_CHANNELS; + internalSound->sampleRate = DEVICE_SAMPLE_RATE; + internalSound->frameCount = frameCount; + internalSound->frameCursorPos = 0; + internalSound->volume = 1; + internalSound->pitch = 1; + internalSound->playing = 0; + internalSound->paused = 0; + internalSound->looping = 0; + AppendSound(internalSound); + + sound.handle = (void*)internalSound; +#else ALenum format = 0; // The OpenAL format is worked out by looking at the number of channels and the sample size (bits per sample) @@ -402,6 +660,7 @@ Sound LoadSoundFromWave(Wave wave) sound.source = source; sound.buffer = buffer; sound.format = format; +#endif } return sound; @@ -418,10 +677,16 @@ void UnloadWave(Wave wave) // Unload sound void UnloadSound(Sound sound) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + RemoveSound(internalSound); + free(internalSound); +#else alSourceStop(sound.source); alDeleteSources(1, &sound.source); alDeleteBuffers(1, &sound.buffer); +#endif TraceLog(LOG_INFO, "[SND ID %i][BUFR ID %i] Unloaded sound data from RAM", sound.source, sound.buffer); } @@ -430,6 +695,22 @@ void UnloadSound(Sound sound) // NOTE: data must match sound.format void UpdateSound(Sound sound, const void *data, int samplesCount) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound"); + return; + } + + internalSound->playing = false; + internalSound->paused = false; + internalSound->frameCursorPos = 0; + + // TODO: May want to lock/unlock this since this data buffer is read at mixing time. However, this puts a mutex in + // in the mixing code which makes it no longer real-time. This is likely not a critical issue for this project, though. + memcpy(internalSound->data, data, samplesCount*internalSound->channels*mal_get_sample_size_in_bytes(internalSound->format)); +#else ALint sampleRate, sampleSize, channels; alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate); alGetBufferi(sound.buffer, AL_BITS, &sampleSize); // It could also be retrieved from sound.format @@ -451,12 +732,26 @@ void UpdateSound(Sound sound, const void *data, int samplesCount) // Attach sound buffer to source again alSourcei(sound.source, AL_BUFFER, sound.buffer); +#endif } // Play a sound void PlaySound(Sound sound) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "PlaySound() : Invalid sound"); + return; + } + + internalSound->playing = 1; + internalSound->paused = 0; + internalSound->frameCursorPos = 0; +#else alSourcePlay(sound.source); // Play the sound +#endif //TraceLog(LOG_INFO, "Playing sound"); @@ -477,28 +772,72 @@ void PlaySound(Sound sound) // Pause a sound void PauseSound(Sound sound) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "PauseSound() : Invalid sound"); + return; + } + + internalSound->paused = true; +#else alSourcePause(sound.source); +#endif } // Resume a paused sound void ResumeSound(Sound sound) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "ResumeSound() : Invalid sound"); + return; + } + + internalSound->paused = false; +#else ALenum state; alGetSourcei(sound.source, AL_SOURCE_STATE, &state); if (state == AL_PAUSED) alSourcePlay(sound.source); +#endif } // Stop reproducing a sound void StopSound(Sound sound) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "StopSound() : Invalid sound"); + return; + } + + internalSound->playing = false; + internalSound->paused = false; +#else alSourceStop(sound.source); +#endif } // Check if a sound is playing bool IsSoundPlaying(Sound sound) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "IsSoundPlaying() : Invalid sound"); + return false; + } + + return internalSound->playing && !internalSound->paused; +#else bool playing = false; ALint state; @@ -506,23 +845,73 @@ bool IsSoundPlaying(Sound sound) if (state == AL_PLAYING) playing = true; return playing; +#endif } // Set volume for a sound void SetSoundVolume(Sound sound, float volume) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "SetSoundVolume() : Invalid sound"); + return; + } + + internalSound->volume = volume; +#else alSourcef(sound.source, AL_GAIN, volume); +#endif } // Set pitch for a sound void SetSoundPitch(Sound sound, float pitch) { +#if USE_MINI_AL + SoundInternal* internalSound = (SoundInternal*)sound.handle; + if (internalSound == NULL) + { + TraceLog(LOG_ERROR, "SetSoundPitch() : Invalid sound"); + return; + } + + internalSound->pitch = pitch; +#else alSourcef(sound.source, AL_PITCH, pitch); +#endif } // Convert wave data to desired format void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { + mal_format formatIn = ((wave->sampleSize == 8) ? mal_format_u8 : ((wave->sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + mal_format formatOut = (( sampleSize == 8) ? mal_format_u8 : (( sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + + mal_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. + + mal_uint32 frameCount = mal_convert_frames(NULL, formatOut, channels, sampleRate, NULL, formatIn, wave->channels, wave->sampleRate, frameCountIn); + if (frameCount == 0) { + TraceLog(LOG_ERROR, "WaveFormat() : Failed to get frame count for format conversion."); + return; + } + + void* data = malloc(frameCount * channels * (sampleSize/8)); + + frameCount = mal_convert_frames(data, formatOut, channels, sampleRate, wave->data, formatIn, wave->channels, wave->sampleRate, frameCountIn); + if (frameCount == 0) { + TraceLog(LOG_ERROR, "WaveFormat() : Format conversion failed."); + return; + } + + wave->sampleCount = frameCount; + wave->sampleSize = sampleSize; + wave->sampleRate = sampleRate; + wave->channels = channels; + free(wave->data); + wave->data = data; + +#if 0 // Format sample rate // NOTE: Only supported 22050 <--> 44100 if (wave->sampleRate != sampleRate) @@ -601,6 +990,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) free(wave->data); wave->data = data; } +#endif } // Copy a wave to a new wave diff --git a/src/external/mini_al.c b/src/external/mini_al.c new file mode 100644 index 00000000..2ea2cc6b --- /dev/null +++ b/src/external/mini_al.c @@ -0,0 +1,2 @@ +#define MAL_IMPLEMENTATION +#include "mini_al.h" // <-- The implementation of mini_al.h #includes windows.h, so need to #undef some stuff. \ No newline at end of file diff --git a/src/external/mini_al.h b/src/external/mini_al.h new file mode 100644 index 00000000..49347e42 --- /dev/null +++ b/src/external/mini_al.h @@ -0,0 +1,10437 @@ +// Mini audio library. Public domain. See "unlicense" statement at the end of this file. +// mini_al - v0.x - 2017-xx-xx +// +// David Reid - davidreidsoftware@gmail.com + +// ABOUT +// ===== +// mini_al is a small library for making it easy to connect to a playback or capture device and send +// or receive data from that device. +// +// mini_al uses an asynchronous API. Every device is created with it's own thread, with audio data +// being delivered to or from the device via a callback. Synchronous APIs are not supported in the +// interest of keeping the library as simple and light-weight as possible. +// +// Supported Backends: +// - WASAPI +// - DirectSound +// - WinMM +// - ALSA +// - OSS +// - OpenSL|ES / Android +// - OpenAL +// - Null (Silence) +// - ... and more in the future. +// - Core Audio (OSX, iOS) +// +// Supported Formats: +// - Unsigned 8-bit PCM +// - Signed 16-bit PCM +// - Signed 24-bit PCM (tightly packed) +// - Signed 32-bit PCM +// - IEEE 32-bit floating point PCM +// +// +// USAGE +// ===== +// mini_al is a single-file library. To use it, do something like the following in one .c file. +// #define MAL_IMPLEMENTATION +// #include "mini_al.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. +// +// The implementation of this library will try #include-ing necessary headers for each backend. If you do not have +// the development packages for any particular backend you can disable it by #define-ing the appropriate MAL_NO_* +// option before the implementation. +// +// +// Building (Windows) +// ------------------ +// The Windows build should compile clean on all modern versions of MSVC without the need to configure any include +// paths nor link to any libraries. The same applies to MinGW/GCC and Clang. +// +// Building (Linux) +// ---------------- +// The Linux build uses ALSA for it's backend so you will need to install the relevant ALSA development packages +// for your preferred distro. It also uses pthreads. Dependencies are dynamically linked at runtime so you do not +// need to link to -lasound nor -lpthread. You will need to link to -ldl. +// +// Building (BSD) +// -------------- +// The BSD build uses OSS and should Just Work without any linking nor include path configuration. +// +// +// Playback Example +// ---------------- +// mal_uint32 on_send_samples(mal_device* pDevice, mal_uint32 frameCount, void* pSamples) +// { +// // This callback is set at initialization time and will be called when a playback device needs more +// // data. You need to write as many frames as you can to pSamples (but no more than frameCount) and +// // then return the number of frames you wrote. +// // +// // The user data (pDevice->pUserData) is set by mal_device_init(). +// return (mal_uint32)drwav_read_f32((drwav*)pDevice->pUserData, frameCount * pDevice->channels, (float*)pSamples) / pDevice->channels; +// } +// +// ... +// +// mal_context context; +// if (mal_context_init(NULL, 0, NULL, &context) != MAL_SUCCESS) { +// printf("Failed to initialize context."); +// return -3; +// } +// +// mal_device_config config = mal_device_config_init_playback(mal_format_s16, wav.channels, wav.sampleRate, on_send_frames_to_device); +// +// mal_device device; +// mal_result result = mal_device_init(&context, mal_device_type_playback, NULL, &config, pMyData, &device); +// if (result != MAL_SUCCESS) { +// return -1; +// } +// +// mal_device_start(&device); // The device is sleeping by default so you'll need to start it manually. +// +// ... +// +// mal_device_uninit(&device); // This will stop the device so no need to do that manually. +// +// +// +// NOTES +// ===== +// - This library uses an asynchronous API for delivering and requesting audio data. Each device will have +// it's own worker thread which is managed by the library. +// - If mal_device_init() is called with a device that's not aligned to the platform's natural alignment +// boundary (4 bytes on 32-bit, 8 bytes on 64-bit), it will _not_ be thread-safe. The reason for this +// is that it depends on members of mal_device being correctly aligned for atomic assignments. +// - Sample data is always little-endian and interleaved. For example, mal_format_s16 means signed 16-bit +// integer samples, interleaved. Let me know if you need non-interleaved and I'll look into it. +// +// +// +// BACKEND NUANCES +// =============== +// - The absolute best latency I am able to get on DirectSound is about 10 milliseconds. This seems very +// consistent so I'm suspecting there's some kind of hard coded limit there or something. +// - DirectSound currently supports a maximum of 4 periods. +// - To capture audio on Android, remember to add the RECORD_AUDIO permission to your manifest: +// +// - UWP is only supported when compiling as C++. +// - UWP only supports default playback and capture devices. +// - UWP requires the Microphone capability to be enabled in the application's manifest (Package.appxmanifest): +// +// ... +// +// +// +// +// +// +// OPTIONS +// ======= +// #define these options before including this file. +// +// #define MAL_NO_WASAPI +// Disables the WASAPI backend. +// +// #define MAL_NO_DSOUND +// Disables the DirectSound backend. +// +// #define MAL_NO_WINMM +// Disables the WinMM backend. +// +// #define MAL_NO_ALSA +// Disables the ALSA backend. +// +// #define MAL_NO_OSS +// Disables the OSS backend. +// +// #define MAL_NO_OPENSL +// Disables the OpenSL|ES backend. +// +// #define MAL_NO_OPENAL +// Disables the OpenAL backend. +// +// #define MAL_NO_NULL +// Disables the null backend. +// +// #define MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS +// When a buffer size of 0 is specified when a device is initialized, it will default to a size with +// this number of milliseconds worth of data. Note that some backends may adjust this setting if that +// particular backend has unusual latency characteristics. +// +// #define MAL_DEFAULT_PERIODS +// When a period count of 0 is specified when a device is initialized, it will default to this. + +#ifndef mini_al_h +#define mini_al_h + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4201) // nonstandard extension used: nameless struct/union +#endif + +// Platform/backend detection. +#ifdef _WIN32 + #define MAL_WIN32 + #if (!defined(WINAPI_FAMILY) || WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) + #define MAL_WIN32_DESKTOP + #endif +#else + #define MAL_POSIX + #include // Unfortunate #include, but needed for pthread_t, pthread_mutex_t and pthread_cond_t types. + + #define MAL_UNIX + #ifdef __linux__ + #define MAL_LINUX + #endif + #ifdef __APPLE__ + #define MAL_APPLE + #endif + #ifdef __ANDROID__ + #define MAL_ANDROID + #endif +#endif + +// Some backends are only supported on certain platforms. +#if defined(MAL_WIN32) + #define MAL_SUPPORT_WASAPI + #if defined(MAL_WIN32_DESKTOP) // DirectSound and WinMM backends are only supported on desktop's. + #define MAL_SUPPORT_DSOUND + #define MAL_SUPPORT_WINMM + #endif + + // Don't support WASAPI on older versions of MSVC for now. + #if defined(_MSC_VER) + #if _MSC_VER < 1600 + #if !defined(__audioclient_h__) + #undef MAL_SUPPORT_WASAPI + #endif + #endif + #endif +#endif +#if defined(MAL_UNIX) + #if defined(MAL_LINUX) + #if !defined(MAL_ANDROID) // ALSA is not supported on Android. + #define MAL_SUPPORT_ALSA + #endif + #endif + #if defined(MAL_APPLE) + #define MAL_SUPPORT_COREAUDIO + #endif + #if defined(MAL_ANDROID) + #define MAL_SUPPORT_OPENSL + #endif + #if !defined(MAL_LINUX) && !defined(MAL_APPLE) && !defined(MAL_ANDROID) + #define MAL_SUPPORT_OSS + #endif +#endif +#define MAL_SUPPORT_OPENAL // All platforms support OpenAL (at least for now). +#define MAL_SUPPORT_NULL // All platforms support the null device. + + +#if !defined(MAL_NO_WASAPI) && defined(MAL_SUPPORT_WASAPI) + #define MAL_ENABLE_WASAPI +#endif +#if !defined(MAL_NO_DSOUND) && defined(MAL_SUPPORT_DSOUND) + #define MAL_ENABLE_DSOUND +#endif +#if !defined(MAL_NO_WINMM) && defined(MAL_SUPPORT_WINMM) + #define MAL_ENABLE_WINMM +#endif +#if !defined(MAL_NO_ALSA) && defined(MAL_SUPPORT_ALSA) + #define MAL_ENABLE_ALSA +#endif +#if !defined(MAL_NO_COREAUDIO) && defined(MAL_SUPPORT_COREAUDIO) + #define MAL_ENABLE_COREAUDIO +#endif +#if !defined(MAL_NO_OSS) && defined(MAL_SUPPORT_OSS) + #define MAL_ENABLE_OSS +#endif +#if !defined(MAL_NO_OPENSL) && defined(MAL_SUPPORT_OPENSL) + #define MAL_ENABLE_OPENSL +#endif +#if !defined(MAL_NO_OPENAL) && defined(MAL_SUPPORT_OPENAL) + #define MAL_ENABLE_OPENAL +#endif +#if !defined(MAL_NO_NULL) && defined(MAL_SUPPORT_NULL) + #define MAL_ENABLE_NULL +#endif + + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char mal_int8; +typedef unsigned char mal_uint8; +typedef signed short mal_int16; +typedef unsigned short mal_uint16; +typedef signed int mal_int32; +typedef unsigned int mal_uint32; +typedef signed __int64 mal_int64; +typedef unsigned __int64 mal_uint64; +#else +#include +typedef int8_t mal_int8; +typedef uint8_t mal_uint8; +typedef int16_t mal_int16; +typedef uint16_t mal_uint16; +typedef int32_t mal_int32; +typedef uint32_t mal_uint32; +typedef int64_t mal_int64; +typedef uint64_t mal_uint64; +#endif +typedef mal_uint8 mal_bool8; +typedef mal_uint32 mal_bool32; +#define MAL_TRUE 1 +#define MAL_FALSE 0 + +typedef void* mal_handle; +typedef void* mal_ptr; +typedef void (* mal_proc)(); + +#ifdef MAL_WIN32 + typedef mal_handle mal_thread; + typedef mal_handle mal_mutex; + typedef mal_handle mal_event; +#else + typedef pthread_t mal_thread; + typedef pthread_mutex_t mal_mutex; + typedef struct + { + pthread_mutex_t mutex; + pthread_cond_t condition; + mal_uint32 value; + } mal_event; +#endif + +#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED) +typedef mal_uint16 wchar_t; +#endif + +// Define NULL for some compilers. +#ifndef NULL +#define NULL 0 +#endif + +#define MAL_MAX_PERIODS_DSOUND 4 +#define MAL_MAX_PERIODS_OPENAL 4 + +typedef mal_uint8 mal_channel; +#define MAL_CHANNEL_NONE 0 +#define MAL_CHANNEL_FRONT_LEFT 1 +#define MAL_CHANNEL_FRONT_RIGHT 2 +#define MAL_CHANNEL_FRONT_CENTER 3 +#define MAL_CHANNEL_LFE 4 +#define MAL_CHANNEL_BACK_LEFT 5 +#define MAL_CHANNEL_BACK_RIGHT 6 +#define MAL_CHANNEL_FRONT_LEFT_CENTER 7 +#define MAL_CHANNEL_FRONT_RIGHT_CENTER 8 +#define MAL_CHANNEL_BACK_CENTER 9 +#define MAL_CHANNEL_SIDE_LEFT 10 +#define MAL_CHANNEL_SIDE_RIGHT 11 +#define MAL_CHANNEL_TOP_CENTER 12 +#define MAL_CHANNEL_TOP_FRONT_LEFT 13 +#define MAL_CHANNEL_TOP_FRONT_CENTER 14 +#define MAL_CHANNEL_TOP_FRONT_RIGHT 15 +#define MAL_CHANNEL_TOP_BACK_LEFT 16 +#define MAL_CHANNEL_TOP_BACK_CENTER 17 +#define MAL_CHANNEL_TOP_BACK_RIGHT 18 +#define MAL_CHANNEL_MONO MAL_CHANNEL_FRONT_CENTER +#define MAL_MAX_CHANNELS 18 + +#define MAL_MAX_SAMPLE_SIZE_IN_BYTES 8 + +typedef int mal_result; +#define MAL_SUCCESS 0 +#define MAL_ERROR -1 // A generic error. +#define MAL_INVALID_ARGS -2 +#define MAL_OUT_OF_MEMORY -3 +#define MAL_FORMAT_NOT_SUPPORTED -4 +#define MAL_NO_BACKEND -5 +#define MAL_NO_DEVICE -6 +#define MAL_API_NOT_FOUND -7 +#define MAL_DEVICE_BUSY -8 +#define MAL_DEVICE_NOT_INITIALIZED -9 +#define MAL_DEVICE_ALREADY_STARTED -10 +#define MAL_DEVICE_ALREADY_STARTING -11 +#define MAL_DEVICE_ALREADY_STOPPED -12 +#define MAL_DEVICE_ALREADY_STOPPING -13 +#define MAL_FAILED_TO_MAP_DEVICE_BUFFER -14 +#define MAL_FAILED_TO_INIT_BACKEND -15 +#define MAL_FAILED_TO_READ_DATA_FROM_CLIENT -16 +#define MAL_FAILED_TO_READ_DATA_FROM_DEVICE -17 +#define MAL_FAILED_TO_SEND_DATA_TO_CLIENT -18 +#define MAL_FAILED_TO_SEND_DATA_TO_DEVICE -19 +#define MAL_FAILED_TO_OPEN_BACKEND_DEVICE -20 +#define MAL_FAILED_TO_START_BACKEND_DEVICE -21 +#define MAL_FAILED_TO_STOP_BACKEND_DEVICE -22 +#define MAL_FAILED_TO_CREATE_MUTEX -23 +#define MAL_FAILED_TO_CREATE_EVENT -24 +#define MAL_FAILED_TO_CREATE_THREAD -25 +#define MAL_INVALID_DEVICE_CONFIG -26 +#define MAL_ACCESS_DENIED -27 +#define MAL_DSOUND_FAILED_TO_CREATE_DEVICE -1024 +#define MAL_DSOUND_FAILED_TO_SET_COOP_LEVEL -1025 +#define MAL_DSOUND_FAILED_TO_CREATE_BUFFER -1026 +#define MAL_DSOUND_FAILED_TO_QUERY_INTERFACE -1027 +#define MAL_DSOUND_FAILED_TO_SET_NOTIFICATIONS -1028 +#define MAL_ALSA_FAILED_TO_OPEN_DEVICE -2048 +#define MAL_ALSA_FAILED_TO_SET_HW_PARAMS -2049 +#define MAL_ALSA_FAILED_TO_SET_SW_PARAMS -2050 +#define MAL_ALSA_FAILED_TO_PREPARE_DEVICE -2051 +#define MAL_ALSA_FAILED_TO_RECOVER_DEVICE -2052 +#define MAL_WASAPI_FAILED_TO_CREATE_DEVICE_ENUMERATOR -3072 +#define MAL_WASAPI_FAILED_TO_CREATE_DEVICE -3073 +#define MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE -3074 +#define MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE -3075 +#define MAL_WASAPI_FAILED_TO_FIND_BEST_FORMAT -3076 +#define MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER -3077 +#define MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER -3078 +#define MAL_WINMM_FAILED_TO_GET_DEVICE_CAPS -4096 +#define MAL_WINMM_FAILED_TO_GET_SUPPORTED_FORMATS -4097 + +typedef struct mal_context mal_context; +typedef struct mal_device mal_device; + +typedef void (* mal_log_proc) (mal_context* pContext, mal_device* pDevice, const char* message); +typedef void (* mal_recv_proc)(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples); +typedef mal_uint32 (* mal_send_proc)(mal_device* pDevice, mal_uint32 frameCount, void* pSamples); +typedef void (* mal_stop_proc)(mal_device* pDevice); + +typedef enum +{ + mal_backend_null, + mal_backend_wasapi, + mal_backend_dsound, + mal_backend_winmm, + mal_backend_alsa, + mal_backend_oss, + mal_backend_opensl, + mal_backend_openal +} mal_backend; + +typedef enum +{ + mal_device_type_playback, + mal_device_type_capture +} mal_device_type; + +typedef enum +{ + // I like to keep these explicitly defined because they're used as a key into a lookup table. When items are + // added to this, make sure there are no gaps and that they're added to the lookup table in mal_get_sample_size_in_bytes(). + mal_format_unknown = 0, // Mainly used for indicating an error. + mal_format_u8 = 1, + mal_format_s16 = 2, // Seems to be the most widely supported format. + mal_format_s24 = 3, // Tightly packed. 3 bytes per sample. + mal_format_s32 = 4, + mal_format_f32 = 5, +} mal_format; + +typedef enum +{ + mal_channel_mix_mode_basic, // Drop excess channels; zeroed out extra channels. + mal_channel_mix_mode_blend, // Blend channels based on locality. +} mal_channel_mix_mode; + +typedef union +{ +#ifdef MAL_SUPPORT_WASAPI + wchar_t wasapi[64]; // WASAPI uses a wchar_t string for identification. +#endif +#ifdef MAL_SUPPORT_DSOUND + mal_uint8 dsound[16]; // DirectSound uses a GUID for identification. +#endif +#ifdef MAL_SUPPORT_WINMM + /*UINT_PTR*/ mal_uint32 winmm; // When creating a device, WinMM expects a Win32 UINT_PTR for device identification. In practice it's actually just a UINT. +#endif +#ifdef MAL_SUPPORT_ALSA + char alsa[256]; // ALSA uses a name string for identification. +#endif +#ifdef MAL_SUPPORT_COREAUDIO + // TODO: Implement me. +#endif +#ifdef MAL_SUPPORT_OSS + char oss[64]; // "dev/dsp0", etc. "dev/dsp" for the default device. +#endif +#ifdef MAL_SUPPORT_OPENSL + mal_uint32 opensl; // OpenSL|ES uses a 32-bit unsigned integer for identification. +#endif +#ifdef MAL_SUPPORT_OPENAL + char openal[256]; // OpenAL seems to use human-readable device names as the ID. +#endif +#ifdef MAL_SUPPORT_NULL + int nullbackend; // Always 0. +#endif +} mal_device_id; + +typedef struct +{ + mal_device_id id; + char name[256]; +} mal_device_info; + +typedef struct +{ + mal_int64 counter; +} mal_timer; + + +typedef struct mal_src mal_src; +typedef mal_uint32 (* mal_src_read_proc)(mal_uint32 frameCount, void* pFramesOut, void* pUserData); // Returns the number of frames that were read. + +typedef enum +{ + mal_src_algorithm_none, + mal_src_algorithm_linear +} mal_src_algorithm; + +#define MAL_SRC_CACHE_SIZE_IN_FRAMES 512 +typedef struct +{ + mal_src* pSRC; + float pCachedFrames[MAL_MAX_CHANNELS * MAL_SRC_CACHE_SIZE_IN_FRAMES]; + mal_uint32 cachedFrameCount; + mal_uint32 iNextFrame; +} mal_src_cache; + +typedef struct +{ + mal_uint32 sampleRateIn; + mal_uint32 sampleRateOut; + mal_format formatIn; + mal_format formatOut; + mal_uint32 channels; + mal_src_algorithm algorithm; + mal_uint32 cacheSizeInFrames; // The number of frames to read from the client at a time. +} mal_src_config; + +struct mal_src +{ + mal_src_config config; + mal_src_read_proc onRead; + void* pUserData; + float ratio; + float bin[256]; + mal_src_cache cache; // <-- For simplifying and optimizing client -> memory reading. + + union + { + struct + { + float alpha; + mal_bool32 isPrevFramesLoaded : 1; + mal_bool32 isNextFramesLoaded : 1; + } linear; + }; +}; + +typedef struct mal_dsp mal_dsp; +typedef mal_uint32 (* mal_dsp_read_proc)(mal_uint32 frameCount, void* pSamplesOut, void* pUserData); + +typedef struct +{ + mal_format formatIn; + mal_uint32 channelsIn; + mal_uint32 sampleRateIn; + mal_channel channelMapIn[MAL_MAX_CHANNELS]; + mal_format formatOut; + mal_uint32 channelsOut; + mal_uint32 sampleRateOut; + mal_channel channelMapOut[MAL_MAX_CHANNELS]; + mal_uint32 cacheSizeInFrames; // Applications should set this to 0 for now. +} mal_dsp_config; + +struct mal_dsp +{ + mal_dsp_config config; + mal_dsp_read_proc onRead; + void* pUserDataForOnRead; + mal_src src; // For sample rate conversion. + mal_channel channelMapInPostMix[MAL_MAX_CHANNELS]; // <-- When mixing, new channels may need to be created. This represents the channel map after mixing. + mal_channel channelShuffleTable[MAL_MAX_CHANNELS]; + mal_bool32 isChannelMappingRequired : 1; + mal_bool32 isSRCRequired : 1; + mal_bool32 isPassthrough : 1; // <-- Will be set to true when the DSP pipeline is an optimized passthrough. +}; + + +typedef struct +{ + mal_format format; + mal_uint32 channels; + mal_uint32 sampleRate; + mal_channel channelMap[MAL_MAX_CHANNELS]; + mal_uint32 bufferSizeInFrames; + mal_uint32 periods; + mal_bool32 preferExclusiveMode; + mal_recv_proc onRecvCallback; + mal_send_proc onSendCallback; + mal_stop_proc onStopCallback; + + struct + { + mal_bool32 noMMap; // Disables MMap mode. + } alsa; +} mal_device_config; + +typedef struct +{ + mal_log_proc onLog; + + struct + { + mal_bool32 useVerboseDeviceEnumeration; + mal_bool32 excludeNullDevice; + } alsa; +} mal_context_config; + +struct mal_context +{ + mal_backend backend; // DirectSound, ALSA, etc. + mal_context_config config; + + union + { +#ifdef MAL_SUPPORT_WASAPI + struct + { + int _unused; + } wasapi; +#endif +#ifdef MAL_SUPPORT_DSOUND + struct + { + /*HMODULE*/ mal_handle hDSoundDLL; + } dsound; +#endif +#ifdef MAL_SUPPORT_WINMM + struct + { + /*HMODULE*/ mal_handle hWinMM; + mal_proc waveOutGetNumDevs; + mal_proc waveOutGetDevCapsA; + mal_proc waveOutOpen; + mal_proc waveOutClose; + mal_proc waveOutPrepareHeader; + mal_proc waveOutUnprepareHeader; + mal_proc waveOutWrite; + mal_proc waveOutReset; + mal_proc waveInGetNumDevs; + mal_proc waveInGetDevCapsA; + mal_proc waveInOpen; + mal_proc waveInClose; + mal_proc waveInPrepareHeader; + mal_proc waveInUnprepareHeader; + mal_proc waveInAddBuffer; + mal_proc waveInStart; + mal_proc waveInReset; + } winmm; +#endif +#ifdef MAL_SUPPORT_ALSA + struct + { + mal_handle asoundSO; + mal_proc snd_pcm_open; + mal_proc snd_pcm_close; + mal_proc snd_pcm_hw_params_sizeof; + mal_proc snd_pcm_hw_params_any; + mal_proc snd_pcm_hw_params_set_format; + mal_proc snd_pcm_hw_params_set_format_first; + mal_proc snd_pcm_hw_params_get_format_mask; + mal_proc snd_pcm_hw_params_set_channels_near; + mal_proc snd_pcm_hw_params_set_rate_resample; + mal_proc snd_pcm_hw_params_set_rate_near; + mal_proc snd_pcm_hw_params_set_buffer_size_near; + mal_proc snd_pcm_hw_params_set_periods_near; + mal_proc snd_pcm_hw_params_set_access; + mal_proc snd_pcm_hw_params_get_format; + mal_proc snd_pcm_hw_params_get_channels; + mal_proc snd_pcm_hw_params_get_rate; + mal_proc snd_pcm_hw_params_get_buffer_size; + mal_proc snd_pcm_hw_params_get_periods; + mal_proc snd_pcm_hw_params_get_access; + mal_proc snd_pcm_hw_params; + mal_proc snd_pcm_sw_params_sizeof; + mal_proc snd_pcm_sw_params_current; + mal_proc snd_pcm_sw_params_set_avail_min; + mal_proc snd_pcm_sw_params_set_start_threshold; + mal_proc snd_pcm_sw_params; + mal_proc snd_pcm_format_mask_sizeof; + mal_proc snd_pcm_format_mask_test; + mal_proc snd_pcm_get_chmap; + mal_proc snd_pcm_prepare; + mal_proc snd_pcm_start; + mal_proc snd_pcm_drop; + mal_proc snd_device_name_hint; + mal_proc snd_device_name_get_hint; + mal_proc snd_card_get_index; + mal_proc snd_device_name_free_hint; + mal_proc snd_pcm_mmap_begin; + mal_proc snd_pcm_mmap_commit; + mal_proc snd_pcm_recover; + mal_proc snd_pcm_readi; + mal_proc snd_pcm_writei; + mal_proc snd_pcm_avail; + mal_proc snd_pcm_avail_update; + mal_proc snd_pcm_wait; + } alsa; +#endif +#ifdef MAL_SUPPORT_COREAUDIO + struct + { + int _unused; + } coreaudio; +#endif +#ifdef MAL_SUPPORT_OSS + struct + { + int versionMajor; + int versionMinor; + } oss; +#endif +#ifdef MAL_SUPPORT_OPENSL + struct + { + int _unused; + } opensl; +#endif +#ifdef MAL_SUPPORT_OPENAL + struct + { + /*HMODULE*/ mal_handle hOpenAL; // OpenAL32.dll, etc. + mal_proc alcCreateContext; + mal_proc alcMakeContextCurrent; + mal_proc alcProcessContext; + mal_proc alcSuspendContext; + mal_proc alcDestroyContext; + mal_proc alcGetCurrentContext; + mal_proc alcGetContextsDevice; + mal_proc alcOpenDevice; + mal_proc alcCloseDevice; + mal_proc alcGetError; + mal_proc alcIsExtensionPresent; + mal_proc alcGetProcAddress; + mal_proc alcGetEnumValue; + mal_proc alcGetString; + mal_proc alcGetIntegerv; + mal_proc alcCaptureOpenDevice; + mal_proc alcCaptureCloseDevice; + mal_proc alcCaptureStart; + mal_proc alcCaptureStop; + mal_proc alcCaptureSamples; + + mal_proc alEnable; + mal_proc alDisable; + mal_proc alIsEnabled; + mal_proc alGetString; + mal_proc alGetBooleanv; + mal_proc alGetIntegerv; + mal_proc alGetFloatv; + mal_proc alGetDoublev; + mal_proc alGetBoolean; + mal_proc alGetInteger; + mal_proc alGetFloat; + mal_proc alGetDouble; + mal_proc alGetError; + mal_proc alIsExtensionPresent; + mal_proc alGetProcAddress; + mal_proc alGetEnumValue; + mal_proc alGenSources; + mal_proc alDeleteSources; + mal_proc alIsSource; + mal_proc alSourcef; + mal_proc alSource3f; + mal_proc alSourcefv; + mal_proc alSourcei; + mal_proc alSource3i; + mal_proc alSourceiv; + mal_proc alGetSourcef; + mal_proc alGetSource3f; + mal_proc alGetSourcefv; + mal_proc alGetSourcei; + mal_proc alGetSource3i; + mal_proc alGetSourceiv; + mal_proc alSourcePlayv; + mal_proc alSourceStopv; + mal_proc alSourceRewindv; + mal_proc alSourcePausev; + mal_proc alSourcePlay; + mal_proc alSourceStop; + mal_proc alSourceRewind; + mal_proc alSourcePause; + mal_proc alSourceQueueBuffers; + mal_proc alSourceUnqueueBuffers; + mal_proc alGenBuffers; + mal_proc alDeleteBuffers; + mal_proc alIsBuffer; + mal_proc alBufferData; + mal_proc alBufferf; + mal_proc alBuffer3f; + mal_proc alBufferfv; + mal_proc alBufferi; + mal_proc alBuffer3i; + mal_proc alBufferiv; + mal_proc alGetBufferf; + mal_proc alGetBuffer3f; + mal_proc alGetBufferfv; + mal_proc alGetBufferi; + mal_proc alGetBuffer3i; + mal_proc alGetBufferiv; + + mal_uint32 isFloat32Supported : 1; + mal_uint32 isMCFormatsSupported : 1; + } openal; +#endif +#ifdef MAL_SUPPORT_NULL + struct + { + int _unused; + } null_device; +#endif + }; + + union + { +#ifdef MAL_WIN32 + struct + { + /*HMODULE*/ mal_handle hOle32DLL; + mal_proc CoInitializeEx; + mal_proc CoUninitialize; + mal_proc CoCreateInstance; + mal_proc CoTaskMemFree; + mal_proc PropVariantClear; + + /*HMODULE*/ mal_handle hUser32DLL; + mal_proc GetForegroundWindow; + mal_proc GetDesktopWindow; + } win32; +#endif +#ifdef MAL_POSIX + struct + { + mal_handle pthreadSO; + mal_proc pthread_create; + mal_proc pthread_join; + mal_proc pthread_mutex_init; + mal_proc pthread_mutex_destroy; + mal_proc pthread_mutex_lock; + mal_proc pthread_mutex_unlock; + mal_proc pthread_cond_init; + mal_proc pthread_cond_destroy; + mal_proc pthread_cond_wait; + mal_proc pthread_cond_signal; + } posix; +#endif + int _unused; + }; +}; + +struct mal_device +{ + mal_context* pContext; + mal_device_type type; + mal_format format; + mal_uint32 channels; + mal_uint32 sampleRate; + mal_uint8 channelMap[MAL_MAX_CHANNELS]; + mal_uint32 bufferSizeInFrames; + mal_uint32 periods; + mal_uint32 state; + mal_recv_proc onRecv; + mal_send_proc onSend; + mal_stop_proc onStop; + void* pUserData; // Application defined data. + mal_mutex lock; + mal_event wakeupEvent; + mal_event startEvent; + mal_event stopEvent; + mal_thread thread; + mal_result workResult; // This is set by the worker thread after it's finished doing a job. + mal_bool32 usingDefaultBufferSize : 1; + mal_bool32 usingDefaultPeriods : 1; + mal_bool32 exclusiveMode : 1; + mal_format internalFormat; + mal_uint32 internalChannels; + mal_uint32 internalSampleRate; + mal_uint8 internalChannelMap[MAL_MAX_CHANNELS]; + mal_dsp dsp; // Samples run through this to convert samples to a format suitable for use by the backend. + mal_uint32 _dspFrameCount; // Internal use only. Used when running the device -> DSP -> client pipeline. See mal_device__on_read_from_device(). + const mal_uint8* _dspFrames; // ^^^ AS ABOVE ^^^ + + union + { +#ifdef MAL_SUPPORT_WASAPI + struct + { + /*IAudioClient**/ mal_ptr pAudioClient; + /*IAudioRenderClient**/ mal_ptr pRenderClient; + /*IAudioCaptureClient**/ mal_ptr pCaptureClient; + /*HANDLE*/ mal_handle hEvent; + /*HANDLE*/ mal_handle hStopEvent; + mal_bool32 breakFromMainLoop; + } wasapi; +#endif +#ifdef MAL_SUPPORT_DSOUND + struct + { + /*HMODULE*/ mal_handle hDSoundDLL; + /*LPDIRECTSOUND*/ mal_ptr pPlayback; + /*LPDIRECTSOUNDBUFFER*/ mal_ptr pPlaybackPrimaryBuffer; + /*LPDIRECTSOUNDBUFFER*/ mal_ptr pPlaybackBuffer; + /*LPDIRECTSOUNDCAPTURE*/ mal_ptr pCapture; + /*LPDIRECTSOUNDCAPTUREBUFFER*/ mal_ptr pCaptureBuffer; + /*LPDIRECTSOUNDNOTIFY*/ mal_ptr pNotify; + /*HANDLE*/ mal_handle pNotifyEvents[MAL_MAX_PERIODS_DSOUND]; // One event handle for each period. + /*HANDLE*/ mal_handle hStopEvent; + mal_uint32 lastProcessedFrame; // This is circular. + mal_bool32 breakFromMainLoop; + } dsound; +#endif +#ifdef MAL_SUPPORT_WINMM + struct + { + /*HWAVEOUT, HWAVEIN*/ mal_handle hDevice; + /*HANDLE*/ mal_handle hEvent; + mal_uint32 fragmentSizeInFrames; + mal_uint32 fragmentSizeInBytes; + mal_uint32 iNextHeader; // [0,periods). Used as an index into pWAVEHDR. + /*WAVEHDR**/ mal_uint8* pWAVEHDR; // One instantiation for each period. + mal_uint8* pIntermediaryBuffer; + mal_uint8* _pHeapData; // Used internally and is used for the heap allocated data for the intermediary buffer and the WAVEHDR structures. + mal_bool32 breakFromMainLoop; + } winmm; +#endif +#ifdef MAL_SUPPORT_ALSA + struct + { + /*snd_pcm_t**/ mal_ptr pPCM; + mal_bool32 isUsingMMap : 1; + mal_bool32 breakFromMainLoop : 1; + void* pIntermediaryBuffer; + } alsa; +#endif +#ifdef MAL_SUPPORT_COREAUDIO + struct + { + int _unused; + } coreaudio; +#endif +#ifdef MAL_SUPPORT_OSS + struct + { + int fd; + mal_uint32 fragmentSizeInFrames; + mal_bool32 breakFromMainLoop; + void* pIntermediaryBuffer; + } oss; +#endif +#ifdef MAL_SUPPORT_OPENSL + struct + { + /*SLObjectItf*/ mal_ptr pOutputMixObj; + /*SLOutputMixItf*/ mal_ptr pOutputMix; + /*SLObjectItf*/ mal_ptr pAudioPlayerObj; + /*SLPlayItf*/ mal_ptr pAudioPlayer; + /*SLObjectItf*/ mal_ptr pAudioRecorderObj; + /*SLRecordItf*/ mal_ptr pAudioRecorder; + /*SLAndroidSimpleBufferQueueItf*/ mal_ptr pBufferQueue; + mal_uint32 periodSizeInFrames; + mal_uint32 currentBufferIndex; + mal_uint8* pBuffer; // This is malloc()'d and is used for storing audio data. Typed as mal_uint8 for easy offsetting. + } opensl; +#endif +#ifdef MAL_SUPPORT_OPENAL + struct + { + /*ALCcontext**/ mal_ptr pContextALC; + /*ALCdevice**/ mal_ptr pDeviceALC; + /*ALuint*/ mal_uint32 sourceAL; + /*ALuint*/ mal_uint32 buffersAL[MAL_MAX_PERIODS_OPENAL]; + /*ALenum*/ mal_uint32 formatAL; + mal_uint32 subBufferSizeInFrames; // This is the size of each of the OpenAL buffers (buffersAL). + mal_uint8* pIntermediaryBuffer; // This is malloc()'d and is used as the destination for reading from the client. Typed as mal_uint8 for easy offsetting. + mal_uint32 iNextBuffer; // The next buffer to unenqueue and then re-enqueue as new data is read. + mal_bool32 breakFromMainLoop; + } openal; +#endif +#ifdef MAL_SUPPORT_NULL + struct + { + mal_timer timer; + mal_uint32 lastProcessedFrame; // This is circular. + mal_bool32 breakFromMainLoop; + mal_uint8* pBuffer; // This is malloc()'d and is used as the destination for reading from the client. Typed as mal_uint8 for easy offsetting. + } null_device; +#endif + }; +}; +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +// Initializes a context. +// +// The context is used for selecting and initializing the relevant backends. +// +// Note that the location of the device cannot change throughout it's lifetime. Consider allocating +// the mal_context object with malloc() if this is an issue. The reason for this is that a pointer +// to the context is stored in the mal_device structure. +// +// is used to allow the application to prioritize backends depending on it's specific +// requirements. This can be null in which case it uses the default priority, which is as follows: +// - WASAPI +// - DirectSound +// - WinMM +// - ALSA +// - OSS +// - OpenSL|ES +// - OpenAL +// - Null +// +// The onLog callback is used for posting log messages back to the client for diagnostics, debugging, +// etc. You can pass NULL for this if you do not need it. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: UNSAFE +// +// Effeciency: LOW +// This will dynamically load backends DLLs/SOs (such as dsound.dll). +mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pConfig, mal_context* pContext); + +// Uninitializes a context. +// +// Results are undefined if you call this while any device created by this context is still active. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: UNSAFE +// +// Efficiency: LOW +// This will unload the backend DLLs/SOs. +mal_result mal_context_uninit(mal_context* pContext); + +// Enumerates over each device of the given type (playback or capture). +// +// It is _not_ safe to assume the first enumerated device is the default device. +// +// Some backends and platforms may only support default playback and capture devices. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: SAFE, SEE NOTES. +// This API uses an application-defined buffer for output. This is thread-safe so long as the +// application ensures mutal exclusion to the output buffer at their level. +// +// Efficiency: LOW +// This API dynamically links to backend DLLs/SOs (such as dsound.dll). +mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo); + +// Initializes a device. +// +// The device ID (pDeviceID) can be null, in which case the default device is used. Otherwise, you +// can retrieve the ID by calling mal_enumerate_devices() and using the ID from the returned data. +// Set pDeviceID to NULL to use the default device. Do _not_ rely on the first device ID returned +// by mal_enumerate_devices() to be the default device. +// +// This will try it's hardest to create a valid device, even if it means adjusting input arguments. +// Look at pDevice->internalChannels, pDevice->internalSampleRate, etc. to determine the actual +// properties after initialization. +// +// If is 0, it will default to MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS. If +// is set to 0 it will default to MAL_DEFAULT_PERIODS. +// +// The property controls how frequently the background thread is woken to check for more +// data. It's tied to the buffer size, so as an example, if your buffer size is equivalent to 10 +// milliseconds and you have 2 periods, the CPU will wake up approximately every 5 milliseconds. +// +// Consider using mal_device_config_init(), mal_device_config_init_playback(), etc. to make it easier +// to initialize a mal_device_config object. +// +// When compiling for UWP you must ensure you call this function on the main UI thread because the +// operating system may need to present the user with a message asking for permissions. Please refer +// to the official documentation for ActivateAudioInterfaceAsync() for more information. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: UNSAFE +// It is not safe to call this function simultaneously for different devices because some backends +// depend on and mutate global state (such as OpenSL|ES). The same applies to calling this as the +// same time as mal_device_uninit(). +// +// Results are undefined if you try using a device before this function has returned. +// +// Efficiency: LOW +// This is just slow due to the nature of it being an initialization API. +mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, void* pUserData, mal_device* pDevice); + +// Uninitializes a device. +// +// This will explicitly stop the device. You do not need to call mal_device_stop() beforehand, but it's +// harmless if you do. +// +// Return Value: +// MAL_SUCCESS if successful; any other error code otherwise. +// +// Thread Safety: UNSAFE +// As soon as this API is called the device should be considered undefined. All bets are off if you +// try using the device at the same time as uninitializing it. +// +// Efficiency: LOW +// This will stop the device with mal_device_stop() which is a slow, synchronized call. It also needs +// to destroy internal objects like the backend-specific objects and the background thread. +void mal_device_uninit(mal_device* pDevice); + +// Sets the callback to use when the application has received data from the device. +// +// Thread Safety: SAFE +// This API is implemented as a simple atomic assignment. +// +// Efficiency: HIGH +// This is just an atomic assignment. +void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc); + +// Sets the callback to use when the application needs to send data to the device for playback. +// +// Note that the implementation of this callback must copy over as many samples as is available. The +// return value specifies how many samples were written to the output buffer. The backend will fill +// any leftover samples with silence. +// +// Thread Safety: SAFE +// This API is implemented as a simple atomic assignment. +// +// Efficiency: HIGH +// This is just an atomic assignment. +void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc); + +// Sets the callback to use when the device has stopped, either explicitly or as a result of an error. +// +// Thread Safety: SAFE +// This API is implemented as a simple atomic assignment. +// +// Efficiency: HIGH +// This is just an atomic assignment. +void mal_device_set_stop_callback(mal_device* pDevice, mal_stop_proc proc); + +// Activates the device. For playback devices this begins playback. For capture devices it begins +// recording. +// +// For a playback device, this will retrieve an initial chunk of audio data from the client before +// returning. The reason for this is to ensure there is valid audio data in the buffer, which needs +// to be done _before_ the device begins playback. +// +// Return Value: +// - MAL_SUCCESS if successful; any other error code otherwise. +// - MAL_INVALID_ARGS +// One or more of the input arguments is invalid. +// - MAL_DEVICE_NOT_INITIALIZED +// The device is not currently or was never initialized. +// - MAL_DEVICE_BUSY +// The device is in the process of stopping. This will only happen if mal_device_start() and +// mal_device_stop() is called simultaneous on separate threads. This will never be returned in +// single-threaded applications. +// - MAL_DEVICE_ALREADY_STARTING +// The device is already in the process of starting. This will never be returned in single-threaded +// applications. +// - MAL_DEVICE_ALREADY_STARTED +// The device is already started. +// - MAL_FAILED_TO_READ_DATA_FROM_CLIENT +// Failed to read the initial chunk of audio data from the client. This initial chunk of data is +// required so that the device has valid audio data as soon as it starts playing. This will never +// be returned for capture devices. +// - MAL_FAILED_TO_START_BACKEND_DEVICE +// There was a backend-specific error starting the device. +// +// Thread Safety: SAFE +// +// Efficiency: LOW +// This API waits until the backend device has been started for real by the worker thread. It also +// waits on a mutex for thread-safety. +mal_result mal_device_start(mal_device* pDevice); + +// Puts the device to sleep, but does not uninitialize it. Use mal_device_start() to start it up again. +// +// Return Value: +// - MAL_SUCCESS if successful; any other error code otherwise. +// - MAL_INVALID_ARGS +// One or more of the input arguments is invalid. +// - MAL_DEVICE_NOT_INITIALIZED +// The device is not currently or was never initialized. +// - MAL_DEVICE_BUSY +// The device is in the process of starting. This will only happen if mal_device_start() and +// mal_device_stop() is called simultaneous on separate threads. This will never be returned in +// single-threaded applications. +// - MAL_DEVICE_ALREADY_STOPPING +// The device is already in the process of stopping. This will never be returned in single-threaded +// applications. +// - MAL_DEVICE_ALREADY_STOPPED +// The device is already stopped. +// - MAL_FAILED_TO_STOP_BACKEND_DEVICE +// There was a backend-specific error stopping the device. +// +// Thread Safety: SAFE +// +// Efficiency: LOW +// This API needs to wait on the worker thread to stop the backend device properly before returning. It +// also waits on a mutex for thread-safety. +// +// In addition, some backends need to wait for the device to finish playback/recording of the current +// fragment which can take some time (usually proportionate to the buffer size used when initializing +// the device). +mal_result mal_device_stop(mal_device* pDevice); + +// Determines whether or not the device is started. +// +// Return Value: +// True if the device is started, false otherwise. +// +// Thread Safety: SAFE +// If another thread calls mal_device_start() or mal_device_stop() at this same time as this function +// is called, there's a very small chance the return value will be out of sync. +// +// Efficiency: HIGH +// This is implemented with a simple accessor. +mal_bool32 mal_device_is_started(mal_device* pDevice); + +// Retrieves the size of the buffer in bytes for the given device. +// +// Thread Safety: SAFE +// This is calculated from constant values which are set at initialization time and never change. +// +// Efficiency: HIGH +// This is implemented with just a few 32-bit integer multiplications. +mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice); + +// Retrieves the size of a sample in bytes for the given format. +// +// Thread Safety: SAFE +// This is API is pure. +// +// Efficiency: HIGH +// This is implemented with a lookup table. +mal_uint32 mal_get_sample_size_in_bytes(mal_format format); + +// Helper function for initializing a mal_context_config object. +mal_context_config mal_context_config_init(mal_log_proc onLog); + +// Helper function for initializing a mal_device_config object. +// +// This is just a helper API, and as such the returned object can be safely modified as needed. +// +// The default channel mapping is based on the channel count, as per the table below. Note that these +// can be freely changed after this function returns if you are needing something in particular. +// +// |---------------|------------------------------| +// | Channel Count | Mapping | +// |---------------|------------------------------| +// | 1 (Mono) | 0: MAL_CHANNEL_FRONT_CENTER | +// |---------------|------------------------------| +// | 2 (Stereo) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// |---------------|------------------------------| +// | 3 (2.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_LFE | +// |---------------|------------------------------| +// | 4 (Quad) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_BACK_LEFT | +// | | 3: MAL_CHANNEL_BACK_RIGHT | +// |---------------|------------------------------| +// | 5 (4.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_BACK_LEFT | +// | | 3: MAL_CHANNEL_BACK_RIGHT | +// | | 4: MAL_CHANNEL_LFE | +// |---------------|------------------------------| +// | 6 (5.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_LFE | +// | | 4: MAL_CHANNEL_BACK_LEFT | +// | | 5: MAL_CHANNEL_BACK_RIGHT | +// |---------------|------------------------------| +// | 8 (7.1) | 0: MAL_CHANNEL_FRONT_LEFT | +// | | 1: MAL_CHANNEL_FRONT_RIGHT | +// | | 2: MAL_CHANNEL_FRONT_CENTER | +// | | 3: MAL_CHANNEL_LFE | +// | | 4: MAL_CHANNEL_BACK_LEFT | +// | | 5: MAL_CHANNEL_BACK_RIGHT | +// | | 6: MAL_CHANNEL_SIDE_LEFT | +// | | 7: MAL_CHANNEL_SIDE_RIGHT | +// |---------------|------------------------------| +// | Other | All channels set to 0. This | +// | | is equivalent to the same | +// | | mapping as the device. | +// |---------------|------------------------------| +// +// Thread Safety: SAFE +// +// Efficiency: HIGH +// This just returns a stack allocated object and consists of just a few assignments. +mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback); + +// A simplified version of mal_device_config_init() for capture devices. +static inline mal_device_config mal_device_config_init_capture(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback) { return mal_device_config_init(format, channels, sampleRate, onRecvCallback, NULL); } + +// A simplified version of mal_device_config_init() for playback devices. +static inline mal_device_config mal_device_config_init_playback(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_send_proc onSendCallback) { return mal_device_config_init(format, channels, sampleRate, NULL, onSendCallback); } + + + + +/////////////////////////////////////////////////////////////////////////////// +// +// SRC +// +/////////////////////////////////////////////////////////////////////////////// + +// Initializes a sample rate conversion object. +mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC); + +// Reads a number of frames. +// +// Returns the number of frames actually read. +mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// DSP +// +/////////////////////////////////////////////////////////////////////////////// + +// Initializes a DSP object. +mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP); + +// Reads a number of frames and runs them through the DSP processor. +mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut); + +// High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to +// determine the required size of the output buffer. +// +// A return value of 0 indicates an error. +// +// This function is useful for one-off bulk conversions, but if you're streaming data you should use the DSP APIs instead. +mal_uint32 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint32 frameCountIn); + + +/////////////////////////////////////////////////////////////////////////////// +// +// Utiltities +// +/////////////////////////////////////////////////////////////////////////////// + +mal_bool32 mal_mutex_create(mal_context* pContext, mal_mutex* pMutex); +void mal_mutex_delete(mal_context* pContext, mal_mutex* pMutex); +void mal_mutex_lock(mal_context* pContext, mal_mutex* pMutex); +void mal_mutex_unlock(mal_context* pContext, mal_mutex* pMutex); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Miscellaneous Helpers +// +/////////////////////////////////////////////////////////////////////////////// + +// Blends two frames in floating point format. +void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint32 channels); + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Format Conversion +// +/////////////////////////////////////////////////////////////////////////////// +void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count); +void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count); +void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count); +void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); +void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount); + +#ifdef __cplusplus +} +#endif +#endif //mini_al_h + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_IMPLEMENTATION +#include + +#ifdef MAL_WIN32 +#include +#else +#include // For malloc()/free() +#include // For memset() +#endif + +#ifdef MAL_POSIX +#include +#include +#endif + +#if !defined(MAL_64BIT) && !defined(MAL_32BIT) +#ifdef _WIN32 +#ifdef _WIN64 +#define MAL_64BIT +#else +#define MAL_32BIT +#endif +#endif +#endif + +#if !defined(MAL_64BIT) && !defined(MAL_32BIT) +#ifdef __GNUC__ +#ifdef __LP64__ +#define MAL_64BIT +#else +#define MAL_32BIT +#endif +#endif +#endif + +#if !defined(MAL_64BIT) && !defined(MAL_32BIT) +#include +#if INTPTR_MAX == INT64_MAX +#define MAL_64BIT +#else +#define MAL_32BIT +#endif +#endif + + +#ifdef MAL_WIN32 + #define MAL_THREADCALL WINAPI + typedef unsigned long mal_thread_result; +#else + #define MAL_THREADCALL + typedef void* mal_thread_result; +#endif +typedef mal_thread_result (MAL_THREADCALL * mal_thread_entry_proc)(void* pData); + +#ifdef MAL_WIN32 +typedef HRESULT (WINAPI * MAL_PFN_CoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); +typedef void (WINAPI * MAL_PFN_CoUninitialize)(); +typedef HRESULT (WINAPI * MAL_PFN_CoCreateInstance)(REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv); +typedef void (WINAPI * MAL_PFN_CoTaskMemFree)(LPVOID pv); +typedef HRESULT (WINAPI * MAL_PFN_PropVariantClear)(PROPVARIANT *pvar); + +typedef HWND (WINAPI * MAL_PFN_GetForegroundWindow)(); +typedef HWND (WINAPI * MAL_PFN_GetDesktopWindow)(); +#endif + + +#define MAL_STATE_UNINITIALIZED 0 +#define MAL_STATE_STOPPED 1 // The device's default state after initialization. +#define MAL_STATE_STARTED 2 // The worker thread is in it's main loop waiting for the driver to request or deliver audio data. +#define MAL_STATE_STARTING 3 // Transitioning from a stopped state to started. +#define MAL_STATE_STOPPING 4 // Transitioning from a started state to stopped. + + +// The default size of the device's buffer in milliseconds. +// +// If this is too small you may get underruns and overruns in which case you'll need to either increase +// this value or use an explicit buffer size. +#ifndef MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS +#define MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS 25 +#endif + +// Default periods when none is specified in mal_device_init(). More periods means more work on the CPU. +#ifndef MAL_DEFAULT_PERIODS +#define MAL_DEFAULT_PERIODS 2 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// Standard Library Stuff +// +/////////////////////////////////////////////////////////////////////////////// +#ifndef mal_zero_memory +#ifdef MAL_WIN32 +#define mal_zero_memory(p, sz) ZeroMemory((p), (sz)) +#else +#define mal_zero_memory(p, sz) memset((p), 0, (sz)) +#endif +#endif + +#define mal_zero_object(p) mal_zero_memory((p), sizeof(*(p))) + +#ifndef mal_copy_memory +#ifdef MAL_WIN32 +#define mal_copy_memory(dst, src, sz) CopyMemory((dst), (src), (sz)) +#else +#define mal_copy_memory(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#endif + +#ifndef mal_malloc +#ifdef MAL_WIN32 +#define mal_malloc(sz) HeapAlloc(GetProcessHeap(), 0, (sz)) +#else +#define mal_malloc(sz) malloc((sz)) +#endif +#endif + +#ifndef mal_realloc +#ifdef MAL_WIN32 +#define mal_realloc(p, sz) (((sz) > 0) ? ((p) ? HeapReAlloc(GetProcessHeap(), 0, (p), (sz)) : HeapAlloc(GetProcessHeap(), 0, (sz))) : ((VOID*)(SIZE_T)(HeapFree(GetProcessHeap(), 0, (p)) & 0))) +#else +#define mal_realloc(p, sz) realloc((p), (sz)) +#endif +#endif + +#ifndef mal_free +#ifdef MAL_WIN32 +#define mal_free(p) HeapFree(GetProcessHeap(), 0, (p)) +#else +#define mal_free(p) free((p)) +#endif +#endif + +#ifndef mal_assert +#ifdef MAL_WIN32 +#define mal_assert(condition) assert(condition) +#else +#define mal_assert(condition) assert(condition) +#endif +#endif + +#define mal_countof(x) (sizeof(x) / sizeof(x[0])) +#define mal_max(x, y) (((x) > (y)) ? (x) : (y)) +#define mal_min(x, y) (((x) < (y)) ? (x) : (y)) + +#define mal_buffer_frame_capacity(buffer, channels, format) (sizeof(buffer) / mal_get_sample_size_in_bytes(format) / (channels)) + +// Some of these string utility functions are unused on some platforms. +#if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-function" +#endif +// Return Values: +// 0: Success +// 22: EINVAL +// 34: ERANGE +// +// Not using symbolic constants for errors because I want to avoid #including errno.h +static int mal_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + size_t i; + for (i = 0; i < dstSizeInBytes && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstSizeInBytes) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + +static int mal_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) +{ + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + size_t maxcount = count; + if (count == ((size_t)-1) || count >= dstSizeInBytes) { // -1 = _TRUNCATE + maxcount = dstSizeInBytes - 1; + } + + size_t i; + for (i = 0; i < maxcount && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (src[i] == '\0' || i == count || count == ((size_t)-1)) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + +static int mal_strcat_s(char* dst, size_t dstSizeInBytes, const char* src) +{ + if (dst == 0) { + return 22; + } + if (dstSizeInBytes == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + char* dstorig = dst; + + while (dstSizeInBytes > 0 && dst[0] != '\0') { + dst += 1; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + return 22; // Unterminated. + } + + + while (dstSizeInBytes > 0 && src[0] != '\0') { + *dst++ = *src++; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes > 0) { + dst[0] = '\0'; + } else { + dstorig[0] = '\0'; + return 34; + } + + return 0; +} + +static int mal_itoa_s(int value, char* dst, size_t dstSizeInBytes, int radix) +{ + if (dst == NULL || dstSizeInBytes == 0) { + return 22; + } + if (radix < 2 || radix > 36) { + dst[0] = '\0'; + return 22; + } + + int sign = (value < 0 && radix == 10) ? -1 : 1; // The negative sign is only used when the base is 10. + + unsigned int valueU; + if (value < 0) { + valueU = -value; + } else { + valueU = value; + } + + char* dstEnd = dst; + do + { + int remainder = valueU % radix; + if (remainder > 9) { + *dstEnd = (char)((remainder - 10) + 'a'); + } else { + *dstEnd = (char)(remainder + '0'); + } + + dstEnd += 1; + dstSizeInBytes -= 1; + valueU /= radix; + } while (dstSizeInBytes > 0 && valueU > 0); + + if (dstSizeInBytes == 0) { + dst[0] = '\0'; + return 22; // Ran out of room in the output buffer. + } + + if (sign < 0) { + *dstEnd++ = '-'; + dstSizeInBytes -= 1; + } + + if (dstSizeInBytes == 0) { + dst[0] = '\0'; + return 22; // Ran out of room in the output buffer. + } + + *dstEnd = '\0'; + + + // At this point the string will be reversed. + dstEnd -= 1; + while (dst < dstEnd) { + char temp = *dst; + *dst = *dstEnd; + *dstEnd = temp; + + dst += 1; + dstEnd -= 1; + } + + return 0; +} + +static int mal_strcmp(const char* str1, const char* str2) +{ + if (str1 == str2) return 0; + + // These checks differ from the standard implementation. It's not important, but I prefer + // it just for sanity. + if (str1 == NULL) return -1; + if (str2 == NULL) return 1; + + for (;;) { + if (str1[0] == '\0') { + break; + } + if (str1[0] != str2[0]) { + break; + } + + str1 += 1; + str2 += 1; + } + + return ((unsigned char*)str1)[0] - ((unsigned char*)str2)[0]; +} +#if defined(__GNUC__) + #pragma GCC diagnostic pop +#endif + + +// Thanks to good old Bit Twiddling Hacks for this one: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 +static inline unsigned int mal_next_power_of_2(unsigned int x) +{ + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x++; + + return x; +} + +static inline unsigned int mal_prev_power_of_2(unsigned int x) +{ + return mal_next_power_of_2(x) >> 1; +} + +static inline unsigned int mal_round_to_power_of_2(unsigned int x) +{ + unsigned int prev = mal_prev_power_of_2(x); + unsigned int next = mal_next_power_of_2(x); + if ((next - x) > (x - prev)) { + return prev; + } else { + return next; + } +} + + + +// Clamps an f32 sample to -1..1 +static inline float mal_clip_f32(float x) +{ + if (x < -1) return -1; + if (x > +1) return +1; + return x; +} + +static inline float mal_mix_f32(float x, float y, float a) +{ + return x*(1-a) + y*a; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Atomics +// +/////////////////////////////////////////////////////////////////////////////// +#if defined(_WIN32) && defined(_MSC_VER) +#define mal_memory_barrier() MemoryBarrier() +#define mal_atomic_exchange_32(a, b) InterlockedExchange((LONG*)a, (LONG)b) +#define mal_atomic_exchange_64(a, b) InterlockedExchange64((LONGLONG*)a, (LONGLONG)b) +#define mal_atomic_increment_32(a) InterlockedIncrement((LONG*)a) +#define mal_atomic_decrement_32(a) InterlockedDecrement((LONG*)a) +#else +#define mal_memory_barrier() __sync_synchronize() +#define mal_atomic_exchange_32(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() +#define mal_atomic_exchange_64(a, b) (void)__sync_lock_test_and_set(a, b); __sync_synchronize() +#define mal_atomic_increment_32(a) __sync_add_and_fetch(a, 1) +#define mal_atomic_decrement_32(a) __sync_sub_and_fetch(a, 1) +#endif + +#ifdef MAL_64BIT +#define mal_atomic_exchange_ptr mal_atomic_exchange_64 +#endif +#ifdef MAL_32BIT +#define mal_atomic_exchange_ptr mal_atomic_exchange_32 +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// Timing +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_WIN32 +static LARGE_INTEGER g_mal_TimerFrequency = {{0}}; +void mal_timer_init(mal_timer* pTimer) +{ + if (g_mal_TimerFrequency.QuadPart == 0) { + QueryPerformanceFrequency(&g_mal_TimerFrequency); + } + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + pTimer->counter = (mal_uint64)counter.QuadPart; +} + +double mal_timer_get_time_in_seconds(mal_timer* pTimer) +{ + LARGE_INTEGER counter; + if (!QueryPerformanceCounter(&counter)) { + return 0; + } + + return (counter.QuadPart - pTimer->counter) / (double)g_mal_TimerFrequency.QuadPart; +} +#else +void mal_timer_init(mal_timer* pTimer) +{ + struct timespec newTime; + clock_gettime(CLOCK_MONOTONIC, &newTime); + + pTimer->counter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; +} + +double mal_timer_get_time_in_seconds(mal_timer* pTimer) +{ + struct timespec newTime; + clock_gettime(CLOCK_MONOTONIC, &newTime); + + uint64_t newTimeCounter = (newTime.tv_sec * 1000000000) + newTime.tv_nsec; + uint64_t oldTimeCounter = pTimer->counter; + + return (newTimeCounter - oldTimeCounter) / 1000000000.0; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// Dynamic Linking +// +/////////////////////////////////////////////////////////////////////////////// +mal_handle mal_dlopen(const char* filename) +{ +#ifdef _WIN32 +#ifdef MAL_WIN32_DESKTOP + return (mal_handle)LoadLibraryA(filename); +#else + // *sigh* It appears there is no ANSI version of LoadPackagedLibrary()... + WCHAR filenameW[4096]; + if (MultiByteToWideChar(CP_UTF8, 0, filename, -1, filenameW, sizeof(filenameW)) == 0) { + return NULL; + } + + return (mal_handle)LoadPackagedLibrary(filenameW, 0); +#endif +#else + return (mal_handle)dlopen(filename, RTLD_NOW); +#endif +} + +void mal_dlclose(mal_handle handle) +{ +#ifdef _WIN32 + FreeLibrary((HMODULE)handle); +#else + dlclose((void*)handle); +#endif +} + +mal_proc mal_dlsym(mal_handle handle, const char* symbol) +{ +#ifdef _WIN32 + return (mal_proc)GetProcAddress((HMODULE)handle, symbol); +#else + return (mal_proc)dlsym((void*)handle, symbol); +#endif +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Threading +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_WIN32 +mal_bool32 mal_thread_create__win32(mal_context* pContext, mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData) +{ + (void)pContext; + + *pThread = CreateThread(NULL, 0, entryProc, pData, 0, NULL); + if (*pThread == NULL) { + return MAL_FALSE; + } + + return MAL_TRUE; +} + +void mal_thread_wait__win32(mal_context* pContext, mal_thread* pThread) +{ + (void)pContext; + + WaitForSingleObject(*pThread, INFINITE); +} + +void mal_sleep__win32(mal_uint32 milliseconds) +{ + Sleep((DWORD)milliseconds); +} + + +mal_bool32 mal_mutex_create__win32(mal_context* pContext, mal_mutex* pMutex) +{ + (void)pContext; + + *pMutex = CreateEventA(NULL, FALSE, TRUE, NULL); + if (*pMutex == NULL) { + return MAL_FALSE; + } + + return MAL_TRUE; +} + +void mal_mutex_delete__win32(mal_context* pContext, mal_mutex* pMutex) +{ + (void)pContext; + + CloseHandle(*pMutex); +} + +void mal_mutex_lock__win32(mal_context* pContext, mal_mutex* pMutex) +{ + (void)pContext; + + WaitForSingleObject(*pMutex, INFINITE); +} + +void mal_mutex_unlock__win32(mal_context* pContext, mal_mutex* pMutex) +{ + (void)pContext; + + SetEvent(*pMutex); +} + + +mal_bool32 mal_event_create__win32(mal_context* pContext, mal_event* pEvent) +{ + (void)pContext; + + *pEvent = CreateEventW(NULL, FALSE, FALSE, NULL); + if (*pEvent == NULL) { + return MAL_FALSE; + } + + return MAL_TRUE; +} + +void mal_event_delete__win32(mal_context* pContext, mal_event* pEvent) +{ + (void)pContext; + + CloseHandle(*pEvent); +} + +mal_bool32 mal_event_wait__win32(mal_context* pContext, mal_event* pEvent) +{ + (void)pContext; + + return WaitForSingleObject(*pEvent, INFINITE) == WAIT_OBJECT_0; +} + +mal_bool32 mal_event_signal__win32(mal_context* pContext, mal_event* pEvent) +{ + (void)pContext; + + return SetEvent(*pEvent); +} +#endif + + +#ifdef MAL_POSIX +typedef int (* mal_pthread_create_proc)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); +typedef int (* mal_pthread_join_proc)(pthread_t thread, void **retval); +typedef int (* mal_pthread_mutex_init_proc)(pthread_mutex_t *__mutex, const pthread_mutexattr_t *__mutexattr); +typedef int (* mal_pthread_mutex_destroy_proc)(pthread_mutex_t *__mutex); +typedef int (* mal_pthread_mutex_lock_proc)(pthread_mutex_t *__mutex); +typedef int (* mal_pthread_mutex_unlock_proc)(pthread_mutex_t *__mutex); +typedef int (* mal_pthread_cond_init_proc)(pthread_cond_t *__restrict __cond, const pthread_condattr_t *__restrict __cond_attr); +typedef int (* mal_pthread_cond_destroy_proc)(pthread_cond_t *__cond); +typedef int (* mal_pthread_cond_signal_proc)(pthread_cond_t *__cond); +typedef int (* mal_pthread_cond_wait_proc)(pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex); + +mal_bool32 mal_thread_create__posix(mal_context* pContext, mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData) +{ + return ((mal_pthread_create_proc)pContext->posix.pthread_create)(pThread, NULL, entryProc, pData) == 0; +} + +void mal_thread_wait__posix(mal_context* pContext, mal_thread* pThread) +{ + ((mal_pthread_join_proc)pContext->posix.pthread_join)(*pThread, NULL); +} + +void mal_sleep__posix(mal_uint32 milliseconds) +{ + usleep(milliseconds * 1000); // <-- usleep is in microseconds. +} + + +mal_bool32 mal_mutex_create__posix(mal_context* pContext, mal_mutex* pMutex) +{ + return ((mal_pthread_mutex_init_proc)pContext->posix.pthread_mutex_init)(pMutex, NULL) == 0; +} + +void mal_mutex_delete__posix(mal_context* pContext, mal_mutex* pMutex) +{ + ((mal_pthread_mutex_destroy_proc)pContext->posix.pthread_mutex_destroy)(pMutex); +} + +void mal_mutex_lock__posix(mal_context* pContext, mal_mutex* pMutex) +{ + ((mal_pthread_mutex_lock_proc)pContext->posix.pthread_mutex_lock)(pMutex); +} + +void mal_mutex_unlock__posix(mal_context* pContext, mal_mutex* pMutex) +{ + ((mal_pthread_mutex_unlock_proc)pContext->posix.pthread_mutex_unlock)(pMutex); +} + + +mal_bool32 mal_event_create__posix(mal_context* pContext, mal_event* pEvent) +{ + if (((mal_pthread_mutex_init_proc)pContext->posix.pthread_mutex_init)(&pEvent->mutex, NULL) != 0) { + return MAL_FALSE; + } + + if (((mal_pthread_cond_init_proc)pContext->posix.pthread_cond_init)(&pEvent->condition, NULL) != 0) { + return MAL_FALSE; + } + + pEvent->value = 0; + return MAL_TRUE; +} + +void mal_event_delete__posix(mal_context* pContext, mal_event* pEvent) +{ + ((mal_pthread_cond_destroy_proc)pContext->posix.pthread_cond_destroy)(&pEvent->condition); + ((mal_pthread_mutex_destroy_proc)pContext->posix.pthread_mutex_destroy)(&pEvent->mutex); +} + +mal_bool32 mal_event_wait__posix(mal_context* pContext, mal_event* pEvent) +{ + ((mal_pthread_mutex_lock_proc)pContext->posix.pthread_mutex_lock)(&pEvent->mutex); + { + while (pEvent->value == 0) { + ((mal_pthread_cond_wait_proc)pContext->posix.pthread_cond_wait)(&pEvent->condition, &pEvent->mutex); + } + + pEvent->value = 0; // Auto-reset. + } + ((mal_pthread_mutex_unlock_proc)pContext->posix.pthread_mutex_unlock)(&pEvent->mutex); + + return MAL_TRUE; +} + +mal_bool32 mal_event_signal__posix(mal_context* pContext, mal_event* pEvent) +{ + ((mal_pthread_mutex_lock_proc)pContext->posix.pthread_mutex_lock)(&pEvent->mutex); + { + pEvent->value = 1; + ((mal_pthread_cond_signal_proc)pContext->posix.pthread_cond_signal)(&pEvent->condition); + } + ((mal_pthread_mutex_unlock_proc)pContext->posix.pthread_mutex_unlock)(&pEvent->mutex); + + return MAL_TRUE; +} +#endif + +mal_bool32 mal_thread_create(mal_context* pContext, mal_thread* pThread, mal_thread_entry_proc entryProc, void* pData) +{ + if (pThread == NULL || entryProc == NULL) return MAL_FALSE; + +#ifdef MAL_WIN32 + return mal_thread_create__win32(pContext, pThread, entryProc, pData); +#endif +#ifdef MAL_POSIX + return mal_thread_create__posix(pContext, pThread, entryProc, pData); +#endif +} + +void mal_thread_wait(mal_context* pContext, mal_thread* pThread) +{ + if (pThread == NULL) return; + +#ifdef MAL_WIN32 + mal_thread_wait__win32(pContext, pThread); +#endif +#ifdef MAL_POSIX + mal_thread_wait__posix(pContext, pThread); +#endif +} + +void mal_sleep(mal_uint32 milliseconds) +{ +#ifdef MAL_WIN32 + mal_sleep__win32(milliseconds); +#endif +#ifdef MAL_POSIX + mal_sleep__posix(milliseconds); +#endif +} + + +mal_bool32 mal_mutex_create(mal_context* pContext, mal_mutex* pMutex) +{ + if (pMutex == NULL) return MAL_FALSE; + +#ifdef MAL_WIN32 + return mal_mutex_create__win32(pContext, pMutex); +#endif +#ifdef MAL_POSIX + return mal_mutex_create__posix(pContext, pMutex); +#endif +} + +void mal_mutex_delete(mal_context* pContext, mal_mutex* pMutex) +{ + if (pMutex == NULL) return; + +#ifdef MAL_WIN32 + mal_mutex_delete__win32(pContext, pMutex); +#endif +#ifdef MAL_POSIX + mal_mutex_delete__posix(pContext, pMutex); +#endif +} + +void mal_mutex_lock(mal_context* pContext, mal_mutex* pMutex) +{ + if (pMutex == NULL) return; + +#ifdef MAL_WIN32 + mal_mutex_lock__win32(pContext, pMutex); +#endif +#ifdef MAL_POSIX + mal_mutex_lock__posix(pContext, pMutex); +#endif +} + +void mal_mutex_unlock(mal_context* pContext, mal_mutex* pMutex) +{ + if (pMutex == NULL) return; + +#ifdef MAL_WIN32 + mal_mutex_unlock__win32(pContext, pMutex); +#endif +#ifdef MAL_POSIX + mal_mutex_unlock__posix(pContext, pMutex); +#endif +} + + +mal_bool32 mal_event_create(mal_context* pContext, mal_event* pEvent) +{ + if (pEvent == NULL) return MAL_FALSE; + +#ifdef MAL_WIN32 + return mal_event_create__win32(pContext, pEvent); +#endif +#ifdef MAL_POSIX + return mal_event_create__posix(pContext, pEvent); +#endif +} + +void mal_event_delete(mal_context* pContext, mal_event* pEvent) +{ + if (pEvent == NULL) return; + +#ifdef MAL_WIN32 + mal_event_delete__win32(pContext, pEvent); +#endif +#ifdef MAL_POSIX + mal_event_delete__posix(pContext, pEvent); +#endif +} + +mal_bool32 mal_event_wait(mal_context* pContext, mal_event* pEvent) +{ + if (pEvent == NULL) return MAL_FALSE; + +#ifdef MAL_WIN32 + return mal_event_wait__win32(pContext, pEvent); +#endif +#ifdef MAL_POSIX + return mal_event_wait__posix(pContext, pEvent); +#endif +} + +mal_bool32 mal_event_signal(mal_context* pContext, mal_event* pEvent) +{ + if (pEvent == NULL) return MAL_FALSE; + +#ifdef MAL_WIN32 + return mal_event_signal__win32(pContext, pEvent); +#endif +#ifdef MAL_POSIX + return mal_event_signal__posix(pContext, pEvent); +#endif +} + + +// Posts a log message. +static void mal_log(mal_context* pContext, mal_device* pDevice, const char* message) +{ + if (pContext == NULL) return; + + mal_log_proc onLog = pContext->config.onLog; + if (onLog) { + onLog(pContext, pDevice, message); + } +} + +// Posts an error. Throw a breakpoint in here if you're needing to debug. The return value is always "resultCode". +static mal_result mal_context_post_error(mal_context* pContext, mal_device* pDevice, const char* message, mal_result resultCode) +{ + // Derive the context from the device if necessary. + if (pContext == NULL) { + if (pDevice != NULL) { + pContext = pDevice->pContext; + } + } + + mal_log(pContext, pDevice, message); + return resultCode; +} + +static mal_result mal_post_error(mal_device* pDevice, const char* message, mal_result resultCode) +{ + return mal_context_post_error(NULL, pDevice, message, resultCode); +} + + +#if !defined(MAL_ANDROID) +static void mal_get_default_channel_mapping(mal_backend backend, mal_uint32 channels, mal_channel channelMap[MAL_MAX_CHANNELS]) +{ + if (channels == 1) { // Mono + channelMap[0] = MAL_CHANNEL_FRONT_CENTER; + } else if (channels == 2) { // Stereo + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + } else if (channels == 3) { // 2.1 + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_LFE; + } else if (channels == 4) { // 4.0 + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_SIDE_LEFT; + channelMap[3] = MAL_CHANNEL_SIDE_RIGHT; + } else if (channels == 5) { // Not sure about this one. 4.1? + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_SIDE_LEFT; + channelMap[3] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[4] = MAL_CHANNEL_LFE; + } else if (channels >= 6) { // 5.1 + // Some backends use different default layouts. + if (backend == mal_backend_wasapi || backend == mal_backend_dsound || backend == mal_backend_winmm || backend == mal_backend_oss) { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + channelMap[3] = MAL_CHANNEL_LFE; + channelMap[4] = MAL_CHANNEL_SIDE_LEFT; + channelMap[5] = MAL_CHANNEL_SIDE_RIGHT; + } else { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + channelMap[2] = MAL_CHANNEL_SIDE_LEFT; + channelMap[3] = MAL_CHANNEL_SIDE_RIGHT; + channelMap[4] = MAL_CHANNEL_FRONT_CENTER; + channelMap[5] = MAL_CHANNEL_LFE; + } + + if (channels == 7) { // Not sure about this one. + channelMap[6] = MAL_CHANNEL_BACK_CENTER; + } else { + // I don't know what mapping to use in this case, but I'm making it upwards compatible with 7.1. Good luck! + mal_assert(channels >= 8); + channelMap[6] = MAL_CHANNEL_BACK_LEFT; + channelMap[7] = MAL_CHANNEL_BACK_RIGHT; + + // Beyond 7.1 I'm just guessing... + if (channels == 9) { + channelMap[8] = MAL_CHANNEL_BACK_CENTER; + } else if (channels == 10) { + channelMap[8] = MAL_CHANNEL_FRONT_LEFT_CENTER; + channelMap[9] = MAL_CHANNEL_FRONT_RIGHT_CENTER; + } else if (channels == 11) { + channelMap[ 8] = MAL_CHANNEL_FRONT_LEFT_CENTER; + channelMap[ 9] = MAL_CHANNEL_FRONT_RIGHT_CENTER; + channelMap[10] = MAL_CHANNEL_BACK_CENTER; + } else { + mal_assert(channels >= 12); + for (mal_uint8 iChannel = 11; iChannel < channels && iChannel < MAL_MAX_CHANNELS; ++iChannel) { + channelMap[iChannel] = iChannel + 1; + } + } + } + } +} +#endif + + +// The callback for reading from the client -> DSP -> device. +static inline mal_uint32 mal_device__on_read_from_client(mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + mal_send_proc onSend = pDevice->onSend; + if (onSend) { + return onSend(pDevice, frameCount, pFramesOut); + } + + return 0; +} + +// The callback for reading from the device -> DSP -> client. +static inline mal_uint32 mal_device__on_read_from_device(mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + if (pDevice->_dspFrameCount == 0) { + return 0; // Nothing left. + } + + mal_uint32 framesToRead = frameCount; + if (framesToRead > pDevice->_dspFrameCount) { + framesToRead = pDevice->_dspFrameCount; + } + + mal_uint32 bytesToRead = framesToRead * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_copy_memory(pFramesOut, pDevice->_dspFrames, bytesToRead); + pDevice->_dspFrameCount -= framesToRead; + pDevice->_dspFrames += bytesToRead; + + return framesToRead; +} + +// A helper function for reading sample data from the client. Returns the number of samples read from the client. Remaining samples +// are filled with silence. +static inline mal_uint32 mal_device__read_frames_from_client(mal_device* pDevice, mal_uint32 frameCount, void* pSamples) +{ + mal_assert(pDevice != NULL); + mal_assert(frameCount > 0); + mal_assert(pSamples != NULL); + + mal_uint32 framesRead = mal_dsp_read_frames(&pDevice->dsp, frameCount, pSamples); + mal_uint32 samplesRead = framesRead * pDevice->internalChannels; + mal_uint32 sampleSize = mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_uint32 consumedBytes = samplesRead*sampleSize; + mal_uint32 remainingBytes = ((frameCount * pDevice->internalChannels) - samplesRead)*sampleSize; + mal_zero_memory((mal_uint8*)pSamples + consumedBytes, remainingBytes); + + return samplesRead; +} + +// A helper for sending sample data to the client. +static inline void mal_device__send_frames_to_client(mal_device* pDevice, mal_uint32 frameCount, const void* pSamples) +{ + mal_assert(pDevice != NULL); + mal_assert(frameCount > 0); + mal_assert(pSamples != NULL); + + mal_recv_proc onRecv = pDevice->onRecv; + if (onRecv) { + pDevice->_dspFrameCount = frameCount; + pDevice->_dspFrames = (const mal_uint8*)pSamples; + + mal_uint8 chunkBuffer[4096]; + mal_uint32 chunkFrameCount = sizeof(chunkBuffer) / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + + for (;;) { + mal_uint32 framesJustRead = mal_dsp_read_frames(&pDevice->dsp, chunkFrameCount, chunkBuffer); + if (framesJustRead == 0) { + break; + } + + onRecv(pDevice, framesJustRead, chunkBuffer); + + if (framesJustRead < chunkFrameCount) { + break; + } + } + } +} + +// A helper for changing the state of the device. +static inline void mal_device__set_state(mal_device* pDevice, mal_uint32 newState) +{ + mal_atomic_exchange_32(&pDevice->state, newState); +} + +// A helper for getting the state of the device. +static inline mal_uint32 mal_device__get_state(mal_device* pDevice) +{ + return pDevice->state; +} + + +#ifdef MAL_WIN32 +static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +//static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_ALAW = {0x00000006, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +//static GUID MAL_GUID_KSDATAFORMAT_SUBTYPE_MULAW = {0x00000007, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// +// Null Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_NULL +mal_result mal_context_init__null(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + // The null backend always works. + (void)pContext; + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__null(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_null); + + (void)pContext; + return MAL_SUCCESS; +} + +static mal_result mal_enumerate_devices__null(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + mal_uint32 infoSize = *pCount; + *pCount = 1; // There's only one "device" each for playback and recording for the null backend. + + if (pInfo != NULL && infoSize > 0) { + mal_zero_object(pInfo); + + if (type == mal_device_type_playback) { + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "NULL Playback Device", (size_t)-1); + } else { + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "NULL Capture Device", (size_t)-1); + } + } + + return MAL_SUCCESS; +} + +static void mal_device_uninit__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + mal_free(pDevice->null_device.pBuffer); +} + +static mal_result mal_device_init__null(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + (void)type; + (void)pDeviceID; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->null_device); + + pDevice->bufferSizeInFrames = pConfig->bufferSizeInFrames; + pDevice->periods = pConfig->periods; + + pDevice->null_device.pBuffer = (mal_uint8*)mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format)); + if (pDevice->null_device.pBuffer == NULL) { + return MAL_OUT_OF_MEMORY; + } + + mal_zero_memory(pDevice->null_device.pBuffer, mal_device_get_buffer_size_in_bytes(pDevice)); + + return MAL_SUCCESS; +} + +static mal_result mal_device__start_backend__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_timer_init(&pDevice->null_device.timer); + pDevice->null_device.lastProcessedFrame = 0; + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + (void)pDevice; + + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->null_device.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; +} + +static mal_bool32 mal_device__get_current_frame__null(mal_device* pDevice, mal_uint32* pCurrentPos) +{ + mal_assert(pDevice != NULL); + mal_assert(pCurrentPos != NULL); + *pCurrentPos = 0; + + mal_uint64 currentFrameAbs = (mal_uint64)(mal_timer_get_time_in_seconds(&pDevice->null_device.timer) * pDevice->sampleRate) / pDevice->channels; + + *pCurrentPos = (mal_uint32)(currentFrameAbs % pDevice->bufferSizeInFrames); + return MAL_TRUE; +} + +static mal_uint32 mal_device__get_available_frames__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_uint32 currentFrame; + if (!mal_device__get_current_frame__null(pDevice, ¤tFrame)) { + return 0; + } + + // In a playback device the last processed frame should always be ahead of the current frame. The space between + // the last processed and current frame (moving forward, starting from the last processed frame) is the amount + // of space available to write. + // + // For a recording device it's the other way around - the last processed frame is always _behind_ the current + // frame and the space between is the available space. + mal_uint32 totalFrameCount = pDevice->bufferSizeInFrames; + if (pDevice->type == mal_device_type_playback) { + mal_uint32 committedBeg = currentFrame; + mal_uint32 committedEnd = pDevice->null_device.lastProcessedFrame; + if (committedEnd <= committedBeg) { + committedEnd += totalFrameCount; // Wrap around. + } + + mal_uint32 committedSize = (committedEnd - committedBeg); + mal_assert(committedSize <= totalFrameCount); + + return totalFrameCount - committedSize; + } else { + mal_uint32 validBeg = pDevice->null_device.lastProcessedFrame; + mal_uint32 validEnd = currentFrame; + if (validEnd < validBeg) { + validEnd += totalFrameCount; // Wrap around. + } + + mal_uint32 validSize = (validEnd - validBeg); + mal_assert(validSize <= totalFrameCount); + + return validSize; + } +} + +static mal_uint32 mal_device__wait_for_frames__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + while (!pDevice->null_device.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__get_available_frames__null(pDevice); + if (framesAvailable > 0) { + return framesAvailable; + } + + mal_sleep(16); + } + + // We'll get here if the loop was terminated. Just return whatever's available. + return mal_device__get_available_frames__null(pDevice); +} + +static mal_result mal_device__main_loop__null(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->null_device.breakFromMainLoop = MAL_FALSE; + while (!pDevice->null_device.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__null(pDevice); + if (framesAvailable == 0) { + continue; + } + + // If it's a playback device, don't bother grabbing more data if the device is being stopped. + if (pDevice->null_device.breakFromMainLoop && pDevice->type == mal_device_type_playback) { + return MAL_FALSE; + } + + if (framesAvailable + pDevice->null_device.lastProcessedFrame > pDevice->bufferSizeInFrames) { + framesAvailable = pDevice->bufferSizeInFrames - pDevice->null_device.lastProcessedFrame; + } + + mal_uint32 sampleCount = framesAvailable * pDevice->channels; + mal_uint32 lockOffset = pDevice->null_device.lastProcessedFrame * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + mal_uint32 lockSize = sampleCount * mal_get_sample_size_in_bytes(pDevice->format); + + if (pDevice->type == mal_device_type_playback) { + if (pDevice->null_device.breakFromMainLoop) { + return MAL_FALSE; + } + + mal_device__read_frames_from_client(pDevice, framesAvailable, pDevice->null_device.pBuffer + lockOffset); + } else { + mal_zero_memory(pDevice->null_device.pBuffer + lockOffset, lockSize); + mal_device__send_frames_to_client(pDevice, framesAvailable, pDevice->null_device.pBuffer + lockOffset); + } + + pDevice->null_device.lastProcessedFrame = (pDevice->null_device.lastProcessedFrame + framesAvailable) % pDevice->bufferSizeInFrames; + } + + return MAL_SUCCESS; +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// WIN32 COMMON +// +/////////////////////////////////////////////////////////////////////////////// +#if defined(MAL_WIN32) +#include "objbase.h" +#if defined(MAL_WIN32_DESKTOP) + #define mal_CoInitializeEx(pContext, pvReserved, dwCoInit) ((MAL_PFN_CoInitializeEx)pContext->win32.CoInitializeEx)(pvReserved, dwCoInit) + #define mal_CoUninitialize(pContext) ((MAL_PFN_CoUninitialize)pContext->win32.CoUninitialize)() + #define mal_CoCreateInstance(pContext, rclsid, pUnkOuter, dwClsContext, riid, ppv) ((MAL_PFN_CoCreateInstance)pContext->win32.CoCreateInstance)(rclsid, pUnkOuter, dwClsContext, riid, ppv) + #define mal_CoTaskMemFree(pContext, pv) ((MAL_PFN_CoTaskMemFree)pContext->win32.CoTaskMemFree)(pv) + #define mal_PropVariantClear(pContext, pvar) ((MAL_PFN_PropVariantClear)pContext->win32.PropVariantClear)(pvar) +#else + #define mal_CoInitializeEx(pContext, pvReserved, dwCoInit) CoInitializeEx(pvReserved, dwCoInit) + #define mal_CoUninitialize(pContext) CoUninitialize() + #define mal_CoCreateInstance(pContext, rclsid, pUnkOuter, dwClsContext, riid, ppv) CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, ppv) + #define mal_CoTaskMemFree(pContext, pv) CoTaskMemFree(pv) + #define mal_PropVariantClear(pContext, pvar) PropVariantClear(pvar) +#endif +#endif + +#if defined(MAL_ENABLE_WASAPI) || defined(MAL_ENABLE_DSOUND) +#include + +#ifndef SPEAKER_FRONT_LEFT +#define SPEAKER_FRONT_LEFT 0x1 +#define SPEAKER_FRONT_RIGHT 0x2 +#define SPEAKER_FRONT_CENTER 0x4 +#define SPEAKER_LOW_FREQUENCY 0x8 +#define SPEAKER_BACK_LEFT 0x10 +#define SPEAKER_BACK_RIGHT 0x20 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +#define SPEAKER_BACK_CENTER 0x100 +#define SPEAKER_SIDE_LEFT 0x200 +#define SPEAKER_SIDE_RIGHT 0x400 +#define SPEAKER_TOP_CENTER 0x800 +#define SPEAKER_TOP_FRONT_LEFT 0x1000 +#define SPEAKER_TOP_FRONT_CENTER 0x2000 +#define SPEAKER_TOP_FRONT_RIGHT 0x4000 +#define SPEAKER_TOP_BACK_LEFT 0x8000 +#define SPEAKER_TOP_BACK_CENTER 0x10000 +#define SPEAKER_TOP_BACK_RIGHT 0x20000 +#endif + +// The SDK that comes with old versions of MSVC (VC6, for example) does not appear to define WAVEFORMATEXTENSIBLE. We +// define our own implementation in this case. +#ifndef _WAVEFORMATEXTENSIBLE_ +typedef struct +{ + WAVEFORMATEX Format; + union + { + WORD wValidBitsPerSample; + WORD wSamplesPerBlock; + WORD wReserved; + } Samples; + DWORD dwChannelMask; + GUID SubFormat; +} WAVEFORMATEXTENSIBLE; +#endif + +#ifndef WAVE_FORMAT_EXTENSIBLE +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE +#endif + +// Converts an individual Win32-style channel identifier (SPEAKER_FRONT_LEFT, etc.) to mini_al. +static mal_uint8 mal_channel_id_to_mal__win32(DWORD id) +{ + switch (id) + { + case SPEAKER_FRONT_LEFT: return MAL_CHANNEL_FRONT_LEFT; + case SPEAKER_FRONT_RIGHT: return MAL_CHANNEL_FRONT_RIGHT; + case SPEAKER_FRONT_CENTER: return MAL_CHANNEL_FRONT_CENTER; + case SPEAKER_LOW_FREQUENCY: return MAL_CHANNEL_LFE; + case SPEAKER_BACK_LEFT: return MAL_CHANNEL_BACK_LEFT; + case SPEAKER_BACK_RIGHT: return MAL_CHANNEL_BACK_RIGHT; + case SPEAKER_FRONT_LEFT_OF_CENTER: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case SPEAKER_FRONT_RIGHT_OF_CENTER: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case SPEAKER_BACK_CENTER: return MAL_CHANNEL_BACK_CENTER; + case SPEAKER_SIDE_LEFT: return MAL_CHANNEL_SIDE_LEFT; + case SPEAKER_SIDE_RIGHT: return MAL_CHANNEL_SIDE_RIGHT; + case SPEAKER_TOP_CENTER: return MAL_CHANNEL_TOP_CENTER; + case SPEAKER_TOP_FRONT_LEFT: return MAL_CHANNEL_TOP_FRONT_LEFT; + case SPEAKER_TOP_FRONT_CENTER: return MAL_CHANNEL_TOP_FRONT_CENTER; + case SPEAKER_TOP_FRONT_RIGHT: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case SPEAKER_TOP_BACK_LEFT: return MAL_CHANNEL_TOP_BACK_LEFT; + case SPEAKER_TOP_BACK_CENTER: return MAL_CHANNEL_TOP_BACK_CENTER; + case SPEAKER_TOP_BACK_RIGHT: return MAL_CHANNEL_TOP_BACK_RIGHT; + default: return 0; + } +} + +// Converts an individual mini_al channel identifier (MAL_CHANNEL_FRONT_LEFT, etc.) to Win32-style. +static DWORD mal_channel_id_to_win32(DWORD id) +{ + switch (id) + { + case MAL_CHANNEL_FRONT_LEFT: return SPEAKER_FRONT_LEFT; + case MAL_CHANNEL_FRONT_RIGHT: return SPEAKER_FRONT_RIGHT; + case MAL_CHANNEL_FRONT_CENTER: return SPEAKER_FRONT_CENTER; + case MAL_CHANNEL_LFE: return SPEAKER_LOW_FREQUENCY; + case MAL_CHANNEL_BACK_LEFT: return SPEAKER_BACK_LEFT; + case MAL_CHANNEL_BACK_RIGHT: return SPEAKER_BACK_RIGHT; + case MAL_CHANNEL_FRONT_LEFT_CENTER: return SPEAKER_FRONT_LEFT_OF_CENTER; + case MAL_CHANNEL_FRONT_RIGHT_CENTER: return SPEAKER_FRONT_RIGHT_OF_CENTER; + case MAL_CHANNEL_BACK_CENTER: return SPEAKER_BACK_CENTER; + case MAL_CHANNEL_SIDE_LEFT: return SPEAKER_SIDE_LEFT; + case MAL_CHANNEL_SIDE_RIGHT: return SPEAKER_SIDE_RIGHT; + case MAL_CHANNEL_TOP_CENTER: return SPEAKER_TOP_CENTER; + case MAL_CHANNEL_TOP_FRONT_LEFT: return SPEAKER_TOP_FRONT_LEFT; + case MAL_CHANNEL_TOP_FRONT_CENTER: return SPEAKER_TOP_FRONT_CENTER; + case MAL_CHANNEL_TOP_FRONT_RIGHT: return SPEAKER_TOP_FRONT_RIGHT; + case MAL_CHANNEL_TOP_BACK_LEFT: return SPEAKER_TOP_BACK_LEFT; + case MAL_CHANNEL_TOP_BACK_CENTER: return SPEAKER_TOP_BACK_CENTER; + case MAL_CHANNEL_TOP_BACK_RIGHT: return SPEAKER_TOP_BACK_RIGHT; + default: return 0; + } +} + +// Converts a channel mapping to a Win32-style channel mask. +static DWORD mal_channel_map_to_channel_mask__win32(const mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_uint32 channels) +{ + DWORD dwChannelMask = 0; + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + dwChannelMask |= mal_channel_id_to_win32(channelMap[iChannel]); + } + + return dwChannelMask; +} + +// Converts a Win32-style channel mask to a mini_al channel map. +static void mal_channel_mask_to_channel_map__win32(DWORD dwChannelMask, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +{ + if (channels == 1 && dwChannelMask == 0) { + channelMap[0] = MAL_CHANNEL_FRONT_CENTER; + } else if (channels == 2 && dwChannelMask == 0) { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + } else { + // Just iterate over each bit. + mal_uint32 iChannel = 0; + for (mal_uint32 iBit = 0; iBit < 32; ++iBit) { + DWORD bitValue = (dwChannelMask & (1 << iBit)); + if (bitValue != 0) { + // The bit is set. + channelMap[iChannel] = mal_channel_id_to_mal__win32(bitValue); + iChannel += 1; + } + } + } +} +#endif + + +/////////////////////////////////////////////////////////////////////////////// +// +// WASAPI Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_WASAPI +#if defined(_MSC_VER) + #pragma warning(push) + #pragma warning(disable:4091) // 'typedef ': ignored on left of '' when no variable is declared +#endif +#include +#include +#include +#if defined(_MSC_VER) + #pragma warning(pop) +#endif + +const PROPERTYKEY g_malPKEY_Device_FriendlyName = {{0xa45c254e, 0xdf1c, 0x4efd, {0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0}}, 14}; +const PROPERTYKEY g_malPKEY_AudioEngine_DeviceFormat = {{0xf19f064d, 0x82c, 0x4e27, {0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c}}, 0}; + +const IID g_malCLSID_MMDeviceEnumerator_Instance = {0xBCDE0395, 0xE52F, 0x467C, {0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E}}; // BCDE0395-E52F-467C-8E3D-C4579291692E = __uuidof(MMDeviceEnumerator) +const IID g_malIID_IMMDeviceEnumerator_Instance = {0xA95664D2, 0x9614, 0x4F35, {0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6}}; // A95664D2-9614-4F35-A746-DE8DB63617E6 = __uuidof(IMMDeviceEnumerator) +const IID g_malIID_IAudioClient_Instance = {0x1CB9AD4C, 0xDBFA, 0x4C32, {0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2}}; // 1CB9AD4C-DBFA-4C32-B178-C2F568A703B2 = __uuidof(IAudioClient) +const IID g_malIID_IAudioRenderClient_Instance = {0xF294ACFC, 0x3146, 0x4483, {0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2}}; // F294ACFC-3146-4483-A7BF-ADDCA7C260E2 = __uuidof(IAudioRenderClient) +const IID g_malIID_IAudioCaptureClient_Instance = {0xC8ADBD64, 0xE71E, 0x48A0, {0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17}}; // C8ADBD64-E71E-48A0-A4DE-185C395CD317 = __uuidof(IAudioCaptureClient) + +#ifndef MAL_WIN32_DESKTOP +const IID g_malIID_DEVINTERFACE_AUDIO_RENDER = {0xE6327CAD, 0xDCEC, 0x4949, {0xAE, 0x8A, 0x99, 0x1E, 0x97, 0x6A, 0x79, 0xD2}}; // E6327CAD-DCEC-4949-AE8A-991E976A79D2 +const IID g_malIID_DEVINTERFACE_AUDIO_CAPTURE = {0x2EEF81BE, 0x33FA, 0x4800, {0x96, 0x70, 0x1C, 0xD4, 0x74, 0x97, 0x2C, 0x3F}}; // 2EEF81BE-33FA-4800-9670-1CD474972C3F +#endif + +#ifdef __cplusplus +#define g_malCLSID_MMDeviceEnumerator g_malCLSID_MMDeviceEnumerator_Instance +#define g_malIID_IMMDeviceEnumerator g_malIID_IMMDeviceEnumerator_Instance +#define g_malIID_IAudioClient g_malIID_IAudioClient_Instance +#define g_malIID_IAudioRenderClient g_malIID_IAudioRenderClient_Instance +#define g_malIID_IAudioCaptureClient g_malIID_IAudioCaptureClient_Instance +#else +#define g_malCLSID_MMDeviceEnumerator &g_malCLSID_MMDeviceEnumerator_Instance +#define g_malIID_IMMDeviceEnumerator &g_malIID_IMMDeviceEnumerator_Instance +#define g_malIID_IAudioClient &g_malIID_IAudioClient_Instance +#define g_malIID_IAudioRenderClient &g_malIID_IAudioRenderClient_Instance +#define g_malIID_IAudioCaptureClient &g_malIID_IAudioCaptureClient_Instance +#endif + +#ifdef __cplusplus +#define mal_is_guid_equal(a, b) IsEqualGUID(a, b) +#else +#define mal_is_guid_equal(a, b) IsEqualGUID(&a, &b) +#endif + +#ifdef MAL_WIN32_DESKTOP + // IMMDeviceEnumerator + #ifdef __cplusplus + #define IMMDeviceEnumerator_Release(p) ((IMMDeviceEnumerator*)p)->Release() + #else + #define IMMDeviceEnumerator_Release(p) ((IMMDeviceEnumerator*)p)->lpVtbl->Release((IMMDeviceEnumerator*)p) + #endif + #ifdef __cplusplus + #define IMMDeviceEnumerator_EnumAudioEndpoints(p, a, b, c) ((IMMDeviceEnumerator*)p)->EnumAudioEndpoints(a, b, c) + #else + #define IMMDeviceEnumerator_EnumAudioEndpoints(p, a, b, c) ((IMMDeviceEnumerator*)p)->lpVtbl->EnumAudioEndpoints(p, a, b, c) + #endif + #ifdef __cplusplus + #define IMMDeviceEnumerator_GetDefaultAudioEndpoint(p, a, b, c) ((IMMDeviceEnumerator*)p)->GetDefaultAudioEndpoint(a, b, c) + #else + #define IMMDeviceEnumerator_GetDefaultAudioEndpoint(p, a, b, c) ((IMMDeviceEnumerator*)p)->lpVtbl->GetDefaultAudioEndpoint(p, a, b, c) + #endif + #ifdef __cplusplus + #define IMMDeviceEnumerator_GetDevice(p, a, b) ((IMMDeviceEnumerator*)p)->GetDevice(a, b) + #else + #define IMMDeviceEnumerator_GetDevice(p, a, b) ((IMMDeviceEnumerator*)p)->lpVtbl->GetDevice(p, a, b) + #endif + + // IMMDeviceCollection + #ifdef __cplusplus + #define IMMDeviceCollection_Release(p) ((IMMDeviceCollection*)p)->Release() + #else + #define IMMDeviceCollection_Release(p) ((IMMDeviceCollection*)p)->lpVtbl->Release((IMMDeviceCollection*)p) + #endif + #ifdef __cplusplus + #define IMMDeviceCollection_GetCount(p, a) ((IMMDeviceCollection*)p)->GetCount(a) + #else + #define IMMDeviceCollection_GetCount(p, a) ((IMMDeviceCollection*)p)->lpVtbl->GetCount((IMMDeviceCollection*)p, a) + #endif + #ifdef __cplusplus + #define IMMDeviceCollection_Item(p, a, b) ((IMMDeviceCollection*)p)->Item(a, b) + #else + #define IMMDeviceCollection_Item(p, a, b) ((IMMDeviceCollection*)p)->lpVtbl->Item((IMMDeviceCollection*)p, a, b) + #endif + + // IMMDevice + #ifdef __cplusplus + #define IMMDevice_Release(p) ((IMMDevice*)p)->Release() + #else + #define IMMDevice_Release(p) ((IMMDevice*)p)->lpVtbl->Release((IMMDevice*)p) + #endif + #ifdef __cplusplus + #define IMMDevice_GetId(p, a) ((IMMDevice*)p)->GetId(a) + #else + #define IMMDevice_GetId(p, a) ((IMMDevice*)p)->lpVtbl->GetId((IMMDevice*)p, a) + #endif + #ifdef __cplusplus + #define IMMDevice_OpenPropertyStore(p, a, b) ((IMMDevice*)p)->OpenPropertyStore(a, b) + #else + #define IMMDevice_OpenPropertyStore(p, a, b) ((IMMDevice*)p)->lpVtbl->OpenPropertyStore((IMMDevice*)p, a, b) + #endif + #ifdef __cplusplus + #define IMMDevice_Activate(p, a, b, c, d) ((IMMDevice*)p)->Activate(a, b, c, d) + #else + #define IMMDevice_Activate(p, a, b, c, d) ((IMMDevice*)p)->lpVtbl->Activate((IMMDevice*)p, a, b, c, d) + #endif +#else + // IActivateAudioInterfaceAsyncOperation + #ifdef __cplusplus + #define IActivateAudioInterfaceAsyncOperation_Release(p) ((IActivateAudioInterfaceAsyncOperation*)p)->Release() + #else + #define IActivateAudioInterfaceAsyncOperation_Release(p) ((IActivateAudioInterfaceAsyncOperation*)p)->lpVtbl->Release((IActivateAudioInterfaceAsyncOperation*)p) + #endif + #ifdef __cplusplus + #define IActivateAudioInterfaceAsyncOperation_GetActivateResult(p, a, b) ((IActivateAudioInterfaceAsyncOperation*)p)->GetActivateResult(a, b) + #else + #define IActivateAudioInterfaceAsyncOperation_GetActivateResult(p, a, b) ((IActivateAudioInterfaceAsyncOperation*)p)->lpVtbl->GetActivateResult((IActivateAudioInterfaceAsyncOperation*)p, a, b) + #endif +#endif + +// IPropertyStore +#ifdef __cplusplus + #define IPropertyStore_Release(p) ((IPropertyStore*)p)->Release() +#else + #define IPropertyStore_Release(p) ((IPropertyStore*)p)->lpVtbl->Release((IPropertyStore*)p) +#endif +#ifdef __cplusplus + #define IPropertyStore_GetValue(p, a, b) ((IPropertyStore*)p)->GetValue(a, b) +#else + #define IPropertyStore_GetValue(p, a, b) ((IPropertyStore*)p)->lpVtbl->GetValue((IPropertyStore*)p, &a, b) +#endif + +// IAudioClient +#ifdef __cplusplus + #define IAudioClient_Release(p) ((IAudioClient*)p)->Release() +#else + #define IAudioClient_Release(p) ((IAudioClient*)p)->lpVtbl->Release((IAudioClient*)p) +#endif +#ifdef __cplusplus + #define IAudioClient_IsFormatSupported(p, a, b, c) ((IAudioClient*)p)->IsFormatSupported(a, b, c) +#else + #define IAudioClient_IsFormatSupported(p, a, b, c) ((IAudioClient*)p)->lpVtbl->IsFormatSupported((IAudioClient*)p, a, b, c) +#endif +#ifdef __cplusplus + #define IAudioClient_GetMixFormat(p, a) ((IAudioClient*)p)->GetMixFormat(a) +#else + #define IAudioClient_GetMixFormat(p, a) ((IAudioClient*)p)->lpVtbl->GetMixFormat((IAudioClient*)p, a) +#endif +#ifdef __cplusplus + #define IAudioClient_Initialize(p, a, b, c, d, e, f) ((IAudioClient*)p)->Initialize(a, b, c, d, e, f) +#else + #define IAudioClient_Initialize(p, a, b, c, d, e, f) ((IAudioClient*)p)->lpVtbl->Initialize((IAudioClient*)p, a, b, c, d, e, f) +#endif +#ifdef __cplusplus + #define IAudioClient_GetBufferSize(p, a) ((IAudioClient*)p)->GetBufferSize(a) +#else + #define IAudioClient_GetBufferSize(p, a) ((IAudioClient*)p)->lpVtbl->GetBufferSize((IAudioClient*)p, a) +#endif +#ifdef __cplusplus + #define IAudioClient_GetService(p, a, b) ((IAudioClient*)p)->GetService(a, b) +#else + #define IAudioClient_GetService(p, a, b) ((IAudioClient*)p)->lpVtbl->GetService((IAudioClient*)p, a, b) +#endif +#ifdef __cplusplus + #define IAudioClient_Start(p) ((IAudioClient*)p)->Start() +#else + #define IAudioClient_Start(p) ((IAudioClient*)p)->lpVtbl->Start((IAudioClient*)p) +#endif +#ifdef __cplusplus + #define IAudioClient_Stop(p) ((IAudioClient*)p)->Stop() +#else + #define IAudioClient_Stop(p) ((IAudioClient*)p)->lpVtbl->Stop((IAudioClient*)p) +#endif +#ifdef __cplusplus + #define IAudioClient_GetCurrentPadding(p, a) ((IAudioClient*)p)->GetCurrentPadding(a) +#else + #define IAudioClient_GetCurrentPadding(p, a) ((IAudioClient*)p)->lpVtbl->GetCurrentPadding((IAudioClient*)p, a) +#endif +#ifdef __cplusplus + #define IAudioClient_SetEventHandle(p, a) ((IAudioClient*)p)->SetEventHandle(a) +#else + #define IAudioClient_SetEventHandle(p, a) ((IAudioClient*)p)->lpVtbl->SetEventHandle((IAudioClient*)p, a) +#endif + +// IAudioRenderClient +#ifdef __cplusplus + #define IAudioRenderClient_Release(p) ((IAudioRenderClient*)p)->Release() +#else + #define IAudioRenderClient_Release(p) ((IAudioRenderClient*)p)->lpVtbl->Release((IAudioRenderClient*)p) +#endif +#ifdef __cplusplus + #define IAudioRenderClient_GetBuffer(p, a, b) ((IAudioRenderClient*)p)->GetBuffer(a, b) +#else + #define IAudioRenderClient_GetBuffer(p, a, b) ((IAudioRenderClient*)p)->lpVtbl->GetBuffer((IAudioRenderClient*)p, a, b) +#endif +#ifdef __cplusplus + #define IAudioRenderClient_ReleaseBuffer(p, a, b) ((IAudioRenderClient*)p)->ReleaseBuffer(a, b) +#else + #define IAudioRenderClient_ReleaseBuffer(p, a, b) ((IAudioRenderClient*)p)->lpVtbl->ReleaseBuffer((IAudioRenderClient*)p, a, b) +#endif + +// IAudioCaptureClient +#ifdef __cplusplus + #define IAudioCaptureClient_Release(p) ((IAudioCaptureClient*)p)->Release() +#else + #define IAudioCaptureClient_Release(p) ((IAudioCaptureClient*)p)->lpVtbl->Release((IAudioCaptureClient*)p) +#endif +#ifdef __cplusplus + #define IAudioCaptureClient_GetNextPacketSize(p, a) ((IAudioCaptureClient*)p)->GetNextPacketSize(a) +#else + #define IAudioCaptureClient_GetNextPacketSize(p, a) ((IAudioCaptureClient*)p)->lpVtbl->GetNextPacketSize((IAudioCaptureClient*)p, a) +#endif +#ifdef __cplusplus + #define IAudioCaptureClient_GetBuffer(p, a, b, c, d, e) ((IAudioCaptureClient*)p)->GetBuffer(a, b, c, d, e) +#else + #define IAudioCaptureClient_GetBuffer(p, a, b, c, d, e) ((IAudioCaptureClient*)p)->lpVtbl->GetBuffer((IAudioCaptureClient*)p, a, b, c, d, e) +#endif +#ifdef __cplusplus + #define IAudioCaptureClient_ReleaseBuffer(p, a) ((IAudioCaptureClient*)p)->ReleaseBuffer(a) +#else + #define IAudioCaptureClient_ReleaseBuffer(p, a) ((IAudioCaptureClient*)p)->lpVtbl->ReleaseBuffer((IAudioCaptureClient*)p, a) +#endif + +mal_result mal_context_init__wasapi(mal_context* pContext) +{ + mal_assert(pContext != NULL); + (void)pContext; + +#ifdef MAL_WIN32_DESKTOP + // WASAPI is only supported in Vista SP1 and newer. The reason for SP1 and not the base version of Vista is that event-driven + // exclusive mode does not work until SP1. + OSVERSIONINFOEXW osvi; + mal_zero_object(&osvi); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 1; + if (VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL), VER_MINORVERSION, VER_GREATER_EQUAL), VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL))) { + return MAL_SUCCESS; + } else { + return MAL_NO_BACKEND; + } +#else + return MAL_SUCCESS; +#endif +} + +mal_result mal_context_uninit__wasapi(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_wasapi); + (void)pContext; + + return MAL_SUCCESS; +} + +static mal_result mal_enumerate_devices__wasapi(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + mal_uint32 infoSize = *pCount; + *pCount = 0; + +#ifdef MAL_WIN32_DESKTOP + IMMDeviceEnumerator* pDeviceEnumerator; + HRESULT hr = mal_CoCreateInstance(pContext, g_malCLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, g_malIID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to create device enumerator.", MAL_WASAPI_FAILED_TO_CREATE_DEVICE_ENUMERATOR); + } + + IMMDeviceCollection* pDeviceCollection; + hr = IMMDeviceEnumerator_EnumAudioEndpoints(pDeviceEnumerator, (type == mal_device_type_playback) ? eRender : eCapture, DEVICE_STATE_ACTIVE, &pDeviceCollection); + if (FAILED(hr)) { + IMMDeviceEnumerator_Release(pDeviceEnumerator); + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to enumerate audio endpoints.", MAL_NO_DEVICE); + } + + IMMDeviceEnumerator_Release(pDeviceEnumerator); + + UINT count; + hr = IMMDeviceCollection_GetCount(pDeviceCollection, &count); + if (FAILED(hr)) { + IMMDeviceCollection_Release(pDeviceCollection); + return mal_context_post_error(pContext, NULL, "[WASAPI] Failed to get device count.", MAL_NO_DEVICE); + } + + for (mal_uint32 iDevice = 0; iDevice < count; ++iDevice) { + if (pInfo != NULL) { + if (infoSize > 0) { + mal_zero_object(pInfo); + + IMMDevice* pDevice; + hr = IMMDeviceCollection_Item(pDeviceCollection, iDevice, &pDevice); + if (SUCCEEDED(hr)) { + // ID. + LPWSTR id; + hr = IMMDevice_GetId(pDevice, &id); + if (SUCCEEDED(hr)) { + size_t idlen = wcslen(id); + if (idlen+sizeof(wchar_t) > sizeof(pInfo->id.wasapi)) { + mal_CoTaskMemFree(pContext, id); + mal_assert(MAL_FALSE); // NOTE: If this is triggered, please report it. It means the format of the ID must haved change and is too long to fit in our fixed sized buffer. + continue; + } + + memcpy(pInfo->id.wasapi, id, idlen * sizeof(wchar_t)); + pInfo->id.wasapi[idlen] = '\0'; + + mal_CoTaskMemFree(pContext, id); + } + + // Description / Friendly Name. + IPropertyStore *pProperties; + hr = IMMDevice_OpenPropertyStore(pDevice, STGM_READ, &pProperties); + if (SUCCEEDED(hr)) { + PROPVARIANT varName; + PropVariantInit(&varName); + hr = IPropertyStore_GetValue(pProperties, g_malPKEY_Device_FriendlyName, &varName); + if (SUCCEEDED(hr)) { + WideCharToMultiByte(CP_UTF8, 0, varName.pwszVal, -1, pInfo->name, sizeof(pInfo->name), 0, FALSE); + mal_PropVariantClear(pContext, &varName); + } + + IPropertyStore_Release(pProperties); + } + } + + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + } + + IMMDeviceCollection_Release(pDeviceCollection); +#else + // The MMDevice API is only supported on desktop applications. For now, while I'm still figuring out how to properly enumerate + // over devices without using MMDevice, I'm restricting devices to defaults. + if (pInfo != NULL) { + if (infoSize > 0) { + if (type == mal_device_type_playback) { + mal_copy_memory(pInfo->id.wasapi, &g_malIID_DEVINTERFACE_AUDIO_RENDER, sizeof(g_malIID_DEVINTERFACE_AUDIO_RENDER)); + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); + } else { + mal_copy_memory(pInfo->id.wasapi, &g_malIID_DEVINTERFACE_AUDIO_CAPTURE, sizeof(g_malIID_DEVINTERFACE_AUDIO_CAPTURE)); + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); + } + + pInfo += 1; + *pCount += 1; + } + } else { + *pCount += 1; + } +#endif + + return MAL_SUCCESS; +} + +static void mal_device_uninit__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->wasapi.pRenderClient) { + IAudioRenderClient_Release(pDevice->wasapi.pRenderClient); + } + if (pDevice->wasapi.pCaptureClient) { + IAudioCaptureClient_Release(pDevice->wasapi.pCaptureClient); + } + if (pDevice->wasapi.pAudioClient) { + IAudioClient_Release(pDevice->wasapi.pAudioClient); + } + + if (pDevice->wasapi.hEvent) { + CloseHandle(pDevice->wasapi.hEvent); + } + if (pDevice->wasapi.hStopEvent) { + CloseHandle(pDevice->wasapi.hStopEvent); + } +} + +#ifndef MAL_WIN32_DESKTOP + #ifdef __cplusplus + #include + class malCompletionHandler : public Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags< Microsoft::WRL::ClassicCom >, Microsoft::WRL::FtmBase, IActivateAudioInterfaceCompletionHandler > + { + public: + + malCompletionHandler() + : m_hEvent(NULL) + { + } + + mal_result Init() + { + m_hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (m_hEvent == NULL) { + return MAL_ERROR; + } + + return MAL_SUCCESS; + } + + void Uninit() + { + if (m_hEvent != NULL) { + CloseHandle(m_hEvent); + } + } + + void Wait() + { + WaitForSingleObject(m_hEvent, INFINITE); + } + + HRESULT STDMETHODCALLTYPE ActivateCompleted(IActivateAudioInterfaceAsyncOperation *activateOperation) + { + (void)activateOperation; + SetEvent(m_hEvent); + return S_OK; + } + + private: + HANDLE m_hEvent; // This is created in Init(), deleted in Uninit(), waited on in Wait() and signaled in ActivateCompleted(). + }; + #else + #error "The UWP build is currently only supported in C++." + #endif +#endif // !MAL_WIN32_DESKTOP + +static mal_result mal_device_init__wasapi(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->wasapi); + + HRESULT hr; + mal_result result = MAL_SUCCESS; + const char* errorMsg = ""; + AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_SHARED; + + WAVEFORMATEXTENSIBLE wf; + mal_zero_object(&wf); + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)pDevice->channels; + wf.Format.nSamplesPerSec = (DWORD)pDevice->sampleRate; + wf.Format.wBitsPerSample = (WORD)mal_get_sample_size_in_bytes(pDevice->format)*8; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.dwChannelMask = mal_channel_map_to_channel_mask__win32(pDevice->channelMap, pDevice->channels); + if (pDevice->format == mal_format_f32) { + wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } else { + wf.SubFormat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM; + } + +#ifdef MAL_WIN32_DESKTOP + IMMDevice* pMMDevice = NULL; + + IMMDeviceEnumerator* pDeviceEnumerator; + hr = mal_CoCreateInstance(pContext, g_malCLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, g_malIID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to create IMMDeviceEnumerator.", result = MAL_WASAPI_FAILED_TO_CREATE_DEVICE_ENUMERATOR; + goto done; + } + + if (pDeviceID == NULL) { + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pDeviceEnumerator, (type == mal_device_type_playback) ? eRender : eCapture, eConsole, &pMMDevice); + if (FAILED(hr)) { + IMMDeviceEnumerator_Release(pDeviceEnumerator); + errorMsg = "[WASAPI] Failed to create default backend device.", result = MAL_WASAPI_FAILED_TO_CREATE_DEVICE; + goto done; + } + } else { + hr = IMMDeviceEnumerator_GetDevice(pDeviceEnumerator, pDeviceID->wasapi, &pMMDevice); + if (FAILED(hr)) { + IMMDeviceEnumerator_Release(pDeviceEnumerator); + errorMsg = "[WASAPI] Failed to create backend device.", result = MAL_WASAPI_FAILED_TO_CREATE_DEVICE; + goto done; + } + } + + IMMDeviceEnumerator_Release(pDeviceEnumerator); + + hr = IMMDevice_Activate(pMMDevice, g_malIID_IAudioClient, CLSCTX_ALL, NULL, &pDevice->wasapi.pAudioClient); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to activate device.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + goto done; + } +#else + IActivateAudioInterfaceAsyncOperation *pAsyncOp = NULL; + malCompletionHandler completionHandler; + + IID iid; + if (pDeviceID != NULL) { + mal_copy_memory(&iid, pDeviceID->wasapi, sizeof(iid)); + } else { + if (type == mal_device_type_playback) { + iid = g_malIID_DEVINTERFACE_AUDIO_RENDER; + } else { + iid = g_malIID_DEVINTERFACE_AUDIO_CAPTURE; + } + } + + LPOLESTR iidStr; + hr = StringFromIID(iid, &iidStr); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to convert device IID to string for ActivateAudioInterfaceAsync(). Out of memory.", result = MAL_OUT_OF_MEMORY; + goto done; + } + + result = completionHandler.Init(); + if (result != MAL_SUCCESS) { + mal_CoTaskMemFree(pContext, iidStr); + + errorMsg = "[WASAPI] Failed to create event for waiting for ActivateAudioInterfaceAsync().", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + goto done; + } + + hr = ActivateAudioInterfaceAsync(iidStr, g_malIID_IAudioClient, NULL, &completionHandler, &pAsyncOp); + if (FAILED(hr)) { + completionHandler.Uninit(); + mal_CoTaskMemFree(pContext, iidStr); + + errorMsg = "[WASAPI] ActivateAudioInterfaceAsync() failed.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + goto done; + } + + mal_CoTaskMemFree(pContext, iidStr); + + // Wait for the async operation for finish. + completionHandler.Wait(); + completionHandler.Uninit(); + + HRESULT activateResult; + IUnknown* pActivatedInterface; + hr = IActivateAudioInterfaceAsyncOperation_GetActivateResult(pAsyncOp, &activateResult, &pActivatedInterface); + if (FAILED(hr) || FAILED(activateResult)) { + errorMsg = "[WASAPI] Failed to activate device.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + goto done; + } + + // Here is where we grab the IAudioClient interface. + hr = pActivatedInterface->QueryInterface(g_malIID_IAudioClient, &pDevice->wasapi.pAudioClient); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to query IAudioClient interface.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + goto done; + } +#endif + + // Here is where we try to determine the best format to use with the device. If the client if wanting exclusive mode, first try finding the best format for that. If this fails, fall back to shared mode. + WAVEFORMATEXTENSIBLE* pBestFormatTemp = NULL; + result = MAL_FORMAT_NOT_SUPPORTED; + if (pConfig->preferExclusiveMode) { + hr = IAudioClient_IsFormatSupported(pDevice->wasapi.pAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX*)&wf, NULL); + #ifdef MAL_WIN32_DESKTOP + if (hr == AUDCLNT_E_UNSUPPORTED_FORMAT) { + // The format isn't supported, so retrieve the actual format from the property store and try that. + IPropertyStore* pStore = NULL; + hr = IMMDevice_OpenPropertyStore(pMMDevice, STGM_READ, &pStore); + if (SUCCEEDED(hr)) { + PROPVARIANT prop; + PropVariantInit(&prop); + hr = IPropertyStore_GetValue(pStore, g_malPKEY_AudioEngine_DeviceFormat, &prop); + if (SUCCEEDED(hr)) { + WAVEFORMATEX* pActualFormat = (WAVEFORMATEX*)prop.blob.pBlobData; + hr = IAudioClient_IsFormatSupported(pDevice->wasapi.pAudioClient, AUDCLNT_SHAREMODE_EXCLUSIVE, pActualFormat, NULL); + if (SUCCEEDED(hr)) { + mal_copy_memory(&wf, pActualFormat, sizeof(WAVEFORMATEXTENSIBLE)); + } + + mal_PropVariantClear(pDevice->pContext, &prop); + } + + IPropertyStore_Release(pStore); + } + } + #endif + + if (hr == S_OK) { + shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; + result = MAL_SUCCESS; + } + } + + // Fall back to shared mode if necessary. + if (result != MAL_SUCCESS) { + hr = IAudioClient_IsFormatSupported(pDevice->wasapi.pAudioClient, AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX*)&wf, (WAVEFORMATEX**)&pBestFormatTemp); + if (hr != S_OK && hr != S_FALSE) { + hr = IAudioClient_GetMixFormat(pDevice->wasapi.pAudioClient, (WAVEFORMATEX**)&pBestFormatTemp); + if (hr != S_OK) { + result = MAL_WASAPI_FAILED_TO_FIND_BEST_FORMAT; + } else { + result = MAL_SUCCESS; + } + } else { + result = MAL_SUCCESS; + } + + shareMode = AUDCLNT_SHAREMODE_SHARED; + } + + // Return an error if we still haven't found a format. + if (result != MAL_SUCCESS) { + errorMsg = "[WASAPI] Failed to find best device mix format.", result = MAL_WASAPI_FAILED_TO_ACTIVATE_DEVICE; + goto done; + } + + if (pBestFormatTemp != NULL) { + mal_copy_memory(&wf, pBestFormatTemp, sizeof(wf)); + mal_CoTaskMemFree(pDevice->pContext, pBestFormatTemp); + } + + + REFERENCE_TIME bufferDurationInMicroseconds = ((mal_uint64)pDevice->bufferSizeInFrames * 1000 * 1000) / pConfig->sampleRate; + + if (mal_is_guid_equal(wf.SubFormat, MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) { + pDevice->internalFormat = mal_format_f32; + } else { + if (wf.Format.wBitsPerSample == 32) { + pDevice->internalFormat = mal_format_s32; + } else if (wf.Format.wBitsPerSample == 24) { + pDevice->internalFormat = mal_format_s24; + } else if (wf.Format.wBitsPerSample == 16) { + pDevice->internalFormat = mal_format_s16; + } else if (wf.Format.wBitsPerSample == 8) { + pDevice->internalFormat = mal_format_u8; + } else { + errorMsg = "[WASAPI] Device's native format is not supported.", result = MAL_FORMAT_NOT_SUPPORTED; + goto done; + } + } + + pDevice->internalChannels = wf.Format.nChannels; + pDevice->internalSampleRate = wf.Format.nSamplesPerSec; + + // Get the internal channel map based on the channel mask. + mal_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + + // Slightly different initialization for shared and exclusive modes. + if (shareMode == AUDCLNT_SHAREMODE_SHARED) { + // Shared. + REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; + hr = IAudioClient_Initialize(pDevice->wasapi.pAudioClient, shareMode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, 0, (WAVEFORMATEX*)&wf, NULL); + if (FAILED(hr)) { + if (hr == E_ACCESSDENIED) { + errorMsg = "[WASAPI] Failed to initialize device. Access denied.", result = MAL_ACCESS_DENIED; + } else { + errorMsg = "[WASAPI] Failed to initialize device.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + } + + goto done; + } + } else { + // Exclusive. + REFERENCE_TIME bufferDuration = bufferDurationInMicroseconds*10; + hr = IAudioClient_Initialize(pDevice->wasapi.pAudioClient, shareMode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); + if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { + UINT bufferSizeInFrames; + hr = IAudioClient_GetBufferSize(pDevice->wasapi.pAudioClient, &bufferSizeInFrames); + if (SUCCEEDED(hr)) { + bufferDuration = (REFERENCE_TIME)((10000.0 * 1000 / wf.Format.nSamplesPerSec * bufferSizeInFrames) + 0.5); + + // Unfortunately we need to release and re-acquire the audio client according to MSDN. Seems silly - why not just call IAudioClient_Initialize() again?! + IAudioClient_Release(pDevice->wasapi.pAudioClient); + + #ifdef MAL_WIN32_DESKTOP + hr = IMMDevice_Activate(pMMDevice, g_malIID_IAudioClient, CLSCTX_ALL, NULL, &pDevice->wasapi.pAudioClient); + #else + hr = pActivatedInterface->QueryInterface(g_malIID_IAudioClient, &pDevice->wasapi.pAudioClient); + #endif + + if (SUCCEEDED(hr)) { + hr = IAudioClient_Initialize(pDevice->wasapi.pAudioClient, shareMode, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, bufferDuration, bufferDuration, (WAVEFORMATEX*)&wf, NULL); + } + } + } + + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to initialize device.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + goto done; + } + } + + hr = IAudioClient_GetBufferSize(pDevice->wasapi.pAudioClient, &pDevice->bufferSizeInFrames); + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to get audio client's actual buffer size.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + goto done; + } + + if (type == mal_device_type_playback) { + hr = IAudioClient_GetService((IAudioClient*)pDevice->wasapi.pAudioClient, g_malIID_IAudioRenderClient, &pDevice->wasapi.pRenderClient); + } else { + hr = IAudioClient_GetService((IAudioClient*)pDevice->wasapi.pAudioClient, g_malIID_IAudioCaptureClient, &pDevice->wasapi.pCaptureClient); + } + + if (FAILED(hr)) { + errorMsg = "[WASAPI] Failed to get audio client service.", result = MAL_WASAPI_FAILED_TO_INITIALIZE_DEVICE; + goto done; + } + + + if (shareMode == AUDCLNT_SHAREMODE_SHARED) { + pDevice->exclusiveMode = MAL_FALSE; + } else /*if (shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)*/ { + pDevice->exclusiveMode = MAL_TRUE; + } + + + // We need to create and set the event for event-driven mode. This event is signalled whenever a new chunk of audio + // data needs to be written or read from the device. + pDevice->wasapi.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDevice->wasapi.hEvent == NULL) { + errorMsg = "[WASAPI] Failed to create main event for main loop.", result = MAL_FAILED_TO_CREATE_EVENT; + goto done; + } + + IAudioClient_SetEventHandle(pDevice->wasapi.pAudioClient, pDevice->wasapi.hEvent); + + + // When the device is playing the worker thread will be waiting on a bunch of notification events. To return from + // this wait state we need to signal a special event. + pDevice->wasapi.hStopEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDevice->wasapi.hStopEvent == NULL) { + errorMsg = "[WASAPI] Failed to create stop event for main loop break notification.", result = MAL_FAILED_TO_CREATE_EVENT; + goto done; + } + + result = MAL_SUCCESS; + +done: + // Clean up. +#ifdef MAL_WIN32_DESKTOP + if (pMMDevice != NULL) { + IMMDevice_Release(pMMDevice); + } +#else + if (pAsyncOp != NULL) { + IActivateAudioInterfaceAsyncOperation_Release(pAsyncOp); + } +#endif + + if (result != MAL_SUCCESS) { + mal_device_uninit__wasapi(pDevice); + return mal_post_error(pDevice, errorMsg, result); + } else { + return MAL_SUCCESS; + } +} + +static mal_result mal_device__start_backend__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // Playback devices need to have an initial chunk of data loaded. + if (pDevice->type == mal_device_type_playback) { + BYTE* pData; + HRESULT hr = IAudioRenderClient_GetBuffer(pDevice->wasapi.pRenderClient, pDevice->bufferSizeInFrames, &pData); + if (FAILED(hr)) { + return mal_post_error(pDevice, "[WASAPI] Failed to retrieve buffer from internal playback device.", MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER); + } + + mal_device__read_frames_from_client(pDevice, pDevice->bufferSizeInFrames, pData); + + hr = IAudioRenderClient_ReleaseBuffer(pDevice->wasapi.pRenderClient, pDevice->bufferSizeInFrames, 0); + if (FAILED(hr)) { + return mal_post_error(pDevice, "[WASAPI] Failed to release internal buffer for playback device.", MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER); + } + } + + HRESULT hr = IAudioClient_Start(pDevice->wasapi.pAudioClient); + if (FAILED(hr)) { + return mal_post_error(pDevice, "[WASAPI] Failed to start internal device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + HRESULT hr = IAudioClient_Stop(pDevice->wasapi.pAudioClient); + if (FAILED(hr)) { + return mal_post_error(pDevice, "[WASAPI] Failed to stop internal device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // The main loop will be waiting on a bunch of events via the WaitForMultipleObjects() API. One of those events + // is a special event we use for forcing that function to return. + pDevice->wasapi.breakFromMainLoop = MAL_TRUE; + SetEvent(pDevice->wasapi.hStopEvent); + return MAL_SUCCESS; +} + +static mal_uint32 mal_device__get_available_frames__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + +#if 1 + if (pDevice->type == mal_device_type_playback) { + UINT32 paddingFramesCount; + HRESULT hr = IAudioClient_GetCurrentPadding(pDevice->wasapi.pAudioClient, &paddingFramesCount); + if (FAILED(hr)) { + return 0; + } + + if (pDevice->exclusiveMode) { + return paddingFramesCount; + } else { + return pDevice->bufferSizeInFrames - paddingFramesCount; + } + } else { + UINT32 framesAvailable; + HRESULT hr = IAudioCaptureClient_GetNextPacketSize(pDevice->wasapi.pCaptureClient, &framesAvailable); + if (FAILED(hr)) { + return 0; + } + + return framesAvailable; + } +#else + UINT32 paddingFramesCount; + HRESULT hr = IAudioClient_GetCurrentPadding(pDevice->wasapi.pAudioClient, &paddingFramesCount); + if (FAILED(hr)) { + return 0; + } + + if (pDevice->exclusiveMode) { + return paddingFramesCount; + } else { + return pDevice->bufferSizeInFrames - paddingFramesCount; + } +#endif +} + +static mal_uint32 mal_device__wait_for_frames__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + while (!pDevice->wasapi.breakFromMainLoop) { + // Wait for a buffer to become available or for the stop event to be signalled. + HANDLE hEvents[2]; + hEvents[0] = (HANDLE)pDevice->wasapi.hEvent; + hEvents[1] = (HANDLE)pDevice->wasapi.hStopEvent; + if (WaitForMultipleObjects(mal_countof(hEvents), hEvents, FALSE, INFINITE) == WAIT_FAILED) { + break; + } + + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + mal_uint32 framesAvailable = mal_device__get_available_frames__wasapi(pDevice); + if (framesAvailable > 0) { + return framesAvailable; + } + } + + // We'll get here if the loop was terminated. Just return whatever's available. + return mal_device__get_available_frames__wasapi(pDevice); +} + +static mal_result mal_device__main_loop__wasapi(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // Make sure the stop event is not signaled to ensure we don't end up immediately returning from WaitForMultipleObjects(). + ResetEvent(pDevice->wasapi.hStopEvent); + + pDevice->wasapi.breakFromMainLoop = MAL_FALSE; + while (!pDevice->wasapi.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__wasapi(pDevice); + if (framesAvailable == 0) { + continue; + } + + // If it's a playback device, don't bother grabbing more data if the device is being stopped. + if (pDevice->wasapi.breakFromMainLoop && pDevice->type == mal_device_type_playback) { + return MAL_FALSE; + } + + if (pDevice->type == mal_device_type_playback) { + BYTE* pData; + HRESULT hr = IAudioRenderClient_GetBuffer(pDevice->wasapi.pRenderClient, framesAvailable, &pData); + if (FAILED(hr)) { + return mal_post_error(pDevice, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for sending new data to the device.", MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER); + } + + mal_device__read_frames_from_client(pDevice, framesAvailable, pData); + + hr = IAudioRenderClient_ReleaseBuffer(pDevice->wasapi.pRenderClient, framesAvailable, 0); + if (FAILED(hr)) { + return mal_post_error(pDevice, "[WASAPI] Failed to release internal buffer from playback device in preparation for sending new data to the device.", MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER); + } + } else { + UINT32 framesRemaining = framesAvailable; + while (framesRemaining > 0) { + BYTE* pData; + UINT32 framesToSend; + DWORD flags; + HRESULT hr = IAudioCaptureClient_GetBuffer(pDevice->wasapi.pCaptureClient, &pData, &framesToSend, &flags, NULL, NULL); + if (FAILED(hr)) { + mal_post_error(pDevice, "[WASAPI] WARNING: Failed to retrieve internal buffer from capture device in preparation for sending new data to the client.", MAL_WASAPI_FAILED_TO_GET_INTERNAL_BUFFER); + break; + } + + if (hr != AUDCLNT_S_BUFFER_EMPTY) { + mal_device__send_frames_to_client(pDevice, framesToSend, pData); + + hr = IAudioCaptureClient_ReleaseBuffer(pDevice->wasapi.pCaptureClient, framesToSend); + if (FAILED(hr)) { + mal_post_error(pDevice, "[WASAPI] WARNING: Failed to release internal buffer from capture device in preparation for sending new data to the client.", MAL_WASAPI_FAILED_TO_RELEASE_INTERNAL_BUFFER); + break; + } + + if (framesRemaining >= framesToSend) { + framesRemaining -= framesToSend; + } else { + framesRemaining = 0; + } + } + } + } + } + + return MAL_SUCCESS; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// +// DirectSound Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_DSOUND +#include + +#if 0 // MAL_GUID_NULL is not currently used, but leaving it here in case I need to add it back again. +static GUID MAL_GUID_NULL = {0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; +#endif +static GUID MAL_GUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; +static GUID MAL_GUID_IID_IDirectSoundCaptureBuffer = {0xb0210782, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; + +typedef HRESULT (WINAPI * mal_DirectSoundCreateProc)(const GUID* pcGuidDevice, LPDIRECTSOUND *ppDS8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * mal_DirectSoundEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); +typedef HRESULT (WINAPI * mal_DirectSoundCaptureCreateProc)(const GUID* pcGuidDevice, LPDIRECTSOUNDCAPTURE *ppDSC8, LPUNKNOWN pUnkOuter); +typedef HRESULT (WINAPI * mal_DirectSoundCaptureEnumerateAProc)(LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext); + +static HMODULE mal_open_dsound_dll() +{ + return LoadLibraryW(L"dsound.dll"); +} + +static void mal_close_dsound_dll(HMODULE hModule) +{ + FreeLibrary(hModule); +} + + +mal_result mal_context_init__dsound(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + (void)pContext; + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__dsound(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_dsound); + + (void)pContext; + return MAL_SUCCESS; +} + + +typedef struct +{ + mal_uint32 deviceCount; + mal_uint32 infoCount; + mal_device_info* pInfo; +} mal_device_enum_data__dsound; + +static BOOL CALLBACK mal_enum_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) +{ + (void)lpcstrModule; + + mal_device_enum_data__dsound* pData = (mal_device_enum_data__dsound*)lpContext; + mal_assert(pData != NULL); + + if (pData->pInfo != NULL) { + if (pData->infoCount > 0) { + mal_zero_object(pData->pInfo); + mal_strncpy_s(pData->pInfo->name, sizeof(pData->pInfo->name), lpcstrDescription, (size_t)-1); + + if (lpGuid != NULL) { + mal_copy_memory(pData->pInfo->id.dsound, lpGuid, 16); + } else { + mal_zero_memory(pData->pInfo->id.dsound, 16); + } + + pData->pInfo += 1; + pData->infoCount -= 1; + pData->deviceCount += 1; + } + } else { + pData->deviceCount += 1; + } + + return TRUE; +} + +static mal_result mal_enumerate_devices__dsound(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + mal_uint32 infoSize = *pCount; + *pCount = 0; + + mal_device_enum_data__dsound enumData; + enumData.deviceCount = 0; + enumData.infoCount = infoSize; + enumData.pInfo = pInfo; + + HMODULE dsoundDLL = mal_open_dsound_dll(); + if (dsoundDLL == NULL) { + return MAL_NO_BACKEND; + } + + if (type == mal_device_type_playback) { + mal_DirectSoundEnumerateAProc pDirectSoundEnumerateA = (mal_DirectSoundEnumerateAProc)GetProcAddress(dsoundDLL, "DirectSoundEnumerateA"); + if (pDirectSoundEnumerateA) { + pDirectSoundEnumerateA(mal_enum_devices_callback__dsound, &enumData); + } + } else { + mal_DirectSoundCaptureEnumerateAProc pDirectSoundCaptureEnumerateA = (mal_DirectSoundCaptureEnumerateAProc)GetProcAddress(dsoundDLL, "DirectSoundCaptureEnumerateA"); + if (pDirectSoundCaptureEnumerateA) { + pDirectSoundCaptureEnumerateA(mal_enum_devices_callback__dsound, &enumData); + } + } + + + mal_close_dsound_dll(dsoundDLL); + + *pCount = enumData.deviceCount; + return MAL_SUCCESS; +} + +static void mal_device_uninit__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->dsound.hDSoundDLL != NULL) { + if (pDevice->dsound.pNotify) { + IDirectSoundNotify_Release((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify); + } + + if (pDevice->dsound.hStopEvent) { + CloseHandle(pDevice->dsound.hStopEvent); + } + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + if (pDevice->dsound.pNotifyEvents[i]) { + CloseHandle(pDevice->dsound.pNotifyEvents[i]); + } + } + + if (pDevice->dsound.pCaptureBuffer) { + IDirectSoundCaptureBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pCaptureBuffer); + } + if (pDevice->dsound.pCapture) { + IDirectSoundCapture_Release((LPDIRECTSOUNDCAPTURE)pDevice->dsound.pCapture); + } + + if (pDevice->dsound.pPlaybackBuffer) { + IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer); + } + if (pDevice->dsound.pPlaybackPrimaryBuffer) { + IDirectSoundBuffer_Release((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer); + } + if (pDevice->dsound.pPlayback != NULL) { + IDirectSound_Release((LPDIRECTSOUND)pDevice->dsound.pPlayback); + } + + mal_close_dsound_dll((HMODULE)pDevice->dsound.hDSoundDLL); + } +} + +static mal_result mal_device_init__dsound(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + +#ifdef __cplusplus + GUID _MAL_GUID_IID_DirectSoundNotify = MAL_GUID_IID_DirectSoundNotify; + GUID _MAL_GUID_IID_IDirectSoundCaptureBuffer = MAL_GUID_IID_IDirectSoundCaptureBuffer; +#else + GUID* _MAL_GUID_IID_DirectSoundNotify = &MAL_GUID_IID_DirectSoundNotify; + GUID* _MAL_GUID_IID_IDirectSoundCaptureBuffer = &MAL_GUID_IID_IDirectSoundCaptureBuffer; +#endif + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->dsound); + + pDevice->dsound.hDSoundDLL = (mal_handle)mal_open_dsound_dll(); + if (pDevice->dsound.hDSoundDLL == NULL) { + return MAL_NO_BACKEND; + } + + // Check that we have a valid format. + GUID subformat; + switch (pConfig->format) + { + case mal_format_u8: + case mal_format_s16: + case mal_format_s24: + case mal_format_s32: + { + subformat = MAL_GUID_KSDATAFORMAT_SUBTYPE_PCM; + } break; + + case mal_format_f32: + { + subformat = MAL_GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } break; + + default: + return MAL_FORMAT_NOT_SUPPORTED; + } + + + WAVEFORMATEXTENSIBLE wf; + mal_zero_object(&wf); + wf.Format.cbSize = sizeof(wf); + wf.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wf.Format.nChannels = (WORD)pConfig->channels; + wf.Format.nSamplesPerSec = (DWORD)pConfig->sampleRate; + wf.Format.wBitsPerSample = (WORD)mal_get_sample_size_in_bytes(pConfig->format)*8; + wf.Format.nBlockAlign = (wf.Format.nChannels * wf.Format.wBitsPerSample) / 8; + wf.Format.nAvgBytesPerSec = wf.Format.nBlockAlign * wf.Format.nSamplesPerSec; + wf.Samples.wValidBitsPerSample = wf.Format.wBitsPerSample; + wf.dwChannelMask = mal_channel_map_to_channel_mask__win32(pConfig->channelMap, pConfig->channels); + wf.SubFormat = subformat; + + DWORD bufferSizeInBytes = 0; + + // Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices :( + if (type == mal_device_type_playback) { + mal_DirectSoundCreateProc pDirectSoundCreate = (mal_DirectSoundCreateProc)GetProcAddress((HMODULE)pDevice->dsound.hDSoundDLL, "DirectSoundCreate"); + if (pDirectSoundCreate == NULL) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Could not find DirectSoundCreate().", MAL_API_NOT_FOUND); + } + + if (FAILED(pDirectSoundCreate((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, (LPDIRECTSOUND*)&pDevice->dsound.pPlayback, NULL))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] DirectSoundCreate() failed for playback device.", MAL_DSOUND_FAILED_TO_CREATE_DEVICE); + } + + // The cooperative level must be set before doing anything else. + HWND hWnd = ((MAL_PFN_GetForegroundWindow)pContext->win32.GetForegroundWindow)(); + if (hWnd == NULL) { + hWnd = ((MAL_PFN_GetDesktopWindow)pContext->win32.GetDesktopWindow)(); + } + if (FAILED(IDirectSound_SetCooperativeLevel((LPDIRECTSOUND)pDevice->dsound.pPlayback, hWnd, (pConfig->preferExclusiveMode) ? DSSCL_EXCLUSIVE : DSSCL_PRIORITY))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_SetCooperateiveLevel() failed for playback device.", MAL_DSOUND_FAILED_TO_SET_COOP_LEVEL); + } + + DSBUFFERDESC descDSPrimary; + mal_zero_object(&descDSPrimary); + descDSPrimary.dwSize = sizeof(DSBUFFERDESC); + descDSPrimary.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME; + if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND)pDevice->dsound.pPlayback, &descDSPrimary, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackPrimaryBuffer, NULL))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's primary buffer.", MAL_DSOUND_FAILED_TO_CREATE_BUFFER); + } + + // From MSDN: + // + // The method succeeds even if the hardware does not support the requested format; DirectSound sets the buffer to the closest + // supported format. To determine whether this has happened, an application can call the GetFormat method for the primary buffer + // and compare the result with the format that was requested with the SetFormat method. + if (FAILED(IDirectSoundBuffer_SetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)&wf))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Failed to set format of playback device's primary buffer.", MAL_FORMAT_NOT_SUPPORTED); + } + + // Get the _actual_ properties of the buffer. This is silly API design... + DWORD requiredSize; + if (FAILED(IDirectSoundBuffer_GetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, NULL, 0, &requiredSize))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", MAL_FORMAT_NOT_SUPPORTED); + } + + char rawdata[1024]; + WAVEFORMATEXTENSIBLE* pActualFormat = (WAVEFORMATEXTENSIBLE*)rawdata; + if (FAILED(IDirectSoundBuffer_GetFormat((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackPrimaryBuffer, (WAVEFORMATEX*)pActualFormat, requiredSize, NULL))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", MAL_FORMAT_NOT_SUPPORTED); + } + + pDevice->internalChannels = pActualFormat->Format.nChannels; + pDevice->internalSampleRate = pActualFormat->Format.nSamplesPerSec; + bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->format); + + // Get the internal channel map based on the channel mask. + mal_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDevice->internalChannels, pDevice->internalChannelMap); + + + // Meaning of dwFlags (from MSDN): + // + // DSBCAPS_CTRLPOSITIONNOTIFY + // The buffer has position notification capability. + // + // DSBCAPS_GLOBALFOCUS + // With this flag set, an application using DirectSound can continue to play its buffers if the user switches focus to + // another application, even if the new application uses DirectSound. + // + // DSBCAPS_GETCURRENTPOSITION2 + // In the first version of DirectSound, the play cursor was significantly ahead of the actual playing sound on emulated + // sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the + // application can get a more accurate play cursor. + DSBUFFERDESC descDS; + mal_zero_object(&descDS); + descDS.dwSize = sizeof(DSBUFFERDESC); + descDS.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2; + descDS.dwBufferBytes = bufferSizeInBytes; + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + if (FAILED(IDirectSound_CreateSoundBuffer((LPDIRECTSOUND)pDevice->dsound.pPlayback, &descDS, (LPDIRECTSOUNDBUFFER*)&pDevice->dsound.pPlaybackBuffer, NULL))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSound_CreateSoundBuffer() failed for playback device's secondary buffer.", MAL_DSOUND_FAILED_TO_CREATE_BUFFER); + } + + // Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer. + if (FAILED(IDirectSoundBuffer_QueryInterface((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, _MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_QueryInterface() failed for playback device's IDirectSoundNotify object.", MAL_DSOUND_FAILED_TO_QUERY_INTERFACE); + } + } else { + // The default buffer size is treated slightly differently for DirectSound which, for some reason, seems to + // have worse latency with capture than playback (sometimes _much_ worse). + if (pDevice->usingDefaultBufferSize) { + pDevice->bufferSizeInFrames *= 2; // <-- Might need to fiddle with this to find a more ideal value. May even be able to just add a fixed amount rather than scaling. + } + + mal_DirectSoundCaptureCreateProc pDirectSoundCaptureCreate = (mal_DirectSoundCaptureCreateProc)GetProcAddress((HMODULE)pDevice->dsound.hDSoundDLL, "DirectSoundCaptureCreate"); + if (pDirectSoundCaptureCreate == NULL) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Could not find DirectSoundCreate().", MAL_API_NOT_FOUND); + } + + if (FAILED(pDirectSoundCaptureCreate((pDeviceID == NULL) ? NULL : (const GUID*)pDeviceID->dsound, (LPDIRECTSOUNDCAPTURE*)&pDevice->dsound.pCapture, NULL))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] DirectSoundCaptureCreate() failed for capture device.", MAL_DSOUND_FAILED_TO_CREATE_DEVICE); + } + + bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + + DSCBUFFERDESC descDS; + mal_zero_object(&descDS); + descDS.dwSize = sizeof(descDS); + descDS.dwFlags = 0; + descDS.dwBufferBytes = bufferSizeInBytes; + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + LPDIRECTSOUNDCAPTUREBUFFER pDSCB_Temp; + if (FAILED(IDirectSoundCapture_CreateCaptureBuffer((LPDIRECTSOUNDCAPTURE)pDevice->dsound.pCapture, &descDS, &pDSCB_Temp, NULL))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCapture_CreateCaptureBuffer() failed for capture device.", MAL_DSOUND_FAILED_TO_CREATE_BUFFER); + } + + HRESULT hr = IDirectSoundCapture_QueryInterface(pDSCB_Temp, _MAL_GUID_IID_IDirectSoundCaptureBuffer, (LPVOID*)&pDevice->dsound.pCaptureBuffer); + IDirectSoundCaptureBuffer_Release(pDSCB_Temp); + if (FAILED(hr)) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCapture_QueryInterface() failed for capture device's IDirectSoundCaptureBuffer8 object.", MAL_DSOUND_FAILED_TO_QUERY_INTERFACE); + } + + // Notifications are set up via a DIRECTSOUNDNOTIFY object which is retrieved from the buffer. + if (FAILED(IDirectSoundCaptureBuffer_QueryInterface((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, _MAL_GUID_IID_DirectSoundNotify, (void**)&pDevice->dsound.pNotify))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_QueryInterface() failed for capture device's IDirectSoundNotify object.", MAL_DSOUND_FAILED_TO_QUERY_INTERFACE); + } + } + + // We need a notification for each period. The notification offset is slightly different depending on whether or not the + // device is a playback or capture device. For a playback device we want to be notified when a period just starts playing, + // whereas for a capture device we want to be notified when a period has just _finished_ capturing. + mal_uint32 periodSizeInBytes = pDevice->bufferSizeInFrames / pDevice->periods; + DSBPOSITIONNOTIFY notifyPoints[MAL_MAX_PERIODS_DSOUND]; // One notification event for each period. + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + pDevice->dsound.pNotifyEvents[i] = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDevice->dsound.pNotifyEvents[i] == NULL) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Failed to create event for buffer notifications.", MAL_FAILED_TO_CREATE_EVENT); + } + + // The notification offset is in bytes. + notifyPoints[i].dwOffset = i * periodSizeInBytes; + notifyPoints[i].hEventNotify = pDevice->dsound.pNotifyEvents[i]; + } + + if (FAILED(IDirectSoundNotify_SetNotificationPositions((LPDIRECTSOUNDNOTIFY)pDevice->dsound.pNotify, pDevice->periods, notifyPoints))) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] IDirectSoundNotify_SetNotificationPositions() failed.", MAL_DSOUND_FAILED_TO_SET_NOTIFICATIONS); + } + + // When the device is playing the worker thread will be waiting on a bunch of notification events. To return from + // this wait state we need to signal a special event. + pDevice->dsound.hStopEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (pDevice->dsound.hStopEvent == NULL) { + mal_device_uninit__dsound(pDevice); + return mal_post_error(pDevice, "[DirectSound] Failed to create event for main loop break notification.", MAL_FAILED_TO_CREATE_EVENT); + } + + return MAL_SUCCESS; +} + + +static mal_result mal_device__start_backend__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + // Before playing anything we need to grab an initial group of samples from the client. + mal_uint32 framesToRead = pDevice->bufferSizeInFrames / pDevice->periods; + mal_uint32 desiredLockSize = framesToRead * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + + void* pLockPtr; + DWORD actualLockSize; + void* pLockPtr2; + DWORD actualLockSize2; + if (SUCCEEDED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, desiredLockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { + framesToRead = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + mal_device__read_frames_from_client(pDevice, framesToRead, pLockPtr); + IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); + + pDevice->dsound.lastProcessedFrame = framesToRead; + if (FAILED(IDirectSoundBuffer_Play((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0, 0, DSBPLAY_LOOPING))) { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Play() failed.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } else { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Lock() failed.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); + } + } else { + if (FAILED(IDirectSoundCaptureBuffer_Start((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, DSCBSTART_LOOPING))) { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + if (FAILED(IDirectSoundBuffer_Stop((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer))) { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Stop() failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + + IDirectSoundBuffer_SetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, 0); + } else { + if (FAILED(IDirectSoundCaptureBuffer_Stop((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer))) { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_Stop() failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // The main loop will be waiting on a bunch of events via the WaitForMultipleObjects() API. One of those events + // is a special event we use for forcing that function to return. + pDevice->dsound.breakFromMainLoop = MAL_TRUE; + SetEvent(pDevice->dsound.hStopEvent); + return MAL_SUCCESS; +} + +static mal_bool32 mal_device__get_current_frame__dsound(mal_device* pDevice, mal_uint32* pCurrentPos) +{ + mal_assert(pDevice != NULL); + mal_assert(pCurrentPos != NULL); + *pCurrentPos = 0; + + DWORD dwCurrentPosition; + if (pDevice->type == mal_device_type_playback) { + if (FAILED(IDirectSoundBuffer_GetCurrentPosition((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, NULL, &dwCurrentPosition))) { + return MAL_FALSE; + } + } else { + if (FAILED(IDirectSoundCaptureBuffer_GetCurrentPosition((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, &dwCurrentPosition, NULL))) { + return MAL_FALSE; + } + } + + *pCurrentPos = (mal_uint32)dwCurrentPosition / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + return MAL_TRUE; +} + +static mal_uint32 mal_device__get_available_frames__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_uint32 currentFrame; + if (!mal_device__get_current_frame__dsound(pDevice, ¤tFrame)) { + return 0; + } + + // In a playback device the last processed frame should always be ahead of the current frame. The space between + // the last processed and current frame (moving forward, starting from the last processed frame) is the amount + // of space available to write. + // + // For a recording device it's the other way around - the last processed frame is always _behind_ the current + // frame and the space between is the available space. + mal_uint32 totalFrameCount = pDevice->bufferSizeInFrames; + if (pDevice->type == mal_device_type_playback) { + mal_uint32 committedBeg = currentFrame; + mal_uint32 committedEnd; + committedEnd = pDevice->dsound.lastProcessedFrame; + if (committedEnd <= committedBeg) { + committedEnd += totalFrameCount; + } + + mal_uint32 committedSize = (committedEnd - committedBeg); + mal_assert(committedSize <= totalFrameCount); + + return totalFrameCount - committedSize; + } else { + mal_uint32 validBeg = pDevice->dsound.lastProcessedFrame; + mal_uint32 validEnd = currentFrame; + if (validEnd < validBeg) { + validEnd += totalFrameCount; // Wrap around. + } + + mal_uint32 validSize = (validEnd - validBeg); + mal_assert(validSize <= totalFrameCount); + + return validSize; + } +} + +static mal_uint32 mal_device__wait_for_frames__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // The timeout to use for putting the thread to sleep is based on the size of the buffer and the period count. + DWORD timeoutInMilliseconds = (pDevice->bufferSizeInFrames / (pDevice->sampleRate/1000)) / pDevice->periods; + if (timeoutInMilliseconds < 1) { + timeoutInMilliseconds = 1; + } + + unsigned int eventCount = pDevice->periods + 1; + HANDLE pEvents[MAL_MAX_PERIODS_DSOUND + 1]; // +1 for the stop event. + mal_copy_memory(pEvents, pDevice->dsound.pNotifyEvents, sizeof(HANDLE) * pDevice->periods); + pEvents[eventCount-1] = pDevice->dsound.hStopEvent; + + while (!pDevice->dsound.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__get_available_frames__dsound(pDevice); + if (framesAvailable > 0) { + return framesAvailable; + } + + // If we get here it means we weren't able to find any frames. We'll just wait here for a bit. + WaitForMultipleObjects(eventCount, pEvents, FALSE, timeoutInMilliseconds); + } + + // We'll get here if the loop was terminated. Just return whatever's available. + return mal_device__get_available_frames__dsound(pDevice); +} + +static mal_result mal_device__main_loop__dsound(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // Make sure the stop event is not signaled to ensure we don't end up immediately returning from WaitForMultipleObjects(). + ResetEvent(pDevice->dsound.hStopEvent); + + pDevice->dsound.breakFromMainLoop = MAL_FALSE; + while (!pDevice->dsound.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__dsound(pDevice); + if (framesAvailable == 0) { + continue; + } + + // If it's a playback device, don't bother grabbing more data if the device is being stopped. + if (pDevice->dsound.breakFromMainLoop && pDevice->type == mal_device_type_playback) { + return MAL_FALSE; + } + + DWORD lockOffset = pDevice->dsound.lastProcessedFrame * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + DWORD lockSize = framesAvailable * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); + + if (pDevice->type == mal_device_type_playback) { + void* pLockPtr; + DWORD actualLockSize; + void* pLockPtr2; + DWORD actualLockSize2; + if (FAILED(IDirectSoundBuffer_Lock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, lockOffset, lockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundBuffer_Lock() failed.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); + } + + mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + mal_device__read_frames_from_client(pDevice, frameCount, pLockPtr); + pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % pDevice->bufferSizeInFrames; + + IDirectSoundBuffer_Unlock((LPDIRECTSOUNDBUFFER)pDevice->dsound.pPlaybackBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); + } else { + void* pLockPtr; + DWORD actualLockSize; + void* pLockPtr2; + DWORD actualLockSize2; + if (FAILED(IDirectSoundCaptureBuffer_Lock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, lockOffset, lockSize, &pLockPtr, &actualLockSize, &pLockPtr2, &actualLockSize2, 0))) { + return mal_post_error(pDevice, "[DirectSound] IDirectSoundCaptureBuffer_Lock() failed.", MAL_FAILED_TO_MAP_DEVICE_BUFFER); + } + + mal_uint32 frameCount = actualLockSize / mal_get_sample_size_in_bytes(pDevice->format) / pDevice->channels; + mal_device__send_frames_to_client(pDevice, frameCount, pLockPtr); + pDevice->dsound.lastProcessedFrame = (pDevice->dsound.lastProcessedFrame + frameCount) % pDevice->bufferSizeInFrames; + + IDirectSoundCaptureBuffer_Unlock((LPDIRECTSOUNDCAPTUREBUFFER)pDevice->dsound.pCaptureBuffer, pLockPtr, actualLockSize, pLockPtr2, actualLockSize2); + } + } + + return MAL_SUCCESS; +} +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// +// WinMM Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_WINMM +#include + +#if !defined(MAXULONG_PTR) +typedef size_t DWORD_PTR; +#endif + +#if !defined(WAVE_FORMAT_44M08) +#define WAVE_FORMAT_44M08 0x00000100 +#define WAVE_FORMAT_44S08 0x00000200 +#define WAVE_FORMAT_44M16 0x00000400 +#define WAVE_FORMAT_44S16 0x00000800 +#define WAVE_FORMAT_48M08 0x00001000 +#define WAVE_FORMAT_48S08 0x00002000 +#define WAVE_FORMAT_48M16 0x00004000 +#define WAVE_FORMAT_48S16 0x00008000 +#define WAVE_FORMAT_96M08 0x00010000 +#define WAVE_FORMAT_96S08 0x00020000 +#define WAVE_FORMAT_96M16 0x00040000 +#define WAVE_FORMAT_96S16 0x00080000 +#endif + +typedef UINT (WINAPI * MAL_PFN_waveOutGetNumDevs)(void); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutGetDevCapsA)(UINT_PTR uDeviceID, LPWAVEOUTCAPSA pwoc, UINT cbwoc); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutOpen)(LPHWAVEOUT phwo, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutClose)(HWAVEOUT hwo); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutPrepareHeader)(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutUnprepareHeader)(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutWrite)(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MAL_PFN_waveOutReset)(HWAVEOUT hwo); +typedef UINT (WINAPI * MAL_PFN_waveInGetNumDevs)(void); +typedef MMRESULT (WINAPI * MAL_PFN_waveInGetDevCapsA)(UINT_PTR uDeviceID, LPWAVEINCAPSA pwic, UINT cbwic); +typedef MMRESULT (WINAPI * MAL_PFN_waveInOpen)(LPHWAVEIN phwi, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWORD_PTR dwCallback, DWORD_PTR dwInstance, DWORD fdwOpen); +typedef MMRESULT (WINAPI * MAL_PFN_waveInClose)(HWAVEIN hwi); +typedef MMRESULT (WINAPI * MAL_PFN_waveInPrepareHeader)(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MAL_PFN_waveInUnprepareHeader)(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MAL_PFN_waveInAddBuffer)(HWAVEIN hwi, LPWAVEHDR pwh, UINT cbwh); +typedef MMRESULT (WINAPI * MAL_PFN_waveInStart)(HWAVEIN hwi); +typedef MMRESULT (WINAPI * MAL_PFN_waveInReset)(HWAVEIN hwi); + +mal_result mal_result_from_MMRESULT(MMRESULT resultMM) +{ + switch (resultMM) { + case MMSYSERR_NOERROR: return MAL_SUCCESS; + case MMSYSERR_BADDEVICEID: return MAL_INVALID_ARGS; + case MMSYSERR_INVALHANDLE: return MAL_INVALID_ARGS; + case MMSYSERR_NOMEM: return MAL_OUT_OF_MEMORY; + case MMSYSERR_INVALFLAG: return MAL_INVALID_ARGS; + case MMSYSERR_INVALPARAM: return MAL_INVALID_ARGS; + case MMSYSERR_HANDLEBUSY: return MAL_DEVICE_BUSY; + case MMSYSERR_ERROR: return MAL_ERROR; + default: return MAL_ERROR; + } +} + +mal_result mal_context_init__winmm(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + pContext->winmm.hWinMM = mal_dlopen("winmm.dll"); + if (pContext->winmm.hWinMM == NULL) { + return MAL_NO_BACKEND; + } + + pContext->winmm.waveOutGetNumDevs = mal_dlsym(pContext->winmm.hWinMM, "waveOutGetNumDevs"); + pContext->winmm.waveOutGetDevCapsA = mal_dlsym(pContext->winmm.hWinMM, "waveOutGetDevCapsA"); + pContext->winmm.waveOutOpen = mal_dlsym(pContext->winmm.hWinMM, "waveOutOpen"); + pContext->winmm.waveOutClose = mal_dlsym(pContext->winmm.hWinMM, "waveOutClose"); + pContext->winmm.waveOutPrepareHeader = mal_dlsym(pContext->winmm.hWinMM, "waveOutPrepareHeader"); + pContext->winmm.waveOutUnprepareHeader = mal_dlsym(pContext->winmm.hWinMM, "waveOutUnprepareHeader"); + pContext->winmm.waveOutWrite = mal_dlsym(pContext->winmm.hWinMM, "waveOutWrite"); + pContext->winmm.waveOutReset = mal_dlsym(pContext->winmm.hWinMM, "waveOutReset"); + pContext->winmm.waveInGetNumDevs = mal_dlsym(pContext->winmm.hWinMM, "waveInGetNumDevs"); + pContext->winmm.waveInGetDevCapsA = mal_dlsym(pContext->winmm.hWinMM, "waveInGetDevCapsA"); + pContext->winmm.waveInOpen = mal_dlsym(pContext->winmm.hWinMM, "waveInOpen"); + pContext->winmm.waveInClose = mal_dlsym(pContext->winmm.hWinMM, "waveInClose"); + pContext->winmm.waveInPrepareHeader = mal_dlsym(pContext->winmm.hWinMM, "waveInPrepareHeader"); + pContext->winmm.waveInUnprepareHeader = mal_dlsym(pContext->winmm.hWinMM, "waveInUnprepareHeader"); + pContext->winmm.waveInAddBuffer = mal_dlsym(pContext->winmm.hWinMM, "waveInAddBuffer"); + pContext->winmm.waveInStart = mal_dlsym(pContext->winmm.hWinMM, "waveInStart"); + pContext->winmm.waveInReset = mal_dlsym(pContext->winmm.hWinMM, "waveInReset"); + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__winmm(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_winmm); + + mal_dlclose(pContext->winmm.hWinMM); + return MAL_SUCCESS; +} + +static mal_result mal_enumerate_devices__winmm(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + mal_uint32 infoSize = *pCount; + *pCount = 0; + + if (type == mal_device_type_playback) { + UINT deviceCount = ((MAL_PFN_waveOutGetNumDevs)pContext->winmm.waveOutGetNumDevs)(); + for (UINT iDevice = 0; iDevice < deviceCount; ++iDevice) { + if (pInfo != NULL) { + if (infoSize > 0) { + WAVEOUTCAPSA caps; + MMRESULT result = ((MAL_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(iDevice, &caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + pInfo->id.winmm = iDevice; + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), caps.szPname, (size_t)-1); + } + + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + } + } else { + UINT deviceCount = ((MAL_PFN_waveInGetNumDevs)pContext->winmm.waveInGetNumDevs)(); + for (UINT iDevice = 0; iDevice < deviceCount; ++iDevice) { + if (pInfo != NULL) { + if (infoSize > 0) { + WAVEINCAPSA caps; + MMRESULT result = ((MAL_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(iDevice, &caps, sizeof(caps)); + if (result == MMSYSERR_NOERROR) { + pInfo->id.winmm = iDevice; + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), caps.szPname, (size_t)-1); + } + + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + } + } + + return MAL_SUCCESS; +} + +static void mal_device_uninit__winmm(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + ((MAL_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevice); + } else { + ((MAL_PFN_waveInClose)pDevice->pContext->winmm.waveInClose)((HWAVEIN)pDevice->winmm.hDevice); + } + + mal_free(pDevice->winmm._pHeapData); + CloseHandle((HANDLE)pDevice->winmm.hEvent); +} + +static mal_result mal_device_init__winmm(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_uint32 heapSize; + mal_uint32 iBit; + + WORD closestBitsPerSample = 0; + WORD closestChannels = 0; + DWORD closestSampleRate = 0; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->winmm); + + UINT winMMDeviceID = 0; + if (pDeviceID != NULL) { + winMMDeviceID = (UINT)pDeviceID->winmm; + } + + const char* errorMsg = ""; + mal_result errorCode = MAL_ERROR; + + + // WinMM doesn't seem to have a good way to query the format of the device. Therefore, we'll restrict the formats to the + // standard formats documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd743855(v=vs.85).aspx. If + // that link goes stale, just look up the documentation for WAVEOUTCAPS or WAVEINCAPS. + WAVEFORMATEX wf; + mal_zero_object(&wf); + wf.cbSize = sizeof(wf); + wf.wFormatTag = WAVE_FORMAT_PCM; + wf.nChannels = (WORD)pConfig->channels; + wf.nSamplesPerSec = (DWORD)pConfig->sampleRate; + wf.wBitsPerSample = (WORD)mal_get_sample_size_in_bytes(pConfig->format)*8; + + if (wf.nChannels > 2) { + wf.nChannels = 2; + } + + if (wf.wBitsPerSample != 8 && wf.wBitsPerSample != 16) { + if (wf.wBitsPerSample <= 8) { + wf.wBitsPerSample = 8; + } else { + wf.wBitsPerSample = 16; + } + } + + if (wf.nSamplesPerSec <= 11025) { + wf.nSamplesPerSec = 11025; + } else if (wf.nSamplesPerSec <= 22050) { + wf.nSamplesPerSec = 22050; + } else if (wf.nSamplesPerSec <= 44100) { + wf.nSamplesPerSec = 44100; + } else if (wf.nSamplesPerSec <= 48000) { + wf.nSamplesPerSec = 48000; + } else { + wf.nSamplesPerSec = 96000; + } + + + // Change the format based on the closest match of the supported standard formats. + DWORD dwFormats = 0; + if (type == mal_device_type_playback) { + WAVEOUTCAPSA caps; + if (((MAL_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(winMMDeviceID, &caps, sizeof(caps)) == MMSYSERR_NOERROR) { + dwFormats = caps.dwFormats; + } else { + errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MAL_WINMM_FAILED_TO_GET_DEVICE_CAPS; + goto on_error; + } + } else { + WAVEINCAPSA caps; + if (((MAL_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(winMMDeviceID, &caps, sizeof(caps)) == MMSYSERR_NOERROR) { + dwFormats = caps.dwFormats; + } else { + errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MAL_WINMM_FAILED_TO_GET_DEVICE_CAPS; + goto on_error; + } + } + + if (dwFormats == 0) { + errorMsg = "[WinMM] Failed to retrieve the supported formats for the internal device.", errorCode = MAL_WINMM_FAILED_TO_GET_SUPPORTED_FORMATS; + goto on_error; + } + + for (iBit = 0; iBit < 32; ++iBit) { + WORD formatBitsPerSample = 0; + WORD formatChannels = 0; + DWORD formatSampleRate = 0; + + DWORD format = (dwFormats & (1 << iBit)); + if (format != 0) { + switch (format) + { + case WAVE_FORMAT_1M08: + { + formatBitsPerSample = 8; + formatChannels = 1; + formatSampleRate = 110025; + } break; + case WAVE_FORMAT_1M16: + { + formatBitsPerSample = 16; + formatChannels = 1; + formatSampleRate = 110025; + } break; + case WAVE_FORMAT_1S08: + { + formatBitsPerSample = 8; + formatChannels = 2; + formatSampleRate = 110025; + } break; + case WAVE_FORMAT_1S16: + { + formatBitsPerSample = 16; + formatChannels = 2; + formatSampleRate = 110025; + } break; + case WAVE_FORMAT_2M08: + { + formatBitsPerSample = 8; + formatChannels = 1; + formatSampleRate = 22050; + } break; + case WAVE_FORMAT_2M16: + { + formatBitsPerSample = 16; + formatChannels = 1; + formatSampleRate = 22050; + } break; + case WAVE_FORMAT_2S08: + { + formatBitsPerSample = 8; + formatChannels = 2; + formatSampleRate = 22050; + } break; + case WAVE_FORMAT_2S16: + { + formatBitsPerSample = 16; + formatChannels = 2; + formatSampleRate = 22050; + } break; + case WAVE_FORMAT_44M08: + { + formatBitsPerSample = 8; + formatChannels = 1; + formatSampleRate = 44100; + } break; + case WAVE_FORMAT_44M16: + { + formatBitsPerSample = 16; + formatChannels = 1; + formatSampleRate = 44100; + } break; + case WAVE_FORMAT_44S08: + { + formatBitsPerSample = 8; + formatChannels = 2; + formatSampleRate = 44100; + } break; + case WAVE_FORMAT_44S16: + { + formatBitsPerSample = 16; + formatChannels = 2; + formatSampleRate = 44100; + } break; + case WAVE_FORMAT_48M08: + { + formatBitsPerSample = 8; + formatChannels = 1; + formatSampleRate = 48000; + } break; + case WAVE_FORMAT_48M16: + { + formatBitsPerSample = 16; + formatChannels = 1; + formatSampleRate = 48000; + } break; + case WAVE_FORMAT_48S08: + { + formatBitsPerSample = 8; + formatChannels = 2; + formatSampleRate = 48000; + } break; + case WAVE_FORMAT_48S16: + { + formatBitsPerSample = 16; + formatChannels = 2; + formatSampleRate = 48000; + } break; + case WAVE_FORMAT_96M08: + { + formatBitsPerSample = 8; + formatChannels = 1; + formatSampleRate = 96000; + } break; + case WAVE_FORMAT_96M16: + { + formatBitsPerSample = 16; + formatChannels = 1; + formatSampleRate = 96000; + } break; + case WAVE_FORMAT_96S08: + { + formatBitsPerSample = 8; + formatChannels = 2; + formatSampleRate = 96000; + } break; + case WAVE_FORMAT_96S16: + { + formatBitsPerSample = 16; + formatChannels = 2; + formatSampleRate = 96000; + } break; + default: + { + errorMsg = "[WinMM] The internal device does not support any of the standard formats.", errorCode = MAL_ERROR; // <-- Should never hit this. + goto on_error; + } break; + } + + if (formatBitsPerSample == wf.wBitsPerSample && formatChannels == wf.nChannels && formatSampleRate == wf.nSamplesPerSec) { + break; // It's an exact match. + } else { + // It's not an exact match. Compare it with the closest match. + if (closestBitsPerSample == 0) { + // This is the first format, so nothing to compare against. + closestBitsPerSample = formatBitsPerSample; + closestChannels = formatChannels; + closestSampleRate = formatSampleRate; + } else { + // Prefer the channel count be the same over the others. + if (formatChannels != closestChannels) { + // Channels aren't equal. Favour the one equal to our desired channel count. + if (formatChannels == wf.nChannels) { + closestBitsPerSample = formatBitsPerSample; + closestChannels = formatChannels; + closestSampleRate = formatSampleRate; + } + } else { + // The channels are equal. Look at the format now. + if (formatBitsPerSample != closestBitsPerSample) { + if (formatBitsPerSample == wf.wBitsPerSample) { + closestBitsPerSample = formatBitsPerSample; + closestChannels = formatChannels; + closestSampleRate = formatSampleRate; + } + } else { + // Both the channels and formats are the same, so now just favour whichever's sample rate is closest to the requested rate. + mal_uint32 closestRateDiff = (closestSampleRate > wf.nSamplesPerSec) ? (closestSampleRate - wf.nSamplesPerSec) : (wf.nSamplesPerSec - closestSampleRate); + mal_uint32 formatRateDiff = (formatSampleRate > wf.nSamplesPerSec) ? (formatSampleRate - wf.nSamplesPerSec) : (wf.nSamplesPerSec - formatSampleRate); + if (formatRateDiff < closestRateDiff) { + closestBitsPerSample = formatBitsPerSample; + closestChannels = formatChannels; + closestSampleRate = formatSampleRate; + } + } + } + } + } + } + } + + wf.wBitsPerSample = closestBitsPerSample; + wf.nChannels = closestChannels; + wf.nSamplesPerSec = closestSampleRate; + wf.nBlockAlign = (wf.nChannels * wf.wBitsPerSample) / 8; + wf.nAvgBytesPerSec = wf.nBlockAlign * wf.nSamplesPerSec; + + + // We use an event to know when a new fragment needs to be enqueued. + pDevice->winmm.hEvent = (mal_handle)CreateEvent(NULL, TRUE, TRUE, NULL); + if (pDevice->winmm.hEvent == NULL) { + errorMsg = "[WinMM] Failed to create event for fragment enqueing.", errorCode = MAL_FAILED_TO_CREATE_EVENT; + goto on_error; + } + + + if (type == mal_device_type_playback) { + MMRESULT result = ((MAL_PFN_waveOutOpen)pContext->winmm.waveOutOpen)((LPHWAVEOUT)&pDevice->winmm.hDevice, winMMDeviceID, &wf, (DWORD_PTR)pDevice->winmm.hEvent, (DWORD_PTR)pDevice, CALLBACK_EVENT | WAVE_ALLOWSYNC); + if (result != MMSYSERR_NOERROR) { + errorMsg = "[WinMM] Failed to open playback device.", errorCode = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; + goto on_error; + } + } else { + MMRESULT result = ((MAL_PFN_waveInOpen)pDevice->pContext->winmm.waveInOpen)((LPHWAVEIN)&pDevice->winmm.hDevice, winMMDeviceID, &wf, (DWORD_PTR)pDevice->winmm.hEvent, (DWORD_PTR)pDevice, CALLBACK_EVENT | WAVE_ALLOWSYNC); + if (result != MMSYSERR_NOERROR) { + errorMsg = "[WinMM] Failed to open capture device.", errorCode = MAL_FAILED_TO_OPEN_BACKEND_DEVICE; + goto on_error; + } + } + + + // The internal formats need to be set based on the wf object. + if (wf.wFormatTag == WAVE_FORMAT_PCM) { + switch (wf.wBitsPerSample) { + case 8: pDevice->internalFormat = mal_format_u8; break; + case 16: pDevice->internalFormat = mal_format_s16; break; + case 24: pDevice->internalFormat = mal_format_s24; break; + case 32: pDevice->internalFormat = mal_format_s32; break; + default: mal_post_error(pDevice, "[WinMM] The device's internal format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + } + } else { + errorMsg = "[WinMM] The device's internal format is not supported by mini_al.", errorCode = MAL_FORMAT_NOT_SUPPORTED; + goto on_error; + } + + pDevice->internalChannels = wf.nChannels; + pDevice->internalSampleRate = wf.nSamplesPerSec; + + + // Just use the default channel mapping. WinMM only supports mono or stereo anyway so it'll reliably be left/right order for stereo. + mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + + + // Latency with WinMM seems pretty bad from my testing... Need to increase the default buffer size. + if (pDevice->usingDefaultBufferSize) { + if (pDevice->type == mal_device_type_playback) { + pDevice->bufferSizeInFrames *= 4; // <-- Might need to fiddle with this to find a more ideal value. May even be able to just add a fixed amount rather than scaling. + } else { + pDevice->bufferSizeInFrames *= 2; + } + } + + // The size of the intermediary buffer needs to be able to fit every fragment. + pDevice->winmm.fragmentSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; + pDevice->winmm.fragmentSizeInBytes = pDevice->winmm.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + + heapSize = (sizeof(WAVEHDR) * pDevice->periods) + (pDevice->winmm.fragmentSizeInBytes * pDevice->periods); + pDevice->winmm._pHeapData = (mal_uint8*)mal_malloc(heapSize); + if (pDevice->winmm._pHeapData == NULL) { + errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MAL_OUT_OF_MEMORY; + goto on_error; + } + + mal_zero_memory(pDevice->winmm._pHeapData, pDevice->winmm.fragmentSizeInBytes * pDevice->periods); + + pDevice->winmm.pWAVEHDR = pDevice->winmm._pHeapData; + pDevice->winmm.pIntermediaryBuffer = pDevice->winmm._pHeapData + (sizeof(WAVEHDR) * pDevice->periods); + + + return MAL_SUCCESS; + +on_error: + if (pDevice->type == mal_device_type_playback) { + ((MAL_PFN_waveOutClose)pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevice); + } else { + ((MAL_PFN_waveInClose)pContext->winmm.waveInClose)((HWAVEIN)pDevice->winmm.hDevice); + } + + mal_free(pDevice->winmm._pHeapData); + return mal_post_error(pDevice, errorMsg, errorCode); +} + + +static mal_result mal_device__start_backend__winmm(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + // Playback. The device is started when we call waveOutWrite() with a block of data. From MSDN: + // + // Unless the device is paused by calling the waveOutPause function, playback begins when the first data block is sent to the device. + // + // When starting the device we commit every fragment. We signal the event before calling waveOutWrite(). + mal_uint32 i; + for (i = 0; i < pDevice->periods; ++i) { + mal_zero_object(&((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i]); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBuffer + (pDevice->winmm.fragmentSizeInBytes * i)); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBufferLength = pDevice->winmm.fragmentSizeInBytes; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwFlags = 0L; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwLoops = 0L; + mal_device__read_frames_from_client(pDevice, pDevice->winmm.fragmentSizeInFrames, ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData); + + if (((MAL_PFN_waveOutPrepareHeader)pDevice->pContext->winmm.waveOutPrepareHeader)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) { + return mal_post_error(pDevice, "[WinMM] Failed to start backend device. Failed to prepare header.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + + ResetEvent(pDevice->winmm.hEvent); + + for (i = 0; i < pDevice->periods; ++i) { + if (((MAL_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)) != MMSYSERR_NOERROR) { + return mal_post_error(pDevice, "[WinMM] Failed to start backend device. Failed to send data to the backend device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + } else { + // Capture. + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + mal_zero_object(&((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i]); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBuffer + (pDevice->winmm.fragmentSizeInBytes * i)); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBufferLength = pDevice->winmm.fragmentSizeInBytes; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwFlags = 0L; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwLoops = 0L; + + MMRESULT resultMM = ((MAL_PFN_waveInPrepareHeader)pDevice->pContext->winmm.waveInPrepareHeader)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to prepare header for capture device in preparation for adding a new capture buffer for the device.", mal_result_from_MMRESULT(resultMM)); + break; + } + + resultMM = ((MAL_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to add new capture buffer to the internal capture device.", mal_result_from_MMRESULT(resultMM)); + break; + } + } + + ResetEvent(pDevice->winmm.hEvent); + + if (((MAL_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDevice) != MMSYSERR_NOERROR) { + return mal_post_error(pDevice, "[WinMM] Failed to start backend device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + + pDevice->winmm.iNextHeader = 0; + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__winmm(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + MMRESULT resultMM = ((MAL_PFN_waveOutReset)pDevice->pContext->winmm.waveOutReset)((HWAVEOUT)pDevice->winmm.hDevice); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] WARNING: Failed to reset playback device.", mal_result_from_MMRESULT(resultMM)); + } + + // Unprepare all WAVEHDR structures. + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + resultMM = ((MAL_PFN_waveOutUnprepareHeader)pDevice->pContext->winmm.waveOutUnprepareHeader)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] WARNING: Failed to unprepare header for playback device.", mal_result_from_MMRESULT(resultMM)); + } + } + } else { + MMRESULT resultMM = ((MAL_PFN_waveInReset)pDevice->pContext->winmm.waveInReset)((HWAVEIN)pDevice->winmm.hDevice); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] WARNING: Failed to reset capture device.", mal_result_from_MMRESULT(resultMM)); + } + + // Unprepare all WAVEHDR structures. + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + resultMM = ((MAL_PFN_waveInUnprepareHeader)pDevice->pContext->winmm.waveInUnprepareHeader)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] WARNING: Failed to unprepare header for playback device.", mal_result_from_MMRESULT(resultMM)); + } + } + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__winmm(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->winmm.breakFromMainLoop = MAL_TRUE; + SetEvent((HANDLE)pDevice->winmm.hEvent); + + return MAL_SUCCESS; +} + +static mal_result mal_device__main_loop__winmm(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_uint32 counter; + + pDevice->winmm.breakFromMainLoop = MAL_FALSE; + while (!pDevice->winmm.breakFromMainLoop) { + // Wait for a block of data to finish processing... + if (WaitForSingleObject((HANDLE)pDevice->winmm.hEvent, INFINITE) != WAIT_OBJECT_0) { + break; + } + + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + // Any headers that are marked as done need to be handled. We start by processing the completed blocks. Then we reset the event + // and then write or add replacement buffers to the device. + mal_uint32 iFirstHeader = pDevice->winmm.iNextHeader; + for (counter = 0; counter < pDevice->periods; ++counter) { + mal_uint32 i = pDevice->winmm.iNextHeader; + if ((((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwFlags & WHDR_DONE) == 0) { + break; + } + + if (pDevice->type == mal_device_type_playback) { + // Playback. + MMRESULT resultMM = ((MAL_PFN_waveOutUnprepareHeader)pDevice->pContext->winmm.waveOutUnprepareHeader)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to unprepare header for playback device in preparation for sending a new block of data to the device for playback.", mal_result_from_MMRESULT(resultMM)); + break; + } + + mal_zero_object(&((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i]); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBuffer + (pDevice->winmm.fragmentSizeInBytes * i)); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBufferLength = pDevice->winmm.fragmentSizeInBytes; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwFlags = 0L; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwLoops = 0L; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwUser = 1; // <-- Used in the next section to identify the buffers that needs to be re-written to the device. + mal_device__read_frames_from_client(pDevice, pDevice->winmm.fragmentSizeInFrames, ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData); + + resultMM = ((MAL_PFN_waveOutPrepareHeader)pDevice->pContext->winmm.waveOutPrepareHeader)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to prepare header for playback device in preparation for sending a new block of data to the device for playback.", mal_result_from_MMRESULT(resultMM)); + break; + } + } else { + // Capture. + mal_uint32 framesCaptured = (mal_uint32)(((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBytesRecorded) / pDevice->internalChannels / mal_get_sample_size_in_bytes(pDevice->internalFormat); + if (framesCaptured > 0) { + mal_device__send_frames_to_client(pDevice, framesCaptured, ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData); + } + + MMRESULT resultMM = ((MAL_PFN_waveInUnprepareHeader)pDevice->pContext->winmm.waveInUnprepareHeader)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to unprepare header for capture device in preparation for adding a new capture buffer for the device.", mal_result_from_MMRESULT(resultMM)); + break; + } + + mal_zero_object(&((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i]); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBuffer + (pDevice->winmm.fragmentSizeInBytes * i)); + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwBufferLength = pDevice->winmm.fragmentSizeInBytes; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwFlags = 0L; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwLoops = 0L; + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwUser = 1; // <-- Used in the next section to identify the buffers that needs to be re-added to the device. + + resultMM = ((MAL_PFN_waveInPrepareHeader)pDevice->pContext->winmm.waveInPrepareHeader)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to prepare header for capture device in preparation for adding a new capture buffer for the device.", mal_result_from_MMRESULT(resultMM)); + break; + } + } + + pDevice->winmm.iNextHeader = (pDevice->winmm.iNextHeader + 1) % pDevice->periods; + } + + ResetEvent((HANDLE)pDevice->winmm.hEvent); + + for (counter = 0; counter < pDevice->periods; ++counter) { + mal_uint32 i = (iFirstHeader + counter) % pDevice->periods; + + if (((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwUser == 1) { + ((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i].dwUser = 0; + + if (pDevice->type == mal_device_type_playback) { + // Playback. + MMRESULT resultMM = ((MAL_PFN_waveOutWrite)pDevice->pContext->winmm.waveOutWrite)((HWAVEOUT)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to write data to the internal playback device.", mal_result_from_MMRESULT(resultMM)); + break; + } + } else { + // Capture. + MMRESULT resultMM = ((MAL_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDevice, &((LPWAVEHDR)pDevice->winmm.pWAVEHDR)[i], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + mal_post_error(pDevice, "[WinMM] Failed to add new capture buffer to the internal capture device.", mal_result_from_MMRESULT(resultMM)); + break; + } + } + } + } + } + + return MAL_SUCCESS; +} +#endif + + + +/////////////////////////////////////////////////////////////////////////////// +// +// ALSA Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_ALSA +#include + +typedef int (* mal_snd_pcm_open_proc) (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode); +typedef int (* mal_snd_pcm_close_proc) (snd_pcm_t *pcm); +typedef size_t (* mal_snd_pcm_hw_params_sizeof_proc) (void); +typedef int (* mal_snd_pcm_hw_params_any_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params); +typedef int (* mal_snd_pcm_hw_params_set_format_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val); +typedef int (* mal_snd_pcm_hw_params_set_format_first_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t *format); +typedef void (* mal_snd_pcm_hw_params_get_format_mask_proc) (snd_pcm_hw_params_t *params, snd_pcm_format_mask_t *mask); +typedef int (* mal_snd_pcm_hw_params_set_channels_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* mal_snd_pcm_hw_params_set_rate_resample_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val); +typedef int (* mal_snd_pcm_hw_params_set_rate_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* mal_snd_pcm_hw_params_set_buffer_size_near_proc)(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val); +typedef int (* mal_snd_pcm_hw_params_set_periods_near_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* mal_snd_pcm_hw_params_set_access_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access); +typedef int (* mal_snd_pcm_hw_params_get_format_proc) (snd_pcm_hw_params_t *params, snd_pcm_format_t *format); +typedef int (* mal_snd_pcm_hw_params_get_channels_proc) (snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* mal_snd_pcm_hw_params_get_rate_proc) (snd_pcm_hw_params_t *params, unsigned int *rate, int *dir); +typedef int (* mal_snd_pcm_hw_params_get_buffer_size_proc) (snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val); +typedef int (* mal_snd_pcm_hw_params_get_periods_proc) (snd_pcm_hw_params_t *params, unsigned int *val, int *dir); +typedef int (* mal_snd_pcm_hw_params_get_access_proc) (snd_pcm_hw_params_t *params, snd_pcm_access_t *_access); +typedef int (* mal_snd_pcm_hw_params_proc) (snd_pcm_t *pcm, snd_pcm_hw_params_t *params); +typedef size_t (* mal_snd_pcm_sw_params_sizeof_proc) (void); +typedef int (* mal_snd_pcm_sw_params_current_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params); +typedef int (* mal_snd_pcm_sw_params_set_avail_min_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); +typedef int (* mal_snd_pcm_sw_params_set_start_threshold_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val); +typedef int (* mal_snd_pcm_sw_params_proc) (snd_pcm_t *pcm, snd_pcm_sw_params_t *params); +typedef size_t (* mal_snd_pcm_format_mask_sizeof_proc) (void); +typedef int (* mal_snd_pcm_format_mask_test_proc) (const snd_pcm_format_mask_t *mask, snd_pcm_format_t val); +typedef snd_pcm_chmap_t * (* mal_snd_pcm_get_chmap_proc) (snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_prepare_proc) (snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_start_proc) (snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_drop_proc) (snd_pcm_t *pcm); +typedef int (* mal_snd_device_name_hint_proc) (int card, const char *iface, void ***hints); +typedef char * (* mal_snd_device_name_get_hint_proc) (const void *hint, const char *id); +typedef int (* mal_snd_card_get_index_proc) (const char *name); +typedef int (* mal_snd_device_name_free_hint_proc) (void **hints); +typedef int (* mal_snd_pcm_mmap_begin_proc) (snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames); +typedef snd_pcm_sframes_t (* mal_snd_pcm_mmap_commit_proc) (snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames); +typedef int (* mal_snd_pcm_recover_proc) (snd_pcm_t *pcm, int err, int silent); +typedef snd_pcm_sframes_t (* mal_snd_pcm_readi_proc) (snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size); +typedef snd_pcm_sframes_t (* mal_snd_pcm_writei_proc) (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size); +typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_proc) (snd_pcm_t *pcm); +typedef snd_pcm_sframes_t (* mal_snd_pcm_avail_update_proc) (snd_pcm_t *pcm); +typedef int (* mal_snd_pcm_wait_proc) (snd_pcm_t *pcm, int timeout); + +static snd_pcm_format_t g_mal_ALSAFormats[] = { + SND_PCM_FORMAT_UNKNOWN, // mal_format_unknown + SND_PCM_FORMAT_U8, // mal_format_u8 + SND_PCM_FORMAT_S16_LE, // mal_format_s16 + SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + SND_PCM_FORMAT_S32_LE, // mal_format_s32 + SND_PCM_FORMAT_FLOAT_LE // mal_format_f32 +}; + +snd_pcm_format_t mal_convert_mal_format_to_alsa_format(mal_format format) +{ + return g_mal_ALSAFormats[format]; +} + +mal_format mal_convert_alsa_format_to_mal_format(snd_pcm_format_t formatALSA) +{ + switch (formatALSA) + { + case SND_PCM_FORMAT_U8: return mal_format_u8; + case SND_PCM_FORMAT_S16_LE: return mal_format_s16; + case SND_PCM_FORMAT_S24_3LE: return mal_format_s24; + case SND_PCM_FORMAT_S32_LE: return mal_format_s32; + case SND_PCM_FORMAT_FLOAT_LE: return mal_format_f32; + default: return mal_format_unknown; + } +} + +mal_channel mal_convert_alsa_channel_position_to_mal_channel(unsigned int alsaChannelPos) +{ + switch (alsaChannelPos) + { + case SND_CHMAP_FL: return MAL_CHANNEL_FRONT_LEFT; + case SND_CHMAP_FR: return MAL_CHANNEL_FRONT_RIGHT; + case SND_CHMAP_RL: return MAL_CHANNEL_BACK_LEFT; + case SND_CHMAP_RR: return MAL_CHANNEL_BACK_RIGHT; + case SND_CHMAP_FC: return MAL_CHANNEL_FRONT_CENTER; + case SND_CHMAP_LFE: return MAL_CHANNEL_LFE; + case SND_CHMAP_SL: return MAL_CHANNEL_SIDE_LEFT; + case SND_CHMAP_SR: return MAL_CHANNEL_SIDE_RIGHT; + case SND_CHMAP_RC: return MAL_CHANNEL_BACK_CENTER; + case SND_CHMAP_FLC: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case SND_CHMAP_FRC: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case SND_CHMAP_RLC: return 0; + case SND_CHMAP_RRC: return 0; + case SND_CHMAP_FLW: return 0; + case SND_CHMAP_FRW: return 0; + case SND_CHMAP_FLH: return 0; + case SND_CHMAP_FCH: return 0; + case SND_CHMAP_FRH: return 0; + case SND_CHMAP_TC: return MAL_CHANNEL_TOP_CENTER; + case SND_CHMAP_TFL: return MAL_CHANNEL_TOP_FRONT_LEFT; + case SND_CHMAP_TFR: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case SND_CHMAP_TFC: return MAL_CHANNEL_TOP_FRONT_CENTER; + case SND_CHMAP_TRL: return MAL_CHANNEL_TOP_BACK_LEFT; + case SND_CHMAP_TRR: return MAL_CHANNEL_TOP_BACK_RIGHT; + case SND_CHMAP_TRC: return MAL_CHANNEL_TOP_BACK_CENTER; + default: break; + } + + return 0; +} + +mal_result mal_context_init__alsa(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + pContext->alsa.asoundSO = mal_dlopen("libasound.so"); + if (pContext->alsa.asoundSO == NULL) { + return MAL_NO_BACKEND; + } + + pContext->alsa.snd_pcm_open = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_open"); + pContext->alsa.snd_pcm_close = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_close"); + pContext->alsa.snd_pcm_hw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_sizeof"); + pContext->alsa.snd_pcm_hw_params_any = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_any"); + pContext->alsa.snd_pcm_hw_params_set_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format"); + pContext->alsa.snd_pcm_hw_params_set_format_first = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format_first"); + pContext->alsa.snd_pcm_hw_params_get_format_mask = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format_mask"); + pContext->alsa.snd_pcm_hw_params_set_channels_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); + pContext->alsa.snd_pcm_hw_params_set_rate_resample = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_resample"); + pContext->alsa.snd_pcm_hw_params_set_rate_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_near"); + pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); + pContext->alsa.snd_pcm_hw_params_set_periods_near = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_periods_near"); + pContext->alsa.snd_pcm_hw_params_set_access = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_set_access"); + pContext->alsa.snd_pcm_hw_params_get_format = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format"); + pContext->alsa.snd_pcm_hw_params_get_channels = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_channels"); + pContext->alsa.snd_pcm_hw_params_get_rate = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_rate"); + pContext->alsa.snd_pcm_hw_params_get_buffer_size = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_buffer_size"); + pContext->alsa.snd_pcm_hw_params_get_periods = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_periods"); + pContext->alsa.snd_pcm_hw_params_get_access = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params_get_access"); + pContext->alsa.snd_pcm_hw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_hw_params"); + pContext->alsa.snd_pcm_sw_params_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_sizeof"); + pContext->alsa.snd_pcm_sw_params_current = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_current"); + pContext->alsa.snd_pcm_sw_params_set_avail_min = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_avail_min"); + pContext->alsa.snd_pcm_sw_params_set_start_threshold = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params_set_start_threshold"); + pContext->alsa.snd_pcm_sw_params = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_sw_params"); + pContext->alsa.snd_pcm_format_mask_sizeof = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_sizeof"); + pContext->alsa.snd_pcm_format_mask_test = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_format_mask_test"); + pContext->alsa.snd_pcm_get_chmap = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_get_chmap"); + pContext->alsa.snd_pcm_prepare = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_prepare"); + pContext->alsa.snd_pcm_start = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_start"); + pContext->alsa.snd_pcm_drop = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_drop"); + pContext->alsa.snd_device_name_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_hint"); + pContext->alsa.snd_device_name_get_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_get_hint"); + pContext->alsa.snd_card_get_index = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_card_get_index"); + pContext->alsa.snd_device_name_free_hint = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_device_name_free_hint"); + pContext->alsa.snd_pcm_mmap_begin = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_mmap_begin"); + pContext->alsa.snd_pcm_mmap_commit = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_mmap_commit"); + pContext->alsa.snd_pcm_recover = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_recover"); + pContext->alsa.snd_pcm_readi = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_readi"); + pContext->alsa.snd_pcm_writei = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_writei"); + pContext->alsa.snd_pcm_avail = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail"); + pContext->alsa.snd_pcm_avail_update = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_avail_update"); + pContext->alsa.snd_pcm_wait = (mal_proc)mal_dlsym(pContext->alsa.asoundSO, "snd_pcm_wait"); + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__alsa(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_alsa); + + (void)pContext; + return MAL_SUCCESS; +} + +static const char* mal_find_char(const char* str, char c, int* index) +{ + int i = 0; + for (;;) { + if (str[i] == '\0') { + if (index) *index = -1; + return NULL; + } + + if (str[i] == c) { + if (index) *index = i; + return str + i; + } + + i += 1; + } + + // Should never get here, but treat it as though the character was not found to make me feel + // better inside. + if (index) *index = -1; + return NULL; +} + +// Waits for a number of frames to become available for either capture or playback. The return +// value is the number of frames available. +// +// This will return early if the main loop is broken with mal_device__break_main_loop(). +static mal_uint32 mal_device__wait_for_frames__alsa(mal_device* pDevice, mal_bool32* pRequiresRestart) +{ + mal_assert(pDevice != NULL); + + if (pRequiresRestart) *pRequiresRestart = MAL_FALSE; + + mal_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; + + while (!pDevice->alsa.breakFromMainLoop) { + // Wait for something to become available. The timeout should not affect latency - it's only used to break from the wait + // so we can check whether or not the device has been stopped. + const int timeoutInMilliseconds = 10; + int waitResult = ((mal_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((snd_pcm_t*)pDevice->alsa.pPCM, timeoutInMilliseconds); + if (waitResult < 0) { + if (waitResult == -EPIPE) { + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MAL_TRUE) < 0) { + return 0; + } + + if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode. + } + } + + if (pDevice->alsa.breakFromMainLoop) { + return 0; + } + + snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM); + if (framesAvailable < 0) { + if (framesAvailable == -EPIPE) { + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, framesAvailable, MAL_TRUE) < 0) { + return 0; + } + + if (pRequiresRestart) *pRequiresRestart = MAL_TRUE; // A device recovery means a restart for mmap mode. + + // Try again, but if it fails this time just return an error. + framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM); + if (framesAvailable < 0) { + return 0; + } + } + } + + // Keep the returned number of samples consistent and based on the period size. + if (framesAvailable >= periodSizeInFrames) { + return periodSizeInFrames; + } + } + + // We'll get here if the loop was terminated. Just return whatever's available. + snd_pcm_sframes_t framesAvailable = ((mal_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((snd_pcm_t*)pDevice->alsa.pPCM); + if (framesAvailable < 0) { + return 0; + } + + return framesAvailable; +} + +static mal_bool32 mal_device_write__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + if (!mal_device_is_started(pDevice) && mal_device__get_state(pDevice) != MAL_STATE_STARTING) { + return MAL_FALSE; + } + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + + if (pDevice->alsa.isUsingMMap) { + // mmap. + mal_bool32 requiresRestart; + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart); + if (framesAvailable == 0) { + return MAL_FALSE; + } + + // Don't bother asking the client for more audio data if we're just stopping the device anyway. + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + const snd_pcm_channel_area_t* pAreas; + snd_pcm_uframes_t mappedOffset; + snd_pcm_uframes_t mappedFrames = framesAvailable; + while (framesAvailable > 0) { + int result = ((mal_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); + if (result < 0) { + return MAL_FALSE; + } + + if (mappedFrames > 0) { + void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); + mal_device__read_frames_from_client(pDevice, mappedFrames, pBuffer); + } + + result = ((mal_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); + if (result < 0 || (snd_pcm_uframes_t)result != mappedFrames) { + ((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, result, MAL_TRUE); + return MAL_FALSE; + } + + if (requiresRestart) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return MAL_FALSE; + } + } + + framesAvailable -= mappedFrames; + } + } else { + // readi/writei. + while (!pDevice->alsa.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL); + if (framesAvailable == 0) { + continue; + } + + // Don't bother asking the client for more audio data if we're just stopping the device anyway. + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + mal_device__read_frames_from_client(pDevice, framesAvailable, pDevice->alsa.pIntermediaryBuffer); + + snd_pcm_sframes_t framesWritten = ((mal_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesWritten < 0) { + if (framesWritten == -EAGAIN) { + continue; // Just keep trying... + } else if (framesWritten == -EPIPE) { + // Underrun. Just recover and try writing again. + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, framesWritten, MAL_TRUE) < 0) { + mal_post_error(pDevice, "[ALSA] Failed to recover device after underrun.", MAL_ALSA_FAILED_TO_RECOVER_DEVICE); + return MAL_FALSE; + } + + framesWritten = ((mal_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesWritten < 0) { + mal_post_error(pDevice, "[ALSA] Failed to write data to the internal device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + return MAL_FALSE; + } + + break; // Success. + } else { + mal_post_error(pDevice, "[ALSA] snd_pcm_writei() failed when writing initial data.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + return MAL_FALSE; + } + } else { + break; // Success. + } + } + } + + return MAL_TRUE; +} + +static mal_bool32 mal_device_read__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + if (!mal_device_is_started(pDevice)) { + return MAL_FALSE; + } + if (pDevice->alsa.breakFromMainLoop) { + return MAL_FALSE; + } + + mal_uint32 framesToSend = 0; + void* pBuffer = NULL; + if (pDevice->alsa.pIntermediaryBuffer == NULL) { + // mmap. + mal_bool32 requiresRestart; + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, &requiresRestart); + if (framesAvailable == 0) { + return MAL_FALSE; + } + + const snd_pcm_channel_area_t* pAreas; + snd_pcm_uframes_t mappedOffset; + snd_pcm_uframes_t mappedFrames = framesAvailable; + while (framesAvailable > 0) { + int result = ((mal_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); + if (result < 0) { + return MAL_FALSE; + } + + if (mappedFrames > 0) { + void* pBuffer = (mal_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); + mal_device__send_frames_to_client(pDevice, mappedFrames, pBuffer); + } + + result = ((mal_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); + if (result < 0 || (snd_pcm_uframes_t)result != mappedFrames) { + ((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, result, MAL_TRUE); + return MAL_FALSE; + } + + if (requiresRestart) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return MAL_FALSE; + } + } + + framesAvailable -= mappedFrames; + } + } else { + // readi/writei. + snd_pcm_sframes_t framesRead = 0; + while (!pDevice->alsa.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__alsa(pDevice, NULL); + if (framesAvailable == 0) { + continue; + } + + framesRead = ((mal_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesRead < 0) { + if (framesRead == -EAGAIN) { + continue; // Just keep trying... + } else if (framesRead == -EPIPE) { + // Overrun. Just recover and try reading again. + if (((mal_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((snd_pcm_t*)pDevice->alsa.pPCM, framesRead, MAL_TRUE) < 0) { + mal_post_error(pDevice, "[ALSA] Failed to recover device after overrun.", MAL_ALSA_FAILED_TO_RECOVER_DEVICE); + return MAL_FALSE; + } + + framesRead = ((mal_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); + if (framesRead < 0) { + mal_post_error(pDevice, "[ALSA] Failed to read data from the internal device.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); + return MAL_FALSE; + } + + break; // Success. + } else { + return MAL_FALSE; + } + } else { + break; // Success. + } + } + + framesToSend = framesRead; + pBuffer = pDevice->alsa.pIntermediaryBuffer; + } + + if (framesToSend > 0) { + mal_device__send_frames_to_client(pDevice, framesToSend, pBuffer); + } + + return MAL_TRUE; +} + + + +static mal_bool32 mal_is_device_name_in_hw_format__alsa(const char* hwid) +{ + // This function is just checking whether or not hwid is in "hw:%d,%d" format. + + if (hwid == NULL) { + return MAL_FALSE; + } + + if (hwid[0] != 'h' || hwid[1] != 'w' || hwid[2] != ':') { + return MAL_FALSE; + } + + hwid += 3; + + int commaPos; + const char* dev = mal_find_char(hwid, ',', &commaPos); + if (dev == NULL) { + return MAL_FALSE; + } else { + dev += 1; // Skip past the ",". + } + + // Check if the part between the ":" and the "," contains only numbers. If not, return false. + for (int i = 0; i < commaPos; ++i) { + if (hwid[i] < '0' || hwid[i] > '9') { + return MAL_FALSE; + } + } + + // Check if everything after the "," is numeric. If not, return false. + int i = 0; + while (dev[i] != '\0') { + if (dev[i] < '0' || dev[i] > '9') { + return MAL_FALSE; + } + i += 1; + } + + return MAL_TRUE; +} + +static int mal_convert_device_name_to_hw_format__alsa(mal_context* pContext, char* dst, size_t dstSize, const char* src) // Returns 0 on success, non-0 on error. +{ + // src should look something like this: "hw:CARD=I82801AAICH,DEV=0" + + if (dst == NULL) return -1; + if (dstSize < 7) return -1; // Absolute minimum size of the output buffer is 7 bytes. + + *dst = '\0'; // Safety. + if (src == NULL) return -1; + + // If the input name is already in "hw:%d,%d" format, just return that verbatim. + if (mal_is_device_name_in_hw_format__alsa(src)) { + return mal_strcpy_s(dst, dstSize, src); + } + + + int colonPos; + src = mal_find_char(src, ':', &colonPos); + if (src == NULL) { + return -1; // Couldn't find a colon + } + + char card[256]; + + int commaPos; + const char* dev = mal_find_char(src, ',', &commaPos); + if (dev == NULL) { + dev = "0"; + mal_strncpy_s(card, sizeof(card), src+6, (size_t)-1); // +6 = ":CARD=" + } else { + dev = dev + 5; // +5 = ",DEV=" + mal_strncpy_s(card, sizeof(card), src+6, commaPos-6); // +6 = ":CARD=" + } + + int cardIndex = ((mal_snd_card_get_index_proc)pContext->alsa.snd_card_get_index)(card); + if (cardIndex < 0) { + return -2; // Failed to retrieve the card index. + } + + //printf("TESTING: CARD=%s,DEV=%s\n", card, dev); + + + // Construction. + dst[0] = 'h'; dst[1] = 'w'; dst[2] = ':'; + if (mal_itoa_s(cardIndex, dst+3, dstSize-3, 10) != 0) { + return -3; + } + if (mal_strcat_s(dst, dstSize, ",") != 0) { + return -3; + } + if (mal_strcat_s(dst, dstSize, dev) != 0) { + return -3; + } + + return 0; +} + +static mal_bool32 mal_does_id_exist_in_list__alsa(mal_device_id* pUniqueIDs, mal_uint32 count, const char* pHWID) +{ + mal_assert(pHWID != NULL); + + for (mal_uint32 i = 0; i < count; ++i) { + if (mal_strcmp(pUniqueIDs[i].alsa, pHWID) == 0) { + return MAL_TRUE; + } + } + + return MAL_FALSE; +} + +static mal_result mal_enumerate_devices__alsa(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + mal_uint32 infoSize = *pCount; + *pCount = 0; + + char** ppDeviceHints; + if (((mal_snd_device_name_hint_proc)pContext->alsa.snd_device_name_hint)(-1, "pcm", (void***)&ppDeviceHints) < 0) { + return MAL_NO_BACKEND; + } + + mal_device_id* pUniqueIDs = NULL; + mal_uint32 uniqueIDCount = 0; + + char** ppNextDeviceHint = ppDeviceHints; + while (*ppNextDeviceHint != NULL) { + char* NAME = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "NAME"); + char* DESC = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "DESC"); + char* IOID = ((mal_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID"); + + // Only include devices if they are of the correct type. Special cases for "default", "null" and "pulse" - these are included for both playback and capture + // regardless of the IOID setting. + mal_bool32 includeThisDevice = MAL_FALSE; + if (strcmp(NAME, "default") == 0 || strcmp(NAME, "pulse") == 0 || strcmp(NAME, "null") == 0) { + includeThisDevice = MAL_TRUE; + + // Exclude the "null" device if requested. + if (strcmp(NAME, "null") == 0 && pContext->config.alsa.excludeNullDevice) { + includeThisDevice = MAL_FALSE; + } + } else { + if ((type == mal_device_type_playback && (IOID == NULL || strcmp(IOID, "Output") == 0)) || + (type == mal_device_type_capture && (IOID != NULL && strcmp(IOID, "Input" ) == 0))) { + includeThisDevice = MAL_TRUE; + } + } + + + + if (includeThisDevice) { +#if 0 + printf("NAME: %s\n", NAME); + printf("DESC: %s\n", DESC); + printf("IOID: %s\n", IOID); + + char hwid2[256]; + mal_convert_device_name_to_hw_format__alsa(pContext, hwid2, sizeof(hwid2), NAME); + printf("DEVICE ID: %s (%d)\n\n", hwid2, *pCount); +#endif + + char hwid[sizeof(pUniqueIDs->alsa)]; + if (NAME != NULL) { + if (pContext->config.alsa.useVerboseDeviceEnumeration) { + // Verbose mode. Use the name exactly as-is. + mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + } else { + // Simplified mode. Use ":%d,%d" format. + if (mal_convert_device_name_to_hw_format__alsa(pContext, hwid, sizeof(hwid), NAME) == 0) { + // At this point, hwid looks like "hw:0,0". In simplified enumeration mode, we actually want to strip off the + // plugin name so it looks like ":0,0". The reason for this is that this special format is detected at device + // initialization time and is used as an indicator to try and use the most appropriate plugin depending on the + // device type and sharing mode. + char* dst = hwid; + char* src = hwid+2; + while ((*dst++ = *src++)); + } else { + // Conversion to "hw:%d,%d" failed. Just use the name as-is. + mal_strncpy_s(hwid, sizeof(hwid), NAME, (size_t)-1); + } + + if (mal_does_id_exist_in_list__alsa(pUniqueIDs, uniqueIDCount, hwid)) { + goto next_device; // The device has already been enumerated. Move on to the next one. + } else { + // The device has not yet been enumerated. Make sure it's added to our list so that it's not enumerated again. + mal_device_id* pNewUniqueIDs = mal_realloc(pUniqueIDs, sizeof(*pUniqueIDs) * (uniqueIDCount + 1)); + if (pNewUniqueIDs == NULL) { + goto next_device; // Failed to allocate memory. + } + + pUniqueIDs = pNewUniqueIDs; + mal_copy_memory(pUniqueIDs[uniqueIDCount].alsa, hwid, sizeof(hwid)); + uniqueIDCount += 1; + } + } + } else { + mal_zero_memory(hwid, sizeof(hwid)); + } + + if (pInfo != NULL) { + if (infoSize > 0) { + mal_zero_object(pInfo); + mal_strncpy_s(pInfo->id.alsa, sizeof(pInfo->id.alsa), hwid, (size_t)-1); + + // DESC is the friendly name. We treat this slightly differently depending on whether or not we are using verbose + // device enumeration. In verbose mode we want to take the entire description so that the end-user can distinguish + // between the subdevices of each card/dev pair. In simplified mode, however, we only want the first part of the + // description. + // + // The value in DESC seems to be split into two lines, with the first line being the name of the device and the + // second line being a description of the device. I don't like having the description be across two lines because + // it makes formatting ugly and annoying. I'm therefore deciding to put it all on a single line with the second line + // being put into parentheses. In simplified mode I'm just stripping the second line entirely. + if (DESC != NULL) { + int lfPos; + const char* line2 = mal_find_char(DESC, '\n', &lfPos); + if (line2 != NULL) { + line2 += 1; // Skip past the new-line character. + + if (pContext->config.alsa.useVerboseDeviceEnumeration) { + // Verbose mode. Put the second line in brackets. + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC, lfPos); + mal_strcat_s (pInfo->name, sizeof(pInfo->name), " ("); + mal_strcat_s (pInfo->name, sizeof(pInfo->name), line2); + mal_strcat_s (pInfo->name, sizeof(pInfo->name), ")"); + } else { + // Simplified mode. Strip the second line entirely. + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), DESC, lfPos); + } + } else { + // There's no second line. Just copy the whole description. + mal_strcpy_s(pInfo->name, sizeof(pInfo->name), DESC); + } + } + + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + } + + next_device: + free(NAME); + free(DESC); + free(IOID); + ppNextDeviceHint += 1; + } + + mal_free(pUniqueIDs); + + ((mal_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); + return MAL_SUCCESS; +} + +static void mal_device_uninit__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if ((snd_pcm_t*)pDevice->alsa.pPCM) { + ((mal_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((snd_pcm_t*)pDevice->alsa.pPCM); + + if (pDevice->alsa.pIntermediaryBuffer != NULL) { + mal_free(pDevice->alsa.pIntermediaryBuffer); + } + } +} + +static mal_result mal_device_init__alsa(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->alsa); + + snd_pcm_format_t formatALSA = mal_convert_mal_format_to_alsa_format(pConfig->format); + snd_pcm_stream_t stream = (type == mal_device_type_playback) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE; + + if (pDeviceID == NULL) { + // We're opening the default device. I don't know if trying anything other than "default" is necessary, but it makes + // me feel better to try as hard as we can get to get _something_ working. + const char* defaultDeviceNames[] = { + "default", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL + }; + + if (pConfig->preferExclusiveMode) { + defaultDeviceNames[1] = "hw"; + defaultDeviceNames[2] = "hw:0"; + defaultDeviceNames[3] = "hw:0,0"; + } else { + if (type == mal_device_type_playback) { + defaultDeviceNames[1] = "dmix"; + defaultDeviceNames[2] = "dmix:0"; + defaultDeviceNames[3] = "dmix:0,0"; + } else { + defaultDeviceNames[1] = "dsnoop"; + defaultDeviceNames[2] = "dsnoop:0"; + defaultDeviceNames[3] = "dsnoop:0,0"; + } + defaultDeviceNames[4] = "hw"; + defaultDeviceNames[5] = "hw:0"; + defaultDeviceNames[6] = "hw:0,0"; + } + + mal_bool32 isDeviceOpen = MAL_FALSE; + for (size_t i = 0; i < mal_countof(defaultDeviceNames); ++i) { + if (defaultDeviceNames[i] != NULL && defaultDeviceNames[i][0] != '\0') { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, defaultDeviceNames[i], stream, 0) == 0) { + isDeviceOpen = MAL_TRUE; + break; + } + } + } + + if (!isDeviceOpen) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed when trying to open an appropriate default device.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); + } + } else { + // We're trying to open a specific device. There's a few things to consider here: + // + // mini_al recongnizes a special format of device id that excludes the "hw", "dmix", etc. prefix. It looks like this: ":0,0", ":0,1", etc. When + // an ID of this format is specified, it indicates to mini_al that it can try different combinations of plugins ("hw", "dmix", etc.) until it + // finds an appropriate one that works. This comes in very handy when trying to open a device in shared mode ("dmix"), vs exclusive mode ("hw"). + mal_bool32 isDeviceOpen = MAL_FALSE; + if (pDeviceID->alsa[0] != ':') { + // The ID is not in ":0,0" format. Use the ID exactly as-is. + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, pDeviceID->alsa, stream, 0) == 0) { + isDeviceOpen = MAL_TRUE; + } + } else { + // The ID is in ":0,0" format. Try different plugins depending on the shared mode. + if (pDeviceID->alsa[1] == '\0') { + pDeviceID->alsa[0] = '\0'; // An ID of ":" should be converted to "". + } + + char hwid[256]; + if (!pConfig->preferExclusiveMode) { + if (type == mal_device_type_playback) { + mal_strcpy_s(hwid, sizeof(hwid), "dmix"); + } else { + mal_strcpy_s(hwid, sizeof(hwid), "dsnoop"); + } + + if (mal_strcat_s(hwid, sizeof(hwid), pDeviceID->alsa) == 0) { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, hwid, stream, 0) == 0) { + isDeviceOpen = MAL_TRUE; + } + } + } + + // If at this point we still don't have an open device it means we're either preferencing exclusive mode or opening with "dmix"/"dsnoop" failed. + if (!isDeviceOpen) { + mal_strcpy_s(hwid, sizeof(hwid), "hw"); + if (mal_strcat_s(hwid, sizeof(hwid), pDeviceID->alsa) == 0) { + if (((mal_snd_pcm_open_proc)pContext->alsa.snd_pcm_open)((snd_pcm_t**)&pDevice->alsa.pPCM, hwid, stream, 0) == 0) { + isDeviceOpen = MAL_TRUE; + } + } + } + } + + if (!isDeviceOpen) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] snd_pcm_open() failed.", MAL_ALSA_FAILED_TO_OPEN_DEVICE); + } + } + + + // Hardware parameters. + snd_pcm_hw_params_t* pHWParams = (snd_pcm_hw_params_t*)alloca(((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); + mal_zero_memory(pHWParams, ((mal_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)()); + + if (((mal_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", MAL_ALSA_FAILED_TO_SET_HW_PARAMS); + } + + + // MMAP Mode + // + // Try using interleaved MMAP access. If this fails, fall back to standard readi/writei. + pDevice->alsa.isUsingMMap = MAL_FALSE; + if (!pConfig->alsa.noMMap && pDevice->type != mal_device_type_capture) { // <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it so I can test it... Contributions welcome. + if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { + pDevice->alsa.isUsingMMap = MAL_TRUE; + } + } + + if (!pDevice->alsa.isUsingMMap) { + if (((mal_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {; + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set access mode to neither SND_PCM_ACCESS_MMAP_INTERLEAVED nor SND_PCM_ACCESS_RW_INTERLEAVED. snd_pcm_hw_params_set_access() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + } + + + // Most important properties first. The documentation for OSS (yes, I know this is ALSA!) recommends format, channels, then sample rate. I can't + // find any documentation for ALSA specifically, so I'm going to copy the recommendation for OSS. + + // Format. + // Try getting every supported format. + snd_pcm_format_mask_t* pFormatMask = (snd_pcm_format_mask_t*)alloca(((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + mal_zero_memory(pFormatMask, ((mal_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)()); + + ((mal_snd_pcm_hw_params_get_format_mask_proc)pContext->alsa.snd_pcm_hw_params_get_format_mask)(pHWParams, pFormatMask); + + // At this point we should have a list of supported formats, so now we need to find the best one. We first check if the requested format is + // supported, and if so, use that one. If it's not supported, we just run though a list of formats and try to find the best one. + if (!((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, formatALSA)) { + // The requested format is not supported so now try running through the list of formats and return the best one. + snd_pcm_format_t preferredFormatsALSA[] = { + SND_PCM_FORMAT_FLOAT_LE, // mal_format_f32 + SND_PCM_FORMAT_S32_LE, // mal_format_s32 + SND_PCM_FORMAT_S24_3LE, // mal_format_s24 + SND_PCM_FORMAT_S16_LE, // mal_format_s16 + SND_PCM_FORMAT_U8 // mal_format_u8 + }; + + formatALSA = SND_PCM_FORMAT_UNKNOWN; + for (size_t i = 0; i < (sizeof(preferredFormatsALSA) / sizeof(preferredFormatsALSA[0])); ++i) { + if (((mal_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, preferredFormatsALSA[i])) { + formatALSA = preferredFormatsALSA[i]; + break; + } + } + + if (formatALSA == SND_PCM_FORMAT_UNKNOWN) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Format not supported. The device does not support any mini_al formats.", MAL_FORMAT_NOT_SUPPORTED); + } + } + + if (((mal_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, formatALSA) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + + pDevice->internalFormat = mal_convert_alsa_format_to_mal_format(formatALSA); + if (pDevice->internalFormat == mal_format_unknown) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] The chosen format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + } + + // Channels. + mal_uint32 channels = pConfig->channels; + if (((mal_snd_pcm_hw_params_set_channels_near_proc)pContext->alsa.snd_pcm_hw_params_set_channels_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &channels) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + pDevice->internalChannels = channels; + + + // Sample Rate. It appears there's either a bug in ALSA, a bug in some drivers, or I'm doing something silly; but having resampling + // enabled causes problems with some device configurations when used in conjunction with MMAP access mode. To fix this problem we + // need to disable resampling. + // + // To reproduce this problem, open the "plug:dmix" device, and set the sample rate to 44100. Internally, it looks like dmix uses a + // sample rate of 48000. The hardware parameters will get set correctly with no errors, but it looks like the 44100 -> 48000 resampling + // doesn't work properly - but only with MMAP access mode. You will notice skipping/crackling in the audio, and it'll run at a slightly + // faster rate. + // + // mini_al has built-in support for sample rate conversion (albeit low quality at the moment), so disabling resampling should be fine + // for us. The only problem is that it won't be taking advantage of any kind of hardware-accelerated resampling and it won't be very + // good quality until I get a chance to improve the quality of mini_al's software sample rate conversion. + // + // I don't currently know if the dmix plugin is the only one with this error. Indeed, this is the only one I've been able to reproduce + // this error with. In the future, we may want to restrict the disabling of resampling to only known bad plugins. + ((mal_snd_pcm_hw_params_set_rate_resample_proc)pContext->alsa.snd_pcm_hw_params_set_rate_resample)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, 0); + + mal_uint32 sampleRate = pConfig->sampleRate; + if (((mal_snd_pcm_hw_params_set_rate_near_proc)pContext->alsa.snd_pcm_hw_params_set_rate_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &sampleRate, 0) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + pDevice->internalSampleRate = sampleRate; + + + // Periods. + mal_uint32 periods = pConfig->periods; + int dir = 0; + if (((mal_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &periods, &dir) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + pDevice->periods = periods; + + // Buffer Size + snd_pcm_uframes_t actualBufferSize = pDevice->bufferSizeInFrames; + if (((mal_snd_pcm_hw_params_set_buffer_size_near_proc)pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams, &actualBufferSize) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + pDevice->bufferSizeInFrames = actualBufferSize; + + + // Apply hardware parameters. + if (((mal_snd_pcm_hw_params_proc)pContext->alsa.snd_pcm_hw_params)((snd_pcm_t*)pDevice->alsa.pPCM, pHWParams) < 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", MAL_ALSA_FAILED_TO_SET_HW_PARAMS); + } + + + + + // Software parameters. + snd_pcm_sw_params_t* pSWParams = (snd_pcm_sw_params_t*)alloca(((mal_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)()); + mal_zero_memory(pSWParams, ((mal_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)()); + + if (((mal_snd_pcm_sw_params_current_proc)pContext->alsa.snd_pcm_sw_params_current)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); + } + + if (((mal_snd_pcm_sw_params_set_avail_min_proc)pContext->alsa.snd_pcm_sw_params_set_avail_min)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, (pDevice->sampleRate/1000) * 1) != 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", MAL_FORMAT_NOT_SUPPORTED); + } + + if (type == mal_device_type_playback && !pDevice->alsa.isUsingMMap) { // Only playback devices in writei/readi mode need a start threshold. + if (((mal_snd_pcm_sw_params_set_start_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_start_threshold)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams, (pDevice->sampleRate/1000) * 1) != 0) { //mal_prev_power_of_2(pDevice->bufferSizeInFrames/pDevice->periods) + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); + } + } + + if (((mal_snd_pcm_sw_params_proc)pContext->alsa.snd_pcm_sw_params)((snd_pcm_t*)pDevice->alsa.pPCM, pSWParams) != 0) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", MAL_ALSA_FAILED_TO_SET_SW_PARAMS); + } + + + + // If we're _not_ using mmap we need to use an intermediary buffer. + if (!pDevice->alsa.isUsingMMap) { + pDevice->alsa.pIntermediaryBuffer = mal_malloc(pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format)); + if (pDevice->alsa.pIntermediaryBuffer == NULL) { + mal_device_uninit__alsa(pDevice); + return mal_post_error(pDevice, "[ALSA] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); + } + } + + + + // Grab the internal channel map. For now we're not going to bother trying to change the channel map and + // instead just do it ourselves. + snd_pcm_chmap_t* pChmap = ((mal_snd_pcm_get_chmap_proc)pContext->alsa.snd_pcm_get_chmap)((snd_pcm_t*)pDevice->alsa.pPCM); + if (pChmap != NULL) { + // There are cases where the returned channel map can have a different channel count than was returned by snd_pcm_hw_params_set_channels_near(). + if (pChmap->channels >= pDevice->internalChannels) { + // Drop excess channels. + for (mal_uint32 iChannel = 0; iChannel < pDevice->internalChannels; ++iChannel) { + pDevice->internalChannelMap[iChannel] = mal_convert_alsa_channel_position_to_mal_channel(pChmap->pos[iChannel]); + } + } else { + // Excess channels use defaults. Do an initial fill with defaults, overwrite the first pChmap->channels, validate to ensure there are no duplicate + // channels. If validation fails, fall back to defaults. + + // Fill with defaults. + mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + + // Overwrite first pChmap->channels channels. + for (mal_uint32 iChannel = 0; iChannel < pChmap->channels; ++iChannel) { + pDevice->internalChannelMap[iChannel] = mal_convert_alsa_channel_position_to_mal_channel(pChmap->pos[iChannel]); + } + + // Validate. + mal_bool32 isValid = MAL_TRUE; + for (mal_uint32 i = 0; i < pDevice->internalChannels && isValid; ++i) { + for (mal_uint32 j = i+1; j < pDevice->internalChannels; ++j) { + if (pDevice->internalChannelMap[i] == pDevice->internalChannelMap[j]) { + isValid = MAL_FALSE; + break; + } + } + } + + // If our channel map is invalid, fall back to defaults. + if (!isValid) { + mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + } + } + + free(pChmap); + pChmap = NULL; + } else { + // Could not retrieve the channel map. Fall back to a hard-coded assumption. + mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + } + + return MAL_SUCCESS; +} + + +static mal_result mal_device__start_backend__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // Prepare the device first... + if (((mal_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return mal_post_error(pDevice, "[ALSA] Failed to prepare device.", MAL_ALSA_FAILED_TO_PREPARE_DEVICE); + } + + // ... and then grab an initial chunk from the client. After this is done, the device should + // automatically start playing, since that's how we configured the software parameters. + if (pDevice->type == mal_device_type_playback) { + if (!mal_device_write__alsa(pDevice)) { + return mal_post_error(pDevice, "[ALSA] Failed to write initial chunk of data to the playback device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } + + // mmap mode requires an explicit start. + if (pDevice->alsa.isUsingMMap) { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + } else { + if (((mal_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((snd_pcm_t*)pDevice->alsa.pPCM) < 0) { + return mal_post_error(pDevice, "[ALSA] Failed to start capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + ((mal_snd_pcm_drop_proc)pDevice->pContext->alsa.snd_pcm_drop)((snd_pcm_t*)pDevice->alsa.pPCM); + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // Fallback. We just set a variable to tell the worker thread to terminate after handling the + // next bunch of frames. This is a slow way of handling this. + pDevice->alsa.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; +} + +static mal_result mal_device__main_loop__alsa(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->alsa.breakFromMainLoop = MAL_FALSE; + if (pDevice->type == mal_device_type_playback) { + // Playback. Read from client, write to device. + while (!pDevice->alsa.breakFromMainLoop && mal_device_write__alsa(pDevice)) { + } + } else { + // Capture. Read from device, write to client. + while (!pDevice->alsa.breakFromMainLoop && mal_device_read__alsa(pDevice)) { + } + } + + return MAL_SUCCESS; +} +#endif // ALSA + + +/////////////////////////////////////////////////////////////////////////////// +// +// OSS Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_OSS +#include +#include +#include +#include + +int mal_open_temp_device__oss() +{ + // The OSS sample code uses "/dev/mixer" as the device for getting system properties so I'm going to do the same. + int fd = open("/dev/mixer", O_RDONLY, 0); + if (fd >= 0) { + return fd; + } + + return -1; +} + +mal_result mal_context_init__oss(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + // Try opening a temporary device first so we can get version information. This is closed at the end. + int fd = mal_open_temp_device__oss(); + if (fd == -1) { + return mal_context_post_error(pContext, NULL, "[OSS] Failed to open temporary device for retrieving system properties.", MAL_NO_BACKEND); // Looks liks OSS isn't installed, or there are no available devices. + } + + // Grab the OSS version. + int ossVersion = 0; + int result = ioctl(fd, OSS_GETVERSION, &ossVersion); + if (result == -1) { + close(fd); + return mal_context_post_error(pContext, NULL, "[OSS] Failed to retrieve OSS version.", MAL_NO_BACKEND); + } + + pContext->oss.versionMajor = ((ossVersion & 0xFF0000) >> 16); + pContext->oss.versionMinor = ((ossVersion & 0x00FF00) >> 8); + + close(fd); + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__oss(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_oss); + + (void)pContext; + return MAL_SUCCESS; +} + +static mal_result mal_enumerate_devices__oss(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + mal_uint32 infoSize = *pCount; + *pCount = 0; + + // The object returned by SNDCTL_SYSINFO will have the information we're after. + int fd = mal_open_temp_device__oss(); + if (fd == -1) { + return mal_context_post_error(pContext, NULL, "[OSS] Failed to open a temporary device for retrieving system information used for device enumeration.", MAL_NO_BACKEND); + } + + oss_sysinfo si; + int result = ioctl(fd, SNDCTL_SYSINFO, &si); + if (result != -1) { + for (int iAudioDevice = 0; iAudioDevice < si.numaudios; ++iAudioDevice) { + oss_audioinfo ai; + ai.dev = iAudioDevice; + result = ioctl(fd, SNDCTL_AUDIOINFO, &ai); + if (result != -1) { + mal_bool32 includeThisDevice = MAL_FALSE; + if (type == mal_device_type_playback && (ai.caps & PCM_CAP_OUTPUT) != 0) { + includeThisDevice = MAL_TRUE; + } else if (type == mal_device_type_capture && (ai.caps & PCM_CAP_INPUT) != 0) { + includeThisDevice = MAL_TRUE; + } + + if (includeThisDevice) { + if (ai.devnode[0] != '\0') { // <-- Can be blank, according to documentation. + if (pInfo != NULL) { + if (infoSize > 0) { + mal_strncpy_s(pInfo->id.oss, sizeof(pInfo->id.oss), ai.devnode, (size_t)-1); + + // The human readable device name should be in the "ai.handle" variable, but it can + // sometimes be empty in which case we just fall back to "ai.name" which is less user + // friendly, but usually has a value. + if (ai.handle[0] != '\0') { + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), ai.handle, (size_t)-1); + } else { + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), ai.name, (size_t)-1); + } + + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + } + } + } + } + } else { + // Failed to retrieve the system information. Just return a default device for both playback and capture. + if (pInfo != NULL) { + if (infoSize > 0) { + mal_strncpy_s(pInfo[0].id.oss, sizeof(pInfo[0].id.oss), "/dev/dsp", (size_t)-1); + if (type == mal_device_type_playback) { + mal_strncpy_s(pInfo[0].name, sizeof(pInfo[0].name), "Default Playback Device", (size_t)-1); + } else { + mal_strncpy_s(pInfo[0].name, sizeof(pInfo[0].name), "Default Capture Device", (size_t)-1); + } + + *pCount = 1; + } + } else { + *pCount = 1; + } + } + + close(fd); + return MAL_SUCCESS; +} + +static void mal_device_uninit__oss(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + close(pDevice->oss.fd); + mal_free(pDevice->oss.pIntermediaryBuffer); +} + +static mal_result mal_device_init__oss(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->oss); + + char deviceName[64]; + if (pDeviceID != NULL) { + mal_strncpy_s(deviceName, sizeof(deviceName), pDeviceID->oss, (size_t)-1); + } else { + mal_strncpy_s(deviceName, sizeof(deviceName), "/dev/dsp", (size_t)-1); + } + + pDevice->oss.fd = open(deviceName, (type == mal_device_type_playback) ? O_WRONLY : O_RDONLY, 0); + if (pDevice->oss.fd == -1) { + return mal_post_error(pDevice, "[OSS] Failed to open device.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + // The OSS documantation is very clear about the order we should be initializing the device's properties: + // 1) Format + // 2) Channels + // 3) Sample rate. + + // Format. + int ossFormat = AFMT_U8; + switch (pDevice->format) { + case mal_format_s16: ossFormat = AFMT_S16_LE; break; + case mal_format_s24: ossFormat = AFMT_S32_LE; break; + case mal_format_s32: ossFormat = AFMT_S32_LE; break; + case mal_format_f32: ossFormat = AFMT_S32_LE; break; + case mal_format_u8: + default: ossFormat = AFMT_U8; break; + } + int result = ioctl(pDevice->oss.fd, SNDCTL_DSP_SETFMT, &ossFormat); + if (result == -1) { + close(pDevice->oss.fd); + return mal_post_error(pDevice, "[OSS] Failed to set format.", MAL_FORMAT_NOT_SUPPORTED); + } + + switch (ossFormat) { + case AFMT_U8: pDevice->internalFormat = mal_format_u8; break; + case AFMT_S16_LE: pDevice->internalFormat = mal_format_s16; break; + case AFMT_S32_LE: pDevice->internalFormat = mal_format_s32; break; + default: mal_post_error(pDevice, "[OSS] The device's internal format is not supported by mini_al.", MAL_FORMAT_NOT_SUPPORTED); + } + + + // Channels. + int ossChannels = (int)pConfig->channels; + result = ioctl(pDevice->oss.fd, SNDCTL_DSP_CHANNELS, &ossChannels); + if (result == -1) { + close(pDevice->oss.fd); + return mal_post_error(pDevice, "[OSS] Failed to set channel count.", MAL_FORMAT_NOT_SUPPORTED); + } + + pDevice->internalChannels = ossChannels; + + + // Sample rate. + int ossSampleRate = (int)pConfig->sampleRate; + result = ioctl(pDevice->oss.fd, SNDCTL_DSP_SPEED, &ossSampleRate); + if (result == -1) { + close(pDevice->oss.fd); + return mal_post_error(pDevice, "[OSS] Failed to set sample rate.", MAL_FORMAT_NOT_SUPPORTED); + } + + pDevice->sampleRate = ossSampleRate; + + + + // The documentation says that the fragment settings should be set as soon as possible, but I'm not sure if + // it should be done before or after format/channels/rate. + // + // OSS wants the fragment size in bytes and a power of 2. When setting, we specify the power, not the actual + // value. + mal_uint32 fragmentSizeInBytes = mal_round_to_power_of_2(pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + if (fragmentSizeInBytes < 16) { + fragmentSizeInBytes = 16; + } + + mal_uint32 ossFragmentSizePower = 4; + fragmentSizeInBytes >>= 4; + while (fragmentSizeInBytes >>= 1) { + ossFragmentSizePower += 1; + } + + int ossFragment = (int)((pDevice->periods << 16) | ossFragmentSizePower); + result = ioctl(pDevice->oss.fd, SNDCTL_DSP_SETFRAGMENT, &ossFragment); + if (result == -1) { + close(pDevice->oss.fd); + return mal_post_error(pDevice, "[OSS] Failed to set fragment size and period count.", MAL_FORMAT_NOT_SUPPORTED); + } + + int actualFragmentSizeInBytes = 1 << (ossFragment & 0xFFFF); + pDevice->oss.fragmentSizeInFrames = actualFragmentSizeInBytes / mal_get_sample_size_in_bytes(pDevice->internalFormat) / pDevice->internalChannels; + + pDevice->periods = (mal_uint32)(ossFragment >> 16); + pDevice->bufferSizeInFrames = (mal_uint32)(pDevice->oss.fragmentSizeInFrames * pDevice->periods); + + + // Set the internal channel map. Not sure if this can be queried. For now just using our default assumptions. + mal_get_default_channel_mapping(pDevice->pContext->backend, pDevice->internalChannels, pDevice->internalChannelMap); + + + // When not using MMAP mode, we need to use an intermediary buffer for the client <-> device transfer. We do + // everything by the size of a fragment. + pDevice->oss.pIntermediaryBuffer = mal_malloc(fragmentSizeInBytes); + if (pDevice->oss.pIntermediaryBuffer == NULL) { + close(pDevice->oss.fd); + return mal_post_error(pDevice, "[OSS] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); + } + + return MAL_SUCCESS; +} + + +static mal_result mal_device__start_backend__oss(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // The device is started by the next calls to read() and write(). For playback it's simple - just read + // data from the client, then write it to the device with write() which will in turn start the device. + // For capture it's a bit less intuitive - we do nothing (it'll be started automatically by the first + // call to read(). + if (pDevice->type == mal_device_type_playback) { + // Playback. + mal_device__read_frames_from_client(pDevice, pDevice->oss.fragmentSizeInFrames, pDevice->oss.pIntermediaryBuffer); + + int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + if (bytesWritten == -1) { + return mal_post_error(pDevice, "[OSS] Failed to send initial chunk of data to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } + } else { + // Capture. Do nothing. + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__oss(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // We want to use SNDCTL_DSP_HALT. From the documentation: + // + // In multithreaded applications SNDCTL_DSP_HALT (SNDCTL_DSP_RESET) must only be called by the thread + // that actually reads/writes the audio device. It must not be called by some master thread to kill the + // audio thread. The audio thread will not stop or get any kind of notification that the device was + // stopped by the master thread. The device gets stopped but the next read or write call will silently + // restart the device. + // + // This is actually safe in our case, because this function is only ever called from within our worker + // thread anyway. Just keep this in mind, though... + + int result = ioctl(pDevice->oss.fd, SNDCTL_DSP_HALT, 0); + if (result == -1) { + return mal_post_error(pDevice, "[OSS] Failed to stop device. SNDCTL_DSP_HALT failed.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__oss(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->oss.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; +} + +static mal_result mal_device__main_loop__oss(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->oss.breakFromMainLoop = MAL_FALSE; + while (!pDevice->oss.breakFromMainLoop) { + // Break from the main loop if the device isn't started anymore. Likely what's happened is the application + // has requested that the device be stopped. + if (!mal_device_is_started(pDevice)) { + break; + } + + if (pDevice->type == mal_device_type_playback) { + // Playback. + mal_device__read_frames_from_client(pDevice, pDevice->oss.fragmentSizeInFrames, pDevice->oss.pIntermediaryBuffer); + + int bytesWritten = write(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + if (bytesWritten < 0) { + return mal_post_error(pDevice, "[OSS] Failed to send data from the client to the device.", MAL_FAILED_TO_SEND_DATA_TO_DEVICE); + } + } else { + // Capture. + int bytesRead = read(pDevice->oss.fd, pDevice->oss.pIntermediaryBuffer, pDevice->oss.fragmentSizeInFrames * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + if (bytesRead < 0) { + return mal_post_error(pDevice, "[OSS] Failed to read data from the device to be sent to the client.", MAL_FAILED_TO_READ_DATA_FROM_DEVICE); + } + + mal_uint32 framesRead = (mal_uint32)bytesRead / pDevice->internalChannels / mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_device__send_frames_to_client(pDevice, framesRead, pDevice->oss.pIntermediaryBuffer); + } + } + + return MAL_SUCCESS; +} +#endif // OSS + + +/////////////////////////////////////////////////////////////////////////////// +// +// OpenSL|ES Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_OPENSL +#include +#ifdef MAL_ANDROID +#include +#endif + +// Converts an individual OpenSL-style channel identifier (SL_SPEAKER_FRONT_LEFT, etc.) to mini_al. +static mal_uint8 mal_channel_id_to_mal__opensl(SLuint32 id) +{ + switch (id) + { + case SL_SPEAKER_FRONT_LEFT: return MAL_CHANNEL_FRONT_LEFT; + case SL_SPEAKER_FRONT_RIGHT: return MAL_CHANNEL_FRONT_RIGHT; + case SL_SPEAKER_FRONT_CENTER: return MAL_CHANNEL_FRONT_CENTER; + case SL_SPEAKER_LOW_FREQUENCY: return MAL_CHANNEL_LFE; + case SL_SPEAKER_BACK_LEFT: return MAL_CHANNEL_BACK_LEFT; + case SL_SPEAKER_BACK_RIGHT: return MAL_CHANNEL_BACK_RIGHT; + case SL_SPEAKER_FRONT_LEFT_OF_CENTER: return MAL_CHANNEL_FRONT_LEFT_CENTER; + case SL_SPEAKER_FRONT_RIGHT_OF_CENTER: return MAL_CHANNEL_FRONT_RIGHT_CENTER; + case SL_SPEAKER_BACK_CENTER: return MAL_CHANNEL_BACK_CENTER; + case SL_SPEAKER_SIDE_LEFT: return MAL_CHANNEL_SIDE_LEFT; + case SL_SPEAKER_SIDE_RIGHT: return MAL_CHANNEL_SIDE_RIGHT; + case SL_SPEAKER_TOP_CENTER: return MAL_CHANNEL_TOP_CENTER; + case SL_SPEAKER_TOP_FRONT_LEFT: return MAL_CHANNEL_TOP_FRONT_LEFT; + case SL_SPEAKER_TOP_FRONT_CENTER: return MAL_CHANNEL_TOP_FRONT_CENTER; + case SL_SPEAKER_TOP_FRONT_RIGHT: return MAL_CHANNEL_TOP_FRONT_RIGHT; + case SL_SPEAKER_TOP_BACK_LEFT: return MAL_CHANNEL_TOP_BACK_LEFT; + case SL_SPEAKER_TOP_BACK_CENTER: return MAL_CHANNEL_TOP_BACK_CENTER; + case SL_SPEAKER_TOP_BACK_RIGHT: return MAL_CHANNEL_TOP_BACK_RIGHT; + default: return 0; + } +} + +// Converts an individual mini_al channel identifier (MAL_CHANNEL_FRONT_LEFT, etc.) to OpenSL-style. +static SLuint32 mal_channel_id_to_opensl(mal_uint8 id) +{ + switch (id) + { + case MAL_CHANNEL_FRONT_LEFT: return SL_SPEAKER_FRONT_LEFT; + case MAL_CHANNEL_FRONT_RIGHT: return SL_SPEAKER_FRONT_RIGHT; + case MAL_CHANNEL_FRONT_CENTER: return SL_SPEAKER_FRONT_CENTER; + case MAL_CHANNEL_LFE: return SL_SPEAKER_LOW_FREQUENCY; + case MAL_CHANNEL_BACK_LEFT: return SL_SPEAKER_BACK_LEFT; + case MAL_CHANNEL_BACK_RIGHT: return SL_SPEAKER_BACK_RIGHT; + case MAL_CHANNEL_FRONT_LEFT_CENTER: return SL_SPEAKER_FRONT_LEFT_OF_CENTER; + case MAL_CHANNEL_FRONT_RIGHT_CENTER: return SL_SPEAKER_FRONT_RIGHT_OF_CENTER; + case MAL_CHANNEL_BACK_CENTER: return SL_SPEAKER_BACK_CENTER; + case MAL_CHANNEL_SIDE_LEFT: return SL_SPEAKER_SIDE_LEFT; + case MAL_CHANNEL_SIDE_RIGHT: return SL_SPEAKER_SIDE_RIGHT; + case MAL_CHANNEL_TOP_CENTER: return SL_SPEAKER_TOP_CENTER; + case MAL_CHANNEL_TOP_FRONT_LEFT: return SL_SPEAKER_TOP_FRONT_LEFT; + case MAL_CHANNEL_TOP_FRONT_CENTER: return SL_SPEAKER_TOP_FRONT_CENTER; + case MAL_CHANNEL_TOP_FRONT_RIGHT: return SL_SPEAKER_TOP_FRONT_RIGHT; + case MAL_CHANNEL_TOP_BACK_LEFT: return SL_SPEAKER_TOP_BACK_LEFT; + case MAL_CHANNEL_TOP_BACK_CENTER: return SL_SPEAKER_TOP_BACK_CENTER; + case MAL_CHANNEL_TOP_BACK_RIGHT: return SL_SPEAKER_TOP_BACK_RIGHT; + default: return 0; + } +} + +// Converts a channel mapping to an OpenSL-style channel mask. +static SLuint32 mal_channel_map_to_channel_mask__opensl(mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_uint32 channels) +{ + SLuint32 channelMask = 0; + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + channelMask |= mal_channel_id_to_opensl(channelMap[iChannel]); + } + + return channelMask; +} + +// Converts an OpenSL-style channel mask to a mini_al channel map. +static void mal_channel_mask_to_channel_map__opensl(SLuint32 channelMask, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +{ + if (channels == 2 && channelMask == 0) { + channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + } else { + // Just iterate over each bit. + mal_uint32 iChannel = 0; + for (mal_uint32 iBit = 0; iBit < 32; ++iBit) { + SLuint32 bitValue = (channelMask & (1 << iBit)); + if (bitValue != 0) { + // The bit is set. + channelMap[iChannel] = mal_channel_id_to_mal__opensl(bitValue); + iChannel += 1; + } + } + } +} + +SLuint32 mal_round_to_standard_sample_rate__opensl(SLuint32 samplesPerSec) +{ + if (samplesPerSec <= SL_SAMPLINGRATE_8) { + return SL_SAMPLINGRATE_8; + } + if (samplesPerSec <= SL_SAMPLINGRATE_11_025) { + return SL_SAMPLINGRATE_11_025; + } + if (samplesPerSec <= SL_SAMPLINGRATE_12) { + return SL_SAMPLINGRATE_12; + } + if (samplesPerSec <= SL_SAMPLINGRATE_16) { + return SL_SAMPLINGRATE_16; + } + if (samplesPerSec <= SL_SAMPLINGRATE_22_05) { + return SL_SAMPLINGRATE_22_05; + } + if (samplesPerSec <= SL_SAMPLINGRATE_24) { + return SL_SAMPLINGRATE_24; + } + if (samplesPerSec <= SL_SAMPLINGRATE_32) { + return SL_SAMPLINGRATE_32; + } + if (samplesPerSec <= SL_SAMPLINGRATE_44_1) { + return SL_SAMPLINGRATE_44_1; + } + if (samplesPerSec <= SL_SAMPLINGRATE_48) { + return SL_SAMPLINGRATE_48; + } + + // Android doesn't support more than 48000. +#ifndef MAL_ANDROID + if (samplesPerSec <= SL_SAMPLINGRATE_64) { + return SL_SAMPLINGRATE_64; + } + if (samplesPerSec <= SL_SAMPLINGRATE_88_2) { + return SL_SAMPLINGRATE_88_2; + } + if (samplesPerSec <= SL_SAMPLINGRATE_96) { + return SL_SAMPLINGRATE_96; + } + if (samplesPerSec <= SL_SAMPLINGRATE_192) { + return SL_SAMPLINGRATE_192; + } +#endif + + return SL_SAMPLINGRATE_16; +} + +mal_result mal_context_init__opensl(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + (void)pContext; + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__opensl(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_opensl); + + (void)pContext; + return MAL_SUCCESS; +} + +mal_result mal_enumerate_devices__opensl(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + (void)pContext; + + mal_uint32 infoSize = *pCount; + *pCount = 0; + + SLObjectItf engineObj; + SLresult resultSL = slCreateEngine(&engineObj, 0, NULL, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + return MAL_NO_BACKEND; + } + + (*engineObj)->Realize(engineObj, SL_BOOLEAN_FALSE); + + // TODO: Test Me. + // + // This is currently untested, so for now we are just returning default devices. +#if 0 + SLuint32 pDeviceIDs[128]; + SLint32 deviceCount = sizeof(pDeviceIDs) / sizeof(pDeviceIDs[0]); + + SLAudioIODeviceCapabilitiesItf deviceCaps; + resultSL = (*engineObj)->GetInterface(engineObj, SL_IID_AUDIOIODEVICECAPABILITIES, &deviceCaps); + if (resultSL != SL_RESULT_SUCCESS) { + // The interface may not be supported so just report a default device. + (*engineObj)->Destroy(engineObj); + goto return_default_device; + } + + if (type == mal_device_type_playback) { + resultSL = (*deviceCaps)->GetAvailableAudioOutputs(deviceCaps, &deviceCount, pDeviceIDs); + if (resultSL != SL_RESULT_SUCCESS) { + (*engineObj)->Destroy(engineObj); + return MAL_NO_DEVICE; + } + } else { + resultSL = (*deviceCaps)->GetAvailableAudioInputs(deviceCaps, &deviceCount, pDeviceIDs); + if (resultSL != SL_RESULT_SUCCESS) { + (*engineObj)->Destroy(engineObj); + return MAL_NO_DEVICE; + } + } + + for (SLint32 iDevice = 0; iDevice < deviceCount; ++iDevice) { + if (pInfo != NULL) { + if (infoSize > 0) { + mal_zero_object(pInfo); + pInfo->id.opensl = pDeviceIDs[iDevice]; + + mal_bool32 isValidDevice = MAL_TRUE; + if (type == mal_device_type_playback) { + SLAudioOutputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioOutputCapabilities(deviceCaps, pInfo->id.opensl, &desc); + if (resultSL != SL_RESULT_SUCCESS) { + isValidDevice = MAL_FALSE; + } + + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), (const char*)desc.pDeviceName, (size_t)-1); + } else { + SLAudioInputDescriptor desc; + resultSL = (*deviceCaps)->QueryAudioInputCapabilities(deviceCaps, pInfo->id.opensl, &desc); + if (resultSL != SL_RESULT_SUCCESS) { + isValidDevice = MAL_FALSE; + } + + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), (const char*)desc.deviceName, (size_t)-1); + } + + if (isValidDevice) { + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } + } else { + *pCount += 1; + } + } + + (*engineObj)->Destroy(engineObj); + return MAL_SUCCESS; +#else + (*engineObj)->Destroy(engineObj); + goto return_default_device; +#endif + +return_default_device: + *pCount = 1; + if (pInfo != NULL) { + if (infoSize > 0) { + if (type == mal_device_type_playback) { + pInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOOUTPUT; + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Playback Device", (size_t)-1); + } else { + pInfo->id.opensl = SL_DEFAULTDEVICEID_AUDIOINPUT; + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), "Default Capture Device", (size_t)-1); + } + } + } + + return MAL_SUCCESS; +} + + +// OpenSL|ES has one-per-application objects :( +static SLObjectItf g_malEngineObjectSL = NULL; +static SLEngineItf g_malEngineSL = NULL; +static mal_uint32 g_malOpenSLInitCounter = 0; + +#define MAL_OPENSL_OBJ(p) (*((SLObjectItf)(p))) +#define MAL_OPENSL_OUTPUTMIX(p) (*((SLOutputMixItf)(p))) +#define MAL_OPENSL_PLAY(p) (*((SLPlayItf)(p))) +#define MAL_OPENSL_RECORD(p) (*((SLRecordItf)(p))) + +#ifdef MAL_ANDROID +#define MAL_OPENSL_BUFFERQUEUE(p) (*((SLAndroidSimpleBufferQueueItf)(p))) +#else +#define MAL_OPENSL_BUFFERQUEUE(p) (*((SLBufferQueueItf)(p))) +#endif + +#ifdef MAL_ANDROID +//static void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, SLuint32 eventFlags, const void* pBuffer, SLuint32 bufferSize, SLuint32 dataUsed, void* pContext) +static void mal_buffer_queue_callback__opensl_android(SLAndroidSimpleBufferQueueItf pBufferQueue, void* pUserData) +{ + (void)pBufferQueue; + + // For now, only supporting Android implementations of OpenSL|ES since that's the only one I've + // been able to test with and I currently depend on Android-specific extensions (simple buffer + // queues). +#ifndef MAL_ANDROID + return MAL_NO_BACKEND; +#endif + + mal_device* pDevice = (mal_device*)pUserData; + mal_assert(pDevice != NULL); + + // For now, don't do anything unless the buffer was fully processed. From what I can tell, it looks like + // OpenSL|ES 1.1 improves on buffer queues to the point that we could much more intelligently handle this, + // but unfortunately it looks like Android is only supporting OpenSL|ES 1.0.1 for now :( + if (pDevice->state != MAL_STATE_STARTED) { + return; + } + + size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + mal_uint8* pBuffer = pDevice->opensl.pBuffer + (pDevice->opensl.currentBufferIndex * periodSizeInBytes); + + if (pDevice->type == mal_device_type_playback) { + if (pDevice->state != MAL_STATE_STARTED) { + return; + } + + mal_device__read_frames_from_client(pDevice, pDevice->opensl.periodSizeInFrames, pBuffer); + + SLresult resultSL = MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, pBuffer, periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + return; + } + } else { + mal_device__send_frames_to_client(pDevice, pDevice->opensl.periodSizeInFrames, pBuffer); + + SLresult resultSL = MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, pBuffer, periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + return; + } + } + + pDevice->opensl.currentBufferIndex = (pDevice->opensl.currentBufferIndex + 1) % pDevice->periods; +} +#endif + +static void mal_device_uninit__opensl(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + // Uninit device. + if (pDevice->type == mal_device_type_playback) { + if (pDevice->opensl.pAudioPlayerObj) MAL_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioPlayerObj); + if (pDevice->opensl.pOutputMixObj) MAL_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Destroy((SLObjectItf)pDevice->opensl.pOutputMixObj); + } else { + if (pDevice->opensl.pAudioRecorderObj) MAL_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Destroy((SLObjectItf)pDevice->opensl.pAudioRecorderObj); + } + + mal_free(pDevice->opensl.pBuffer); + + + // Uninit global data. + if (g_malOpenSLInitCounter > 0) { + if (mal_atomic_decrement_32(&g_malOpenSLInitCounter) == 0) { + (*g_malEngineObjectSL)->Destroy(g_malEngineObjectSL); + } + } +} + +static mal_result mal_device_init__opensl(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + (void)pContext; + + // For now, only supporting Android implementations of OpenSL|ES since that's the only one I've + // been able to test with and I currently depend on Android-specific extensions (simple buffer + // queues). +#ifndef MAL_ANDROID + return MAL_NO_BACKEND; +#endif + + // Use s32 as the internal format for when floating point is specified. + if (pConfig->format == mal_format_f32) { + pDevice->internalFormat = mal_format_s32; + } + + // Initialize global data first if applicable. + if (mal_atomic_increment_32(&g_malOpenSLInitCounter) == 1) { + SLresult resultSL = slCreateEngine(&g_malEngineObjectSL, 0, NULL, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + mal_atomic_decrement_32(&g_malOpenSLInitCounter); + return mal_post_error(pDevice, "[OpenSL] slCreateEngine() failed.", MAL_NO_BACKEND); + } + + (*g_malEngineObjectSL)->Realize(g_malEngineObjectSL, SL_BOOLEAN_FALSE); + + resultSL = (*g_malEngineObjectSL)->GetInterface(g_malEngineObjectSL, SL_IID_ENGINE, &g_malEngineSL); + if (resultSL != SL_RESULT_SUCCESS) { + (*g_malEngineObjectSL)->Destroy(g_malEngineObjectSL); + mal_atomic_decrement_32(&g_malOpenSLInitCounter); + return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_ENGINE interface.", MAL_NO_BACKEND); + } + } + + + // Now we can start initializing the device properly. + mal_assert(pDevice != NULL); + mal_zero_object(&pDevice->opensl); + + pDevice->opensl.currentBufferIndex = 0; + pDevice->opensl.periodSizeInFrames = pDevice->bufferSizeInFrames / pConfig->periods; + pDevice->bufferSizeInFrames = pDevice->opensl.periodSizeInFrames * pConfig->periods; + + SLDataLocator_AndroidSimpleBufferQueue queue; + queue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + queue.numBuffers = pConfig->periods; + + SLDataFormat_PCM* pFormat = NULL; + +#if defined(MAL_ANDROID) && __ANDROID_API__ >= 21 + SLAndroidDataFormat_PCM_EX pcmEx; + if (pDevice->format == mal_format_f32 /*|| pDevice->format == mal_format_f64*/) { + pcmEx.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + pcmEx.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; + } else { + pcmEx.formatType = SL_DATAFORMAT_PCM; + } + pFormat = (SLDataFormat_PCM*)&pcmEx; +#else + SLDataFormat_PCM pcm; + pcm.formatType = SL_DATAFORMAT_PCM; + pFormat = &pcm; +#endif + + pFormat->numChannels = pDevice->channels; + pFormat->samplesPerSec = mal_round_to_standard_sample_rate__opensl(pDevice->sampleRate * 1000); // In millihertz. + pFormat->bitsPerSample = mal_get_sample_size_in_bytes(pDevice->format)*8; + pFormat->containerSize = pFormat->bitsPerSample; // Always tightly packed for now. + pFormat->channelMask = mal_channel_map_to_channel_mask__opensl(pConfig->channelMap, pFormat->numChannels); + pFormat->endianness = SL_BYTEORDER_LITTLEENDIAN; + + // Android has a few restrictions on the format as documented here: https://developer.android.com/ndk/guides/audio/opensl-for-android.html + // - Only mono and stereo is supported. + // - Only u8 and s16 formats are supported. + // - Limited to a sample rate of 48000. +#ifdef MAL_ANDROID + if (pFormat->numChannels > 2) { + pFormat->numChannels = 2; + } +#if __ANDROID_API__ >= 21 + if (pFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { + // It's floating point. + mal_assert(pcmEx.representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); + if (pFormat->bitsPerSample > 32) { + pFormat->bitsPerSample = 32; + } + } else { + if (pFormat->bitsPerSample > 16) { + pFormat->bitsPerSample = 16; + } + } +#else + if (pFormat->bitsPerSample > 16) { + pFormat->bitsPerSample = 16; + } +#endif + pFormat->containerSize = pFormat->bitsPerSample; // Always tightly packed for now. + + if (pFormat->samplesPerSec > SL_SAMPLINGRATE_48) { + pFormat->samplesPerSec = SL_SAMPLINGRATE_48; + } +#endif + + if (type == mal_device_type_playback) { + SLresult resultSL = (*g_malEngineSL)->CreateOutputMix(g_malEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); + if (resultSL != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to create output mix.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->Realize((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_BOOLEAN_FALSE)) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to realize output mix object.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + // Set the output device. + if (pDeviceID != NULL) { + MAL_OPENSL_OUTPUTMIX(pDevice->opensl.pOutputMix)->ReRoute((SLOutputMixItf)pDevice->opensl.pOutputMix, 1, &pDeviceID->opensl); + } + + SLDataSource source; + source.pLocator = &queue; + source.pFormat = pFormat; + + SLDataLocator_OutputMix outmixLocator; + outmixLocator.locatorType = SL_DATALOCATOR_OUTPUTMIX; + outmixLocator.outputMix = (SLObjectItf)pDevice->opensl.pOutputMixObj; + + SLDataSink sink; + sink.pLocator = &outmixLocator; + sink.pFormat = NULL; + + const SLInterfaceID itfIDs1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean itfIDsRequired1[] = {SL_BOOLEAN_TRUE}; + resultSL = (*g_malEngineSL)->CreateAudioPlayer(g_malEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + // Unsupported format. Fall back to something safer and try again. If this fails, just abort. + pFormat->formatType = SL_DATAFORMAT_PCM; + pFormat->numChannels = 2; + pFormat->samplesPerSec = SL_SAMPLINGRATE_16; + pFormat->bitsPerSample = 16; + pFormat->containerSize = pFormat->bitsPerSample; // Always tightly packed for now. + pFormat->channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + resultSL = (*g_malEngineSL)->CreateAudioPlayer(g_malEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioPlayerObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + } + + if (resultSL != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to create audio player.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + + if (MAL_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to realize audio player.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_IID_PLAY, &pDevice->opensl.pAudioPlayer) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_PLAY interface.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueue) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, mal_buffer_queue_callback__opensl_android, pDevice) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to register buffer queue callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } else { + SLDataLocator_IODevice locatorDevice; + locatorDevice.locatorType = SL_DATALOCATOR_IODEVICE; + locatorDevice.deviceType = SL_IODEVICE_AUDIOINPUT; + locatorDevice.deviceID = (pDeviceID == NULL) ? SL_DEFAULTDEVICEID_AUDIOINPUT : pDeviceID->opensl; + locatorDevice.device = NULL; + + SLDataSource source; + source.pLocator = &locatorDevice; + source.pFormat = NULL; + + SLDataSink sink; + sink.pLocator = &queue; + sink.pFormat = pFormat; + + const SLInterfaceID itfIDs1[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; + const SLboolean itfIDsRequired1[] = {SL_BOOLEAN_TRUE}; + SLresult resultSL = (*g_malEngineSL)->CreateAudioRecorder(g_malEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + if (resultSL == SL_RESULT_CONTENT_UNSUPPORTED) { + // Unsupported format. Fall back to something safer and try again. If this fails, just abort. + pFormat->formatType = SL_DATAFORMAT_PCM; + pFormat->numChannels = 1; + pFormat->samplesPerSec = SL_SAMPLINGRATE_16; + pFormat->bitsPerSample = 16; + pFormat->containerSize = pFormat->bitsPerSample; // Always tightly packed for now. + pFormat->channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + resultSL = (*g_malEngineSL)->CreateAudioRecorder(g_malEngineSL, (SLObjectItf*)&pDevice->opensl.pAudioRecorderObj, &source, &sink, 1, itfIDs1, itfIDsRequired1); + } + + if (resultSL != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to create audio recorder.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to realize audio recorder.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_IID_RECORD, &pDevice->opensl.pAudioRecorder) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_RECORD interface.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueue) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + + if (MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->RegisterCallback((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, mal_buffer_queue_callback__opensl_android, pDevice) != SL_RESULT_SUCCESS) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to register buffer queue callback.", MAL_FAILED_TO_OPEN_BACKEND_DEVICE); + } + } + + + // The internal format is determined by the pFormat object. + mal_bool32 isFloatingPoint = MAL_FALSE; +#if defined(MAL_ANDROID) && __ANDROID_API__ >= 21 + if (pFormat->formatType == SL_ANDROID_DATAFORMAT_PCM_EX) { + mal_assert(pcmEx.representation == SL_ANDROID_PCM_REPRESENTATION_FLOAT); + isFloatingPoint = MAL_TRUE; + } +#endif + if (isFloatingPoint) { + if (pFormat->bitsPerSample == 32) { + pDevice->internalFormat = mal_format_f32; + } +#if 0 + if (pFormat->bitsPerSample == 64) { + pDevice->internalFormat = mal_format_f64; + } +#endif + } else { + if (pFormat->bitsPerSample == 8) { + pDevice->internalFormat = mal_format_u8; + } else if (pFormat->bitsPerSample == 16) { + pDevice->internalFormat = mal_format_s16; + } else if (pFormat->bitsPerSample == 24) { + pDevice->internalFormat = mal_format_s24; + } else if (pFormat->bitsPerSample == 32) { + pDevice->internalFormat = mal_format_s32; + } + } + + pDevice->internalChannels = pFormat->numChannels; + pDevice->internalSampleRate = pFormat->samplesPerSec / 1000; + mal_channel_mask_to_channel_map__opensl(pFormat->channelMask, pDevice->internalChannels, pDevice->internalChannelMap); + + + size_t bufferSizeInBytes = pDevice->bufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + pDevice->opensl.pBuffer = (mal_uint8*)mal_malloc(bufferSizeInBytes); + if (pDevice->opensl.pBuffer == NULL) { + mal_device_uninit__opensl(pDevice); + return mal_post_error(pDevice, "[OpenSL] Failed to allocate memory for data buffer.", MAL_OUT_OF_MEMORY); + } + + mal_zero_memory(pDevice->opensl.pBuffer, bufferSizeInBytes); + + return MAL_SUCCESS; +} + +static mal_result mal_device__start_backend__opensl(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + SLresult resultSL = MAL_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_PLAYING); + if (resultSL != SL_RESULT_SUCCESS) { + return mal_post_error(pDevice, "[OpenSL] Failed to start internal playback device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + + // We need to enqueue a buffer for each period. + mal_device__read_frames_from_client(pDevice, pDevice->bufferSizeInFrames, pDevice->opensl.pBuffer); + + size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; ++iPeriod) { + resultSL = MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, pDevice->opensl.pBuffer + (periodSizeInBytes * iPeriod), periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + MAL_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); + return mal_post_error(pDevice, "[OpenSL] Failed to enqueue buffer for playback device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + } else { + SLresult resultSL = MAL_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_RECORDING); + if (resultSL != SL_RESULT_SUCCESS) { + return mal_post_error(pDevice, "[OpenSL] Failed to start internal capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + + size_t periodSizeInBytes = pDevice->opensl.periodSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat); + for (mal_uint32 iPeriod = 0; iPeriod < pDevice->periods; ++iPeriod) { + resultSL = MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue, pDevice->opensl.pBuffer + (periodSizeInBytes * iPeriod), periodSizeInBytes); + if (resultSL != SL_RESULT_SUCCESS) { + MAL_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); + return mal_post_error(pDevice, "[OpenSL] Failed to enqueue buffer for capture device.", MAL_FAILED_TO_START_BACKEND_DEVICE); + } + } + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__opensl(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + SLresult resultSL = MAL_OPENSL_PLAY(pDevice->opensl.pAudioPlayer)->SetPlayState((SLPlayItf)pDevice->opensl.pAudioPlayer, SL_PLAYSTATE_STOPPED); + if (resultSL != SL_RESULT_SUCCESS) { + return mal_post_error(pDevice, "[OpenSL] Failed to stop internal playback device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + } else { + SLresult resultSL = MAL_OPENSL_RECORD(pDevice->opensl.pAudioRecorder)->SetRecordState((SLRecordItf)pDevice->opensl.pAudioRecorder, SL_RECORDSTATE_STOPPED); + if (resultSL != SL_RESULT_SUCCESS) { + return mal_post_error(pDevice, "[OpenSL] Failed to stop internal capture device.", MAL_FAILED_TO_STOP_BACKEND_DEVICE); + } + } + + // Make sure any queued buffers are cleared. + MAL_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueue)->Clear((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueue); + + // Make sure the client is aware that the device has stopped. There may be an OpenSL|ES callback for this, but I haven't found it. + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + if (pDevice->onStop) { + pDevice->onStop(pDevice); + } + + return MAL_SUCCESS; +} +#endif // OpenSL|ES + +/////////////////////////////////////////////////////////////////////////////// +// +// OpenAL Backend +// +/////////////////////////////////////////////////////////////////////////////// +#ifdef MAL_ENABLE_OPENAL +#ifdef MAL_WIN32 +#define MAL_AL_APIENTRY __cdecl +#else +#define MAL_AL_APIENTRY +#endif + +typedef struct mal_ALCdevice_struct mal_ALCdevice; +typedef struct mal_ALCcontext_struct mal_ALCcontext; +typedef char mal_ALCboolean; +typedef char mal_ALCchar; +typedef signed char mal_ALCbyte; +typedef unsigned char mal_ALCubyte; +typedef short mal_ALCshort; +typedef unsigned short mal_ALCushort; +typedef int mal_ALCint; +typedef unsigned int mal_ALCuint; +typedef int mal_ALCsizei; +typedef int mal_ALCenum; +typedef float mal_ALCfloat; +typedef double mal_ALCdouble; +typedef void mal_ALCvoid; + +typedef mal_ALCboolean mal_ALboolean; +typedef mal_ALCchar mal_ALchar; +typedef mal_ALCbyte mal_ALbyte; +typedef mal_ALCubyte mal_ALubyte; +typedef mal_ALCshort mal_ALshort; +typedef mal_ALCushort mal_ALushort; +typedef mal_ALCint mal_ALint; +typedef mal_ALCuint mal_ALuint; +typedef mal_ALCsizei mal_ALsizei; +typedef mal_ALCenum mal_ALenum; +typedef mal_ALCfloat mal_ALfloat; +typedef mal_ALCdouble mal_ALdouble; +typedef mal_ALCvoid mal_ALvoid; + +#define MAL_ALC_DEVICE_SPECIFIER 0x1005 +#define MAL_ALC_CAPTURE_DEVICE_SPECIFIER 0x310 +#define MAL_ALC_CAPTURE_SAMPLES 0x312 + +#define MAL_AL_SOURCE_STATE 0x1010 +#define MAL_AL_INITIAL 0x1011 +#define MAL_AL_PLAYING 0x1012 +#define MAL_AL_PAUSED 0x1013 +#define MAL_AL_STOPPED 0x1014 +#define MAL_AL_BUFFERS_PROCESSED 0x1016 + +#define MAL_AL_FORMAT_MONO8 0x1100 +#define MAL_AL_FORMAT_MONO16 0x1101 +#define MAL_AL_FORMAT_STEREO8 0x1102 +#define MAL_AL_FORMAT_STEREO16 0x1103 +#define MAL_AL_FORMAT_MONO_FLOAT32 0x10010 +#define MAL_AL_FORMAT_STEREO_FLOAT32 0x10011 +#define MAL_AL_FORMAT_51CHN16 0x120B +#define MAL_AL_FORMAT_51CHN32 0x120C +#define MAL_AL_FORMAT_51CHN8 0x120A +#define MAL_AL_FORMAT_61CHN16 0x120E +#define MAL_AL_FORMAT_61CHN32 0x120F +#define MAL_AL_FORMAT_61CHN8 0x120D +#define MAL_AL_FORMAT_71CHN16 0x1211 +#define MAL_AL_FORMAT_71CHN32 0x1212 +#define MAL_AL_FORMAT_71CHN8 0x1210 +#define MAL_AL_FORMAT_QUAD16 0x1205 +#define MAL_AL_FORMAT_QUAD32 0x1206 +#define MAL_AL_FORMAT_QUAD8 0x1204 +#define MAL_AL_FORMAT_REAR16 0x1208 +#define MAL_AL_FORMAT_REAR32 0x1209 +#define MAL_AL_FORMAT_REAR8 0x1207 + +typedef mal_ALCcontext* (MAL_AL_APIENTRY * MAL_LPALCCREATECONTEXT) (mal_ALCdevice *device, const mal_ALCint *attrlist); +typedef mal_ALCboolean (MAL_AL_APIENTRY * MAL_LPALCMAKECONTEXTCURRENT) (mal_ALCcontext *context); +typedef void (MAL_AL_APIENTRY * MAL_LPALCPROCESSCONTEXT) (mal_ALCcontext *context); +typedef void (MAL_AL_APIENTRY * MAL_LPALCSUSPENDCONTEXT) (mal_ALCcontext *context); +typedef void (MAL_AL_APIENTRY * MAL_LPALCDESTROYCONTEXT) (mal_ALCcontext *context); +typedef mal_ALCcontext* (MAL_AL_APIENTRY * MAL_LPALCGETCURRENTCONTEXT) (void); +typedef mal_ALCdevice* (MAL_AL_APIENTRY * MAL_LPALCGETCONTEXTSDEVICE) (mal_ALCcontext *context); +typedef mal_ALCdevice* (MAL_AL_APIENTRY * MAL_LPALCOPENDEVICE) (const mal_ALCchar *devicename); +typedef mal_ALCboolean (MAL_AL_APIENTRY * MAL_LPALCCLOSEDEVICE) (mal_ALCdevice *device); +typedef mal_ALCenum (MAL_AL_APIENTRY * MAL_LPALCGETERROR) (mal_ALCdevice *device); +typedef mal_ALCboolean (MAL_AL_APIENTRY * MAL_LPALCISEXTENSIONPRESENT) (mal_ALCdevice *device, const mal_ALCchar *extname); +typedef void* (MAL_AL_APIENTRY * MAL_LPALCGETPROCADDRESS) (mal_ALCdevice *device, const mal_ALCchar *funcname); +typedef mal_ALCenum (MAL_AL_APIENTRY * MAL_LPALCGETENUMVALUE) (mal_ALCdevice *device, const mal_ALCchar *enumname); +typedef const mal_ALCchar* (MAL_AL_APIENTRY * MAL_LPALCGETSTRING) (mal_ALCdevice *device, mal_ALCenum param); +typedef void (MAL_AL_APIENTRY * MAL_LPALCGETINTEGERV) (mal_ALCdevice *device, mal_ALCenum param, mal_ALCsizei size, mal_ALCint *values); +typedef mal_ALCdevice* (MAL_AL_APIENTRY * MAL_LPALCCAPTUREOPENDEVICE) (const mal_ALCchar *devicename, mal_ALCuint frequency, mal_ALCenum format, mal_ALCsizei buffersize); +typedef mal_ALCboolean (MAL_AL_APIENTRY * MAL_LPALCCAPTURECLOSEDEVICE) (mal_ALCdevice *device); +typedef void (MAL_AL_APIENTRY * MAL_LPALCCAPTURESTART) (mal_ALCdevice *device); +typedef void (MAL_AL_APIENTRY * MAL_LPALCCAPTURESTOP) (mal_ALCdevice *device); +typedef void (MAL_AL_APIENTRY * MAL_LPALCCAPTURESAMPLES) (mal_ALCdevice *device, mal_ALCvoid *buffer, mal_ALCsizei samples); + +typedef void (MAL_AL_APIENTRY * MAL_LPALENABLE) (mal_ALenum capability); +typedef void (MAL_AL_APIENTRY * MAL_LPALDISABLE) (mal_ALenum capability); +typedef mal_ALboolean (MAL_AL_APIENTRY * MAL_LPALISENABLED) (mal_ALenum capability); +typedef const mal_ALchar* (MAL_AL_APIENTRY * MAL_LPALGETSTRING) (mal_ALenum param); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBOOLEANV) (mal_ALenum param, mal_ALboolean *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETINTEGERV) (mal_ALenum param, mal_ALint *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETFLOATV) (mal_ALenum param, mal_ALfloat *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETDOUBLEV) (mal_ALenum param, mal_ALdouble *values); +typedef mal_ALboolean (MAL_AL_APIENTRY * MAL_LPALGETBOOLEAN) (mal_ALenum param); +typedef mal_ALint (MAL_AL_APIENTRY * MAL_LPALGETINTEGER) (mal_ALenum param); +typedef mal_ALfloat (MAL_AL_APIENTRY * MAL_LPALGETFLOAT) (mal_ALenum param); +typedef mal_ALdouble (MAL_AL_APIENTRY * MAL_LPALGETDOUBLE) (mal_ALenum param); +typedef mal_ALenum (MAL_AL_APIENTRY * MAL_LPALGETERROR) (void); +typedef mal_ALboolean (MAL_AL_APIENTRY * MAL_LPALISEXTENSIONPRESENT) (const mal_ALchar *extname); +typedef void* (MAL_AL_APIENTRY * MAL_LPALGETPROCADDRESS) (const mal_ALchar *fname); +typedef mal_ALenum (MAL_AL_APIENTRY * MAL_LPALGETENUMVALUE) (const mal_ALchar *ename); +typedef void (MAL_AL_APIENTRY * MAL_LPALGENSOURCES) (mal_ALsizei n, mal_ALuint *sources); +typedef void (MAL_AL_APIENTRY * MAL_LPALDELETESOURCES) (mal_ALsizei n, const mal_ALuint *sources); +typedef mal_ALboolean (MAL_AL_APIENTRY * MAL_LPALISSOURCE) (mal_ALuint source); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEF) (mal_ALuint source, mal_ALenum param, mal_ALfloat value); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCE3F) (mal_ALuint source, mal_ALenum param, mal_ALfloat value1, mal_ALfloat value2, mal_ALfloat value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEFV) (mal_ALuint source, mal_ALenum param, const mal_ALfloat *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEI) (mal_ALuint source, mal_ALenum param, mal_ALint value); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCE3I) (mal_ALuint source, mal_ALenum param, mal_ALint value1, mal_ALint value2, mal_ALint value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEIV) (mal_ALuint source, mal_ALenum param, const mal_ALint *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETSOURCEF) (mal_ALuint source, mal_ALenum param, mal_ALfloat *value); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETSOURCE3F) (mal_ALuint source, mal_ALenum param, mal_ALfloat *value1, mal_ALfloat *value2, mal_ALfloat *value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETSOURCEFV) (mal_ALuint source, mal_ALenum param, mal_ALfloat *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETSOURCEI) (mal_ALuint source, mal_ALenum param, mal_ALint *value); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETSOURCE3I) (mal_ALuint source, mal_ALenum param, mal_ALint *value1, mal_ALint *value2, mal_ALint *value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETSOURCEIV) (mal_ALuint source, mal_ALenum param, mal_ALint *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEPLAYV) (mal_ALsizei n, const mal_ALuint *sources); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCESTOPV) (mal_ALsizei n, const mal_ALuint *sources); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEREWINDV) (mal_ALsizei n, const mal_ALuint *sources); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEPAUSEV) (mal_ALsizei n, const mal_ALuint *sources); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEPLAY) (mal_ALuint source); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCESTOP) (mal_ALuint source); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEREWIND) (mal_ALuint source); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEPAUSE) (mal_ALuint source); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEQUEUEBUFFERS) (mal_ALuint source, mal_ALsizei nb, const mal_ALuint *buffers); +typedef void (MAL_AL_APIENTRY * MAL_LPALSOURCEUNQUEUEBUFFERS)(mal_ALuint source, mal_ALsizei nb, mal_ALuint *buffers); +typedef void (MAL_AL_APIENTRY * MAL_LPALGENBUFFERS) (mal_ALsizei n, mal_ALuint *buffers); +typedef void (MAL_AL_APIENTRY * MAL_LPALDELETEBUFFERS) (mal_ALsizei n, const mal_ALuint *buffers); +typedef mal_ALboolean (MAL_AL_APIENTRY * MAL_LPALISBUFFER) (mal_ALuint buffer); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFERDATA) (mal_ALuint buffer, mal_ALenum format, const mal_ALvoid *data, mal_ALsizei size, mal_ALsizei freq); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFERF) (mal_ALuint buffer, mal_ALenum param, mal_ALfloat value); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFER3F) (mal_ALuint buffer, mal_ALenum param, mal_ALfloat value1, mal_ALfloat value2, mal_ALfloat value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFERFV) (mal_ALuint buffer, mal_ALenum param, const mal_ALfloat *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFERI) (mal_ALuint buffer, mal_ALenum param, mal_ALint value); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFER3I) (mal_ALuint buffer, mal_ALenum param, mal_ALint value1, mal_ALint value2, mal_ALint value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALBUFFERIV) (mal_ALuint buffer, mal_ALenum param, const mal_ALint *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFERF) (mal_ALuint buffer, mal_ALenum param, mal_ALfloat *value); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFER3F) (mal_ALuint buffer, mal_ALenum param, mal_ALfloat *value1, mal_ALfloat *value2, mal_ALfloat *value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFERFV) (mal_ALuint buffer, mal_ALenum param, mal_ALfloat *values); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFERI) (mal_ALuint buffer, mal_ALenum param, mal_ALint *value); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFER3I) (mal_ALuint buffer, mal_ALenum param, mal_ALint *value1, mal_ALint *value2, mal_ALint *value3); +typedef void (MAL_AL_APIENTRY * MAL_LPALGETBUFFERIV) (mal_ALuint buffer, mal_ALenum param, mal_ALint *values); + +mal_result mal_context_init__openal(mal_context* pContext) +{ + mal_assert(pContext != NULL); + + const char* libName = NULL; +#ifdef MAL_WIN32 + libName = "OpenAL32.dll"; +#endif +#if defined(MAL_UNIX) && !defined(MAL_APPLE) + libName = "libopenal.so"; +#endif +#ifdef MAL_APPLE + // I don't own a Mac so a contribution here would be much appreciated! Just don't know what the library is called... +#endif + if (libName == NULL) { + return MAL_NO_BACKEND; // Don't know what the library name is called. + } + + + pContext->openal.hOpenAL = mal_dlopen(libName); + +#ifdef MAL_WIN32 + // Special case for Win32 - try "soft_oal.dll" for OpenAL-Soft drop-ins. + if (pContext->openal.hOpenAL == NULL) { + pContext->openal.hOpenAL = mal_dlopen("soft_oal.dll"); + } +#endif + + if (pContext->openal.hOpenAL == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + pContext->openal.alcCreateContext = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCreateContext"); + pContext->openal.alcMakeContextCurrent = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcMakeContextCurrent"); + pContext->openal.alcProcessContext = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcProcessContext"); + pContext->openal.alcSuspendContext = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcSuspendContext"); + pContext->openal.alcDestroyContext = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcDestroyContext"); + pContext->openal.alcGetCurrentContext = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetCurrentContext"); + pContext->openal.alcGetContextsDevice = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetContextsDevice"); + pContext->openal.alcOpenDevice = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcOpenDevice"); + pContext->openal.alcCloseDevice = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCloseDevice"); + pContext->openal.alcGetError = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetError"); + pContext->openal.alcIsExtensionPresent = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcIsExtensionPresent"); + pContext->openal.alcGetProcAddress = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetProcAddress"); + pContext->openal.alcGetEnumValue = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetEnumValue"); + pContext->openal.alcGetString = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetString"); + pContext->openal.alcGetIntegerv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcGetIntegerv"); + pContext->openal.alcCaptureOpenDevice = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCaptureOpenDevice"); + pContext->openal.alcCaptureCloseDevice = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCaptureCloseDevice"); + pContext->openal.alcCaptureStart = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCaptureStart"); + pContext->openal.alcCaptureStop = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCaptureStop"); + pContext->openal.alcCaptureSamples = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alcCaptureSamples"); + + pContext->openal.alEnable = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alEnable"); + pContext->openal.alDisable = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alDisable"); + pContext->openal.alIsEnabled = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alIsEnabled"); + pContext->openal.alGetString = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetString"); + pContext->openal.alGetBooleanv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBooleanv"); + pContext->openal.alGetIntegerv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetIntegerv"); + pContext->openal.alGetFloatv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetFloatv"); + pContext->openal.alGetDoublev = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetDoublev"); + pContext->openal.alGetBoolean = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBoolean"); + pContext->openal.alGetInteger = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetInteger"); + pContext->openal.alGetFloat = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetFloat"); + pContext->openal.alGetDouble = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetDouble"); + pContext->openal.alGetError = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetError"); + pContext->openal.alIsExtensionPresent = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alIsExtensionPresent"); + pContext->openal.alGetProcAddress = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetProcAddress"); + pContext->openal.alGetEnumValue = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetEnumValue"); + pContext->openal.alGenSources = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGenSources"); + pContext->openal.alDeleteSources = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alDeleteSources"); + pContext->openal.alIsSource = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alIsSource"); + pContext->openal.alSourcef = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcef"); + pContext->openal.alSource3f = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSource3f"); + pContext->openal.alSourcefv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcefv"); + pContext->openal.alSourcei = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcei"); + pContext->openal.alSource3i = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSource3i"); + pContext->openal.alSourceiv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceiv"); + pContext->openal.alGetSourcef = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetSourcef"); + pContext->openal.alGetSource3f = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetSource3f"); + pContext->openal.alGetSourcefv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetSourcefv"); + pContext->openal.alGetSourcei = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetSourcei"); + pContext->openal.alGetSource3i = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetSource3i"); + pContext->openal.alGetSourceiv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetSourceiv"); + pContext->openal.alSourcePlayv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcePlayv"); + pContext->openal.alSourceStopv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceStopv"); + pContext->openal.alSourceRewindv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceRewindv"); + pContext->openal.alSourcePausev = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcePausev"); + pContext->openal.alSourcePlay = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcePlay"); + pContext->openal.alSourceStop = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceStop"); + pContext->openal.alSourceRewind = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceRewind"); + pContext->openal.alSourcePause = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourcePause"); + pContext->openal.alSourceQueueBuffers = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceQueueBuffers"); + pContext->openal.alSourceUnqueueBuffers = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alSourceUnqueueBuffers"); + pContext->openal.alGenBuffers = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGenBuffers"); + pContext->openal.alDeleteBuffers = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alDeleteBuffers"); + pContext->openal.alIsBuffer = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alIsBuffer"); + pContext->openal.alBufferData = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBufferData"); + pContext->openal.alBufferf = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBufferf"); + pContext->openal.alBuffer3f = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBuffer3f"); + pContext->openal.alBufferfv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBufferfv"); + pContext->openal.alBufferi = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBufferi"); + pContext->openal.alBuffer3i = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBuffer3i"); + pContext->openal.alBufferiv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alBufferiv"); + pContext->openal.alGetBufferf = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBufferf"); + pContext->openal.alGetBuffer3f = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBuffer3f"); + pContext->openal.alGetBufferfv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBufferfv"); + pContext->openal.alGetBufferi = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBufferi"); + pContext->openal.alGetBuffer3i = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBuffer3i"); + pContext->openal.alGetBufferiv = (mal_proc)mal_dlsym(pContext->openal.hOpenAL, "alGetBufferiv"); + + // We depend on the ALC_ENUMERATION_EXT extension. + if (!((MAL_LPALCISEXTENSIONPRESENT)pContext->openal.alcIsExtensionPresent)(NULL, "ALC_ENUMERATION_EXT")) { + mal_dlclose(pContext->openal.hOpenAL); + return MAL_FAILED_TO_INIT_BACKEND; + } + + pContext->openal.isFloat32Supported = ((MAL_LPALISEXTENSIONPRESENT)pContext->openal.alIsExtensionPresent)("AL_EXT_float32"); + pContext->openal.isMCFormatsSupported = ((MAL_LPALISEXTENSIONPRESENT)pContext->openal.alIsExtensionPresent)("AL_EXT_MCFORMATS"); + + return MAL_SUCCESS; +} + +mal_result mal_context_uninit__openal(mal_context* pContext) +{ + mal_assert(pContext != NULL); + mal_assert(pContext->backend == mal_backend_openal); + + mal_dlclose(pContext->openal.hOpenAL); + return MAL_SUCCESS; +} + +mal_result mal_enumerate_devices__openal(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + mal_uint32 infoSize = *pCount; + *pCount = 0; + + const mal_ALCchar* pDeviceNames = ((MAL_LPALCGETSTRING)pContext->openal.alcGetString)(NULL, (type == mal_device_type_playback) ? MAL_ALC_DEVICE_SPECIFIER : MAL_ALC_CAPTURE_DEVICE_SPECIFIER); + if (pDeviceNames == NULL) { + return MAL_NO_DEVICE; + } + + // Each device is stored in pDeviceNames, separated by a null-terminator. The string itself is double-null-terminated. + const mal_ALCchar* pNextDeviceName = pDeviceNames; + while (pNextDeviceName[0] != '\0') { + if (pInfo != NULL) { + if (infoSize > 0) { + mal_strncpy_s(pInfo->id.openal, sizeof(pInfo->id.openal), (const char*)pNextDeviceName, (size_t)-1); + mal_strncpy_s(pInfo->name, sizeof(pInfo->name), (const char*)pNextDeviceName, (size_t)-1); + + pInfo += 1; + infoSize -= 1; + *pCount += 1; + } + } else { + *pCount += 1; + } + + // Move to the next device name. + while (*pNextDeviceName != '\0') { + pNextDeviceName += 1; + } + + // Skip past the null terminator. + pNextDeviceName += 1; + }; + + return MAL_SUCCESS; +} + +static void mal_device_uninit__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)(NULL); + ((MAL_LPALCDESTROYCONTEXT)pDevice->pContext->openal.alcDestroyContext)((mal_ALCcontext*)pDevice->openal.pContextALC); + + if (pDevice->type == mal_device_type_playback) { + ((MAL_LPALCCLOSEDEVICE)pDevice->pContext->openal.alcCloseDevice)((mal_ALCdevice*)pDevice->openal.pDeviceALC); + } else { + ((MAL_LPALCCAPTURECLOSEDEVICE)pDevice->pContext->openal.alcCaptureCloseDevice)((mal_ALCdevice*)pDevice->openal.pDeviceALC); + } + + mal_free(pDevice->openal.pIntermediaryBuffer); +} + +static mal_result mal_device_init__openal(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, mal_device* pDevice) +{ + if (pDevice->periods > MAL_MAX_PERIODS_OPENAL) { + pDevice->periods = MAL_MAX_PERIODS_OPENAL; + } + + // OpenAL has bad latency in my testing :( + if (pDevice->usingDefaultBufferSize) { + pDevice->bufferSizeInFrames *= 4; + } + + mal_ALCsizei bufferSizeInSamplesAL = pDevice->bufferSizeInFrames; + mal_ALCuint frequencyAL = pConfig->sampleRate; + + mal_uint32 channelsAL = 0; + + // OpenAL currently only supports only mono and stereo. TODO: Check for the AL_EXT_MCFORMATS extension and use one of those formats for quad, 5.1, etc. + mal_ALCenum formatAL = 0; + if (pConfig->channels == 1) { + // Mono. + channelsAL = 1; + if (pConfig->format == mal_format_f32) { + if (pContext->openal.isFloat32Supported) { + formatAL = MAL_AL_FORMAT_MONO_FLOAT32; + } else { + formatAL = MAL_AL_FORMAT_MONO16; + } + } else if (pConfig->format == mal_format_s32) { + formatAL = MAL_AL_FORMAT_MONO16; + } else if (pConfig->format == mal_format_s24) { + formatAL = MAL_AL_FORMAT_MONO16; + } else if (pConfig->format == mal_format_s16) { + formatAL = MAL_AL_FORMAT_MONO16; + } else if (pConfig->format == mal_format_u8) { + formatAL = MAL_AL_FORMAT_MONO8; + } + } else { + // Stereo. + channelsAL = 2; + if (pConfig->format == mal_format_f32) { + if (pContext->openal.isFloat32Supported) { + formatAL = MAL_AL_FORMAT_STEREO_FLOAT32; + } else { + formatAL = MAL_AL_FORMAT_STEREO16; + } + } else if (pConfig->format == mal_format_s32) { + formatAL = MAL_AL_FORMAT_STEREO16; + } else if (pConfig->format == mal_format_s24) { + formatAL = MAL_AL_FORMAT_STEREO16; + } else if (pConfig->format == mal_format_s16) { + formatAL = MAL_AL_FORMAT_STEREO16; + } else if (pConfig->format == mal_format_u8) { + formatAL = MAL_AL_FORMAT_STEREO8; + } + } + + if (formatAL == 0) { + return MAL_FORMAT_NOT_SUPPORTED; + } + + bufferSizeInSamplesAL *= channelsAL; + + + // OpenAL feels a bit unintuitive to me... The global object is a device, and it would appear that each device can have + // many context's... + mal_ALCdevice* pDeviceALC = NULL; + if (type == mal_device_type_playback) { + pDeviceALC = ((MAL_LPALCOPENDEVICE)pContext->openal.alcOpenDevice)((pDeviceID == NULL) ? NULL : pDeviceID->openal); + } else { + pDeviceALC = ((MAL_LPALCCAPTUREOPENDEVICE)pContext->openal.alcCaptureOpenDevice)((pDeviceID == NULL) ? NULL : pDeviceID->openal, frequencyAL, formatAL, bufferSizeInSamplesAL); + } + + if (pDeviceALC == NULL) { + return mal_context_post_error(pContext, NULL, "[OpenAL] Failed to open device.", MAL_FAILED_TO_INIT_BACKEND); + } + + // A context is only required for playback. + mal_ALCcontext* pContextALC = NULL; + if (pDevice->type == mal_device_type_playback) { + pContextALC = ((MAL_LPALCCREATECONTEXT)pContext->openal.alcCreateContext)(pDeviceALC, NULL); + if (pContextALC == NULL) { + ((MAL_LPALCCLOSEDEVICE)pDevice->pContext->openal.alcCloseDevice)(pDeviceALC); + return mal_context_post_error(pContext, NULL, "[OpenAL] Failed to open OpenAL context.", MAL_FAILED_TO_INIT_BACKEND); + } + + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)(pContextALC); + + mal_ALuint sourceAL; + ((MAL_LPALGENSOURCES)pDevice->pContext->openal.alGenSources)(1, &sourceAL); + pDevice->openal.sourceAL = sourceAL; + + // We create the buffers, but only fill and queue them when the device is started. + mal_ALuint buffersAL[MAL_MAX_PERIODS_OPENAL]; + ((MAL_LPALGENBUFFERS)pDevice->pContext->openal.alGenBuffers)(pDevice->periods, buffersAL); + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + pDevice->openal.buffersAL[i] = buffersAL[i]; + } + } + + pDevice->internalChannels = channelsAL; + pDevice->internalSampleRate = frequencyAL; + + // The internal format is a little bit straight with OpenAL. + switch (formatAL) + { + case MAL_AL_FORMAT_MONO8: + case MAL_AL_FORMAT_STEREO8: + case MAL_AL_FORMAT_REAR8: + case MAL_AL_FORMAT_QUAD8: + case MAL_AL_FORMAT_51CHN8: + case MAL_AL_FORMAT_61CHN8: + case MAL_AL_FORMAT_71CHN8: + { + pDevice->internalFormat = mal_format_u8; + } break; + + case MAL_AL_FORMAT_MONO16: + case MAL_AL_FORMAT_STEREO16: + case MAL_AL_FORMAT_REAR16: + case MAL_AL_FORMAT_QUAD16: + case MAL_AL_FORMAT_51CHN16: + case MAL_AL_FORMAT_61CHN16: + case MAL_AL_FORMAT_71CHN16: + { + pDevice->internalFormat = mal_format_s16; + } break; + + case MAL_AL_FORMAT_REAR32: + case MAL_AL_FORMAT_QUAD32: + case MAL_AL_FORMAT_51CHN32: + case MAL_AL_FORMAT_61CHN32: + case MAL_AL_FORMAT_71CHN32: + { + pDevice->internalFormat = mal_format_s32; + } break; + + case MAL_AL_FORMAT_MONO_FLOAT32: + case MAL_AL_FORMAT_STEREO_FLOAT32: + { + pDevice->internalFormat = mal_format_f32; + } break; + } + + // From what I can tell, the ordering of channels is fixed for OpenAL. + switch (formatAL) + { + case MAL_AL_FORMAT_MONO8: + case MAL_AL_FORMAT_MONO16: + case MAL_AL_FORMAT_MONO_FLOAT32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case MAL_AL_FORMAT_STEREO8: + case MAL_AL_FORMAT_STEREO16: + case MAL_AL_FORMAT_STEREO_FLOAT32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_FRONT_LEFT; + pDevice->internalChannelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + } break; + + case MAL_AL_FORMAT_REAR8: + case MAL_AL_FORMAT_REAR16: + case MAL_AL_FORMAT_REAR32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_BACK_LEFT; + pDevice->internalChannelMap[1] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case MAL_AL_FORMAT_QUAD8: + case MAL_AL_FORMAT_QUAD16: + case MAL_AL_FORMAT_QUAD32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_FRONT_LEFT; + pDevice->internalChannelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + pDevice->internalChannelMap[2] = MAL_CHANNEL_BACK_LEFT; + pDevice->internalChannelMap[3] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case MAL_AL_FORMAT_51CHN8: + case MAL_AL_FORMAT_51CHN16: + case MAL_AL_FORMAT_51CHN32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_FRONT_LEFT; + pDevice->internalChannelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + pDevice->internalChannelMap[2] = MAL_CHANNEL_FRONT_CENTER; + pDevice->internalChannelMap[3] = MAL_CHANNEL_LFE; + pDevice->internalChannelMap[4] = MAL_CHANNEL_BACK_LEFT; + pDevice->internalChannelMap[5] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case MAL_AL_FORMAT_61CHN8: + case MAL_AL_FORMAT_61CHN16: + case MAL_AL_FORMAT_61CHN32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_FRONT_LEFT; + pDevice->internalChannelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + pDevice->internalChannelMap[2] = MAL_CHANNEL_FRONT_CENTER; + pDevice->internalChannelMap[3] = MAL_CHANNEL_LFE; + pDevice->internalChannelMap[4] = MAL_CHANNEL_BACK_CENTER; + pDevice->internalChannelMap[5] = MAL_CHANNEL_SIDE_LEFT; + pDevice->internalChannelMap[6] = MAL_CHANNEL_SIDE_RIGHT; + } break; + + case MAL_AL_FORMAT_71CHN8: + case MAL_AL_FORMAT_71CHN16: + case MAL_AL_FORMAT_71CHN32: + { + pDevice->internalChannelMap[0] = MAL_CHANNEL_FRONT_LEFT; + pDevice->internalChannelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + pDevice->internalChannelMap[2] = MAL_CHANNEL_FRONT_CENTER; + pDevice->internalChannelMap[3] = MAL_CHANNEL_LFE; + pDevice->internalChannelMap[4] = MAL_CHANNEL_BACK_LEFT; + pDevice->internalChannelMap[5] = MAL_CHANNEL_BACK_RIGHT; + pDevice->internalChannelMap[6] = MAL_CHANNEL_SIDE_LEFT; + pDevice->internalChannelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + } break; + + default: break; + } + + pDevice->openal.pDeviceALC = pDeviceALC; + pDevice->openal.pContextALC = pContextALC; + pDevice->openal.formatAL = formatAL; + pDevice->openal.subBufferSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; + pDevice->openal.pIntermediaryBuffer = (mal_uint8*)mal_malloc(pDevice->openal.subBufferSizeInFrames * channelsAL * mal_get_sample_size_in_bytes(pDevice->internalFormat)); + if (pDevice->openal.pIntermediaryBuffer == NULL) { + mal_device_uninit__openal(pDevice); + return mal_context_post_error(pContext, NULL, "[OpenAL] Failed to allocate memory for intermediary buffer.", MAL_OUT_OF_MEMORY); + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__start_backend__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + // Playback. + // + // When starting playback we want to ensure each buffer is filled and queued before playing the source. + pDevice->openal.iNextBuffer = 0; + + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)((mal_ALCcontext*)pDevice->openal.pContextALC); + + for (mal_uint32 i = 0; i < pDevice->periods; ++i) { + mal_device__read_frames_from_client(pDevice, pDevice->openal.subBufferSizeInFrames, pDevice->openal.pIntermediaryBuffer); + + mal_ALuint bufferAL = pDevice->openal.buffersAL[i]; + ((MAL_LPALBUFFERDATA)pDevice->pContext->openal.alBufferData)(bufferAL, pDevice->openal.formatAL, pDevice->openal.pIntermediaryBuffer, pDevice->openal.subBufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat), pDevice->internalSampleRate); + ((MAL_LPALSOURCEQUEUEBUFFERS)pDevice->pContext->openal.alSourceQueueBuffers)(pDevice->openal.sourceAL, 1, &bufferAL); + } + + // Start the source only after filling and queueing each buffer. + ((MAL_LPALSOURCEPLAY)pDevice->pContext->openal.alSourcePlay)(pDevice->openal.sourceAL); + } else { + // Capture. + ((MAL_LPALCCAPTURESTART)pDevice->pContext->openal.alcCaptureStart)((mal_ALCdevice*)pDevice->openal.pDeviceALC); + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__stop_backend__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)((mal_ALCcontext*)pDevice->openal.pContextALC); + ((MAL_LPALSOURCESTOP)pDevice->pContext->openal.alSourceStop)(pDevice->openal.sourceAL); + } else { + ((MAL_LPALCCAPTURESTOP)pDevice->pContext->openal.alcCaptureStop)((mal_ALCdevice*)pDevice->openal.pDeviceALC); + } + + return MAL_SUCCESS; +} + +static mal_result mal_device__break_main_loop__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->openal.breakFromMainLoop = MAL_TRUE; + return MAL_SUCCESS; +} + +static mal_uint32 mal_device__get_available_frames__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + if (pDevice->type == mal_device_type_playback) { + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)((mal_ALCcontext*)pDevice->openal.pContextALC); + + mal_ALint processedBufferCount = 0; + ((MAL_LPALGETSOURCEI)pDevice->pContext->openal.alGetSourcei)(pDevice->openal.sourceAL, MAL_AL_BUFFERS_PROCESSED, &processedBufferCount); + + return processedBufferCount * pDevice->openal.subBufferSizeInFrames; + } else { + mal_ALint samplesAvailable = 0; + ((MAL_LPALCGETINTEGERV)pDevice->pContext->openal.alcGetIntegerv)((mal_ALCdevice*)pDevice->openal.pDeviceALC, MAL_ALC_CAPTURE_SAMPLES, 1, &samplesAvailable); + + return samplesAvailable / pDevice->channels; + } +} + +static mal_uint32 mal_device__wait_for_frames__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + while (!pDevice->openal.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__get_available_frames__openal(pDevice); + if (framesAvailable > 0) { + return framesAvailable; + } + + mal_sleep(1); + } + + // We'll get here if the loop was terminated. When capturing we want to return whatever is available. For playback we just drop it. + if (pDevice->type == mal_device_type_playback) { + return 0; + } else { + return mal_device__get_available_frames__openal(pDevice); + } +} + +static mal_result mal_device__main_loop__openal(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + pDevice->openal.breakFromMainLoop = MAL_FALSE; + while (!pDevice->openal.breakFromMainLoop) { + mal_uint32 framesAvailable = mal_device__wait_for_frames__openal(pDevice); + if (framesAvailable == 0) { + continue; + } + + // If it's a playback device, don't bother grabbing more data if the device is being stopped. + if (pDevice->openal.breakFromMainLoop && pDevice->type == mal_device_type_playback) { + return MAL_FALSE; + } + + if (pDevice->type == mal_device_type_playback) { + while (framesAvailable > 0) { + mal_uint32 framesToRead = (framesAvailable > pDevice->openal.subBufferSizeInFrames) ? pDevice->openal.subBufferSizeInFrames : framesAvailable; + + mal_ALuint bufferAL = pDevice->openal.buffersAL[pDevice->openal.iNextBuffer]; + pDevice->openal.iNextBuffer = (pDevice->openal.iNextBuffer + 1) % pDevice->periods; + + mal_device__read_frames_from_client(pDevice, framesToRead, pDevice->openal.pIntermediaryBuffer); + + ((MAL_LPALCMAKECONTEXTCURRENT)pDevice->pContext->openal.alcMakeContextCurrent)((mal_ALCcontext*)pDevice->openal.pContextALC); + ((MAL_LPALSOURCEUNQUEUEBUFFERS)pDevice->pContext->openal.alSourceUnqueueBuffers)(pDevice->openal.sourceAL, 1, &bufferAL); + ((MAL_LPALBUFFERDATA)pDevice->pContext->openal.alBufferData)(bufferAL, pDevice->openal.formatAL, pDevice->openal.pIntermediaryBuffer, pDevice->openal.subBufferSizeInFrames * pDevice->internalChannels * mal_get_sample_size_in_bytes(pDevice->internalFormat), pDevice->internalSampleRate); + ((MAL_LPALSOURCEQUEUEBUFFERS)pDevice->pContext->openal.alSourceQueueBuffers)(pDevice->openal.sourceAL, 1, &bufferAL); + + framesAvailable -= framesToRead; + } + + + // There's a chance the source has stopped playing due to there not being any buffer's queue. Make sure it's restarted. + mal_ALenum state; + ((MAL_LPALGETSOURCEI)pDevice->pContext->openal.alGetSourcei)(pDevice->openal.sourceAL, MAL_AL_SOURCE_STATE, &state); + + if (state != MAL_AL_PLAYING) { + ((MAL_LPALSOURCEPLAY)pDevice->pContext->openal.alSourcePlay)(pDevice->openal.sourceAL); + } + } else { + while (framesAvailable > 0) { + mal_uint32 framesToSend = (framesAvailable > pDevice->openal.subBufferSizeInFrames) ? pDevice->openal.subBufferSizeInFrames : framesAvailable; + ((MAL_LPALCCAPTURESAMPLES)pDevice->pContext->openal.alcCaptureSamples)((mal_ALCdevice*)pDevice->openal.pDeviceALC, pDevice->openal.pIntermediaryBuffer, framesToSend); + + mal_device__send_frames_to_client(pDevice, framesToSend, pDevice->openal.pIntermediaryBuffer); + framesAvailable -= framesToSend; + } + } + } + + return MAL_SUCCESS; +} +#endif // OpenAL + + +mal_bool32 mal__is_channel_map_valid(const mal_channel* channelMap, mal_uint32 channels) +{ + mal_assert(channels > 0); + + // A channel cannot be present in the channel map more than once. + for (mal_uint32 iChannel = 0; iChannel < channels; ++iChannel) { + for (mal_uint32 jChannel = iChannel + 1; jChannel < channels; ++jChannel) { + if (channelMap[iChannel] == channelMap[jChannel]) { + return MAL_FALSE; + } + } + } + + return MAL_TRUE; +} + + +static mal_result mal_device__start_backend(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_result result = MAL_NO_BACKEND; +#ifdef MAL_ENABLE_WASAPI + if (pDevice->pContext->backend == mal_backend_wasapi) { + result = mal_device__start_backend__wasapi(pDevice); + } +#endif +#ifdef MAL_ENABLE_DSOUND + if (pDevice->pContext->backend == mal_backend_dsound) { + result = mal_device__start_backend__dsound(pDevice); + } +#endif +#ifdef MAL_ENABLE_WINMM + if (pDevice->pContext->backend == mal_backend_winmm) { + result = mal_device__start_backend__winmm(pDevice); + } +#endif +#ifdef MAL_ENABLE_ALSA + if (pDevice->pContext->backend == mal_backend_alsa) { + result = mal_device__start_backend__alsa(pDevice); + } +#endif +#ifdef MAL_ENABLE_OSS + if (pDevice->pContext->backend == mal_backend_oss) { + result = mal_device__start_backend__oss(pDevice); + } +#endif +#ifdef MAL_ENABLE_OPENAL + if (pDevice->pContext->backend == mal_backend_openal) { + result = mal_device__start_backend__openal(pDevice); + } +#endif +#ifdef MAL_ENABLE_NULL + if (pDevice->pContext->backend == mal_backend_null) { + result = mal_device__start_backend__null(pDevice); + } +#endif + + return result; +} + +static mal_result mal_device__stop_backend(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_result result = MAL_NO_BACKEND; +#ifdef MAL_ENABLE_WASAPI + if (pDevice->pContext->backend == mal_backend_wasapi) { + result = mal_device__stop_backend__wasapi(pDevice); + } +#endif +#ifdef MAL_ENABLE_DSOUND + if (pDevice->pContext->backend == mal_backend_dsound) { + result = mal_device__stop_backend__dsound(pDevice); + } +#endif +#ifdef MAL_ENABLE_WINMM + if (pDevice->pContext->backend == mal_backend_winmm) { + result = mal_device__stop_backend__winmm(pDevice); + } +#endif +#ifdef MAL_ENABLE_ALSA + if (pDevice->pContext->backend == mal_backend_alsa) { + result = mal_device__stop_backend__alsa(pDevice); + } +#endif +#ifdef MAL_ENABLE_OSS + if (pDevice->pContext->backend == mal_backend_oss) { + result = mal_device__stop_backend__oss(pDevice); + } +#endif +#ifdef MAL_ENABLE_OPENAL + if (pDevice->pContext->backend == mal_backend_openal) { + result = mal_device__stop_backend__openal(pDevice); + } +#endif +#ifdef MAL_ENABLE_NULL + if (pDevice->pContext->backend == mal_backend_null) { + result = mal_device__stop_backend__null(pDevice); + } +#endif + + return result; +} + +static mal_result mal_device__break_main_loop(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_result result = MAL_NO_BACKEND; +#ifdef MAL_ENABLE_WASAPI + if (pDevice->pContext->backend == mal_backend_wasapi) { + result = mal_device__break_main_loop__wasapi(pDevice); + } +#endif +#ifdef MAL_ENABLE_DSOUND + if (pDevice->pContext->backend == mal_backend_dsound) { + result = mal_device__break_main_loop__dsound(pDevice); + } +#endif +#ifdef MAL_ENABLE_WINMM + if (pDevice->pContext->backend == mal_backend_winmm) { + result = mal_device__break_main_loop__winmm(pDevice); + } +#endif +#ifdef MAL_ENABLE_ALSA + if (pDevice->pContext->backend == mal_backend_alsa) { + result = mal_device__break_main_loop__alsa(pDevice); + } +#endif +#ifdef MAL_ENABLE_OSS + if (pDevice->pContext->backend == mal_backend_oss) { + result = mal_device__break_main_loop__oss(pDevice); + } +#endif +#ifdef MAL_ENABLE_OPENAL + if (pDevice->pContext->backend == mal_backend_openal) { + result = mal_device__break_main_loop__openal(pDevice); + } +#endif +#ifdef MAL_ENABLE_NULL + if (pDevice->pContext->backend == mal_backend_null) { + result = mal_device__break_main_loop__null(pDevice); + } +#endif + + return result; +} + +static mal_result mal_device__main_loop(mal_device* pDevice) +{ + mal_assert(pDevice != NULL); + + mal_result result = MAL_NO_BACKEND; +#ifdef MAL_ENABLE_WASAPI + if (pDevice->pContext->backend == mal_backend_wasapi) { + result = mal_device__main_loop__wasapi(pDevice); + } +#endif +#ifdef MAL_ENABLE_DSOUND + if (pDevice->pContext->backend == mal_backend_dsound) { + result = mal_device__main_loop__dsound(pDevice); + } +#endif +#ifdef MAL_ENABLE_WINMM + if (pDevice->pContext->backend == mal_backend_winmm) { + result = mal_device__main_loop__winmm(pDevice); + } +#endif +#ifdef MAL_ENABLE_ALSA + if (pDevice->pContext->backend == mal_backend_alsa) { + result = mal_device__main_loop__alsa(pDevice); + } +#endif +#ifdef MAL_ENABLE_OSS + if (pDevice->pContext->backend == mal_backend_oss) { + result = mal_device__main_loop__oss(pDevice); + } +#endif +#ifdef MAL_ENABLE_OPENAL + if (pDevice->pContext->backend == mal_backend_openal) { + result = mal_device__main_loop__openal(pDevice); + } +#endif +#ifdef MAL_ENABLE_NULL + if (pDevice->pContext->backend == mal_backend_null) { + result = mal_device__main_loop__null(pDevice); + } +#endif + + return result; +} + +mal_thread_result MAL_THREADCALL mal_worker_thread(void* pData) +{ + mal_device* pDevice = (mal_device*)pData; + mal_assert(pDevice != NULL); + +#ifdef MAL_WIN32 + mal_CoInitializeEx(pDevice->pContext, NULL, 0); // 0 = COINIT_MULTITHREADED +#endif + + // This is only used to prevent posting onStop() when the device is first initialized. + mal_bool32 skipNextStopEvent = MAL_TRUE; + + for (;;) { + // At the start of iteration the device is stopped - we must explicitly mark it as such. + mal_device__stop_backend(pDevice); + + if (!skipNextStopEvent) { + mal_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + } else { + skipNextStopEvent = MAL_FALSE; + } + + + // Let the other threads know that the device has stopped. + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + mal_event_signal(pDevice->pContext, &pDevice->stopEvent); + + // We use an event to wait for a request to wake up. + mal_event_wait(pDevice->pContext, &pDevice->wakeupEvent); + + // Default result code. + pDevice->workResult = MAL_SUCCESS; + + // Just break if we're terminating. + if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) { + break; + } + + + // Getting here means we just started the device and we need to wait for the device to + // either deliver us data (recording) or request more data (playback). + mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STARTING); + + pDevice->workResult = mal_device__start_backend(pDevice); + if (pDevice->workResult != MAL_SUCCESS) { + mal_event_signal(pDevice->pContext, &pDevice->startEvent); + continue; + } + + // The thread that requested the device to start playing is waiting for this thread to start the + // device for real, which is now. + mal_device__set_state(pDevice, MAL_STATE_STARTED); + mal_event_signal(pDevice->pContext, &pDevice->startEvent); + + // Now we just enter the main loop. The main loop can be broken with mal_device__break_main_loop(). + mal_device__main_loop(pDevice); + } + + // Make sure we aren't continuously waiting on a stop event. + mal_event_signal(pDevice->pContext, &pDevice->stopEvent); // <-- Is this still needed? + +#ifdef MAL_WIN32 + mal_CoUninitialize(pDevice->pContext); +#endif + + return (mal_thread_result)0; +} + + +// Helper for determining whether or not the given device is initialized. +mal_bool32 mal_device__is_initialized(mal_device* pDevice) +{ + if (pDevice == NULL) return MAL_FALSE; + return mal_device__get_state(pDevice) != MAL_STATE_UNINITIALIZED; +} + + +#ifdef MAL_WIN32 +mal_result mal_context_uninit_backend_apis__win32(mal_context* pContext) +{ + mal_CoUninitialize(pContext); + mal_dlclose(pContext->win32.hUser32DLL); + mal_dlclose(pContext->win32.hOle32DLL); + + return MAL_SUCCESS; +} + +mal_result mal_context_init_backend_apis__win32(mal_context* pContext) +{ +#ifdef MAL_WIN32_DESKTOP + // Ole32.dll + pContext->win32.hOle32DLL = mal_dlopen("ole32.dll"); + if (pContext->win32.hOle32DLL == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.CoInitializeEx = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "CoInitializeEx"); + pContext->win32.CoUninitialize = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "CoUninitialize"); + pContext->win32.CoCreateInstance = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "CoCreateInstance"); + pContext->win32.CoTaskMemFree = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "CoTaskMemFree"); + pContext->win32.PropVariantClear = (mal_proc)mal_dlsym(pContext->win32.hOle32DLL, "PropVariantClear"); + + + // User32.dll + pContext->win32.hUser32DLL = mal_dlopen("user32.dll"); + if (pContext->win32.hUser32DLL == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + pContext->win32.GetForegroundWindow = (mal_proc)mal_dlsym(pContext->win32.hUser32DLL, "GetForegroundWindow"); + pContext->win32.GetDesktopWindow = (mal_proc)mal_dlsym(pContext->win32.hUser32DLL, "GetDesktopWindow"); +#endif + + mal_CoInitializeEx(pContext, NULL, 0); // 0 = COINIT_MULTITHREADED + return MAL_SUCCESS; +} +#else +mal_result mal_context_uninit_backend_apis__nix(mal_context* pContext) +{ + mal_dlclose(pContext->posix.pthreadSO); + + return MAL_SUCCESS; +} + +mal_result mal_context_init_backend_apis__nix(mal_context* pContext) +{ + // pthread + const char* libpthreadFileNames[] = { + "libpthread.so", + "libpthread.so.0" + }; + + for (size_t i = 0; i < sizeof(libpthreadFileNames) / sizeof(libpthreadFileNames[0]); ++i) { + pContext->posix.pthreadSO = mal_dlopen(libpthreadFileNames[i]); + if (pContext->posix.pthreadSO != NULL) { + break; + } + } + + if (pContext->posix.pthreadSO == NULL) { + return MAL_FAILED_TO_INIT_BACKEND; + } + + pContext->posix.pthread_create = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_create"); + pContext->posix.pthread_join = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_join"); + pContext->posix.pthread_mutex_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_init"); + pContext->posix.pthread_mutex_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_destroy"); + pContext->posix.pthread_mutex_lock = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_lock"); + pContext->posix.pthread_mutex_unlock = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_mutex_unlock"); + pContext->posix.pthread_cond_init = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_init"); + pContext->posix.pthread_cond_destroy = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_destroy"); + pContext->posix.pthread_cond_wait = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_wait"); + pContext->posix.pthread_cond_signal = (mal_proc)mal_dlsym(pContext->posix.pthreadSO, "pthread_cond_signal"); + + return MAL_SUCCESS; +} +#endif + +mal_result mal_context_init_backend_apis(mal_context* pContext) +{ + mal_result result = MAL_NO_BACKEND; +#ifdef MAL_WIN32 + result = mal_context_init_backend_apis__win32(pContext); +#else + result = mal_context_init_backend_apis__nix(pContext); +#endif + + return result; +} + +mal_result mal_context_uninit_backend_apis(mal_context* pContext) +{ + mal_result result = MAL_NO_BACKEND; +#ifdef MAL_WIN32 + result = mal_context_uninit_backend_apis__win32(pContext); +#else + result = mal_context_uninit_backend_apis__nix(pContext); +#endif + + return result; +} + +mal_result mal_context_init(mal_backend backends[], mal_uint32 backendCount, const mal_context_config* pConfig, mal_context* pContext) +{ + if (pContext == NULL) return MAL_INVALID_ARGS; + mal_zero_object(pContext); + + // Always make sure the config is set first to ensure properties are available as soon as possible. + if (pConfig != NULL) { + pContext->config = *pConfig; + } else { + pContext->config = mal_context_config_init(NULL); + } + + // Backend APIs need to be initialized first. This is where external libraries will be loaded and linked. + mal_result result = mal_context_init_backend_apis(pContext); + if (result != MAL_SUCCESS) { + return result; + } + + static mal_backend defaultBackends[] = { + mal_backend_wasapi, + mal_backend_dsound, + mal_backend_winmm, + mal_backend_alsa, + mal_backend_oss, + mal_backend_opensl, + mal_backend_openal, + mal_backend_null + }; + + if (backends == NULL) { + backends = defaultBackends; + backendCount = sizeof(defaultBackends) / sizeof(defaultBackends[0]); + } + + mal_assert(backends != NULL); + + for (mal_uint32 iBackend = 0; iBackend < backendCount; ++iBackend) { + mal_backend backend = backends[iBackend]; + + result = MAL_NO_BACKEND; + switch (backend) { + #ifdef MAL_ENABLE_WASAPI + case mal_backend_wasapi: + { + result = mal_context_init__wasapi(pContext); + } break; + #endif + #ifdef MAL_ENABLE_DSOUND + case mal_backend_dsound: + { + result = mal_context_init__dsound(pContext); + } break; + #endif + #ifdef MAL_ENABLE_WINMM + case mal_backend_winmm: + { + result = mal_context_init__winmm(pContext); + } break; + #endif + #ifdef MAL_ENABLE_ALSA + case mal_backend_alsa: + { + result = mal_context_init__alsa(pContext); + } break; + #endif + #ifdef MAL_ENABLE_OSS + case mal_backend_oss: + { + result = mal_context_init__oss(pContext); + } break; + #endif + #ifdef MAL_ENABLE_OPENSL + case mal_backend_opensl: + { + result = mal_context_init__opensl(pContext); + } break; + #endif + #ifdef MAL_ENABLE_OPENAL + case mal_backend_openal: + { + result = mal_context_init__openal(pContext); + } break; + #endif + #ifdef MAL_ENABLE_NULL + case mal_backend_null: + { + result = mal_context_init__null(pContext); + } break; + #endif + + default: break; + } + + // If this iteration was successful, return. + if (result == MAL_SUCCESS) { + pContext->backend = backend; + return result; + } + } + + mal_zero_object(pContext); // Safety. + return MAL_NO_BACKEND; +} + +mal_result mal_context_uninit(mal_context* pContext) +{ + if (pContext == NULL) return MAL_INVALID_ARGS; + + switch (pContext->backend) { + #ifdef MAL_ENABLE_WASAPI + case mal_backend_wasapi: + { + return mal_context_uninit__wasapi(pContext); + } break; + #endif + #ifdef MAL_ENABLE_DSOUND + case mal_backend_dsound: + { + return mal_context_uninit__dsound(pContext); + } break; + #endif + #ifdef MAL_ENABLE_WINMM + case mal_backend_winmm: + { + return mal_context_uninit__winmm(pContext); + } break; + #endif + #ifdef MAL_ENABLE_ALSA + case mal_backend_alsa: + { + return mal_context_uninit__alsa(pContext); + } break; + #endif + #ifdef MAL_ENABLE_OSS + case mal_backend_oss: + { + return mal_context_uninit__oss(pContext); + } break; + #endif + #ifdef MAL_ENABLE_OPENSL + case mal_backend_opensl: + { + return mal_context_uninit__opensl(pContext); + } break; + #endif + #ifdef MAL_ENABLE_OPENAL + case mal_backend_openal: + { + return mal_context_uninit__openal(pContext); + } break; + #endif + #ifdef MAL_ENABLE_NULL + case mal_backend_null: + { + return mal_context_uninit__null(pContext); + } break; + #endif + + default: break; + } + + mal_context_uninit_backend_apis(pContext); + + mal_assert(MAL_FALSE); + return MAL_NO_BACKEND; +} + + +mal_result mal_enumerate_devices(mal_context* pContext, mal_device_type type, mal_uint32* pCount, mal_device_info* pInfo) +{ + if (pCount == NULL) return mal_post_error(NULL, "mal_enumerate_devices() called with invalid arguments (pCount == 0).", MAL_INVALID_ARGS); + + switch (pContext->backend) + { + #ifdef MAL_ENABLE_WASAPI + case mal_backend_wasapi: + { + return mal_enumerate_devices__wasapi(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_DSOUND + case mal_backend_dsound: + { + return mal_enumerate_devices__dsound(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_WINMM + case mal_backend_winmm: + { + return mal_enumerate_devices__winmm(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_ALSA + case mal_backend_alsa: + { + return mal_enumerate_devices__alsa(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_OSS + case mal_backend_oss: + { + return mal_enumerate_devices__oss(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_OPENSL + case mal_backend_opensl: + { + return mal_enumerate_devices__opensl(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_OPENAL + case mal_backend_openal: + { + return mal_enumerate_devices__openal(pContext, type, pCount, pInfo); + } break; + #endif + #ifdef MAL_ENABLE_NULL + case mal_backend_null: + { + return mal_enumerate_devices__null(pContext, type, pCount, pInfo); + } break; + #endif + + default: break; + } + + mal_assert(MAL_FALSE); + return MAL_NO_BACKEND; +} + +mal_result mal_device_init(mal_context* pContext, mal_device_type type, mal_device_id* pDeviceID, const mal_device_config* pConfig, void* pUserData, mal_device* pDevice) +{ + if (pDevice == NULL) { + return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pDevice == NULL).", MAL_INVALID_ARGS); + } + if (pConfig == NULL) { + return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pConfig == NULL).", MAL_INVALID_ARGS); + } + + // Make a copy of the config to ensure we don't override the caller's object. + mal_device_config config = *pConfig; + + mal_zero_object(pDevice); + pDevice->pContext = pContext; + + // Set the user data and log callback ASAP to ensure it is available for the entire initialization process. + pDevice->pUserData = pUserData; + pDevice->onStop = config.onStopCallback; + pDevice->onSend = config.onSendCallback; + pDevice->onRecv = config.onRecvCallback; + + if (((mal_uint64)pDevice % sizeof(pDevice)) != 0) { + if (pContext->config.onLog) { + pContext->config.onLog(pContext, pDevice, "WARNING: mal_device_init() called for a device that is not properly aligned. Thread safety is not supported."); + } + } + + + if (pContext == NULL) { + return mal_post_error(pDevice, "mal_device_init() called with invalid arguments (pContext == NULL).", MAL_INVALID_ARGS); + } + + + // Basic config validation. + if (config.channels == 0) { + return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count must be greater than 0.", MAL_INVALID_DEVICE_CONFIG); + } + if (config.channels > MAL_MAX_CHANNELS) { + return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Channel count cannot exceed 18.", MAL_INVALID_DEVICE_CONFIG); + } + + if (config.sampleRate == 0) { + return mal_post_error(pDevice, "mal_device_init() called with an invalid config. Sample rate must be greater than 0.", MAL_INVALID_DEVICE_CONFIG); + } + + if (!mal__is_channel_map_valid(pConfig->channelMap, pConfig->channels)) { + return mal_post_error(pDevice, "mal_device_init() called with invalid arguments. Channel map is invalid.", MAL_INVALID_DEVICE_CONFIG); + } + + + // Default buffer size and periods. + if (config.bufferSizeInFrames == 0) { + config.bufferSizeInFrames = (config.sampleRate/1000) * MAL_DEFAULT_BUFFER_SIZE_IN_MILLISECONDS; + pDevice->usingDefaultBufferSize = MAL_TRUE; + } + if (config.periods == 0) { + config.periods = MAL_DEFAULT_PERIODS; + pDevice->usingDefaultPeriods = MAL_TRUE; + } + + pDevice->type = type; + pDevice->format = config.format; + pDevice->channels = config.channels; + mal_copy_memory(config.channelMap, config.channelMap, sizeof(config.channelMap[0]) * config.channels); + pDevice->sampleRate = config.sampleRate; + pDevice->bufferSizeInFrames = config.bufferSizeInFrames; + pDevice->periods = config.periods; + + // The internal format, channel count and sample rate can be modified by the backend. + pDevice->internalFormat = pDevice->format; + pDevice->internalChannels = pDevice->channels; + pDevice->internalSampleRate = pDevice->sampleRate; + mal_copy_memory(pDevice->internalChannelMap, pDevice->channelMap, sizeof(pDevice->channelMap)); + + if (!mal_mutex_create(pContext, &pDevice->lock)) { + return mal_post_error(pDevice, "Failed to create mutex.", MAL_FAILED_TO_CREATE_MUTEX); + } + + // When the device is started, the worker thread is the one that does the actual startup of the backend device. We + // use a semaphore to wait for the background thread to finish the work. The same applies for stopping the device. + // + // Each of these semaphores is released internally by the worker thread when the work is completed. The start + // semaphore is also used to wake up the worker thread. + if (!mal_event_create(pContext, &pDevice->wakeupEvent)) { + mal_mutex_delete(pContext, &pDevice->lock); + return mal_post_error(pDevice, "Failed to create worker thread wakeup event.", MAL_FAILED_TO_CREATE_EVENT); + } + if (!mal_event_create(pContext, &pDevice->startEvent)) { + mal_event_delete(pContext, &pDevice->wakeupEvent); + mal_mutex_delete(pContext, &pDevice->lock); + return mal_post_error(pDevice, "Failed to create worker thread start event.", MAL_FAILED_TO_CREATE_EVENT); + } + if (!mal_event_create(pContext, &pDevice->stopEvent)) { + mal_event_delete(pContext, &pDevice->startEvent); + mal_event_delete(pContext, &pDevice->wakeupEvent); + mal_mutex_delete(pContext, &pDevice->lock); + return mal_post_error(pDevice, "Failed to create worker thread stop event.", MAL_FAILED_TO_CREATE_EVENT); + } + + + mal_result result = MAL_NO_BACKEND; + switch (pContext->backend) + { + #ifdef MAL_ENABLE_WASAPI + case mal_backend_wasapi: + { + result = mal_device_init__wasapi(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_DSOUND + case mal_backend_dsound: + { + result = mal_device_init__dsound(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_WINMM + case mal_backend_winmm: + { + result = mal_device_init__winmm(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_ALSA + case mal_backend_alsa: + { + result = mal_device_init__alsa(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_OSS + case mal_backend_oss: + { + result = mal_device_init__oss(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_OPENSL + case mal_backend_opensl: + { + result = mal_device_init__opensl(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_OPENAL + case mal_backend_openal: + { + result = mal_device_init__openal(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + #ifdef MAL_ENABLE_NULL + case mal_backend_null: + { + result = mal_device_init__null(pContext, type, pDeviceID, &config, pDevice); + } break; + #endif + + default: break; + } + + if (result != MAL_SUCCESS) { + return MAL_NO_BACKEND; // The error message will have been posted with mal_post_error() by the source of the error so don't bother calling it here. + } + + + // We need a DSP object which is where samples are moved through in order to convert them to the + // format required by the backend. + mal_dsp_config dspConfig; + dspConfig.cacheSizeInFrames = pDevice->bufferSizeInFrames; + if (type == mal_device_type_playback) { + dspConfig.formatIn = pDevice->format; + dspConfig.channelsIn = pDevice->channels; + dspConfig.sampleRateIn = pDevice->sampleRate; + mal_copy_memory(dspConfig.channelMapIn, pDevice->channelMap, sizeof(dspConfig.channelMapIn)); + dspConfig.formatOut = pDevice->internalFormat; + dspConfig.channelsOut = pDevice->internalChannels; + dspConfig.sampleRateOut = pDevice->internalSampleRate; + mal_copy_memory(dspConfig.channelMapOut, pDevice->internalChannelMap, sizeof(dspConfig.channelMapOut)); + mal_dsp_init(&dspConfig, mal_device__on_read_from_client, pDevice, &pDevice->dsp); + } else { + dspConfig.formatIn = pDevice->internalFormat; + dspConfig.channelsIn = pDevice->internalChannels; + dspConfig.sampleRateIn = pDevice->internalSampleRate; + mal_copy_memory(dspConfig.channelMapIn, pDevice->internalChannelMap, sizeof(dspConfig.channelMapIn)); + dspConfig.formatOut = pDevice->format; + dspConfig.channelsOut = pDevice->channels; + dspConfig.sampleRateOut = pDevice->sampleRate; + mal_copy_memory(dspConfig.channelMapOut, pDevice->channelMap, sizeof(dspConfig.channelMapOut)); + mal_dsp_init(&dspConfig, mal_device__on_read_from_device, pDevice, &pDevice->dsp); + } + + + + + // Some backends don't require the worker thread. + if (pContext->backend != mal_backend_opensl) { + // The worker thread. + if (!mal_thread_create(pContext, &pDevice->thread, mal_worker_thread, pDevice)) { + mal_device_uninit(pDevice); + return mal_post_error(pDevice, "Failed to create worker thread.", MAL_FAILED_TO_CREATE_THREAD); + } + + // Wait for the worker thread to put the device into it's stopped state for real. + mal_event_wait(pContext, &pDevice->stopEvent); + } else { + mal_device__set_state(pDevice, MAL_STATE_STOPPED); + } + + mal_assert(mal_device__get_state(pDevice) == MAL_STATE_STOPPED); + return MAL_SUCCESS; +} + +void mal_device_uninit(mal_device* pDevice) +{ + if (!mal_device__is_initialized(pDevice)) return; + + // Make sure the device is stopped first. The backends will probably handle this naturally, + // but I like to do it explicitly for my own sanity. + if (mal_device_is_started(pDevice)) { + while (mal_device_stop(pDevice) == MAL_DEVICE_BUSY) { + mal_sleep(1); + } + } + + // Putting the device into an uninitialized state will make the worker thread return. + mal_device__set_state(pDevice, MAL_STATE_UNINITIALIZED); + + // Wake up the worker thread and wait for it to properly terminate. + if (pDevice->pContext->backend != mal_backend_opensl) { + mal_event_signal(pDevice->pContext, &pDevice->wakeupEvent); + mal_thread_wait(pDevice->pContext, &pDevice->thread); + } + + mal_event_delete(pDevice->pContext, &pDevice->stopEvent); + mal_event_delete(pDevice->pContext, &pDevice->startEvent); + mal_event_delete(pDevice->pContext, &pDevice->wakeupEvent); + mal_mutex_delete(pDevice->pContext, &pDevice->lock); + +#ifdef MAL_ENABLE_WASAPI + if (pDevice->pContext->backend == mal_backend_wasapi) { + mal_device_uninit__wasapi(pDevice); + } +#endif +#ifdef MAL_ENABLE_DSOUND + if (pDevice->pContext->backend == mal_backend_dsound) { + mal_device_uninit__dsound(pDevice); + } +#endif +#ifdef MAL_ENABLE_WINMM + if (pDevice->pContext->backend == mal_backend_winmm) { + mal_device_uninit__winmm(pDevice); + } +#endif +#ifdef MAL_ENABLE_ALSA + if (pDevice->pContext->backend == mal_backend_alsa) { + mal_device_uninit__alsa(pDevice); + } +#endif +#ifdef MAL_ENABLE_OSS + if (pDevice->pContext->backend == mal_backend_oss) { + mal_device_uninit__oss(pDevice); + } +#endif +#ifdef MAL_ENABLE_OPENSL + if (pDevice->pContext->backend == mal_backend_opensl) { + mal_device_uninit__opensl(pDevice); + } +#endif +#ifdef MAL_ENABLE_OPENAL + if (pDevice->pContext->backend == mal_backend_openal) { + mal_device_uninit__openal(pDevice); + } +#endif +#ifdef MAL_ENABLE_NULL + if (pDevice->pContext->backend == mal_backend_null) { + mal_device_uninit__null(pDevice); + } +#endif + + mal_zero_object(pDevice); +} + +void mal_device_set_recv_callback(mal_device* pDevice, mal_recv_proc proc) +{ + if (pDevice == NULL) return; + mal_atomic_exchange_ptr(&pDevice->onRecv, proc); +} + +void mal_device_set_send_callback(mal_device* pDevice, mal_send_proc proc) +{ + if (pDevice == NULL) return; + mal_atomic_exchange_ptr(&pDevice->onSend, proc); +} + +void mal_device_set_stop_callback(mal_device* pDevice, mal_stop_proc proc) +{ + if (pDevice == NULL) return; + mal_atomic_exchange_ptr(&pDevice->onStop, proc); +} + +mal_result mal_device_start(mal_device* pDevice) +{ + if (pDevice == NULL) return mal_post_error(pDevice, "mal_device_start() called with invalid arguments (pDevice == NULL).", MAL_INVALID_ARGS); + if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) return mal_post_error(pDevice, "mal_device_start() called for an uninitialized device.", MAL_DEVICE_NOT_INITIALIZED); + + mal_result result = MAL_ERROR; + mal_mutex_lock(pDevice->pContext, &pDevice->lock); + { + // Be a bit more descriptive if the device is already started or is already in the process of starting. This is likely + // a bug with the application. + if (mal_device__get_state(pDevice) == MAL_STATE_STARTING) { + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + return mal_post_error(pDevice, "mal_device_start() called while another thread is already starting it.", MAL_DEVICE_ALREADY_STARTING); + } + if (mal_device__get_state(pDevice) == MAL_STATE_STARTED) { + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + return mal_post_error(pDevice, "mal_device_start() called for a device that's already started.", MAL_DEVICE_ALREADY_STARTED); + } + + // The device needs to be in a stopped state. If it's not, we just let the caller know the device is busy. + if (mal_device__get_state(pDevice) != MAL_STATE_STOPPED) { + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + return mal_post_error(pDevice, "mal_device_start() called while another thread is in the process of stopping it.", MAL_DEVICE_BUSY); + } + + mal_device__set_state(pDevice, MAL_STATE_STARTING); + + // Asynchronous backends need to be handled differently. +#ifdef MAL_ENABLE_OPENSL + if (pDevice->pContext->backend == mal_backend_opensl) { + mal_device__start_backend__opensl(pDevice); + mal_device__set_state(pDevice, MAL_STATE_STARTED); + } else +#endif + // Synchronous backends. + { + mal_event_signal(pDevice->pContext, &pDevice->wakeupEvent); + + // Wait for the worker thread to finish starting the device. Note that the worker thread will be the one + // who puts the device into the started state. Don't call mal_device__set_state() here. + mal_event_wait(pDevice->pContext, &pDevice->startEvent); + result = pDevice->workResult; + } + } + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + + return result; +} + +mal_result mal_device_stop(mal_device* pDevice) +{ + if (pDevice == NULL) return mal_post_error(pDevice, "mal_device_stop() called with invalid arguments (pDevice == NULL).", MAL_INVALID_ARGS); + if (mal_device__get_state(pDevice) == MAL_STATE_UNINITIALIZED) return mal_post_error(pDevice, "mal_device_stop() called for an uninitialized device.", MAL_DEVICE_NOT_INITIALIZED); + + mal_result result = MAL_ERROR; + mal_mutex_lock(pDevice->pContext, &pDevice->lock); + { + // Be a bit more descriptive if the device is already stopped or is already in the process of stopping. This is likely + // a bug with the application. + if (mal_device__get_state(pDevice) == MAL_STATE_STOPPING) { + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + return mal_post_error(pDevice, "mal_device_stop() called while another thread is already stopping it.", MAL_DEVICE_ALREADY_STOPPING); + } + if (mal_device__get_state(pDevice) == MAL_STATE_STOPPED) { + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + return mal_post_error(pDevice, "mal_device_stop() called for a device that's already stopped.", MAL_DEVICE_ALREADY_STOPPED); + } + + // The device needs to be in a started state. If it's not, we just let the caller know the device is busy. + if (mal_device__get_state(pDevice) != MAL_STATE_STARTED) { + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + return mal_post_error(pDevice, "mal_device_stop() called while another thread is in the process of starting it.", MAL_DEVICE_BUSY); + } + + mal_device__set_state(pDevice, MAL_STATE_STOPPING); + + // There's no need to wake up the thread like we do when starting. + + // Asynchronous backends need to be handled differently. +#ifdef MAL_ENABLE_OPENSL + if (pDevice->pContext->backend == mal_backend_opensl) { + mal_device__stop_backend__opensl(pDevice); + } else +#endif + // Synchronous backends. + { + // When we get here the worker thread is likely in a wait state while waiting for the backend device to deliver or request + // audio data. We need to force these to return as quickly as possible. + mal_device__break_main_loop(pDevice); + + // We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be + // the one who puts the device into the stopped state. Don't call mal_device__set_state() here. + mal_event_wait(pDevice->pContext, &pDevice->stopEvent); + result = MAL_SUCCESS; + } + } + mal_mutex_unlock(pDevice->pContext, &pDevice->lock); + + return result; +} + +mal_bool32 mal_device_is_started(mal_device* pDevice) +{ + if (pDevice == NULL) return MAL_FALSE; + return mal_device__get_state(pDevice) == MAL_STATE_STARTED; +} + +mal_uint32 mal_device_get_buffer_size_in_bytes(mal_device* pDevice) +{ + if (pDevice == NULL) return 0; + return pDevice->bufferSizeInFrames * pDevice->channels * mal_get_sample_size_in_bytes(pDevice->format); +} + +mal_uint32 mal_get_sample_size_in_bytes(mal_format format) +{ + mal_uint32 sizes[] = { + 0, // unknown + 1, // u8 + 2, // s16 + 3, // s24 + 4, // s32 + 4, // f32 + }; + return sizes[format]; +} + +mal_context_config mal_context_config_init(mal_log_proc onLog) +{ + mal_context_config config; + mal_zero_object(&config); + + config.onLog = onLog; + + return config; +} + +mal_device_config mal_device_config_init(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_recv_proc onRecvCallback, mal_send_proc onSendCallback) +{ + mal_device_config config; + mal_zero_object(&config); + + config.format = format; + config.channels = channels; + config.sampleRate = sampleRate; + config.onRecvCallback = onRecvCallback; + config.onSendCallback = onSendCallback; + + switch (channels) + { + case 1: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_CENTER; + } break; + + case 2: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + } break; + + case 3: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + config.channelMap[2] = MAL_CHANNEL_LFE; + } break; + + case 4: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + config.channelMap[2] = MAL_CHANNEL_BACK_LEFT; + config.channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 5: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + config.channelMap[2] = MAL_CHANNEL_BACK_LEFT; + config.channelMap[3] = MAL_CHANNEL_BACK_RIGHT; + config.channelMap[4] = MAL_CHANNEL_LFE; + } break; + + case 6: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + config.channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + config.channelMap[3] = MAL_CHANNEL_LFE; + config.channelMap[4] = MAL_CHANNEL_BACK_LEFT; + config.channelMap[5] = MAL_CHANNEL_BACK_RIGHT; + } break; + + case 8: + { + config.channelMap[0] = MAL_CHANNEL_FRONT_LEFT; + config.channelMap[1] = MAL_CHANNEL_FRONT_RIGHT; + config.channelMap[2] = MAL_CHANNEL_FRONT_CENTER; + config.channelMap[3] = MAL_CHANNEL_LFE; + config.channelMap[4] = MAL_CHANNEL_BACK_LEFT; + config.channelMap[5] = MAL_CHANNEL_BACK_RIGHT; + config.channelMap[6] = MAL_CHANNEL_SIDE_LEFT; + config.channelMap[7] = MAL_CHANNEL_SIDE_RIGHT; + } break; + + default: + { + // Just leave it all blank in this case. This will use the same mapping as the device's native mapping. + } break; + } + + return config; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// SRC +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mal_src_cache_init(mal_src* pSRC, mal_src_cache* pCache) +{ + mal_assert(pSRC != NULL); + mal_assert(pCache != NULL); + + pCache->pSRC = pSRC; + pCache->cachedFrameCount = 0; + pCache->iNextFrame = 0; +} + +mal_uint32 mal_src_cache_read_frames(mal_src_cache* pCache, mal_uint32 frameCount, float* pFramesOut) +{ + mal_assert(pCache != NULL); + mal_assert(pCache->pSRC != NULL); + mal_assert(pCache->pSRC->onRead != NULL); + mal_assert(frameCount > 0); + mal_assert(pFramesOut != NULL); + + mal_uint32 channels = pCache->pSRC->config.channels; + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + // If there's anything in memory go ahead and copy that over first. + mal_uint32 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; + mal_uint32 framesToReadFromMemory = frameCount; + if (framesToReadFromMemory > framesRemainingInMemory) { + framesToReadFromMemory = framesRemainingInMemory; + } + + mal_copy_memory(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, framesToReadFromMemory * channels * sizeof(float)); + pCache->iNextFrame += framesToReadFromMemory; + + totalFramesRead += framesToReadFromMemory; + frameCount -= framesToReadFromMemory; + if (frameCount == 0) { + break; + } + + + // At this point there are still more frames to read from the client, so we'll need to reload the cache with fresh data. + mal_assert(frameCount > 0); + pFramesOut += framesToReadFromMemory * channels; + + pCache->iNextFrame = 0; + pCache->cachedFrameCount = 0; + if (pCache->pSRC->config.formatIn == mal_format_f32) { + // No need for a conversion - read straight into the cache. + mal_uint32 framesToReadFromClient = mal_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; + if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { + framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; + } + + pCache->cachedFrameCount = pCache->pSRC->onRead(framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); + } else { + // A format conversion is required which means we need to use an intermediary buffer. + mal_uint8 pIntermediaryBuffer[sizeof(pCache->pCachedFrames)]; + mal_uint32 framesToReadFromClient = mal_min(mal_buffer_frame_capacity(pIntermediaryBuffer, channels, pCache->pSRC->config.formatIn), mal_buffer_frame_capacity(pCache->pCachedFrames, channels, mal_format_f32)); + if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { + framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; + } + + pCache->cachedFrameCount = pCache->pSRC->onRead(framesToReadFromClient, pIntermediaryBuffer, pCache->pSRC->pUserData); + + // Convert to f32. + mal_pcm_convert(pCache->pCachedFrames, mal_format_f32, pIntermediaryBuffer, pCache->pSRC->config.formatIn, pCache->cachedFrameCount * channels); + } + + + // Get out of this loop if nothing was able to be retrieved. + if (pCache->cachedFrameCount == 0) { + break; + } + } + + return totalFramesRead; +} + + +mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); +mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); + +mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC) +{ + if (pSRC == NULL) return MAL_INVALID_ARGS; + mal_zero_object(pSRC); + + if (pConfig == NULL || onRead == NULL) return MAL_INVALID_ARGS; + if (pConfig->channels == 0 || pConfig->channels > MAL_MAX_CHANNELS) return MAL_INVALID_ARGS; + + pSRC->config = *pConfig; + pSRC->onRead = onRead; + pSRC->pUserData = pUserData; + + // If the in and out sample rates are the same, fall back to the passthrough algorithm. + if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { + pSRC->config.algorithm = mal_src_algorithm_none; + } + + if (pSRC->config.cacheSizeInFrames > MAL_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { + pSRC->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES; + } + + pSRC->ratio = (float)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + + mal_src_cache_init(pSRC, &pSRC->cache); + return MAL_SUCCESS; +} + +mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; + + // Could just use a function pointer instead of a switch for this... + switch (pSRC->config.algorithm) + { + case mal_src_algorithm_none: return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut); + case mal_src_algorithm_linear: return mal_src_read_frames_linear(pSRC, frameCount, pFramesOut); + default: return 0; + } +} + +mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + mal_assert(pSRC != NULL); + mal_assert(frameCount > 0); + mal_assert(pFramesOut != NULL); + + // Fast path. No need for data conversion - just pass right through. + if (pSRC->config.formatIn == pSRC->config.formatOut) { + return pSRC->onRead(frameCount, pFramesOut, pSRC->pUserData); + } + + // Slower path. Need to do a format conversion. + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + mal_uint8 pStagingBuffer[MAL_MAX_CHANNELS * 2048]; + mal_uint32 stagingBufferSizeInFrames = sizeof(pStagingBuffer) / mal_get_sample_size_in_bytes(pSRC->config.formatIn) / pSRC->config.channels; + mal_uint32 framesToRead = stagingBufferSizeInFrames; + if (framesToRead > frameCount) { + framesToRead = frameCount; + } + + mal_uint32 framesRead = pSRC->onRead(framesToRead, pStagingBuffer, pSRC->pUserData); + if (framesRead == 0) { + break; + } + + mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pStagingBuffer, pSRC->config.formatIn, framesRead * pSRC->config.channels); + + pFramesOut = (mal_uint8*)pFramesOut + (framesRead * pSRC->config.channels * mal_get_sample_size_in_bytes(pSRC->config.formatOut)); + frameCount -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + +mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + mal_assert(pSRC != NULL); + mal_assert(frameCount > 0); + mal_assert(pFramesOut != NULL); + + // For linear SRC, the bin is only 2 frames: 1 prior, 1 future. + + // Load the bin if necessary. + if (!pSRC->linear.isPrevFramesLoaded) { + mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); + if (framesRead == 0) { + return 0; + } + pSRC->linear.isPrevFramesLoaded = MAL_TRUE; + } + if (!pSRC->linear.isNextFramesLoaded) { + mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); + if (framesRead == 0) { + return 0; + } + pSRC->linear.isNextFramesLoaded = MAL_TRUE; + } + + float factor = pSRC->ratio; + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + // The bin is where the previous and next frames are located. + float* pPrevFrame = pSRC->bin; + float* pNextFrame = pSRC->bin + pSRC->config.channels; + + float pFrame[MAL_MAX_CHANNELS]; + mal_blend_f32(pFrame, pPrevFrame, pNextFrame, pSRC->linear.alpha, pSRC->config.channels); + + pSRC->linear.alpha += factor; + + // The new alpha value is how we determine whether or not we need to read fresh frames. + mal_uint32 framesToReadFromClient = (mal_uint32)pSRC->linear.alpha; + pSRC->linear.alpha = pSRC->linear.alpha - framesToReadFromClient; + + for (mal_uint32 i = 0; i < framesToReadFromClient; ++i) { + for (mal_uint32 j = 0; j < pSRC->config.channels; ++j) { + pPrevFrame[j] = pNextFrame[j]; + } + + mal_uint32 framesRead = mal_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); + if (framesRead == 0) { + for (mal_uint32 j = 0; j < pSRC->config.channels; ++j) { + pNextFrame[j] = 0; + } + + pSRC->linear.isNextFramesLoaded = MAL_FALSE; + break; + } + } + + mal_pcm_convert(pFramesOut, pSRC->config.formatOut, pFrame, mal_format_f32, 1 * pSRC->config.channels); + + pFramesOut = (mal_uint8*)pFramesOut + (1 * pSRC->config.channels * mal_get_sample_size_in_bytes(pSRC->config.formatOut)); + frameCount -= 1; + totalFramesRead += 1; + + // If there's no frames available we need to get out of this loop. + if (!pSRC->linear.isNextFramesLoaded) { + break; + } + } + + return totalFramesRead; +} + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// FORMAT CONVERSION +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count); +void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count); +void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count); +void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count); +void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count); +void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count); +void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count); +void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count); +void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count); + +void mal_pcm_convert(void* pOut, mal_format formatOut, const void* pIn, mal_format formatIn, unsigned int sampleCount) +{ + if (formatOut == formatIn) { + mal_copy_memory(pOut, pIn, sampleCount * mal_get_sample_size_in_bytes(formatOut)); + return; + } + + switch (formatIn) + { + case mal_format_u8: + { + switch (formatOut) + { + case mal_format_s16: mal_pcm_u8_to_s16((short*)pOut, (const unsigned char*)pIn, sampleCount); return; + case mal_format_s24: mal_pcm_u8_to_s24( pOut, (const unsigned char*)pIn, sampleCount); return; + case mal_format_s32: mal_pcm_u8_to_s32( (int*)pOut, (const unsigned char*)pIn, sampleCount); return; + case mal_format_f32: mal_pcm_u8_to_f32((float*)pOut, (const unsigned char*)pIn, sampleCount); return; + default: break; + } + } break; + + case mal_format_s16: + { + switch (formatOut) + { + case mal_format_u8: mal_pcm_s16_to_u8( (unsigned char*)pOut, (const short*)pIn, sampleCount); return; + case mal_format_s24: mal_pcm_s16_to_s24( pOut, (const short*)pIn, sampleCount); return; + case mal_format_s32: mal_pcm_s16_to_s32( (int*)pOut, (const short*)pIn, sampleCount); return; + case mal_format_f32: mal_pcm_s16_to_f32( (float*)pOut, (const short*)pIn, sampleCount); return; + default: break; + } + } break; + + case mal_format_s24: + { + switch (formatOut) + { + case mal_format_u8: mal_pcm_s24_to_u8( (unsigned char*)pOut, pIn, sampleCount); return; + case mal_format_s16: mal_pcm_s24_to_s16( (short*)pOut, pIn, sampleCount); return; + case mal_format_s32: mal_pcm_s24_to_s32( (int*)pOut, pIn, sampleCount); return; + case mal_format_f32: mal_pcm_s24_to_f32( (float*)pOut, pIn, sampleCount); return; + default: break; + } + } break; + + case mal_format_s32: + { + switch (formatOut) + { + case mal_format_u8: mal_pcm_s32_to_u8( (unsigned char*)pOut, (const int*)pIn, sampleCount); return; + case mal_format_s16: mal_pcm_s32_to_s16( (short*)pOut, (const int*)pIn, sampleCount); return; + case mal_format_s24: mal_pcm_s32_to_s24( pOut, (const int*)pIn, sampleCount); return; + case mal_format_f32: mal_pcm_s32_to_f32( (float*)pOut, (const int*)pIn, sampleCount); return; + default: break; + } + } break; + + case mal_format_f32: + { + switch (formatOut) + { + case mal_format_u8: mal_pcm_f32_to_u8( (unsigned char*)pOut, (const float*)pIn, sampleCount); return; + case mal_format_s16: mal_pcm_f32_to_s16( (short*)pOut, (const float*)pIn, sampleCount); return; + case mal_format_s24: mal_pcm_f32_to_s24( pOut, (const float*)pIn, sampleCount); return; + case mal_format_s32: mal_pcm_f32_to_s32( (int*)pOut, (const float*)pIn, sampleCount); return; + default: break; + } + } break; + + default: break; + } +} + + +static void mal_rearrange_channels_u8(mal_uint8* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +{ + mal_uint8 temp[MAL_MAX_CHANNELS]; + mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + + switch (channels) { + case 18: pFrame[17] = temp[channelMap[17]]; + case 17: pFrame[16] = temp[channelMap[16]]; + case 16: pFrame[15] = temp[channelMap[15]]; + case 15: pFrame[14] = temp[channelMap[14]]; + case 14: pFrame[13] = temp[channelMap[13]]; + case 13: pFrame[12] = temp[channelMap[12]]; + case 12: pFrame[11] = temp[channelMap[11]]; + case 11: pFrame[10] = temp[channelMap[10]]; + case 10: pFrame[ 9] = temp[channelMap[ 9]]; + case 9: pFrame[ 8] = temp[channelMap[ 8]]; + case 8: pFrame[ 7] = temp[channelMap[ 7]]; + case 7: pFrame[ 6] = temp[channelMap[ 6]]; + case 6: pFrame[ 5] = temp[channelMap[ 5]]; + case 5: pFrame[ 4] = temp[channelMap[ 4]]; + case 4: pFrame[ 3] = temp[channelMap[ 3]]; + case 3: pFrame[ 2] = temp[channelMap[ 2]]; + case 2: pFrame[ 1] = temp[channelMap[ 1]]; + case 1: pFrame[ 0] = temp[channelMap[ 0]]; + } +} + +static void mal_rearrange_channels_s16(mal_int16* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +{ + mal_int16 temp[MAL_MAX_CHANNELS]; + mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + + switch (channels) { + case 18: pFrame[17] = temp[channelMap[17]]; + case 17: pFrame[16] = temp[channelMap[16]]; + case 16: pFrame[15] = temp[channelMap[15]]; + case 15: pFrame[14] = temp[channelMap[14]]; + case 14: pFrame[13] = temp[channelMap[13]]; + case 13: pFrame[12] = temp[channelMap[12]]; + case 12: pFrame[11] = temp[channelMap[11]]; + case 11: pFrame[10] = temp[channelMap[10]]; + case 10: pFrame[ 9] = temp[channelMap[ 9]]; + case 9: pFrame[ 8] = temp[channelMap[ 8]]; + case 8: pFrame[ 7] = temp[channelMap[ 7]]; + case 7: pFrame[ 6] = temp[channelMap[ 6]]; + case 6: pFrame[ 5] = temp[channelMap[ 5]]; + case 5: pFrame[ 4] = temp[channelMap[ 4]]; + case 4: pFrame[ 3] = temp[channelMap[ 3]]; + case 3: pFrame[ 2] = temp[channelMap[ 2]]; + case 2: pFrame[ 1] = temp[channelMap[ 1]]; + case 1: pFrame[ 0] = temp[channelMap[ 0]]; + } +} + +static void mal_rearrange_channels_s32(mal_int32* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +{ + mal_int32 temp[MAL_MAX_CHANNELS]; + mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + + switch (channels) { + case 18: pFrame[17] = temp[channelMap[17]]; + case 17: pFrame[16] = temp[channelMap[16]]; + case 16: pFrame[15] = temp[channelMap[15]]; + case 15: pFrame[14] = temp[channelMap[14]]; + case 14: pFrame[13] = temp[channelMap[13]]; + case 13: pFrame[12] = temp[channelMap[12]]; + case 12: pFrame[11] = temp[channelMap[11]]; + case 11: pFrame[10] = temp[channelMap[10]]; + case 10: pFrame[ 9] = temp[channelMap[ 9]]; + case 9: pFrame[ 8] = temp[channelMap[ 8]]; + case 8: pFrame[ 7] = temp[channelMap[ 7]]; + case 7: pFrame[ 6] = temp[channelMap[ 6]]; + case 6: pFrame[ 5] = temp[channelMap[ 5]]; + case 5: pFrame[ 4] = temp[channelMap[ 4]]; + case 4: pFrame[ 3] = temp[channelMap[ 3]]; + case 3: pFrame[ 2] = temp[channelMap[ 2]]; + case 2: pFrame[ 1] = temp[channelMap[ 1]]; + case 1: pFrame[ 0] = temp[channelMap[ 0]]; + } +} + +static void mal_rearrange_channels_f32(float* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS]) +{ + float temp[MAL_MAX_CHANNELS]; + mal_copy_memory(temp, pFrame, sizeof(temp[0]) * channels); + + switch (channels) { + case 18: pFrame[17] = temp[channelMap[17]]; + case 17: pFrame[16] = temp[channelMap[16]]; + case 16: pFrame[15] = temp[channelMap[15]]; + case 15: pFrame[14] = temp[channelMap[14]]; + case 14: pFrame[13] = temp[channelMap[13]]; + case 13: pFrame[12] = temp[channelMap[12]]; + case 12: pFrame[11] = temp[channelMap[11]]; + case 11: pFrame[10] = temp[channelMap[10]]; + case 10: pFrame[ 9] = temp[channelMap[ 9]]; + case 9: pFrame[ 8] = temp[channelMap[ 8]]; + case 8: pFrame[ 7] = temp[channelMap[ 7]]; + case 7: pFrame[ 6] = temp[channelMap[ 6]]; + case 6: pFrame[ 5] = temp[channelMap[ 5]]; + case 5: pFrame[ 4] = temp[channelMap[ 4]]; + case 4: pFrame[ 3] = temp[channelMap[ 3]]; + case 3: pFrame[ 2] = temp[channelMap[ 2]]; + case 2: pFrame[ 1] = temp[channelMap[ 1]]; + case 1: pFrame[ 0] = temp[channelMap[ 0]]; + } +} + +static void mal_rearrange_channels_generic(void* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_format format) +{ + mal_uint32 sampleSizeInBytes = mal_get_sample_size_in_bytes(format); + + mal_uint8 temp[MAL_MAX_CHANNELS * 8]; // x8 to ensure it's large enough for all formats. + mal_copy_memory(temp, pFrame, sampleSizeInBytes * channels); + + switch (channels) { + case 18: mal_copy_memory((mal_uint8*)pFrame + (17 * sampleSizeInBytes), &temp[channelMap[17] * sampleSizeInBytes], sampleSizeInBytes); + case 17: mal_copy_memory((mal_uint8*)pFrame + (16 * sampleSizeInBytes), &temp[channelMap[16] * sampleSizeInBytes], sampleSizeInBytes); + case 16: mal_copy_memory((mal_uint8*)pFrame + (15 * sampleSizeInBytes), &temp[channelMap[15] * sampleSizeInBytes], sampleSizeInBytes); + case 15: mal_copy_memory((mal_uint8*)pFrame + (14 * sampleSizeInBytes), &temp[channelMap[14] * sampleSizeInBytes], sampleSizeInBytes); + case 14: mal_copy_memory((mal_uint8*)pFrame + (13 * sampleSizeInBytes), &temp[channelMap[13] * sampleSizeInBytes], sampleSizeInBytes); + case 13: mal_copy_memory((mal_uint8*)pFrame + (12 * sampleSizeInBytes), &temp[channelMap[12] * sampleSizeInBytes], sampleSizeInBytes); + case 12: mal_copy_memory((mal_uint8*)pFrame + (11 * sampleSizeInBytes), &temp[channelMap[11] * sampleSizeInBytes], sampleSizeInBytes); + case 11: mal_copy_memory((mal_uint8*)pFrame + (10 * sampleSizeInBytes), &temp[channelMap[10] * sampleSizeInBytes], sampleSizeInBytes); + case 10: mal_copy_memory((mal_uint8*)pFrame + ( 9 * sampleSizeInBytes), &temp[channelMap[ 9] * sampleSizeInBytes], sampleSizeInBytes); + case 9: mal_copy_memory((mal_uint8*)pFrame + ( 8 * sampleSizeInBytes), &temp[channelMap[ 8] * sampleSizeInBytes], sampleSizeInBytes); + case 8: mal_copy_memory((mal_uint8*)pFrame + ( 7 * sampleSizeInBytes), &temp[channelMap[ 7] * sampleSizeInBytes], sampleSizeInBytes); + case 7: mal_copy_memory((mal_uint8*)pFrame + ( 6 * sampleSizeInBytes), &temp[channelMap[ 6] * sampleSizeInBytes], sampleSizeInBytes); + case 6: mal_copy_memory((mal_uint8*)pFrame + ( 5 * sampleSizeInBytes), &temp[channelMap[ 5] * sampleSizeInBytes], sampleSizeInBytes); + case 5: mal_copy_memory((mal_uint8*)pFrame + ( 4 * sampleSizeInBytes), &temp[channelMap[ 4] * sampleSizeInBytes], sampleSizeInBytes); + case 4: mal_copy_memory((mal_uint8*)pFrame + ( 3 * sampleSizeInBytes), &temp[channelMap[ 3] * sampleSizeInBytes], sampleSizeInBytes); + case 3: mal_copy_memory((mal_uint8*)pFrame + ( 2 * sampleSizeInBytes), &temp[channelMap[ 2] * sampleSizeInBytes], sampleSizeInBytes); + case 2: mal_copy_memory((mal_uint8*)pFrame + ( 1 * sampleSizeInBytes), &temp[channelMap[ 1] * sampleSizeInBytes], sampleSizeInBytes); + case 1: mal_copy_memory((mal_uint8*)pFrame + ( 0 * sampleSizeInBytes), &temp[channelMap[ 0] * sampleSizeInBytes], sampleSizeInBytes); + } +} + +static void mal_rearrange_channels(void* pFrame, mal_uint32 channels, mal_uint8 channelMap[MAL_MAX_CHANNELS], mal_format format) +{ + switch (format) + { + case mal_format_u8: mal_rearrange_channels_u8( (mal_uint8*)pFrame, channels, channelMap); break; + case mal_format_s16: mal_rearrange_channels_s16((mal_int16*)pFrame, channels, channelMap); break; + case mal_format_s32: mal_rearrange_channels_s32((mal_int32*)pFrame, channels, channelMap); break; + case mal_format_f32: mal_rearrange_channels_f32( (float*)pFrame, channels, channelMap); break; + default: mal_rearrange_channels_generic(pFrame, channels, channelMap, format); break; + } +} + +static void mal_dsp_mix_channels__dec(float* pFramesOut, mal_uint32 channelsOut, const mal_uint8 channelMapOut[MAL_MAX_CHANNELS], const float* pFramesIn, mal_uint32 channelsIn, const mal_uint8 channelMapIn[MAL_MAX_CHANNELS], mal_uint32 frameCount, mal_channel_mix_mode mode) +{ + mal_assert(pFramesOut != NULL); + mal_assert(channelsOut > 0); + mal_assert(pFramesIn != NULL); + mal_assert(channelsIn > 0); + mal_assert(channelsOut < channelsIn); + + (void)channelMapOut; + (void)channelMapIn; + + if (mode == mal_channel_mix_mode_basic) { + // Basic mode is where we just drop excess channels. + for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { + switch (channelsOut) { + case 17: pFramesOut[iFrame*channelsOut+16] = pFramesIn[iFrame*channelsIn+16]; + case 16: pFramesOut[iFrame*channelsOut+15] = pFramesIn[iFrame*channelsIn+15]; + case 15: pFramesOut[iFrame*channelsOut+14] = pFramesIn[iFrame*channelsIn+14]; + case 14: pFramesOut[iFrame*channelsOut+13] = pFramesIn[iFrame*channelsIn+13]; + case 13: pFramesOut[iFrame*channelsOut+12] = pFramesIn[iFrame*channelsIn+12]; + case 12: pFramesOut[iFrame*channelsOut+11] = pFramesIn[iFrame*channelsIn+11]; + case 11: pFramesOut[iFrame*channelsOut+10] = pFramesIn[iFrame*channelsIn+10]; + case 10: pFramesOut[iFrame*channelsOut+ 9] = pFramesIn[iFrame*channelsIn+ 9]; + case 9: pFramesOut[iFrame*channelsOut+ 8] = pFramesIn[iFrame*channelsIn+ 8]; + case 8: pFramesOut[iFrame*channelsOut+ 7] = pFramesIn[iFrame*channelsIn+ 7]; + case 7: pFramesOut[iFrame*channelsOut+ 6] = pFramesIn[iFrame*channelsIn+ 6]; + case 6: pFramesOut[iFrame*channelsOut+ 5] = pFramesIn[iFrame*channelsIn+ 5]; + case 5: pFramesOut[iFrame*channelsOut+ 4] = pFramesIn[iFrame*channelsIn+ 4]; + case 4: pFramesOut[iFrame*channelsOut+ 3] = pFramesIn[iFrame*channelsIn+ 3]; + case 3: pFramesOut[iFrame*channelsOut+ 2] = pFramesIn[iFrame*channelsIn+ 2]; + case 2: pFramesOut[iFrame*channelsOut+ 1] = pFramesIn[iFrame*channelsIn+ 1]; + case 1: pFramesOut[iFrame*channelsOut+ 0] = pFramesIn[iFrame*channelsIn+ 0]; + } + } + } else { + // Blend mode is where we just use simple averaging to blend based on spacial locality. + if (channelsOut == 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { + float total = 0; + switch (channelsIn) { + case 18: total += pFramesIn[iFrame*channelsIn+17]; + case 17: total += pFramesIn[iFrame*channelsIn+16]; + case 16: total += pFramesIn[iFrame*channelsIn+15]; + case 15: total += pFramesIn[iFrame*channelsIn+14]; + case 14: total += pFramesIn[iFrame*channelsIn+13]; + case 13: total += pFramesIn[iFrame*channelsIn+12]; + case 12: total += pFramesIn[iFrame*channelsIn+11]; + case 11: total += pFramesIn[iFrame*channelsIn+10]; + case 10: total += pFramesIn[iFrame*channelsIn+ 9]; + case 9: total += pFramesIn[iFrame*channelsIn+ 8]; + case 8: total += pFramesIn[iFrame*channelsIn+ 7]; + case 7: total += pFramesIn[iFrame*channelsIn+ 6]; + case 6: total += pFramesIn[iFrame*channelsIn+ 5]; + case 5: total += pFramesIn[iFrame*channelsIn+ 4]; + case 4: total += pFramesIn[iFrame*channelsIn+ 3]; + case 3: total += pFramesIn[iFrame*channelsIn+ 2]; + case 2: total += pFramesIn[iFrame*channelsIn+ 1]; + case 1: total += pFramesIn[iFrame*channelsIn+ 0]; + } + + pFramesOut[iFrame+0] = total / channelsIn; + } + } else if (channelsOut == 2) { + // TODO: Implement proper stereo blending. + mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); + } else { + // Fall back to basic mode. + mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); + } + } +} + +static void mal_dsp_mix_channels__inc(float* pFramesOut, mal_uint32 channelsOut, const mal_uint8 channelMapOut[MAL_MAX_CHANNELS], const float* pFramesIn, mal_uint32 channelsIn, const mal_uint8 channelMapIn[MAL_MAX_CHANNELS], mal_uint32 frameCount, mal_channel_mix_mode mode) +{ + mal_assert(pFramesOut != NULL); + mal_assert(channelsOut > 0); + mal_assert(pFramesIn != NULL); + mal_assert(channelsIn > 0); + mal_assert(channelsOut > channelsIn); + + (void)channelMapOut; + (void)channelMapIn; + + if (mode == mal_channel_mix_mode_basic) {\ + // Basic mode is where we just zero out extra channels. + for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { + switch (channelsIn) { + case 17: pFramesOut[iFrame*channelsOut+16] = pFramesIn[iFrame*channelsIn+16]; + case 16: pFramesOut[iFrame*channelsOut+15] = pFramesIn[iFrame*channelsIn+15]; + case 15: pFramesOut[iFrame*channelsOut+14] = pFramesIn[iFrame*channelsIn+14]; + case 14: pFramesOut[iFrame*channelsOut+13] = pFramesIn[iFrame*channelsIn+13]; + case 13: pFramesOut[iFrame*channelsOut+12] = pFramesIn[iFrame*channelsIn+12]; + case 12: pFramesOut[iFrame*channelsOut+11] = pFramesIn[iFrame*channelsIn+11]; + case 11: pFramesOut[iFrame*channelsOut+10] = pFramesIn[iFrame*channelsIn+10]; + case 10: pFramesOut[iFrame*channelsOut+ 9] = pFramesIn[iFrame*channelsIn+ 9]; + case 9: pFramesOut[iFrame*channelsOut+ 8] = pFramesIn[iFrame*channelsIn+ 8]; + case 8: pFramesOut[iFrame*channelsOut+ 7] = pFramesIn[iFrame*channelsIn+ 7]; + case 7: pFramesOut[iFrame*channelsOut+ 6] = pFramesIn[iFrame*channelsIn+ 6]; + case 6: pFramesOut[iFrame*channelsOut+ 5] = pFramesIn[iFrame*channelsIn+ 5]; + case 5: pFramesOut[iFrame*channelsOut+ 4] = pFramesIn[iFrame*channelsIn+ 4]; + case 4: pFramesOut[iFrame*channelsOut+ 3] = pFramesIn[iFrame*channelsIn+ 3]; + case 3: pFramesOut[iFrame*channelsOut+ 2] = pFramesIn[iFrame*channelsIn+ 2]; + case 2: pFramesOut[iFrame*channelsOut+ 1] = pFramesIn[iFrame*channelsIn+ 1]; + case 1: pFramesOut[iFrame*channelsOut+ 0] = pFramesIn[iFrame*channelsIn+ 0]; + } + + // Zero out extra channels. + switch (channelsOut - channelsIn) { + case 17: pFramesOut[iFrame*channelsOut+16] = 0; + case 16: pFramesOut[iFrame*channelsOut+15] = 0; + case 15: pFramesOut[iFrame*channelsOut+14] = 0; + case 14: pFramesOut[iFrame*channelsOut+13] = 0; + case 13: pFramesOut[iFrame*channelsOut+12] = 0; + case 12: pFramesOut[iFrame*channelsOut+11] = 0; + case 11: pFramesOut[iFrame*channelsOut+10] = 0; + case 10: pFramesOut[iFrame*channelsOut+ 9] = 0; + case 9: pFramesOut[iFrame*channelsOut+ 8] = 0; + case 8: pFramesOut[iFrame*channelsOut+ 7] = 0; + case 7: pFramesOut[iFrame*channelsOut+ 6] = 0; + case 6: pFramesOut[iFrame*channelsOut+ 5] = 0; + case 5: pFramesOut[iFrame*channelsOut+ 4] = 0; + case 4: pFramesOut[iFrame*channelsOut+ 3] = 0; + case 3: pFramesOut[iFrame*channelsOut+ 2] = 0; + case 2: pFramesOut[iFrame*channelsOut+ 1] = 0; + case 1: pFramesOut[iFrame*channelsOut+ 0] = 0; + } + } + } else { + // Using blended mixing mode. Basically this is just the mode where audio is distributed across all channels + // based on spacial locality. + if (channelsIn == 1) { + for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { + switch (channelsOut) { + case 18: pFramesOut[iFrame*channelsOut+17] = pFramesIn[iFrame*channelsIn+0]; + case 17: pFramesOut[iFrame*channelsOut+16] = pFramesIn[iFrame*channelsIn+0]; + case 16: pFramesOut[iFrame*channelsOut+15] = pFramesIn[iFrame*channelsIn+0]; + case 15: pFramesOut[iFrame*channelsOut+14] = pFramesIn[iFrame*channelsIn+0]; + case 14: pFramesOut[iFrame*channelsOut+13] = pFramesIn[iFrame*channelsIn+0]; + case 13: pFramesOut[iFrame*channelsOut+12] = pFramesIn[iFrame*channelsIn+0]; + case 12: pFramesOut[iFrame*channelsOut+11] = pFramesIn[iFrame*channelsIn+0]; + case 11: pFramesOut[iFrame*channelsOut+10] = pFramesIn[iFrame*channelsIn+0]; + case 10: pFramesOut[iFrame*channelsOut+ 9] = pFramesIn[iFrame*channelsIn+0]; + case 9: pFramesOut[iFrame*channelsOut+ 8] = pFramesIn[iFrame*channelsIn+0]; + case 8: pFramesOut[iFrame*channelsOut+ 7] = pFramesIn[iFrame*channelsIn+0]; + case 7: pFramesOut[iFrame*channelsOut+ 6] = pFramesIn[iFrame*channelsIn+0]; + case 6: pFramesOut[iFrame*channelsOut+ 5] = pFramesIn[iFrame*channelsIn+0]; + case 5: pFramesOut[iFrame*channelsOut+ 4] = pFramesIn[iFrame*channelsIn+0]; + case 4: pFramesOut[iFrame*channelsOut+ 3] = pFramesIn[iFrame*channelsIn+0]; + case 3: pFramesOut[iFrame*channelsOut+ 2] = pFramesIn[iFrame*channelsIn+0]; + case 2: pFramesOut[iFrame*channelsOut+ 1] = pFramesIn[iFrame*channelsIn+0]; + case 1: pFramesOut[iFrame*channelsOut+ 0] = pFramesIn[iFrame*channelsIn+0]; + } + } + } else if (channelsIn == 2) { + // TODO: Implement an optimized stereo conversion. + mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); + } else { + // Fall back to basic mixing mode. + mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mal_channel_mix_mode_basic); + } + } +} + +static void mal_dsp_mix_channels(float* pFramesOut, mal_uint32 channelsOut, const mal_uint8 channelMapOut[MAL_MAX_CHANNELS], const float* pFramesIn, mal_uint32 channelsIn, const mal_uint8 channelMapIn[MAL_MAX_CHANNELS], mal_uint32 frameCount, mal_channel_mix_mode mode) +{ + if (channelsIn < channelsOut) { + // Increasing the channel count. + mal_dsp_mix_channels__inc(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mode); + } else { + // Decreasing the channel count. + mal_dsp_mix_channels__dec(pFramesOut, channelsOut, channelMapOut, pFramesIn, channelsIn, channelMapIn, frameCount, mode); + } +} + + +mal_uint32 mal_dsp__src_on_read(mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + mal_dsp* pDSP = (mal_dsp*)pUserData; + mal_assert(pDSP != NULL); + + return pDSP->onRead(frameCount, pFramesOut, pDSP->pUserDataForOnRead); +} + +mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP) +{ + if (pDSP == NULL) return MAL_INVALID_ARGS; + mal_zero_object(pDSP); + pDSP->config = *pConfig; + pDSP->onRead = onRead; + pDSP->pUserDataForOnRead = pUserData; + + if (pDSP->config.cacheSizeInFrames > MAL_SRC_CACHE_SIZE_IN_FRAMES || pDSP->config.cacheSizeInFrames == 0) { + pDSP->config.cacheSizeInFrames = MAL_SRC_CACHE_SIZE_IN_FRAMES; + } + + if (pConfig->sampleRateIn != pConfig->sampleRateOut) { + pDSP->isSRCRequired = MAL_TRUE; + + mal_src_config srcConfig; + srcConfig.sampleRateIn = pConfig->sampleRateIn; + srcConfig.sampleRateOut = pConfig->sampleRateOut; + srcConfig.formatIn = pConfig->formatIn; + srcConfig.formatOut = mal_format_f32; + srcConfig.channels = pConfig->channelsIn; + srcConfig.algorithm = mal_src_algorithm_linear; + srcConfig.cacheSizeInFrames = pConfig->cacheSizeInFrames; + mal_result result = mal_src_init(&srcConfig, mal_dsp__src_on_read, pDSP, &pDSP->src); + if (result != MAL_SUCCESS) { + return result; + } + } + + + + pDSP->isChannelMappingRequired = MAL_FALSE; + if (pConfig->channelMapIn[0] != MAL_CHANNEL_NONE && pConfig->channelMapOut[0] != MAL_CHANNEL_NONE) { // <-- Channel mapping will be ignored if the first channel map is MAL_CHANNEL_NONE. + // When using channel mapping we need to figure out a shuffling table. The first thing to do is convert the input channel map + // so that it contains the same number of channels as the output channel count. + mal_uint32 iChannel; + mal_uint32 channelsMin = mal_min(pConfig->channelsIn, pConfig->channelsOut); + for (iChannel = 0; iChannel < channelsMin; ++iChannel) { + pDSP->channelMapInPostMix[iChannel] = pConfig->channelMapIn[iChannel]; + } + + // Any excess channels need to be filled with the relevant channels from the output channel map. Currently we're justing filling it with + // the first channels that are not present in the input channel map. + if (pConfig->channelsOut > pConfig->channelsIn) { + for (iChannel = pConfig->channelsIn; iChannel < pConfig->channelsOut; ++iChannel) { + mal_uint8 newChannel = MAL_CHANNEL_NONE; + for (mal_uint32 iChannelOut = 0; iChannelOut < pConfig->channelsOut; ++iChannelOut) { + mal_bool32 exists = MAL_FALSE; + for (mal_uint32 iChannelIn = 0; iChannelIn < pConfig->channelsIn; ++iChannelIn) { + if (pConfig->channelMapOut[iChannelOut] == pConfig->channelMapIn[iChannelIn]) { + exists = MAL_TRUE; + break; + } + } + + if (!exists) { + newChannel = pConfig->channelMapOut[iChannelOut]; + break; + } + } + + pDSP->channelMapInPostMix[iChannel] = newChannel; + } + } + + // We only need to do a channel mapping if the map after mixing is different to the final output map. + for (iChannel = 0; iChannel < pConfig->channelsOut; ++iChannel) { + if (pDSP->channelMapInPostMix[iChannel] != pConfig->channelMapOut[iChannel]) { + pDSP->isChannelMappingRequired = MAL_TRUE; + break; + } + } + + // Now we need to create the shuffling table. + if (pDSP->isChannelMappingRequired) { + for (mal_uint32 iChannelIn = 0; iChannelIn < pConfig->channelsOut; ++iChannelIn) { + for (mal_uint32 iChannelOut = 0; iChannelOut < pConfig->channelsOut; ++iChannelOut) { + if (pDSP->channelMapInPostMix[iChannelOut] == pConfig->channelMapOut[iChannelIn]) { + pDSP->channelShuffleTable[iChannelOut] = (mal_uint8)iChannelIn; + } + } + } + } + } + + if (pConfig->formatIn == pConfig->formatOut && pConfig->channelsIn == pConfig->channelsOut && pConfig->sampleRateIn == pConfig->sampleRateOut && !pDSP->isChannelMappingRequired) { + pDSP->isPassthrough = MAL_TRUE; + } else { + pDSP->isPassthrough = MAL_FALSE; + } + + return MAL_SUCCESS; +} + +mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut) +{ + if (pDSP == NULL || pFramesOut == NULL) return 0; + + // Fast path. + if (pDSP->isPassthrough) { + return pDSP->onRead(frameCount, pFramesOut, pDSP->pUserDataForOnRead); + } + + + // Slower path - where the real work is done. + mal_uint8 pFrames[2][MAL_MAX_CHANNELS * 512 * MAL_MAX_SAMPLE_SIZE_IN_BYTES]; + mal_format pFramesFormat[2]; + mal_uint32 iFrames = 0; // <-- Used as an index into pFrames and cycles between 0 and 1. + + mal_uint32 totalFramesRead = 0; + while (frameCount > 0) { + iFrames = 0; + + mal_uint32 framesToRead = mal_countof(pFrames[0]) / (mal_max(pDSP->config.channelsIn, pDSP->config.channelsOut) * MAL_MAX_SAMPLE_SIZE_IN_BYTES); + if (framesToRead > frameCount) { + framesToRead = frameCount; + } + + // The initial filling of sample data depends on whether or not we are using SRC. + mal_uint32 framesRead = 0; + if (pDSP->isSRCRequired) { + framesRead = mal_src_read_frames(&pDSP->src, framesToRead, pFrames[iFrames]); + pFramesFormat[iFrames] = pDSP->src.config.formatOut; // Should always be f32. + } else { + framesRead = pDSP->onRead(framesToRead, pFrames[iFrames], pDSP->pUserDataForOnRead); + pFramesFormat[iFrames] = pDSP->config.formatIn; + } + + if (framesRead == 0) { + break; + } + + + // Channel mixing. The input format must be in f32 which may require a conversion. + if (pDSP->config.channelsIn != pDSP->config.channelsOut) { + if (pFramesFormat[iFrames] != mal_format_f32) { + mal_pcm_convert(pFrames[(iFrames + 1) % 2], mal_format_f32, pFrames[iFrames], pDSP->config.formatIn, framesRead * pDSP->config.channelsIn); + iFrames = (iFrames + 1) % 2; + pFramesFormat[iFrames] = mal_format_f32; + } + + mal_dsp_mix_channels((float*)(pFrames[(iFrames + 1) % 2]), pDSP->config.channelsOut, pDSP->config.channelMapOut, (const float*)(pFrames[iFrames]), pDSP->config.channelsIn, pDSP->config.channelMapIn, framesRead, mal_channel_mix_mode_blend); + iFrames = (iFrames + 1) % 2; + pFramesFormat[iFrames] = mal_format_f32; + } + + + // Channel mapping. + if (pDSP->isChannelMappingRequired) { + for (mal_uint32 i = 0; i < framesRead; ++i) { + mal_rearrange_channels(pFrames[iFrames] + (i * pDSP->config.channelsOut * mal_get_sample_size_in_bytes(pFramesFormat[iFrames])), pDSP->config.channelsOut, pDSP->channelShuffleTable, pFramesFormat[iFrames]); + } + } + + + // Final conversion to output format. + mal_pcm_convert(pFramesOut, pDSP->config.formatOut, pFrames[iFrames], pFramesFormat[iFrames], framesRead * pDSP->config.channelsOut); + + pFramesOut = (mal_uint8*)pFramesOut + (framesRead * pDSP->config.channelsOut * mal_get_sample_size_in_bytes(pDSP->config.formatOut)); + frameCount -= framesRead; + totalFramesRead += framesRead; + } + + return totalFramesRead; +} + + +mal_uint32 mal_calculate_frame_count_after_src(mal_uint32 sampleRateOut, mal_uint32 sampleRateIn, mal_uint32 frameCountIn) +{ + double srcRatio = (double)sampleRateOut / sampleRateIn; + double frameCountOutF = frameCountIn * srcRatio; + + mal_uint32 frameCountOut = (mal_uint32)frameCountOutF; + + // If the output frame count is fractional, make sure we add an extra frame to ensure there's enough room for that last sample. + if ((frameCountOutF - frameCountOut) > 0.0) { + frameCountOut += 1; + } + + return frameCountOut; +} + +typedef struct +{ + const void* pDataIn; + mal_format formatIn; + mal_uint32 channelsIn; + mal_uint32 totalFrameCount; + mal_uint32 iNextFrame; +} mal_convert_frames__data; + +mal_uint32 mal_convert_frames__on_read(mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + mal_convert_frames__data* pData = (mal_convert_frames__data*)pUserData; + mal_assert(pData != NULL); + mal_assert(pData->totalFrameCount >= pData->iNextFrame); + + mal_uint32 framesToRead = frameCount; + mal_uint32 framesRemaining = (pData->totalFrameCount - pData->iNextFrame); + if (framesToRead > framesRemaining) { + framesToRead = framesRemaining; + } + + mal_uint32 frameSizeInBytes = mal_get_sample_size_in_bytes(pData->formatIn) * pData->channelsIn; + mal_copy_memory(pFramesOut, (const mal_uint8*)pData->pDataIn + (frameSizeInBytes * pData->iNextFrame), frameSizeInBytes * framesToRead); + + pData->iNextFrame += framesToRead; + return framesToRead; +} + +mal_uint32 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 channelsOut, mal_uint32 sampleRateOut, const void* pIn, mal_format formatIn, mal_uint32 channelsIn, mal_uint32 sampleRateIn, mal_uint32 frameCountIn) +{ + if (frameCountIn == 0) { + return 0; + } + + mal_uint32 frameCountOut = mal_calculate_frame_count_after_src(sampleRateOut, sampleRateIn, frameCountIn); + if (pOut == NULL) { + return frameCountOut; + } + + mal_convert_frames__data data; + data.pDataIn = pIn; + data.formatIn = formatIn; + data.channelsIn = channelsIn; + data.totalFrameCount = frameCountIn; + data.iNextFrame = 0; + + mal_dsp_config config; + mal_zero_object(&config); + config.formatIn = formatIn; + config.channelsIn = channelsIn; + config.sampleRateIn = sampleRateIn; + config.formatOut = formatOut; + config.channelsOut = channelsOut; + config.sampleRateOut = sampleRateOut; + + mal_dsp dsp; + if (mal_dsp_init(&config, mal_convert_frames__on_read, &data, &dsp) != MAL_SUCCESS) { + return 0; + } + + return mal_dsp_read_frames(&dsp, frameCountOut, pOut); +} + + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Miscellaneous Helpers +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void mal_blend_f32(float* pOut, float* pInA, float* pInB, float factor, mal_uint32 channels) +{ + for (mal_uint32 i = 0; i < channels; ++i) { + pOut[i] = mal_mix_f32(pInA[i], pInB[i], factor); + } +} + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// +// +// AUTO-GENERATED +// +// +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// FORMAT CONVERSION +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if 0 +#include "tools/malgen/bin/malgen_test0.c" +#else +void mal_pcm_u8_to_s16(short* pOut, const unsigned char* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x - 128; + r = r << 8; + pOut[i] = (short)r; + } +} + +void mal_pcm_u8_to_s24(void* pOut, const unsigned char* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x - 128; + r = r << 16; + ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); + } +} + +void mal_pcm_u8_to_s32(int* pOut, const unsigned char* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x - 128; + r = r << 24; + pOut[i] = (int)r; + } +} + +void mal_pcm_u8_to_f32(float* pOut, const unsigned char* pIn, unsigned int count) +{ + float r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x * 0.00784313725490196078f; + r = r - 1; + pOut[i] = (float)r; + } +} + +void mal_pcm_s16_to_u8(unsigned char* pOut, const short* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x >> 8; + r = r + 128; + pOut[i] = (unsigned char)r; + } +} + +void mal_pcm_s16_to_s24(void* pOut, const short* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x << 8; + ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); + } +} + +void mal_pcm_s16_to_s32(int* pOut, const short* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x << 16; + pOut[i] = (int)r; + } +} + +void mal_pcm_s16_to_f32(float* pOut, const short* pIn, unsigned int count) +{ + float r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = (float)(x + 32768); + r = r * 0.00003051804379339284f; + r = r - 1; + pOut[i] = (float)r; + } +} + +void mal_pcm_s24_to_u8(unsigned char* pOut, const void* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 16; + r = r + 128; + pOut[i] = (unsigned char)r; + } +} + +void mal_pcm_s24_to_s16(short* pOut, const void* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 8; + pOut[i] = (short)r; + } +} + +void mal_pcm_s24_to_s32(int* pOut, const void* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + r = x << 8; + pOut[i] = (int)r; + } +} + +void mal_pcm_s24_to_f32(float* pOut, const void* pIn, unsigned int count) +{ + float r; + for (unsigned int i = 0; i < count; ++i) { + int x = ((int)(((unsigned int)(((unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + r = (float)(x + 8388608); + r = r * 0.00000011920929665621f; + r = r - 1; + pOut[i] = (float)r; + } +} + +void mal_pcm_s32_to_u8(unsigned char* pOut, const int* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x >> 24; + r = r + 128; + pOut[i] = (unsigned char)r; + } +} + +void mal_pcm_s32_to_s16(short* pOut, const int* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x >> 16; + pOut[i] = (short)r; + } +} + +void mal_pcm_s32_to_s24(void* pOut, const int* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + r = x >> 8; + ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); + } +} + +void mal_pcm_s32_to_f32(float* pOut, const int* pIn, unsigned int count) +{ + float r; + for (unsigned int i = 0; i < count; ++i) { + int x = pIn[i]; + int s; + s = ((*((int*)&x)) & 0x80000000) >> 31; + s = s + 2147483647; + r = x / (float)(unsigned int)s; + pOut[i] = (float)r; + } +} + +void mal_pcm_f32_to_u8(unsigned char* pOut, const float* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + float x = pIn[i]; + float c; + int s; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + s = ((*((int*)&x)) & 0x80000000) >> 31; + s = s + 127; + r = (int)(c * s); + r = r + 128; + pOut[i] = (unsigned char)r; + } +} + +void mal_pcm_f32_to_s16(short* pOut, const float* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + float x = pIn[i]; + float c; + int s; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + s = ((*((int*)&x)) & 0x80000000) >> 31; + s = s + 32767; + r = (int)(c * s); + pOut[i] = (short)r; + } +} + +void mal_pcm_f32_to_s24(void* pOut, const float* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + float x = pIn[i]; + float c; + int s; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + s = ((*((int*)&x)) & 0x80000000) >> 31; + s = s + 8388607; + r = (int)(c * s); + ((unsigned char*)pOut)[(i*3)+0] = (unsigned char)(r & 0xFF); ((unsigned char*)pOut)[(i*3)+1] = (unsigned char)((r & 0xFF00) >> 8); ((unsigned char*)pOut)[(i*3)+2] = (unsigned char)((r & 0xFF0000) >> 16); + } +} + +void mal_pcm_f32_to_s32(int* pOut, const float* pIn, unsigned int count) +{ + int r; + for (unsigned int i = 0; i < count; ++i) { + float x = pIn[i]; + float c; + mal_int64 s; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + s = ((*((int*)&x)) & 0x80000000) >> 31; + s = s + 2147483647; + r = (int)(c * s); + pOut[i] = (int)r; + } +} +#endif + +#endif + + +// REVISION HISTORY +// ================ +// +// v0.x - 2017-xx-xx +// - Add mal_convert_frames(). This is a high-level helper API for performing a one-time, bulk conversion of +// audio data to a different format. +// - Expose the mutex APIs. +// +// v0.5 - 2017-11-11 +// - API CHANGE: The mal_context_init() function now takes a pointer to a mal_context_config object for +// configuring the context. The works in the same kind of way as the device config. The rationale for this +// change is to give applications better control over context-level properties, add support for backend- +// specific configurations, and support extensibility without breaking the API. +// - API CHANGE: The alsa.preferPlugHW device config variable has been removed since it's not really useful for +// anything anymore. +// - ALSA: By default, device enumeration will now only enumerate over unique card/device pairs. Applications +// can enable verbose device enumeration by setting the alsa.useVerboseDeviceEnumeration context config +// variable. +// - ALSA: When opening a device in shared mode (the default), the dmix/dsnoop plugin will be prioritized. If +// this fails it will fall back to the hw plugin. With this change the preferExclusiveMode config is now +// honored. Note that this does not happen when alsa.useVerboseDeviceEnumeration is set to true (see above) +// which is by design. +// - ALSA: Add support for excluding the "null" device using the alsa.excludeNullDevice context config variable. +// - ALSA: Fix a bug with channel mapping which causes an assertion to fail. +// - Fix errors with enumeration when pInfo is set to NULL. +// - OSS: Fix a bug when starting a device when the client sends 0 samples for the initial buffer fill. +// +// v0.4 - 2017-11-05 +// - API CHANGE: The log callback is now per-context rather than per-device and as is thus now passed to +// mal_context_init(). The rationale for this change is that it allows applications to capture diagnostic +// messages at the context level. Previously this was only available at the device level. +// - API CHANGE: The device config passed to mal_device_init() is now const. +// - Added support for OSS which enables support on BSD platforms. +// - Added support for WinMM (waveOut/waveIn). +// - Added support for UWP (Universal Windows Platform) applications. Currently C++ only. +// - Added support for exclusive mode for selected backends. Currently supported on WASAPI. +// - POSIX builds no longer require explicit linking to libpthread (-lpthread). +// - ALSA: Explicit linking to libasound (-lasound) is no longer required. +// - ALSA: Latency improvements. +// - ALSA: Use MMAP mode where available. This can be disabled with the alsa.noMMap config. +// - ALSA: Use "hw" devices instead of "plughw" devices by default. This can be disabled with the +// alsa.preferPlugHW config. +// - WASAPI is now the highest priority backend on Windows platforms. +// - Fixed an error with sample rate conversion which was causing crackling when capturing. +// - Improved error handling. +// - Improved compiler support. +// - Miscellaneous bug fixes. +// +// v0.3 - 2017-06-19 +// - API CHANGE: Introduced the notion of a context. The context is the highest level object and is required for +// enumerating and creating devices. Now, applications must first create a context, and then use that to +// enumerate and create devices. The reason for this change is to ensure device enumeration and creation is +// tied to the same backend. In addition, some backends are better suited to this design. +// - API CHANGE: Removed the rewinding APIs because they're too inconsistent across the different backends, hard +// to test and maintain, and just generally unreliable. +// - Added helper APIs for initializing mal_device_config objects. +// - Null Backend: Fixed a crash when recording. +// - Fixed build for UWP. +// - Added support for f32 formats to the OpenSL|ES backend. +// - Added initial implementation of the WASAPI backend. +// - Added initial implementation of the OpenAL backend. +// - Added support for low quality linear sample rate conversion. +// - Added early support for basic channel mapping. +// +// v0.2 - 2016-10-28 +// - API CHANGE: Add user data pointer as the last parameter to mal_device_init(). The rationale for this +// change is to ensure the logging callback has access to the user data during initialization. +// - API CHANGE: Have device configuration properties be passed to mal_device_init() via a structure. Rationale: +// 1) The number of parameters is just getting too much. +// 2) It makes it a bit easier to add new configuration properties in the future. In particular, there's a +// chance there will be support added for backend-specific properties. +// - Dropped support for f64, A-law and Mu-law formats since they just aren't common enough to justify the +// added maintenance cost. +// - DirectSound: Increased the default buffer size for capture devices. +// - Added initial implementation of the OpenSL|ES backend. +// +// v0.1 - 2016-10-21 +// - Initial versioned release. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/src/raylib.h b/src/raylib.h index 392e0a24..ae1e5813 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -486,6 +486,8 @@ typedef struct Wave { // Sound source type typedef struct Sound { + void* handle; // A pointer to internal data used by the sound system. + unsigned int source; // OpenAL audio source id unsigned int buffer; // OpenAL audio buffer id int format; // OpenAL audio format specifier -- cgit v1.2.3 From b0852002b83c615a67ae1b38feb8940213f6eed9 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 12 Nov 2017 14:54:37 +1000 Subject: Rename SoundInternal to SoundData for consistency with MusicData. --- src/audio.c | 42 +++++++++++++++++++++++------------------- src/raylib.h | 2 +- 2 files changed, 24 insertions(+), 20 deletions(-) (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index a5f117b5..83c80d8e 100644 --- a/src/audio.c +++ b/src/audio.c @@ -211,8 +211,8 @@ void TraceLog(int msgType, const char *text, ...); // Show trace lo #define DEVICE_CHANNELS 2 #define DEVICE_SAMPLE_RATE 44100 -typedef struct SoundInternal SoundInternal; -struct SoundInternal +typedef struct SoundData SoundData; +struct SoundData { mal_format format; mal_uint32 channels; @@ -224,8 +224,8 @@ struct SoundInternal bool playing; bool paused; bool looping; - SoundInternal* next; - SoundInternal* prev; + SoundData* next; + SoundData* prev; mal_uint8 data[1]; // Raw audio data. }; @@ -234,10 +234,10 @@ static mal_device device; static mal_bool32 isAudioInitialized = MAL_FALSE; static float masterVolume = 1; static mal_mutex soundLock; -static SoundInternal* firstSound; // Sounds are tracked in a linked list. -static SoundInternal* lastSound; +static SoundData* firstSound; // Sounds are tracked in a linked list. +static SoundData* lastSound; -static void AppendSound(SoundInternal* internalSound) +static void AppendSound(SoundData* internalSound) { mal_mutex_lock(&context, &soundLock); { @@ -253,7 +253,7 @@ static void AppendSound(SoundInternal* internalSound) mal_mutex_unlock(&context, &soundLock); } -static void RemoveSound(SoundInternal* internalSound) +static void RemoveSound(SoundData* internalSound) { mal_mutex_lock(&context, &soundLock); { @@ -294,7 +294,7 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC float* pFramesOutF = (float*)pFramesOut; // <-- Just for convenience. // Sounds. - for (SoundInternal* internalSound = firstSound; internalSound != NULL; internalSound = internalSound->next) + for (SoundData* internalSound = firstSound; internalSound != NULL; internalSound = internalSound->next) { // Ignore stopped or paused sounds. if (!internalSound->playing || internalSound->paused) { @@ -583,7 +583,7 @@ Sound LoadSoundFromWave(Wave wave) TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to get frame count for format conversion."); } - SoundInternal* internalSound = (SoundInternal*)calloc(sizeof(*internalSound) + (frameCount*DEVICE_CHANNELS*4), 1); // <-- Make sure this is initialized to zero for safety. + SoundData* internalSound = (SoundData*)calloc(sizeof(*internalSound) + (frameCount*DEVICE_CHANNELS*4), 1); // <-- Make sure this is initialized to zero for safety. if (internalSound == NULL) { TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to allocate memory for internal buffer"); } @@ -678,7 +678,7 @@ void UnloadWave(Wave wave) void UnloadSound(Sound sound) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; RemoveSound(internalSound); free(internalSound); #else @@ -696,7 +696,7 @@ void UnloadSound(Sound sound) void UpdateSound(Sound sound, const void *data, int samplesCount) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound"); @@ -739,7 +739,7 @@ void UpdateSound(Sound sound, const void *data, int samplesCount) void PlaySound(Sound sound) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "PlaySound() : Invalid sound"); @@ -773,7 +773,7 @@ void PlaySound(Sound sound) void PauseSound(Sound sound) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "PauseSound() : Invalid sound"); @@ -790,7 +790,7 @@ void PauseSound(Sound sound) void ResumeSound(Sound sound) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "ResumeSound() : Invalid sound"); @@ -811,7 +811,7 @@ void ResumeSound(Sound sound) void StopSound(Sound sound) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "StopSound() : Invalid sound"); @@ -829,7 +829,7 @@ void StopSound(Sound sound) bool IsSoundPlaying(Sound sound) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "IsSoundPlaying() : Invalid sound"); @@ -852,7 +852,7 @@ bool IsSoundPlaying(Sound sound) void SetSoundVolume(Sound sound, float volume) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "SetSoundVolume() : Invalid sound"); @@ -869,7 +869,7 @@ void SetSoundVolume(Sound sound, float volume) void SetSoundPitch(Sound sound, float pitch) { #if USE_MINI_AL - SoundInternal* internalSound = (SoundInternal*)sound.handle; + SoundData* internalSound = (SoundData*)sound.handle; if (internalSound == NULL) { TraceLog(LOG_ERROR, "SetSoundPitch() : Invalid sound"); @@ -1173,7 +1173,11 @@ void UnloadMusicStream(Music music) // Start music playing (open stream) void PlayMusicStream(Music music) { +#if USE_MINI_AL + //InternalMusic* internalMusic = +#else alSourcePlay(music->stream.source); +#endif } // Pause music playing diff --git a/src/raylib.h b/src/raylib.h index ae1e5813..b21b0878 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -486,7 +486,7 @@ typedef struct Wave { // Sound source type typedef struct Sound { - void* handle; // A pointer to internal data used by the sound system. + void* handle; // A pointer to internal data used by the audio system. unsigned int source; // OpenAL audio source id unsigned int buffer; // OpenAL audio buffer id -- cgit v1.2.3 From 02dd4d32b5ed7586e6691a6cdae03a658127c7be Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 12 Nov 2017 11:00:28 +0100 Subject: Allow custom distortion shader - IN PROGRESS - --- src/raylib.h | 17 +++++----- src/rlgl.c | 30 ++++++++--------- src/rlgl.h | 108 ++++++++++++++++++++++++++++++++--------------------------- 3 files changed, 81 insertions(+), 74 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index e5b74743..d5e907ab 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1095,14 +1095,15 @@ RLAPI void BeginBlendMode(int mode); // Beg RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) // VR control functions -VrDeviceInfo GetVrDeviceInfo(int vrDeviceType); // Get VR device information for some standard devices -void InitVrSimulator(VrDeviceInfo info); // Init VR simulator for selected device parameters -RLAPI void CloseVrSimulator(void); // Close VR simulator for current device -RLAPI bool IsVrSimulatorReady(void); // Detect if VR simulator is ready -RLAPI void UpdateVrTracking(Camera *camera); // Update VR tracking (position and orientation) and camera -RLAPI void ToggleVrMode(void); // Enable/Disable VR experience -RLAPI void BeginVrDrawing(void); // Begin VR simulator stereo rendering -RLAPI void EndVrDrawing(void); // End VR simulator stereo rendering +RLAPI VrDeviceInfo GetVrDeviceInfo(int vrDeviceType); // Get VR device information for some standard devices +RLAPI void InitVrSimulator(VrDeviceInfo info); // Init VR simulator for selected device parameters +RLAPI void CloseVrSimulator(void); // Close VR simulator for current device +RLAPI bool IsVrSimulatorReady(void); // Detect if VR simulator is ready +RLAPI void SetVrDistortionShader(Shader shader); // Set VR distortion shader for stereoscopic rendering +RLAPI void UpdateVrTracking(Camera *camera); // Update VR tracking (position and orientation) and camera +RLAPI void ToggleVrMode(void); // Enable/Disable VR experience +RLAPI void BeginVrDrawing(void); // Begin VR simulator stereo rendering +RLAPI void EndVrDrawing(void); // End VR simulator stereo rendering //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) diff --git a/src/rlgl.c b/src/rlgl.c index 518b0470..f273e55d 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -232,7 +232,7 @@ typedef struct DrawCall { typedef struct VrStereoConfig { RenderTexture2D stereoFbo; // VR stereo rendering framebuffer Shader distortionShader; // VR stereo rendering distortion shader - //Rectangle eyesViewport[2]; // VR stereo rendering eyes viewports + Rectangle eyesViewport[2]; // VR stereo rendering eyes viewports Matrix eyesProjection[2]; // VR stereo rendering eyes projection matrices Matrix eyesViewOffset[2]; // VR stereo rendering eyes view offset matrices } VrStereoConfig; @@ -2926,7 +2926,7 @@ void InitVrSimulator(VrDeviceInfo info) vrConfig.stereoFbo = rlLoadRenderTexture(screenWidth, screenHeight); #if defined(SUPPORT_DISTORTION_SHADER) - // Load distortion shader (initialized by default with Oculus Rift CV1 parameters) + // Load distortion shader unsigned int vertexShaderId = CompileShader(distortionVShaderStr, GL_VERTEX_SHADER); unsigned int fragmentShaderId = CompileShader(distortionFShaderStr, GL_FRAGMENT_SHADER); @@ -2934,6 +2934,7 @@ void InitVrSimulator(VrDeviceInfo info) if (vrConfig.distortionShader.id > 0) SetShaderDefaultLocations(&vrConfig.distortionShader); #endif + // Set VR configutarion parameters, including distortion shader SetStereoConfig(info); vrSimulatorReady = true; @@ -2958,18 +2959,6 @@ void CloseVrSimulator(void) #endif } -// TODO: Review VR system to be more flexible, -// move distortion shader to user side, -// SetStereoConfig() must be reviewed... -/* -// Set VR view distortion shader -void SetVrDistortionShader(Shader shader) -{ - vrConfig.distortionShader = shader; - SetStereoConfig(info); -} -*/ - // Detect if VR simulator is running bool IsVrSimulatorReady(void) { @@ -2980,6 +2969,15 @@ bool IsVrSimulatorReady(void) #endif } +// Set VR distortion shader for stereoscopic rendering +// TODO: Review VR system to be more flexible, move distortion shader to user side +void SetVrDistortionShader(Shader shader) +{ + vrConfig.distortionShader = shader; + + //SetStereoConfig(info); // TODO: Must be reviewed to set new distortion shader uniform values... +} + // Enable/Disable VR experience (device or simulator) void ToggleVrMode(void) { @@ -4026,8 +4024,8 @@ static void SetStereoConfig(VrDeviceInfo hmd) vrConfig.eyesViewOffset[1] = MatrixTranslate(hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); // Compute eyes Viewports - //vrConfig.eyesViewport[0] = (Rectangle){ 0, 0, hmd.hResolution/2, hmd.vResolution }; - //vrConfig.eyesViewport[1] = (Rectangle){ hmd.hResolution/2, 0, hmd.hResolution/2, hmd.vResolution }; + vrConfig.eyesViewport[0] = (Rectangle){ 0, 0, hmd.hResolution/2, hmd.vResolution }; + vrConfig.eyesViewport[1] = (Rectangle){ hmd.hResolution/2, 0, hmd.hResolution/2, hmd.vResolution }; } // Set internal projection and modelview matrix depending on eyes tracking data diff --git a/src/rlgl.h b/src/rlgl.h index 3f06a697..2e67c699 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -145,56 +145,6 @@ typedef unsigned char byte; // Boolean type typedef enum { false, true } bool; #endif - - // Shader location point type - typedef enum { - LOC_VERTEX_POSITION = 0, - LOC_VERTEX_TEXCOORD01, - LOC_VERTEX_TEXCOORD02, - LOC_VERTEX_NORMAL, - LOC_VERTEX_TANGENT, - LOC_VERTEX_COLOR, - LOC_MATRIX_MVP, - LOC_MATRIX_MODEL, - LOC_MATRIX_VIEW, - LOC_MATRIX_PROJECTION, - LOC_VECTOR_VIEW, - LOC_COLOR_DIFFUSE, - LOC_COLOR_SPECULAR, - LOC_COLOR_AMBIENT, - LOC_MAP_ALBEDO, // LOC_MAP_DIFFUSE - LOC_MAP_METALNESS, // LOC_MAP_SPECULAR - LOC_MAP_NORMAL, - LOC_MAP_ROUGHNESS, - LOC_MAP_OCCUSION, - LOC_MAP_EMISSION, - LOC_MAP_HEIGHT, - LOC_MAP_CUBEMAP, - LOC_MAP_IRRADIANCE, - LOC_MAP_PREFILTER, - LOC_MAP_BRDF - } ShaderLocationIndex; - - #define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO - #define LOC_MAP_SPECULAR LOC_MAP_METALNESS - - // Material map type - typedef enum { - MAP_ALBEDO = 0, // MAP_DIFFUSE - MAP_METALNESS = 1, // MAP_SPECULAR - MAP_NORMAL = 2, - MAP_ROUGHNESS = 3, - MAP_OCCLUSION, - MAP_EMISSION, - MAP_HEIGHT, - MAP_CUBEMAP, // NOTE: Uses GL_TEXTURE_CUBE_MAP - MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP - MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP - MAP_BRDF - } TexmapIndex; - - #define MAP_DIFFUSE MAP_ALBEDO - #define MAP_SPECULAR MAP_METALNESS // Color type, RGBA (32bit) typedef struct Color { @@ -204,6 +154,14 @@ typedef unsigned char byte; unsigned char a; } Color; + // Rectangle type + typedef struct Rectangle { + int x; + int y; + int width; + int height; + } Rectangle; + // Texture2D type // NOTE: Data stored in GPU memory typedef struct Texture2D { @@ -341,6 +299,56 @@ typedef unsigned char byte; BLEND_MULTIPLIED } BlendMode; + // Shader location point type + typedef enum { + LOC_VERTEX_POSITION = 0, + LOC_VERTEX_TEXCOORD01, + LOC_VERTEX_TEXCOORD02, + LOC_VERTEX_NORMAL, + LOC_VERTEX_TANGENT, + LOC_VERTEX_COLOR, + LOC_MATRIX_MVP, + LOC_MATRIX_MODEL, + LOC_MATRIX_VIEW, + LOC_MATRIX_PROJECTION, + LOC_VECTOR_VIEW, + LOC_COLOR_DIFFUSE, + LOC_COLOR_SPECULAR, + LOC_COLOR_AMBIENT, + LOC_MAP_ALBEDO, // LOC_MAP_DIFFUSE + LOC_MAP_METALNESS, // LOC_MAP_SPECULAR + LOC_MAP_NORMAL, + LOC_MAP_ROUGHNESS, + LOC_MAP_OCCUSION, + LOC_MAP_EMISSION, + LOC_MAP_HEIGHT, + LOC_MAP_CUBEMAP, + LOC_MAP_IRRADIANCE, + LOC_MAP_PREFILTER, + LOC_MAP_BRDF + } ShaderLocationIndex; + + #define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO + #define LOC_MAP_SPECULAR LOC_MAP_METALNESS + + // Material map type + typedef enum { + MAP_ALBEDO = 0, // MAP_DIFFUSE + MAP_METALNESS = 1, // MAP_SPECULAR + MAP_NORMAL = 2, + MAP_ROUGHNESS = 3, + MAP_OCCLUSION, + MAP_EMISSION, + MAP_HEIGHT, + MAP_CUBEMAP, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MAP_PREFILTER, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MAP_BRDF + } TexmapIndex; + + #define MAP_DIFFUSE MAP_ALBEDO + #define MAP_SPECULAR MAP_METALNESS + // VR Head Mounted Display devices typedef enum { HMD_DEFAULT_DEVICE = 0, -- cgit v1.2.3 From 24b12e5e2380584b1668e864f2af5bed0db38487 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 12 Nov 2017 11:45:35 +0100 Subject: Remove PLATFORM_ checks from raylib header Now header is truly multiplatform... Actually still a small pending check on XBOX gamepad controls that hopefully will be removed with next GLFW 3.3 --- src/core.c | 23 ++++++++++++----------- src/raylib.h | 34 ++++++---------------------------- 2 files changed, 18 insertions(+), 39 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 0ca2f3e1..97d8a74c 100644 --- a/src/core.c +++ b/src/core.c @@ -279,10 +279,11 @@ static int renderOffsetY = 0; // Offset Y from render area (must b static bool fullscreen = false; // Fullscreen mode (useful only for PLATFORM_DESKTOP) static Matrix downscaleView; // Matrix to downscale view (in case screen size bigger than display size) +static bool cursorHidden = false; // Track if cursor is hidden + #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) -static const char *windowTitle; // Window text title... +static const char *windowTitle = NULL; // Window text title... static bool cursorOnScreen = false; // Tracks if cursor is inside client area -static bool cursorHidden = false; // Track if cursor is hidden static int screenshotCounter = 0; // Screenshots counter // Register mouse states @@ -409,12 +410,13 @@ static void *GamepadThread(void *arg); // Mouse reading thread //---------------------------------------------------------------------------------- #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) // Initialize window and OpenGL context -void InitWindow(int width, int height, const char *title) +// NOTE: data parameter could be used to pass any kind of required data to the initialization +void InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.8.0)"); - // Store window title (could be useful...) - windowTitle = title; + // Input data is window title char data + windowTitle = (char *)data; // Init graphics device (display device and OpenGL context) InitGraphicsDevice(width, height); @@ -471,15 +473,17 @@ void InitWindow(int width, int height, const char *title) #endif #if defined(PLATFORM_ANDROID) -// Initialize Android activity -void InitWindow(int width, int height, void *state) +// Initialize window and OpenGL context (and Android activity) +// NOTE: data parameter could be used to pass any kind of required data to the initialization +void InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.8.0)"); screenWidth = width; screenHeight = height; - app = (struct android_app *)state; + // Input data is android app pointer + app = (struct android_app *)data; internalDataPath = app->activity->internalDataPath; // Set desired windows flags before initializing anything @@ -508,7 +512,6 @@ void InitWindow(int width, int height, void *state) //AConfiguration_getScreenSize(app->config); //AConfiguration_getScreenLong(app->config); - //state->userData = &engine; app->onAppCmd = AndroidCommandCallback; app->onInputEvent = AndroidInputCallback; @@ -709,7 +712,6 @@ int GetScreenHeight(void) return screenHeight; } -#if !defined(PLATFORM_ANDROID) // Show mouse cursor void ShowCursor() { @@ -772,7 +774,6 @@ void DisableCursor() #endif cursorHidden = true; } -#endif // !defined(PLATFORM_ANDROID) // Set background color (framebuffer clear color) void ClearBackground(Color color) diff --git a/src/raylib.h b/src/raylib.h index d5e907ab..e5ad8a9d 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -72,20 +72,6 @@ #ifndef RAYLIB_H #define RAYLIB_H -// Choose your platform here or just define it at compile time: -DPLATFORM_DESKTOP -//#define PLATFORM_DESKTOP // Windows, Linux or OSX -//#define PLATFORM_ANDROID // Android device -//#define PLATFORM_RPI // Raspberry Pi -//#define PLATFORM_WEB // HTML5 (emscripten, asm.js) - -// Security check in case no PLATFORM_* defined -#if !defined(PLATFORM_DESKTOP) && \ - !defined(PLATFORM_ANDROID) && \ - !defined(PLATFORM_RPI) && \ - !defined(PLATFORM_WEB) - #define PLATFORM_DESKTOP -#endif - #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) #define RLAPI __declspec(dllexport) // We are building raylib as a Win32 shared library (.dll) #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) @@ -179,13 +165,11 @@ #define KEY_Y 89 #define KEY_Z 90 -#if defined(PLATFORM_ANDROID) - // Android Physical Buttons - #define KEY_BACK 4 - #define KEY_MENU 82 - #define KEY_VOLUME_UP 24 - #define KEY_VOLUME_DOWN 25 -#endif +// Android Physical Buttons +#define KEY_BACK 4 +#define KEY_MENU 82 +#define KEY_VOLUME_UP 24 +#define KEY_VOLUME_DOWN 25 // Mouse Buttons #define MOUSE_LEFT_BUTTON 0 @@ -710,11 +694,7 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Window-related functions -#if defined(PLATFORM_ANDROID) -RLAPI void InitWindow(int width, int height, void *state); // Initialize Android activity -#elif defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) -RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context -#endif +RLAPI void InitWindow(int width, int height, void *data); // Initialize window and OpenGL context RLAPI void CloseWindow(void); // Close window and unload OpenGL context RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI bool IsWindowMinimized(void); // Check if window has been minimized (or lost focus) @@ -727,14 +707,12 @@ RLAPI void SetWindowMinSize(int width, int height); // Set window RLAPI int GetScreenWidth(void); // Get current screen width RLAPI int GetScreenHeight(void); // Get current screen height -#if !defined(PLATFORM_ANDROID) // Cursor-related functions RLAPI void ShowCursor(void); // Shows cursor RLAPI void HideCursor(void); // Hides cursor RLAPI bool IsCursorHidden(void); // Check if cursor is not visible RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) RLAPI void DisableCursor(void); // Disables cursor (lock cursor) -#endif // Drawing-related functions RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) -- cgit v1.2.3 From 68bf6c9701f9c1705849600838571f4f3b5c1446 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 12 Nov 2017 20:59:16 +1000 Subject: Initial work on porting AudioStream to use mini_al. --- src/audio.c | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/raylib.h | 2 + 2 files changed, 336 insertions(+), 3 deletions(-) (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index 83c80d8e..b04cc679 100644 --- a/src/audio.c +++ b/src/audio.c @@ -176,6 +176,22 @@ typedef struct MusicData { unsigned int samplesLeft; // Number of samples left to end } MusicData; +// AudioStreamData +typedef struct AudioStreamData AudioStreamData; +struct AudioStreamData { + mal_dsp dsp; // AudioStream data needs to flow through a persistent conversion pipeline. Not doing this will result in glitches between buffer updates. + float volume; + float pitch; + bool playing; + bool paused; + bool isSubBufferProcessed[2]; + unsigned int frameCursorPos; + unsigned int bufferSizeInFrames; + AudioStreamData* next; + AudioStreamData* prev; + unsigned char buffer[1]; +}; + #if defined(AUDIO_STANDALONE) typedef enum { LOG_INFO = 0, LOG_ERROR, LOG_WARNING, LOG_DEBUG, LOG_OTHER } TraceLogType; #endif @@ -236,6 +252,8 @@ static float masterVolume = 1; static mal_mutex soundLock; static SoundData* firstSound; // Sounds are tracked in a linked list. static SoundData* lastSound; +static AudioStreamData* firstAudioStream; +static AudioStreamData* lastAudioStream; static void AppendSound(SoundData* internalSound) { @@ -272,6 +290,42 @@ static void RemoveSound(SoundData* internalSound) mal_mutex_unlock(&context, &soundLock); } +static void AppendAudioStream(AudioStreamData* internalAudioStream) +{ + mal_mutex_lock(&context, &soundLock); + { + if (firstAudioStream == NULL) { + firstAudioStream = internalAudioStream; + } else { + lastAudioStream->next = internalAudioStream; + internalAudioStream->prev = lastAudioStream; + } + + lastAudioStream = internalAudioStream; + } + mal_mutex_unlock(&context, &soundLock); +} + +static void RemoveAudioStream(AudioStreamData* internalAudioStream) +{ + mal_mutex_lock(&context, &soundLock); + { + if (internalAudioStream->prev == NULL) { + firstAudioStream = internalAudioStream->next; + } else { + internalAudioStream->prev->next = internalAudioStream->next; + } + + if (internalAudioStream->next == NULL) { + lastAudioStream = internalAudioStream->prev; + } else { + internalAudioStream->next->prev = internalAudioStream->prev; + } + } + mal_mutex_unlock(&context, &soundLock); +} + + static void OnLog_MAL(mal_context* pContext, mal_device* pDevice, const char* message) { (void)pContext; @@ -342,8 +396,63 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC } } - // Music. - // TODO: Implement me. + // AudioStreams. These are handling slightly differently to sounds because we do data conversion at mixing time rather than + // load time. + for (AudioStreamData* internalData = firstAudioStream; internalData != NULL; internalData = internalData->next) + { + // Ignore stopped or paused streams. + if (!internalData->playing || internalData->paused) { + continue; + } + + mal_uint32 framesRead = 0; + for (;;) { + if (framesRead > frameCount) { + TraceLog(LOG_DEBUG, "Mixed too many frames from sound"); + break; + } + if (framesRead == frameCount) { + break; + } + + // Just read as much data we can from the stream. + mal_uint32 framesToRead = (frameCount - framesRead); + while (framesToRead > 0) { + float tempBuffer[1024]; // 512 frames for stereo. + + mal_uint32 framesToReadRightNow = framesToRead; + if (framesToReadRightNow > sizeof(tempBuffer)/DEVICE_CHANNELS) { + framesToReadRightNow = sizeof(tempBuffer)/DEVICE_CHANNELS; + } + + mal_uint32 framesJustRead = mal_dsp_read_frames(&internalData->dsp, framesToReadRightNow, tempBuffer); + if (framesJustRead > 0) { + // This is where the real mixing takes place. This can be optimized. This assumes the device and sound are of the same format. + // + // TODO: Implement pitching. + for (mal_uint32 iFrame = 0; iFrame < framesToRead; ++iFrame) { + float* pFrameOut = pFramesOutF + ((framesRead+iFrame) * device.channels); + float* pFrameIn = tempBuffer + (iFrame * device.channels); + + for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) { + pFrameOut[iChannel] += pFrameIn[iChannel] * masterVolume * internalData->volume; + } + } + + framesToRead -= framesJustRead; + framesRead += framesJustRead; + } else { + break; // Avoid an infinite loop. + } + } + + // If for some reason we weren't able to read every frame we'll need to break from the loop. Not doing this could + // theoretically put us into an infinite loop. + if (framesToRead > 0) { + break; + } + } + } } mal_mutex_unlock(&context, &soundLock); @@ -1375,6 +1484,65 @@ float GetMusicTimePlayed(Music music) return secondsPlayed; } + +static mal_uint32 UpdateAudioStream_OnDSPRead(mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + AudioStreamData* internalData = (AudioStreamData*)pUserData; + + mal_uint32 subBufferSizeInFrames = AUDIO_BUFFER_SIZE; + mal_uint32 currentSubBufferIndex = internalData->frameCursorPos / subBufferSizeInFrames; + if (currentSubBufferIndex > 1) { + TraceLog(LOG_DEBUG, "Frame cursor position moved too far forward in audio stream"); + return 0; + } + + // Another thread can update the processed state of buffers so we just take a copy here to try and avoid potential synchronization problems. + bool isSubBufferProcessed[2]; + isSubBufferProcessed[0] = internalData->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = internalData->isSubBufferProcessed[1]; + + mal_uint32 channels = internalData->dsp.config.channelsIn; + mal_uint32 sampleSizeInBytes = mal_get_sample_size_in_bytes(internalData->dsp.config.formatIn); + mal_uint32 frameSizeInBytes = sampleSizeInBytes*channels; + + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. + mal_uint32 framesRead = 0; + while (!isSubBufferProcessed[currentSubBufferIndex]) + { + mal_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) { + break; + } + + mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; + mal_uint32 framesRemainingInThisSubBuffer = subBufferSizeInFrames - (internalData->frameCursorPos - firstFrameIndexOfThisSubBuffer); + + mal_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInThisSubBuffer) { + framesToRead = framesRemainingInThisSubBuffer; + } + + memcpy((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), internalData->buffer + (internalData->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + + framesRead += framesToRead; + internalData->frameCursorPos = (internalData->frameCursorPos + framesToRead) % internalData->bufferSizeInFrames; + + // If we've read to the end of the buffer, mark it as processed. + if (framesToRead == framesRemainingInThisSubBuffer) { + internalData->isSubBufferProcessed[currentSubBufferIndex] = true; + currentSubBufferIndex = (currentSubBufferIndex + 1) % 2; + } + } + + // Zero-fill excess. + mal_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) { + memset((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + } + + return frameCount; +} + // Init audio stream (to stream audio pcm data) AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) { @@ -1391,6 +1559,41 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un stream.channels = 1; // Fallback to mono channel } + +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)calloc(1, sizeof(*internalData) + (AUDIO_BUFFER_SIZE*2 * stream.channels*(stream.sampleSize/8))); + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Failed to allocate buffer for audio stream"); + return stream; + } + + mal_dsp_config config; + memset(&config, 0, sizeof(config)); + config.formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); + config.channelsIn = stream.channels; + config.sampleRateIn = stream.sampleRate; + config.formatOut = DEVICE_FORMAT; + config.channelsOut = DEVICE_CHANNELS; + config.sampleRateOut = DEVICE_SAMPLE_RATE; + mal_result result = mal_dsp_init(&config, UpdateAudioStream_OnDSPRead, internalData, &internalData->dsp); + if (result != MAL_SUCCESS) + { + TraceLog(LOG_ERROR, "InitAudioStream() : Failed to initialize data conversion pipeline"); + free(internalData); + return stream; + } + + // Buffers should be marked as processed by default so that a call to UpdateAudioStream() immediately after initialization works correctly. + internalData->isSubBufferProcessed[0] = true; + internalData->isSubBufferProcessed[1] = true; + internalData->bufferSizeInFrames = AUDIO_BUFFER_SIZE*2; + internalData->volume = 1; + internalData->pitch = 1; + AppendAudioStream(internalData); + + stream.handle = internalData; +#else // Setup OpenAL format if (stream.channels == 1) { @@ -1435,6 +1638,7 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un free(pcm); alSourceQueueBuffers(stream.source, MAX_STREAM_BUFFERS, stream.buffers); +#endif TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo"); @@ -1444,6 +1648,11 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un // Close audio stream and free memory void CloseAudioStream(AudioStream stream) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + RemoveAudioStream(internalData); + free(internalData); +#else // Stop playing channel alSourceStop(stream.source); @@ -1462,7 +1671,8 @@ void CloseAudioStream(AudioStream stream) // Delete source and buffers alDeleteSources(1, &stream.source); alDeleteBuffers(MAX_STREAM_BUFFERS, stream.buffers); - +#endif + TraceLog(LOG_INFO, "[AUD ID %i] Unloaded audio stream data", stream.source); } @@ -1471,6 +1681,67 @@ void CloseAudioStream(AudioStream stream) // NOTE 2: To unqueue a buffer it needs to be processed: IsAudioBufferProcessed() void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + // We need to determine which half of the buffer needs updating. If the stream is not started and the cursor position is + // at the front of the buffer, update the first subbuffer. + if (internalData->isSubBufferProcessed[0] || internalData->isSubBufferProcessed[1]) + { + mal_uint32 subBufferToUpdate; + if (internalData->isSubBufferProcessed[0] && internalData->isSubBufferProcessed[1]) + { + // Both buffers are available for updating. Update the first one and make sure the cursor is moved back to the front. + subBufferToUpdate = 0; + internalData->frameCursorPos = 0; + } + else + { + // Just update whichever sub-buffer is processed. + subBufferToUpdate = (internalData->isSubBufferProcessed[0]) ? 0 : 1; + } + + mal_uint32 subBufferSizeInFrames = AUDIO_BUFFER_SIZE; + unsigned char *subBuffer = internalData->buffer + ((subBufferSizeInFrames * stream.channels * (stream.sampleSize/8)) * subBufferToUpdate); + + // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic. + if (subBufferSizeInFrames >= (mal_uint32)samplesCount) + { + mal_uint32 framesToWrite = subBufferSizeInFrames; + if (framesToWrite > (mal_uint32)samplesCount) { + framesToWrite = (mal_uint32)samplesCount; + } + + mal_uint32 bytesToWrite = framesToWrite * stream.channels * (stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros. + mal_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + if (leftoverFrameCount > 0) { + memset(subBuffer + bytesToWrite, 0, leftoverFrameCount * stream.channels * (stream.sampleSize/8)); + } + + internalData->isSubBufferProcessed[subBufferToUpdate] = false; + } + else + { + TraceLog(LOG_ERROR, "[AUD ID %i] UpdateAudioStream() : Attempting to write too many frames to buffer"); + return; + } + } + else + { + TraceLog(LOG_ERROR, "[AUD ID %i] Audio buffer not available for updating"); + return; + } + + +#else ALuint buffer = 0; alSourceUnqueueBuffers(stream.source, 1, &buffer); @@ -1481,44 +1752,104 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) alSourceQueueBuffers(stream.source, 1, &buffer); } else TraceLog(LOG_WARNING, "[AUD ID %i] Audio buffer not available for unqueuing", stream.source); +#endif } // Check if any audio stream buffers requires refill bool IsAudioBufferProcessed(AudioStream stream) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return false; + } + + return internalData->isSubBufferProcessed[0] || internalData->isSubBufferProcessed[1]; +#else ALint processed = 0; // Determine if music stream is ready to be written alGetSourcei(stream.source, AL_BUFFERS_PROCESSED, &processed); return (processed > 0); +#endif } // Play audio stream void PlayAudioStream(AudioStream stream) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + internalData->playing = true; +#else alSourcePlay(stream.source); +#endif } // Play audio stream void PauseAudioStream(AudioStream stream) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + internalData->paused = true; +#else alSourcePause(stream.source); +#endif } // Resume audio stream playing void ResumeAudioStream(AudioStream stream) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + internalData->paused = false; +#else ALenum state; alGetSourcei(stream.source, AL_SOURCE_STATE, &state); if (state == AL_PAUSED) alSourcePlay(stream.source); +#endif } // Stop audio stream void StopAudioStream(AudioStream stream) { +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + internalData->playing = 0; + internalData->paused = 0; + internalData->frameCursorPos = 0; + internalData->isSubBufferProcessed[0] = true; + internalData->isSubBufferProcessed[1] = true; +#else alSourceStop(stream.source); +#endif } //---------------------------------------------------------------------------------- diff --git a/src/raylib.h b/src/raylib.h index b21b0878..5e3724c9 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -504,6 +504,8 @@ typedef struct AudioStream { unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo) + void* handle; // A pointer to internal data used by the audio system. + int format; // OpenAL audio format specifier unsigned int source; // OpenAL audio source id unsigned int buffers[2]; // OpenAL audio buffers (double buffering) -- cgit v1.2.3 From ac4c911ad7e08cca3691896befbe8c09e3f3262f Mon Sep 17 00:00:00 2001 From: David Reid Date: Sun, 12 Nov 2017 21:55:24 +1000 Subject: Work on porting Music to mini_al. --- src/audio.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/raylib.h | 3 +- 2 files changed, 116 insertions(+), 3 deletions(-) (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index b04cc679..d4f426e0 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1283,7 +1283,7 @@ void UnloadMusicStream(Music music) void PlayMusicStream(Music music) { #if USE_MINI_AL - //InternalMusic* internalMusic = + PlayAudioStream(music->stream); #else alSourcePlay(music->stream.source); #endif @@ -1292,12 +1292,19 @@ void PlayMusicStream(Music music) // Pause music playing void PauseMusicStream(Music music) { +#if USE_MINI_AL + PauseAudioStream(music->stream); +#else alSourcePause(music->stream.source); +#endif } // Resume music playing void ResumeMusicStream(Music music) { +#if USE_MINI_AL + ResumeAudioStream(music->stream); +#else ALenum state; alGetSourcei(music->stream.source, AL_SOURCE_STATE, &state); @@ -1306,12 +1313,16 @@ void ResumeMusicStream(Music music) TraceLog(LOG_INFO, "[AUD ID %i] Resume music stream playing", music->stream.source); alSourcePlay(music->stream.source); } +#endif } // Stop music playing (close stream) // TODO: To clear a buffer, make sure they have been already processed! void StopMusicStream(Music music) { +#if USE_MINI_AL + StopAudioStream(music->stream); +#else alSourceStop(music->stream.source); /* @@ -1327,6 +1338,7 @@ void StopMusicStream(Music music) free(pcm); */ +#endif // Restart music context switch (music->ctxType) @@ -1351,6 +1363,77 @@ void StopMusicStream(Music music) // TODO: Make sure buffers are ready for update... check music state void UpdateMusicStream(Music music) { +#if USE_MINI_AL + bool streamEnding = false; + + // NOTE: Using dynamic allocation because it could require more than 16KB + void *pcm = calloc(AUDIO_BUFFER_SIZE*music->stream.sampleSize/8*music->stream.channels, 1); + + int samplesCount = 0; // Total size of data steamed in L+R samples for xm floats, individual L or R for ogg shorts + + while (IsAudioBufferProcessed(music->stream)) + { + 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; + #if defined(SUPPORT_FILEFORMAT_FLAC) + 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; + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: jar_xm_generate_samples_16bit(music->ctxXm, pcm, samplesCount); break; + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: jar_mod_fillbuffer(&music->ctxMod, pcm, samplesCount, 0); break; + #endif + default: break; + } + + UpdateAudioStream(music->stream, pcm, samplesCount); + music->samplesLeft -= samplesCount; + + if (music->samplesLeft <= 0) + { + streamEnding = true; + break; + } + } + + // Free allocated pcm data + free(pcm); + + // Reset audio stream for looping + if (streamEnding) + { + 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 (IsMusicPlaying(music)) PlayMusicStream(music); + } +#else ALenum state; ALint processed = 0; @@ -1431,11 +1514,15 @@ void UpdateMusicStream(Music music) if (state != AL_PLAYING) PlayMusicStream(music); } } +#endif } // Check if any music is playing bool IsMusicPlaying(Music music) { +#if USE_MINI_AL + return IsAudioStreamPlaying(music->stream); +#else bool playing = false; ALint state; @@ -1444,6 +1531,7 @@ bool IsMusicPlaying(Music music) if (state == AL_PLAYING) playing = true; return playing; +#endif } // Set volume for music @@ -1460,7 +1548,7 @@ void SetMusicPitch(Music music, float pitch) // Set music loop count (loop repeats) // NOTE: If set to -1, means infinite loop -void SetMusicLoopCount(Music music, float count) +void SetMusicLoopCount(Music music, int count) { music->loopCount = count; } @@ -1831,6 +1919,30 @@ void ResumeAudioStream(AudioStream stream) #endif } +// Check if audio stream is playing. +bool IsAudioStreamPlaying(AudioStream stream) +{ +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return false; + } + + return internalData->playing; +#else + bool playing = false; + ALint state; + + alGetSourcei(stream.source, AL_SOURCE_STATE, &state); + + if (state == AL_PLAYING) playing = true; + + return playing; +#endif +} + // Stop audio stream void StopAudioStream(AudioStream stream) { diff --git a/src/raylib.h b/src/raylib.h index 5e3724c9..725eb23b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1150,7 +1150,7 @@ RLAPI void ResumeMusicStream(Music music); // Resume RLAPI bool IsMusicPlaying(Music music); // Check if music is playing RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -RLAPI void SetMusicLoopCount(Music music, float count); // Set music loop count (loop repeats) +RLAPI void SetMusicLoopCount(Music music, int count); // Set music loop count (loop repeats) RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) @@ -1163,6 +1163,7 @@ RLAPI bool IsAudioBufferProcessed(AudioStream stream); // Check i RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream #ifdef __cplusplus -- cgit v1.2.3 From 88d2810fa3d6c87eeec673c54788a6f39e15f77a Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 14 Nov 2017 21:15:50 +1000 Subject: Bug fixes for Music with mini_al. --- src/audio.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/external/mini_al.h | 54 ++++++++++++++++++++++++++++++++++++++++---------- src/raylib.h | 2 ++ 3 files changed, 100 insertions(+), 10 deletions(-) (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index d29ad1ba..48e33d19 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1426,6 +1426,13 @@ void UpdateMusicStream(Music music) music->loopCount--; // Decrease loop count PlayMusicStream(music); // Play again } + else + { + if (music->loopCount == -1) + { + PlayMusicStream(music); + } + } } else { @@ -1506,6 +1513,13 @@ void UpdateMusicStream(Music music) music->loopCount--; // Decrease loop count PlayMusicStream(music); // Play again } + else + { + if (music->loopCount == -1) + { + PlayMusicStream(music); + } + } } else { @@ -1537,13 +1551,21 @@ bool IsMusicPlaying(Music music) // Set volume for music void SetMusicVolume(Music music, float volume) { +#if USE_MINI_AL + SetAudioStreamVolume(music->stream, volume); +#else alSourcef(music->stream.source, AL_GAIN, volume); +#endif } // Set pitch for music void SetMusicPitch(Music music, float pitch) { +#if USE_MINI_AL + SetAudioStreamPitch(music->stream, pitch); +#else alSourcef(music->stream.source, AL_PITCH, pitch); +#endif } // Set music loop count (loop repeats) @@ -1964,6 +1986,38 @@ void StopAudioStream(AudioStream stream) #endif } +void SetAudioStreamVolume(AudioStream stream, float volume) +{ +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + internalData->volume = volume; +#else + alSourcef(stream.source, AL_GAIN, volume); +#endif +} + +void SetAudioStreamPitch(AudioStream stream, float pitch) +{ +#if USE_MINI_AL + AudioStreamData* internalData = (AudioStreamData*)stream.handle; + if (internalData == NULL) + { + TraceLog(LOG_ERROR, "Invalid audio stream"); + return; + } + + internalData->pitch = pitch; +#else + alSourcef(stream.source, AL_PITCH, pitch); +#endif +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- diff --git a/src/external/mini_al.h b/src/external/mini_al.h index abb52295..58f542cf 100644 --- a/src/external/mini_al.h +++ b/src/external/mini_al.h @@ -1358,6 +1358,13 @@ mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* // Returns the number of frames actually read. mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); +// The same mal_src_read_frames() with extra control over whether or not the internal buffers should be flushed at the end. +// +// Internally there exists a buffer that keeps track of the previous and next samples for sample rate conversion. The simple +// version of this function does _not_ flush this buffer because otherwise it causes clitches for streaming based conversion +// pipelines. The problem, however, is that sometimes you need those last few samples (such as if you're doing a bulk conversion +// of a static file). Enabling flushing will fix this for you. +mal_uint32 mal_src_read_frames_ex(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); /////////////////////////////////////////////////////////////////////////////// @@ -1370,8 +1377,16 @@ mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFram mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* pUserData, mal_dsp* pDSP); // Reads a number of frames and runs them through the DSP processor. +// +// This this _not_ flush the internal buffers which means you may end up with a few less frames than you may expect. Look at +// mal_dsp_read_frames_ex() if you want to flush the buffers at the end of the read. mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut); +// The same mal_dsp_read_frames() with extra control over whether or not the internal buffers should be flushed at the end. +// +// See documentation for mal_src_read_frames_ex() for an explanation on flushing. +mal_uint32 mal_dsp_read_frames_ex(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); + // High-level helper for doing a full format conversion in one go. Returns the number of output frames. Call this with pOut set to NULL to // determine the required size of the output buffer. // @@ -9283,8 +9298,8 @@ mal_uint32 mal_src_cache_read_frames(mal_src_cache* pCache, mal_uint32 frameCoun } -mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); -mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut); +mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); +mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush); mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* pUserData, mal_src* pSRC) { @@ -9314,24 +9329,31 @@ mal_result mal_src_init(mal_src_config* pConfig, mal_src_read_proc onRead, void* } mal_uint32 mal_src_read_frames(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +{ + return mal_src_read_frames_ex(pSRC, frameCount, pFramesOut, MAL_FALSE); +} + +mal_uint32 mal_src_read_frames_ex(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) { if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) return 0; // Could just use a function pointer instead of a switch for this... switch (pSRC->config.algorithm) { - case mal_src_algorithm_none: return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut); - case mal_src_algorithm_linear: return mal_src_read_frames_linear(pSRC, frameCount, pFramesOut); + case mal_src_algorithm_none: return mal_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); + case mal_src_algorithm_linear: return mal_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); default: return 0; } } -mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) { mal_assert(pSRC != NULL); mal_assert(frameCount > 0); mal_assert(pFramesOut != NULL); + (void)flush; // Passthrough need not care about flushing. + // Fast path. No need for data conversion - just pass right through. if (pSRC->config.formatIn == pSRC->config.formatOut) { return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); @@ -9362,7 +9384,7 @@ mal_uint32 mal_src_read_frames_passthrough(mal_src* pSRC, mal_uint32 frameCount, return totalFramesRead; } -mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut) +mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) { mal_assert(pSRC != NULL); mal_assert(frameCount > 0); @@ -9414,7 +9436,14 @@ mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void pNextFrame[j] = 0; } - pSRC->linear.isNextFramesLoaded = MAL_FALSE; + if (pSRC->linear.isNextFramesLoaded) { + pSRC->linear.isNextFramesLoaded = MAL_FALSE; + } else { + if (flush) { + pSRC->linear.isPrevFramesLoaded = MAL_FALSE; + } + } + break; } } @@ -9426,7 +9455,7 @@ mal_uint32 mal_src_read_frames_linear(mal_src* pSRC, mal_uint32 frameCount, void totalFramesRead += 1; // If there's no frames available we need to get out of this loop. - if (!pSRC->linear.isNextFramesLoaded) { + if (!pSRC->linear.isNextFramesLoaded && (!flush || !pSRC->linear.isPrevFramesLoaded)) { break; } } @@ -9967,6 +9996,11 @@ mal_result mal_dsp_init(mal_dsp_config* pConfig, mal_dsp_read_proc onRead, void* } mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut) +{ + return mal_dsp_read_frames_ex(pDSP, frameCount, pFramesOut, MAL_FALSE); +} + +mal_uint32 mal_dsp_read_frames_ex(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, mal_bool32 flush) { if (pDSP == NULL || pFramesOut == NULL) return 0; @@ -9993,7 +10027,7 @@ mal_uint32 mal_dsp_read_frames(mal_dsp* pDSP, mal_uint32 frameCount, void* pFram // The initial filling of sample data depends on whether or not we are using SRC. mal_uint32 framesRead = 0; if (pDSP->isSRCRequired) { - framesRead = mal_src_read_frames(&pDSP->src, framesToRead, pFrames[iFrames]); + framesRead = mal_src_read_frames_ex(&pDSP->src, framesToRead, pFrames[iFrames], flush); pFramesFormat[iFrames] = pDSP->src.config.formatOut; // Should always be f32. } else { framesRead = pDSP->onRead(pDSP, framesToRead, pFrames[iFrames], pDSP->pUserDataForOnRead); @@ -10116,7 +10150,7 @@ mal_uint32 mal_convert_frames(void* pOut, mal_format formatOut, mal_uint32 chann return 0; } - return mal_dsp_read_frames(&dsp, frameCountOut, pOut); + return mal_dsp_read_frames_ex(&dsp, frameCountOut, pOut, MAL_TRUE); } diff --git a/src/raylib.h b/src/raylib.h index 725eb23b..1f138c2b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1165,6 +1165,8 @@ RLAPI void PauseAudioStream(AudioStream stream); // Pause a RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) #ifdef __cplusplus } -- cgit v1.2.3 From 60d7215b2af12614e0dd99b30ac4330168100fe8 Mon Sep 17 00:00:00 2001 From: David Reid Date: Sat, 18 Nov 2017 08:42:14 +1000 Subject: mini_al: Unify the buffer system for Sounds and AudioStreams. --- src/audio.c | 832 ++++++++++++++++++++++++++--------------------------------- src/raylib.h | 4 +- 2 files changed, 369 insertions(+), 467 deletions(-) (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index d20380d0..83538f06 100644 --- a/src/audio.c +++ b/src/audio.c @@ -213,126 +213,75 @@ void TraceLog(int msgType, const char *text, ...); // Show trace lo #define DEVICE_CHANNELS 2 #define DEVICE_SAMPLE_RATE 44100 -typedef struct SoundData SoundData; -struct SoundData -{ - mal_dsp dsp; // Necessary for pitch shift. This is an optimized passthrough when the pitch == 1. - float volume; - float pitch; - bool playing; - bool paused; - bool looping; - unsigned int frameCursorPos; // Keeps track of the next frame to read when mixing - unsigned int bufferSizeInFrames; - SoundData* next; - SoundData* prev; - unsigned char data[1]; // Raw audio data. -}; +typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; -// AudioStreamData -typedef struct AudioStreamData AudioStreamData; -struct AudioStreamData +typedef struct AudioBuffer AudioBuffer; +struct AudioBuffer { - mal_dsp dsp; // AudioStream data needs to flow through a persistent conversion pipeline. Not doing this will result in glitches between buffer updates. + mal_dsp dsp; // For format conversion. float volume; float pitch; bool playing; bool paused; + bool looping; // Always true for AudioStreams. + AudioBufferUsage usage; // Slightly different logic is used when feeding data to the playback device depending on whether or not data is streamed. bool isSubBufferProcessed[2]; unsigned int frameCursorPos; unsigned int bufferSizeInFrames; - AudioStreamData* next; - AudioStreamData* prev; + AudioBuffer* next; + AudioBuffer* prev; unsigned char buffer[1]; }; +void StopAudioBuffer(AudioBuffer* audioBuffer); + + static mal_context context; static mal_device device; static mal_bool32 isAudioInitialized = MAL_FALSE; static float masterVolume = 1; static mal_mutex soundLock; -static SoundData* firstSound; // Sounds are tracked in a linked list. -static SoundData* lastSound; -static AudioStreamData* firstAudioStream; -static AudioStreamData* lastAudioStream; - -static void AppendSound(SoundData* internalSound) -{ - mal_mutex_lock(&soundLock); - { - if (firstSound == NULL) { - firstSound = internalSound; - } else { - lastSound->next = internalSound; - internalSound->prev = lastSound; - } - - lastSound = internalSound; - } - mal_mutex_unlock(&soundLock); -} - -static void RemoveSound(SoundData* internalSound) -{ - mal_mutex_lock(&soundLock); - { - if (internalSound->prev == NULL) { - firstSound = internalSound->next; - } else { - internalSound->prev->next = internalSound->next; - } - - if (internalSound->next == NULL) { - lastSound = internalSound->prev; - } else { - internalSound->next->prev = internalSound->prev; - } +static AudioBuffer* firstAudioBuffer = NULL; // Audio buffers are tracked in a linked list. +static AudioBuffer* lastAudioBuffer = NULL; - internalSound->prev = NULL; - internalSound->next = NULL; - } - mal_mutex_unlock(&soundLock); -} - -static void AppendAudioStream(AudioStreamData* internalAudioStream) +static void TrackAudioBuffer(AudioBuffer* audioBuffer) { mal_mutex_lock(&soundLock); { - if (firstAudioStream == NULL) { - firstAudioStream = internalAudioStream; + if (firstAudioBuffer == NULL) { + firstAudioBuffer = audioBuffer; } else { - lastAudioStream->next = internalAudioStream; - internalAudioStream->prev = lastAudioStream; + lastAudioBuffer->next = audioBuffer; + audioBuffer->prev = lastAudioBuffer; } - lastAudioStream = internalAudioStream; + lastAudioBuffer = audioBuffer; } mal_mutex_unlock(&soundLock); } -static void RemoveAudioStream(AudioStreamData* internalAudioStream) +static void UntrackAudioBuffer(AudioBuffer* audioBuffer) { mal_mutex_lock(&soundLock); { - if (internalAudioStream->prev == NULL) { - firstAudioStream = internalAudioStream->next; + if (audioBuffer->prev == NULL) { + firstAudioBuffer = audioBuffer->next; } else { - internalAudioStream->prev->next = internalAudioStream->next; + audioBuffer->prev->next = audioBuffer->next; } - if (internalAudioStream->next == NULL) { - lastAudioStream = internalAudioStream->prev; + if (audioBuffer->next == NULL) { + lastAudioBuffer = audioBuffer->prev; } else { - internalAudioStream->next->prev = internalAudioStream->prev; + audioBuffer->next->prev = audioBuffer->prev; } - internalAudioStream->prev = NULL; - internalAudioStream->next = NULL; + audioBuffer->prev = NULL; + audioBuffer->next = NULL; } mal_mutex_unlock(&soundLock); } - static void OnLog_MAL(mal_context* pContext, mal_device* pDevice, const char* message) { (void)pContext; @@ -347,8 +296,8 @@ static void MixFrames(float* framesOut, const float* framesIn, mal_uint32 frameC { for (mal_uint32 iFrame = 0; iFrame < frameCount; ++iFrame) { for (mal_uint32 iChannel = 0; iChannel < device.channels; ++iChannel) { - float* frameOut = framesOut + (iFrame * device.channels); - float* frameIn = framesIn + (iFrame * device.channels); + float* frameOut = framesOut + (iFrame * device.channels); + const float* frameIn = framesIn + (iFrame * device.channels); frameOut[iChannel] += frameIn[iChannel] * masterVolume * localVolume; } @@ -367,27 +316,24 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC // want to consider how you might want to avoid this. mal_mutex_lock(&soundLock); { - float* framesOutF = (float*)pFramesOut; // <-- Just for convenience. - - // Sounds. - for (SoundData* internalSound = firstSound; internalSound != NULL; internalSound = internalSound->next) + for (AudioBuffer* audioBuffer = firstAudioBuffer; audioBuffer != NULL; audioBuffer = audioBuffer->next) { // Ignore stopped or paused sounds. - if (!internalSound->playing || internalSound->paused) { + if (!audioBuffer->playing || audioBuffer->paused) { continue; } mal_uint32 framesRead = 0; for (;;) { if (framesRead > frameCount) { - TraceLog(LOG_DEBUG, "Mixed too many frames from sound"); + TraceLog(LOG_DEBUG, "Mixed too many frames from audio buffer"); break; } if (framesRead == frameCount) { break; } - // Just read as much data we can from the stream. + // Just read as much data as we can from the stream. mal_uint32 framesToRead = (frameCount - framesRead); while (framesToRead > 0) { float tempBuffer[1024]; // 512 frames for stereo. @@ -399,13 +345,13 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC // If we're not looping, we need to make sure we flush the internal buffers of the DSP pipeline to ensure we get the // last few samples. - mal_bool32 flushDSP = !internalSound->looping; + mal_bool32 flushDSP = !audioBuffer->looping; - mal_uint32 framesJustRead = mal_dsp_read_frames_ex(&internalSound->dsp, framesToReadRightNow, tempBuffer, flushDSP); + mal_uint32 framesJustRead = mal_dsp_read_frames_ex(&audioBuffer->dsp, framesToReadRightNow, tempBuffer, flushDSP); if (framesJustRead > 0) { - float* framesOut = framesOutF + (framesRead * device.channels); + float* framesOut = (float*)pFramesOut + (framesRead * device.channels); float* framesIn = tempBuffer; - MixFrames(framesOut, framesIn, framesJustRead, internalSound->volume); + MixFrames(framesOut, framesIn, framesJustRead, audioBuffer->volume); framesToRead -= framesJustRead; framesRead += framesJustRead; @@ -413,13 +359,12 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC // If we weren't able to read all the frames we requested, break. if (framesJustRead < framesToReadRightNow) { - if (!internalSound->looping) { - internalSound->playing = MAL_FALSE; - internalSound->frameCursorPos = 0; + if (!audioBuffer->looping) { + StopAudioBuffer(audioBuffer); break; } else { // Should never get here, but just for safety, move the cursor position back to the start and continue the loop. - internalSound->frameCursorPos = 0; + audioBuffer->frameCursorPos = 0; continue; } } @@ -432,56 +377,6 @@ static mal_uint32 OnSendAudioDataToDevice(mal_device* pDevice, mal_uint32 frameC } } } - - // AudioStreams. These are handled slightly differently to sounds because we do data conversion at mixing time rather than - // load time. - for (AudioStreamData* internalData = firstAudioStream; internalData != NULL; internalData = internalData->next) - { - // Ignore stopped or paused streams. - if (!internalData->playing || internalData->paused) { - continue; - } - - mal_uint32 framesRead = 0; - for (;;) { - if (framesRead > frameCount) { - TraceLog(LOG_DEBUG, "Mixed too many frames from sound"); - break; - } - if (framesRead == frameCount) { - break; - } - - // Just read as much data we can from the stream. - mal_uint32 framesToRead = (frameCount - framesRead); - while (framesToRead > 0) { - float tempBuffer[1024]; // 512 frames for stereo. - - mal_uint32 framesToReadRightNow = framesToRead; - if (framesToReadRightNow > sizeof(tempBuffer)/DEVICE_CHANNELS) { - framesToReadRightNow = sizeof(tempBuffer)/DEVICE_CHANNELS; - } - - mal_uint32 framesJustRead = mal_dsp_read_frames(&internalData->dsp, framesToReadRightNow, tempBuffer); - if (framesJustRead > 0) { - float* framesOut = framesOutF + (framesRead * device.channels); - float* framesIn = tempBuffer; - MixFrames(framesOut, framesIn, framesJustRead, internalData->volume); - - framesToRead -= framesJustRead; - framesRead += framesJustRead; - } else { - break; // Avoid an infinite loop. - } - } - - // If for some reason we weren't able to read every frame we'll need to break from the loop. Not doing this could - // theoretically put us into an infinite loop. - if (framesToRead > 0) { - break; - } - } - } } mal_mutex_unlock(&soundLock); @@ -630,6 +525,262 @@ void SetMasterVolume(float volume) #endif } + +//---------------------------------------------------------------------------------- +// Audio Buffer +//---------------------------------------------------------------------------------- +#if USE_MINI_AL +static mal_uint32 AudioBuffer_OnDSPRead(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) +{ + AudioBuffer* audioBuffer = (AudioBuffer*)pUserData; + + mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames / 2; + mal_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos / subBufferSizeInFrames; + if (currentSubBufferIndex > 1) { + TraceLog(LOG_DEBUG, "Frame cursor position moved too far forward in audio stream"); + return 0; + } + + // Another thread can update the processed state of buffers so we just take a copy here to try and avoid potential synchronization problems. + bool isSubBufferProcessed[2]; + isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; + + mal_uint32 frameSizeInBytes = mal_get_sample_size_in_bytes(audioBuffer->dsp.config.formatIn) * audioBuffer->dsp.config.channelsIn; + + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. + mal_uint32 framesRead = 0; + for (;;) + { + // We break from this loop differently depending on the buffer's usage. For static buffers, we simply fill as much data as we can. For + // streaming buffers we only fill the halves of the buffer that are processed. Unprocessed halves must keep their audio data in-tact. + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { + if (framesRead >= frameCount) { + break; + } + } else { + if (isSubBufferProcessed[currentSubBufferIndex]) { + break; + } + } + + mal_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) { + break; + } + + mal_uint32 framesRemainingInOutputBuffer; + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) { + framesRemainingInOutputBuffer = audioBuffer->bufferSizeInFrames - audioBuffer->frameCursorPos; + } else { + mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; + framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); + } + + + + mal_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInOutputBuffer) { + framesToRead = framesRemainingInOutputBuffer; + } + + memcpy((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), audioBuffer->buffer + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead) % audioBuffer->bufferSizeInFrames; + framesRead += framesToRead; + + // If we've read to the end of the buffer, mark it as processed. + if (framesToRead == framesRemainingInOutputBuffer) { + audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; + isSubBufferProcessed[currentSubBufferIndex] = true; + + currentSubBufferIndex = (currentSubBufferIndex + 1) % 2; + + // We need to break from this loop if we're not looping. + if (!audioBuffer->looping) { + StopAudioBuffer(audioBuffer); + break; + } + } + } + + // Zero-fill excess. + mal_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) { + memset((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + + // For static buffers we can fill the remaining frames with silence for safety, but we don't want + // to report those frames as "read". The reason for this is that the caller uses the return value + // to know whether or not a non-looping sound has finished playback. + if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) { + framesRead += totalFramesRemaining; + } + } + + return framesRead; +} + +// Create a new audio buffer. Initially filled with silence. +AudioBuffer* CreateAudioBuffer(mal_format format, mal_uint32 channels, mal_uint32 sampleRate, mal_uint32 bufferSizeInFrames, AudioBufferUsage usage) +{ + AudioBuffer* audioBuffer = (AudioBuffer*)calloc(sizeof(*audioBuffer) + (bufferSizeInFrames*channels*mal_get_sample_size_in_bytes(format)), 1); + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "CreateAudioBuffer() : Failed to allocate memory for audio buffer"); + return NULL; + } + + // We run audio data through a format converter. + mal_dsp_config dspConfig; + memset(&dspConfig, 0, sizeof(dspConfig)); + dspConfig.formatIn = format; + dspConfig.formatOut = DEVICE_FORMAT; + dspConfig.channelsIn = channels; + dspConfig.channelsOut = DEVICE_CHANNELS; + dspConfig.sampleRateIn = sampleRate; + dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE; + mal_result resultMAL = mal_dsp_init(&dspConfig, AudioBuffer_OnDSPRead, audioBuffer, &audioBuffer->dsp); + if (resultMAL != MAL_SUCCESS) { + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to create data conversion pipeline"); + free(audioBuffer); + return NULL; + } + + audioBuffer->volume = 1; + audioBuffer->pitch = 1; + audioBuffer->playing = 0; + audioBuffer->paused = 0; + audioBuffer->looping = 0; + audioBuffer->usage = usage; + audioBuffer->bufferSizeInFrames = bufferSizeInFrames; + audioBuffer->frameCursorPos = 0; + + // Buffers should be marked as processed by default so that a call to UpdateAudioStream() immediately after initialization works correctly. + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; + + TrackAudioBuffer(audioBuffer); + + return audioBuffer; +} + +// Delete an audio buffer. +void DeleteAudioBuffer(AudioBuffer* audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + UntrackAudioBuffer(audioBuffer); + free(audioBuffer); +} + +// Check if an audio buffer is playing. +bool IsAudioBufferPlaying(AudioBuffer* audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return false; + } + + return audioBuffer->playing && !audioBuffer->paused; +} + +// Play an audio buffer. +// +// This will restart the buffer from the start. Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position +// should be maintained. +void PlayAudioBuffer(AudioBuffer* audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->playing = true; + audioBuffer->paused = false; + audioBuffer->frameCursorPos = 0; +} + +// Stop an audio buffer. +void StopAudioBuffer(AudioBuffer* audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + // Don't do anything if the audio buffer is already stopped. + if (!IsAudioBufferPlaying(audioBuffer)) + { + return; + } + + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->frameCursorPos = 0; + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; +} + +// Pause an audio buffer. +void PauseAudioBuffer(AudioBuffer* audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->paused = true; +} + +// Resume an audio buffer. +void ResumeAudioBuffer(AudioBuffer* audioBuffer) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->paused = false; +} + +// Set volume for an audio buffer. +void SetAudioBufferVolume(AudioBuffer* audioBuffer, float volume) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->volume = volume; +} + +// Set pitch for an audio buffer. +void SetAudioBufferPitch(AudioBuffer* audioBuffer, float pitch) +{ + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayAudioBuffer() : No audio buffer"); + return; + } + + audioBuffer->pitch = pitch; + + // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches + // will make the sound faster; lower pitches make it slower. + mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.config.sampleRateOut / (float)audioBuffer->dsp.config.sampleRateIn) / pitch) * audioBuffer->dsp.config.sampleRateIn); + mal_dsp_set_output_sample_rate(&audioBuffer->dsp, newOutputSampleRate); +} +#endif + //---------------------------------------------------------------------------------- // Module Functions Definition - Sounds loading and playing (.WAV) //---------------------------------------------------------------------------------- @@ -696,39 +847,6 @@ Sound LoadSound(const char *fileName) return sound; } -#if USE_MINI_AL -static mal_uint32 Sound_OnDSPRead(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) -{ - SoundData* internalData = (SoundData*)pUserData; - - mal_uint32 frameSizeInBytes = mal_get_sample_size_in_bytes(internalData->dsp.config.formatIn)*internalData->dsp.config.channelsIn; - - // Just keep reading as much as we can. Do not zero fill excess data in the output buffer. - mal_uint32 framesRead = 0; - while (framesRead < frameCount) - { - mal_uint32 framesRemaining = internalData->bufferSizeInFrames - internalData->frameCursorPos; - mal_uint32 framesToRead = (frameCount - framesRead); - if (framesToRead > framesRemaining) { - framesToRead = framesRemaining; - } - - memcpy((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), internalData->data + (internalData->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); - internalData->frameCursorPos += framesToRead; - framesRead += framesToRead; - - // If we've reached the end of the buffer but we're not looping, return. - if (framesToRead == framesRemaining) { - if (!internalData->looping) { - break; - } - } - } - - return framesRead; -} -#endif - // Load sound from wave data // NOTE: Wave data must be unallocated manually Sound LoadSoundFromWave(Wave wave) @@ -751,44 +869,24 @@ Sound LoadSoundFromWave(Wave wave) mal_uint32 frameCount = mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn); if (frameCount == 0) { - TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to get frame count for format conversion."); + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to get frame count for format conversion"); } - SoundData* internalSound = (SoundData*)calloc(sizeof(*internalSound) + (frameCount*DEVICE_CHANNELS*4), 1); // <-- Make sure this is initialized to zero for safety. - if (internalSound == NULL) { - TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to allocate memory for internal buffer"); - } - frameCount = mal_convert_frames(internalSound->data, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); - if (frameCount == 0) { - TraceLog(LOG_ERROR, "LoadSoundFromWave() : Format conversion failed."); + AudioBuffer* audioBuffer = CreateAudioBuffer(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to create audio buffer"); } - // We run audio data through a sample rate converter in order to support pitch shift. By default this will use an optimized passthrough - // algorithm, but when the application changes the pitch it will change to a less optimal linear SRC. - mal_dsp_config dspConfig; - memset(&dspConfig, 0, sizeof(dspConfig)); - dspConfig.formatIn = DEVICE_FORMAT; - dspConfig.formatOut = DEVICE_FORMAT; - dspConfig.channelsIn = DEVICE_CHANNELS; - dspConfig.channelsOut = DEVICE_CHANNELS; - dspConfig.sampleRateIn = DEVICE_SAMPLE_RATE; - dspConfig.sampleRateOut = DEVICE_SAMPLE_RATE; - mal_result resultMAL = mal_dsp_init(&dspConfig, Sound_OnDSPRead, internalSound, &internalSound->dsp); - if (resultMAL != MAL_SUCCESS) { - TraceLog(LOG_ERROR, "LoadSoundFromWave() : Failed to create data conversion pipeline"); - } - internalSound->volume = 1; - internalSound->pitch = 1; - internalSound->playing = 0; - internalSound->paused = 0; - internalSound->looping = 0; - internalSound->bufferSizeInFrames = frameCount; - internalSound->frameCursorPos = 0; - AppendSound(internalSound); + frameCount = mal_convert_frames(audioBuffer->buffer, audioBuffer->dsp.config.formatIn, audioBuffer->dsp.config.channelsIn, audioBuffer->dsp.config.sampleRateIn, wave.data, formatIn, wave.channels, wave.sampleRate, frameCountIn); + if (frameCount == 0) + { + TraceLog(LOG_ERROR, "LoadSoundFromWave() : Format conversion failed"); + } - sound.handle = (void*)internalSound; + sound.audioBuffer = audioBuffer; #else ALenum format = 0; @@ -861,9 +959,7 @@ void UnloadWave(Wave wave) void UnloadSound(Sound sound) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - RemoveSound(internalSound); - free(internalSound); + DeleteAudioBuffer((AudioBuffer*)sound.audioBuffer); #else alSourceStop(sound.source); @@ -879,19 +975,17 @@ void UnloadSound(Sound sound) void UpdateSound(Sound sound, const void *data, int samplesCount) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) + AudioBuffer* audioBuffer = (AudioBuffer*)sound.audioBuffer; + if (audioBuffer == NULL) { - TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound"); + TraceLog(LOG_ERROR, "UpdateSound() : Invalid sound - no audio buffer"); return; } - internalSound->playing = false; - internalSound->paused = false; - internalSound->frameCursorPos = 0; + StopAudioBuffer(audioBuffer); // TODO: May want to lock/unlock this since this data buffer is read at mixing time. - memcpy(internalSound->data, data, samplesCount*internalSound->dsp.config.channelsIn*mal_get_sample_size_in_bytes(internalSound->dsp.config.formatIn)); + memcpy(audioBuffer->buffer, data, samplesCount*audioBuffer->dsp.config.channelsIn*mal_get_sample_size_in_bytes(audioBuffer->dsp.config.formatIn)); #else ALint sampleRate, sampleSize, channels; alGetBufferi(sound.buffer, AL_FREQUENCY, &sampleRate); @@ -921,16 +1015,7 @@ void UpdateSound(Sound sound, const void *data, int samplesCount) void PlaySound(Sound sound) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "PlaySound() : Invalid sound"); - return; - } - - internalSound->playing = 1; - internalSound->paused = 0; - internalSound->frameCursorPos = 0; + PlayAudioBuffer((AudioBuffer*)sound.audioBuffer); #else alSourcePlay(sound.source); // Play the sound #endif @@ -955,14 +1040,7 @@ void PlaySound(Sound sound) void PauseSound(Sound sound) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "PauseSound() : Invalid sound"); - return; - } - - internalSound->paused = true; + PauseAudioBuffer((AudioBuffer*)sound.audioBuffer); #else alSourcePause(sound.source); #endif @@ -972,14 +1050,7 @@ void PauseSound(Sound sound) void ResumeSound(Sound sound) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "ResumeSound() : Invalid sound"); - return; - } - - internalSound->paused = false; + ResumeAudioBuffer((AudioBuffer*)sound.audioBuffer); #else ALenum state; @@ -993,15 +1064,7 @@ void ResumeSound(Sound sound) void StopSound(Sound sound) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "StopSound() : Invalid sound"); - return; - } - - internalSound->playing = false; - internalSound->paused = false; + StopAudioBuffer((AudioBuffer*)sound.audioBuffer); #else alSourceStop(sound.source); #endif @@ -1011,14 +1074,7 @@ void StopSound(Sound sound) bool IsSoundPlaying(Sound sound) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "IsSoundPlaying() : Invalid sound"); - return false; - } - - return internalSound->playing && !internalSound->paused; + return IsAudioBufferPlaying((AudioBuffer*)sound.audioBuffer); #else bool playing = false; ALint state; @@ -1034,14 +1090,7 @@ bool IsSoundPlaying(Sound sound) void SetSoundVolume(Sound sound, float volume) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "SetSoundVolume() : Invalid sound"); - return; - } - - internalSound->volume = volume; + SetAudioBufferVolume((AudioBuffer*)sound.audioBuffer, volume); #else alSourcef(sound.source, AL_GAIN, volume); #endif @@ -1051,19 +1100,7 @@ void SetSoundVolume(Sound sound, float volume) void SetSoundPitch(Sound sound, float pitch) { #if USE_MINI_AL - SoundData* internalSound = (SoundData*)sound.handle; - if (internalSound == NULL) - { - TraceLog(LOG_ERROR, "SetSoundPitch() : Invalid sound"); - return; - } - - internalSound->pitch = pitch; - - // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches - // will make the sound faster; lower pitches make it slower. - mal_uint32 newOutputSampleRate = (mal_uint32)((((float)internalSound->dsp.config.sampleRateOut / (float)internalSound->dsp.config.sampleRateIn) / pitch) * internalSound->dsp.config.sampleRateIn); - mal_dsp_set_output_sample_rate(&internalSound->dsp, newOutputSampleRate); + SetAudioBufferPitch((AudioBuffer*)sound.audioBuffer, pitch); #else alSourcef(sound.source, AL_PITCH, pitch); #endif @@ -1361,7 +1398,22 @@ void UnloadMusicStream(Music music) void PlayMusicStream(Music music) { #if USE_MINI_AL - PlayAudioStream(music->stream); + AudioBuffer* audioBuffer = (AudioBuffer*)music->stream.audioBuffer; + if (audioBuffer == NULL) + { + TraceLog(LOG_ERROR, "PlayMusicStream() : No audio buffer"); + return; + } + + // For music streams, we need to make sure we maintain the frame cursor position. This is hack for this section of code in UpdateMusicStream() + // // NOTE: In case window is minimized, music stream is stopped, + // // just make sure to play again on window restore + // if (IsMusicPlaying(music)) PlayMusicStream(music); + mal_uint32 frameCursorPos = audioBuffer->frameCursorPos; + { + PlayAudioStream(music->stream); // <-- This resets the cursor position. + } + audioBuffer->frameCursorPos = frameCursorPos; #else alSourcePlay(music->stream.source); #endif @@ -1672,65 +1724,6 @@ float GetMusicTimePlayed(Music music) return secondsPlayed; } -#if USE_MINI_AL -static mal_uint32 UpdateAudioStream_OnDSPRead(mal_dsp* pDSP, mal_uint32 frameCount, void* pFramesOut, void* pUserData) -{ - AudioStreamData* internalData = (AudioStreamData*)pUserData; - - mal_uint32 subBufferSizeInFrames = AUDIO_BUFFER_SIZE; - mal_uint32 currentSubBufferIndex = internalData->frameCursorPos / subBufferSizeInFrames; - if (currentSubBufferIndex > 1) { - TraceLog(LOG_DEBUG, "Frame cursor position moved too far forward in audio stream"); - return 0; - } - - // Another thread can update the processed state of buffers so we just take a copy here to try and avoid potential synchronization problems. - bool isSubBufferProcessed[2]; - isSubBufferProcessed[0] = internalData->isSubBufferProcessed[0]; - isSubBufferProcessed[1] = internalData->isSubBufferProcessed[1]; - - mal_uint32 channels = internalData->dsp.config.channelsIn; - mal_uint32 sampleSizeInBytes = mal_get_sample_size_in_bytes(internalData->dsp.config.formatIn); - mal_uint32 frameSizeInBytes = sampleSizeInBytes*channels; - - // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0. - mal_uint32 framesRead = 0; - while (!isSubBufferProcessed[currentSubBufferIndex]) - { - mal_uint32 totalFramesRemaining = (frameCount - framesRead); - if (totalFramesRemaining == 0) { - break; - } - - mal_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames * currentSubBufferIndex; - mal_uint32 framesRemainingInThisSubBuffer = subBufferSizeInFrames - (internalData->frameCursorPos - firstFrameIndexOfThisSubBuffer); - - mal_uint32 framesToRead = totalFramesRemaining; - if (framesToRead > framesRemainingInThisSubBuffer) { - framesToRead = framesRemainingInThisSubBuffer; - } - - memcpy((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), internalData->buffer + (internalData->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); - - framesRead += framesToRead; - internalData->frameCursorPos = (internalData->frameCursorPos + framesToRead) % internalData->bufferSizeInFrames; - - // If we've read to the end of the buffer, mark it as processed. - if (framesToRead == framesRemainingInThisSubBuffer) { - internalData->isSubBufferProcessed[currentSubBufferIndex] = true; - currentSubBufferIndex = (currentSubBufferIndex + 1) % 2; - } - } - - // Zero-fill excess. - mal_uint32 totalFramesRemaining = (frameCount - framesRead); - if (totalFramesRemaining > 0) { - memset((unsigned char*)pFramesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); - } - - return frameCount; -} -#endif // Init audio stream (to stream audio pcm data) AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) @@ -1750,38 +1743,17 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)calloc(1, sizeof(*internalData) + (AUDIO_BUFFER_SIZE*2 * stream.channels*(stream.sampleSize/8))); - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Failed to allocate buffer for audio stream"); - return stream; - } + mal_format formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - mal_dsp_config config; - memset(&config, 0, sizeof(config)); - config.formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32)); - config.channelsIn = stream.channels; - config.sampleRateIn = stream.sampleRate; - config.formatOut = DEVICE_FORMAT; - config.channelsOut = DEVICE_CHANNELS; - config.sampleRateOut = DEVICE_SAMPLE_RATE; - mal_result result = mal_dsp_init(&config, UpdateAudioStream_OnDSPRead, internalData, &internalData->dsp); - if (result != MAL_SUCCESS) + AudioBuffer* audioBuffer = CreateAudioBuffer(formatIn, stream.channels, stream.sampleRate, AUDIO_BUFFER_SIZE*2, AUDIO_BUFFER_USAGE_STREAM); + if (audioBuffer == NULL) { - TraceLog(LOG_ERROR, "InitAudioStream() : Failed to initialize data conversion pipeline"); - free(internalData); + TraceLog(LOG_ERROR, "InitAudioStream() : Failed to create audio buffer"); return stream; } - // Buffers should be marked as processed by default so that a call to UpdateAudioStream() immediately after initialization works correctly. - internalData->isSubBufferProcessed[0] = true; - internalData->isSubBufferProcessed[1] = true; - internalData->bufferSizeInFrames = AUDIO_BUFFER_SIZE*2; - internalData->volume = 1; - internalData->pitch = 1; - AppendAudioStream(internalData); - - stream.handle = internalData; + audioBuffer->looping = true; // Always loop for streaming buffers. + stream.audioBuffer = audioBuffer; #else // Setup OpenAL format if (stream.channels == 1) @@ -1838,9 +1810,7 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un void CloseAudioStream(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - RemoveAudioStream(internalData); - free(internalData); + DeleteAudioBuffer((AudioBuffer*)stream.audioBuffer); #else // Stop playing channel alSourceStop(stream.source); @@ -1871,32 +1841,30 @@ void CloseAudioStream(AudioStream stream) void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) + AudioBuffer* audioBuffer = (AudioBuffer*)stream.audioBuffer; + if (audioBuffer == NULL) { - TraceLog(LOG_ERROR, "Invalid audio stream"); + TraceLog(LOG_ERROR, "UpdateAudioStream() : No audio buffer"); return; } - // We need to determine which half of the buffer needs updating. If the stream is not started and the cursor position is - // at the front of the buffer, update the first subbuffer. - if (internalData->isSubBufferProcessed[0] || internalData->isSubBufferProcessed[1]) + if (audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]) { mal_uint32 subBufferToUpdate; - if (internalData->isSubBufferProcessed[0] && internalData->isSubBufferProcessed[1]) + if (audioBuffer->isSubBufferProcessed[0] && audioBuffer->isSubBufferProcessed[1]) { // Both buffers are available for updating. Update the first one and make sure the cursor is moved back to the front. subBufferToUpdate = 0; - internalData->frameCursorPos = 0; + audioBuffer->frameCursorPos = 0; } else { // Just update whichever sub-buffer is processed. - subBufferToUpdate = (internalData->isSubBufferProcessed[0]) ? 0 : 1; + subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0]) ? 0 : 1; } - mal_uint32 subBufferSizeInFrames = AUDIO_BUFFER_SIZE; - unsigned char *subBuffer = internalData->buffer + ((subBufferSizeInFrames * stream.channels * (stream.sampleSize/8)) * subBufferToUpdate); + mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2; + unsigned char *subBuffer = audioBuffer->buffer + ((subBufferSizeInFrames * stream.channels * (stream.sampleSize/8)) * subBufferToUpdate); // Does this API expect a whole buffer to be updated in one go? Assuming so, but if not will need to change this logic. if (subBufferSizeInFrames >= (mal_uint32)samplesCount) @@ -1915,21 +1883,19 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount * stream.channels * (stream.sampleSize/8)); } - internalData->isSubBufferProcessed[subBufferToUpdate] = false; + audioBuffer->isSubBufferProcessed[subBufferToUpdate] = false; } else { - TraceLog(LOG_ERROR, "[AUD ID %i] UpdateAudioStream() : Attempting to write too many frames to buffer"); + TraceLog(LOG_ERROR, "UpdateAudioStream() : Attempting to write too many frames to buffer"); return; } } else { - TraceLog(LOG_ERROR, "[AUD ID %i] Audio buffer not available for updating"); + TraceLog(LOG_ERROR, "Audio buffer not available for updating"); return; } - - #else ALuint buffer = 0; alSourceUnqueueBuffers(stream.source, 1, &buffer); @@ -1948,14 +1914,14 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount) bool IsAudioBufferProcessed(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) + AudioBuffer* audioBuffer = (AudioBuffer*)stream.audioBuffer; + if (audioBuffer == NULL) { - TraceLog(LOG_ERROR, "Invalid audio stream"); + TraceLog(LOG_ERROR, "IsAudioBufferProcessed() : No audio buffer"); return false; } - return internalData->isSubBufferProcessed[0] || internalData->isSubBufferProcessed[1]; + return audioBuffer->isSubBufferProcessed[0] || audioBuffer->isSubBufferProcessed[1]; #else ALint processed = 0; @@ -1970,14 +1936,7 @@ bool IsAudioBufferProcessed(AudioStream stream) void PlayAudioStream(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return; - } - - internalData->playing = true; + PlayAudioBuffer((AudioBuffer*)stream.audioBuffer); #else alSourcePlay(stream.source); #endif @@ -1987,14 +1946,7 @@ void PlayAudioStream(AudioStream stream) void PauseAudioStream(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return; - } - - internalData->paused = true; + PauseAudioBuffer((AudioBuffer*)stream.audioBuffer); #else alSourcePause(stream.source); #endif @@ -2004,14 +1956,7 @@ void PauseAudioStream(AudioStream stream) void ResumeAudioStream(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return; - } - - internalData->paused = false; + ResumeAudioBuffer((AudioBuffer*)stream.audioBuffer); #else ALenum state; alGetSourcei(stream.source, AL_SOURCE_STATE, &state); @@ -2024,14 +1969,7 @@ void ResumeAudioStream(AudioStream stream) bool IsAudioStreamPlaying(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return false; - } - - return internalData->playing; + return IsAudioBufferPlaying((AudioBuffer*)stream.audioBuffer); #else bool playing = false; ALint state; @@ -2048,18 +1986,7 @@ bool IsAudioStreamPlaying(AudioStream stream) void StopAudioStream(AudioStream stream) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return; - } - - internalData->playing = 0; - internalData->paused = 0; - internalData->frameCursorPos = 0; - internalData->isSubBufferProcessed[0] = true; - internalData->isSubBufferProcessed[1] = true; + StopAudioBuffer((AudioBuffer*)stream.audioBuffer); #else alSourceStop(stream.source); #endif @@ -2068,14 +1995,7 @@ void StopAudioStream(AudioStream stream) void SetAudioStreamVolume(AudioStream stream, float volume) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return; - } - - internalData->volume = volume; + SetAudioBufferVolume((AudioBuffer*)stream.audioBuffer, volume); #else alSourcef(stream.source, AL_GAIN, volume); #endif @@ -2084,25 +2004,7 @@ void SetAudioStreamVolume(AudioStream stream, float volume) void SetAudioStreamPitch(AudioStream stream, float pitch) { #if USE_MINI_AL - AudioStreamData* internalData = (AudioStreamData*)stream.handle; - if (internalData == NULL) - { - TraceLog(LOG_ERROR, "Invalid audio stream"); - return; - } - - if (pitch == 0) - { - TraceLog(LOG_ERROR, "Attempting to set pitch to 0"); - return; - } - - internalData->pitch = pitch; - - // Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches - // will make the sound faster; lower pitches make it slower. - mal_uint32 newOutputSampleRate = (mal_uint32)((((float)internalData->dsp.config.sampleRateOut / (float)internalData->dsp.config.sampleRateIn) / pitch) * internalData->dsp.config.sampleRateIn); - mal_dsp_set_output_sample_rate(&internalData->dsp, newOutputSampleRate); + SetAudioBufferPitch((AudioBuffer*)stream.audioBuffer, pitch); #else alSourcef(stream.source, AL_PITCH, pitch); #endif diff --git a/src/raylib.h b/src/raylib.h index 1f138c2b..331817cf 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -486,7 +486,7 @@ typedef struct Wave { // Sound source type typedef struct Sound { - void* handle; // A pointer to internal data used by the audio system. + void* audioBuffer; // A pointer to internal data used by the audio system. unsigned int source; // OpenAL audio source id unsigned int buffer; // OpenAL audio buffer id @@ -504,7 +504,7 @@ typedef struct AudioStream { unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo) - void* handle; // A pointer to internal data used by the audio system. + void* audioBuffer; // A pointer to internal data used by the audio system. int format; // OpenAL audio format specifier unsigned int source; // OpenAL audio source id -- cgit v1.2.3 From 54d0acc3b6200e044771c28fb01ef97dde5b90a0 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 5 Dec 2017 00:05:05 +0100 Subject: Change version number for develop Updated raylib version to 1.9-dev for development pourposes. Next raylib version is planned to implement a big amount of changes, so the version bump. --- src/core.c | 4 ++-- src/raylib.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index c11c69ce..b3dde142 100644 --- a/src/core.c +++ b/src/core.c @@ -414,7 +414,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread // NOTE: data parameter could be used to pass any kind of required data to the initialization void InitWindow(int width, int height, void *data) { - TraceLog(LOG_INFO, "Initializing raylib (v1.8.0)"); + TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); // Input data is window title char data windowTitle = (char *)data; @@ -478,7 +478,7 @@ void InitWindow(int width, int height, void *data) // NOTE: data parameter could be used to pass any kind of required data to the initialization void InitWindow(int width, int height, void *data) { - TraceLog(LOG_INFO, "Initializing raylib (v1.8.0)"); + TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); screenWidth = width; screenHeight = height; diff --git a/src/raylib.h b/src/raylib.h index e5ad8a9d..399b1d4a 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib v1.8.0 +* raylib v1.9-dev * * A simple and easy-to-use library to learn videogames programming (www.raylib.com) * -- cgit v1.2.3 From acbfba92508566cb93ff91da2939ebc0cc4b800e Mon Sep 17 00:00:00 2001 From: Ray San Date: Tue, 5 Dec 2017 13:22:26 +0100 Subject: Updated library features and dependencies --- README.md | 16 ++++++++-------- src/raylib.h | 34 +++++++++++++++++----------------- 2 files changed, 25 insertions(+), 25 deletions(-) (limited to 'src/raylib.h') diff --git a/README.md b/README.md index db8e9698..a82ea12c 100644 --- a/README.md +++ b/README.md @@ -18,20 +18,20 @@ pure spartan-programmers way. Are you ready to learn? Jump to [code examples!](h features -------- - + * Written in plain C code (C99) in PascalCase/camelCase notation - * Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES2) + * Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES2 - choose at compile) * Unique OpenGL abstraction layer (usable as standalone module): [rlgl](https://github.com/raysan5/raylib/blob/master/src/rlgl.c) - * Powerful fonts module with SpriteFonts support (XNA bitmap fonts, AngelCode fonts, TTF) - * Outstanding texture formats support, including compressed formats (DXT, ETC, PVRT, ASTC) - * Basic 3d support for Geometrics, Models, Billboards, Heightmaps and Cubicmaps + * Powerful fonts module with SpriteFonts support (XNA fonts, AngelCode fonts, TTF) + * Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) + * Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! * Flexible Materials system, supporting classic maps and PBR maps * Shaders support, including Model shaders and Postprocessing shaders * Powerful math module for Vector, Matrix and Quaternion operations: [raymath](https://github.com/raysan5/raylib/blob/master/src/raymath.h) * Audio loading and playing with streaming support (WAV, OGG, FLAC, XM, MOD) - * Multiple platforms support: Windows, Linux, Mac, **Android**, **Raspberry Pi** and **HTML5** - * VR stereo rendering support with configurable HMD device parameters - * Minimal external dependencies (OpenGL, OpenAL) + * Multiple platforms support: Windows, Linux, FreeBSD, MacOS, UWP, Android, Raspberry Pi, HTML5. + * VR stereo rendering with configurable HMD device parameters + * NO external dependencies, all required libraries included with raylib * Complete bindings to LUA ([raylib-lua](https://github.com/raysan5/raylib-lua)) and Go ([raylib-go](https://github.com/gen2brain/raylib-go)) raylib uses on its core module the outstanding [GLFW3](http://www.glfw.org/) library. The best option I found for diff --git a/src/raylib.h b/src/raylib.h index 95247235..a3914c4d 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -6,18 +6,18 @@ * * FEATURES: * - Written in plain C code (C99) in PascalCase/camelCase notation -* - Multiple platforms support: Windows, Linux, Mac, Android, Raspberry Pi and HTML5 -* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES 2.0) +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES2 - choose at compile) * - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] -* - Powerful fonts module with SpriteFonts support (XNA bitmap fonts, AngelCode fonts, TTF) -* - Outstanding texture formats support, including compressed formats (DXT, ETC, PVRT, ASTC) -* - Basic 3d support for Geometrics, Models, Billboards, Heightmaps and Cubicmaps +* - Powerful fonts module with SpriteFonts support (XNA fonts, AngelCode fonts, TTF) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! * - Flexible Materials system, supporting classic maps and PBR maps * - Shaders support, including Model shaders and Postprocessing shaders -* - Powerful math module for Vector2, Vector3, Matrix and Quaternion operations: [raymath] -* - Audio loading and playing with streaming support and mixing channels: [audio] -* - VR stereo rendering support with configurable HMD device parameters -* - Minimal external dependencies (GLFW3, OpenGL, OpenAL) +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, FLAC, XM, MOD) +* - Multiple platforms support: Windows, Linux, FreeBSD, MacOS, UWP, Android, Raspberry Pi, HTML5. +* - VR stereo rendering with configurable HMD device parameters +* - NO external dependencies, all required libraries included with raylib * - Complete bindings to LUA (raylib-lua) and Go (raylib-go) * * NOTES: @@ -25,17 +25,17 @@ * If using OpenGL 3.3 or ES2, one default shader is loaded automatically (internally defined) [rlgl] * If using OpenGL 3.3 or ES2, several vertex buffers (VAO/VBO) are created to manage lines-triangles-quads * -* DEPENDENCIES: -* GLFW3 (www.glfw.org) for window/context management and input [core] -* GLAD for OpenGL extensions loading (3.3 Core profile, only PLATFORM_DESKTOP) [rlgl] -* OpenAL Soft for audio device/context management [audio] +* DEPENDENCIES (included): +* rglfw (github.com/glfw/glfw) for window/context management and input (only PLATFORM_DESKTOP) [core] +* glad (github.com/Dav1dde/glad) for OpenGL extensions loading (3.3 Core profile, only PLATFORM_DESKTOP) [rlgl] +* mini_al (github.com/dr-soft/mini_al) for audio device/context management [audio] * -* OPTIONAL DEPENDENCIES: -* stb_image (Sean Barret) for images loading (JPEG, PNG, BMP, TGA) [textures] +* OPTIONAL DEPENDENCIES (included): +* stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) [textures] * stb_image_resize (Sean Barret) for image resizing algorythms [textures] * stb_image_write (Sean Barret) for image writting (PNG) [utils] * stb_truetype (Sean Barret) for ttf fonts loading [text] -* stb_vorbis (Sean Barret) for ogg audio loading [audio] +* stb_vorbis (Sean Barret) for OGG audio loading [audio] * stb_perlin (Sean Barret) for Perlin noise image generation [textures] * par_shapes (Philip Rideout) for parametric 3d shapes generation [models] * jar_xm (Joshua Reisenauer) for XM audio module loading [audio] @@ -50,7 +50,7 @@ * raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software: * -* Copyright (c) 2013-2017 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2018 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. -- cgit v1.2.3 From a6f9cc5629841b7adf3d9ff21eaa2aaabb3e4bc1 Mon Sep 17 00:00:00 2001 From: Ray Date: Mon, 11 Dec 2017 11:55:02 +0100 Subject: Remove rres support Let the user choose if using rres external library --- src/audio.c | 13 -- src/external/tinfl.c | 592 --------------------------------------------------- src/raylib.h | 16 -- src/rres.h | 483 ----------------------------------------- src/text.c | 29 +-- src/textures.c | 13 +- src/utils.c | 3 - src/utils.h | 2 - 8 files changed, 2 insertions(+), 1149 deletions(-) delete mode 100644 src/external/tinfl.c delete mode 100644 src/rres.h (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index d397b064..15f50a24 100644 --- a/src/audio.c +++ b/src/audio.c @@ -812,19 +812,6 @@ Wave LoadWave(const char *fileName) #endif #if defined(SUPPORT_FILEFORMAT_FLAC) else if (IsFileExtension(fileName, ".flac")) wave = LoadFLAC(fileName); -#endif -#if !defined(AUDIO_STANDALONE) - else if (IsFileExtension(fileName, ".rres")) - { - RRES rres = LoadResource(fileName, 0); - - // NOTE: Parameters for RRES_TYPE_WAVE are: sampleCount, sampleRate, sampleSize, channels - - 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(LOG_WARNING, "[%s] Resource file does not contain wave data", fileName); - - UnloadResource(rres); - } #endif else TraceLog(LOG_WARNING, "[%s] Audio fileformat not supported, it can't be loaded", fileName); diff --git a/src/external/tinfl.c b/src/external/tinfl.c deleted file mode 100644 index a17a156b..00000000 --- a/src/external/tinfl.c +++ /dev/null @@ -1,592 +0,0 @@ -/* tinfl.c v1.11 - public domain inflate with zlib header parsing/adler32 checking (inflate-only subset of miniz.c) - See "unlicense" statement at the end of this file. - Rich Geldreich , last updated May 20, 2011 - Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt - - The entire decompressor coroutine is implemented in tinfl_decompress(). The other functions are optional high-level helpers. -*/ -#ifndef TINFL_HEADER_INCLUDED -#define TINFL_HEADER_INCLUDED - -#include - -typedef unsigned char mz_uint8; -typedef signed short mz_int16; -typedef unsigned short mz_uint16; -typedef unsigned int mz_uint32; -typedef unsigned int mz_uint; -typedef unsigned long long mz_uint64; - -#if defined(_M_IX86) || defined(_M_X64) -// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 if integer loads and stores to unaligned addresses are acceptable on the target platform (slightly faster). -#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 -// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. -#define MINIZ_LITTLE_ENDIAN 1 -#endif - -#if defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) -// Set MINIZ_HAS_64BIT_REGISTERS to 1 if the processor has 64-bit general purpose registers (enables 64-bit bitbuffer in inflator) -#define MINIZ_HAS_64BIT_REGISTERS 1 -#endif - -// Works around MSVC's spammy "warning C4127: conditional expression is constant" message. -#ifdef _MSC_VER - #define MZ_MACRO_END while (0, 0) -#else - #define MZ_MACRO_END while (0) -#endif - -// Decompression flags used by tinfl_decompress(). -// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. -// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. -// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). -// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. -enum -{ - TINFL_FLAG_PARSE_ZLIB_HEADER = 1, - TINFL_FLAG_HAS_MORE_INPUT = 2, - TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, - TINFL_FLAG_COMPUTE_ADLER32 = 8 -}; - -// High level decompression functions: -// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). -// On entry: -// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. -// On return: -// Function returns a pointer to the decompressed data, or NULL on failure. -// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. -// The caller must free() the returned block when it's no longer needed. -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); - -// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. -// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. -#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); - -// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. -// Returns 1 on success or 0 on failure. -typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); - -struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; - -// Max size of LZ dictionary. -#define TINFL_LZ_DICT_SIZE 32768 - -// Return status. -typedef enum -{ - TINFL_STATUS_BAD_PARAM = -3, - TINFL_STATUS_ADLER32_MISMATCH = -2, - TINFL_STATUS_FAILED = -1, - TINFL_STATUS_DONE = 0, - TINFL_STATUS_NEEDS_MORE_INPUT = 1, - TINFL_STATUS_HAS_MORE_OUTPUT = 2 -} tinfl_status; - -// Initializes the decompressor to its initial state. -#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END -#define tinfl_get_adler32(r) (r)->m_check_adler32 - -// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. -// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); - -// Internal/private bits follow. -enum -{ - TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, - TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS -}; - -typedef struct -{ - mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; - mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; -} tinfl_huff_table; - -#if MINIZ_HAS_64BIT_REGISTERS - #define TINFL_USE_64BIT_BITBUF 1 -#endif - -#if TINFL_USE_64BIT_BITBUF - typedef mz_uint64 tinfl_bit_buf_t; - #define TINFL_BITBUF_SIZE (64) -#else - typedef mz_uint32 tinfl_bit_buf_t; - #define TINFL_BITBUF_SIZE (32) -#endif - -struct tinfl_decompressor_tag -{ - mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; - tinfl_bit_buf_t m_bit_buf; - size_t m_dist_from_out_buf_start; - tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; - mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; -}; - -#endif // #ifdef TINFL_HEADER_INCLUDED - -// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) - -#ifndef TINFL_HEADER_FILE_ONLY - -#include - -// MZ_MALLOC, etc. are only used by the optional high-level helper functions. -#ifdef MINIZ_NO_MALLOC - #define MZ_MALLOC(x) NULL - #define MZ_FREE(x) x, ((void)0) - #define MZ_REALLOC(p, x) NULL -#else - #define MZ_MALLOC(x) malloc(x) - #define MZ_FREE(x) free(x) - #define MZ_REALLOC(p, x) realloc(p, x) -#endif - -#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) -#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) -#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) - -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN - #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) - #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) -#else - #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) - #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) -#endif - -#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) -#define TINFL_MEMSET(p, c, l) memset(p, c, l) - -#define TINFL_CR_BEGIN switch(r->m_state) { case 0: -#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END -#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END -#define TINFL_CR_FINISH } - -// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never -// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. -#define TINFL_GET_BYTE(state_index, c) do { \ - if (pIn_buf_cur >= pIn_buf_end) { \ - for ( ; ; ) { \ - if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ - TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ - if (pIn_buf_cur < pIn_buf_end) { \ - c = *pIn_buf_cur++; \ - break; \ - } \ - } else { \ - c = 0; \ - break; \ - } \ - } \ - } else c = *pIn_buf_cur++; } MZ_MACRO_END - -#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) -#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END -#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END - -// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. -// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a -// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the -// bit buffer contains >=15 bits (deflate's max. Huffman code size). -#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ - do { \ - temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ - if (temp >= 0) { \ - code_len = temp >> 9; \ - if ((code_len) && (num_bits >= code_len)) \ - break; \ - } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ - code_len = TINFL_FAST_LOOKUP_BITS; \ - do { \ - temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ - } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ - } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ - } while (num_bits < 15); - -// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read -// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully -// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. -// The slow path is only executed at the very end of the input buffer. -#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ - int temp; mz_uint code_len, c; \ - if (num_bits < 15) { \ - if ((pIn_buf_end - pIn_buf_cur) < 2) { \ - TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ - } else { \ - bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ - } \ - } \ - if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ - code_len = temp >> 9, temp &= 511; \ - else { \ - code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ - } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END - -tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) -{ - static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; - static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; - static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; - static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; - static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; - static const int s_min_table_sizes[3] = { 257, 1, 4 }; - - tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; - const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; - mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; - size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; - - // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). - if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } - - num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; - TINFL_CR_BEGIN - - bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); - counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); - if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); - if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } - } - - do - { - TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; - if (r->m_type == 0) - { - TINFL_SKIP_BITS(5, num_bits & 7); - for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } - if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } - while ((counter) && (num_bits)) - { - TINFL_GET_BITS(51, dist, 8); - while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } - *pOut_buf_cur++ = (mz_uint8)dist; - counter--; - } - while (counter) - { - size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } - while (pIn_buf_cur >= pIn_buf_end) - { - if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) - { - TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); - } - else - { - TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); - } - } - n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); - TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; - } - } - else if (r->m_type == 3) - { - TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); - } - else - { - if (r->m_type == 1) - { - mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; - r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); - for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; - } - else - { - for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } - MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } - r->m_table_sizes[2] = 19; - } - for ( ; (int)r->m_type >= 0; r->m_type--) - { - int tree_next, tree_cur; tinfl_huff_table *pTable; - mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); - for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; - used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; - for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } - if ((65536 != total) && (used_syms > 1)) - { - TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); - } - for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) - { - mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; - cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); - if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } - if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } - rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); - for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) - { - tree_cur -= ((rev_code >>= 1) & 1); - if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; - } - tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; - } - if (r->m_type == 2) - { - for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) - { - mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } - if ((dist == 16) && (!counter)) - { - TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); - } - num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; - TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; - } - if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) - { - TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); - } - TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); - } - } - for ( ; ; ) - { - mz_uint8 *pSrc; - for ( ; ; ) - { - if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) - { - TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); - if (counter >= 256) - break; - while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } - *pOut_buf_cur++ = (mz_uint8)counter; - } - else - { - int sym2; mz_uint code_len; -#if TINFL_USE_64BIT_BITBUF - if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } -#else - if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } -#endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); - } - counter = sym2; bit_buf >>= code_len; num_bits -= code_len; - if (counter & 256) - break; - -#if !TINFL_USE_64BIT_BITBUF - if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } -#endif - if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) - code_len = sym2 >> 9; - else - { - code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); - } - bit_buf >>= code_len; num_bits -= code_len; - - pOut_buf_cur[0] = (mz_uint8)counter; - if (sym2 & 256) - { - pOut_buf_cur++; - counter = sym2; - break; - } - pOut_buf_cur[1] = (mz_uint8)sym2; - pOut_buf_cur += 2; - } - } - if ((counter &= 511) == 256) break; - - num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; - if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } - - TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); - num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; - if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } - - dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; - if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) - { - TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); - } - - pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); - - if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) - { - while (counter--) - { - while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } - *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; - } - continue; - } -#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES - else if ((counter >= 9) && (counter <= dist)) - { - const mz_uint8 *pSrc_end = pSrc + (counter & ~7); - do - { - ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; - ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; - pOut_buf_cur += 8; - } while ((pSrc += 8) < pSrc_end); - if ((counter &= 7) < 3) - { - if (counter) - { - pOut_buf_cur[0] = pSrc[0]; - if (counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; - } - continue; - } - } -#endif - do - { - pOut_buf_cur[0] = pSrc[0]; - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur[2] = pSrc[2]; - pOut_buf_cur += 3; pSrc += 3; - } while ((int)(counter -= 3) > 2); - if ((int)counter > 0) - { - pOut_buf_cur[0] = pSrc[0]; - if ((int)counter > 1) - pOut_buf_cur[1] = pSrc[1]; - pOut_buf_cur += counter; - } - } - } - } while (!(r->m_final & 1)); - if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) - { - TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } - } - TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); - TINFL_CR_FINISH - -common_exit: - r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; - *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; - if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) - { - const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; - mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; - while (buf_len) - { - for (i = 0; i + 7 < block_len; i += 8, ptr += 8) - { - s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; - s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; - } - for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; - s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; - } - r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; - } - return status; -} - -// Higher level helper functions. -void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) -{ - tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; - *pOut_len = 0; - tinfl_init(&decomp); - for ( ; ; ) - { - size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, - (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) - { - MZ_FREE(pBuf); *pOut_len = 0; return NULL; - } - src_buf_ofs += src_buf_size; - *pOut_len += dst_buf_size; - if (status == TINFL_STATUS_DONE) break; - new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; - pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); - if (!pNew_buf) - { - MZ_FREE(pBuf); *pOut_len = 0; return NULL; - } - pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; - } - return pBuf; -} - -size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) -{ - tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); - status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); - return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; -} - -int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) -{ - int result = 0; - tinfl_decompressor decomp; - mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; - if (!pDict) - return TINFL_STATUS_FAILED; - tinfl_init(&decomp); - for ( ; ; ) - { - size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; - tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, - (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); - in_buf_ofs += in_buf_size; - if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) - break; - if (status != TINFL_STATUS_HAS_MORE_OUTPUT) - { - result = (status == TINFL_STATUS_DONE); - break; - } - dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); - } - MZ_FREE(pDict); - *pIn_buf_size = in_buf_ofs; - return result; -} - -#endif // #ifndef TINFL_HEADER_FILE_ONLY - -/* - This is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this software, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this software dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - - For more information, please refer to -*/ diff --git a/src/raylib.h b/src/raylib.h index a3914c4d..ac739830 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -495,22 +495,6 @@ typedef struct AudioStream { unsigned int buffers[2]; // OpenAL audio buffers (double buffering) } AudioStream; -// rRES data returned when reading a resource, -// it contains all required data for user (24 byte) -typedef struct RRESData { - unsigned int type; // Resource type (4 byte) - - unsigned int param1; // Resouce parameter 1 (4 byte) - unsigned int param2; // Resouce parameter 2 (4 byte) - unsigned int param3; // Resouce parameter 3 (4 byte) - unsigned int param4; // Resouce parameter 4 (4 byte) - - void *data; // Resource data pointer (4 byte) -} RRESData; - -// RRES type (pointer to RRESData array) -typedef struct RRESData *RRES; - // Head-Mounted-Display device parameters typedef struct VrDeviceInfo { int hResolution; // HMD horizontal resolution in pixels diff --git a/src/rres.h b/src/rres.h deleted file mode 100644 index 75faf640..00000000 --- a/src/rres.h +++ /dev/null @@ -1,483 +0,0 @@ -/********************************************************************************************** -* -* rres v1.0 - raylib resource (rRES) custom fileformat management functions -* -* CONFIGURATION: -* -* #define RREM_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* DEPENDENCIES: -* tinfl - DEFLATE decompression functions -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2016-2017 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. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -/* -References: - RIFF file-format: http://www.johnloomis.org/cpe102/asgn/asgn1/riff.html - ZIP file-format: https://en.wikipedia.org/wiki/Zip_(file_format) - http://www.onicos.com/staff/iz/formats/zip.html - XNB file-format: http://xbox.create.msdn.com/en-US/sample/xnb_format -*/ - -#ifndef RRES_H -#define RRES_H - -#if !defined(RRES_STANDALONE) - #include "raylib.h" -#endif - -//#define RRES_STATIC -#ifdef RRES_STATIC - #define RRESDEF static // Functions just visible to module including this file -#else - #ifdef __cplusplus - #define RRESDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) - #else - #define RRESDEF extern // Functions visible from other files - #endif -#endif - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#define MAX_RESOURCES_SUPPORTED 256 - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -// NOTE: Some types are required for RAYGUI_STANDALONE usage -//---------------------------------------------------------------------------------- -#if defined(RRES_STANDALONE) - // rRES data returned when reading a resource, it contains all required data for user (24 byte) - // NOTE: Using void *data pointer, so we can load to image.data, wave.data, mesh.*, (unsigned char *) - typedef struct RRESData { - unsigned int type; // Resource type (4 byte) - - unsigned int param1; // Resouce parameter 1 (4 byte) - unsigned int param2; // Resouce parameter 2 (4 byte) - unsigned int param3; // Resouce parameter 3 (4 byte) - unsigned int param4; // Resouce parameter 4 (4 byte) - - void *data; // Resource data pointer (4 byte) - } RRESData; - - // RRES type (pointer to RRESData array) - typedef struct RRESData *RRES; // Resource pointer - - // RRESData type - typedef enum { - RRES_TYPE_RAW = 0, - RRES_TYPE_IMAGE, - RRES_TYPE_WAVE, - RRES_TYPE_VERTEX, - RRES_TYPE_TEXT, - RRES_TYPE_FONT_IMAGE, - RRES_TYPE_FONT_CHARDATA, // CharInfo { int value, recX, recY, recWidth, recHeight, offsetX, offsetY, xAdvance } - RRES_TYPE_DIRECTORY - } RRESDataType; - -// Parameters information depending on resource type - -// RRES_TYPE_RAW params: -// RRES_TYPE_IMAGE params: width, height, mipmaps, format -// RRES_TYPE_WAVE params: sampleCount, sampleRate, sampleSize, channels -// RRES_TYPE_VERTEX params: vertexCount, vertexType, vertexFormat // Use masks instead? -// RRES_TYPE_TEXT params: charsCount, cultureCode -// RRES_TYPE_FONT_IMAGE params: width, height, format, mipmaps; -// RRES_TYPE_FONT_CHARDATA params: charsCount, baseSize -// RRES_TYPE_DIRECTORY params: fileCount, directoryCount - -// SpriteFont = RRES_TYPE_FONT_IMAGE chunk + RRES_TYPE_FONT_DATA chunk -// Mesh = multiple RRES_TYPE_VERTEX chunks - - -#endif - -//---------------------------------------------------------------------------------- -// Global variables -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- -//RRESDEF RRESData LoadResourceData(const char *rresFileName, int rresId, int part); -RRESDEF RRES LoadResource(const char *fileName, int rresId); -RRESDEF void UnloadResource(RRES rres); - -/* -QUESTION: How to load each type of data from RRES ? - -rres->type == RRES_TYPE_RAW -unsigned char data = (unsigned char *)rres[0]->data; - -rres->type == RRES_TYPE_IMAGE -Image image; -image.data = rres[0]->data; // Be careful, duplicate pointer -image.width = rres[0]->param1; -image.height = rres[0]->param2; -image.mipmaps = rres[0]->param3; -image.format = rres[0]->format; - -rres->type == RRES_TYPE_WAVE -Wave wave; -wave.data = rres[0]->data; -wave.sampleCount = rres[0]->param1; -wave.sampleRate = rres[0]->param2; -wave.sampleSize = rres[0]->param3; -wave.channels = rres[0]->param4; - -rres->type == RRES_TYPE_VERTEX (multiple parts) -Mesh mesh; -mesh.vertexCount = rres[0]->param1; -mesh.vertices = (float *)rres[0]->data; -mesh.texcoords = (float *)rres[1]->data; -mesh.normals = (float *)rres[2]->data; -mesh.tangents = (float *)rres[3]->data; -mesh.tangents = (unsigned char *)rres[4]->data; - -rres->type == RRES_TYPE_TEXT -unsigned char *text = (unsigned char *)rres->data; -Shader shader = LoadShaderText(text, rres->param1); Shader LoadShaderText(const char *shdrText, int length); - -rres->type == RRES_TYPE_FONT_IMAGE (multiple parts) -rres->type == RRES_TYPE_FONT_CHARDATA -SpriteFont font; -font.texture = LoadTextureFromImage(image); // rres[0] -font.chars = (CharInfo *)rres[1]->data; -font.charsCount = rres[1]->param1; -font.baseSize = rres[1]->param2; - -rres->type == RRES_TYPE_DIRECTORY -unsigned char *fileNames = (unsigned char *)rres[0]->data; // fileNames separed by \n -int filesCount = rres[0]->param1; -*/ - -#endif // RRES_H - - -/*********************************************************************************** -* -* RRES IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RRES_IMPLEMENTATION) - -#include // Required for: FILE, fopen(), fclose() - -// Check if custom malloc/free functions defined, if not, using standard ones -#if !defined(RRES_MALLOC) - #include // Required for: malloc(), free() - - #define RRES_MALLOC(size) malloc(size) - #define RRES_FREE(ptr) free(ptr) -#endif - -#include "external/tinfl.c" // Required for: tinfl_decompress_mem_to_mem() - // NOTE: DEFLATE algorythm data decompression - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- - -// rRES file header (8 byte) -typedef struct { - char id[4]; // File identifier: rRES (4 byte) - unsigned short version; // File version and subversion (2 byte) - unsigned short count; // Number of resources in this file (2 byte) -} RRESFileHeader; - -// rRES info header, every resource includes this header (16 byte + 16 byte) -typedef struct { - unsigned int id; // Resource unique identifier (4 byte) - unsigned char dataType; // Resource data type (1 byte) - unsigned char compType; // Resource data compression type (1 byte) - unsigned char cryptoType; // Resource data encryption type (1 byte) - unsigned char partsCount; // Resource data parts count, used for splitted data (1 byte) - unsigned int dataSize; // Resource data size (compressed or not, only DATA) (4 byte) - unsigned int uncompSize; // Resource data size (uncompressed, only DATA) (4 byte) - - unsigned int param1; // Resouce parameter 1 (4 byte) - unsigned int param2; // Resouce parameter 2 (4 byte) - unsigned int param3; // Resouce parameter 3 (4 byte) - unsigned int param4; // Resouce parameter 4 (4 byte) -} RRESInfoHeader; - -// Compression types -typedef enum { - RRES_COMP_NONE = 0, // No data compression - RRES_COMP_DEFLATE, // DEFLATE compression - RRES_COMP_LZ4, // LZ4 compression - RRES_COMP_LZMA, // LZMA compression - RRES_COMP_BROTLI, // BROTLI compression - // gzip, zopfli, lzo, zstd // Other compression algorythms... -} RRESCompressionType; - -// Encryption types -typedef enum { - RRES_CRYPTO_NONE = 0, // No data encryption - RRES_CRYPTO_XOR, // XOR (128 bit) encryption - RRES_CRYPTO_AES, // RIJNDAEL (128 bit) encryption (AES) - RRES_CRYPTO_TDES, // Triple DES encryption - RRES_CRYPTO_BLOWFISH, // BLOWFISH encryption - RRES_CRYPTO_XTEA, // XTEA encryption - // twofish, RC5, RC6 // Other encryption algorythm... -} RRESEncryptionType; - -// Image/Texture data type -typedef enum { - RRES_IM_UNCOMP_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - RRES_IM_UNCOMP_GRAY_ALPHA, // 16 bpp (2 channels) - RRES_IM_UNCOMP_R5G6B5, // 16 bpp - RRES_IM_UNCOMP_R8G8B8, // 24 bpp - RRES_IM_UNCOMP_R5G5B5A1, // 16 bpp (1 bit alpha) - RRES_IM_UNCOMP_R4G4B4A4, // 16 bpp (4 bit alpha) - RRES_IM_UNCOMP_R8G8B8A8, // 32 bpp - RRES_IM_COMP_DXT1_RGB, // 4 bpp (no alpha) - RRES_IM_COMP_DXT1_RGBA, // 4 bpp (1 bit alpha) - RRES_IM_COMP_DXT3_RGBA, // 8 bpp - RRES_IM_COMP_DXT5_RGBA, // 8 bpp - RRES_IM_COMP_ETC1_RGB, // 4 bpp - RRES_IM_COMP_ETC2_RGB, // 4 bpp - RRES_IM_COMP_ETC2_EAC_RGBA, // 8 bpp - RRES_IM_COMP_PVRT_RGB, // 4 bpp - RRES_IM_COMP_PVRT_RGBA, // 4 bpp - RRES_IM_COMP_ASTC_4x4_RGBA, // 8 bpp - RRES_IM_COMP_ASTC_8x8_RGBA // 2 bpp - //... -} RRESImageFormat; - -// Vertex data type -typedef enum { - RRES_VERT_POSITION, - RRES_VERT_TEXCOORD1, - RRES_VERT_TEXCOORD2, - RRES_VERT_TEXCOORD3, - RRES_VERT_TEXCOORD4, - RRES_VERT_NORMAL, - RRES_VERT_TANGENT, - RRES_VERT_COLOR, - RRES_VERT_INDEX, - //... -} RRESVertexType; - -// Vertex data format type -typedef enum { - RRES_VERT_BYTE, - RRES_VERT_SHORT, - RRES_VERT_INT, - RRES_VERT_HFLOAT, - RRES_VERT_FLOAT, - //... -} RRESVertexFormat; - -#if defined(RRES_STANDALONE) -typedef enum { LOG_INFO = 0, LOG_ERROR, LOG_WARNING, LOG_DEBUG, LOG_OTHER } TraceLogType; -#endif - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -//... - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -static void *DecompressData(const unsigned char *data, unsigned long compSize, int uncompSize); - -//---------------------------------------------------------------------------------- -// Module Functions Definition -//---------------------------------------------------------------------------------- - -// Load resource from file by id (could be multiple parts) -// NOTE: Returns uncompressed data with parameters, search resource by id -RRESDEF RRES LoadResource(const char *fileName, int rresId) -{ - RRES rres = { 0 }; - - RRESFileHeader fileHeader; - RRESInfoHeader infoHeader; - - FILE *rresFile = fopen(fileName, "rb"); - - if (rresFile == NULL) TraceLog(LOG_WARNING, "[%s] rRES raylib resource file could not be opened", fileName); - else - { - // Read rres file info header - fread(&fileHeader.id[0], sizeof(char), 1, rresFile); - fread(&fileHeader.id[1], sizeof(char), 1, rresFile); - fread(&fileHeader.id[2], sizeof(char), 1, rresFile); - fread(&fileHeader.id[3], sizeof(char), 1, rresFile); - fread(&fileHeader.version, sizeof(short), 1, rresFile); - fread(&fileHeader.count, sizeof(short), 1, rresFile); - - // Verify "rRES" identifier - if ((fileHeader.id[0] != 'r') && (fileHeader.id[1] != 'R') && (fileHeader.id[2] != 'E') && (fileHeader.id[3] != 'S')) - { - TraceLog(LOG_WARNING, "[%s] This is not a valid raylib resource file", fileName); - } - else - { - for (int i = 0; i < fileHeader.count; i++) - { - // Read resource info and parameters - fread(&infoHeader, sizeof(RRESInfoHeader), 1, rresFile); - - if (infoHeader.id == rresId) - { - rres = (RRES)malloc(sizeof(RRESData)*infoHeader.partsCount); - - // Load all required resources parts - for (int k = 0; k < infoHeader.partsCount; k++) - { - // TODO: Verify again that rresId is the same in every part - - // Register data type and parameters - rres[k].type = infoHeader.dataType; - rres[k].param1 = infoHeader.param1; - rres[k].param2 = infoHeader.param2; - rres[k].param3 = infoHeader.param3; - rres[k].param4 = infoHeader.param4; - - // Read resource data block - void *data = RRES_MALLOC(infoHeader.dataSize); - fread(data, infoHeader.dataSize, 1, rresFile); - - if (infoHeader.compType == RRES_COMP_DEFLATE) - { - void *uncompData = DecompressData(data, infoHeader.dataSize, infoHeader.uncompSize); - - rres[k].data = uncompData; - - RRES_FREE(data); - } - else rres[k].data = data; - - if (rres[k].data != NULL) TraceLog(LOG_INFO, "[%s][ID %i] Resource data loaded successfully", fileName, (int)infoHeader.id); - - // Read next part - fread(&infoHeader, sizeof(RRESInfoHeader), 1, rresFile); - } - } - else - { - // Skip required data to read next resource infoHeader - fseek(rresFile, infoHeader.dataSize, SEEK_CUR); - } - } - - if (rres[0].data == NULL) TraceLog(LOG_WARNING, "[%s][ID %i] Requested resource could not be found", fileName, (int)rresId); - } - - fclose(rresFile); - } - - return rres; -} - -// Unload resource data -RRESDEF void UnloadResource(RRES rres) -{ - // TODO: When you load resource... how many parts conform it? depends on type? --> Not clear... - - if (rres[0].data != NULL) free(rres[0].data); -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -// Data decompression function -// NOTE: Allocated data MUST be freed by user -static void *DecompressData(const unsigned char *data, unsigned long compSize, int uncompSize) -{ - int tempUncompSize; - void *uncompData; - - // Allocate buffer to hold decompressed data - uncompData = (mz_uint8 *)RRES_MALLOC((size_t)uncompSize); - - // Check correct memory allocation - if (uncompData == NULL) - { - TraceLog(LOG_WARNING, "Out of memory while decompressing data"); - } - else - { - // Decompress data - tempUncompSize = tinfl_decompress_mem_to_mem(uncompData, (size_t)uncompSize, data, compSize, 1); - - if (tempUncompSize == -1) - { - TraceLog(LOG_WARNING, "Data decompression failed"); - RRES_FREE(uncompData); - } - - if (uncompSize != (int)tempUncompSize) - { - TraceLog(LOG_WARNING, "Expected uncompressed size do not match, data may be corrupted"); - TraceLog(LOG_WARNING, " -- Expected uncompressed size: %i", uncompSize); - TraceLog(LOG_WARNING, " -- Returned uncompressed size: %i", tempUncompSize); - } - - TraceLog(LOG_INFO, "Data decompressed successfully from %u bytes to %u bytes", (mz_uint32)compSize, (mz_uint32)tempUncompSize); - } - - return uncompData; -} - -// Some required functions for rres standalone module version -#if defined(RRES_STANDALONE) -// Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) -void TraceLog(int logType, const char *text, ...) -{ - va_list args; - va_start(args, text); - - switch (msgType) - { - case LOG_INFO: fprintf(stdout, "INFO: "); break; - case LOG_ERROR: fprintf(stdout, "ERROR: "); break; - case LOG_WARNING: fprintf(stdout, "WARNING: "); break; - case LOG_DEBUG: fprintf(stdout, "DEBUG: "); break; - default: break; - } - - vfprintf(stdout, text, args); - fprintf(stdout, "\n"); - - va_end(args); - - if (msgType == LOG_ERROR) exit(1); -} -#endif - -#endif // RRES_IMPLEMENTATION diff --git a/src/text.c b/src/text.c index 8db2fc9f..0215bb5b 100644 --- a/src/text.c +++ b/src/text.c @@ -288,35 +288,8 @@ SpriteFont LoadSpriteFont(const char *fileName) SpriteFont spriteFont = { 0 }; - // Check file extension - if (IsFileExtension(fileName, ".rres")) - { - // TODO: Read multiple resource blocks from file (RRES_FONT_IMAGE, RRES_FONT_CHARDATA) - RRES rres = LoadResource(fileName, 0); - - // Load sprite font texture - if (rres[0].type == RRES_TYPE_FONT_IMAGE) - { - // NOTE: Parameters for RRES_FONT_IMAGE type are: width, height, format, mipmaps - Image image = LoadImagePro(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3); - spriteFont.texture = LoadTextureFromImage(image); - UnloadImage(image); - } - - // Load sprite characters data - if (rres[1].type == RRES_TYPE_FONT_CHARDATA) - { - // NOTE: Parameters for RRES_FONT_CHARDATA type are: fontSize, charsCount - spriteFont.baseSize = rres[1].param1; - spriteFont.charsCount = rres[1].param2; - spriteFont.chars = rres[1].data; - } - - // TODO: Do not free rres.data memory (chars info data!) - //UnloadResource(rres[0]); - } #if defined(SUPPORT_FILEFORMAT_TTF) - else if (IsFileExtension(fileName, ".ttf")) spriteFont = LoadSpriteFontEx(fileName, DEFAULT_TTF_FONTSIZE, 0, NULL); + if (IsFileExtension(fileName, ".ttf")) spriteFont = LoadSpriteFontEx(fileName, DEFAULT_TTF_FONTSIZE, 0, NULL); #endif #if defined(SUPPORT_FILEFORMAT_FNT) else if (IsFileExtension(fileName, ".fnt")) spriteFont = LoadBMFont(fileName); diff --git a/src/textures.c b/src/textures.c index 090de24b..6bd306f4 100644 --- a/src/textures.c +++ b/src/textures.c @@ -167,18 +167,7 @@ Image LoadImage(const char *fileName) { Image image = { 0 }; - if (IsFileExtension(fileName, ".rres")) - { - RRES rres = LoadResource(fileName, 0); - - // NOTE: Parameters for RRES_TYPE_IMAGE are: width, height, format, mipmaps - - if (rres[0].type == RRES_TYPE_IMAGE) image = LoadImagePro(rres[0].data, rres[0].param1, rres[0].param2, rres[0].param3); - else TraceLog(LOG_WARNING, "[%s] Resource file does not contain image data", fileName); - - UnloadResource(rres); - } - else if ((IsFileExtension(fileName, ".png")) + if ((IsFileExtension(fileName, ".png")) #if defined(SUPPORT_FILEFORMAT_BMP) || (IsFileExtension(fileName, ".bmp")) #endif diff --git a/src/utils.c b/src/utils.c index 967ed916..aaa5bdb4 100644 --- a/src/utils.c +++ b/src/utils.c @@ -66,9 +66,6 @@ #include "external/stb_image_write.h" // Required for: stbi_write_bmp(), stbi_write_png() #endif -#define RRES_IMPLEMENTATION -#include "rres.h" - //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- diff --git a/src/utils.h b/src/utils.h index 64592c73..a1801245 100644 --- a/src/utils.h +++ b/src/utils.h @@ -32,8 +32,6 @@ #include // Required for: AAssetManager #endif -#include "rres.h" - #define SUPPORT_SAVE_PNG //---------------------------------------------------------------------------------- -- cgit v1.2.3 From b872de951b0c1a830e3452ede6fbdd471df0d025 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Dec 2017 11:49:31 +0100 Subject: fix for GetMatrixModelview (former cl) --- src/raylib.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index ac739830..9af21872 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1046,6 +1046,7 @@ RLAPI void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int RLAPI void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) RLAPI void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) RLAPI void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI Matrix GetMatrixModelview(); // Texture maps generation (PBR) // NOTE: Required shaders should be provided -- cgit v1.2.3 From 48d0c93ace63225231d56f6a7aaa058218622578 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Dec 2017 11:50:35 +0100 Subject: make GetTime available to user of library --- src/core.c | 4 ++-- src/raylib.h | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 882aadcd..011e0d76 100644 --- a/src/core.c +++ b/src/core.c @@ -352,7 +352,7 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads default fo static void InitGraphicsDevice(int width, int height); // Initialize graphics device static void SetupFramebufferSize(int displayWidth, int displayHeight); static void InitTimer(void); // Initialize timer -static double GetTime(void); // Returns time since InitTimer() was run + double GetTime(void); // Returns time since InitTimer() was run static void Wait(float ms); // Wait for some milliseconds (stop program execution) static bool GetKeyStatus(int key); // Returns if a key has been pressed static bool GetMouseButtonStatus(int button); // Returns if a mouse button has been pressed @@ -2120,7 +2120,7 @@ static void InitTimer(void) } // Get current time measure (in seconds) since InitTimer() -static double GetTime(void) +double GetTime(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) return glfwGetTime(); diff --git a/src/raylib.h b/src/raylib.h index 9af21872..0a9eab46 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -723,6 +723,9 @@ RLAPI void SetTargetFPS(int fps); // Set target RLAPI int GetFPS(void); // Returns current FPS RLAPI float GetFrameTime(void); // Returns time in seconds for last frame drawn +RLAPI double GetTime(void); // Return time in seconds + + // Color-related functions RLAPI int GetHexValue(Color color); // Returns hexadecimal value for a Color RLAPI Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value -- cgit v1.2.3 From 2affac820e64393ac4294c53c1a7f47742e8dec9 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 14 Dec 2017 11:52:45 +0100 Subject: make raylib not clash with windows-header --- src/core.c | 10 +++++----- src/raylib.h | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 011e0d76..8e26d2b1 100644 --- a/src/core.c +++ b/src/core.c @@ -413,7 +413,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) // Initialize window and OpenGL context // NOTE: data parameter could be used to pass any kind of required data to the initialization -void InitWindow(int width, int height, void *data) +void InitRLWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -477,7 +477,7 @@ void InitWindow(int width, int height, void *data) #if defined(PLATFORM_ANDROID) // Initialize window and OpenGL context (and Android activity) // NOTE: data parameter could be used to pass any kind of required data to the initialization -void InitWindow(int width, int height, void *data) +void InitRLWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -538,7 +538,7 @@ void InitWindow(int width, int height, void *data) #endif // Close window and unload OpenGL context -void CloseWindow(void) +void CloseRLWindow(void) { #if defined(SUPPORT_GIF_RECORDING) if (gifRecording) @@ -715,7 +715,7 @@ int GetScreenHeight(void) } // Show mouse cursor -void ShowCursor() +void ShowRLCursor() { #if defined(PLATFORM_DESKTOP) #if defined(__linux__) @@ -728,7 +728,7 @@ void ShowCursor() } // Hides mouse cursor -void HideCursor() +void HideRLCursor() { #if defined(PLATFORM_DESKTOP) #if defined(__linux__) diff --git a/src/raylib.h b/src/raylib.h index 0a9eab46..16595b5b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -682,8 +682,8 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Window-related functions -RLAPI void InitWindow(int width, int height, void *data); // Initialize window and OpenGL context -RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI void InitRLWindow(int width, int height, void *data); // Initialize window and OpenGL context +RLAPI void CloseRLWindow(void); // Close window and unload OpenGL context RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI bool IsWindowMinimized(void); // Check if window has been minimized (or lost focus) RLAPI void ToggleFullscreen(void); // Toggle fullscreen mode (only PLATFORM_DESKTOP) @@ -696,8 +696,8 @@ RLAPI int GetScreenWidth(void); // Get current RLAPI int GetScreenHeight(void); // Get current screen height // Cursor-related functions -RLAPI void ShowCursor(void); // Shows cursor -RLAPI void HideCursor(void); // Hides cursor +RLAPI void ShowRLCursor(void); // Shows cursor +RLAPI void HideRLCursor(void); // Hides cursor RLAPI bool IsCursorHidden(void); // Check if cursor is not visible RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) RLAPI void DisableCursor(void); // Disables cursor (lock cursor) -- cgit v1.2.3 From 53ad53d05127ac083f7439c5c2e1c2ab5e73e1c0 Mon Sep 17 00:00:00 2001 From: Ray San Date: Fri, 15 Dec 2017 13:44:31 +0100 Subject: Manually review previous PR --- src/core.c | 21 ++++++++++----------- src/raylib.h | 14 ++++++-------- src/raymath.h | 12 ++++++++---- src/rlgl.c | 6 ++++++ src/rlgl.h | 10 ++++------ src/textures.c | 5 ++--- 6 files changed, 36 insertions(+), 32 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 8e26d2b1..ec7db978 100644 --- a/src/core.c +++ b/src/core.c @@ -153,9 +153,8 @@ //#define GLFW_DLL // Using GLFW DLL on Windows -> No, we use static version! #if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) - // NOTE: Those functions require linking with winmm library - unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); - unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); + __stdcall unsigned int timeBeginPeriod(unsigned int uPeriod); + __stdcall unsigned int timeEndPeriod(unsigned int uPeriod); #endif #endif @@ -352,7 +351,7 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads default fo static void InitGraphicsDevice(int width, int height); // Initialize graphics device static void SetupFramebufferSize(int displayWidth, int displayHeight); static void InitTimer(void); // Initialize timer - double GetTime(void); // Returns time since InitTimer() was run +static double GetTime(void); // Returns time since InitTimer() was run static void Wait(float ms); // Wait for some milliseconds (stop program execution) static bool GetKeyStatus(int key); // Returns if a key has been pressed static bool GetMouseButtonStatus(int button); // Returns if a mouse button has been pressed @@ -413,7 +412,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) // Initialize window and OpenGL context // NOTE: data parameter could be used to pass any kind of required data to the initialization -void InitRLWindow(int width, int height, void *data) +void InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -477,7 +476,7 @@ void InitRLWindow(int width, int height, void *data) #if defined(PLATFORM_ANDROID) // Initialize window and OpenGL context (and Android activity) // NOTE: data parameter could be used to pass any kind of required data to the initialization -void InitRLWindow(int width, int height, void *data) +void InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -538,7 +537,7 @@ void InitRLWindow(int width, int height, void *data) #endif // Close window and unload OpenGL context -void CloseRLWindow(void) +void CloseWindow(void) { #if defined(SUPPORT_GIF_RECORDING) if (gifRecording) @@ -715,7 +714,7 @@ int GetScreenHeight(void) } // Show mouse cursor -void ShowRLCursor() +void ShowCursor() { #if defined(PLATFORM_DESKTOP) #if defined(__linux__) @@ -728,7 +727,7 @@ void ShowRLCursor() } // Hides mouse cursor -void HideRLCursor() +void HideCursor() { #if defined(PLATFORM_DESKTOP) #if defined(__linux__) @@ -1135,7 +1134,7 @@ void SetConfigFlags(char flags) // Takes a screenshot of current screen (saved a .png) void TakeScreenshot(const char *fileName) { -#if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)) && defined(SUPPORT_SAVE_PNG) +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) unsigned char *imgData = rlReadScreenPixels(renderWidth, renderHeight); SavePNG(fileName, imgData, renderWidth, renderHeight, 4); // Save image as PNG free(imgData); @@ -2120,7 +2119,7 @@ static void InitTimer(void) } // Get current time measure (in seconds) since InitTimer() -double GetTime(void) +static double GetTime(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) return glfwGetTime(); diff --git a/src/raylib.h b/src/raylib.h index 16595b5b..a581929e 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -682,8 +682,8 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Window-related functions -RLAPI void InitRLWindow(int width, int height, void *data); // Initialize window and OpenGL context -RLAPI void CloseRLWindow(void); // Close window and unload OpenGL context +RLAPI void InitWindow(int width, int height, void *data); // Initialize window and OpenGL context +RLAPI void CloseWindow(void); // Close window and unload OpenGL context RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI bool IsWindowMinimized(void); // Check if window has been minimized (or lost focus) RLAPI void ToggleFullscreen(void); // Toggle fullscreen mode (only PLATFORM_DESKTOP) @@ -696,8 +696,8 @@ RLAPI int GetScreenWidth(void); // Get current RLAPI int GetScreenHeight(void); // Get current screen height // Cursor-related functions -RLAPI void ShowRLCursor(void); // Shows cursor -RLAPI void HideRLCursor(void); // Hides cursor +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor RLAPI bool IsCursorHidden(void); // Check if cursor is not visible RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) RLAPI void DisableCursor(void); // Disables cursor (lock cursor) @@ -722,9 +722,7 @@ RLAPI Matrix GetCameraMatrix(Camera camera); // Returns cam RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) RLAPI int GetFPS(void); // Returns current FPS RLAPI float GetFrameTime(void); // Returns time in seconds for last frame drawn - -RLAPI double GetTime(void); // Return time in seconds - +//RLAPI double GetCurrentTime(void); // Return current time in seconds // Color-related functions RLAPI int GetHexValue(Color color); // Returns hexadecimal value for a Color @@ -1049,7 +1047,7 @@ RLAPI void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int RLAPI void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) RLAPI void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) RLAPI void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) -RLAPI Matrix GetMatrixModelview(); +RLAPI Matrix GetMatrixModelview(); // Get internal modelview matrix // Texture maps generation (PBR) // NOTE: Required shaders should be provided diff --git a/src/raymath.h b/src/raymath.h index 3d95a089..c29c6b95 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -227,13 +227,15 @@ RMDEF float Clamp(float value, float min, float max) //---------------------------------------------------------------------------------- // Vector with components value 0.0f -RMDEF Vector2 Vector2Zero(void) { +RMDEF Vector2 Vector2Zero(void) +{ Vector2 tmp = {0.0f, 0.0f}; return tmp; } // Vector with components value 1.0f -RMDEF Vector2 Vector2One(void) { +RMDEF Vector2 Vector2One(void) +{ Vector2 tmp = {1.0f, 1.0f}; return tmp; } @@ -312,13 +314,15 @@ RMDEF void Vector2Normalize(Vector2 *v) //---------------------------------------------------------------------------------- // Vector with components value 0.0f -RMDEF Vector3 Vector3Zero(void) { +RMDEF Vector3 Vector3Zero(void) +{ Vector3 tmp = { 0.0f, 0.0f, 0.0f }; return tmp; } // Vector with components value 1.0f -RMDEF Vector3 Vector3One(void) { +RMDEF Vector3 Vector3One(void) +{ Vector3 tmp = { 1.0f, 1.0f, 1.0f }; return tmp; } diff --git a/src/rlgl.c b/src/rlgl.c index 713646ac..96932686 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -1293,6 +1293,12 @@ int rlGetVersion(void) #endif } +// Set debug marker +void rlSetDebugMarker(const char *text) +{ + if(debugMarkerSupported) glInsertEventMarkerEXT(0, text); // 0 terminated string +} + // Load OpenGL extensions // NOTE: External loader function could be passed as a pointer void rlLoadExtensions(void *loader) diff --git a/src/rlgl.h b/src/rlgl.h index 93afb44e..2437487d 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -422,6 +422,7 @@ void rlglClose(void); // De-inititialize rlgl (buffers void rlglDraw(void); // Update and Draw default buffers (lines, triangles, quads) int rlGetVersion(void); // Returns current OpenGL version +void rlDebugSetMarker(const char *text); // Set debug marker for analysis void rlLoadExtensions(void *loader); // Load OpenGL extensions Vector3 rlUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates @@ -440,9 +441,6 @@ void rlUpdateMesh(Mesh mesh, int buffer, int numVertex); // Update ve void rlDrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform void rlUnloadMesh(Mesh *mesh); // Unload mesh data from CPU and GPU -// Debug Marker for Analysis -void rlSetMarker(const char *text); - // NOTE: There is a set of shader related functions that are available to end user, // to avoid creating function wrappers through core module, they have been directly declared in raylib.h @@ -462,9 +460,9 @@ int GetShaderLocation(Shader shader, const char *uniformName); // G void SetShaderValue(Shader shader, int uniformLoc, const float *value, int size); // Set shader uniform value (float) void SetShaderValuei(Shader shader, int uniformLoc, const int *value, int size); // Set shader uniform value (int) void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) -void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) -void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) -Matrix GetMatrixModelview(); +void SetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +void SetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +Matrix GetMatrixModelview(); // Get internal modelview matrix // Texture maps generation (PBR) diff --git a/src/textures.c b/src/textures.c index 55749118..01b8e4fc 100644 --- a/src/textures.c +++ b/src/textures.c @@ -554,13 +554,12 @@ void UpdateTexture(Texture2D texture, const void *pixels) // Save image to a PNG file void SaveImageAs(const char* fileName, Image image) { -#if (defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI)) && defined(SUPPORT_SAVE_PNG) - unsigned char* imgData = (unsigned char*)GetImageData(image); // this works since Color is just a container for the RGBA values + // NOTE: Getting Color array as RGBA unsigned char values + unsigned char* imgData = (unsigned char*)GetImageData(image); SavePNG(fileName, imgData, image.width, image.height, 4); free(imgData); TraceLog(LOG_INFO, "Image saved: %s", fileName); -#endif } // Convert image data to desired format -- cgit v1.2.3 From 5290390494c23055c689c183aea6d88039b0d5fb Mon Sep 17 00:00:00 2001 From: Ray San Date: Tue, 19 Dec 2017 14:06:54 +0100 Subject: Expose GetTime() function to users Monotonic time since InitWindow() could be retrieved with this function. --- src/core.c | 57 +++++++++++++++++++++++++++++---------------------------- src/gestures.h | 6 +++--- src/raylib.h | 2 +- 3 files changed, 33 insertions(+), 32 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 8a2e40b3..6468539f 100644 --- a/src/core.c +++ b/src/core.c @@ -150,11 +150,11 @@ #include // which are required for hiding mouse #endif //#include // OpenGL functions (GLFW3 already includes gl.h) - //#define GLFW_DLL // Using GLFW DLL on Windows -> No, we use static version! - + #if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) - __stdcall unsigned int timeBeginPeriod(unsigned int uPeriod); - __stdcall unsigned int timeEndPeriod(unsigned int uPeriod); + // NOTE: Those functions require linking with winmm library + unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); + unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #endif #endif @@ -351,7 +351,6 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads default fo static void InitGraphicsDevice(int width, int height); // Initialize graphics device static void SetupFramebufferSize(int displayWidth, int displayHeight); static void InitTimer(void); // Initialize timer -static double GetTime(void); // Returns time since InitTimer() was run static void Wait(float ms); // Wait for some milliseconds (stop program execution) static bool GetKeyStatus(int key); // Returns if a key has been pressed static bool GetMouseButtonStatus(int button); // Returns if a mouse button has been pressed @@ -421,6 +420,9 @@ void InitWindow(int width, int height, void *data) // Init graphics device (display device and OpenGL context) InitGraphicsDevice(width, height); + + // Init hi-res timer + InitTimer(); #if defined(SUPPORT_DEFAULT_FONT) // Load default font @@ -428,9 +430,6 @@ void InitWindow(int width, int height, void *data) LoadDefaultFont(); #endif - // Init hi-res timer - InitTimer(); - #if defined(PLATFORM_RPI) // Init raw input system InitMouse(); // Mouse init @@ -786,7 +785,7 @@ void ClearBackground(Color color) // Setup canvas (framebuffer) to start drawing void BeginDrawing(void) { - currentTime = GetTime(); // Number of elapsed seconds since InitTimer() was called + currentTime = GetTime(); // Number of elapsed seconds since InitTimer() updateTime = currentTime - previousTime; previousTime = currentTime; @@ -1060,6 +1059,24 @@ float GetFrameTime(void) return (float)frameTime; } +// Get elapsed time measure in seconds since InitTimer() +// NOTE: On PLATFORM_DESKTOP InitTimer() is called on InitWindow() +// NOTE: On PLATFORM_DESKTOP, timer is initialized on glfwInit() +double GetTime(void) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + return glfwGetTime(); // Elapsed time since glfwInit() +#endif + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; + + return (double)(time - baseTime)*1e-9; // Elapsed time since InitTimer() +#endif +} + // Converts Color to float array and normalizes float *ColorToFloat(Color color) { @@ -2118,22 +2135,6 @@ static void InitTimer(void) previousTime = GetTime(); // Get time as double } -// Get elapsed time measure (in seconds) -static double GetTime(void) -{ -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) - return glfwGetTime(); // Elapsed time since glfwInit() -#endif - -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - uint64_t time = (uint64_t)ts.tv_sec*1000000000LLU + (uint64_t)ts.tv_nsec; - - return (double)(time - baseTime)*1e-9; // Elapsed time since InitTimer() -#endif -} - // Wait for some milliseconds (stop program execution) // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could // take longer than expected... for that reason we use the busy wait loop @@ -2602,6 +2603,9 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) { // Init graphics device (display device and OpenGL context) InitGraphicsDevice(screenWidth, screenHeight); + + // Init hi-res timer + InitTimer(); #if defined(SUPPORT_DEFAULT_FONT) // Load default font @@ -2624,9 +2628,6 @@ static void AndroidCommandCallback(struct android_app *app, int32_t cmd) } */ - // Init hi-res timer - InitTimer(); - // raylib logo appearing animation (if enabled) if (showLogo) { diff --git a/src/gestures.h b/src/gestures.h index 68bdc11b..58670ef5 100644 --- a/src/gestures.h +++ b/src/gestures.h @@ -522,7 +522,7 @@ static double GetCurrentTime(void) #if defined(_WIN32) unsigned long long int clockFrequency, currentTime; - QueryPerformanceFrequency(&clockFrequency); + QueryPerformanceFrequency(&clockFrequency); // BE CAREFUL: Costly operation! QueryPerformanceCounter(¤tTime); time = (double)currentTime/clockFrequency*1000.0f; // Time in miliseconds @@ -538,8 +538,8 @@ static double GetCurrentTime(void) #endif #if defined(__APPLE__) - //#define CLOCK_REALTIME CALENDAR_CLOCK - //#define CLOCK_MONOTONIC SYSTEM_CLOCK + //#define CLOCK_REALTIME CALENDAR_CLOCK // returns UTC time since 1970-01-01 + //#define CLOCK_MONOTONIC SYSTEM_CLOCK // returns the time since boot time clock_serv_t cclock; mach_timespec_t now; diff --git a/src/raylib.h b/src/raylib.h index a581929e..4100cee1 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -722,7 +722,7 @@ RLAPI Matrix GetCameraMatrix(Camera camera); // Returns cam RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) RLAPI int GetFPS(void); // Returns current FPS RLAPI float GetFrameTime(void); // Returns time in seconds for last frame drawn -//RLAPI double GetCurrentTime(void); // Return current time in seconds +RLAPI double GetTime(void); // Returns elapsed time in seconds since InitWindow() // Color-related functions RLAPI int GetHexValue(Color color); // Returns hexadecimal value for a Color -- cgit v1.2.3 From b63ffcfa0f7dcedac190a49ec48fd71d7fe2faf1 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 20 Dec 2017 00:34:31 +0100 Subject: Some code tweaks Audio module requires a complete formatting review.... --- src/audio.c | 31 +++++++++++++++++-------------- src/audio.h | 25 ++++++++++++++++--------- src/raylib.h | 32 ++++++++++---------------------- 3 files changed, 43 insertions(+), 45 deletions(-) (limited to 'src/raylib.h') diff --git a/src/audio.c b/src/audio.c index 15f50a24..7a8e185b 100644 --- a/src/audio.c +++ b/src/audio.c @@ -153,7 +153,12 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef enum { MUSIC_AUDIO_OGG = 0, MUSIC_AUDIO_FLAC, MUSIC_MODULE_XM, MUSIC_MODULE_MOD } MusicContextType; +typedef enum { + MUSIC_AUDIO_OGG = 0, + MUSIC_AUDIO_FLAC, + MUSIC_MODULE_XM, + MUSIC_MODULE_MOD +} MusicContextType; // Music type (file streaming from memory) typedef struct MusicData { @@ -179,7 +184,13 @@ typedef struct MusicData { } MusicData; #if defined(AUDIO_STANDALONE) -typedef enum { LOG_INFO = 0, LOG_ERROR, LOG_WARNING, LOG_DEBUG, LOG_OTHER } TraceLogType; +typedef enum { + LOG_INFO = 0, + LOG_ERROR, + LOG_WARNING, + LOG_DEBUG, + LOG_OTHER +} TraceLogType; #endif //---------------------------------------------------------------------------------- @@ -215,9 +226,8 @@ void TraceLog(int msgType, const char *text, ...); // Show trace lo typedef enum { AUDIO_BUFFER_USAGE_STATIC = 0, AUDIO_BUFFER_USAGE_STREAM } AudioBufferUsage; -typedef struct AudioBuffer AudioBuffer; -struct AudioBuffer -{ +// Audio buffer structure +typedef struct AudioBuffer { mal_dsp dsp; // For format conversion. float volume; float pitch; @@ -231,10 +241,9 @@ struct AudioBuffer AudioBuffer* next; AudioBuffer* prev; unsigned char buffer[1]; -}; - -void StopAudioBuffer(AudioBuffer* audioBuffer); +} AudioBuffer; +void StopAudioBuffer(AudioBuffer *audioBuffer); static mal_context context; static mal_device device; @@ -400,12 +409,6 @@ void InitAudioDevice(void) // Device. Using the default device. Format is floating point because it simplifies mixing. mal_device_config deviceConfig = mal_device_config_init(DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, OnSendAudioDataToDevice); - // Special case for PLATFORM_RPI. -//#if defined(PLATFORM_RPI) -// deviceConfig.alsa.noMMap = MAL_TRUE; -// deviceConfig.bufferSizeInFrames = 2048; -//#endif - result = mal_device_init(&context, mal_device_type_playback, NULL, &deviceConfig, NULL, &device); if (result != MAL_SUCCESS) { diff --git a/src/audio.h b/src/audio.h index 48ef7403..4c9faf25 100644 --- a/src/audio.h +++ b/src/audio.h @@ -81,9 +81,11 @@ typedef struct Wave { // Sound source type typedef struct Sound { - unsigned int source; // OpenAL audio source id - unsigned int buffer; // OpenAL audio buffer id - int format; // OpenAL audio format specifier + void *audioBuffer; // Pointer to internal data used by the audio system + + unsigned int source; // Audio source id + unsigned int buffer; // Audio buffer id + int format; // Audio format specifier } Sound; // Music type (file streaming from memory) @@ -97,9 +99,11 @@ typedef struct AudioStream { unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo) - int format; // OpenAL audio format specifier - unsigned int source; // OpenAL audio source id - unsigned int buffers[2]; // OpenAL audio buffers (double buffering) + void *audioBuffer; // Pointer to internal data used by the audio system. + + int format; // Audio format specifier + unsigned int source; // Audio source id + unsigned int buffers[2]; // Audio buffers (double buffering) } AudioStream; #ifdef __cplusplus @@ -147,12 +151,12 @@ void ResumeMusicStream(Music music); // Resume playin bool IsMusicPlaying(Music music); // Check if music is playing void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -void SetMusicLoopCount(Music music, float count); // Set music loop count (loop repeats) +void SetMusicLoopCount(Music music, int count); // Set music loop count (loop repeats) float GetMusicTimeLength(Music music); // Get music time length (in seconds) float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) -// Raw audio stream functions -AudioStream InitAudioStream(unsigned int sampleRate, +// AudioStream management functions +AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Init audio stream (to stream raw audio pcm data) void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount); // Update audio stream buffers with data @@ -161,7 +165,10 @@ bool IsAudioBufferProcessed(AudioStream stream); // Check if any void PlayAudioStream(AudioStream stream); // Play audio stream void PauseAudioStream(AudioStream stream); // Pause audio stream void ResumeAudioStream(AudioStream stream); // Resume audio stream +bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing void StopAudioStream(AudioStream stream); // Stop audio stream +void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) #ifdef __cplusplus } diff --git a/src/raylib.h b/src/raylib.h index 4100cee1..b0ff1fc1 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -470,11 +470,11 @@ typedef struct Wave { // Sound source type typedef struct Sound { - void* audioBuffer; // A pointer to internal data used by the audio system. + void *audioBuffer; // Pointer to internal data used by the audio system - unsigned int source; // OpenAL audio source id - unsigned int buffer; // OpenAL audio buffer id - int format; // OpenAL audio format specifier + unsigned int source; // Audio source id + unsigned int buffer; // Audio buffer id + int format; // Audio format specifier } Sound; // Music type (file streaming from memory) @@ -488,11 +488,11 @@ typedef struct AudioStream { unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo) - void* audioBuffer; // A pointer to internal data used by the audio system. + void *audioBuffer; // Pointer to internal data used by the audio system. - int format; // OpenAL audio format specifier - unsigned int source; // OpenAL audio source id - unsigned int buffers[2]; // OpenAL audio buffers (double buffering) + int format; // Audio format specifier + unsigned int source; // Audio source id + unsigned int buffers[2]; // Audio buffers (double buffering) } AudioStream; // Head-Mounted-Display device parameters @@ -656,18 +656,6 @@ typedef enum { HMD_SONY_PSVR } VrDeviceType; -// RRESData type -typedef enum { - RRES_TYPE_RAW = 0, - RRES_TYPE_IMAGE, - RRES_TYPE_WAVE, - RRES_TYPE_VERTEX, - RRES_TYPE_TEXT, - RRES_TYPE_FONT_IMAGE, - RRES_TYPE_FONT_CHARDATA, // CharInfo data array - RRES_TYPE_DIRECTORY -} RRESDataType; - #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -1131,8 +1119,8 @@ RLAPI void PauseAudioStream(AudioStream stream); // Pause a RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream -RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) -RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) #ifdef __cplusplus } -- cgit v1.2.3 From e517d8fd168b361719c214d57463ef3ee461d425 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 24 Dec 2017 16:47:33 +0100 Subject: Added function SetTraceLogTypes() Trace log messages could be configured with this function to select wich ones are shown --- src/core.c | 4 ++-- src/raylib.h | 13 +++++++------ src/utils.c | 38 +++++++++++++++++++++++--------------- 3 files changed, 32 insertions(+), 23 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 05c794ad..7d7e188c 100644 --- a/src/core.c +++ b/src/core.c @@ -329,7 +329,7 @@ static double updateTime, drawTime; // Time measures for update and draw static double frameTime = 0.0; // Time measure for one frame static double targetTime = 0.0; // Desired time for one frame, if 0 not applied -static char configFlags = 0; // Configuration flags (bit based) +static unsigned char configFlags = 0; // Configuration flags (bit based) static bool showLogo = false; // Track if showing logo at init is enabled #if defined(SUPPORT_GIF_RECORDING) @@ -1138,7 +1138,7 @@ void ShowLogo(void) } // Setup window configuration flags (view FLAGS) -void SetConfigFlags(char flags) +void SetConfigFlags(unsigned char flags) { configFlags = flags; diff --git a/src/raylib.h b/src/raylib.h index b0ff1fc1..d3d58afa 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -514,11 +514,11 @@ typedef struct VrDeviceInfo { //---------------------------------------------------------------------------------- // Trace log type typedef enum { - LOG_INFO = 0, - LOG_WARNING, - LOG_ERROR, - LOG_DEBUG, - LOG_OTHER + LOG_INFO = 1, + LOG_WARNING = 2, + LOG_ERROR = 4, + LOG_DEBUG = 8, + LOG_OTHER = 16 } LogType; // Shader location point type @@ -727,7 +727,8 @@ RLAPI Matrix MatrixIdentity(void); // Returns ide // Misc. functions RLAPI void ShowLogo(void); // Activate raylib logo at startup (can be done with flags) -RLAPI void SetConfigFlags(char flags); // Setup window configuration flags (view FLAGS) +RLAPI void SetConfigFlags(unsigned char flags); // Setup window configuration flags (view FLAGS) +RLAPI void SetTraceLogTypes(unsigned char types); // Enable trace log message types (bit flags based) RLAPI void TraceLog(int logType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (saved a .png) RLAPI int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) diff --git a/src/utils.c b/src/utils.c index 72d4f2da..b262118c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -16,9 +16,6 @@ * Show TraceLog() output messages * NOTE: By default LOG_DEBUG traces not shown * -* #define SUPPORT_TRACELOG_DEBUG -* Show TraceLog() LOG_DEBUG messages -* * DEPENDENCIES: * stb_image_write - BMP/PNG writting functions * @@ -45,7 +42,6 @@ **********************************************************************************************/ #define SUPPORT_TRACELOG // Output tracelog messages -//#define SUPPORT_TRACELOG_DEBUG // Avoid LOG_DEBUG messages tracing #include "raylib.h" // WARNING: Required for: LogType enum #include "utils.h" @@ -63,12 +59,16 @@ #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) #define STB_IMAGE_WRITE_IMPLEMENTATION - #include "external/stb_image_write.h" // Required for: stbi_write_bmp(), stbi_write_png() + #include "external/stb_image_write.h" // Required for: stbi_write_bmp(), stbi_write_png() #endif //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- + +// Log types messages supported flags (bit based) +static unsigned char logTypeFlags = LOG_INFO | LOG_WARNING | LOG_ERROR; + #if defined(PLATFORM_ANDROID) AAssetManager *assetManager; #endif @@ -87,16 +87,17 @@ static int android_close(void *cookie); // Module Functions Definition - Utilities //---------------------------------------------------------------------------------- +// Enable trace log message types (bit flags based) +void SetTraceLogTypes(unsigned char types) +{ + logTypeFlags = types; +} + // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) void TraceLog(int msgType, const char *text, ...) { #if defined(SUPPORT_TRACELOG) static char buffer[128]; - int traceDebugMsgs = 0; - -#if defined(SUPPORT_TRACELOG_DEBUG) - traceDebugMsgs = 1; -#endif switch(msgType) { @@ -116,14 +117,21 @@ void TraceLog(int msgType, const char *text, ...) #if defined(PLATFORM_ANDROID) switch(msgType) { - case LOG_INFO: __android_log_vprint(ANDROID_LOG_INFO, "raylib", buffer, args); break; - case LOG_ERROR: __android_log_vprint(ANDROID_LOG_ERROR, "raylib", buffer, args); break; - case LOG_WARNING: __android_log_vprint(ANDROID_LOG_WARN, "raylib", buffer, args); break; - case LOG_DEBUG: if (traceDebugMsgs) __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", buffer, args); break; + case LOG_INFO: if (logTypeFlags & LOG_INFO) __android_log_vprint(ANDROID_LOG_INFO, "raylib", buffer, args); break; + case LOG_WARNING: if (logTypeFlags & LOG_WARNING) __android_log_vprint(ANDROID_LOG_WARN, "raylib", buffer, args); break; + case LOG_ERROR: if (logTypeFlags & LOG_ERROR) __android_log_vprint(ANDROID_LOG_ERROR, "raylib", buffer, args); break; + case LOG_DEBUG: if (logTypeFlags & LOG_DEBUG) __android_log_vprint(ANDROID_LOG_DEBUG, "raylib", buffer, args); break; default: break; } #else - if ((msgType != LOG_DEBUG) || ((msgType == LOG_DEBUG) && (traceDebugMsgs))) vprintf(buffer, args); + switch(msgType) + { + case LOG_INFO: if (logTypeFlags & LOG_INFO) vprintf(buffer, args); break; + case LOG_WARNING: if (logTypeFlags & LOG_WARNING) vprintf(buffer, args); break; + case LOG_ERROR: if (logTypeFlags & LOG_ERROR) vprintf(buffer, args); break; + case LOG_DEBUG: if (logTypeFlags & LOG_DEBUG) vprintf(buffer, args); break; + default: break; + } #endif va_end(args); -- cgit v1.2.3 From 0bd06eec5100da648e1d3dc9bd0fd796b240e079 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 24 Dec 2017 16:49:54 +0100 Subject: Renamed function to SetTraceLog() I think is clearer this way... --- src/raylib.h | 2 +- src/utils.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index d3d58afa..75dafa42 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -728,7 +728,7 @@ RLAPI Matrix MatrixIdentity(void); // Returns ide // Misc. functions RLAPI void ShowLogo(void); // Activate raylib logo at startup (can be done with flags) RLAPI void SetConfigFlags(unsigned char flags); // Setup window configuration flags (view FLAGS) -RLAPI void SetTraceLogTypes(unsigned char types); // Enable trace log message types (bit flags based) +RLAPI void SetTraceLog(unsigned char types); // Enable trace log message types (bit flags based) RLAPI void TraceLog(int logType, const char *text, ...); // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (saved a .png) RLAPI int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) diff --git a/src/utils.c b/src/utils.c index b262118c..743ef98c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -88,7 +88,7 @@ static int android_close(void *cookie); //---------------------------------------------------------------------------------- // Enable trace log message types (bit flags based) -void SetTraceLogTypes(unsigned char types) +void SetTraceLog(unsigned char types) { logTypeFlags = types; } -- cgit v1.2.3 From b19e155b3419b50bd546632f9b77793bc95824d1 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Thu, 28 Dec 2017 17:58:37 +0100 Subject: Support UNCOMPRESSED_R32G32B32A32 texture format --- src/raylib.h | 1 + src/rlgl.c | 4 +++- src/rlgl.h | 1 + src/textures.c | 32 ++++++++++++++++++++++++++++++-- 4 files changed, 35 insertions(+), 3 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 75dafa42..e71ff47b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -582,6 +582,7 @@ typedef enum { UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) UNCOMPRESSED_R8G8B8A8, // 32 bpp UNCOMPRESSED_R32G32B32, // 32 bit per channel (float) - HDR + UNCOMPRESSED_R32G32B32A32, // 32 bit per channel (float) - HDR COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) COMPRESSED_DXT3_RGBA, // 8 bpp diff --git a/src/rlgl.c b/src/rlgl.c index 8f8a97c1..58e9187d 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -1421,6 +1421,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi #endif #if defined(GRAPHICS_API_OPENGL_ES2) case UNCOMPRESSED_R32G32B32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; // NOTE: Requires extension OES_texture_float + case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; // NOTE: Requires extension OES_texture_float case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, mipmapCount); break; case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, mipmapCount); break; case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, mipmapCount); break; // NOTE: Not supported by WebGL @@ -1468,13 +1469,14 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } break; - + // Supported texture formats: https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break; case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break; case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break; case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break; case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; case UNCOMPRESSED_R32G32B32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; + case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, mipmapCount); break; case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, mipmapCount); break; case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, mipmapCount); break; diff --git a/src/rlgl.h b/src/rlgl.h index d3982eb7..0e4a5d74 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -260,6 +260,7 @@ typedef unsigned char byte; UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) UNCOMPRESSED_R8G8B8A8, // 32 bpp UNCOMPRESSED_R32G32B32, // 32 bit per channel (float) - HDR + UNCOMPRESSED_R32G32B32A32, // 32 bit per channel (float) - HDR COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) COMPRESSED_DXT3_RGBA, // 8 bpp diff --git a/src/textures.c b/src/textures.c index 9c41ae7d..34f2c323 100644 --- a/src/textures.c +++ b/src/textures.c @@ -227,10 +227,11 @@ Image LoadImage(const char *fileName) image.mipmaps = 1; if (imgBpp == 3) image.format = UNCOMPRESSED_R32G32B32; + else if (imgBpp == 4) image.format = UNCOMPRESSED_R32G32B32A32; else { // TODO: Support different number of channels at 32 bit float - TraceLog(LOG_WARNING, "[%s] Image fileformat not supported (only 3 channel 32 bit floats)", fileName); + TraceLog(LOG_WARNING, "[%s] Image fileformat not supported", fileName); UnloadImage(image); } } @@ -329,6 +330,7 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int case UNCOMPRESSED_R4G4B4A4: image.data = (unsigned short *)malloc(size); break; // 16 bpp (4 bit alpha) case UNCOMPRESSED_R8G8B8A8: image.data = (unsigned char *)malloc(size*4); size *= 4; break; // 32 bpp case UNCOMPRESSED_R32G32B32: image.data = (float *)malloc(size*12); size *= 12; break; // 4 byte per channel (12 byte) + case UNCOMPRESSED_R32G32B32A32: image.data = (float *)malloc(size*16); size *= 16; break; // 4 byte per channel (16 byte) default: TraceLog(LOG_WARNING, "Image format not suported"); break; } @@ -679,7 +681,7 @@ void ImageFormat(Image *image, int newFormat) { image->data = (unsigned char *)malloc(image->width*image->height*4*sizeof(unsigned char)); - for (int i = 0; i < image->width*image->height*4; i += 4) + for (int i = 0; i < image->width*image->height*3; i += 3) { ((unsigned char *)image->data)[i] = pixels[k].r; ((unsigned char *)image->data)[i + 1] = pixels[k].g; @@ -688,6 +690,31 @@ void ImageFormat(Image *image, int newFormat) k++; } } break; + case UNCOMPRESSED_R32G32B32: + { + image->data = (float *)malloc(image->width*image->height*3*sizeof(float)); + + for (int i = 0; i < image->width*image->height*3; i += 3) + { + ((float *)image->data)[i] = (float)pixels[k].r/255.0f; + ((float *)image->data)[i + 1] = (float)pixels[k].g/255.0f; + ((float *)image->data)[i + 2] = (float)pixels[k].b/255.0f; + k++; + } + } break; + case UNCOMPRESSED_R32G32B32A32: + { + image->data = (float *)malloc(image->width*image->height*4*sizeof(float)); + + for (int i = 0; i < image->width*image->height*4; i += 4) + { + ((float *)image->data)[i] = (float)pixels[k].r/255.0f; + ((float *)image->data)[i + 1] = (float)pixels[k].g/255.0f; + ((float *)image->data)[i + 2] = (float)pixels[k].b/255.0f; + ((float *)image->data)[i + 3] = (float)pixels[k].a/255.0f; + k++; + } + } break; default: break; } @@ -806,6 +833,7 @@ Image ImageCopy(Image image) case UNCOMPRESSED_R8G8B8: byteSize *= 3; break; // 24 bpp (3 bytes) case UNCOMPRESSED_R8G8B8A8: byteSize *= 4; break; // 32 bpp (4 bytes) case UNCOMPRESSED_R32G32B32: byteSize *= 12; break; // 4 byte per channel (12 bytes) + case UNCOMPRESSED_R32G32B32A32: byteSize *= 16; break; // 4 byte per channel (16 bytes) case COMPRESSED_DXT3_RGBA: case COMPRESSED_DXT5_RGBA: case COMPRESSED_ETC2_EAC_RGBA: -- cgit v1.2.3 From e1baae02498aab2f4dd84c44732717bb963d18d1 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Mon, 1 Jan 2018 16:54:32 +0100 Subject: Removed function DrawRectangleT() Functionality integrated in DrawRectangle() and selectable with config flag USE_DEFAULT_FONT_TEXTURE --- src/raylib.h | 3 +- src/shapes.c | 180 ++++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 110 insertions(+), 73 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index e71ff47b..3e2dbe0c 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -830,14 +830,13 @@ RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color colo RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors -RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline -RLAPI void DrawRectangleT(int posX, int posY, int width, int height, Color color); // Draw rectangle using text character RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) diff --git a/src/shapes.c b/src/shapes.c index 9f405419..80ba64fa 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -10,6 +10,10 @@ * #define SUPPORT_TRIANGLES_ONLY * Draw shapes using only TRIANGLES, vertex are accumulated in TRIANGLES arrays * +* #define USE_DEFAULT_FONT_TEXTURE +* Draw rectangle shapes using font texture white character instead of default white texture +* Allows drawing rectangles and text with a single draw call, very useful for GUI systems! +* * * LICENSE: zlib/libpng * @@ -245,6 +249,78 @@ void DrawRectangle(int posX, int posY, int width, int height, Color color) DrawRectangleV(position, size, color); } +// Draw a color-filled rectangle (Vector version) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) +void DrawRectangleV(Vector2 position, Vector2 size, Color color) +{ + if (rlGetVersion() == OPENGL_11) + { + rlBegin(RL_TRIANGLES); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlVertex2i(position.x, position.y); + rlVertex2i(position.x, position.y + size.y); + rlVertex2i(position.x + size.x, position.y + size.y); + + rlVertex2i(position.x, position.y); + rlVertex2i(position.x + size.x, position.y + size.y); + rlVertex2i(position.x + size.x, position.y); + rlEnd(); + } + else if ((rlGetVersion() == OPENGL_21) || (rlGetVersion() == OPENGL_33) || (rlGetVersion() == OPENGL_ES_20)) + { +#if defined(USE_DEFAULT_FONT_TEXTURE) + // Draw rectangle using font texture white character + rlEnableTexture(GetDefaultFont().texture.id); + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + rlNormal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rlTexCoord2f((float)GetDefaultFont().chars[95].rec.x/GetDefaultFont().texture.width, + (float)GetDefaultFont().chars[95].rec.y/GetDefaultFont().texture.height); + rlVertex2f(rec.x, rec.y); + + rlTexCoord2f((float)GetDefaultFont().chars[95].rec.x/GetDefaultFont().texture.width, + (float)(GetDefaultFont().chars[95].rec.y + GetDefaultFont().chars[95].rec.height)/GetDefaultFont().texture.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlTexCoord2f((float)(GetDefaultFont().chars[95].rec.x + GetDefaultFont().chars[95].rec.width)/GetDefaultFont().texture.width, + (float)(GetDefaultFont().chars[95].rec.y + GetDefaultFont().chars[95].rec.height)/GetDefaultFont().texture.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlTexCoord2f((float)(GetDefaultFont().chars[95].rec.x + GetDefaultFont().chars[95].rec.width)/GetDefaultFont().texture.width, + (float)GetDefaultFont().chars[95].rec.y/GetDefaultFont().texture.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); + + rlDisableTexture(); +#else + rlEnableTexture(GetTextureDefault().id); // Default white texture + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + rlNormal3f(0.0f, 0.0f, 1.0f); + + rlTexCoord2f(0.0f, 0.0f); + rlVertex2f(position.x, position.y); + + rlTexCoord2f(0.0f, 1.0f); + rlVertex2f(position.x, position.y + size.y); + + rlTexCoord2f(1.0f, 1.0f); + rlVertex2f(position.x + size.x, position.y + size.y); + + rlTexCoord2f(1.0f, 0.0f); + rlVertex2f(position.x + size.x, position.y); + rlEnd(); + + rlDisableTexture(); +#endif + } +} + // Draw a color-filled rectangle void DrawRectangleRec(Rectangle rec, Color color) { @@ -292,6 +368,37 @@ void DrawRectangleGradientH(int posX, int posY, int width, int height, Color col // NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) { +#if defined(USE_DEFAULT_FONT_TEXTURE) + // Draw rectangle using font texture white character + rlEnableTexture(GetDefaultFont().texture.id); + + rlBegin(RL_QUADS); + rlNormal3f(0.0f, 0.0f, 1.0f); + + // NOTE: Default raylib font character 95 is a white square + rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlTexCoord2f((float)GetDefaultFont().chars[95].rec.x/GetDefaultFont().texture.width, + (float)GetDefaultFont().chars[95].rec.y/GetDefaultFont().texture.height); + rlVertex2f(rec.x, rec.y); + + rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlTexCoord2f((float)GetDefaultFont().chars[95].rec.x/GetDefaultFont().texture.width, + (float)(GetDefaultFont().chars[95].rec.y + GetDefaultFont().chars[95].rec.height)/GetDefaultFont().texture.height); + rlVertex2f(rec.x, rec.y + rec.height); + + rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlTexCoord2f((float)(GetDefaultFont().chars[95].rec.x + GetDefaultFont().chars[95].rec.width)/GetDefaultFont().texture.width, + (float)(GetDefaultFont().chars[95].rec.y + GetDefaultFont().chars[95].rec.height)/GetDefaultFont().texture.height); + rlVertex2f(rec.x + rec.width, rec.y + rec.height); + + rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlTexCoord2f((float)(GetDefaultFont().chars[95].rec.x + GetDefaultFont().chars[95].rec.width)/GetDefaultFont().texture.width, + (float)GetDefaultFont().chars[95].rec.y/GetDefaultFont().texture.height); + rlVertex2f(rec.x + rec.width, rec.y); + rlEnd(); + + rlDisableTexture(); +#else rlEnableTexture(GetTextureDefault().id); // Default white texture rlBegin(RL_QUADS); @@ -313,70 +420,9 @@ void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, rlTexCoord2f(1.0f, 0.0f); rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); - - // Draw rectangle using font texture white character - /* - rlTexCoord2f((float)GetDefaultFont().chars[95].rec.x/GetDefaultFont().texture.width, - (float)GetDefaultFont().chars[95].rec.y/GetDefaultFont().texture.height); - rlVertex2f(rec.x, rec.y); - - rlTexCoord2f((float)GetDefaultFont().chars[95].rec.x/GetDefaultFont().texture.width, - (float)(GetDefaultFont().chars[95].rec.y + GetDefaultFont().chars[95].rec.height)/GetDefaultFont().texture.height); - rlVertex2f(rec.x, rec.y + rec.height); - - rlTexCoord2f((float)(GetDefaultFont().chars[95].rec.x + GetDefaultFont().chars[95].rec.width)/GetDefaultFont().texture.width, - (float)(GetDefaultFont().chars[95].rec.y + GetDefaultFont().chars[95].rec.height)/GetDefaultFont().texture.height); - rlVertex2f(rec.x + rec.width, rec.y + rec.height); - - rlTexCoord2f((float)(GetDefaultFont().chars[95].rec.x + GetDefaultFont().chars[95].rec.width)/GetDefaultFont().texture.width, - (float)GetDefaultFont().chars[95].rec.y/GetDefaultFont().texture.height); - rlVertex2f(rec.x + rec.width, rec.y); - */ - - rlDisableTexture(); -} - -// Draw a color-filled rectangle (Vector version) -// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) -void DrawRectangleV(Vector2 position, Vector2 size, Color color) -{ - if (rlGetVersion() == OPENGL_11) - { - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - - rlVertex2i(position.x, position.y); - rlVertex2i(position.x, position.y + size.y); - rlVertex2i(position.x + size.x, position.y + size.y); - - rlVertex2i(position.x, position.y); - rlVertex2i(position.x + size.x, position.y + size.y); - rlVertex2i(position.x + size.x, position.y); - rlEnd(); - } - else if ((rlGetVersion() == OPENGL_21) || (rlGetVersion() == OPENGL_33) || (rlGetVersion() == OPENGL_ES_20)) - { - rlEnableTexture(GetTextureDefault().id); // Default white texture - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); - - rlTexCoord2f(0.0f, 0.0f); - rlVertex2f(position.x, position.y); - - rlTexCoord2f(0.0f, 1.0f); - rlVertex2f(position.x, position.y + size.y); - - rlTexCoord2f(1.0f, 1.0f); - rlVertex2f(position.x + size.x, position.y + size.y); - - rlTexCoord2f(1.0f, 0.0f); - rlVertex2f(position.x + size.x, position.y); - rlEnd(); - - rlDisableTexture(); - } + rlDisableTexture(); +#endif } // Draw rectangle outline @@ -409,14 +455,6 @@ void DrawRectangleLines(int posX, int posY, int width, int height, Color color) } } -// Draw rectangle using text character (char: 127) -// NOTE: Useful to avoid changing to default white texture -void DrawRectangleT(int posX, int posY, int width, int height, Color color) -{ - DrawTexturePro(GetDefaultFont().texture, GetDefaultFont().chars[95].rec, - (Rectangle){ posX, posY, width, height }, (Vector2){ 0, 0 }, 0.0f, color); -} - // Draw a triangle void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { -- cgit v1.2.3 From 1a82e1ab26d3db6f9c9c2f633df1e2980524ebfa Mon Sep 17 00:00:00 2001 From: raysan5 Date: Tue, 2 Jan 2018 02:26:05 +0100 Subject: Added function GetFileName() Review comments --- src/core.c | 13 ++++++++++++- src/raylib.h | 7 ++++--- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 60bc58b8..371a22ee 100644 --- a/src/core.c +++ b/src/core.c @@ -1174,7 +1174,7 @@ bool IsFileExtension(const char *fileName, const char *ext) return result; } -// Get the extension for a filename +// Get pointer to extension for a filename string const char *GetExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); @@ -1184,6 +1184,17 @@ const char *GetExtension(const char *fileName) return (dot + 1); } +// Get pointer to filename for a path string +const char *GetFileName(const char *filePath) +{ + const char *fileName = strrchr(filePath, '\\'); + + if (!fileName || fileName == filePath) return filePath; + + return fileName + 1; +} + + // Get directory for a given fileName (with path) const char *GetDirectoryPath(const char *fileName) { diff --git a/src/raylib.h b/src/raylib.h index 3e2dbe0c..75bd883d 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -736,9 +736,10 @@ RLAPI int GetRandomValue(int min, int max); // Returns a r // Files management functions RLAPI bool IsFileExtension(const char *fileName, const char *ext);// Check file extension -RLAPI const char *GetExtension(const char *fileName); // Get file extension -RLAPI const char *GetDirectoryPath(const char *fileName); // Get directory for a given fileName (with path) -RLAPI const char *GetWorkingDirectory(void); // Get current working directory +RLAPI const char *GetExtension(const char *fileName); // Get pointer to extension for a filename string +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetDirectoryPath(const char *fileName); // Get full path for a given fileName (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) RLAPI bool ChangeDirectory(const char *dir); // Change working directory, returns true if success RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI char **GetDroppedFiles(int *count); // Get dropped files names -- cgit v1.2.3 From 7fa28611607c210789054056095588b17c5bdaac Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 6 Jan 2018 02:50:20 +0100 Subject: Added function: GetPixelDataSize() Just found I need that function... --- src/raylib.h | 1 + src/textures.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 75bd883d..01699ae7 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -869,6 +869,7 @@ RLAPI void UnloadImage(Image image); RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) RLAPI Color *GetImageData(Image image); // Get pixel data from image as a Color struct array +RLAPI int GetImageDataSize(Image image); // Get image data size in bytes RLAPI Image GetTextureData(Texture2D texture); // Get pixel data from GPU texture and return an Image RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data RLAPI void SaveImageAs(const char *fileName, Image image); // Save image to a PNG file diff --git a/src/textures.c b/src/textures.c index fd2fae0d..9abee62b 100644 --- a/src/textures.c +++ b/src/textures.c @@ -515,6 +515,43 @@ Color *GetImageData(Image image) return pixels; } +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +int GetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case UNCOMPRESSED_GRAY_ALPHA: + case UNCOMPRESSED_R5G6B5: + case UNCOMPRESSED_R5G5B5A1: + case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case UNCOMPRESSED_R8G8B8: bpp = 24; break; + case UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case COMPRESSED_DXT1_RGB: + case COMPRESSED_DXT1_RGBA: + case COMPRESSED_ETC1_RGB: + case COMPRESSED_ETC2_RGB: + case COMPRESSED_PVRT_RGB: + case COMPRESSED_PVRT_RGBA: bpp = 4; break; + case COMPRESSED_DXT3_RGBA: + case COMPRESSED_DXT5_RGBA: + case COMPRESSED_ETC2_EAC_RGBA: + case COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + dataSize = width*height*bpp/8; // Total data size in bytes + + return dataSize; +} + // Get pixel data from GPU texture and return an Image // NOTE: Compressed texture formats not supported Image GetTextureData(Texture2D texture) -- cgit v1.2.3 From 1f0f8c33fa8c1033dcf3037524504f2317f5777a Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 6 Jan 2018 02:51:28 +0100 Subject: Added function declaration --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 01699ae7..a2f28661 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -869,7 +869,7 @@ RLAPI void UnloadImage(Image image); RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) RLAPI Color *GetImageData(Image image); // Get pixel data from image as a Color struct array -RLAPI int GetImageDataSize(Image image); // Get image data size in bytes +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) RLAPI Image GetTextureData(Texture2D texture); // Get pixel data from GPU texture and return an Image RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data RLAPI void SaveImageAs(const char *fileName, Image image); // Save image to a PNG file -- cgit v1.2.3 From 7caa3201d51a04b739122ffadac476493bd05395 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sat, 6 Jan 2018 13:43:48 +0100 Subject: Improved pixel formats support - Renamed enum TextureFormat to PixelFormat for consistency - Added support for pixel format UNCOMPRESSED_R32 - Using GetPixelDataSize() where required --- src/raylib.h | 15 +++++++------- src/rlgl.c | 2 ++ src/rlgl.h | 9 +++++---- src/textures.c | 63 +++++++++++++++++----------------------------------------- 4 files changed, 33 insertions(+), 56 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index a2f28661..c44653e8 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -341,7 +341,7 @@ typedef struct Image { int width; // Image base width int height; // Image base height int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (TextureFormat type) + int format; // Data format (PixelFormat type) } Image; // Texture2D type @@ -351,7 +351,7 @@ typedef struct Texture2D { int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (TextureFormat type) + int format; // Data format (PixelFormat type) } Texture2D; // RenderTexture2D type, for texture rendering @@ -571,18 +571,19 @@ typedef enum { #define MAP_DIFFUSE MAP_ALBEDO #define MAP_SPECULAR MAP_METALNESS -// Texture formats +// Pixel formats // NOTE: Support depends on OpenGL version and platform typedef enum { UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - UNCOMPRESSED_GRAY_ALPHA, // 16 bpp (2 channels) + UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) UNCOMPRESSED_R5G6B5, // 16 bpp UNCOMPRESSED_R8G8B8, // 24 bpp UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) UNCOMPRESSED_R8G8B8A8, // 32 bpp - UNCOMPRESSED_R32G32B32, // 32 bit per channel (float) - HDR - UNCOMPRESSED_R32G32B32A32, // 32 bit per channel (float) - HDR + UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) COMPRESSED_DXT3_RGBA, // 8 bpp @@ -594,7 +595,7 @@ typedef enum { COMPRESSED_PVRT_RGBA, // 4 bpp COMPRESSED_ASTC_4x4_RGBA, // 8 bpp COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} TextureFormat; +} PixelFormat; // Texture parameters: filter mode // NOTE 1: Filtering considers mipmaps if available in the texture diff --git a/src/rlgl.c b/src/rlgl.c index 4bbebf03..e7ab7cd7 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -1420,6 +1420,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, mipmapCount); break; #endif #if defined(GRAPHICS_API_OPENGL_ES2) + case UNCOMPRESSED_R32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; // NOTE: Requires extension OES_texture_float case UNCOMPRESSED_R32G32B32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; // NOTE: Requires extension OES_texture_float case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; // NOTE: Requires extension OES_texture_float case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, mipmapCount); break; @@ -1475,6 +1476,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break; case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break; case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; case UNCOMPRESSED_R32G32B32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; case UNCOMPRESSED_R32G32B32A32: if (texFloatSupported) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGB, GL_FLOAT, (float *)data); break; case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadTextureCompressed((unsigned char *)data, width, height, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, mipmapCount); break; diff --git a/src/rlgl.h b/src/rlgl.h index 0e4a5d74..68d8d4eb 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -169,7 +169,7 @@ typedef unsigned char byte; int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (TextureFormat) + int format; // Data format (PixelFormat) } Texture2D; // RenderTexture2D type, for texture rendering @@ -259,8 +259,9 @@ typedef unsigned char byte; UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) UNCOMPRESSED_R8G8B8A8, // 32 bpp - UNCOMPRESSED_R32G32B32, // 32 bit per channel (float) - HDR - UNCOMPRESSED_R32G32B32A32, // 32 bit per channel (float) - HDR + UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) COMPRESSED_DXT3_RGBA, // 8 bpp @@ -272,7 +273,7 @@ typedef unsigned char byte; COMPRESSED_PVRT_RGBA, // 4 bpp COMPRESSED_ASTC_4x4_RGBA, // 8 bpp COMPRESSED_ASTC_8x8_RGBA // 2 bpp - } TextureFormat; + } PixelFormat; // Texture parameters: filter mode // NOTE 1: Filtering considers mipmaps if available in the texture diff --git a/src/textures.c b/src/textures.c index 9abee62b..7ea54c2c 100644 --- a/src/textures.c +++ b/src/textures.c @@ -226,11 +226,11 @@ Image LoadImage(const char *fileName) image.mipmaps = 1; - if (imgBpp == 3) image.format = UNCOMPRESSED_R32G32B32; + if (imgBpp == 1) image.format = UNCOMPRESSED_R32; + else if (imgBpp == 3) image.format = UNCOMPRESSED_R32G32B32; else if (imgBpp == 4) image.format = UNCOMPRESSED_R32G32B32A32; else { - // TODO: Support different number of channels at 32 bit float TraceLog(LOG_WARNING, "[%s] Image fileformat not supported", fileName); UnloadImage(image); } @@ -318,21 +318,9 @@ Image LoadImageRaw(const char *fileName, int width, int height, int format, int { if (headerSize > 0) fseek(rawFile, headerSize, SEEK_SET); - unsigned int size = width*height; - - switch (format) - { - case UNCOMPRESSED_GRAYSCALE: image.data = (unsigned char *)malloc(size); break; // 8 bit per pixel (no alpha) - case UNCOMPRESSED_GRAY_ALPHA: image.data = (unsigned char *)malloc(size*2); size *= 2; break; // 16 bpp (2 channels) - case UNCOMPRESSED_R5G6B5: image.data = (unsigned short *)malloc(size); break; // 16 bpp - case UNCOMPRESSED_R8G8B8: image.data = (unsigned char *)malloc(size*3); size *= 3; break; // 24 bpp - case UNCOMPRESSED_R5G5B5A1: image.data = (unsigned short *)malloc(size); break; // 16 bpp (1 bit alpha) - case UNCOMPRESSED_R4G4B4A4: image.data = (unsigned short *)malloc(size); break; // 16 bpp (4 bit alpha) - case UNCOMPRESSED_R8G8B8A8: image.data = (unsigned char *)malloc(size*4); size *= 4; break; // 32 bpp - case UNCOMPRESSED_R32G32B32: image.data = (float *)malloc(size*12); size *= 12; break; // 4 byte per channel (12 byte) - case UNCOMPRESSED_R32G32B32A32: image.data = (float *)malloc(size*16); size *= 16; break; // 4 byte per channel (16 byte) - default: TraceLog(LOG_WARNING, "Image format not suported"); break; - } + unsigned int size = GetPixelDataSize(width, height, format); + + image.data = malloc(size); // Allocate required memory in bytes // NOTE: fread() returns num read elements instead of bytes, // to get bytes we need to read (1 byte size, elements) instead of (x byte size, 1 element) @@ -531,6 +519,7 @@ int GetPixelDataSize(int width, int height, int format) case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; case UNCOMPRESSED_R8G8B8: bpp = 24; break; + case UNCOMPRESSED_R32: bpp = 32; break; case UNCOMPRESSED_R32G32B32: bpp = 32*3; break; case UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; case COMPRESSED_DXT1_RGB: @@ -727,6 +716,15 @@ void ImageFormat(Image *image, int newFormat) k++; } } break; + case UNCOMPRESSED_R32: + { + image->data = (float *)malloc(image->width*image->height*sizeof(float)); + + for (int i = 0; i < image->width*image->height; i++) + { + ((float *)image->data)[i] = (float)((float)pixels[i].r*0.299f/255.0f + (float)pixels[i].g*0.587f/255.0f + (float)pixels[i].b*0.114f/255.0f); + } + } break; case UNCOMPRESSED_R32G32B32: { image->data = (float *)malloc(image->width*image->height*3*sizeof(float)); @@ -858,39 +856,14 @@ Image ImageCopy(Image image) { Image newImage = { 0 }; - int byteSize = image.width*image.height; - - switch (image.format) - { - case UNCOMPRESSED_GRAYSCALE: break; // 8 bpp (1 byte) - case UNCOMPRESSED_GRAY_ALPHA: // 16 bpp - case UNCOMPRESSED_R5G6B5: // 16 bpp - case UNCOMPRESSED_R5G5B5A1: // 16 bpp - case UNCOMPRESSED_R4G4B4A4: byteSize *= 2; break; // 16 bpp (2 bytes) - case UNCOMPRESSED_R8G8B8: byteSize *= 3; break; // 24 bpp (3 bytes) - case UNCOMPRESSED_R8G8B8A8: byteSize *= 4; break; // 32 bpp (4 bytes) - case UNCOMPRESSED_R32G32B32: byteSize *= 12; break; // 4 byte per channel (12 bytes) - case UNCOMPRESSED_R32G32B32A32: byteSize *= 16; break; // 4 byte per channel (16 bytes) - case COMPRESSED_DXT3_RGBA: - case COMPRESSED_DXT5_RGBA: - case COMPRESSED_ETC2_EAC_RGBA: - case COMPRESSED_ASTC_4x4_RGBA: break; // 8 bpp (1 byte) - case COMPRESSED_DXT1_RGB: - case COMPRESSED_DXT1_RGBA: - case COMPRESSED_ETC1_RGB: - case COMPRESSED_ETC2_RGB: - case COMPRESSED_PVRT_RGB: - case COMPRESSED_PVRT_RGBA: byteSize /= 2; break; // 4 bpp - case COMPRESSED_ASTC_8x8_RGBA: byteSize /= 4; break;// 2 bpp - default: TraceLog(LOG_WARNING, "Image format not recognized"); break; - } + int size = GetPixelDataSize(image.width, image.height, image.format); - newImage.data = malloc(byteSize); + newImage.data = malloc(size); if (newImage.data != NULL) { // NOTE: Size must be provided in bytes - memcpy(newImage.data, image.data, byteSize); + memcpy(newImage.data, image.data, size); newImage.width = image.width; newImage.height = image.height; -- cgit v1.2.3 From e4be917d1b36a65286ca49b85328187ea8c190ad Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 7 Jan 2018 00:51:26 +0100 Subject: Added new image functions - Added: ImageAlphaClear() - Added: ImageAlphaPremultiply() - Reorganized some functions --- src/raylib.h | 6 +- src/textures.c | 374 +++++++++++++++++++++++++++++++-------------------------- 2 files changed, 210 insertions(+), 170 deletions(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index c44653e8..b482ab3b 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -876,14 +876,16 @@ RLAPI void UpdateTexture(Texture2D texture, const void *pixels); RLAPI void SaveImageAs(const char *fileName, Image image); // Save image to a PNG file // Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) RLAPI void ImageToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image -RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) -RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize and image (bilinear filtering) RLAPI void ImageResizeNN(Image *image,int newWidth,int newHeight); // Resize and image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(SpriteFont font, const char *text, float fontSize, int spacing, Color tint); // Create an image from text (custom sprite font) RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec); // Draw a source image within a destination image diff --git a/src/textures.c b/src/textures.c index fe75bd74..44a9fef2 100644 --- a/src/textures.c +++ b/src/textures.c @@ -593,6 +593,74 @@ void SaveImageAs(const char *fileName, Image image) #endif } +// Copy an image to a new image +Image ImageCopy(Image image) +{ + Image newImage = { 0 }; + + int size = GetPixelDataSize(image.width, image.height, image.format); + + newImage.data = malloc(size); + + if (newImage.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newImage.data, image.data, size); + + newImage.width = image.width; + newImage.height = image.height; + newImage.mipmaps = image.mipmaps; + newImage.format = image.format; + } + + return newImage; +} + +// Convert image to POT (power-of-two) +// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) +void ImageToPOT(Image *image, Color fillColor) +{ + Color *pixels = GetImageData(*image); // Get pixels data + + // Calculate next power-of-two values + // NOTE: Just add the required amount of pixels at the right and bottom sides of image... + int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); + int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); + + // Check if POT texture generation is required (if texture is not already POT) + if ((potWidth != image->width) || (potHeight != image->height)) + { + Color *pixelsPOT = NULL; + + // Generate POT array from NPOT data + pixelsPOT = (Color *)malloc(potWidth*potHeight*sizeof(Color)); + + for (int j = 0; j < potHeight; j++) + { + for (int i = 0; i < potWidth; i++) + { + if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i]; + else pixelsPOT[j*potWidth + i] = fillColor; + } + } + + TraceLog(LOG_WARNING, "Image converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); + + free(pixels); // Free pixels data + free(image->data); // Free old image data + + int format = image->format; // Store image data format to reconvert later + + // TODO: Image width and height changes... do we want to store new values or keep the old ones? + // NOTE: Issues when using image.width and image.height for sprite animations... + *image = LoadImageEx(pixelsPOT, potWidth, potHeight); + + free(pixelsPOT); // Free POT pixels data + + ImageFormat(image, format); // Reconvert image to previous format + } +} + // Convert image data to desired format void ImageFormat(Image *image, int newFormat) { @@ -806,75 +874,45 @@ void ImageAlphaMask(Image *image, Image alphaMask) } } -// Convert image to POT (power-of-two) -// NOTE: It could be useful on OpenGL ES 2.0 (RPI, HTML5) -void ImageToPOT(Image *image, Color fillColor) +// Clear alpha channel to desired color +// NOTE: Threshold defines the alpha limit, 0.0f to 1.0f +void ImageAlphaClear(Image *image, Color color, float threshold) { - Color *pixels = GetImageData(*image); // Get pixels data - - // Calculate next power-of-two values - // NOTE: Just add the required amount of pixels at the right and bottom sides of image... - int potWidth = (int)powf(2, ceilf(logf((float)image->width)/logf(2))); - int potHeight = (int)powf(2, ceilf(logf((float)image->height)/logf(2))); - - // Check if POT texture generation is required (if texture is not already POT) - if ((potWidth != image->width) || (potHeight != image->height)) - { - Color *pixelsPOT = NULL; - - // Generate POT array from NPOT data - pixelsPOT = (Color *)malloc(potWidth*potHeight*sizeof(Color)); - - for (int j = 0; j < potHeight; j++) - { - for (int i = 0; i < potWidth; i++) - { - if ((j < image->height) && (i < image->width)) pixelsPOT[j*potWidth + i] = pixels[j*image->width + i]; - else pixelsPOT[j*potWidth + i] = fillColor; - } - } - - TraceLog(LOG_WARNING, "Image converted to POT: (%ix%i) -> (%ix%i)", image->width, image->height, potWidth, potHeight); - - free(pixels); // Free pixels data - free(image->data); // Free old image data - - int format = image->format; // Store image data format to reconvert later - - // TODO: Image width and height changes... do we want to store new values or keep the old ones? - // NOTE: Issues when using image.width and image.height for sprite animations... - *image = LoadImageEx(pixelsPOT, potWidth, potHeight); - - free(pixelsPOT); // Free POT pixels data + Color *pixels = GetImageData(*image); + + for (int i = 0; i < image->width*image->height; i++) if (pixels[i].a <= (unsigned char)(threshold*255.0f)) pixels[i] = color; - ImageFormat(image, format); // Reconvert image to previous format - } + UnloadImage(*image); + + int prevFormat = image->format; + *image = LoadImageEx(pixels, image->width, image->height); + + ImageFormat(image, prevFormat); } -#if defined(SUPPORT_IMAGE_MANIPULATION) -// Copy an image to a new image -Image ImageCopy(Image image) +// Premultiply alpha channel +void ImageAlphaPremultiply(Image *image) { - Image newImage = { 0 }; - - int size = GetPixelDataSize(image.width, image.height, image.format); - - newImage.data = malloc(size); - - if (newImage.data != NULL) + float alpha = 0.0f; + Color *pixels = GetImageData(*image); + + for (int i = 0; i < image->width*image->height; i++) { - // NOTE: Size must be provided in bytes - memcpy(newImage.data, image.data, size); - - newImage.width = image.width; - newImage.height = image.height; - newImage.mipmaps = image.mipmaps; - newImage.format = image.format; + alpha = (float)pixels[i].a/255.0f; + pixels[i].r = (unsigned char)((float)pixels[i].r*alpha); + pixels[i].g = (unsigned char)((float)pixels[i].g*alpha); + pixels[i].b = (unsigned char)((float)pixels[i].b*alpha); } - return newImage; + UnloadImage(*image); + + int prevFormat = image->format; + *image = LoadImageEx(pixels, image->width, image->height); + + ImageFormat(image, prevFormat); } +#if defined(SUPPORT_IMAGE_MANIPULATION) // Crop an image to area defined by a rectangle // NOTE: Security checks are performed in case rectangle goes out of bounds void ImageCrop(Image *image, Rectangle crop) @@ -982,6 +1020,115 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) free(pixels); } +// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +// NOTE: In case selected bpp do not represent an known 16bit format, +// dithered data is stored in the LSB part of the unsigned short +void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) +{ + if (image->format >= COMPRESSED_DXT1_RGB) + { + TraceLog(LOG_WARNING, "Compressed data formats can not be dithered"); + return; + } + + if ((rBpp+gBpp+bBpp+aBpp) > 16) + { + TraceLog(LOG_WARNING, "Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); + } + else + { + Color *pixels = GetImageData(*image); + + free(image->data); // free old image data + + if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) + { + TraceLog(LOG_WARNING, "Image format is already 16bpp or lower, dithering could have no effect"); + } + + // Define new image format, check if desired bpp match internal known format + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = UNCOMPRESSED_R4G4B4A4; + else + { + image->format = 0; + TraceLog(LOG_WARNING, "Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); + } + + // NOTE: We will store the dithered data as unsigned short (16bpp) + image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); + + Color oldPixel = WHITE; + Color newPixel = WHITE; + + int rError, gError, bError; + unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition + + #define MIN(a,b) (((a)<(b))?(a):(b)) + + for (int y = 0; y < image->height; y++) + { + for (int x = 0; x < image->width; x++) + { + oldPixel = pixels[y*image->width + x]; + + // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) + newPixel.r = oldPixel.r >> (8 - rBpp); // R bits + newPixel.g = oldPixel.g >> (8 - gBpp); // G bits + newPixel.b = oldPixel.b >> (8 - bBpp); // B bits + newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) + + // NOTE: Error must be computed between new and old pixel but using same number of bits! + // We want to know how much color precision we have lost... + rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); + gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); + bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); + + pixels[y*image->width + x] = newPixel; + + // NOTE: Some cases are out of the array and should be ignored + if (x < (image->width - 1)) + { + pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); + pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); + pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); + } + + if ((x > 0) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); + pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); + } + + if (y < (image->height - 1)) + { + pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); + pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); + } + + if ((x < (image->width - 1)) && (y < (image->height - 1))) + { + pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); + pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); + } + + rPixel = (unsigned short)newPixel.r; + gPixel = (unsigned short)newPixel.g; + bPixel = (unsigned short)newPixel.b; + aPixel = (unsigned short)newPixel.a; + + ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; + } + } + + free(pixels); + } +} + // Draw an image (source) within an image (destination) // TODO: Feel this function could be simplified... void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec) @@ -1205,115 +1352,6 @@ void ImageFlipHorizontal(Image *image) image->data = processed.data; } -// Dither image data to 16bpp or lower (Floyd-Steinberg dithering) -// NOTE: In case selected bpp do not represent an known 16bit format, -// dithered data is stored in the LSB part of the unsigned short -void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) -{ - if (image->format >= COMPRESSED_DXT1_RGB) - { - TraceLog(LOG_WARNING, "Compressed data formats can not be dithered"); - return; - } - - if ((rBpp+gBpp+bBpp+aBpp) > 16) - { - TraceLog(LOG_WARNING, "Unsupported dithering bpps (%ibpp), only 16bpp or lower modes supported", (rBpp+gBpp+bBpp+aBpp)); - } - else - { - Color *pixels = GetImageData(*image); - - free(image->data); // free old image data - - if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) - { - TraceLog(LOG_WARNING, "Image format is already 16bpp or lower, dithering could have no effect"); - } - - // Define new image format, check if desired bpp match internal known format - if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = UNCOMPRESSED_R5G6B5; - else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = UNCOMPRESSED_R5G5B5A1; - else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = UNCOMPRESSED_R4G4B4A4; - else - { - image->format = 0; - TraceLog(LOG_WARNING, "Unsupported dithered OpenGL internal format: %ibpp (R%iG%iB%iA%i)", (rBpp+gBpp+bBpp+aBpp), rBpp, gBpp, bBpp, aBpp); - } - - // NOTE: We will store the dithered data as unsigned short (16bpp) - image->data = (unsigned short *)malloc(image->width*image->height*sizeof(unsigned short)); - - Color oldPixel = WHITE; - Color newPixel = WHITE; - - int rError, gError, bError; - unsigned short rPixel, gPixel, bPixel, aPixel; // Used for 16bit pixel composition - - #define MIN(a,b) (((a)<(b))?(a):(b)) - - for (int y = 0; y < image->height; y++) - { - for (int x = 0; x < image->width; x++) - { - oldPixel = pixels[y*image->width + x]; - - // NOTE: New pixel obtained by bits truncate, it would be better to round values (check ImageFormat()) - newPixel.r = oldPixel.r >> (8 - rBpp); // R bits - newPixel.g = oldPixel.g >> (8 - gBpp); // G bits - newPixel.b = oldPixel.b >> (8 - bBpp); // B bits - newPixel.a = oldPixel.a >> (8 - aBpp); // A bits (not used on dithering) - - // NOTE: Error must be computed between new and old pixel but using same number of bits! - // We want to know how much color precision we have lost... - rError = (int)oldPixel.r - (int)(newPixel.r << (8 - rBpp)); - gError = (int)oldPixel.g - (int)(newPixel.g << (8 - gBpp)); - bError = (int)oldPixel.b - (int)(newPixel.b << (8 - bBpp)); - - pixels[y*image->width + x] = newPixel; - - // NOTE: Some cases are out of the array and should be ignored - if (x < (image->width - 1)) - { - pixels[y*image->width + x+1].r = MIN((int)pixels[y*image->width + x+1].r + (int)((float)rError*7.0f/16), 0xff); - pixels[y*image->width + x+1].g = MIN((int)pixels[y*image->width + x+1].g + (int)((float)gError*7.0f/16), 0xff); - pixels[y*image->width + x+1].b = MIN((int)pixels[y*image->width + x+1].b + (int)((float)bError*7.0f/16), 0xff); - } - - if ((x > 0) && (y < (image->height - 1))) - { - pixels[(y+1)*image->width + x-1].r = MIN((int)pixels[(y+1)*image->width + x-1].r + (int)((float)rError*3.0f/16), 0xff); - pixels[(y+1)*image->width + x-1].g = MIN((int)pixels[(y+1)*image->width + x-1].g + (int)((float)gError*3.0f/16), 0xff); - pixels[(y+1)*image->width + x-1].b = MIN((int)pixels[(y+1)*image->width + x-1].b + (int)((float)bError*3.0f/16), 0xff); - } - - if (y < (image->height - 1)) - { - pixels[(y+1)*image->width + x].r = MIN((int)pixels[(y+1)*image->width + x].r + (int)((float)rError*5.0f/16), 0xff); - pixels[(y+1)*image->width + x].g = MIN((int)pixels[(y+1)*image->width + x].g + (int)((float)gError*5.0f/16), 0xff); - pixels[(y+1)*image->width + x].b = MIN((int)pixels[(y+1)*image->width + x].b + (int)((float)bError*5.0f/16), 0xff); - } - - if ((x < (image->width - 1)) && (y < (image->height - 1))) - { - pixels[(y+1)*image->width + x+1].r = MIN((int)pixels[(y+1)*image->width + x+1].r + (int)((float)rError*1.0f/16), 0xff); - pixels[(y+1)*image->width + x+1].g = MIN((int)pixels[(y+1)*image->width + x+1].g + (int)((float)gError*1.0f/16), 0xff); - pixels[(y+1)*image->width + x+1].b = MIN((int)pixels[(y+1)*image->width + x+1].b + (int)((float)bError*1.0f/16), 0xff); - } - - rPixel = (unsigned short)newPixel.r; - gPixel = (unsigned short)newPixel.g; - bPixel = (unsigned short)newPixel.b; - aPixel = (unsigned short)newPixel.a; - - ((unsigned short *)image->data)[y*image->width + x] = (rPixel << (gBpp + bBpp + aBpp)) | (gPixel << (bBpp + aBpp)) | (bPixel << aBpp) | aPixel; - } - } - - free(pixels); - } -} - // Modify image color: tint void ImageColorTint(Image *image, Color color) { -- cgit v1.2.3 From 278d8575bdd09ba6536291e1b66dc9d32224fb2d Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 11 Jan 2018 10:22:32 +0100 Subject: Added new function: ImageAlphaCrop() --- src/raylib.h | 1 + src/textures.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index b482ab3b..dc02370d 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -881,6 +881,7 @@ RLAPI void ImageToPOT(Image *image, Color fillColor); RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize and image (bilinear filtering) diff --git a/src/textures.c b/src/textures.c index 44a9fef2..665e5447 100644 --- a/src/textures.c +++ b/src/textures.c @@ -890,6 +890,51 @@ void ImageAlphaClear(Image *image, Color color, float threshold) ImageFormat(image, prevFormat); } +// Crop image depending on alpha value +void ImageAlphaCrop(Image *image, float threshold) +{ + Rectangle crop = { 0 }; + + Color *pixels = GetImageData(*image); + + int minx = 0; + int miny = 0; + + for (int i = 0; i < image->width*image->height; i++) + { + if (pixels[i].a > (unsigned char)(threshold*255.0f)) + { + minx = i%image->width; + miny = -(-((i/image->width) + 1) + 1); + + if (crop.y == 0) crop.y = miny; + + if (crop.x == 0) crop.x = minx; + else if (minx < crop.x) crop.x = minx; + + if (crop.width == 0) crop.width = minx; + else if (crop.width < minx) crop.width = minx; + + if (crop.height == 0) crop.height = miny; + else if (crop.height < miny) crop.height = miny; + } + } + + crop.width -= (crop.x - 1); + crop.height -= (crop.y - 1); + + TraceLog(LOG_INFO, "Crop rectangle: (%i, %i, %i, %i)", crop.x, crop.y, crop.width, crop.height); + + free(pixels); + + // NOTE: Added this weird check to avoid additional 1px crop to + // image data that has already been cropped... + if ((crop.x != 1) && + (crop.y != 1) && + (crop.width != image->width - 1) && + (crop.height != image->height - 1)) ImageCrop(image, crop); +} + // Premultiply alpha channel void ImageAlphaPremultiply(Image *image) { @@ -912,6 +957,8 @@ void ImageAlphaPremultiply(Image *image) ImageFormat(image, prevFormat); } + + #if defined(SUPPORT_IMAGE_MANIPULATION) // Crop an image to area defined by a rectangle // NOTE: Security checks are performed in case rectangle goes out of bounds -- cgit v1.2.3 From c8e97df233f50b0bc1cba07e014aca93c5c0f15c Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 17 Jan 2018 00:43:30 +0100 Subject: Reviewed function GenImagePerlinNoise() Added support for noise image offset --- examples/textures/textures_image_generation.c | 4 ++-- src/raylib.h | 2 +- src/textures.c | 21 +++++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) (limited to 'src/raylib.h') diff --git a/examples/textures/textures_image_generation.c b/examples/textures/textures_image_generation.c index 7d8e017e..790c34f1 100644 --- a/examples/textures/textures_image_generation.c +++ b/examples/textures/textures_image_generation.c @@ -24,10 +24,10 @@ int main() Image verticalGradient = GenImageGradientV(screenWidth, screenHeight, RED, BLUE); Image horizontalGradient = GenImageGradientH(screenWidth, screenHeight, RED, BLUE); - Image radialGradient = GenImageGradientRadial(screenWidth, screenHeight, 0.f, WHITE, BLACK); + Image radialGradient = GenImageGradientRadial(screenWidth, screenHeight, 0.0f, WHITE, BLACK); Image checked = GenImageChecked(screenWidth, screenHeight, 32, 32, RED, BLUE); Image whiteNoise = GenImageWhiteNoise(screenWidth, screenHeight, 0.5f); - Image perlinNoise = GenImagePerlinNoise(screenWidth, screenHeight, 8.f); + Image perlinNoise = GenImagePerlinNoise(screenWidth, screenHeight, 50, 50, 4.0f); Image cellular = GenImageCellular(screenWidth, screenHeight, 32); Texture2D textures[NUM_TEXTURES]; diff --git a/src/raylib.h b/src/raylib.h index dc02370d..2bb04f77 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -908,7 +908,7 @@ RLAPI Image GenImageGradientH(int width, int height, Color left, Color right); RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise -RLAPI Image GenImagePerlinNoise(int width, int height, float scale); // Generate image: perlin noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm. Bigger tileSize means bigger cells // Texture2D configuration functions diff --git a/src/textures.c b/src/textures.c index 665e5447..e228b356 100644 --- a/src/textures.c +++ b/src/textures.c @@ -611,6 +611,8 @@ Image ImageCopy(Image image) newImage.height = image.height; newImage.mipmaps = image.mipmaps; newImage.format = image.format; + + //if (image.mipmaps > 1) ImageMipmaps(&newImage); } return newImage; @@ -823,6 +825,8 @@ void ImageFormat(Image *image, int newFormat) } free(pixels); + + //if (image->mipmaps > 1) ImageMipmaps(image); } else TraceLog(LOG_WARNING, "Image data format is compressed, can not be converted"); } @@ -1688,7 +1692,7 @@ Image GenImageWhiteNoise(int width, int height, float factor) } // Generate image: perlin noise -Image GenImagePerlinNoise(int width, int height, float scale) +Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale) { Color *pixels = (Color *)malloc(width*height*sizeof(Color)); @@ -1696,13 +1700,18 @@ Image GenImagePerlinNoise(int width, int height, float scale) { for (int x = 0; x < width; x++) { - float nx = (float)x*scale/(float)width; - float ny = (float)y*scale/(float)height; + float nx = (float)(x + offsetX)*scale/(float)width; + float ny = (float)(y + offsetY)*scale/(float)height; - // we need to translate the data from [-1; 1] to [0; 1] - float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6, 0, 0, 0) + 1.0f) / 2.0f; + // Typical values to start playing with: + // lacunarity = ~2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output) + // gain = 0.5 -- relative weighting applied to each successive octave + // octaves = 6 -- number of "octaves" of noise3() to sum + + // NOTE: We need to translate the data from [-1..1] to [0..1] + float p = (stb_perlin_fbm_noise3(nx, ny, 1.0f, 2.0f, 0.5f, 6, 0, 0, 0) + 1.0f)/2.0f; - int intensity = (int)(p * 255.0f); + int intensity = (int)(p*255.0f); pixels[y*width + x] = (Color){intensity, intensity, intensity, 255}; } } -- cgit v1.2.3 From ce9f191f1b0dee3bc9607e58232a166c10cef9c7 Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 18 Jan 2018 00:23:28 +0100 Subject: Added function: ImageMipmaps() --- src/raylib.h | 1 + src/textures.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 2bb04f77..85c035c3 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -886,6 +886,7 @@ RLAPI void ImageAlphaPremultiply(Image *image); RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize and image (bilinear filtering) RLAPI void ImageResizeNN(Image *image,int newWidth,int newHeight); // Resize and image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageMipmaps(Image *image); // Generate all mipmap levels for a provided image RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) RLAPI Image ImageTextEx(SpriteFont font, const char *text, float fontSize, int spacing, Color tint); // Create an image from text (custom sprite font) diff --git a/src/textures.c b/src/textures.c index e228b356..f53adb73 100644 --- a/src/textures.c +++ b/src/textures.c @@ -1071,6 +1071,81 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) free(pixels); } +// Generate all mipmap levels for a provided image +// NOTE 1: Supports POT and NPOT images +// NOTE 2: image.data is scaled to include mipmap levels +// NOTE 3: Mipmaps format is the same as base image +void ImageMipmaps(Image *image) +{ + int mipCount = 1; // Required mipmap levels count (including base level) + int mipWidth = image->width; // Base image width + int mipHeight = image->height; // Base image height + int mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); // Image data size (in bytes) + + // Count mipmap levels required + while ((mipWidth != 1) || (mipHeight != 1)) + { + if (mipWidth != 1) mipWidth /= 2; + if (mipHeight != 1) mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + TraceLog(LOG_DEBUG, "Next mipmap level: %i x %i - current size %i", mipWidth, mipHeight, mipSize); + + mipCount++; + mipSize += GetPixelDataSize(mipWidth, mipHeight, image->format); // Add mipmap size (in bytes) + } + + TraceLog(LOG_DEBUG, "Total mipmaps required: %i", mipCount); + TraceLog(LOG_DEBUG, "Total size of data required: %i", mipSize); + TraceLog(LOG_DEBUG, "Image data original memory point: %i", image->data); + + if (image->mipmaps < mipCount) + { + void *temp = realloc(image->data, mipSize); + + if (temp != NULL) + { + image->data = temp; // Assign new pointer (new size) to store mipmaps data + TraceLog(LOG_DEBUG, "Image data memory point reallocated: %i", temp); + } + else TraceLog(LOG_WARNING, "Mipmaps required memory could not be allocated"); + + // Pointer to allocated memory point where store next mipmap level data + unsigned char *nextmip = image->data + GetPixelDataSize(image->width, image->height, image->format); + + mipWidth = image->width/2; + mipHeight = image->height/2; + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + Image imCopy = ImageCopy(*image); + + for (int i = 1; i < mipCount; i++) + { + TraceLog(LOG_DEBUG, "Next mipmap level %i (%i x %i) - size %i - mem pos: %i", i, mipWidth, mipHeight, mipSize, nextmip); + + ImageResize(&imCopy, mipWidth, mipHeight); // Uses internally Mitchell cubic downscale filter + + memcpy(nextmip, imCopy.data, mipSize); + nextmip += mipSize; + image->mipmaps++; + + mipWidth /= 2; + mipHeight /= 2; + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + + mipSize = GetPixelDataSize(mipWidth, mipHeight, image->format); + } + + UnloadImage(imCopy); + } + else TraceLog(LOG_WARNING, "Image mipmaps already available"); +} + // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) // NOTE: In case selected bpp do not represent an known 16bit format, // dithered data is stored in the LSB part of the unsigned short -- cgit v1.2.3 From 44c95af463945023e0359790a69d2a991ef7185e Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Jan 2018 14:08:08 +0100 Subject: Added missing keys definitions --- src/raylib.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 85c035c3..938aee86 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -103,11 +103,23 @@ #define KEY_SPACE 32 #define KEY_ESCAPE 256 #define KEY_ENTER 257 +#define KEY_TAB 258 #define KEY_BACKSPACE 259 +#define KEY_INSERT 260 +#define KEY_DELETE 261 #define KEY_RIGHT 262 #define KEY_LEFT 263 #define KEY_DOWN 264 #define KEY_UP 265 +#define KEY_PAGE_UP 266 +#define PAGE_DOWN 267 +#define KEY_HOME 268 +#define KEY_END 269 +#define KEY_CAPS_LOCK 280 +#define KEY_SCROLL_LOCK 281 +#define KEY_NUM_LOCK 282 +#define KEY_PRINT_SCREEN 283 +#define KEY_PAUSE 284 #define KEY_F1 290 #define KEY_F2 291 #define KEY_F3 292 -- cgit v1.2.3 From 58346414f7322a0e4d643439f457b6147ee4cbc5 Mon Sep 17 00:00:00 2001 From: Ray Date: Wed, 31 Jan 2018 15:32:40 +0100 Subject: Corrected typo --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 938aee86..76ad9b55 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -112,7 +112,7 @@ #define KEY_DOWN 264 #define KEY_UP 265 #define KEY_PAGE_UP 266 -#define PAGE_DOWN 267 +#define KEY_PAGE_DOWN 267 #define KEY_HOME 268 #define KEY_END 269 #define KEY_CAPS_LOCK 280 -- cgit v1.2.3 From d1ef6869a9e9404ce7039ddddad8fa35240d4f42 Mon Sep 17 00:00:00 2001 From: Ray San Date: Fri, 2 Feb 2018 11:01:38 +0100 Subject: Added function DrawRectangleLinesEx() --- src/raylib.h | 1 + src/shapes.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+) (limited to 'src/raylib.h') diff --git a/src/raylib.h b/src/raylib.h index 76ad9b55..f881dc68 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -851,6 +851,7 @@ RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Col RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) diff --git a/src/shapes.c b/src/shapes.c index fac14760..693463ff 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -457,6 +457,21 @@ void DrawRectangleLines(int posX, int posY, int width, int height, Color color) } } +// Draw rectangle outline with extended parameters +void DrawRectangleLinesEx(Rectangle rec, int lineThick, Color color) +{ + if (lineThick > rec.width || lineThick > rec.height) + { + if(rec.width > rec.height) lineThick = rec.height/2; + else if (rec.width < rec.height) lineThick = rec.width/2; + } + + DrawRectangle(rec.x, rec.y, rec.width, lineThick, color); + DrawRectangle(rec.x - lineThick + rec.width, rec.y + lineThick, lineThick, rec.height - lineThick*2, color); + DrawRectangle(rec.x, rec.y + rec.height - lineThick, rec.width, lineThick, color); + DrawRectangle(rec.x, rec.y + lineThick, lineThick, rec.height - lineThick*2, color); +} + // Draw a triangle void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { -- cgit v1.2.3 From 26c9176a1422125d062185b9fe9ce3c8777d87db Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Sat, 3 Feb 2018 12:12:50 +0100 Subject: Return false from InitWindow if glfwInit or glfwCreateWindow fails You can't do much with raylib if glfwInit or glfwCreateWindow fails, currently it just exits by means of TraceLog(LOG_ERROR. User code, however, might want to fall back to a text-only UI or display a warning if raylib can't be used. --- src/core.c | 23 ++++++++++++++++------- src/raylib.h | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 648d586d..75a0781f 100644 --- a/src/core.c +++ b/src/core.c @@ -362,7 +362,7 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads default fo //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static void InitGraphicsDevice(int width, int height); // Initialize graphics device +static bool InitGraphicsDevice(int width, int height); // Initialize graphics device static void SetupFramebufferSize(int displayWidth, int displayHeight); static void InitTimer(void); // Initialize timer static void Wait(float ms); // Wait for some milliseconds (stop program execution) @@ -429,7 +429,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) // Initialize window and OpenGL context // NOTE: data parameter could be used to pass any kind of required data to the initialization -void InitWindow(int width, int height, void *data) +bool InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -442,7 +442,8 @@ void InitWindow(int width, int height, void *data) #endif // Init graphics device (display device and OpenGL context) - InitGraphicsDevice(width, height); + if (!InitGraphicsDevice(width, height)) + return false; // Init hi-res timer InitTimer(); @@ -492,13 +493,14 @@ void InitWindow(int width, int height, void *data) SetTargetFPS(60); LogoAnimation(); } + return true; } #endif #if defined(PLATFORM_ANDROID) // Initialize window and OpenGL context (and Android activity) // NOTE: data parameter could be used to pass any kind of required data to the initialization -void InitWindow(int width, int height, void *data) +bool InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -555,6 +557,7 @@ void InitWindow(int width, int height, void *data) //if (app->destroyRequested != 0) windowShouldClose = true; } } + return true; } #endif @@ -1697,7 +1700,7 @@ Vector2 GetTouchPosition(int index) // Initialize display device and framebuffer // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size // If width or height are 0, default display size will be used for framebuffer size -static void InitGraphicsDevice(int width, int height) +static bool InitGraphicsDevice(int width, int height) { screenWidth = width; // User desired width screenHeight = height; // User desired height @@ -1711,7 +1714,11 @@ static void InitGraphicsDevice(int width, int height) #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) glfwSetErrorCallback(ErrorCallback); - if (!glfwInit()) TraceLog(LOG_ERROR, "Failed to initialize GLFW"); + if (!glfwInit()) + { + TraceLog(LOG_WARNING, "Failed to initialize GLFW"); + return false; + } // NOTE: Getting video modes is not implemented in emscripten GLFW3 version #if defined(PLATFORM_DESKTOP) @@ -1842,7 +1849,8 @@ static void InitGraphicsDevice(int width, int height) if (!window) { glfwTerminate(); - TraceLog(LOG_ERROR, "GLFW Failed to initialize Window"); + TraceLog(LOG_WARNING, "GLFW Failed to initialize Window"); + return false; } else { @@ -2199,6 +2207,7 @@ static void InitGraphicsDevice(int width, int height) #if defined(PLATFORM_ANDROID) windowReady = true; // IMPORTANT! #endif + return true; } // Set viewport parameters diff --git a/src/raylib.h b/src/raylib.h index f881dc68..0aa5ccae 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -684,7 +684,7 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Window-related functions -RLAPI void InitWindow(int width, int height, void *data); // Initialize window and OpenGL context +RLAPI bool InitWindow(int width, int height, void *data); // Initialize window and OpenGL context RLAPI void CloseWindow(void); // Close window and unload OpenGL context RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI bool IsWindowMinimized(void); // Check if window has been minimized (or lost focus) -- cgit v1.2.3 From 70e0070a853a76c9ffe5518c2861bf24396e9676 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 4 Feb 2018 12:26:28 +0100 Subject: Reviewed window initialization In case graphic device could not be created it returns false instead of failing with an error tracelog (and consequently closing the program). Window initialization success could be checked with new function IsWindowReady() --- src/core.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++-------------- src/raylib.h | 3 ++- 2 files changed, 69 insertions(+), 21 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 75a0781f..ca833ee7 100644 --- a/src/core.c +++ b/src/core.c @@ -236,7 +236,8 @@ static GLFWwindow *window; // Native window (graphic device) #endif -static bool windowMinimized = false; +static bool windowReady = false; // Check if window has been initialized successfully +static bool windowMinimized = false; // Check if window has been minimized #if defined(PLATFORM_ANDROID) static struct android_app *app; // Android activity @@ -244,7 +245,6 @@ static struct android_poll_source *source; // Android events polling source static int ident, events; // Android ALooper_pollAll() variables static const char *internalDataPath; // Android internal data path to write data (/data/data//files) -static bool windowReady = false; // Used to detect display initialization static bool appEnabled = true; // Used to detec if app is active static bool contextRebindRequired = false; // Used to know context rebind required #endif @@ -429,7 +429,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) // Initialize window and OpenGL context // NOTE: data parameter could be used to pass any kind of required data to the initialization -bool InitWindow(int width, int height, void *data) +void InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -442,9 +442,9 @@ bool InitWindow(int width, int height, void *data) #endif // Init graphics device (display device and OpenGL context) - if (!InitGraphicsDevice(width, height)) - return false; - + // NOTE: returns true if window and graphic device has been initialized successfully + windowReady = InitGraphicsDevice(width, height); + // Init hi-res timer InitTimer(); @@ -493,14 +493,13 @@ bool InitWindow(int width, int height, void *data) SetTargetFPS(60); LogoAnimation(); } - return true; } #endif #if defined(PLATFORM_ANDROID) // Initialize window and OpenGL context (and Android activity) // NOTE: data parameter could be used to pass any kind of required data to the initialization -bool InitWindow(int width, int height, void *data) +void InitWindow(int width, int height, void *data) { TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); @@ -557,7 +556,6 @@ bool InitWindow(int width, int height, void *data) //if (app->destroyRequested != 0) windowShouldClose = true; } } - return true; } #endif @@ -625,6 +623,12 @@ void CloseWindow(void) TraceLog(LOG_INFO, "Window closed successfully"); } +// Check if window has been initialized successfully +bool IsWindowReady(void) +{ + return windowReady; +} + // Check if KEY_ESCAPE pressed or Close icon pressed bool WindowShouldClose(void) { @@ -1700,6 +1704,7 @@ Vector2 GetTouchPosition(int index) // Initialize display device and framebuffer // NOTE: width and height represent the screen (framebuffer) desired size, not actual display size // If width or height are 0, default display size will be used for framebuffer size +// NOTE: returns false in case graphic device could not be created static bool InitGraphicsDevice(int width, int height) { screenWidth = width; // User desired width @@ -1998,7 +2003,11 @@ static bool InitGraphicsDevice(int width, int height) // eglGetPlatformDisplayEXT is an alternative to eglGetDisplay. It allows us to pass in display attributes, used to configure D3D11. PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)(eglGetProcAddress("eglGetPlatformDisplayEXT")); - if (!eglGetPlatformDisplayEXT) TraceLog(LOG_ERROR, "Failed to get function eglGetPlatformDisplayEXT"); + if (!eglGetPlatformDisplayEXT) + { + TraceLog(LOG_WARNING, "Failed to get function eglGetPlatformDisplayEXT"); + return false; + } // // To initialize the display, we make three sets of calls to eglGetPlatformDisplayEXT and eglInitialize, with varying @@ -2012,24 +2021,37 @@ static bool InitGraphicsDevice(int width, int height) // This tries to initialize EGL to D3D11 Feature Level 10_0+. See above comment for details. display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, defaultDisplayAttributes); - if (display == EGL_NO_DISPLAY) TraceLog(LOG_ERROR, "Failed to get EGL display"); - + if (display == EGL_NO_DISPLAY) + { + TraceLog(LOG_WARNING, "Failed to initialize EGL display"); + return false; + } + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { // This tries to initialize EGL to D3D11 Feature Level 9_3, if 10_0+ is unavailable (e.g. on some mobile devices). display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, fl9_3DisplayAttributes); - if (display == EGL_NO_DISPLAY) TraceLog(LOG_ERROR, "Failed to get EGL display"); + if (display == EGL_NO_DISPLAY) + { + TraceLog(LOG_WARNING, "Failed to initialize EGL display"); + return false; + } if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { // This initializes EGL to D3D11 Feature Level 11_0 on WARP, if 9_3+ is unavailable on the default GPU. display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, warpDisplayAttributes); - if (display == EGL_NO_DISPLAY) TraceLog(LOG_ERROR, "Failed to get EGL display"); + if (display == EGL_NO_DISPLAY) + { + TraceLog(LOG_WARNING, "Failed to initialize EGL display"); + return false; + } if (eglInitialize(display, NULL, NULL) == EGL_FALSE) { // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. - TraceLog(LOG_ERROR, "Failed to initialize EGL"); + TraceLog(LOG_WARNING, "Failed to initialize EGL"); + return false; } } } @@ -2039,7 +2061,8 @@ static bool InitGraphicsDevice(int width, int height) EGLint numConfigs = 0; if ((eglChooseConfig(display, framebufferAttribs, &config, 1, &numConfigs) == EGL_FALSE) || (numConfigs == 0)) { - TraceLog(LOG_ERROR, "Failed to choose first EGLConfig"); + TraceLog(LOG_WARNING, "Failed to choose first EGLConfig"); + return false; } // Create a PropertySet and initialize with the EGLNativeWindowType. @@ -2074,10 +2097,18 @@ static bool InitGraphicsDevice(int width, int height) //surface = eglCreateWindowSurface(display, config, reinterpret_cast(surfaceCreationProperties), surfaceAttributes); surface = eglCreateWindowSurface(display, config, uwpWindow, surfaceAttributes); - if (surface == EGL_NO_SURFACE) TraceLog(LOG_ERROR, "Failed to create EGL fullscreen surface"); + if (surface == EGL_NO_SURFACE) + { + TraceLog(LOG_WARNING, "Failed to create EGL fullscreen surface"); + return false; + } context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); - if (context == EGL_NO_CONTEXT) TraceLog(LOG_ERROR, "Failed to create EGL context"); + if (context == EGL_NO_CONTEXT) + { + TraceLog(LOG_WARNING, "Failed to create EGL context"); + return false; + } // Get EGL display window size eglQuerySurface(display, surface, EGL_WIDTH, &screenWidth); @@ -2088,9 +2119,19 @@ static bool InitGraphicsDevice(int width, int height) // Get an EGL display connection display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) + { + TraceLog(LOG_WARNING, "Failed to initialize EGL display"); + return false; + } // Initialize the EGL display connection - eglInitialize(display, NULL, NULL); + if (eglInitialize(display, NULL, NULL) == EGL_FALSE) + { + // If all of the calls to eglInitialize returned EGL_FALSE then an error has occurred. + TraceLog(LOG_WARNING, "Failed to initialize EGL"); + return false; + } // Get an appropriate EGL framebuffer configuration eglChooseConfig(display, framebufferAttribs, &config, 1, &numConfigs); @@ -2100,6 +2141,11 @@ static bool InitGraphicsDevice(int width, int height) // Create an EGL rendering context context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context == EGL_NO_CONTEXT) + { + TraceLog(LOG_WARNING, "Failed to create EGL context"); + return false; + } #endif // Create an EGL window surface @@ -2168,7 +2214,8 @@ static bool InitGraphicsDevice(int width, int height) if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { - TraceLog(LOG_ERROR, "Unable to attach EGL rendering context to EGL surface"); + TraceLog(LOG_WARNING, "Unable to attach EGL rendering context to EGL surface"); + return false; } else { diff --git a/src/raylib.h b/src/raylib.h index 0aa5ccae..61a14a8c 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -684,8 +684,9 @@ extern "C" { // Prevents name mangling of functions //------------------------------------------------------------------------------------ // Window-related functions -RLAPI bool InitWindow(int width, int height, void *data); // Initialize window and OpenGL context +RLAPI void InitWindow(int width, int height, void *data); // Initialize window and OpenGL context RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI bool IsWindowMinimized(void); // Check if window has been minimized (or lost focus) RLAPI void ToggleFullscreen(void); // Toggle fullscreen mode (only PLATFORM_DESKTOP) -- cgit v1.2.3 From 6dc2f979ccbb4ec6f8166805b5f4f6377efbce70 Mon Sep 17 00:00:00 2001 From: raysan5 Date: Sun, 4 Feb 2018 12:33:46 +0100 Subject: Updated raylib version Note that this version is under development and could be buggy on some platforms... --- src/core.c | 4 ++-- src/raylib.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/raylib.h') diff --git a/src/core.c b/src/core.c index 821a2d61..1cafac28 100644 --- a/src/core.c +++ b/src/core.c @@ -431,7 +431,7 @@ static void *GamepadThread(void *arg); // Mouse reading thread // NOTE: data parameter could be used to pass any kind of required data to the initialization void InitWindow(int width, int height, void *data) { - TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); + TraceLog(LOG_INFO, "Initializing raylib (v1.9.3-dev)"); #if defined(PLATFORM_DESKTOP) windowTitle = (char *)data; @@ -501,7 +501,7 @@ void InitWindow(int width, int height, void *data) // NOTE: data parameter could be used to pass any kind of required data to the initialization void InitWindow(int width, int height, void *data) { - TraceLog(LOG_INFO, "Initializing raylib (v1.9-dev)"); + TraceLog(LOG_INFO, "Initializing raylib (v1.9.3-dev)"); screenWidth = width; screenHeight = height; diff --git a/src/raylib.h b/src/raylib.h index 61a14a8c..a34ef9d6 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib v1.9-dev +* raylib v1.9.3-dev * * A simple and easy-to-use library to learn videogames programming (www.raylib.com) * -- cgit v1.2.3