From c1525b67af91545d6c8688ddc8129d1d71494dd0 Mon Sep 17 00:00:00 2001 From: Milan Nikolic Date: Tue, 25 May 2021 14:35:17 +0200 Subject: [PATCH] Update C sources --- raylib/camera.h | 54 +- raylib/config.h | 69 +- raylib/core.c | 2766 +++- raylib/external/cgltf.h | 1618 +- raylib/external/dr_flac.h | 8530 +++++++--- raylib/external/dr_mp3.h | 2453 ++- raylib/external/dr_wav.h | 1048 +- raylib/external/glfw/include/GLFW/glfw3.h | 171 +- raylib/external/glfw/src/cocoa_init.m | 10 +- raylib/external/glfw/src/cocoa_joystick.h | 4 - raylib/external/glfw/src/cocoa_joystick.m | 24 +- raylib/external/glfw/src/cocoa_monitor.m | 18 +- raylib/external/glfw/src/cocoa_platform.h | 7 +- raylib/external/glfw/src/cocoa_window.m | 103 +- raylib/external/glfw/src/egl_context.c | 84 +- raylib/external/glfw/src/egl_context.h | 54 +- raylib/external/glfw/src/init.c | 9 + raylib/external/glfw/src/input.c | 47 + raylib/external/glfw/src/internal.h | 22 +- raylib/external/glfw/src/linux_joystick.c | 97 +- raylib/external/glfw/src/linux_joystick.h | 3 - raylib/external/glfw/src/mappings.h | 670 +- raylib/external/glfw/src/nsgl_context.m | 2 +- raylib/external/glfw/src/null_init.c | 5 + raylib/external/glfw/src/null_joystick.c | 9 + raylib/external/glfw/src/null_monitor.c | 86 +- raylib/external/glfw/src/null_platform.h | 43 +- raylib/external/glfw/src/null_window.c | 384 +- raylib/external/glfw/src/osmesa_context.h | 4 - raylib/external/glfw/src/wgl_context.h | 4 - raylib/external/glfw/src/win32_init.c | 5 +- raylib/external/glfw/src/win32_joystick.c | 74 +- raylib/external/glfw/src/win32_joystick.h | 3 - raylib/external/glfw/src/win32_monitor.c | 2 + raylib/external/glfw/src/win32_platform.h | 18 +- raylib/external/glfw/src/win32_window.c | 213 +- raylib/external/glfw/src/window.c | 36 +- raylib/external/glfw/src/wl_init.c | 25 +- raylib/external/glfw/src/wl_monitor.c | 4 +- raylib/external/glfw/src/wl_platform.h | 5 - raylib/external/glfw/src/wl_window.c | 86 +- raylib/external/glfw/src/x11_init.c | 665 +- raylib/external/glfw/src/x11_monitor.c | 2 +- raylib/external/glfw/src/x11_platform.h | 353 +- raylib/external/glfw/src/x11_window.c | 240 +- raylib/external/jar_xm.h | 1950 +-- raylib/external/miniaudio.h | 17309 +++++++++++--------- raylib/external/msf_gif.h | 608 + raylib/external/rgif.h | 930 -- raylib/external/sdefl.h | 697 + raylib/external/sinfl.h | 464 + raylib/external/stb_image.h | 214 +- raylib/external/stb_image_write.h | 58 +- raylib/external/stb_vorbis.c | 5077 ------ raylib/external/stb_vorbis.h | 2 +- raylib/external/tinyobj_loader_c.h | 6 +- raylib/gestures.h | 10 +- raylib/models.c | 2861 ++-- raylib/raudio.c | 737 +- raylib/raudio.h | 15 +- raylib/raylib.h | 831 +- raylib/raymath.h | 181 +- raylib/rlgl.h | 5292 +++--- raylib/shapes.c | 732 +- raylib/text.c | 231 +- raylib/textures.c | 1519 +- raylib/utils.c | 158 +- raylib/utils.h | 2 +- 68 files changed, 34056 insertions(+), 25957 deletions(-) create mode 100644 raylib/external/msf_gif.h delete mode 100644 raylib/external/rgif.h create mode 100644 raylib/external/sdefl.h create mode 100644 raylib/external/sinfl.h delete mode 100644 raylib/external/stb_vorbis.c diff --git a/raylib/camera.h b/raylib/camera.h index 3b0feb4..35eda83 100644 --- a/raylib/camera.h +++ b/raylib/camera.h @@ -22,7 +22,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2021 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. @@ -91,7 +91,7 @@ typedef enum { CAMERA_PERSPECTIVE = 0, CAMERA_ORTHOGRAPHIC - } CameraType; + } CameraProjection; #endif #ifdef __cplusplus @@ -110,12 +110,12 @@ extern "C" { // Prevents name mangling of functions void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) void UpdateCamera(Camera *camera); // Update camera position for selected mode -void SetCameraPanControl(int panKey); // Set camera pan key to combine with mouse movement (free camera) -void SetCameraAltControl(int altKey); // Set camera alt key to combine with mouse movement (free camera) +void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) void SetCameraSmoothZoomControl(int szoomKey); // Set camera smooth zoom key to combine with mouse (free camera) -void SetCameraMoveControls(int frontKey, int backKey, - int rightKey, int leftKey, - int upKey, int downKey); // Set camera move controls (1st person and 3rd person cameras) +void SetCameraMoveControls(int keyFront, int keyBack, + int keyRight, int keyLeft, + int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) #endif #ifdef __cplusplus @@ -222,7 +222,7 @@ static CameraData CAMERA = { // Global CAMERA state context .moveControl = { 'W', 'S', 'D', 'A', 'E', 'Q' }, .smoothZoomControl = 341, // raylib: KEY_LEFT_CONTROL .altControl = 342, // raylib: KEY_LEFT_ALT - .panControl = 2 // raylib: MOUSE_MIDDLE_BUTTON + .panControl = 2 // raylib: MOUSE_BUTTON_MIDDLE }; //---------------------------------------------------------------------------------- @@ -236,7 +236,7 @@ static void DisableCursor() {} // Lock cursor static int IsKeyDown(int key) { return 0; } static int IsMouseButtonDown(int button) { return 0;} -static int GetMouseWheelMove() { return 0; } +static float GetMouseWheelMove() { return 0.0f; } static Vector2 GetMousePosition() { return (Vector2){ 0.0f, 0.0f }; } #endif @@ -285,12 +285,12 @@ void UpdateCamera(Camera *camera) // Mouse movement detection Vector2 mousePositionDelta = { 0.0f, 0.0f }; Vector2 mousePosition = GetMousePosition(); - int mouseWheelMove = GetMouseWheelMove(); + float mouseWheelMove = GetMouseWheelMove(); // Keys input detection // TODO: Input detection is raylib-dependant, it could be moved outside the module - bool panKey = IsMouseButtonDown(CAMERA.panControl); - bool altKey = IsKeyDown(CAMERA.altControl); + bool keyPan = IsMouseButtonDown(CAMERA.panControl); + bool keyAlt = IsKeyDown(CAMERA.altControl); bool szoomKey = IsKeyDown(CAMERA.smoothZoomControl); bool direction[6] = { IsKeyDown(CAMERA.moveControl[MOVE_FRONT]), IsKeyDown(CAMERA.moveControl[MOVE_BACK]), @@ -319,7 +319,7 @@ void UpdateCamera(Camera *camera) CAMERA.targetDistance -= (mouseWheelMove*CAMERA_MOUSE_SCROLL_SENSITIVITY); if (CAMERA.targetDistance > CAMERA_FREE_DISTANCE_MAX_CLAMP) CAMERA.targetDistance = CAMERA_FREE_DISTANCE_MAX_CLAMP; } - + // Camera looking down else if ((camera->position.y > camera->target.y) && (CAMERA.targetDistance == CAMERA_FREE_DISTANCE_MAX_CLAMP) && (mouseWheelMove < 0)) { @@ -362,9 +362,9 @@ void UpdateCamera(Camera *camera) } // Input keys checks - if (panKey) + if (keyPan) { - if (altKey) // Alternative key behaviour + if (keyAlt) // Alternative key behaviour { if (szoomKey) { @@ -443,7 +443,7 @@ void UpdateCamera(Camera *camera) camera->target.x = camera->position.x - transform.m12; camera->target.y = camera->position.y - transform.m13; camera->target.z = camera->position.z - transform.m14; - + // If movement detected (some key pressed), increase swinging for (int i = 0; i < 6; i++) if (direction[i]) { swingCounter++; break; } @@ -487,10 +487,10 @@ void UpdateCamera(Camera *camera) // TODO: It seems camera->position is not correctly updated or some rounding issue makes the camera move straight to camera->target... camera->position.x = sinf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.x; - + if (CAMERA.angle.y <= 0.0f) camera->position.y = sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; else camera->position.y = -sinf(CAMERA.angle.y)*CAMERA.targetDistance*sinf(CAMERA.angle.y) + camera->target.y; - + camera->position.z = cosf(CAMERA.angle.x)*CAMERA.targetDistance*cosf(CAMERA.angle.y) + camera->target.z; } break; @@ -500,23 +500,23 @@ void UpdateCamera(Camera *camera) } // Set camera pan key to combine with mouse movement (free camera) -void SetCameraPanControl(int panKey) { CAMERA.panControl = panKey; } +void SetCameraPanControl(int keyPan) { CAMERA.panControl = keyPan; } // Set camera alt key to combine with mouse movement (free camera) -void SetCameraAltControl(int altKey) { CAMERA.altControl = altKey; } +void SetCameraAltControl(int keyAlt) { CAMERA.altControl = keyAlt; } // Set camera smooth zoom key to combine with mouse (free camera) void SetCameraSmoothZoomControl(int szoomKey) { CAMERA.smoothZoomControl = szoomKey; } // Set camera move controls (1st person and 3rd person cameras) -void SetCameraMoveControls(int frontKey, int backKey, int rightKey, int leftKey, int upKey, int downKey) +void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown) { - CAMERA.moveControl[MOVE_FRONT] = frontKey; - CAMERA.moveControl[MOVE_BACK] = backKey; - CAMERA.moveControl[MOVE_RIGHT] = rightKey; - CAMERA.moveControl[MOVE_LEFT] = leftKey; - CAMERA.moveControl[MOVE_UP] = upKey; - CAMERA.moveControl[MOVE_DOWN] = downKey; + CAMERA.moveControl[MOVE_FRONT] = keyFront; + CAMERA.moveControl[MOVE_BACK] = keyBack; + CAMERA.moveControl[MOVE_RIGHT] = keyRight; + CAMERA.moveControl[MOVE_LEFT] = keyLeft; + CAMERA.moveControl[MOVE_UP] = keyUp; + CAMERA.moveControl[MOVE_DOWN] = keyDown; } #endif // CAMERA_IMPLEMENTATION diff --git a/raylib/config.h b/raylib/config.h index d9d5b5d..6cec344 100644 --- a/raylib/config.h +++ b/raylib/config.h @@ -6,7 +6,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2018-2020 Ahmad Fatoum & Ramon Santamaria (@raysan5) +* Copyright (c) 2018-2021 Ahmad Fatoum & 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. @@ -25,14 +25,6 @@ * **********************************************************************************************/ -#define RAYLIB_VERSION "3.1-dev" - -// Edit to control what features Makefile'd raylib is compiled with -#if defined(RAYLIB_CMAKE) - // Edit CMakeOptions.txt for CMake instead - #include "cmake/config.h" -#else - //------------------------------------------------------------------------------------ // Module: core - Configuration Flags //------------------------------------------------------------------------------------ @@ -44,24 +36,25 @@ #define SUPPORT_MOUSE_GESTURES 1 // Reconfigure standard input to receive key inputs, works with SSH connection. #define SUPPORT_SSH_KEYBOARD_RPI 1 -// Draw a mouse reference on screen (square cursor box) -#define SUPPORT_MOUSE_CURSOR_RPI 1 +// Draw a mouse pointer on screen +#define SUPPORT_MOUSE_CURSOR_NATIVE 1 +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +#define SUPPORT_WINMM_HIGHRES_TIMER 1 // Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used //#define SUPPORT_BUSY_WAIT_LOOP 1 -// Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end -#define SUPPORT_HALFBUSY_WAIT_LOOP +// Use a partial-busy wait loop, in this case frame sleeps for most of the time, but then runs a busy loop at the end for accuracy +#define SUPPORT_PARTIALBUSY_WAIT_LOOP // Wait for events passively (sleeping while no events) instead of polling them actively every frame //#define SUPPORT_EVENTS_WAITING 1 // Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() #define SUPPORT_SCREEN_CAPTURE 1 // Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() -//#define SUPPORT_GIF_RECORDING 1 -// Allow scale all the drawn content to match the high-DPI equivalent size (only PLATFORM_DESKTOP) -//#define SUPPORT_HIGH_DPI 1 +#define SUPPORT_GIF_RECORDING 1 // Support CompressData() and DecompressData() functions -#define SUPPORT_COMPRESSION_API 1 +#define SUPPORT_COMPRESSION_API 1 // Support saving binary data automatically to a generated storage.data file. This file is managed internally. -#define SUPPORT_DATA_STORAGE 1 +#define SUPPORT_DATA_STORAGE 1 // core: Configuration values //------------------------------------------------------------------------------------ @@ -79,15 +72,15 @@ #define STORAGE_DATA_FILE "storage.data" // Automatic storage filename +#define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB + //------------------------------------------------------------------------------------ -// Module: rlgl - Configuration Flags +// Module: rlgl - Configuration values //------------------------------------------------------------------------------------ -// Support VR simulation functionality (stereo rendering) -#define SUPPORT_VR_SIMULATOR 1 +// Show OpenGL extensions and capabilities detailed logs on init +//#define SUPPORT_GL_DETAILS_INFO 1 -// rlgl: Configuration values -//------------------------------------------------------------------------------------ #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) #define DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch limits #elif defined(GRAPHICS_API_OPENGL_ES2) @@ -98,6 +91,7 @@ #define DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) #define MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +#define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh #define MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported #define MAX_MATERIAL_MAPS 12 // Maximum number of shader maps supported @@ -116,9 +110,6 @@ //------------------------------------------------------------------------------------ // Module: shapes - Configuration Flags //------------------------------------------------------------------------------------ -// 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! -#define SUPPORT_FONT_TEXTURE 1 // Use QUADS instead of TRIANGLES for drawing when possible // Some lines-based shapes could still use lines #define SUPPORT_QUADS_DRAW_MODE 1 @@ -129,17 +120,17 @@ //------------------------------------------------------------------------------------ // Selecte desired fileformats to be supported for image data loading #define SUPPORT_FILEFORMAT_PNG 1 -#define SUPPORT_FILEFORMAT_BMP 1 -#define SUPPORT_FILEFORMAT_TGA 1 -//#define SUPPORT_FILEFORMAT_JPG 1 +//#define SUPPORT_FILEFORMAT_BMP 1 +//#define SUPPORT_FILEFORMAT_TGA 1 +//#define SUPPORT_FILEFORMAT_JPG 1 #define SUPPORT_FILEFORMAT_GIF 1 -//#define SUPPORT_FILEFORMAT_PSD 1 +//#define SUPPORT_FILEFORMAT_PSD 1 #define SUPPORT_FILEFORMAT_DDS 1 #define SUPPORT_FILEFORMAT_HDR 1 -#define SUPPORT_FILEFORMAT_KTX 1 -#define SUPPORT_FILEFORMAT_ASTC 1 -//#define SUPPORT_FILEFORMAT_PKM 1 -//#define SUPPORT_FILEFORMAT_PVR 1 +//#define SUPPORT_FILEFORMAT_KTX 1 +//#define SUPPORT_FILEFORMAT_ASTC 1 +//#define SUPPORT_FILEFORMAT_PKM 1 +//#define SUPPORT_FILEFORMAT_PVR 1 // Support image export functionality (.png, .bmp, .tga, .jpg) #define SUPPORT_IMAGE_EXPORT 1 @@ -193,21 +184,22 @@ #define SUPPORT_FILEFORMAT_OGG 1 #define SUPPORT_FILEFORMAT_XM 1 #define SUPPORT_FILEFORMAT_MOD 1 -//#define SUPPORT_FILEFORMAT_FLAC 1 #define SUPPORT_FILEFORMAT_MP3 1 +//#define SUPPORT_FILEFORMAT_FLAC 1 // audio: Configuration values //------------------------------------------------------------------------------------ #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (miniaudio: float-32bit) #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo -#define AUDIO_DEVICE_SAMPLE_RATE 44100 // Device output sample rate +#define AUDIO_DEVICE_SAMPLE_RATE 0 // Device sample rate (device default) -#define DEFAULT_AUDIO_BUFFER_SIZE 4096 // Default audio buffer size for streaming #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Maximum number of audio pool channels //------------------------------------------------------------------------------------ // Module: utils - Configuration Flags //------------------------------------------------------------------------------------ +// Standard file io library (stdio.h) included +#define SUPPORT_STANDARD_FILEIO // Show TRACELOG() output messages // NOTE: By default LOG_DEBUG traces not shown #define SUPPORT_TRACELOG 1 @@ -217,6 +209,3 @@ //------------------------------------------------------------------------------------ #define MAX_TRACELOG_MSG_LENGTH 128 // Max length of one trace-log message #define MAX_UWP_MESSAGES 512 // Max UWP messages to process - - -#endif //defined(RAYLIB_CMAKE) diff --git a/raylib/core.c b/raylib/core.c index 9083d08..e84d82c 100644 --- a/raylib/core.c +++ b/raylib/core.c @@ -9,6 +9,7 @@ * - PLATFORM_DESKTOP: OSX/macOS * - PLATFORM_ANDROID: Android 4.0 (ARM, ARM64) * - PLATFORM_RPI: Raspberry Pi 0,1,2,3,4 (Raspbian) +* - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver * - PLATFORM_WEB: HTML5 with asm.js (Chrome, Firefox) * - PLATFORM_UWP: Windows 10 App, Windows Phone, Xbox One * @@ -55,14 +56,14 @@ * WARNING: Reconfiguring standard input could lead to undesired effects, like breaking other running processes or * blocking the device is not restored properly. Use with care. * -* #define SUPPORT_MOUSE_CURSOR_RPI (Raspberry Pi only) -* Draw a mouse reference on screen (square cursor box) +* #define SUPPORT_MOUSE_CURSOR_NATIVE (Raspberry Pi and DRM only) +* Draw a mouse pointer on screen * * #define SUPPORT_BUSY_WAIT_LOOP * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used * -* #define SUPPORT_HALFBUSY_WAIT_LOOP -* Use a half-busy wait loop, in this case frame sleeps for some time and runs a busy-wait-loop at the end +* #define SUPPORT_PARTIALBUSY_WAIT_LOOP +* Use a partial-busy wait loop, in this case frame sleeps for most of the time and runs a busy-wait-loop at the end * * #define SUPPORT_EVENTS_WAITING * Wait for events passively (sleeping while no events) instead of polling them actively every frame @@ -73,17 +74,14 @@ * #define SUPPORT_GIF_RECORDING * Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() * -* #define SUPPORT_HIGH_DPI -* Allow scale all the drawn content to match the high-DPI equivalent size (only PLATFORM_DESKTOP) -* NOTE: This flag is forced on macOS, since most displays are high-DPI -* * #define SUPPORT_COMPRESSION_API * Support CompressData() and DecompressData() functions, those functions use zlib implementation * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module * for linkage * * #define SUPPORT_DATA_STORAGE -* Support saving binary data automatically to a generated storage.data file. This file is managed internally. +* Support saving binary data automatically to a generated storage.data file. This file is managed internally +* * * DEPENDENCIES: * rglfw - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly) @@ -94,7 +92,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -118,8 +116,6 @@ // Check if config flags have been externally provided on compilation line #if !defined(EXTERNAL_CONFIG_FLAGS) #include "config.h" // Defines module configuration flags -#else - #define RAYLIB_VERSION "3.1-dev" #endif #include "utils.h" // Required for: TRACELOG macros @@ -146,18 +142,26 @@ #endif #if defined(SUPPORT_GIF_RECORDING) - #define RGIF_MALLOC RL_MALLOC - #define RGIF_FREE RL_FREE + //#define MSF_GIF_MALLOC RL_MALLOC + //#define MSF_GIF_FREE RL_FREE - #define RGIF_IMPLEMENTATION - #include "external/rgif.h" // Support GIF recording + #define MSF_GIF_IMPL + #include "external/msf_gif.h" // Support GIF recording +#endif + +#if defined(SUPPORT_COMPRESSION_API) + #define SINFL_IMPLEMENTATION + #include "external/sinfl.h" + + #define SDEFL_IMPLEMENTATION + #include "external/sdefl.h" #endif #include // Required for: srand(), rand(), atexit() #include // Required for: sprintf() [Used in OpenURL()] #include // Required for: strrchr(), strcmp(), strlen() #include // Required for: time() [Used in InitTimer()] -#include // Required for: tan() [Used in BeginMode3D()] +#include // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] #include // Required for: stat() [Used in GetFileModTime()] @@ -184,32 +188,33 @@ #if defined(PLATFORM_DESKTOP) #define GLFW_INCLUDE_NONE // Disable the standard OpenGL header inclusion on GLFW3 // NOTE: Already provided by rlgl implementation (on glad.h) - #include // GLFW3 library: Windows, OpenGL context and Input management + #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management // NOTE: GLFW3 already includes gl.h (OpenGL) headers // Support retrieving native window handlers #if defined(_WIN32) #define GLFW_EXPOSE_NATIVE_WIN32 - #include // WARNING: It requires customization to avoid windows.h inclusion! + #include "GLFW/glfw3native.h" // WARNING: It requires customization to avoid windows.h inclusion! - #if !defined(SUPPORT_BUSY_WAIT_LOOP) + #if defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) // NOTE: Those functions require linking with winmm library unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); #endif - - #elif defined(__linux__) + #endif + #if defined(__linux__) || defined(__FreeBSD__) #include // Required for: timespec, nanosleep(), select() - POSIX //#define GLFW_EXPOSE_NATIVE_X11 // WARNING: Exposing Xlib.h > X.h results in dup symbols for Font type //#define GLFW_EXPOSE_NATIVE_WAYLAND //#define GLFW_EXPOSE_NATIVE_MIR - #include // Required for: glfwGetX11Window() - #elif defined(__APPLE__) + #include "GLFW/glfw3native.h" // Required for: glfwGetX11Window() + #endif + #if defined(__APPLE__) #include // Required for: usleep() //#define GLFW_EXPOSE_NATIVE_COCOA // WARNING: Fails due to type redefinition - #include // Required for: glfwGetCocoaWindow() + #include "GLFW/glfw3native.h" // Required for: glfwGetCocoaWindow() #endif #endif @@ -218,39 +223,47 @@ #include // Defines AWINDOW_FLAG_FULLSCREEN and others #include // Defines basic app state struct and manages activity - #include // EGL library - Native platform display device control functions - #include // OpenGL ES 2.0 library + #include // Native platform windowing system interface + //#include // OpenGL ES 2.0 library (not required in this module) #endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) #include // POSIX file control definitions - open(), creat(), fcntl() #include // POSIX standard function definitions - read(), close(), STDIN_FILENO #include // POSIX terminal control definitions - tcgetattr(), tcsetattr() #include // POSIX threads management (inputs reading) #include // POSIX directory browsing - + #include // UNIX System call for device-specific input/output operations - ioctl() #include // Linux: KDSKBMODE, K_MEDIUMRAM constants definition #include // Linux: Keycodes constants definition (KEY_A, ...) #include // Linux: Joystick support library - + +#if defined(PLATFORM_RPI) #include "bcm_host.h" // Raspberry Pi VideoCore IV access functions - - #include "EGL/egl.h" // EGL library - Native platform display device control functions - #include "EGL/eglext.h" // EGL library - Extensions - #include "GLES2/gl2.h" // OpenGL ES 2.0 library +#endif + +#if defined(PLATFORM_DRM) + #include // Generic Buffer Management + #include // Direct Rendering Manager user-level library interface + #include // Direct Rendering Manager modesetting interface +#endif + + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions + //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module) #endif #if defined(PLATFORM_UWP) - #include "EGL/egl.h" // EGL library - Native platform display device control functions - #include "EGL/eglext.h" // EGL library - Extensions - #include "GLES2/gl2.h" // OpenGL ES 2.0 library + #include "EGL/egl.h" // Native platform windowing system interface + #include "EGL/eglext.h" // EGL extensions + //#include "GLES2/gl2.h" // OpenGL ES 2.0 library (not required in this module) #include "uwp_events.h" // UWP bootstrapping functions #endif #if defined(PLATFORM_WEB) #define GLFW_INCLUDE_ES2 // GLFW3: Enable OpenGL ES 2.0 (translated to WebGL) - #include // GLFW3 library: Windows, OpenGL context and Input management + #include "GLFW/glfw3.h" // GLFW3 library: Windows, OpenGL context and Input management #include // Required for: timespec, nanosleep(), select() - POSIX #include // Emscripten library - LLVM to JavaScript compiler @@ -266,7 +279,7 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) #define USE_LAST_TOUCH_DEVICE // When multiple touchscreens are connected, only use the one with the highest event number #define DEFAULT_GAMEPAD_DEV "/dev/input/js" // Gamepad input (base dev for all gamepads: js0, js1, ...) @@ -294,7 +307,10 @@ #define MAX_TOUCH_POINTS 10 // Maximum number of touch points supported #endif #ifndef MAX_KEY_PRESSED_QUEUE - #define MAX_KEY_PRESSED_QUEUE 16 // Max number of characters in the key input queue + #define MAX_KEY_PRESSED_QUEUE 16 // Max number of keys in the key input queue +#endif +#ifndef MAX_CHAR_PRESSED_QUEUE + #define MAX_CHAR_PRESSED_QUEUE 16 // Max number of characters in the char input queue #endif #if defined(SUPPORT_DATA_STORAGE) @@ -303,10 +319,20 @@ #endif #endif +#ifndef MAX_DECOMPRESSION_SIZE + #define MAX_DECOMPRESSION_SIZE 64 // Max size allocated for decompression in MB +#endif + +// Flags operation macros +#define FLAG_SET(n, f) ((n) |= (f)) +#define FLAG_CLEAR(n, f) ((n) &= ~(f)) +#define FLAG_TOGGLE(n, f) ((n) ^= (f)) +#define FLAG_CHECK(n, f) ((n) & (f)) + //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) typedef struct { pthread_t threadId; // Event reading thread id int fd; // File descriptor to the device it is assigned to @@ -319,12 +345,6 @@ typedef struct { bool isKeyboard; // True if device has letter keycodes bool isGamepad; // True if device has gamepad buttons } InputEventWorker; - -typedef struct { - int contents[8]; // Key events FIFO contents (8 positions) - char head; // Key events FIFO head position - char tail; // Key events FIFO tail position -} KeyEventFifo; #endif typedef struct { int x; int y; } Point; @@ -337,25 +357,30 @@ typedef struct CoreData { GLFWwindow *handle; // Native window handle (graphic device) #endif #if defined(PLATFORM_RPI) - // NOTE: RPI4 does not support Dispmanx anymore, system should be redesigned EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DRM) + int fd; // /dev/dri/... file descriptor + drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector + int modeIndex; // index of the used mode of connector->modes + drmModeCrtc *crtc; // crt controller + struct gbm_device *gbmDevice; // device of Generic Buffer Management (GBM, native platform for EGL on DRM) + struct gbm_surface *gbmSurface; // surface of GBM + struct gbm_bo *prevBO; // previous used GBM buffer object (during frame swapping) + uint32_t prevFB; // previous used GBM framebufer (during frame swapping) +#endif EGLDisplay device; // Native display device (physical screen connection) EGLSurface surface; // Surface to draw on, framebuffers (connected to context) EGLContext context; // Graphic context, mode in which drawing can be done EGLConfig config; // Graphic config #endif - unsigned int flags; // Configuration flags (bit based) const char *title; // Window text title const pointer - bool ready; // Flag to check if window has been initialized successfully - bool minimized; // Flag to check if window has been minimized - bool maximized; // Flag to check if window has been maximized - bool focused; // Flag to check if window has been focused - bool resized; // Flag to check if window has been resized - bool fullscreen; // Flag to check if fullscreen mode required - bool alwaysRun; // Flag to keep window update/draw running on minimized - bool shouldClose; // Flag to set window for closing + unsigned int flags; // Configuration flags (bit based), keeps window state + bool ready; // Check if window has been initialized successfully + bool fullscreen; // Check if fullscreen mode is enabled + bool shouldClose; // Check if window set for closing + bool resizedLastFrame; // Check if window has been resized last frame Point position; // Window position on screen (required on fullscreen toggle) Size display; // Display width and height (monitor, device-screen, LCD, ...) @@ -384,7 +409,7 @@ typedef struct CoreData { } UWP; #endif struct { -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) InputEventWorker eventWorker[10]; // List of worker threads for every monitored "/dev/input/event" #endif struct { @@ -392,12 +417,16 @@ typedef struct CoreData { char currentKeyState[512]; // Registers current frame key state char previousKeyState[512]; // Registers previous frame key state - int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input characters queue - int keyPressedQueueCount; // Input characters queue count -#if defined(PLATFORM_RPI) + int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue + int keyPressedQueueCount; // Input keys queue count + + int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue + int charPressedQueueCount; // Input characters queue count + +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) int defaultMode; // Default keyboard mode struct termios defaultSettings; // Default keyboard settings - KeyEventFifo lastKeyPressed; // Buffer for holding keydown events as they arrive (Needed due to multitreading of event workers) + int fd; // File descriptor for the evdev keyboard #endif } Keyboard; struct { @@ -405,15 +434,16 @@ typedef struct CoreData { Vector2 offset; // Mouse offset Vector2 scale; // Mouse scaling + int cursor; // Tracks current mouse cursor bool cursorHidden; // Track if cursor is hidden bool cursorOnScreen; // Tracks if cursor is inside client area - char currentButtonState[3]; // Registers current mouse button state - char previousButtonState[3]; // Registers previous mouse button state - int currentWheelMove; // Registers current mouse wheel variation - int previousWheelMove; // Registers previous mouse wheel variation -#if defined(PLATFORM_RPI) - char currentButtonStateEvdev[3]; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update) + char currentButtonState[MOUSE_BUTTON_MAX]; // Registers current mouse button state + char previousButtonState[MOUSE_BUTTON_MAX]; // Registers previous mouse button state + float currentWheelMove; // Registers current mouse wheel variation + float previousWheelMove; // Registers previous mouse wheel variation +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + char currentButtonStateEvdev[MOUSE_BUTTON_MAX]; // Holds the new mouse state for the next polling event to grab (Can't be written directly due to multithreading, app could miss the update) #endif } Mouse; struct { @@ -424,13 +454,11 @@ typedef struct CoreData { struct { int lastButtonPressed; // Register last gamepad button pressed int axisCount; // Register number of available gamepad axis -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state char currentState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state char previousState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state -#endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) pthread_t threadId; // Gamepad reading thread id int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor char name[64]; // Gamepad name holder @@ -444,7 +472,7 @@ typedef struct CoreData { double draw; // Time measure for frame draw double frame; // Time measure for one frame double target; // Desired time for one frame, if 0 not applied -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) unsigned long long base; // Base time measure for hi-res timer #endif } Time; @@ -465,6 +493,7 @@ static int screenshotCounter = 0; // Screenshots counter #if defined(SUPPORT_GIF_RECORDING) static int gifFramesCounter = 0; // GIF frames counter static bool gifRecording = false; // GIF recording state +static MsfGifState gifState = { 0 }; // MSGIF context state #endif //----------------------------------------------------------------------------------- @@ -482,27 +511,30 @@ extern void UnloadFontDefault(void); // [Module: text] Unloads default fo static bool InitGraphicsDevice(int width, int height); // Initialize graphics device static void SetupFramebuffer(int width, int height); // Setup main framebuffer static void SetupViewport(int width, int height); // Set viewport for a provided width and height -static void SwapBuffers(void); // Copy back buffer to front buffers +static void SwapBuffers(void); // Copy back buffer to front buffer static void InitTimer(void); // Initialize timer static void Wait(float ms); // Wait for some milliseconds (stop program execution) -static int GetGamepadButton(int button); // Get gamepad button generic to all platforms static void PollInputEvents(void); // Register user events #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) static void ErrorCallback(int error, const char *description); // GLFW3 Error Callback, runs on GLFW3 error -static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed -static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed -static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move -static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) -static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel -static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area +// Window callbacks events static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +#if !defined(PLATFORM_WEB) +static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +#endif static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus static void WindowDropCallback(GLFWwindow *window, int count, const char **paths); // GLFW3 Window Drop Callback, runs when drop files into window -static void WindowMaximizeCallback(GLFWwindow *window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized +// Input callbacks events +static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods); // GLFW3 Keyboard Callback, runs on key pressed +static void CharCallback(GLFWwindow *window, unsigned int key); // GLFW3 Char Key Callback, runs on key pressed (get char value) +static void MouseButtonCallback(GLFWwindow *window, int button, int action, int mods); // GLFW3 Mouse Button Callback, runs on mouse button pressed +static void MouseCursorPosCallback(GLFWwindow *window, double x, double y); // GLFW3 Cursor Position Callback, runs on mouse move +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel +static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area #endif #if defined(PLATFORM_ANDROID) @@ -511,15 +543,11 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) #endif #if defined(PLATFORM_WEB) -static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); -static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const void *reserved, void *userData); -static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData); -static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData); #endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) #if defined(SUPPORT_SSH_KEYBOARD_RPI) static void InitKeyboard(void); // Init raw keyboard system (standard input reading) static void ProcessKeyboard(void); // Process keyboard events @@ -530,15 +558,23 @@ static void RestoreTerminal(void); // Restore terminal #endif static void InitEvdevInput(void); // Evdev inputs initialization -static void EventThreadSpawn(char *device); // Identifies a input device and spawns a thread to handle it if needed +static void ConfigureEvdevDevice(char *device); // Identifies a input device and configures it for use if appropriate +static void PollKeyboardEvents(void); // Process evdev keyboard events. static void *EventThread(void *arg); // Input device events reading thread static void InitGamepad(void); // Init raw gamepad input static void *GamepadThread(void *arg); // Mouse reading thread -#endif // PLATFORM_RPI + +#if defined(PLATFORM_DRM) +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode); // Search matching DRM mode in connector's mode list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search exactly matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced); // Search the nearest matching DRM connector mode in connector's list +#endif + +#endif // PLATFORM_RPI || PLATFORM_DRM #if defined(_WIN32) - // NOTE: We include Sleep() function signature here to avoid windows.h inclusion + // NOTE: We include Sleep() function signature here to avoid windows.h inclusion (kernel32 lib) void __stdcall Sleep(unsigned long msTimeout); // Required for Wait() #endif @@ -549,17 +585,15 @@ static void *GamepadThread(void *arg); // Mouse reading thread #if defined(PLATFORM_ANDROID) // To allow easier porting to android, we allow the user to define a // main function which we call from android_main, defined by ourselves -//extern int main(int argc, char *argv[]); -extern void android_run(); +extern int main(int argc, char *argv[]); void android_main(struct android_app *app) { char arg0[] = "raylib"; // NOTE: argv[] are mutable CORE.Android.app = app; - (void)android_run(); // TODO: Should we maybe report != 0 return codes somewhere? - //(void)main(1, (char *[]) { arg0, NULL }); + (void)main(1, (char *[]) { arg0, NULL }); } // TODO: Add this to header (if apps really need it) @@ -568,7 +602,7 @@ struct android_app *GetAndroidApp(void) return CORE.Android.app; } #endif -#if defined(PLATFORM_RPI) && !defined(SUPPORT_SSH_KEYBOARD_RPI) +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && !defined(SUPPORT_SSH_KEYBOARD_RPI) // Init terminal (block echo and signal short cuts) static void InitTerminal(void) { @@ -630,6 +664,7 @@ void InitWindow(int width, int height, const char *title) // Initialize required global values different than 0 CORE.Input.Keyboard.exitKey = KEY_ESCAPE; CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; + CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; CORE.Input.Gamepad.lastButtonPressed = -1; #if defined(PLATFORM_UWP) @@ -695,13 +730,13 @@ void InitWindow(int width, int height, const char *title) //if (CORE.Android.app->destroyRequested != 0) CORE.Window.shouldClose = true; } } -#else +#endif +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) || defined(PLATFORM_DRM) // Init graphics device (display device and OpenGL context) // NOTE: returns true if window and graphic device has been initialized successfully CORE.Window.ready = InitGraphicsDevice(width, height); if (!CORE.Window.ready) return; - else CORE.Window.focused = true; // Init hi-res timer InitTimer(); @@ -713,13 +748,19 @@ void InitWindow(int width, int height, const char *title) Rectangle rec = GetFontDefault().recs[95]; // NOTE: We setup a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); +#else + // Set default internal texture (1px white) and rectangle to be used for shapes drawing + SetShapesTexture(rlGetTextureDefault(), (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); #endif -#if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) - // Set default font texture filter for HighDPI (blurry) - SetTextureFilter(GetFontDefault().texture, FILTER_BILINEAR); +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // Set default font texture filter for HighDPI (blurry) + SetTextureFilter(GetFontDefault().texture, TEXTURE_FILTER_BILINEAR); + } #endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) // Init raw input system InitEvdevInput(); // Evdev inputs initialization InitGamepad(); // Gamepad init @@ -732,14 +773,12 @@ void InitWindow(int width, int height, const char *title) #if defined(PLATFORM_WEB) // Detect fullscreen change events - emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); + //emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); + //emscripten_set_resize_callback("#canvas", NULL, 1, EmscriptenResizeCallback); // Support keyboard events //emscripten_set_keypress_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); - emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); - - // Support mouse events - emscripten_set_click_callback("#canvas", NULL, 1, EmscriptenMouseCallback); + //emscripten_set_keydown_callback("#canvas", NULL, 1, EmscriptenKeyboardCallback); // Support touch events emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenTouchCallback); @@ -763,7 +802,8 @@ void CloseWindow(void) #if defined(SUPPORT_GIF_RECORDING) if (gifRecording) { - GifEnd(); + MsfGifResult result = msf_gif_end(&gifState); + msf_gif_free(result); gifRecording = false; } #endif @@ -779,16 +819,63 @@ void CloseWindow(void) glfwTerminate(); #endif -#if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) && !defined(PLATFORM_UWP) +#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) timeEndPeriod(1); // Restore time period #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DRM) + if (CORE.Window.prevFB) + { + drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); + CORE.Window.prevFB = 0; + } + + if (CORE.Window.prevBO) + { + gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); + CORE.Window.prevBO = NULL; + } + + if (CORE.Window.gbmSurface) + { + gbm_surface_destroy(CORE.Window.gbmSurface); + CORE.Window.gbmSurface = NULL; + } + + if (CORE.Window.gbmDevice) + { + gbm_device_destroy(CORE.Window.gbmDevice); + CORE.Window.gbmDevice = NULL; + } + + if (CORE.Window.crtc) + { + if (CORE.Window.connector) + { + drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, CORE.Window.crtc->buffer_id, + CORE.Window.crtc->x, CORE.Window.crtc->y, &CORE.Window.connector->connector_id, 1, &CORE.Window.crtc->mode); + drmModeFreeConnector(CORE.Window.connector); + CORE.Window.connector = NULL; + } + + drmModeFreeCrtc(CORE.Window.crtc); + CORE.Window.crtc = NULL; + } + + if (CORE.Window.fd != -1) + { + close(CORE.Window.fd); + CORE.Window.fd = -1; + } +#endif + // Close surface, context and display if (CORE.Window.device != EGL_NO_DISPLAY) { +#if !defined(PLATFORM_DRM) eglMakeCurrent(CORE.Window.device, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - +#endif if (CORE.Window.surface != EGL_NO_SURFACE) { eglDestroySurface(CORE.Window.device, CORE.Window.surface); @@ -806,13 +893,20 @@ void CloseWindow(void) } #endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) // Wait for mouse and gamepad threads to finish before closing // NOTE: Those threads should already have finished at this point // because they are controlled by CORE.Window.shouldClose variable CORE.Window.shouldClose = true; // Added to force threads to exit when the close window is called + // Close the evdev keyboard + if (CORE.Input.Keyboard.fd != -1) + { + close(CORE.Input.Keyboard.fd); + CORE.Input.Keyboard.fd = -1; + } + for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) { if (CORE.Input.eventWorker[i].threadId) @@ -821,18 +915,14 @@ void CloseWindow(void) } } + if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL); #endif + CORE.Window.ready = false; TRACELOG(LOG_INFO, "Window closed successfully"); } -// Check if window has been initialized successfully -bool IsWindowReady(void) -{ - return CORE.Window.ready; -} - // Check if KEY_ESCAPE pressed or Close icon pressed bool WindowShouldClose(void) { @@ -850,10 +940,10 @@ bool WindowShouldClose(void) if (CORE.Window.ready) { // While window minimized, stop loop execution - while (!CORE.Window.alwaysRun && CORE.Window.minimized) glfwWaitEvents(); + while (IsWindowState(FLAG_WINDOW_MINIMIZED) && !IsWindowState(FLAG_WINDOW_ALWAYS_RUN)) glfwWaitEvents(); CORE.Window.shouldClose = glfwWindowShouldClose(CORE.Window.handle); - + // Reset close status for next frame glfwSetWindowShouldClose(CORE.Window.handle, GLFW_FALSE); @@ -862,17 +952,38 @@ bool WindowShouldClose(void) else return true; #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) if (CORE.Window.ready) return CORE.Window.shouldClose; else return true; #endif } +// Check if window has been initialized successfully +bool IsWindowReady(void) +{ + return CORE.Window.ready; +} + +// Check if window is currently fullscreen +bool IsWindowFullscreen(void) +{ + return CORE.Window.fullscreen; +} + +// Check if window is currently hidden +bool IsWindowHidden(void) +{ +#if defined(PLATFORM_DESKTOP) + return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); +#endif + return false; +} + // Check if window has been minimized bool IsWindowMinimized(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) - return CORE.Window.minimized; + return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); #else return false; #endif @@ -882,7 +993,7 @@ bool IsWindowMinimized(void) bool IsWindowMaximized(void) { #if defined(PLATFORM_DESKTOP) - return CORE.Window.maximized; + return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); #else return false; #endif @@ -892,35 +1003,26 @@ bool IsWindowMaximized(void) bool IsWindowFocused(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) - return CORE.Window.focused; + return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); // TODO! #else - return false; + return true; #endif } -// Check if window has been resized +// Check if window has been resizedLastFrame bool IsWindowResized(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) - return CORE.Window.resized; + return CORE.Window.resizedLastFrame; #else return false; #endif } -// Check if window is currently hidden -bool IsWindowHidden(void) +// Check if one specific window flag is enabled +bool IsWindowState(unsigned int flag) { -#if defined(PLATFORM_DESKTOP) - return (glfwGetWindowAttrib(CORE.Window.handle, GLFW_VISIBLE) == GLFW_FALSE); -#endif - return false; -} - -// Check if window is currently fullscreen -bool IsWindowFullscreen(void) -{ - return CORE.Window.fullscreen; + return ((CORE.Window.flags & flag) > 0); } // Toggle fullscreen mode (only PLATFORM_DESKTOP) @@ -933,62 +1035,343 @@ void ToggleFullscreen(void) // Store previous window position (in case we exit fullscreen) glfwGetWindowPos(CORE.Window.handle, &CORE.Window.position.x, &CORE.Window.position.y); - GLFWmonitor *monitor = glfwGetPrimaryMonitor(); + int monitorCount = 0; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + int monitorIndex = GetCurrentMonitor(); + + // Use current monitor, so we correctly get the display the window is on + GLFWmonitor* monitor = monitorIndex < monitorCount ? monitors[monitorIndex] : NULL; + if (!monitor) { TRACELOG(LOG_WARNING, "GLFW: Failed to get monitor"); - glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, NULL, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); return; } - const GLFWvidmode *mode = glfwGetVideoMode(monitor); - glfwSetWindowMonitor(CORE.Window.handle, glfwGetPrimaryMonitor(), 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, mode->refreshRate); + CORE.Window.fullscreen = true; // Toggle fullscreen flag + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; - // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) - // NOTE: V-Sync can be enabled by graphic driver configuration - if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); + glfwSetWindowMonitor(CORE.Window.handle, monitor, 0, 0, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); } - else glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + else + { + CORE.Window.fullscreen = false; // Toggle fullscreen flag + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + + glfwSetWindowMonitor(CORE.Window.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + } + + // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) + // NOTE: V-Sync can be enabled by graphic driver configuration + if (CORE.Window.flags & FLAG_VSYNC_HINT) glfwSwapInterval(1); #endif #if defined(PLATFORM_WEB) - /* - EM_ASM( + EM_ASM + ( + // This strategy works well while using raylib minimal web shell for emscripten, + // it re-scales the canvas to fullscreen using monitor resolution, for tools this + // is a good strategy but maybe games prefer to keep current canvas resolution and + // display it in fullscreen, adjusting monitor resolution if possible if (document.fullscreenElement) document.exitFullscreen(); - else Module.requestFullscreen(true, true); + else Module.requestFullscreen(false, true); ); - */ - - //EM_ASM(Module.requestFullscreen(false, false);); - /* +/* if (!CORE.Window.fullscreen) { - //https://github.com/emscripten-core/emscripten/issues/5124 + // Option 1: Request fullscreen for the canvas element + // This option does not seem to work at all + //emscripten_request_fullscreen("#canvas", false); + + // Option 2: Request fullscreen for the canvas element with strategy + // This option does not seem to work at all + // Ref: https://github.com/emscripten-core/emscripten/issues/5124 + // EmscriptenFullscreenStrategy strategy = { + // .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + // .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, + // .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, + // .canvasResizedCallback = EmscriptenWindowResizedCallback, + // .canvasResizedCallbackUserData = NULL + // }; + //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); + + // Option 3: Request fullscreen for the canvas element with strategy + // It works as expected but only inside the browser (client area) EmscriptenFullscreenStrategy strategy = { - .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH, //EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, + .scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_ASPECT, .canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF, .filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT, - .canvasResizedCallback = EmscriptenWindowResizedCallback, //on_canvassize_changed, + .canvasResizedCallback = EmscriptenWindowResizedCallback, .canvasResizedCallbackUserData = NULL }; - - emscripten_request_fullscreen("#canvas", false); - //emscripten_request_fullscreen_strategy("#canvas", EM_FALSE, &strategy); - //emscripten_enter_soft_fullscreen("canvas", &strategy); - TRACELOG(LOG_INFO, "emscripten_request_fullscreen_strategy"); + emscripten_enter_soft_fullscreen("#canvas", &strategy); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Enter fullscreen: Canvas size: %i x %i", width, height); } - else + else { - TRACELOG(LOG_INFO, "emscripten_exit_fullscreen"); - emscripten_exit_fullscreen(); + //emscripten_exit_fullscreen(); + emscripten_exit_soft_fullscreen(); + + int width, height; + emscripten_get_canvas_element_size("#canvas", &width, &height); + TRACELOG(LOG_WARNING, "Emscripten: Exit fullscreen: Canvas size: %i x %i", width, height); } - */ -#endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); -#endif +*/ CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag + CORE.Window.flags ^= FLAG_FULLSCREEN_MODE; +#endif +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + TRACELOG(LOG_WARNING, "SYSTEM: Failed to toggle to windowed mode"); +#endif +} + +// Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +void MaximizeWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + glfwMaximizeWindow(CORE.Window.handle); + CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; + } +#endif +} + +// Set window state: minimized (only PLATFORM_DESKTOP) +void MinimizeWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + // NOTE: Following function launches callback that sets appropiate flag! + glfwIconifyWindow(CORE.Window.handle); +#endif +} + +// Set window state: not minimized/maximized (only PLATFORM_DESKTOP) +void RestoreWindow(void) +{ +#if defined(PLATFORM_DESKTOP) + if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) + { + // Restores the specified window if it was previously iconified (minimized) or maximized + glfwRestoreWindow(CORE.Window.handle); + CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + } +#endif +} + +// Set window configuration state using flags +void SetWindowState(unsigned int flags) +{ +#if defined(PLATFORM_DESKTOP) + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (((CORE.Window.flags & FLAG_VSYNC_HINT) != (flags & FLAG_VSYNC_HINT)) && ((flags & FLAG_VSYNC_HINT) > 0)) + { + glfwSwapInterval(1); + CORE.Window.flags |= FLAG_VSYNC_HINT; + } + + // State change: FLAG_FULLSCREEN_MODE + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) != (flags & FLAG_WINDOW_RESIZABLE)) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_RESIZABLE; + } + + // State change: FLAG_WINDOW_UNDECORATED + if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) != (flags & FLAG_WINDOW_UNDECORATED)) && (flags & FLAG_WINDOW_UNDECORATED)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); + CORE.Window.flags |= FLAG_WINDOW_UNDECORATED; + } + + // State change: FLAG_WINDOW_HIDDEN + if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) != (flags & FLAG_WINDOW_HIDDEN)) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + { + glfwHideWindow(CORE.Window.handle); + CORE.Window.flags |= FLAG_WINDOW_HIDDEN; + } + + // State change: FLAG_WINDOW_MINIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) != (flags & FLAG_WINDOW_MINIMIZED)) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + { + //GLFW_ICONIFIED + MinimizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) != (flags & FLAG_WINDOW_MAXIMIZED)) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + { + //GLFW_MAXIMIZED + MaximizeWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) != (flags & FLAG_WINDOW_UNFOCUSED)) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_FALSE); + CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; + } + + // State change: FLAG_WINDOW_TOPMOST + if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) != (flags & FLAG_WINDOW_TOPMOST)) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_TRUE); + CORE.Window.flags |= FLAG_WINDOW_TOPMOST; + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) != (flags & FLAG_WINDOW_ALWAYS_RUN)) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + { + CORE.Window.flags |= FLAG_WINDOW_ALWAYS_RUN; + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) != (flags & FLAG_WINDOW_TRANSPARENT)) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) != (flags & FLAG_WINDOW_HIGHDPI)) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); + } + + // State change: FLAG_MSAA_4X_HINT + if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) != (flags & FLAG_MSAA_4X_HINT)) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if (((CORE.Window.flags & FLAG_INTERLACED_HINT) != (flags & FLAG_INTERLACED_HINT)) && ((flags & FLAG_INTERLACED_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); + } +#endif +} + +// Clear window configuration state flags +void ClearWindowState(unsigned int flags) +{ +#if defined(PLATFORM_DESKTOP) + // Check previous state and requested state to apply required changes + // NOTE: In most cases the functions already change the flags internally + + // State change: FLAG_VSYNC_HINT + if (((CORE.Window.flags & FLAG_VSYNC_HINT) > 0) && ((flags & FLAG_VSYNC_HINT) > 0)) + { + glfwSwapInterval(0); + CORE.Window.flags &= ~FLAG_VSYNC_HINT; + } + + // State change: FLAG_FULLSCREEN_MODE + if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) + { + ToggleFullscreen(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_RESIZABLE + if (((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) && ((flags & FLAG_WINDOW_RESIZABLE) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_RESIZABLE; + } + + // State change: FLAG_WINDOW_UNDECORATED + if (((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) && ((flags & FLAG_WINDOW_UNDECORATED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); + CORE.Window.flags &= ~FLAG_WINDOW_UNDECORATED; + } + + // State change: FLAG_WINDOW_HIDDEN + if (((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) && ((flags & FLAG_WINDOW_HIDDEN) > 0)) + { + glfwShowWindow(CORE.Window.handle); + CORE.Window.flags &= ~FLAG_WINDOW_HIDDEN; + } + + // State change: FLAG_WINDOW_MINIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) && ((flags & FLAG_WINDOW_MINIMIZED) > 0)) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_MAXIMIZED + if (((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) && ((flags & FLAG_WINDOW_MAXIMIZED) > 0)) + { + RestoreWindow(); // NOTE: Window state flag updated inside function + } + + // State change: FLAG_WINDOW_UNFOCUSED + if (((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) && ((flags & FLAG_WINDOW_UNFOCUSED) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FOCUS_ON_SHOW, GLFW_TRUE); + CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; + } + + // State change: FLAG_WINDOW_TOPMOST + if (((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) && ((flags & FLAG_WINDOW_TOPMOST) > 0)) + { + glfwSetWindowAttrib(CORE.Window.handle, GLFW_FLOATING, GLFW_FALSE); + CORE.Window.flags &= ~FLAG_WINDOW_TOPMOST; + } + + // State change: FLAG_WINDOW_ALWAYS_RUN + if (((CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) > 0) && ((flags & FLAG_WINDOW_ALWAYS_RUN) > 0)) + { + CORE.Window.flags &= ~FLAG_WINDOW_ALWAYS_RUN; + } + + // The following states can not be changed after window creation + + // State change: FLAG_WINDOW_TRANSPARENT + if (((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) && ((flags & FLAG_WINDOW_TRANSPARENT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: Framebuffer transparency can only by configured before window initialization"); + } + + // State change: FLAG_WINDOW_HIGHDPI + if (((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) && ((flags & FLAG_WINDOW_HIGHDPI) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: High DPI can only by configured before window initialization"); + } + + // State change: FLAG_MSAA_4X_HINT + if (((CORE.Window.flags & FLAG_MSAA_4X_HINT) > 0) && ((flags & FLAG_MSAA_4X_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "WINDOW: MSAA can only by configured before window initialization"); + } + + // State change: FLAG_INTERLACED_HINT + if (((CORE.Window.flags & FLAG_INTERLACED_HINT) > 0) && ((flags & FLAG_INTERLACED_HINT) > 0)) + { + TRACELOG(LOG_WARNING, "RPI: Interlaced mode can only by configured before window initialization"); + } +#endif } // Set icon for window (only PLATFORM_DESKTOP) @@ -996,7 +1379,7 @@ void ToggleFullscreen(void) void SetWindowIcon(Image image) { #if defined(PLATFORM_DESKTOP) - if (image.format == UNCOMPRESSED_R8G8B8A8) + if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) { GLFWimage icon[1] = { 0 }; @@ -1064,7 +1447,7 @@ void SetWindowSize(int width, int height) glfwSetWindowSize(CORE.Window.handle, width, height); #endif #if defined(PLATFORM_WEB) - emscripten_set_canvas_size(width, height); // DEPRECATED! + //emscripten_set_canvas_size(width, height); // DEPRECATED! // TODO: Below functions should be used to replace previous one but // they do not seem to work properly @@ -1073,70 +1456,16 @@ void SetWindowSize(int width, int height) #endif } -// Show the window -void UnhideWindow(void) -{ -#if defined(PLATFORM_DESKTOP) - glfwShowWindow(CORE.Window.handle); -#endif -} - -// Hide the window -void HideWindow(void) -{ -#if defined(PLATFORM_DESKTOP) - glfwHideWindow(CORE.Window.handle); -#endif -} - -// Decorate the window (only PLATFORM_DESKTOP) -void DecorateWindow(void) -{ -#if defined(PLATFORM_DESKTOP) - glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_TRUE); -#endif -} - -// // Undecorate the window (only PLATFORM_DESKTOP) -void UndecorateWindow(void) -{ -#if defined(PLATFORM_DESKTOP) - glfwSetWindowAttrib(CORE.Window.handle, GLFW_DECORATED, GLFW_FALSE); -#endif -} - -// Maximize the window, if resizable (only PLATFORM_DESKTOP) -void MaximizeWindow(void) -{ -#if defined(PLATFORM_DESKTOP) - if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) - { - glfwMaximizeWindow(CORE.Window.handle); - } -#endif -} - -// Restore the window, if resizable (only PLATFORM_DESKTOP) -void RestoreWindow(void) -{ -#if defined(PLATFORM_DESKTOP) - if (glfwGetWindowAttrib(CORE.Window.handle, GLFW_RESIZABLE) == GLFW_TRUE) - { - glfwRestoreWindow(CORE.Window.handle); - } -#endif -} - // Get current screen width int GetScreenWidth(void) { - return CORE.Window.screen.width; + return CORE.Window.currentFbo.width; } // Get current screen height int GetScreenHeight(void) { - return CORE.Window.screen.height; + return CORE.Window.currentFbo.height; } // Get native window handle @@ -1145,18 +1474,20 @@ void *GetWindowHandle(void) #if defined(PLATFORM_DESKTOP) && defined(_WIN32) // NOTE: Returned handle is: void *HWND (windows.h) return glfwGetWin32Window(CORE.Window.handle); -#elif defined(__linux__) +#endif +#if defined(__linux__) // NOTE: Returned handle is: unsigned long Window (X.h) // typedef unsigned long XID; // typedef XID Window; //unsigned long id = (unsigned long)glfwGetX11Window(window); return NULL; // TODO: Find a way to return value... cast to void *? -#elif defined(__APPLE__) +#endif +#if defined(__APPLE__) // NOTE: Returned handle is: (objc_object *) return NULL; // TODO: return (void *)glfwGetCocoaWindow(window); -#else - return NULL; #endif + + return NULL; } // Get number of monitors @@ -1171,7 +1502,74 @@ int GetMonitorCount(void) #endif } -// Get primary monitor width +// Get number of monitors +int GetCurrentMonitor(void) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + GLFWmonitor* monitor = NULL; + + if (monitorCount == 1) // easy out + return 0; + + if (IsWindowFullscreen()) + { + monitor = glfwGetWindowMonitor(CORE.Window.handle); + for (int i = 0; i < monitorCount; i++) + { + if (monitors[i] == monitor) + return i; + } + return 0; + } + else + { + int x = 0; + int y = 0; + + glfwGetWindowPos(CORE.Window.handle, &x, &y); + + for (int i = 0; i < monitorCount; i++) + { + int mx = 0; + int my = 0; + + int width = 0; + int height = 0; + + monitor = monitors[i]; + glfwGetMonitorWorkarea(monitor, &mx, &my, &width, &height); + if (x >= mx && x <= (mx + width) && y >= my && y <= (my + height)) + return i; + } + } + return 0; +#else + return 0; +#endif +} + +// Get selected monitor width +Vector2 GetMonitorPosition(int monitor) +{ +#if defined(PLATFORM_DESKTOP) + int monitorCount; + GLFWmonitor** monitors = glfwGetMonitors(&monitorCount); + + if ((monitor >= 0) && (monitor < monitorCount)) + { + int x, y; + glfwGetMonitorPos(monitors[monitor], &x, &y); + + return (Vector2){ (float)x, (float)y }; + } + else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif + return (Vector2){ 0, 0 }; +} + +// Get selected monitor width (max available by monitor) int GetMonitorWidth(int monitor) { #if defined(PLATFORM_DESKTOP) @@ -1180,15 +1578,19 @@ int GetMonitorWidth(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); - return mode->width; + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + + // We return the maximum resolution available, the last one in the modes array + if (count > 0) return modes[count - 1].width; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); } else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); #endif return 0; } -// Get primary monitor width +// Get selected monitor width (max available by monitor) int GetMonitorHeight(int monitor) { #if defined(PLATFORM_DESKTOP) @@ -1197,15 +1599,19 @@ int GetMonitorHeight(int monitor) if ((monitor >= 0) && (monitor < monitorCount)) { - const GLFWvidmode *mode = glfwGetVideoMode(monitors[monitor]); - return mode->height; + int count = 0; + const GLFWvidmode *modes = glfwGetVideoModes(monitors[monitor], &count); + + // We return the maximum resolution available, the last one in the modes array + if (count > 0) return modes[count - 1].height; + else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); } else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); #endif return 0; } -// Get primary montior physical width in millimetres +// Get selected monitor physical width in millimetres int GetMonitorPhysicalWidth(int monitor) { #if defined(PLATFORM_DESKTOP) @@ -1253,6 +1659,12 @@ int GetMonitorRefreshRate(int monitor) return vidmode->refreshRate; } else TRACELOG(LOG_WARNING, "GLFW: Failed to find selected monitor"); +#endif +#if defined(PLATFORM_DRM) + if ((CORE.Window.connector) && (CORE.Window.modeIndex >= 0)) + { + return CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh; + } #endif return 0; } @@ -1272,12 +1684,31 @@ Vector2 GetWindowPosition(void) Vector2 GetWindowScaleDPI(void) { Vector2 scale = { 1.0f, 1.0f }; - -#if defined(PLATFORM_DESKTOP) - GLFWmonitor *monitor = glfwGetPrimaryMonitor(); - if (monitor != NULL) glfwGetMonitorContentScale(monitor, &scale.x, &scale.y); - else TRACELOG(LOG_WARNING, "GLFW: Failed to get primary monitor"); +#if defined(PLATFORM_DESKTOP) + float xdpi = 1.0; + float ydpi = 1.0; + Vector2 windowPos = GetWindowPosition(); + + int monitorCount = 0; + GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); + + // Check window monitor + for (int i = 0; i < monitorCount; i++) + { + glfwGetMonitorContentScale(monitors[i], &xdpi, &ydpi); + + int xpos, ypos, width, height; + glfwGetMonitorWorkarea(monitors[i], &xpos, &ypos, &width, &height); + + if ((windowPos.x >= xpos) && (windowPos.x < xpos + width) && + (windowPos.y >= ypos) && (windowPos.y < ypos + height)) + { + scale.x = xdpi; + scale.y = ydpi; + break; + } + } #endif return scale; @@ -1354,6 +1785,9 @@ void EnableCursor(void) #if defined(PLATFORM_DESKTOP) glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL); #endif +#if defined(PLATFORM_WEB) + emscripten_exit_pointerlock(); +#endif #if defined(PLATFORM_UWP) UWPGetMouseUnlockFunc()(); #endif @@ -1366,6 +1800,9 @@ void DisableCursor(void) #if defined(PLATFORM_DESKTOP) glfwSetInputMode(CORE.Window.handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED); #endif +#if defined(PLATFORM_WEB) + emscripten_request_pointerlock("#canvas", 1); +#endif #if defined(PLATFORM_UWP) UWPGetMouseLockFunc()(); #endif @@ -1402,13 +1839,16 @@ void BeginDrawing(void) // End canvas drawing and swap buffers (double buffering) void EndDrawing(void) { -#if defined(PLATFORM_RPI) && defined(SUPPORT_MOUSE_CURSOR_RPI) - // On RPI native mode we have no system mouse cursor, so, +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_MOUSE_CURSOR_NATIVE) + // On native mode we have no system mouse cursor, so, // we draw a small rectangle for user reference - DrawRectangle(CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y, 3, 3, MAROON); + if (!CORE.Input.Mouse.cursorHidden) + { + DrawRectangle(CORE.Input.Mouse.position.x, CORE.Input.Mouse.position.y, 3, 3, MAROON); + } #endif - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlDrawRenderBatchActive(); // Update and draw internal render batch #if defined(SUPPORT_GIF_RECORDING) #define GIF_RECORD_FRAMERATE 10 @@ -1421,11 +1861,11 @@ void EndDrawing(void) if ((gifFramesCounter%GIF_RECORD_FRAMERATE) == 0) { // Get image data for the current frame (from backbuffer) - // NOTE: This process is very slow... :( + // NOTE: This process is quite slow... :( unsigned char *screenData = rlReadScreenPixels(CORE.Window.screen.width, CORE.Window.screen.height); - GifWriteFrame(screenData, CORE.Window.screen.width, CORE.Window.screen.height, 10, 8, false); + msf_gif_frame(&gifState, screenData, 10, 16, CORE.Window.screen.width*4); - RL_FREE(screenData); // Free image data + RL_FREE(screenData); // Free image data } if (((gifFramesCounter/15)%2) == 1) @@ -1434,12 +1874,11 @@ void EndDrawing(void) DrawText("RECORDING", 50, CORE.Window.screen.height - 25, 10, MAROON); } - rlglDraw(); // Draw RECORDING message + rlDrawRenderBatchActive(); // Update and draw internal render batch } #endif SwapBuffers(); // Copy back buffer to front buffer - PollInputEvents(); // Poll user events // Frame time control system CORE.Time.current = GetTime(); @@ -1459,14 +1898,16 @@ void EndDrawing(void) CORE.Time.frame += waitTime; // Total frame time: update + draw + wait } + + PollInputEvents(); // Poll user events } // Initialize 2D mode with custom camera (2D) void BeginMode2D(Camera2D camera) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlDrawRenderBatchActive(); // Update and draw internal render batch - rlLoadIdentity(); // Reset current matrix (modelview) + rlLoadIdentity(); // Reset current matrix (modelview) // Apply 2d camera transformation to modelview rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); @@ -1478,24 +1919,25 @@ void BeginMode2D(Camera2D camera) // Ends 2D mode with custom camera void EndMode2D(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlDrawRenderBatchActive(); // Update and draw internal render batch - rlLoadIdentity(); // Reset current matrix (modelview) + rlLoadIdentity(); // Reset current matrix (modelview) rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required } // Initializes 3D mode with custom camera (3D) void BeginMode3D(Camera3D camera) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlDrawRenderBatchActive(); // Update and draw internal render batch - rlMatrixMode(RL_PROJECTION); // Switch to projection matrix - rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection - rlLoadIdentity(); // Reset current matrix (projection) + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection + rlLoadIdentity(); // Reset current matrix (projection) float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; - if (camera.type == CAMERA_PERSPECTIVE) + // NOTE: zNear and zFar values are important when computing depth buffer values + if (camera.projection == CAMERA_PERSPECTIVE) { // Setup perspective projection double top = RL_CULL_DISTANCE_NEAR*tan(camera.fovy*0.5*DEG2RAD); @@ -1503,7 +1945,7 @@ void BeginMode3D(Camera3D camera) rlFrustum(-right, right, -top, top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); } - else if (camera.type == CAMERA_ORTHOGRAPHIC) + else if (camera.projection == CAMERA_ORTHOGRAPHIC) { // Setup orthographic projection double top = camera.fovy/2.0; @@ -1512,55 +1954,53 @@ void BeginMode3D(Camera3D camera) rlOrtho(-right, right, -top,top, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); } - // NOTE: zNear and zFar values are important when computing depth buffer values - - rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix - rlLoadIdentity(); // Reset current matrix (modelview) + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) // Setup Camera view Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) - rlEnableDepthTest(); // Enable DEPTH_TEST for 3D + rlEnableDepthTest(); // Enable DEPTH_TEST for 3D } // Ends 3D mode and returns to default 2D orthographic mode void EndMode3D(void) { - rlglDraw(); // Process internal buffers (update + draw) + rlDrawRenderBatchActive(); // Update and draw internal render batch - rlMatrixMode(RL_PROJECTION); // Switch to projection matrix - rlPopMatrix(); // Restore previous matrix (projection) from matrix stack + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlPopMatrix(); // Restore previous matrix (projection) from matrix stack - rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix - rlLoadIdentity(); // Reset current matrix (modelview) + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required - rlDisableDepthTest(); // Disable DEPTH_TEST for 2D + rlDisableDepthTest(); // Disable DEPTH_TEST for 2D } // Initializes render texture for drawing void BeginTextureMode(RenderTexture2D target) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlDrawRenderBatchActive(); // Update and draw internal render batch - rlEnableRenderTexture(target.id); // Enable render target + rlEnableFramebuffer(target.id); // Enable render target // Set viewport to framebuffer size rlViewport(0, 0, target.texture.width, target.texture.height); - rlMatrixMode(RL_PROJECTION); // Switch to projection matrix - rlLoadIdentity(); // Reset current matrix (projection) + rlMatrixMode(RL_PROJECTION); // Switch to projection matrix + rlLoadIdentity(); // Reset current matrix (projection) // Set orthographic projection to current framebuffer size // NOTE: Configured top-left corner as (0, 0) rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); - rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix - rlLoadIdentity(); // Reset current matrix (modelview) + rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix + rlLoadIdentity(); // Reset current matrix (modelview) - //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) + //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) // Setup current width/height for proper aspect ratio // calculation when using BeginMode3D() @@ -1571,23 +2011,48 @@ void BeginTextureMode(RenderTexture2D target) // Ends drawing to render texture void EndTextureMode(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlDrawRenderBatchActive(); // Update and draw internal render batch - rlDisableRenderTexture(); // Disable render target + rlDisableFramebuffer(); // Disable render target (fbo) // Set viewport to default framebuffer size SetupViewport(CORE.Window.render.width, CORE.Window.render.height); - // Reset current screen size - CORE.Window.currentFbo.width = GetScreenWidth(); - CORE.Window.currentFbo.height = GetScreenHeight(); + // Reset current fbo to screen size + CORE.Window.currentFbo.width = CORE.Window.screen.width; + CORE.Window.currentFbo.height = CORE.Window.screen.height; +} + +// Begin custom shader mode +void BeginShaderMode(Shader shader) +{ + rlSetShader(shader); +} + +// End custom shader mode (returns to default shader) +void EndShaderMode(void) +{ + rlSetShader(rlGetShaderDefault()); +} + +// Begin blending mode (alpha, additive, multiplied) +// NOTE: Only 3 blending modes supported, default blend mode is alpha +void BeginBlendMode(int mode) +{ + rlSetBlendMode(mode); +} + +// End blending mode (reset to default: alpha blending) +void EndBlendMode(void) +{ + rlSetBlendMode(BLEND_ALPHA); } // Begin scissor mode (define screen area for following drawing) // NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left void BeginScissorMode(int x, int y, int width, int height) { - rlglDraw(); // Force drawing elements + rlDrawRenderBatchActive(); // Update and draw internal render batch rlEnableScissorTest(); rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); @@ -1596,14 +2061,264 @@ void BeginScissorMode(int x, int y, int width, int height) // End scissor mode void EndScissorMode(void) { - rlglDraw(); // Force drawing elements + rlDrawRenderBatchActive(); // Update and draw internal render batch rlDisableScissorTest(); } +// Begin VR drawing configuration +void BeginVrStereoMode(VrStereoConfig config) +{ + rlEnableStereoRender(); + + // Set stereo render matrices + rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); + rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); +} + +// End VR drawing process (and desktop mirror) +void EndVrStereoMode(void) +{ + rlDisableStereoRender(); +} + +// Load VR stereo config for VR simulator device parameters +VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) +{ + VrStereoConfig config = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Compute aspect ratio + float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; + + // Compute lens parameters + float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; + config.leftLensCenter[0] = 0.25f + lensShift; + config.leftLensCenter[1] = 0.5f; + config.rightLensCenter[0] = 0.75f - lensShift; + config.rightLensCenter[1] = 0.5f; + config.leftScreenCenter[0] = 0.25f; + config.leftScreenCenter[1] = 0.5f; + config.rightScreenCenter[0] = 0.75f; + config.rightScreenCenter[1] = 0.5f; + + // Compute distortion scale parameters + // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] + float lensRadius = fabsf(-1.0f - 4.0f*lensShift); + float lensRadiusSq = lensRadius*lensRadius; + float distortionScale = device.lensDistortionValues[0] + + device.lensDistortionValues[1]*lensRadiusSq + + device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + + device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; + + float normScreenWidth = 0.5f; + float normScreenHeight = 1.0f; + config.scaleIn[0] = 2.0f/normScreenWidth; + config.scaleIn[1] = 2.0f/normScreenHeight/aspect; + config.scale[0] = normScreenWidth*0.5f/distortionScale; + config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; + + // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) + // ...but with lens distortion it is increased (see Oculus SDK Documentation) + //float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? + float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); + + // Compute camera projection matrices + float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] + Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + + config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); + config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); + + // Compute camera transformation matrices + // NOTE: Camera movement might seem more natural if we model the head. + // Our axis of rotation is the base of our head, so we might want to add + // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. + config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + + // Compute eyes Viewports + /* + config.eyeViewportRight[0] = 0; + config.eyeViewportRight[1] = 0; + config.eyeViewportRight[2] = device.hResolution/2; + config.eyeViewportRight[3] = device.vResolution; + + config.eyeViewportLeft[0] = device.hResolution/2; + config.eyeViewportLeft[1] = 0; + config.eyeViewportLeft[2] = device.hResolution/2; + config.eyeViewportLeft[3] = device.vResolution; + */ +#else + TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); +#endif + + return config; +} + +// Unload VR stereo config properties +void UnloadVrStereoConfig(VrStereoConfig config) +{ + //... +} + +// Load shader from files and bind default locations +// NOTE: If shader string is NULL, using default vertex/fragment shaders +Shader LoadShader(const char *vsFileName, const char *fsFileName) +{ + Shader shader = { 0 }; + shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + + char *vShaderStr = NULL; + char *fShaderStr = NULL; + + if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); + if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); + + shader.id = rlLoadShaderCode(vShaderStr, fShaderStr); + + if (vShaderStr != NULL) UnloadFileText(vShaderStr); + if (fShaderStr != NULL) UnloadFileText(fShaderStr); + + // After shader loading, we TRY to set default location names + if (shader.id > 0) + { + // Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // NOTE: If any location is not found, loc point becomes -1 + + // Get handles to GLSL input attibute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, "mvp"); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, "view"); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, "projection"); + shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, "matNormal"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, "colDiffuse"); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, "texture0"); + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, "texture1"); + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, "texture2"); + } + + return shader; +} + +// Load shader from code strings and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) +{ + Shader shader = { 0 }; + shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + + shader.id = rlLoadShaderCode(vsCode, fsCode); + + // After shader loading, we TRY to set default location names + if (shader.id > 0) + { + // Default shader attrib locations have been fixed before linking: + // vertex position location = 0 + // vertex texcoord location = 1 + // vertex normal location = 2 + // vertex color location = 3 + // vertex tangent location = 4 + // vertex texcoord2 location = 5 + + // NOTE: If any location is not found, loc point becomes -1 + + // Get handles to GLSL input attibute locations + shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Get handles to GLSL uniform locations (vertex shader) + shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, "mvp"); + shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, "projection"); + shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, "view"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, "colDiffuse"); + shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, "texture0"); + shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, "texture1"); + shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, "texture2"); + } + + return shader; +} + +// Unload shader from GPU memory (VRAM) +void UnloadShader(Shader shader) +{ + if (shader.id != rlGetShaderDefault().id) + { + rlUnloadShaderProgram(shader.id); + RL_FREE(shader.locs); + } +} + +// Get shader uniform location +int GetShaderLocation(Shader shader, const char *uniformName) +{ + return rlGetLocationUniform(shader.id, uniformName); +} + +// Get shader attribute location +int GetShaderLocationAttrib(Shader shader, const char *attribName) +{ + return rlGetLocationAttrib(shader.id, attribName); +} + +// Set shader uniform value +void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) +{ + SetShaderValueV(shader, locIndex, value, uniformType, 1); +} + +// Set shader uniform value vector +void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) +{ + rlEnableShader(shader.id); + rlSetUniform(locIndex, value, uniformType, count); + //rlDisableShader(); // Avoid reseting current shader program, in case other uniforms are set +} + +// Set shader uniform value (matrix 4x4) +void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) +{ + rlEnableShader(shader.id); + rlSetUniformMatrix(locIndex, mat); + //rlDisableShader(); +} + +// Set shader uniform value for texture +void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) +{ + rlEnableShader(shader.id); + rlSetUniformSampler(locIndex, texture.id); + //rlDisableShader(); +} + // Returns a ray trace from mouse position Ray GetMouseRay(Vector2 mouse, Camera camera) { - Ray ray; + Ray ray = { 0 }; // Calculate normalized device coordinates // NOTE: y value is negative @@ -1619,12 +2334,12 @@ Ray GetMouseRay(Vector2 mouse, Camera camera) Matrix matProj = MatrixIdentity(); - if (camera.type == CAMERA_PERSPECTIVE) + if (camera.projection == CAMERA_PERSPECTIVE) { // Calculate projection matrix from perspective matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)GetScreenWidth()/(double)GetScreenHeight()), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); } - else if (camera.type == CAMERA_ORTHOGRAPHIC) + else if (camera.projection == CAMERA_ORTHOGRAPHIC) { float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; double top = camera.fovy/2.0; @@ -1646,8 +2361,8 @@ Ray GetMouseRay(Vector2 mouse, Camera camera) // Calculate normalized direction vector Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); - if (camera.type == CAMERA_PERSPECTIVE) ray.position = camera.position; - else if (camera.type == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; + if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; + else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; // Apply calculated vectors to ray ray.direction = direction; @@ -1703,12 +2418,12 @@ Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int heigh // Calculate projection matrix (from perspective instead of frustum Matrix matProj = MatrixIdentity(); - if (camera.type == CAMERA_PERSPECTIVE) + if (camera.projection == CAMERA_PERSPECTIVE) { // Calculate projection matrix from perspective - matProj = MatrixPerspective(camera.fovy * DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); + matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); } - else if (camera.type == CAMERA_ORTHOGRAPHIC) + else if (camera.projection == CAMERA_ORTHOGRAPHIC) { float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; double top = camera.fovy/2.0; @@ -1793,7 +2508,7 @@ int GetFPS(void) return (int)roundf(1.0f/average); } -// Returns time in seconds for last frame drawn +// Returns time in seconds for last frame drawn (delta time) float GetFrameTime(void) { return (float)CORE.Time.frame; @@ -1808,7 +2523,7 @@ double GetTime(void) return glfwGetTime(); // Elapsed time since glfwInit() #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; @@ -1822,12 +2537,14 @@ double GetTime(void) } // Setup window configuration flags (view FLAGS) +// NOTE: This function is expected to be called before window creation, +// because it setups some flags for the window creation process. +// To configure window states after creation, just use SetWindowState() void SetConfigFlags(unsigned int flags) { - CORE.Window.flags = flags; - - if (CORE.Window.flags & FLAG_FULLSCREEN_MODE) CORE.Window.fullscreen = true; - if (CORE.Window.flags & FLAG_WINDOW_ALWAYS_RUN) CORE.Window.alwaysRun = true; + // Selected flags are set but not evaluated at this point, + // flag evaluation happens at InitWindow() or SetWindowState() + CORE.Window.flags |= flags; } // NOTE TRACELOG() function is located in [utils.h] @@ -1838,7 +2555,7 @@ void SetConfigFlags(unsigned int flags) void TakeScreenshot(const char *fileName) { unsigned char *imgData = rlReadScreenPixels(CORE.Window.render.width, CORE.Window.render.height); - Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, UNCOMPRESSED_R8G8B8A8 }; + Image image = { imgData, CORE.Window.render.width, CORE.Window.render.height, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; char path[512] = { 0 }; #if defined(PLATFORM_ANDROID) @@ -1898,7 +2615,7 @@ bool FileExists(const char *fileName) bool IsFileExtension(const char *fileName, const char *ext) { bool result = false; - const char *fileExt = GetExtension(fileName); + const char *fileExt = GetFileExtension(fileName); if (fileExt != NULL) { @@ -1911,7 +2628,7 @@ bool IsFileExtension(const char *fileName, const char *ext) for (int i = 0; i < extCount; i++) { - if (TextIsEqual(fileExtLower, TextToLower(checkExts[i] + 1))) + if (TextIsEqual(fileExtLower, TextToLower(checkExts[i]))) { result = true; break; @@ -1940,14 +2657,14 @@ bool DirectoryExists(const char *dirPath) return result; } -// Get pointer to extension for a filename string -const char *GetExtension(const char *fileName) +// Get pointer to extension for a filename string (includes the dot: .png) +const char *GetFileExtension(const char *fileName) { const char *dot = strrchr(fileName, '.'); if (!dot || dot == fileName) return NULL; - return (dot + 1); + return dot; } // String pointer reverse break: returns right-most occurrence of charset in s @@ -1964,7 +2681,7 @@ const char *GetFileName(const char *filePath) const char *fileName = NULL; if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); - if (!fileName || (fileName == filePath)) return filePath; + if (!fileName) return filePath; return fileName + 1; } @@ -2010,9 +2727,9 @@ const char *GetDirectoryPath(const char *filePath) static char dirPath[MAX_FILEPATH_LENGTH]; memset(dirPath, 0, MAX_FILEPATH_LENGTH); - // In case provided path does not contains a root drive letter (C:\, D:\), + // In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /), // we add the current directory path to dirPath - if (filePath[1] != ':') + if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/') { // For security, we set starting path to current directory, // obtained path will be concated to this @@ -2023,9 +2740,18 @@ const char *GetDirectoryPath(const char *filePath) lastSlash = strprbrk(filePath, "\\/"); if (lastSlash) { - // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' - memcpy(dirPath + ((filePath[1] != ':')? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); - dirPath[strlen(filePath) - strlen(lastSlash) + ((filePath[1] != ':')? 2 : 0)] = '\0'; // Add '\0' manually + if (lastSlash == filePath) + { + // The last and only slash is the leading one: path is in a root directory + dirPath[0] = filePath[0]; + dirPath[1] = '\0'; + } + else + { + // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' + memcpy(dirPath + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0), filePath, strlen(filePath) - (strlen(lastSlash) - 1)); + dirPath[strlen(filePath) - strlen(lastSlash) + (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/' ? 2 : 0)] = '\0'; // Add '\0' manually + } } return dirPath; @@ -2046,7 +2772,7 @@ const char *GetPrevDirectoryPath(const char *dirPath) { // Check for root: "C:\" or "/" if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; - + strncpy(prevDirPath, dirPath, i); break; } @@ -2061,9 +2787,9 @@ const char *GetWorkingDirectory(void) static char currentDir[MAX_FILEPATH_LENGTH]; memset(currentDir, 0, MAX_FILEPATH_LENGTH); - GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); + char *ptr = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); - return currentDir; + return ptr; } // Get filenames in a directory path (max 512 files) @@ -2075,8 +2801,8 @@ char **GetDirectoryFiles(const char *dirPath, int *fileCount) ClearDirectoryFiles(); // Memory allocation for MAX_DIRECTORY_FILES - dirFilesPath = (char **)RL_MALLOC(sizeof(char *)*MAX_DIRECTORY_FILES); - for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); + dirFilesPath = (char **)RL_MALLOC(MAX_DIRECTORY_FILES*sizeof(char *)); + for (int i = 0; i < MAX_DIRECTORY_FILES; i++) dirFilesPath[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); int counter = 0; struct dirent *entity; @@ -2117,10 +2843,14 @@ void ClearDirectoryFiles(void) dirFilesCount = 0; } -// Change working directory, returns true if success +// Change working directory, returns true on success bool ChangeDirectory(const char *dir) { - return (CHDIR(dir) == 0); + bool result = CHDIR(dir); + + if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); + + return (result == 0); } // Check if a file has been dropped into window @@ -2173,7 +2903,13 @@ unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLe unsigned char *compData = NULL; #if defined(SUPPORT_COMPRESSION_API) - compData = stbi_zlib_compress(data, dataLength, compDataLength, COMPRESSION_QUALITY_DEFLATE); + // Compress data and generate a valid DEFLATE stream + struct sdefl sdefl = { 0 }; + int bounds = sdefl_bound(dataLength); + compData = (unsigned char *)RL_CALLOC(bounds, 1); + *compDataLength = sdeflate(&sdefl, compData, data, dataLength, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbwi + + TraceLog(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataLength, compDataLength); #endif return compData; @@ -2182,19 +2918,31 @@ unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLe // Decompress data (DEFLATE algorythm) unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength) { - char *data = NULL; + unsigned char *data = NULL; #if defined(SUPPORT_COMPRESSION_API) - data = stbi_zlib_decode_malloc((char *)compData, compDataLength, dataLength); + // Decompress data from a valid DEFLATE stream + data = RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); + int length = sinflate(data, compData, compDataLength); + unsigned char *temp = RL_REALLOC(data, length); + + if (temp != NULL) data = temp; + else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); + + *dataLength = length; + + TraceLog(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataLength, dataLength); #endif - return (unsigned char *)data; + return data; } // Save integer value to storage file (to defined position) // NOTE: Storage positions is directly related to file memory layout (4 bytes each integer) -void SaveStorageValue(unsigned int position, int value) +bool SaveStorageValue(unsigned int position, int value) { + bool success = false; + #if defined(SUPPORT_DATA_STORAGE) char path[512] = { 0 }; #if defined(PLATFORM_ANDROID) @@ -2226,13 +2974,13 @@ void SaveStorageValue(unsigned int position, int value) { // RL_REALLOC succeded int *dataPtr = (int *)newFileData; - dataPtr[position] = value; + dataPtr[position] = value; } else { // RL_REALLOC failed - TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", path, dataSize, position*sizeof(int)); - + TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to realloc data (%u), position in bytes (%u) bigger than actual file size", path, dataSize, position*sizeof(int)); + // We store the old size of the file newFileData = fileData; newDataSize = dataSize; @@ -2249,7 +2997,7 @@ void SaveStorageValue(unsigned int position, int value) dataPtr[position] = value; } - SaveFileData(path, newFileData, newDataSize); + success = SaveFileData(path, newFileData, newDataSize); RL_FREE(newFileData); } else @@ -2261,10 +3009,12 @@ void SaveStorageValue(unsigned int position, int value) int *dataPtr = (int *)fileData; dataPtr[position] = value; - SaveFileData(path, fileData, dataSize); - RL_FREE(fileData); + success = SaveFileData(path, fileData, dataSize); + UnloadFileData(fileData); } #endif + + return success; } // Load integer value from storage file (from defined position) @@ -2272,6 +3022,7 @@ void SaveStorageValue(unsigned int position, int value) int LoadStorageValue(unsigned int position) { int value = 0; + #if defined(SUPPORT_DATA_STORAGE) char path[512] = { 0 }; #if defined(PLATFORM_ANDROID) @@ -2298,7 +3049,7 @@ int LoadStorageValue(unsigned int position) value = dataPtr[position]; } - RL_FREE(fileData); + UnloadFileData(fileData); } #endif return value; @@ -2308,7 +3059,7 @@ int LoadStorageValue(unsigned int position) // NOTE: This function is only safe to use if you control the URL given. // A user could craft a malicious string performing another action. // Only call this function yourself not with user input or make sure to check the string yourself. -// CHECK: https://github.com/raysan5/raylib/issues/686 +// Ref: https://github.com/raysan5/raylib/issues/686 void OpenURL(const char *url) { // Small security check trying to avoid (partially) malicious code... @@ -2323,9 +3074,11 @@ void OpenURL(const char *url) char *cmd = (char *)RL_CALLOC(strlen(url) + 10, sizeof(char)); #if defined(_WIN32) sprintf(cmd, "explorer %s", url); - #elif defined(__linux__) + #endif + #if defined(__linux__) || defined(__FreeBSD__) sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser - #elif defined(__APPLE__) + #endif + #if defined(__APPLE__) sprintf(cmd, "open '%s'", url); #endif system(cmd); @@ -2387,7 +3140,8 @@ int GetKeyPressed(void) value = CORE.Input.Keyboard.keyPressedQueue[0]; // Shift elements 1 step toward the head. - for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; + for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) + CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; // Reset last character in the queue CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = 0; @@ -2397,6 +3151,28 @@ int GetKeyPressed(void) return value; } +// Get the last char pressed +int GetCharPressed(void) +{ + int value = 0; + + if (CORE.Input.Keyboard.charPressedQueueCount > 0) + { + // Get character from the queue head + value = CORE.Input.Keyboard.charPressedQueue[0]; + + // Shift elements 1 step toward the head. + for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) + CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; + + // Reset last character in the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = 0; + CORE.Input.Keyboard.charPressedQueueCount--; + } + + return value; +} + // Set a custom key to exit program // NOTE: default exitKey is ESCAPE void SetExitKey(int key) @@ -2413,9 +3189,7 @@ bool IsGamepadAvailable(int gamepad) { bool result = false; -#if !defined(PLATFORM_ANDROID) if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; -#endif return result; } @@ -2424,13 +3198,10 @@ bool IsGamepadAvailable(int gamepad) bool IsGamepadName(int gamepad, const char *name) { bool result = false; - -#if !defined(PLATFORM_ANDROID) const char *currentName = NULL; if (CORE.Input.Gamepad.ready[gamepad]) currentName = GetGamepadName(gamepad); if ((name != NULL) && (currentName != NULL)) result = (strcmp(name, currentName) == 0); -#endif return result; } @@ -2441,23 +3212,23 @@ const char *GetGamepadName(int gamepad) #if defined(PLATFORM_DESKTOP) if (CORE.Input.Gamepad.ready[gamepad]) return glfwGetJoystickName(gamepad); else return NULL; -#elif defined(PLATFORM_RPI) - if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name); - - return CORE.Input.Gamepad.name; -#else - return NULL; #endif +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGNAME(64), &CORE.Input.Gamepad.name); + return CORE.Input.Gamepad.name; +#endif + return NULL; } // Return gamepad axis count int GetGamepadAxisCount(int gamepad) { -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) int axisCount = 0; if (CORE.Input.Gamepad.ready[gamepad]) ioctl(CORE.Input.Gamepad.streamId[gamepad], JSIOCGAXES, &axisCount); CORE.Input.Gamepad.axisCount = axisCount; #endif + return CORE.Input.Gamepad.axisCount; } @@ -2466,11 +3237,8 @@ float GetGamepadAxisMovement(int gamepad, int axis) { float value = 0; -#if !defined(PLATFORM_ANDROID) if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS) && - ((axis == GAMEPAD_AXIS_LEFT_TRIGGER) || (axis == GAMEPAD_AXIS_RIGHT_TRIGGER) || - (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) >= 0.2f))) value = CORE.Input.Gamepad.axisState[gamepad][axis]; -#endif + (fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]) > 0.1f)) value = CORE.Input.Gamepad.axisState[gamepad][axis]; // 0.1f = GAMEPAD_AXIS_MINIMUM_DRIFT/DELTA return value; } @@ -2480,11 +3248,9 @@ bool IsGamepadButtonPressed(int gamepad, int button) { bool pressed = false; -#if !defined(PLATFORM_ANDROID) if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && - (CORE.Input.Gamepad.currentState[gamepad][button] != CORE.Input.Gamepad.previousState[gamepad][button]) && - (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) pressed = true; -#endif + (CORE.Input.Gamepad.previousState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) pressed = true; + else pressed = false; return pressed; } @@ -2494,10 +3260,8 @@ bool IsGamepadButtonDown(int gamepad, int button) { bool result = false; -#if !defined(PLATFORM_ANDROID) if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && (CORE.Input.Gamepad.currentState[gamepad][button] == 1)) result = true; -#endif return result; } @@ -2507,24 +3271,20 @@ bool IsGamepadButtonReleased(int gamepad, int button) { bool released = false; -#if !defined(PLATFORM_ANDROID) if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && - (CORE.Input.Gamepad.currentState[gamepad][button] != CORE.Input.Gamepad.previousState[gamepad][button]) && - (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) released = true; -#endif + (CORE.Input.Gamepad.previousState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) released = true; + else released = false; return released; } -// Detect if a mouse button is NOT being pressed +// Detect if a gamepad button is NOT being pressed bool IsGamepadButtonUp(int gamepad, int button) { bool result = false; -#if !defined(PLATFORM_ANDROID) if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && (CORE.Input.Gamepad.currentState[gamepad][button] == 0)) result = true; -#endif return result; } @@ -2535,6 +3295,18 @@ int GetGamepadButtonPressed(void) return CORE.Input.Gamepad.lastButtonPressed; } +// Set internal gamepad mappings +int SetGamepadMappings(const char *mappings) +{ + int result = 0; + +#if defined(PLATFORM_DESKTOP) + result = glfwUpdateGamepadMappings(mappings); +#endif + + return result; +} + // Detect if a mouse button has been pressed once bool IsMouseButtonPressed(int button) { @@ -2643,14 +3415,30 @@ void SetMouseScale(float scaleX, float scaleY) } // Returns mouse wheel movement Y -int GetMouseWheelMove(void) +float GetMouseWheelMove(void) { #if defined(PLATFORM_ANDROID) - return 0; -#elif defined(PLATFORM_WEB) - return CORE.Input.Mouse.previousWheelMove/100; -#else + return 0.0f; +#endif +#if defined(PLATFORM_WEB) + return CORE.Input.Mouse.previousWheelMove/100.0f; +#endif + return CORE.Input.Mouse.previousWheelMove; +} + +// Set mouse cursor +// NOTE: This is a no-op on platforms other than PLATFORM_DESKTOP +void SetMouseCursor(int cursor) +{ +#if defined(PLATFORM_DESKTOP) + CORE.Input.Mouse.cursor = cursor; + if (cursor == MOUSE_CURSOR_DEFAULT) glfwSetCursor(CORE.Window.handle, NULL); + else + { + // NOTE: We are relating internal GLFW enum values to our MouseCursor enum values + glfwSetCursor(CORE.Window.handle, glfwCreateStandardCursor(0x00036000 + cursor)); + } #endif } @@ -2659,7 +3447,7 @@ int GetTouchX(void) { #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) return (int)CORE.Input.Touch.position[0].x; -#else // PLATFORM_DESKTOP, PLATFORM_RPI +#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM return GetMouseX(); #endif } @@ -2669,7 +3457,7 @@ int GetTouchY(void) { #if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_UWP) return (int)CORE.Input.Touch.position[0].y; -#else // PLATFORM_DESKTOP, PLATFORM_RPI +#else // PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_DRM return GetMouseY(); #endif } @@ -2680,11 +3468,16 @@ Vector2 GetTouchPosition(int index) { Vector2 position = { -1.0f, -1.0f }; -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_DESKTOP) + // TODO: GLFW does not support multi-touch input just yet + // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch + // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages + if (index == 0) position = GetMousePosition(); +#endif +#if defined(PLATFORM_ANDROID) if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); - #if defined(PLATFORM_ANDROID) if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) { position.x = position.x*((float)CORE.Window.screen.width/(float)(CORE.Window.display.width - CORE.Window.renderOffset.x)) - CORE.Window.renderOffset.x/2; @@ -2695,13 +3488,12 @@ Vector2 GetTouchPosition(int index) position.x = position.x*((float)CORE.Window.render.width/(float)CORE.Window.display.width) - CORE.Window.renderOffset.x/2; position.y = position.y*((float)CORE.Window.render.height/(float)CORE.Window.display.height) - CORE.Window.renderOffset.y/2; } - #endif +#endif +#if defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) + if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; + else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); -#elif defined(PLATFORM_DESKTOP) - // TODO: GLFW is not supporting multi-touch input just yet - // https://www.codeproject.com/Articles/668404/Programming-for-Multi-Touch - // https://docs.microsoft.com/en-us/windows/win32/wintouch/getting-started-with-multi-touch-messages - if (index == 0) position = GetMousePosition(); + // TODO: Touch position scaling required? #endif return position; @@ -2719,7 +3511,6 @@ static bool InitGraphicsDevice(int width, int height) { CORE.Window.screen.width = width; // User desired width CORE.Window.screen.height = height; // User desired height - CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default // NOTE: Framebuffer (render area - CORE.Window.render.width, CORE.Window.render.height) could include black bars... @@ -2753,8 +3544,8 @@ static bool InitGraphicsDevice(int width, int height) CORE.Window.display.height = mode->height; // Screen size security check - if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; - if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; + if (CORE.Window.screen.width == 0) CORE.Window.screen.width = CORE.Window.display.width; + if (CORE.Window.screen.height == 0) CORE.Window.screen.height = CORE.Window.display.height; #endif // PLATFORM_DESKTOP #if defined(PLATFORM_WEB) @@ -2762,7 +3553,7 @@ static bool InitGraphicsDevice(int width, int height) CORE.Window.display.height = CORE.Window.screen.height; #endif // PLATFORM_WEB - glfwDefaultWindowHints(); // Set default windows hints: + glfwDefaultWindowHints(); // Set default windows hints //glfwWindowHint(GLFW_RED_BITS, 8); // Framebuffer red color component bits //glfwWindowHint(GLFW_GREEN_BITS, 8); // Framebuffer green color component bits //glfwWindowHint(GLFW_BLUE_BITS, 8); // Framebuffer blue color component bits @@ -2771,29 +3562,55 @@ static bool InitGraphicsDevice(int width, int height) //glfwWindowHint(GLFW_REFRESH_RATE, 0); // Refresh rate for fullscreen window //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // OpenGL API to use. Alternative: GLFW_OPENGL_ES_API //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers -#if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) - // Resize window content area based on the monitor content scale. - // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. - // On platforms like macOS the resolution of the framebuffer is changed independently of the window size. - glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on -#endif - // Check some Window creation flags - if (CORE.Window.flags & FLAG_WINDOW_HIDDEN) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window + // Check window creation flags + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) CORE.Window.fullscreen = true; + + if ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Visible window else glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); // Window initially hidden - if (CORE.Window.flags & FLAG_WINDOW_RESIZABLE) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window + if ((CORE.Window.flags & FLAG_WINDOW_UNDECORATED) > 0) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window + else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window + + if ((CORE.Window.flags & FLAG_WINDOW_RESIZABLE) > 0) glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); // Resizable window else glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // Avoid window being resizable - if (CORE.Window.flags & FLAG_WINDOW_UNDECORATED) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); // Border and buttons on Window - else glfwWindowHint(GLFW_DECORATED, GLFW_TRUE); // Decorated window - // FLAG_WINDOW_TRANSPARENT not supported on HTML5 and not included in any released GLFW version yet -#if defined(GLFW_TRANSPARENT_FRAMEBUFFER) - if (CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer + // Disable FLAG_WINDOW_MINIMIZED, not supported on initialization + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; + + // Disable FLAG_WINDOW_MAXIMIZED, not supported on initialization + if ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0) CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; + + if ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) > 0) glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE); + else glfwWindowHint(GLFW_FOCUSED, GLFW_TRUE); + + if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); + else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); + + // NOTE: Some GLFW flags are not supported on HTML5 +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_TRANSPARENT) > 0) glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); // Transparent framebuffer else glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_FALSE); // Opaque framebuffer + + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // Resize window content area based on the monitor content scale. + // NOTE: This hint only has an effect on platforms where screen coordinates and pixels always map 1:1 such as Windows and X11. + // On platforms like macOS the resolution of the framebuffer is changed independently of the window size. + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); // Scale content area based on the monitor content scale where window is placed on + #if defined(__APPLE__) + glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); + #endif + } + else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); #endif - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + // NOTE: MSAA is only enabled for main framebuffer, not user-created FBOs + TRACELOG(LOG_INFO, "DISPLAY: Trying to enable MSAA x4"); + glfwWindowHint(GLFW_SAMPLES, 4); // Tries to enable multisampling x4 (MSAA), default is 0 + } // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version // with forward compatibility to older OpenGL versions. @@ -2830,6 +3647,14 @@ static bool InitGraphicsDevice(int width, int height) #endif } +#if defined(PLATFORM_DESKTOP) + // NOTE: GLFW 3.4+ defers initialization of the Joystick subsystem on the first call to any Joystick related functions. + // Forcing this initialization here avoids doing it on `PollInputEvents` called by `EndDrawing` after first frame has been just drawn. + // The initialization will still happen and possible delays still occur, but before the window is shown, which is a nicer experience. + // REF: https://github.com/raysan5/raylib/issues/1554 + if (MAX_GAMEPADS > 0) glfwSetJoystickCallback(NULL); +#endif + if (CORE.Window.fullscreen) { // remember center for switchinging from fullscreen to window @@ -2922,17 +3747,21 @@ static bool InitGraphicsDevice(int width, int height) TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); } + // Set window callback events glfwSetWindowSizeCallback(CORE.Window.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! - glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); - glfwSetKeyCallback(CORE.Window.handle, KeyCallback); - glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); - glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes - glfwSetCharCallback(CORE.Window.handle, CharCallback); - glfwSetScrollCallback(CORE.Window.handle, ScrollCallback); +#if !defined(PLATFORM_WEB) + glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback); +#endif glfwSetWindowIconifyCallback(CORE.Window.handle, WindowIconifyCallback); glfwSetWindowFocusCallback(CORE.Window.handle, WindowFocusCallback); glfwSetDropCallback(CORE.Window.handle, WindowDropCallback); - glfwSetWindowMaximizeCallback(CORE.Window.handle, WindowMaximizeCallback); + // Set input callback events + glfwSetKeyCallback(CORE.Window.handle, KeyCallback); + glfwSetCharCallback(CORE.Window.handle, CharCallback); + glfwSetMouseButtonCallback(CORE.Window.handle, MouseButtonCallback); + glfwSetCursorPosCallback(CORE.Window.handle, MouseCursorPosCallback); // Track mouse position changes + glfwSetScrollCallback(CORE.Window.handle, MouseScrollCallback); + glfwSetCursorEnterCallback(CORE.Window.handle, CursorEnterCallback); glfwMakeContextCurrent(CORE.Window.handle); @@ -2940,12 +3769,6 @@ static bool InitGraphicsDevice(int width, int height) glfwSwapInterval(0); // No V-Sync by default #endif -#if defined(PLATFORM_DESKTOP) - // Load OpenGL 3.3 extensions - // NOTE: GLFW loader function is passed as parameter - rlLoadExtensions(glfwGetProcAddress); -#endif - // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) // NOTE: V-Sync can be enabled by graphic driver configuration if (CORE.Window.flags & FLAG_VSYNC_HINT) @@ -2954,10 +3777,11 @@ static bool InitGraphicsDevice(int width, int height) glfwSwapInterval(1); TRACELOG(LOG_INFO, "DISPLAY: Trying to enable VSYNC"); } -#endif // PLATFORM_DESKTOP || PLATFORM_WEB +#endif // PLATFORM_DESKTOP || PLATFORM_WEB -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) CORE.Window.fullscreen = true; + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; #if defined(PLATFORM_RPI) bcm_host_init(); @@ -2970,6 +3794,157 @@ static bool InitGraphicsDevice(int width, int height) VC_RECT_T srcRect; #endif +#if defined(PLATFORM_DRM) + CORE.Window.fd = -1; + CORE.Window.connector = NULL; + CORE.Window.modeIndex = -1; + CORE.Window.crtc = NULL; + CORE.Window.gbmDevice = NULL; + CORE.Window.gbmSurface = NULL; + CORE.Window.prevBO = NULL; + CORE.Window.prevFB = 0; + +#if defined(DEFAULT_GRAPHIC_DEVICE_DRM) + CORE.Window.fd = open(DEFAULT_GRAPHIC_DEVICE_DRM, O_RDWR); +#else + TRACELOG(LOG_INFO, "DISPLAY: No graphic card set, trying card1"); + CORE.Window.fd = open("/dev/dri/card1", O_RDWR); // VideoCore VI (Raspberry Pi 4) + if ((-1 == CORE.Window.fd) || (drmModeGetResources(CORE.Window.fd) == NULL)) + { + TRACELOG(LOG_INFO, "DISPLAY: Failed to open graphic card1, trying card0"); + CORE.Window.fd = open("/dev/dri/card0", O_RDWR); // VideoCore IV (Raspberry Pi 1-3) + } +#endif + if (-1 == CORE.Window.fd) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to open graphic card"); + return false; + } + + drmModeRes *res = drmModeGetResources(CORE.Window.fd); + if (!res) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed get DRM resources"); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: Connectors found: %i", res->count_connectors); + for (size_t i = 0; i < res->count_connectors; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: Connector index %i", i); + drmModeConnector *con = drmModeGetConnector(CORE.Window.fd, res->connectors[i]); + TRACELOG(LOG_TRACE, "DISPLAY: Connector modes detected: %i", con->count_modes); + if ((con->connection == DRM_MODE_CONNECTED) && (con->encoder_id)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode connected"); + CORE.Window.connector = con; + break; + } + else + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode NOT connected (deleting)"); + drmModeFreeConnector(con); + } + } + if (!CORE.Window.connector) + { + TRACELOG(LOG_WARNING, "DISPLAY: No suitable DRM connector found"); + drmModeFreeResources(res); + return false; + } + + drmModeEncoder *enc = drmModeGetEncoder(CORE.Window.fd, CORE.Window.connector->encoder_id); + if (!enc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode encoder"); + drmModeFreeResources(res); + return false; + } + + CORE.Window.crtc = drmModeGetCrtc(CORE.Window.fd, enc->crtc_id); + if (!CORE.Window.crtc) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get DRM mode crtc"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + // If InitWindow should use the current mode find it in the connector's mode list + if ((CORE.Window.screen.width <= 0) || (CORE.Window.screen.height <= 0)) + { + TRACELOG(LOG_TRACE, "DISPLAY: Selecting DRM connector mode for current used mode..."); + + CORE.Window.modeIndex = FindMatchingConnectorMode(CORE.Window.connector, &CORE.Window.crtc->mode); + + if (CORE.Window.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: No matching DRM connector mode found"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + + const bool allowInterlaced = CORE.Window.flags & FLAG_INTERLACED_HINT; + const int fps = (CORE.Time.target > 0) ? (1.0/CORE.Time.target) : 60; + // try to find an exact matching mode + CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + // if nothing found, try to find a nearly matching mode + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, allowInterlaced); + // if nothing found, try to find an exactly matching mode including interlaced + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindExactConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + // if nothing found, try to find a nearly matching mode including interlaced + if (CORE.Window.modeIndex < 0) + CORE.Window.modeIndex = FindNearestConnectorMode(CORE.Window.connector, CORE.Window.screen.width, CORE.Window.screen.height, fps, true); + // if nothing found, there is no suitable mode + if (CORE.Window.modeIndex < 0) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable DRM connector mode"); + drmModeFreeEncoder(enc); + drmModeFreeResources(res); + return false; + } + + CORE.Window.display.width = CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay; + CORE.Window.display.height = CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay; + + TRACELOG(LOG_INFO, "DISPLAY: Selected DRM connector mode %s (%ux%u%c@%u)", CORE.Window.connector->modes[CORE.Window.modeIndex].name, + CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, + (CORE.Window.connector->modes[CORE.Window.modeIndex].flags & DRM_MODE_FLAG_INTERLACE) ? 'i' : 'p', + CORE.Window.connector->modes[CORE.Window.modeIndex].vrefresh); + + // Use the width and height of the surface for render + CORE.Window.render.width = CORE.Window.screen.width; + CORE.Window.render.height = CORE.Window.screen.height; + + drmModeFreeEncoder(enc); + enc = NULL; + + drmModeFreeResources(res); + res = NULL; + + CORE.Window.gbmDevice = gbm_create_device(CORE.Window.fd); + if (!CORE.Window.gbmDevice) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM device"); + return false; + } + + CORE.Window.gbmSurface = gbm_surface_create(CORE.Window.gbmDevice, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, + CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, GBM_FORMAT_ARGB8888, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + if (!CORE.Window.gbmSurface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create GBM surface"); + return false; + } +#endif + EGLint samples = 0; EGLint sampleBuffer = 0; if (CORE.Window.flags & FLAG_MSAA_4X_HINT) @@ -2982,11 +3957,15 @@ static bool InitGraphicsDevice(int width, int height) const EGLint framebufferAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? - //EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! +#if defined(PLATFORM_DRM) + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, // Don't use it on Android! +#endif EGL_RED_SIZE, 8, // RED color bit depth (alternative: 5) EGL_GREEN_SIZE, 8, // GREEN color bit depth (alternative: 6) EGL_BLUE_SIZE, 8, // BLUE color bit depth (alternative: 5) - //EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) +#if defined(PLATFORM_DRM) + EGL_ALPHA_SIZE, 8, // ALPHA bit depth (required for transparent framebuffer) +#endif //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) //EGL_STENCIL_SIZE, 8, // Stencil buffer size @@ -3054,7 +4033,7 @@ static bool InitGraphicsDevice(int width, int height) PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)(eglGetProcAddress("eglGetPlatformDisplayEXT")); if (!eglGetPlatformDisplayEXT) { - TRACELOG(LOG_WARNING, "DISPLAY: Failed to get function eglGetPlatformDisplayEXT"); + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get function pointer: eglGetPlatformDisplayEXT()"); return false; } @@ -3170,11 +4149,15 @@ static bool InitGraphicsDevice(int width, int height) #endif // PLATFORM_UWP -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) EGLint numConfigs = 0; // Get an EGL device connection +#if defined(PLATFORM_DRM) + CORE.Window.device = eglGetDisplay((EGLNativeDisplayType)CORE.Window.gbmDevice); +#else CORE.Window.device = eglGetDisplay(EGL_DEFAULT_DISPLAY); +#endif if (CORE.Window.device == EGL_NO_DISPLAY) { TRACELOG(LOG_WARNING, "DISPLAY: Failed to initialize EGL device"); @@ -3189,8 +4172,63 @@ static bool InitGraphicsDevice(int width, int height) return false; } +#if defined(PLATFORM_DRM) + if (!eglChooseConfig(CORE.Window.device, NULL, NULL, 0, &numConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config count: 0x%x", eglGetError()); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL configs available: %d", numConfigs); + + EGLConfig *configs = RL_CALLOC(numConfigs, sizeof(*configs)); + if (!configs) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get memory for EGL configs"); + return false; + } + + EGLint matchingNumConfigs = 0; + if (!eglChooseConfig(CORE.Window.device, framebufferAttribs, configs, numConfigs, &matchingNumConfigs)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to choose EGL config: 0x%x", eglGetError()); + free(configs); + return false; + } + + TRACELOG(LOG_TRACE, "DISPLAY: EGL matching configs available: %d", matchingNumConfigs); + + // find the EGL config that matches the previously setup GBM format + int found = 0; + for (EGLint i = 0; i < matchingNumConfigs; ++i) + { + EGLint id = 0; + if (!eglGetConfigAttrib(CORE.Window.device, configs[i], EGL_NATIVE_VISUAL_ID, &id)) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to get EGL config attribute: 0x%x", eglGetError()); + continue; + } + + if (GBM_FORMAT_ARGB8888 == id) + { + TRACELOG(LOG_TRACE, "DISPLAY: Using EGL config: %d", i); + CORE.Window.config = configs[i]; + found = 1; + break; + } + } + + RL_FREE(configs); + + if (!found) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to find a suitable EGL config"); + return false; + } +#else // Get an appropriate EGL framebuffer configuration eglChooseConfig(CORE.Window.device, framebufferAttribs, &CORE.Window.config, 1, &numConfigs); +#endif // Set rendering API eglBindAPI(EGL_OPENGL_ES_API); @@ -3214,7 +4252,7 @@ static bool InitGraphicsDevice(int width, int height) eglGetConfigAttrib(CORE.Window.device, CORE.Window.config, EGL_NATIVE_VISUAL_ID, &displayFormat); // At this point we need to manage render size vs screen size - // NOTE: This function use and modify global module variables: + // NOTE: This function use and modify global module variables: // -> CORE.Window.screen.width/CORE.Window.screen.height // -> CORE.Window.render.width/CORE.Window.render.height // -> CORE.Window.screenScale @@ -3228,16 +4266,16 @@ static bool InitGraphicsDevice(int width, int height) #if defined(PLATFORM_RPI) graphics_get_display_size(0, &CORE.Window.display.width, &CORE.Window.display.height); - + // Screen size security check if (CORE.Window.screen.width <= 0) CORE.Window.screen.width = CORE.Window.display.width; if (CORE.Window.screen.height <= 0) CORE.Window.screen.height = CORE.Window.display.height; // At this point we need to manage render size vs screen size - // NOTE: This function use and modify global module variables: + // NOTE: This function use and modify global module variables: // -> CORE.Window.screen.width/CORE.Window.screen.height // -> CORE.Window.render.width/CORE.Window.render.height - // -> CORE.Window.screenScale + // -> CORE.Window.screenScale SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); dstRect.x = 0; @@ -3250,7 +4288,7 @@ static bool InitGraphicsDevice(int width, int height) srcRect.width = CORE.Window.render.width << 16; srcRect.height = CORE.Window.render.height << 16; - // NOTE: RPI dispmanx windowing system takes care of srcRec scaling to dstRec by hardware (no cost) + // NOTE: RPI dispmanx windowing system takes care of source rectangle scaling to destination rectangle by hardware (no cost) // Take care that renderWidth/renderHeight fit on displayWidth/displayHeight aspect ratio VC_DISPMANX_ALPHA_T alpha; @@ -3271,9 +4309,29 @@ static bool InitGraphicsDevice(int width, int height) vc_dispmanx_update_submit_sync(dispmanUpdate); CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, &CORE.Window.handle, NULL); + + const unsigned char *const renderer = glGetString(GL_RENDERER); + if (renderer) TRACELOG(LOG_INFO, "DISPLAY: Renderer name is: %s", renderer); + else TRACELOG(LOG_WARNING, "DISPLAY: Failed to get renderer name"); //--------------------------------------------------------------------------------- #endif // PLATFORM_RPI +#if defined(PLATFORM_DRM) + CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL); + if (EGL_NO_SURFACE == CORE.Window.surface) + { + TRACELOG(LOG_WARNING, "DISPLAY: Failed to create EGL window surface: 0x%04x", eglGetError()); + return false; + } + + // At this point we need to manage render size vs screen size + // NOTE: This function use and modify global module variables: + // -> CORE.Window.screen.width/CORE.Window.screen.height + // -> CORE.Window.render.width/CORE.Window.render.height + // -> CORE.Window.screenScale + SetupFramebuffer(CORE.Window.display.width, CORE.Window.display.height); +#endif // PLATFORM_DRM + // There must be at least one frame displayed before the buffers are swapped //eglSwapInterval(CORE.Window.device, 1); @@ -3290,7 +4348,15 @@ static bool InitGraphicsDevice(int width, int height) TRACELOG(LOG_INFO, " > Screen size: %i x %i", CORE.Window.screen.width, CORE.Window.screen.height); TRACELOG(LOG_INFO, " > Viewport offsets: %i, %i", CORE.Window.renderOffset.x, CORE.Window.renderOffset.y); } -#endif // PLATFORM_ANDROID || PLATFORM_RPI || defined(PLATFORM_UWP) +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP + + // Load OpenGL extensions + // NOTE: GL procedures address loader is required to load extensions +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + rlLoadExtensions(glfwGetProcAddress); +#else + rlLoadExtensions(eglGetProcAddress); +#endif // Initialize OpenGL context (states and resources) // NOTE: CORE.Window.screen.width and CORE.Window.screen.height not used, just stored as globals in rlgl @@ -3299,15 +4365,22 @@ static bool InitGraphicsDevice(int width, int height) int fbWidth = CORE.Window.render.width; int fbHeight = CORE.Window.render.height; -#if defined(PLATFORM_DESKTOP) && defined(SUPPORT_HIGH_DPI) - glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); +#if defined(PLATFORM_DESKTOP) + if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) + { + // NOTE: On APPLE platforms system should manage window/input scaling and also framebuffer scaling + // Framebuffer scaling should be activated with: glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE); + #if !defined(__APPLE__) + glfwGetFramebufferSize(CORE.Window.handle, &fbWidth, &fbHeight); - // Screen scaling matrix is required in case desired screen area is different than display area - CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); -#if !defined(__APPLE__) - SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); + // Screen scaling matrix is required in case desired screen area is different than display area + CORE.Window.screenScale = MatrixScale((float)fbWidth/CORE.Window.screen.width, (float)fbHeight/CORE.Window.screen.height, 1.0f); + + // Mouse input scaling for the new screen size + SetMouseScale((float)CORE.Window.screen.width/fbWidth, (float)CORE.Window.screen.height/fbHeight); + #endif + } #endif -#endif // PLATFORM_DESKTOP && SUPPORT_HIGH_DPI // Setup default viewport SetupViewport(fbWidth, fbHeight); @@ -3320,6 +4393,9 @@ static bool InitGraphicsDevice(int width, int height) #if defined(PLATFORM_ANDROID) || defined(PLATFORM_UWP) CORE.Window.ready = true; #endif + + if ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0) MinimizeWindow(); + return true; } @@ -3330,9 +4406,15 @@ static void SetupViewport(int width, int height) CORE.Window.render.height = height; // Set viewport width and height - // NOTE: We consider render size and offset in case black bars are required and + // NOTE: We consider render size (scaled) and offset in case black bars are required and // render area does not match full display area (this situation is only applicable on fullscreen mode) +#if defined(__APPLE__) + float xScale = 1.0f, yScale = 1.0f; + glfwGetWindowContentScale(CORE.Window.handle, &xScale, &yScale); + rlViewport(CORE.Window.renderOffset.x/2*xScale, CORE.Window.renderOffset.y/2*yScale, (CORE.Window.render.width - CORE.Window.renderOffset.x)*xScale, (CORE.Window.render.height - CORE.Window.renderOffset.y)*yScale); +#else rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width - CORE.Window.renderOffset.x, CORE.Window.render.height - CORE.Window.renderOffset.y); +#endif rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlLoadIdentity(); // Reset current matrix (projection) @@ -3389,6 +4471,12 @@ static void SetupFramebuffer(int width, int height) // Required screen size is smaller than display size TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); + if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) + { + CORE.Window.screen.width = CORE.Window.display.width; + CORE.Window.screen.height = CORE.Window.display.height; + } + // Upscaling to fit display with border-bars float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; @@ -3420,14 +4508,18 @@ static void SetupFramebuffer(int width, int height) // Initialize hi-resolution timer static void InitTimer(void) { - srand((unsigned int)time(NULL)); // Initialize random seed + srand((unsigned int)time(NULL)); // Initialize random seed -#if !defined(SUPPORT_BUSY_WAIT_LOOP) && defined(_WIN32) && !defined(PLATFORM_UWP) - timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) +// Setting a higher resolution can improve the accuracy of time-out intervals in wait functions. +// However, it can also reduce overall system performance, because the thread scheduler switches tasks more often. +// High resolutions can also prevent the CPU power management system from entering power-saving modes. +// Setting a higher resolution does not improve the accuracy of the high-resolution performance counter. +#if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_UWP) + timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) - struct timespec now; +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + struct timespec now = { 0 }; if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success { @@ -3436,7 +4528,7 @@ static void InitTimer(void) else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); #endif - CORE.Time.previous = GetTime(); // Get time as double + CORE.Time.previous = GetTime(); // Get time as double } // Wait for some milliseconds (stop program execution) @@ -3458,15 +4550,21 @@ static void Wait(float ms) // Busy wait loop while ((nextTime - prevTime) < ms/1000.0f) nextTime = GetTime(); #else - #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) - #define MAX_HALFBUSY_WAIT_TIME 4 + #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) + #define DEFAULT_PARTIALBUSY_WAIT_TIME 4 + #define PARTIALBUSY_WAIT_FACTOR 0.95 + + double halfWait = DEFAULT_PARTIALBUSY_WAIT_TIME; + if (CORE.Time.target > 0) halfWait = CORE.Time.target*PARTIALBUSY_WAIT_FACTOR; + double destTime = GetTime() + ms/1000; - if (ms > MAX_HALFBUSY_WAIT_TIME) ms -= MAX_HALFBUSY_WAIT_TIME; + if (ms > halfWait) ms -= (float)halfWait; #endif #if defined(_WIN32) Sleep((unsigned int)ms); - #elif defined(__linux__) || defined(PLATFORM_WEB) + #endif + #if defined(__linux__) || defined(__FreeBSD__) || defined(__EMSCRIPTEN__) struct timespec req = { 0 }; time_t sec = (int)(ms/1000.0f); ms -= (sec*1000); @@ -3475,75 +4573,17 @@ static void Wait(float ms) // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated. while (nanosleep(&req, &req) == -1) continue; - #elif defined(__APPLE__) + #endif + #if defined(__APPLE__) usleep(ms*1000.0f); #endif - #if defined(SUPPORT_HALFBUSY_WAIT_LOOP) + #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) while (GetTime() < destTime) { } #endif #endif } -// Get gamepad button generic to all platforms -static int GetGamepadButton(int button) -{ - int btn = GAMEPAD_BUTTON_UNKNOWN; -#if defined(PLATFORM_DESKTOP) - switch (button) - { - case GLFW_GAMEPAD_BUTTON_Y: btn = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; - case GLFW_GAMEPAD_BUTTON_B: btn = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; - case GLFW_GAMEPAD_BUTTON_A: btn = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; - case GLFW_GAMEPAD_BUTTON_X: btn = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; - - case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; - case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; - - case GLFW_GAMEPAD_BUTTON_BACK: btn = GAMEPAD_BUTTON_MIDDLE_LEFT; break; - case GLFW_GAMEPAD_BUTTON_GUIDE: btn = GAMEPAD_BUTTON_MIDDLE; break; - case GLFW_GAMEPAD_BUTTON_START: btn = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; - - case GLFW_GAMEPAD_BUTTON_DPAD_UP: btn = GAMEPAD_BUTTON_LEFT_FACE_UP; break; - case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: btn = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; - case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: btn = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; - case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: btn = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; - - case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: btn = GAMEPAD_BUTTON_LEFT_THUMB; break; - case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: btn = GAMEPAD_BUTTON_RIGHT_THUMB; break; - } -#endif - -#if defined(PLATFORM_UWP) - btn = button; // UWP will provide the correct button -#endif - -#if defined(PLATFORM_WEB) - // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface - switch (button) - { - case 0: btn = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; - case 1: btn = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; - case 2: btn = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; - case 3: btn = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; - case 4: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; - case 5: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; - case 6: btn = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; - case 7: btn = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; - case 8: btn = GAMEPAD_BUTTON_MIDDLE_LEFT; break; - case 9: btn = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; - case 10: btn = GAMEPAD_BUTTON_LEFT_THUMB; break; - case 11: btn = GAMEPAD_BUTTON_RIGHT_THUMB; break; - case 12: btn = GAMEPAD_BUTTON_LEFT_FACE_UP; break; - case 13: btn = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; - case 14: btn = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; - case 15: btn = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; - } -#endif - - return btn; -} - // Poll (store) all input events static void PollInputEvents(void) { @@ -3553,36 +4593,40 @@ static void PollInputEvents(void) UpdateGestures(); #endif - // Reset key pressed registered + // Reset keys/chars pressed registered CORE.Input.Keyboard.keyPressedQueueCount = 0; + CORE.Input.Keyboard.charPressedQueueCount = 0; -#if !defined(PLATFORM_RPI) +#if !(defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) // Reset last gamepad button/axis registered state CORE.Input.Gamepad.lastButtonPressed = -1; CORE.Input.Gamepad.axisCount = 0; #endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) // Register previous keys states for (int i = 0; i < 512; i++) CORE.Input.Keyboard.previousKeyState[i] = CORE.Input.Keyboard.currentKeyState[i]; - // Grab a keypress from the evdev fifo if avalable - if (CORE.Input.Keyboard.lastKeyPressed.head != CORE.Input.Keyboard.lastKeyPressed.tail) - { - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = CORE.Input.Keyboard.lastKeyPressed.contents[CORE.Input.Keyboard.lastKeyPressed.tail]; // Read the key from the buffer - CORE.Input.Keyboard.keyPressedQueueCount++; - - CORE.Input.Keyboard.lastKeyPressed.tail = (CORE.Input.Keyboard.lastKeyPressed.tail + 1) & 0x07; // Increment the tail pointer forwards and binary wraparound after 7 (fifo is 8 elements long) - } + PollKeyboardEvents(); // Register previous mouse states CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; - CORE.Input.Mouse.currentWheelMove = 0; + CORE.Input.Mouse.currentWheelMove = 0.0f; for (int i = 0; i < 3; i++) { CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; CORE.Input.Mouse.currentButtonState[i] = CORE.Input.Mouse.currentButtonStateEvdev[i]; } + + // Register gamepads buttons events + for (int i = 0; i < MAX_GAMEPADS; i++) + { + if (CORE.Input.Gamepad.ready[i]) // Check if gamepad is available + { + // Register previous gamepad states + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) CORE.Input.Gamepad.previousState[i][k] = CORE.Input.Gamepad.currentState[i][k]; + } + } #endif #if defined(PLATFORM_UWP) @@ -3599,7 +4643,7 @@ static void PollInputEvents(void) // Register previous mouse states CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; - CORE.Input.Mouse.currentWheelMove = 0; + CORE.Input.Mouse.currentWheelMove = 0.0f; for (int i = 0; i < 3; i++) CORE.Input.Mouse.previousButtonState[i] = CORE.Input.Mouse.currentButtonState[i]; #endif // PLATFORM_UWP @@ -3615,7 +4659,7 @@ static void PollInputEvents(void) // Register previous mouse wheel state CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; - CORE.Input.Mouse.currentWheelMove = 0; + CORE.Input.Mouse.currentWheelMove = 0.0f; #endif // Register previous touch states @@ -3643,19 +4687,46 @@ static void PollInputEvents(void) // Get remapped buttons GLFWgamepadstate state = { 0 }; glfwGetGamepadState(i, &state); // This remapps all gamepads so they have their buttons mapped like an xbox controller - + const unsigned char *buttons = state.buttons; for (int k = 0; (buttons != NULL) && (k < GLFW_GAMEPAD_BUTTON_DPAD_LEFT + 1) && (k < MAX_GAMEPAD_BUTTONS); k++) { - const GamepadButton button = GetGamepadButton(k); + GamepadButton button = -1; - if (buttons[k] == GLFW_PRESS) + switch (k) { - CORE.Input.Gamepad.currentState[i][button] = 1; - CORE.Input.Gamepad.lastButtonPressed = button; + case GLFW_GAMEPAD_BUTTON_Y: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_B: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_A: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_X: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + + case GLFW_GAMEPAD_BUTTON_BACK: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case GLFW_GAMEPAD_BUTTON_GUIDE: button = GAMEPAD_BUTTON_MIDDLE; break; + case GLFW_GAMEPAD_BUTTON_START: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + + case GLFW_GAMEPAD_BUTTON_DPAD_UP: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (buttons[k] == GLFW_PRESS) + { + CORE.Input.Gamepad.currentState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentState[i][button] = 0; } - else CORE.Input.Gamepad.currentState[i][button] = 0; } // Get current axis state @@ -3670,18 +4741,18 @@ static void PollInputEvents(void) CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_LEFT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_LEFT_TRIGGER] > 0.1); CORE.Input.Gamepad.currentState[i][GAMEPAD_BUTTON_RIGHT_TRIGGER_2] = (char)(CORE.Input.Gamepad.axisState[i][GAMEPAD_AXIS_RIGHT_TRIGGER] > 0.1); - CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST; + CORE.Input.Gamepad.axisCount = GLFW_GAMEPAD_AXIS_LAST + 1; } } - CORE.Window.resized = false; + CORE.Window.resizedLastFrame = false; #if defined(SUPPORT_EVENTS_WAITING) glfwWaitEvents(); #else glfwPollEvents(); // Register keyboard/mouse events (callbacks)... and window events! #endif -#endif //defined(PLATFORM_DESKTOP) +#endif // PLATFORM_DESKTOP // Gamepad support using emscripten API // NOTE: GLFW3 joystick functionality not available in web @@ -3704,13 +4775,39 @@ static void PollInputEvents(void) // Register buttons data for every connected gamepad for (int j = 0; (j < gamepadState.numButtons) && (j < MAX_GAMEPAD_BUTTONS); j++) { - const GamepadButton button = GetGamepadButton(j); - if (gamepadState.digitalButton[j] == 1) + GamepadButton button = -1; + + // Gamepad Buttons reference: https://www.w3.org/TR/gamepad/#gamepad-interface + switch (j) { - CORE.Input.Gamepad.currentState[i][button] = 1; - CORE.Input.Gamepad.lastButtonPressed = button; + case 0: button = GAMEPAD_BUTTON_RIGHT_FACE_DOWN; break; + case 1: button = GAMEPAD_BUTTON_RIGHT_FACE_RIGHT; break; + case 2: button = GAMEPAD_BUTTON_RIGHT_FACE_LEFT; break; + case 3: button = GAMEPAD_BUTTON_RIGHT_FACE_UP; break; + case 4: button = GAMEPAD_BUTTON_LEFT_TRIGGER_1; break; + case 5: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_1; break; + case 6: button = GAMEPAD_BUTTON_LEFT_TRIGGER_2; break; + case 7: button = GAMEPAD_BUTTON_RIGHT_TRIGGER_2; break; + case 8: button = GAMEPAD_BUTTON_MIDDLE_LEFT; break; + case 9: button = GAMEPAD_BUTTON_MIDDLE_RIGHT; break; + case 10: button = GAMEPAD_BUTTON_LEFT_THUMB; break; + case 11: button = GAMEPAD_BUTTON_RIGHT_THUMB; break; + case 12: button = GAMEPAD_BUTTON_LEFT_FACE_UP; break; + case 13: button = GAMEPAD_BUTTON_LEFT_FACE_DOWN; break; + case 14: button = GAMEPAD_BUTTON_LEFT_FACE_LEFT; break; + case 15: button = GAMEPAD_BUTTON_LEFT_FACE_RIGHT; break; + default: break; + } + + if (button != -1) // Check for valid button + { + if (gamepadState.digitalButton[j] == 1) + { + CORE.Input.Gamepad.currentState[i][button] = 1; + CORE.Input.Gamepad.lastButtonPressed = button; + } + else CORE.Input.Gamepad.currentState[i][button] = 0; } - else CORE.Input.Gamepad.currentState[i][button] = 0; //TRACELOGD("INPUT: Gamepad %d, button %d: Digital: %d, Analog: %g", gamepadState.index, j, gamepadState.digitalButton[j], gamepadState.analogButton[j]); } @@ -3751,7 +4848,7 @@ static void PollInputEvents(void) } #endif -#if defined(PLATFORM_RPI) && defined(SUPPORT_SSH_KEYBOARD_RPI) +#if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI) // NOTE: Keyboard reading could be done using input_event(s) reading or just read from stdin, // we now use both methods inside here. 2nd method is still used for legacy purposes (Allows for input trough SSH console) ProcessKeyboard(); @@ -3768,9 +4865,58 @@ static void SwapBuffers(void) glfwSwapBuffers(CORE.Window.handle); #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_UWP) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP) eglSwapBuffers(CORE.Window.device, CORE.Window.surface); -#endif + +#if defined(PLATFORM_DRM) + if (!CORE.Window.gbmSurface || (-1 == CORE.Window.fd) || !CORE.Window.connector || !CORE.Window.crtc) + { + TRACELOG(LOG_ERROR, "DISPLAY: DRM initialization failed to swap"); + abort(); + } + + struct gbm_bo *bo = gbm_surface_lock_front_buffer(CORE.Window.gbmSurface); + if (!bo) + { + TRACELOG(LOG_ERROR, "DISPLAY: Failed GBM to lock front buffer"); + abort(); + } + + uint32_t fb = 0; + int result = drmModeAddFB(CORE.Window.fd, CORE.Window.connector->modes[CORE.Window.modeIndex].hdisplay, + CORE.Window.connector->modes[CORE.Window.modeIndex].vdisplay, 24, 32, gbm_bo_get_stride(bo), gbm_bo_get_handle(bo).u32, &fb); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeAddFB() failed with result: %d", result); + abort(); + } + + result = drmModeSetCrtc(CORE.Window.fd, CORE.Window.crtc->crtc_id, fb, 0, 0, + &CORE.Window.connector->connector_id, 1, &CORE.Window.connector->modes[CORE.Window.modeIndex]); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeSetCrtc() failed with result: %d", result); + abort(); + } + + if (CORE.Window.prevFB) + { + result = drmModeRmFB(CORE.Window.fd, CORE.Window.prevFB); + if (0 != result) + { + TRACELOG(LOG_ERROR, "DISPLAY: drmModeRmFB() failed with result: %d", result); + abort(); + } + } + CORE.Window.prevFB = fb; + + if (CORE.Window.prevBO) + { + gbm_surface_release_buffer(CORE.Window.gbmSurface, CORE.Window.prevBO); + } + CORE.Window.prevBO = bo; +#endif // PLATFORM_DRM +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP } #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) @@ -3780,21 +4926,60 @@ static void ErrorCallback(int error, const char *description) TRACELOG(LOG_WARNING, "GLFW: Error: %i Description: %s", error, description); } -// GLFW3 Srolling Callback, runs on mouse wheel -static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset) + +// GLFW3 WindowSize Callback, runs when window is resizedLastFrame +// NOTE: Window resizing not allowed by default +static void WindowSizeCallback(GLFWwindow *window, int width, int height) { - CORE.Input.Mouse.currentWheelMove = (int)yoffset; + SetupViewport(width, height); // Reset viewport and projection matrix for new size + CORE.Window.currentFbo.width = width; + CORE.Window.currentFbo.height = height; + CORE.Window.resizedLastFrame = true; + + if (IsWindowFullscreen()) return; + + // Set current screen size + CORE.Window.screen.width = width; + CORE.Window.screen.height = height; + // NOTE: Postprocessing texture is not scaled to new size + +} + +// GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowIconifyCallback(GLFWwindow *window, int iconified) +{ + if (iconified) CORE.Window.flags |= FLAG_WINDOW_MINIMIZED; // The window was iconified + else CORE.Window.flags &= ~FLAG_WINDOW_MINIMIZED; // The window was restored +} + +#if !defined(PLATFORM_WEB) +// GLFW3 WindowMaximize Callback, runs when window is maximized/restored +static void WindowMaximizeCallback(GLFWwindow *window, int maximized) +{ + if (maximized) CORE.Window.flags |= FLAG_WINDOW_MAXIMIZED; // The window was maximized + else CORE.Window.flags &= ~FLAG_WINDOW_MAXIMIZED; // The window was restored +} +#endif + +// GLFW3 WindowFocus Callback, runs when window get/lose focus +static void WindowFocusCallback(GLFWwindow *window, int focused) +{ + if (focused) CORE.Window.flags &= ~FLAG_WINDOW_UNFOCUSED; // The window was focused + else CORE.Window.flags |= FLAG_WINDOW_UNFOCUSED; // The window lost focus } // GLFW3 Keyboard Callback, runs on key pressed static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) { + //TRACELOG(LOG_DEBUG, "Key Callback: KEY:%i(%c) - SCANCODE:%i (STATE:%i)", key, key, scancode, action); + if (key == CORE.Input.Keyboard.exitKey && action == GLFW_PRESS) { glfwSetWindowShouldClose(CORE.Window.handle, GLFW_TRUE); // NOTE: Before closing window, while loop must be left! } +#if defined(SUPPORT_SCREEN_CAPTURE) else if (key == GLFW_KEY_F12 && action == GLFW_PRESS) { #if defined(SUPPORT_GIF_RECORDING) @@ -3802,9 +4987,21 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i { if (gifRecording) { - GifEnd(); gifRecording = false; + MsfGifResult result = msf_gif_end(&gifState); + + char path[512] = { 0 }; + #if defined(PLATFORM_ANDROID) + strcpy(path, CORE.Android.internalDataPath); + strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); + #else + strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); + #endif + + SaveFileData(path, result.data, (unsigned int)result.dataSize); + msf_gif_free(result); + #if defined(PLATFORM_WEB) // Download file from MEMFS (emscripten memory filesystem) // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html @@ -3818,17 +5015,7 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i gifRecording = true; gifFramesCounter = 0; - char path[512] = { 0 }; - #if defined(PLATFORM_ANDROID) - strcpy(path, CORE.Android.internalDataPath); - strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); - #else - strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); - #endif - - // NOTE: delay represents the time between frames in the gif, if we capture a gif frame every - // 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10. - GifBegin(path, CORE.Window.screen.width, CORE.Window.screen.height, (int)(GetFrameTime()*10.0f), 8, false); + msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); screenshotCounter++; TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); @@ -3836,19 +5023,45 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i } else #endif // SUPPORT_GIF_RECORDING -#if defined(SUPPORT_SCREEN_CAPTURE) { TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); screenshotCounter++; } -#endif // SUPPORT_SCREEN_CAPTURE } +#endif // SUPPORT_SCREEN_CAPTURE else { // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1 // to work properly with our implementation (IsKeyDown/IsKeyUp checks) if (action == GLFW_RELEASE) CORE.Input.Keyboard.currentKeyState[key] = 0; else CORE.Input.Keyboard.currentKeyState[key] = 1; + + // Check if there is space available in the key queue + if ((CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) && (action == GLFW_PRESS)) + { + // Add character to the queue + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; + CORE.Input.Keyboard.keyPressedQueueCount++; + } + } +} + +// GLFW3 Char Key Callback, runs on key down (gets equivalent unicode char value) +static void CharCallback(GLFWwindow *window, unsigned int key) +{ + //TRACELOG(LOG_DEBUG, "Char Callback: KEY:%i(%c)", key, key); + + // NOTE: Registers any key down considering OS keyboard layout but + // do not detects action events, those should be managed by user... + // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 + // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char + + // Check if there is space available in the queue + if (CORE.Input.Keyboard.charPressedQueueCount < MAX_KEY_PRESSED_QUEUE) + { + // Add character to the queue + CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount] = key; + CORE.Input.Keyboard.charPressedQueueCount++; } } @@ -3918,21 +5131,10 @@ static void MouseCursorPosCallback(GLFWwindow *window, double x, double y) #endif } -// GLFW3 Char Key Callback, runs on key down (get unicode char value) -static void CharCallback(GLFWwindow *window, unsigned int key) +// GLFW3 Srolling Callback, runs on mouse wheel +static void MouseScrollCallback(GLFWwindow *window, double xoffset, double yoffset) { - // NOTE: Registers any key down considering OS keyboard layout but - // do not detects action events, those should be managed by user... - // Ref: https://github.com/glfw/glfw/issues/668#issuecomment-166794907 - // Ref: https://www.glfw.org/docs/latest/input_guide.html#input_char - - // Check if there is space available in the queue - if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) - { - // Add character to the queue - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = key; - CORE.Input.Keyboard.keyPressedQueueCount++; - } + CORE.Input.Mouse.currentWheelMove = (float)yoffset; } // GLFW3 CursorEnter Callback, when cursor enters the window @@ -3942,37 +5144,6 @@ static void CursorEnterCallback(GLFWwindow *window, int enter) else CORE.Input.Mouse.cursorOnScreen = false; } -// GLFW3 WindowSize Callback, runs when window is resized -// NOTE: Window resizing not allowed by default -static void WindowSizeCallback(GLFWwindow *window, int width, int height) -{ - SetupViewport(width, height); // Reset viewport and projection matrix for new size - - // Set current screen size - CORE.Window.screen.width = width; - CORE.Window.screen.height = height; - CORE.Window.currentFbo.width = width; - CORE.Window.currentFbo.height = height; - - // NOTE: Postprocessing texture is not scaled to new size - - CORE.Window.resized = true; -} - -// GLFW3 WindowIconify Callback, runs when window is minimized/restored -static void WindowIconifyCallback(GLFWwindow *window, int iconified) -{ - if (iconified) CORE.Window.minimized = true; // The window was iconified - else CORE.Window.minimized = false; // The window was restored -} - -// GLFW3 WindowFocus Callback, runs when window get/lose focus -static void WindowFocusCallback(GLFWwindow *window, int focused) -{ - if (focused) CORE.Window.focused = true; // The window was focused - else CORE.Window.focused = false; // The window lost focus -} - // GLFW3 Window Drop Callback, runs when drop files into window // NOTE: Paths are stored in dynamic memory for further retrieval // Everytime new files are dropped, old ones are discarded @@ -3980,22 +5151,16 @@ static void WindowDropCallback(GLFWwindow *window, int count, const char **paths { ClearDroppedFiles(); - CORE.Window.dropFilesPath = (char **)RL_MALLOC(sizeof(char *)*count); + CORE.Window.dropFilesPath = (char **)RL_MALLOC(count*sizeof(char *)); for (int i = 0; i < count; i++) { - CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(sizeof(char)*MAX_FILEPATH_LENGTH); + CORE.Window.dropFilesPath[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); strcpy(CORE.Window.dropFilesPath[i], paths[i]); } CORE.Window.dropFilesCount = count; } - -static void WindowMaximizeCallback(GLFWwindow *window, int maximized) -{ - if (maximized) CORE.Window.maximized = true; // The window was maximized - else CORE.Window.maximized = false; // The window was restored -} #endif #if defined(PLATFORM_ANDROID) @@ -4114,7 +5279,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (type == AINPUT_EVENT_TYPE_MOTION) { - if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || + if (((source & AINPUT_SOURCE_JOYSTICK) == AINPUT_SOURCE_JOYSTICK) || ((source & AINPUT_SOURCE_GAMEPAD) == AINPUT_SOURCE_GAMEPAD)) { // Get first touch position @@ -4186,11 +5351,11 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) if (flags == AMOTION_EVENT_ACTION_DOWN || flags == AMOTION_EVENT_ACTION_MOVE) { - CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 1; + CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 1; } else if (flags == AMOTION_EVENT_ACTION_UP) { - CORE.Input.Touch.currentTouchState[MOUSE_LEFT_BUTTON] = 0; + CORE.Input.Touch.currentTouchState[MOUSE_BUTTON_LEFT] = 0; } #if defined(SUPPORT_GESTURES_SYSTEM) @@ -4236,69 +5401,6 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) #endif #if defined(PLATFORM_WEB) -// Register fullscreen change events -static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) -{ - //isFullscreen: int event->isFullscreen - //fullscreenEnabled: int event->fullscreenEnabled - //fs element nodeName: (char *) event->nodeName - //fs element id: (char *) event->id - //Current element size: (int) event->elementWidth, (int) event->elementHeight - //Screen size:(int) event->screenWidth, (int) event->screenHeight -/* - if (event->isFullscreen) - { - CORE.Window.fullscreen = true; - TRACELOG(LOG_INFO, "WEB: Canvas scaled to fullscreen. ElementSize: (%ix%i), ScreenSize(%ix%i)", event->elementWidth, event->elementHeight, event->screenWidth, event->screenHeight); - } - else - { - CORE.Window.fullscreen = false; - TRACELOG(LOG_INFO, "WEB: Canvas scaled to windowed. ElementSize: (%ix%i), ScreenSize(%ix%i)", event->elementWidth, event->elementHeight, event->screenWidth, event->screenHeight); - } - - // TODO: Depending on scaling factor (screen vs element), calculate factor to scale mouse/touch input -*/ - return 0; -} - -// Register keyboard input events -static EM_BOOL EmscriptenKeyboardCallback(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData) -{ - if ((eventType == EMSCRIPTEN_EVENT_KEYDOWN) && (keyEvent->keyCode == 27)) // ESCAPE key (strcmp(keyEvent->code, "Escape") == 0) - { - // WARNING: Not executed when pressing Esc to exit fullscreen, it seems document has priority over #canvas - - emscripten_exit_pointerlock(); - CORE.Window.fullscreen = false; - TRACELOG(LOG_INFO, "CORE.Window.fullscreen = %s", CORE.Window.fullscreen? "true" : "false"); - } - - return 0; -} - -// Register mouse input events -static EM_BOOL EmscriptenMouseCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) -{ - // Lock mouse pointer when click on screen - if (eventType == EMSCRIPTEN_EVENT_CLICK) - { - EmscriptenPointerlockChangeEvent plce; - emscripten_get_pointerlock_status(&plce); - - int result = emscripten_request_pointerlock("#canvas", 1); // TODO: It does not work! - - // result -> EMSCRIPTEN_RESULT_DEFERRED - // The requested operation cannot be completed now for web security reasons, - // and has been deferred for completion in the next event handler. --> but it never happens! - - //if (!plce.isActive) emscripten_request_pointerlock(0, 1); - //else emscripten_exit_pointerlock(); - } - - return 0; -} - // Register touch input events static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { @@ -4384,19 +5486,9 @@ static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadE return 0; } - -static EM_BOOL EmscriptenWindowResizedCallback(int eventType, const void *reserved, void *userData) -{ - double width, height; - emscripten_get_element_css_size("canvas", &width, &height); - - // TODO. - - return true; -} #endif -#if defined(PLATFORM_RPI) +#if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) #if defined(SUPPORT_SSH_KEYBOARD_RPI) // Initialize Keyboard system (using standard input) @@ -4459,7 +5551,7 @@ static void ProcessKeyboard(void) bufferByteCount = read(STDIN_FILENO, keysBuffer, MAX_KEYBUFFER_SIZE); // POSIX system call // Reset pressed keys array (it will be filled below) - for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; + if (bufferByteCount > 0) for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; // Check keys from event input workers (This is the new keyboard reading method) //for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = CORE.Input.Keyboard.currentKeyStateEvdev[i]; @@ -4568,7 +5660,7 @@ static void RestoreKeyboard(void) // Reconfigure keyboard to default mode ioctl(STDIN_FILENO, KDSKBMODE, CORE.Input.Keyboard.defaultMode); } -#endif //SUPPORT_SSH_KEYBOARD_RPI +#endif // SUPPORT_SSH_KEYBOARD_RPI // Initialise user input from evdev(/dev/input/event) this means mouse, keyboard or gamepad devices static void InitEvdevInput(void) @@ -4577,6 +5669,9 @@ static void InitEvdevInput(void) DIR *directory; struct dirent *entity; + // Initialise keyboard file descriptor + CORE.Input.Keyboard.fd = -1; + // Reset variables for (int i = 0; i < MAX_TOUCH_POINTS; ++i) { @@ -4584,10 +5679,6 @@ static void InitEvdevInput(void) CORE.Input.Touch.position[i].y = -1; } - // Reset keypress buffer - CORE.Input.Keyboard.lastKeyPressed.head = 0; - CORE.Input.Keyboard.lastKeyPressed.tail = 0; - // Reset keyboard key state for (int i = 0; i < 512; i++) CORE.Input.Keyboard.currentKeyState[i] = 0; @@ -4601,7 +5692,7 @@ static void InitEvdevInput(void) if (strncmp("event", entity->d_name, strlen("event")) == 0) // Search for devices named "event*" { sprintf(path, "%s%s", DEFAULT_EVDEV_PATH, entity->d_name); - EventThreadSpawn(path); // Identify the device and spawn a thread for it + ConfigureEvdevDevice(path); // Configure the device if appropriate } } @@ -4610,8 +5701,8 @@ static void InitEvdevInput(void) else TRACELOG(LOG_WARNING, "RPI: Failed to open linux event directory: %s", DEFAULT_EVDEV_PATH); } -// Identifies a input device and spawns a thread to handle it if needed -static void EventThreadSpawn(char *device) +// Identifies a input device and configures it for use if appropriate +static void ConfigureEvdevDevice(char *device) { #define BITS_PER_LONG (8*sizeof(long)) #define NBITS(x) ((((x) - 1)/BITS_PER_LONG) + 1) @@ -4661,7 +5752,7 @@ static void EventThreadSpawn(char *device) fd = open(device, O_RDONLY | O_NONBLOCK); if (fd < 0) { - TRACELOG(LOG_WARNING, "RPI: Failed to open input device (error: %d)", device, worker->fd); + TRACELOG(LOG_WARNING, "RPI: Failed to open input device %s", device); return; } worker->fd = fd; @@ -4683,7 +5774,7 @@ static void EventThreadSpawn(char *device) // Identify the device //------------------------------------------------------------------------------------------------------- - ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the avalable device properties + ioctl(fd, EVIOCGBIT(0, sizeof(evBits)), evBits); // Read a bitfield of the available device properties // Check for absolute input devices if (TEST_BIT(evBits, EV_ABS)) @@ -4759,15 +5850,22 @@ static void EventThreadSpawn(char *device) // Decide what to do with the device //------------------------------------------------------------------------------------------------------- - if (worker->isTouch || worker->isMouse || worker->isKeyboard) + if (worker->isKeyboard && CORE.Input.Keyboard.fd == -1) + { + // Use the first keyboard encountered. This assumes that a device that says it's a keyboard is just a + // keyboard. The keyboard is polled synchronously, whereas other input devices are polled in separate + // threads so that they don't drop events when the frame rate is slow. + TRACELOG(LOG_INFO, "RPI: Opening keyboard device: %s", device); + CORE.Input.Keyboard.fd = worker->fd; + } + else if (worker->isTouch || worker->isMouse) { // Looks like an interesting device - TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s%s)", device, + TRACELOG(LOG_INFO, "RPI: Opening input device: %s (%s%s%s%s)", device, worker->isMouse? "mouse " : "", worker->isMultitouch? "multitouch " : "", worker->isTouch? "touchscreen " : "", - worker->isGamepad? "gamepad " : "", - worker->isKeyboard? "keyboard " : ""); + worker->isGamepad? "gamepad " : ""); // Create a thread for this device int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker); @@ -4787,7 +5885,7 @@ static void EventThreadSpawn(char *device) if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum > maxTouchNumber)) maxTouchNumber = CORE.Input.eventWorker[i].eventNum; } - // Find toucnscreens with lower indexes + // Find touchscreens with lower indexes for (int i = 0; i < sizeof(CORE.Input.eventWorker)/sizeof(InputEventWorker); ++i) { if (CORE.Input.eventWorker[i].isTouch && (CORE.Input.eventWorker[i].eventNum < maxTouchNumber)) @@ -4806,8 +5904,7 @@ static void EventThreadSpawn(char *device) //------------------------------------------------------------------------------------------------------- } -// Input device events reading thread -static void *EventThread(void *arg) +static void PollKeyboardEvents(void) { // Scancode to keycode mapping for US keyboards // TODO: Probably replace this with a keymap from the X11 to get the correct regional map for the keyboard: @@ -4829,17 +5926,67 @@ static void *EventThread(void *arg) 227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242, 243,244,245,246,247,248,0,0,0,0,0,0,0, }; + int fd = CORE.Input.Keyboard.fd; + if (fd == -1) return; + + struct input_event event; + int keycode; + + // Try to read data from the keyboard and only continue if successful + while (read(fd, &event, sizeof(event)) == (int)sizeof(event)) + { + // Button parsing + if (event.type == EV_KEY) + { + // Keyboard button parsing + if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 + { + keycode = keymap_US[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode + + // Make sure we got a valid keycode + if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) + { + // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt + // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, + // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat + CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; + if (event.value >= 1) + { + CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed + CORE.Input.Keyboard.keyPressedQueueCount++; + } + + #if defined(SUPPORT_SCREEN_CAPTURE) + // Check screen capture key (raylib key: KEY_F12) + if (CORE.Input.Keyboard.currentKeyState[301] == 1) + { + TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); + screenshotCounter++; + } + #endif + + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; + + TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); + } + } + } + } +} + +// Input device events reading thread +static void *EventThread(void *arg) +{ struct input_event event; InputEventWorker *worker = (InputEventWorker *)arg; int touchAction = -1; bool gestureUpdate = false; - int keycode; while (!CORE.Window.shouldClose) { // Try to read data from the device and only continue if successful - if (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) + while (read(worker->fd, &event, sizeof(event)) == (int)sizeof(event)) { // Relative movement parsing if (event.type == EV_REL) @@ -4875,7 +6022,8 @@ static void *EventThread(void *arg) // Basic movement if (event.code == ABS_X) { - CORE.Input.Mouse.position.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + CORE.Input.Mouse.position.x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange + CORE.Input.Touch.position[0].x = (event.value - worker->absRange.x)*CORE.Window.screen.width/worker->absRange.width; // Scale acording to absRange #if defined(SUPPORT_GESTURES_SYSTEM) touchAction = TOUCH_MOVE; @@ -4885,7 +6033,8 @@ static void *EventThread(void *arg) if (event.code == ABS_Y) { - CORE.Input.Mouse.position.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + CORE.Input.Mouse.position.y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange + CORE.Input.Touch.position[0].y = (event.value - worker->absRange.y)*CORE.Window.screen.height/worker->absRange.height; // Scale acording to absRange #if defined(SUPPORT_GESTURES_SYSTEM) touchAction = TOUCH_MOVE; @@ -4894,7 +6043,7 @@ static void *EventThread(void *arg) } // Multitouch movement - if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remeber the slot number for the folowing events + if (event.code == ABS_MT_SLOT) worker->touchSlot = event.value; // Remember the slot number for the folowing events if (event.code == ABS_MT_POSITION_X) { @@ -4915,6 +6064,33 @@ static void *EventThread(void *arg) CORE.Input.Touch.position[worker->touchSlot].y = -1; } } + + // Touchscreen tap + if (event.code == ABS_PRESSURE) + { + int previousMouseLeftButtonState = CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT]; + + if (!event.value && previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 0; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_UP; + gestureUpdate = true; + #endif + } + + if (event.value && !previousMouseLeftButtonState) + { + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = 1; + + #if defined(SUPPORT_GESTURES_SYSTEM) + touchAction = TOUCH_DOWN; + gestureUpdate = true; + #endif + } + } + } // Button parsing @@ -4923,7 +6099,7 @@ static void *EventThread(void *arg) // Mouse button parsing if ((event.code == BTN_TOUCH) || (event.code == BTN_LEFT)) { - CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_LEFT_BUTTON] = event.value; + CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_LEFT] = event.value; #if defined(SUPPORT_GESTURES_SYSTEM) if (event.value > 0) touchAction = TOUCH_DOWN; @@ -4932,49 +6108,23 @@ static void *EventThread(void *arg) #endif } - if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_RIGHT_BUTTON] = event.value; - if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_MIDDLE_BUTTON] = event.value; - - // Keyboard button parsing - if ((event.code >= 1) && (event.code <= 255)) //Keyboard keys appear for codes 1 to 255 - { - keycode = keymap_US[event.code & 0xFF]; // The code we get is a scancode so we look up the apropriate keycode - - // Make sure we got a valid keycode - if ((keycode > 0) && (keycode < sizeof(CORE.Input.Keyboard.currentKeyState))) - { - // WARNING: https://www.kernel.org/doc/Documentation/input/input.txt - // Event interface: 'value' is the value the event carries. Either a relative change for EV_REL, - // absolute new value for EV_ABS (joysticks ...), or 0 for EV_KEY for release, 1 for keypress and 2 for autorepeat - CORE.Input.Keyboard.currentKeyState[keycode] = (event.value >= 1)? 1 : 0; - if (event.value >= 1) - { - CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = keycode; // Register last key pressed - CORE.Input.Keyboard.keyPressedQueueCount++; - } - - #if defined(SUPPORT_SCREEN_CAPTURE) - // Check screen capture key (raylib key: KEY_F12) - if (CORE.Input.Keyboard.currentKeyState[301] == 1) - { - TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); - screenshotCounter++; - } - #endif - - if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; - - TRACELOGD("RPI: KEY_%s ScanCode: %4i KeyCode: %4i", event.value == 0 ? "UP":"DOWN", event.code, keycode); - } - } + if (event.code == BTN_RIGHT) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_RIGHT] = event.value; + if (event.code == BTN_MIDDLE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_MIDDLE] = event.value; + if (event.code == BTN_SIDE) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_SIDE] = event.value; + if (event.code == BTN_EXTRA) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_EXTRA] = event.value; + if (event.code == BTN_FORWARD) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_FORWARD] = event.value; + if (event.code == BTN_BACK) CORE.Input.Mouse.currentButtonStateEvdev[MOUSE_BUTTON_BACK] = event.value; } // Screen confinement - if (CORE.Input.Mouse.position.x < 0) CORE.Input.Mouse.position.x = 0; - if (CORE.Input.Mouse.position.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.position.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; + if (!CORE.Input.Mouse.cursorHidden) + { + if (CORE.Input.Mouse.position.x < 0) CORE.Input.Mouse.position.x = 0; + if (CORE.Input.Mouse.position.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.position.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; - if (CORE.Input.Mouse.position.y < 0) CORE.Input.Mouse.position.y = 0; - if (CORE.Input.Mouse.position.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.position.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + if (CORE.Input.Mouse.position.y < 0) CORE.Input.Mouse.position.y = 0; + if (CORE.Input.Mouse.position.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.position.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + } // Gesture update if (gestureUpdate) @@ -5004,7 +6154,7 @@ static void *EventThread(void *arg) #endif } } - else Wait(5); // Sleep for 5ms to avoid hogging CPU time + Wait(5); // Sleep for 5ms to avoid hogging CPU time } close(worker->fd); @@ -5070,7 +6220,7 @@ static void *GamepadThread(void *arg) // Process gamepad events by type if (gamepadEvent.type == JS_EVENT_BUTTON) { - TRACELOGD("RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + //TRACELOG(LOG_WARNING, "RPI: Gamepad button: %i, value: %i", gamepadEvent.number, gamepadEvent.value); if (gamepadEvent.number < MAX_GAMEPAD_BUTTONS) { @@ -5083,7 +6233,7 @@ static void *GamepadThread(void *arg) } else if (gamepadEvent.type == JS_EVENT_AXIS) { - TRACELOGD("RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); + //TRACELOG(LOG_WARNING, "RPI: Gamepad axis: %i, value: %i", gamepadEvent.number, gamepadEvent.value); if (gamepadEvent.number < MAX_GAMEPAD_AXIS) { @@ -5098,7 +6248,7 @@ static void *GamepadThread(void *arg) return NULL; } -#endif // PLATFORM_RPI +#endif // PLATFORM_RPI || PLATFORM_DRM #if defined(PLATFORM_UWP) // UWP function pointers @@ -5152,7 +6302,7 @@ void UWPSetMouseSetPosFunc(UWPMouseSetPosFunc func) { uwpMouseSetPosFunc = func; void *UWPGetCoreWindowPtr() { return uwpCoreWindow; } void UWPSetCoreWindowPtr(void* ptr) { uwpCoreWindow = ptr; } -void UWPMouseWheelEvent(int deltaY) { CORE.Input.Mouse.currentWheelMove = (int)deltaY; } +void UWPMouseWheelEvent(int deltaY) { CORE.Input.Mouse.currentWheelMove = (float)deltaY; } void UWPKeyDownEvent(int key, bool down, bool controlKey) { @@ -5161,6 +6311,7 @@ void UWPKeyDownEvent(int key, bool down, bool controlKey) // Time to close the window. CORE.Window.shouldClose = true; } +#if defined(SUPPORT_SCREEN_CAPTURE) else if (key == KEY_F12 && down) { #if defined(SUPPORT_GIF_RECORDING) @@ -5168,9 +6319,13 @@ void UWPKeyDownEvent(int key, bool down, bool controlKey) { if (gifRecording) { - GifEnd(); gifRecording = false; + MsfGifResult result = msf_gif_end(&gifState); + + SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.UWP.internalDataPath, screenshotCounter), result.data, result.dataSize); + msf_gif_free(result); + #if defined(PLATFORM_WEB) // Download file from MEMFS (emscripten memory filesystem) // saveFileFromMEMFSToDisk() function is defined in raylib/templates/web_shel/shell.html @@ -5183,13 +6338,7 @@ void UWPKeyDownEvent(int key, bool down, bool controlKey) gifRecording = true; gifFramesCounter = 0; - char path[512] = { 0 }; - strcpy(path, CORE.UWP.internalDataPath); - strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter)); - - // NOTE: delay represents the time between frames in the gif, if we capture a gif frame every - // 10 game frames and each frame trakes 16.6ms (60fps), delay between gif frames should be ~16.6*10. - GifBegin(path, CORE.Window.screen.width, CORE.Window.screen.height, (int)(GetFrameTime() * 10.0f), 8, false); + msf_gif_begin(&gifState, CORE.Window.screen.width, CORE.Window.screen.height); screenshotCounter++; TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); @@ -5197,13 +6346,12 @@ void UWPKeyDownEvent(int key, bool down, bool controlKey) } else #endif // SUPPORT_GIF_RECORDING -#if defined(SUPPORT_SCREEN_CAPTURE) { TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); screenshotCounter++; } -#endif // SUPPORT_SCREEN_CAPTURE } +#endif // SUPPORT_SCREEN_CAPTURE else { CORE.Input.Keyboard.currentKeyState[key] = down; @@ -5294,7 +6442,7 @@ void UWPResizeEvent(int width, int height) // NOTE: Postprocessing texture is not scaled to new size - CORE.Window.resized = true; + CORE.Window.resizedLastFrame = true; } void UWPActivateGamepadEvent(int gamepad, bool active) @@ -5372,4 +6520,100 @@ void UWPGestureTouch(int pointer, float x, float y, bool touch) #endif } -#endif // PLATFORM_UWP +#endif // PLATFORM_UWP + +#if defined(PLATFORM_DRM) +// Search matching DRM mode in connector's mode list +static int FindMatchingConnectorMode(const drmModeConnector *connector, const drmModeModeInfo *mode) +{ + if (NULL == connector) return -1; + if (NULL == mode) return -1; + + // safe bitwise comparison of two modes + #define BINCMP(a, b) memcmp((a), (b), (sizeof(a) < sizeof(b)) ? sizeof(a) : sizeof(b)) + + for (size_t i = 0; i < connector->count_modes; i++) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, connector->modes[i].hdisplay, connector->modes[i].vdisplay, + connector->modes[i].vrefresh, (connector->modes[i].flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if (0 == BINCMP(&CORE.Window.crtc->mode, &CORE.Window.connector->modes[i])) return i; + } + + return -1; + + #undef BINCMP +} + +// Search exactly matching DRM connector mode in connector's list +static int FindExactConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching exact connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); + + if (NULL == connector) return -1; + + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM Mode %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) continue; + + if ((mode->hdisplay == width) && (mode->vdisplay == height) && (mode->vrefresh == fps)) return i; + } + + TRACELOG(LOG_TRACE, "DISPLAY: No DRM exact matching mode found"); + return -1; +} + +// Search the nearest matching DRM connector mode in connector's list +static int FindNearestConnectorMode(const drmModeConnector *connector, uint width, uint height, uint fps, bool allowInterlaced) +{ + TRACELOG(LOG_TRACE, "DISPLAY: Searching nearest connector mode for %ux%u@%u, selecting an interlaced mode is allowed: %s", width, height, fps, allowInterlaced ? "yes" : "no"); + + if (NULL == connector) return -1; + + int nearestIndex = -1; + for (int i = 0; i < CORE.Window.connector->count_modes; i++) + { + const drmModeModeInfo *const mode = &CORE.Window.connector->modes[i]; + + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode: %d %ux%u@%u %s", i, mode->hdisplay, mode->vdisplay, mode->vrefresh, + (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "interlaced" : "progressive"); + + if ((mode->hdisplay < width) || (mode->vdisplay < height) | (mode->vrefresh < fps)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM mode is too small"); + continue; + } + + if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && (!allowInterlaced)) + { + TRACELOG(LOG_TRACE, "DISPLAY: DRM shouldn't choose an interlaced mode"); + continue; + } + + if ((mode->hdisplay >= width) && (mode->vdisplay >= height) && (mode->vrefresh >= fps)) + { + const int widthDiff = mode->hdisplay - width; + const int heightDiff = mode->vdisplay - height; + const int fpsDiff = mode->vrefresh - fps; + + if (nearestIndex < 0) + { + nearestIndex = i; + continue; + } + + const int nearestWidthDiff = CORE.Window.connector->modes[nearestIndex].hdisplay - width; + const int nearestHeightDiff = CORE.Window.connector->modes[nearestIndex].vdisplay - height; + const int nearestFpsDiff = CORE.Window.connector->modes[nearestIndex].vrefresh - fps; + + if ((widthDiff < nearestWidthDiff) || (heightDiff < nearestHeightDiff) || (fpsDiff < nearestFpsDiff)) nearestIndex = i; + } + } + + return nearestIndex; +} +#endif diff --git a/raylib/external/cgltf.h b/raylib/external/cgltf.h index bbc641c..4cc2d7e 100644 --- a/raylib/external/cgltf.h +++ b/raylib/external/cgltf.h @@ -1,7 +1,7 @@ /** * cgltf - a single-file glTF 2.0 parser written in C99. * - * Version: 1.5 + * Version: 1.10 * * Website: https://github.com/jkuhlmann/cgltf * @@ -24,7 +24,7 @@ * * `cgltf_options` is the struct passed to `cgltf_parse()` to control * parts of the parsing process. You can use it to force the file type - * and provide memory allocation as well as file operation callbacks. + * and provide memory allocation as well as file operation callbacks. * Should be zero-initialized to trigger default behavior. * * `cgltf_data` is the struct allocated and filled by `cgltf_parse()`. @@ -42,8 +42,8 @@ * * `cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, * cgltf_size size, const char* base64, void** out_data)` decodes - * base64-encoded data content. Used internally by `cgltf_load_buffers()` - * and may be useful if you're not dealing with normal files. + * base64-encoded data content. Used internally by `cgltf_load_buffers()`. + * This is useful when decoding data URIs in images. * * `cgltf_result cgltf_parse_file(const cgltf_options* options, const * char* path, cgltf_data** out_data)` can be used to open the given @@ -239,22 +239,61 @@ typedef struct cgltf_extras { cgltf_size end_offset; } cgltf_extras; +typedef struct cgltf_extension { + char* name; + char* data; +} cgltf_extension; + typedef struct cgltf_buffer { + char* name; cgltf_size size; char* uri; void* data; /* loaded by cgltf_load_buffers */ cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_buffer; +typedef enum cgltf_meshopt_compression_mode { + cgltf_meshopt_compression_mode_invalid, + cgltf_meshopt_compression_mode_attributes, + cgltf_meshopt_compression_mode_triangles, + cgltf_meshopt_compression_mode_indices, +} cgltf_meshopt_compression_mode; + +typedef enum cgltf_meshopt_compression_filter { + cgltf_meshopt_compression_filter_none, + cgltf_meshopt_compression_filter_octahedral, + cgltf_meshopt_compression_filter_quaternion, + cgltf_meshopt_compression_filter_exponential, +} cgltf_meshopt_compression_filter; + +typedef struct cgltf_meshopt_compression +{ + cgltf_buffer* buffer; + cgltf_size offset; + cgltf_size size; + cgltf_size stride; + cgltf_size count; + cgltf_meshopt_compression_mode mode; + cgltf_meshopt_compression_filter filter; +} cgltf_meshopt_compression; + typedef struct cgltf_buffer_view { + char *name; cgltf_buffer* buffer; cgltf_size offset; cgltf_size size; cgltf_size stride; /* 0 == automatically determined by accessor */ cgltf_buffer_view_type type; + void* data; /* overrides buffer->data if present, filled by extensions */ + cgltf_bool has_meshopt_compression; + cgltf_meshopt_compression meshopt_compression; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_buffer_view; typedef struct cgltf_accessor_sparse @@ -268,10 +307,17 @@ typedef struct cgltf_accessor_sparse cgltf_extras extras; cgltf_extras indices_extras; cgltf_extras values_extras; + cgltf_size extensions_count; + cgltf_extension* extensions; + cgltf_size indices_extensions_count; + cgltf_extension* indices_extensions; + cgltf_size values_extensions_count; + cgltf_extension* values_extensions; } cgltf_accessor_sparse; typedef struct cgltf_accessor { + char* name; cgltf_component_type component_type; cgltf_bool normalized; cgltf_type type; @@ -286,6 +332,8 @@ typedef struct cgltf_accessor cgltf_bool is_sparse; cgltf_accessor_sparse sparse; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_accessor; typedef struct cgltf_attribute @@ -303,15 +351,20 @@ typedef struct cgltf_image cgltf_buffer_view* buffer_view; char* mime_type; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_image; typedef struct cgltf_sampler { + char* name; cgltf_int mag_filter; cgltf_int min_filter; cgltf_int wrap_s; cgltf_int wrap_t; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_sampler; typedef struct cgltf_texture @@ -320,6 +373,8 @@ typedef struct cgltf_texture cgltf_image* image; cgltf_sampler* sampler; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_texture; typedef struct cgltf_texture_transform @@ -338,6 +393,8 @@ typedef struct cgltf_texture_view cgltf_bool has_transform; cgltf_texture_transform transform; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_texture_view; typedef struct cgltf_pbr_metallic_roughness @@ -362,13 +419,70 @@ typedef struct cgltf_pbr_specular_glossiness cgltf_float glossiness_factor; } cgltf_pbr_specular_glossiness; +typedef struct cgltf_clearcoat +{ + cgltf_texture_view clearcoat_texture; + cgltf_texture_view clearcoat_roughness_texture; + cgltf_texture_view clearcoat_normal_texture; + + cgltf_float clearcoat_factor; + cgltf_float clearcoat_roughness_factor; +} cgltf_clearcoat; + +typedef struct cgltf_transmission +{ + cgltf_texture_view transmission_texture; + cgltf_float transmission_factor; +} cgltf_transmission; + +typedef struct cgltf_ior +{ + cgltf_float ior; +} cgltf_ior; + +typedef struct cgltf_specular +{ + cgltf_texture_view specular_texture; + cgltf_texture_view specular_color_texture; + cgltf_float specular_color_factor[3]; + cgltf_float specular_factor; +} cgltf_specular; + +typedef struct cgltf_volume +{ + cgltf_texture_view thickness_texture; + cgltf_float thickness_factor; + cgltf_float attenuation_color[3]; + cgltf_float attenuation_distance; +} cgltf_volume; + +typedef struct cgltf_sheen +{ + cgltf_texture_view sheen_color_texture; + cgltf_float sheen_color_factor[3]; + cgltf_texture_view sheen_roughness_texture; + cgltf_float sheen_roughness_factor; +} cgltf_sheen; + typedef struct cgltf_material { char* name; cgltf_bool has_pbr_metallic_roughness; cgltf_bool has_pbr_specular_glossiness; + cgltf_bool has_clearcoat; + cgltf_bool has_transmission; + cgltf_bool has_volume; + cgltf_bool has_ior; + cgltf_bool has_specular; + cgltf_bool has_sheen; cgltf_pbr_metallic_roughness pbr_metallic_roughness; cgltf_pbr_specular_glossiness pbr_specular_glossiness; + cgltf_clearcoat clearcoat; + cgltf_ior ior; + cgltf_specular specular; + cgltf_sheen sheen; + cgltf_transmission transmission; + cgltf_volume volume; cgltf_texture_view normal_texture; cgltf_texture_view occlusion_texture; cgltf_texture_view emissive_texture; @@ -378,13 +492,28 @@ typedef struct cgltf_material cgltf_bool double_sided; cgltf_bool unlit; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_material; +typedef struct cgltf_material_mapping +{ + cgltf_size variant; + cgltf_material* material; + cgltf_extras extras; +} cgltf_material_mapping; + typedef struct cgltf_morph_target { cgltf_attribute* attributes; cgltf_size attributes_count; } cgltf_morph_target; +typedef struct cgltf_draco_mesh_compression { + cgltf_buffer_view* buffer_view; + cgltf_attribute* attributes; + cgltf_size attributes_count; +} cgltf_draco_mesh_compression; + typedef struct cgltf_primitive { cgltf_primitive_type type; cgltf_accessor* indices; @@ -394,6 +523,12 @@ typedef struct cgltf_primitive { cgltf_morph_target* targets; cgltf_size targets_count; cgltf_extras extras; + cgltf_bool has_draco_mesh_compression; + cgltf_draco_mesh_compression draco_mesh_compression; + cgltf_material_mapping* mappings; + cgltf_size mappings_count; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_primitive; typedef struct cgltf_mesh { @@ -405,6 +540,8 @@ typedef struct cgltf_mesh { char** target_names; cgltf_size target_names_count; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_mesh; typedef struct cgltf_node cgltf_node; @@ -416,11 +553,15 @@ typedef struct cgltf_skin { cgltf_node* skeleton; cgltf_accessor* inverse_bind_matrices; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_skin; typedef struct cgltf_camera_perspective { + cgltf_bool has_aspect_ratio; cgltf_float aspect_ratio; cgltf_float yfov; + cgltf_bool has_zfar; cgltf_float zfar; cgltf_float znear; cgltf_extras extras; @@ -442,6 +583,8 @@ typedef struct cgltf_camera { cgltf_camera_orthographic orthographic; } data; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_camera; typedef struct cgltf_light { @@ -474,6 +617,8 @@ struct cgltf_node { cgltf_float scale[3]; cgltf_float matrix[16]; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; }; typedef struct cgltf_scene { @@ -481,6 +626,8 @@ typedef struct cgltf_scene { cgltf_node** nodes; cgltf_size nodes_count; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_scene; typedef struct cgltf_animation_sampler { @@ -488,6 +635,8 @@ typedef struct cgltf_animation_sampler { cgltf_accessor* output; cgltf_interpolation_type interpolation; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_animation_sampler; typedef struct cgltf_animation_channel { @@ -495,6 +644,8 @@ typedef struct cgltf_animation_channel { cgltf_node* target_node; cgltf_animation_path_type target_path; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_animation_channel; typedef struct cgltf_animation { @@ -504,14 +655,24 @@ typedef struct cgltf_animation { cgltf_animation_channel* channels; cgltf_size channels_count; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_animation; +typedef struct cgltf_material_variant +{ + char* name; + cgltf_extras extras; +} cgltf_material_variant; + typedef struct cgltf_asset { char* copyright; char* generator; char* version; char* min_version; cgltf_extras extras; + cgltf_size extensions_count; + cgltf_extension* extensions; } cgltf_asset; typedef struct cgltf_data @@ -565,8 +726,14 @@ typedef struct cgltf_data cgltf_animation* animations; cgltf_size animations_count; + cgltf_material_variant* variants; + cgltf_size variants_count; + cgltf_extras extras; + cgltf_size data_extensions_count; + cgltf_extension* data_extensions; + char** extensions_used; cgltf_size extensions_used_count; @@ -599,9 +766,10 @@ cgltf_result cgltf_load_buffers( cgltf_data* data, const char* gltf_path); - cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data); +void cgltf_decode_uri(char* uri); + cgltf_result cgltf_validate(cgltf_data* data); void cgltf_free(cgltf_data* data); @@ -632,8 +800,8 @@ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* * */ -#ifdef __INTELLISENSE__ -/* This makes MSVC intellisense work. */ +#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) +/* This makes MSVC/CLion intellisense work. */ #define CGLTF_IMPLEMENTATION #endif @@ -643,6 +811,7 @@ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* #include /* For strncpy */ #include /* For fopen */ #include /* For UINT_MAX etc */ +#include /* For FLT_MAX */ #if !defined(CGLTF_MALLOC) || !defined(CGLTF_FREE) || !defined(CGLTF_ATOI) || !defined(CGLTF_ATOF) #include /* For malloc, free, atoi, atof */ @@ -714,6 +883,9 @@ static const uint32_t GlbMagicBinChunk = 0x004E4942; #ifndef CGLTF_ATOF #define CGLTF_ATOF(str) atof(str) #endif +#ifndef CGLTF_VALIDATE_ENABLE_ASSERTS +#define CGLTF_VALIDATE_ENABLE_ASSERTS 0 +#endif static void* cgltf_default_alloc(void* user, cgltf_size size) { @@ -948,7 +1120,6 @@ cgltf_result cgltf_parse_file(const cgltf_options* options, const char* path, cg return cgltf_result_invalid_options; } - void* (*memory_alloc)(void*, cgltf_size) = options->memory.alloc ? options->memory.alloc : &cgltf_default_alloc; void (*memory_free)(void*, void*) = options->memory.free ? options->memory.free : &cgltf_default_free; cgltf_result (*file_read)(const struct cgltf_memory_options*, const struct cgltf_file_options*, const char*, cgltf_size*, void**) = options->file.read ? options->file.read : &cgltf_default_file_read; @@ -1006,16 +1177,17 @@ static cgltf_result cgltf_load_buffer_file(const cgltf_options* options, cgltf_s cgltf_combine_paths(path, gltf_path, uri); + // after combining, the tail of the resulting path is a uri; decode_uri converts it into path + cgltf_decode_uri(path + strlen(path) - strlen(uri)); + void* file_data = NULL; cgltf_result result = file_read(&options->memory, &options->file, path, &size, &file_data); - if (result != cgltf_result_success) - { - return result; - } - *out_data = file_data; + memory_free(options->memory.user_data, path); - return cgltf_result_success; + *out_data = (result == cgltf_result_success) ? file_data : NULL; + + return result; } cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size size, const char* base64, void** out_data) @@ -1065,6 +1237,45 @@ cgltf_result cgltf_load_buffer_base64(const cgltf_options* options, cgltf_size s return cgltf_result_success; } +static int cgltf_unhex(char ch) +{ + return + (unsigned)(ch - '0') < 10 ? (ch - '0') : + (unsigned)(ch - 'A') < 6 ? (ch - 'A') + 10 : + (unsigned)(ch - 'a') < 6 ? (ch - 'a') + 10 : + -1; +} + +void cgltf_decode_uri(char* uri) +{ + char* write = uri; + char* i = uri; + + while (*i) + { + if (*i == '%') + { + int ch1 = cgltf_unhex(i[1]); + + if (ch1 >= 0) + { + int ch2 = cgltf_unhex(i[2]); + + if (ch2 >= 0) + { + *write++ = (char)(ch1 * 16 + ch2); + i += 3; + continue; + } + } + } + + *write++ = *i++; + } + + *write = 0; +} + cgltf_result cgltf_load_buffers(const cgltf_options* options, cgltf_data* data, const char* gltf_path) { if (options == NULL) @@ -1172,6 +1383,12 @@ static cgltf_size cgltf_calc_index_bound(cgltf_buffer_view* buffer_view, cgltf_s return bound; } +#if CGLTF_VALIDATE_ENABLE_ASSERTS +#define CGLTF_ASSERT_IF(cond, result) assert(!(cond)); if (cond) return result; +#else +#define CGLTF_ASSERT_IF(cond, result) if (cond) return result; +#endif + cgltf_result cgltf_validate(cgltf_data* data) { for (cgltf_size i = 0; i < data->accessors_count; ++i) @@ -1184,10 +1401,7 @@ cgltf_result cgltf_validate(cgltf_data* data) { cgltf_size req_size = accessor->offset + accessor->stride * (accessor->count - 1) + element_size; - if (accessor->buffer_view->size < req_size) - { - return cgltf_result_data_too_short; - } + CGLTF_ASSERT_IF(accessor->buffer_view->size < req_size, cgltf_result_data_too_short); } if (accessor->is_sparse) @@ -1198,27 +1412,18 @@ cgltf_result cgltf_validate(cgltf_data* data) cgltf_size indices_req_size = sparse->indices_byte_offset + indices_component_size * sparse->count; cgltf_size values_req_size = sparse->values_byte_offset + element_size * sparse->count; - if (sparse->indices_buffer_view->size < indices_req_size || - sparse->values_buffer_view->size < values_req_size) - { - return cgltf_result_data_too_short; - } + CGLTF_ASSERT_IF(sparse->indices_buffer_view->size < indices_req_size || + sparse->values_buffer_view->size < values_req_size, cgltf_result_data_too_short); - if (sparse->indices_component_type != cgltf_component_type_r_8u && - sparse->indices_component_type != cgltf_component_type_r_16u && - sparse->indices_component_type != cgltf_component_type_r_32u) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(sparse->indices_component_type != cgltf_component_type_r_8u && + sparse->indices_component_type != cgltf_component_type_r_16u && + sparse->indices_component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); if (sparse->indices_buffer_view->buffer->data) { cgltf_size index_bound = cgltf_calc_index_bound(sparse->indices_buffer_view, sparse->indices_byte_offset, sparse->indices_component_type, sparse->count); - if (index_bound >= accessor->count) - { - return cgltf_result_data_too_short; - } + CGLTF_ASSERT_IF(index_bound >= accessor->count, cgltf_result_data_too_short); } } } @@ -1227,9 +1432,31 @@ cgltf_result cgltf_validate(cgltf_data* data) { cgltf_size req_size = data->buffer_views[i].offset + data->buffer_views[i].size; - if (data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size) + CGLTF_ASSERT_IF(data->buffer_views[i].buffer && data->buffer_views[i].buffer->size < req_size, cgltf_result_data_too_short); + + if (data->buffer_views[i].has_meshopt_compression) { - return cgltf_result_data_too_short; + cgltf_meshopt_compression* mc = &data->buffer_views[i].meshopt_compression; + + CGLTF_ASSERT_IF(mc->buffer == NULL || mc->buffer->size < mc->offset + mc->size, cgltf_result_data_too_short); + + CGLTF_ASSERT_IF(data->buffer_views[i].stride && mc->stride != data->buffer_views[i].stride, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(data->buffer_views[i].size != mc->stride * mc->count, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_invalid, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_attributes && !(mc->stride % 4 == 0 && mc->stride <= 256), cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->mode == cgltf_meshopt_compression_mode_triangles && mc->count % 3 != 0, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->stride != 2 && mc->stride != 4, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF((mc->mode == cgltf_meshopt_compression_mode_triangles || mc->mode == cgltf_meshopt_compression_mode_indices) && mc->filter != cgltf_meshopt_compression_filter_none, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_octahedral && mc->stride != 4 && mc->stride != 8, cgltf_result_invalid_gltf); + + CGLTF_ASSERT_IF(mc->filter == cgltf_meshopt_compression_filter_quaternion && mc->stride != 8, cgltf_result_invalid_gltf); } } @@ -1237,26 +1464,17 @@ cgltf_result cgltf_validate(cgltf_data* data) { if (data->meshes[i].weights) { - if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].weights_count, cgltf_result_invalid_gltf); } if (data->meshes[i].target_names) { - if (data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(data->meshes[i].primitives_count && data->meshes[i].primitives[0].targets_count != data->meshes[i].target_names_count, cgltf_result_invalid_gltf); } for (cgltf_size j = 0; j < data->meshes[i].primitives_count; ++j) { - if (data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets_count != data->meshes[i].primitives[0].targets_count, cgltf_result_invalid_gltf); if (data->meshes[i].primitives[j].attributes_count) { @@ -1264,41 +1482,34 @@ cgltf_result cgltf_validate(cgltf_data* data) for (cgltf_size k = 0; k < data->meshes[i].primitives[j].attributes_count; ++k) { - if (data->meshes[i].primitives[j].attributes[k].data->count != first->count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].attributes[k].data->count != first->count, cgltf_result_invalid_gltf); } for (cgltf_size k = 0; k < data->meshes[i].primitives[j].targets_count; ++k) { for (cgltf_size m = 0; m < data->meshes[i].primitives[j].targets[k].attributes_count; ++m) { - if (data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].targets[k].attributes[m].data->count != first->count, cgltf_result_invalid_gltf); } } cgltf_accessor* indices = data->meshes[i].primitives[j].indices; - if (indices && + CGLTF_ASSERT_IF(indices && indices->component_type != cgltf_component_type_r_8u && indices->component_type != cgltf_component_type_r_16u && - indices->component_type != cgltf_component_type_r_32u) - { - return cgltf_result_invalid_gltf; - } + indices->component_type != cgltf_component_type_r_32u, cgltf_result_invalid_gltf); if (indices && indices->buffer_view && indices->buffer_view->buffer->data) { cgltf_size index_bound = cgltf_calc_index_bound(indices->buffer_view, indices->offset, indices->component_type, indices->count); - if (index_bound >= first->count) - { - return cgltf_result_data_too_short; - } + CGLTF_ASSERT_IF(index_bound >= first->count, cgltf_result_data_too_short); + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) + { + CGLTF_ASSERT_IF(data->meshes[i].primitives[j].mappings[k].variant >= data->variants_count, cgltf_result_invalid_gltf); } } } @@ -1308,10 +1519,7 @@ cgltf_result cgltf_validate(cgltf_data* data) { if (data->nodes[i].weights && data->nodes[i].mesh) { - if (data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF (data->nodes[i].mesh->primitives_count && data->nodes[i].mesh->primitives[0].targets_count != data->nodes[i].weights_count, cgltf_result_invalid_gltf); } } @@ -1322,10 +1530,7 @@ cgltf_result cgltf_validate(cgltf_data* data) while (p1 && p2) { - if (p1 == p2) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(p1 == p2, cgltf_result_invalid_gltf); p1 = p1->parent; p2 = p2->parent ? p2->parent->parent : NULL; @@ -1336,10 +1541,7 @@ cgltf_result cgltf_validate(cgltf_data* data) { for (cgltf_size j = 0; j < data->scenes[i].nodes_count; ++j) { - if (data->scenes[i].nodes[j]->parent) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(data->scenes[i].nodes[j]->parent, cgltf_result_invalid_gltf); } } @@ -1358,20 +1560,14 @@ cgltf_result cgltf_validate(cgltf_data* data) if (channel->target_path == cgltf_animation_path_type_weights) { - if (!channel->target_node->mesh || !channel->target_node->mesh->primitives_count) - { - return cgltf_result_invalid_gltf; - } + CGLTF_ASSERT_IF(!channel->target_node->mesh || !channel->target_node->mesh->primitives_count, cgltf_result_invalid_gltf); components = channel->target_node->mesh->primitives[0].targets_count; } cgltf_size values = channel->sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1; - if (channel->sampler->input->count * components * values != channel->sampler->output->count) - { - return cgltf_result_data_too_short; - } + CGLTF_ASSERT_IF(channel->sampler->input->count * components * values != channel->sampler->output->count, cgltf_result_data_too_short); } } @@ -1406,6 +1602,16 @@ cgltf_result cgltf_copy_extras_json(const cgltf_data* data, const cgltf_extras* return cgltf_result_success; } +void cgltf_free_extensions(cgltf_data* data, cgltf_extension* extensions, cgltf_size extensions_count) +{ + for (cgltf_size i = 0; i < extensions_count; ++i) + { + data->memory.free(data->memory.user_data, extensions[i].name); + data->memory.free(data->memory.user_data, extensions[i].data); + } + data->memory.free(data->memory.user_data, extensions); +} + void cgltf_free(cgltf_data* data) { if (!data) @@ -1420,17 +1626,42 @@ void cgltf_free(cgltf_data* data) data->memory.free(data->memory.user_data, data->asset.version); data->memory.free(data->memory.user_data, data->asset.min_version); + cgltf_free_extensions(data, data->asset.extensions, data->asset.extensions_count); + + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + data->memory.free(data->memory.user_data, data->accessors[i].name); + + if(data->accessors[i].is_sparse) + { + cgltf_free_extensions(data, data->accessors[i].sparse.extensions, data->accessors[i].sparse.extensions_count); + cgltf_free_extensions(data, data->accessors[i].sparse.indices_extensions, data->accessors[i].sparse.indices_extensions_count); + cgltf_free_extensions(data, data->accessors[i].sparse.values_extensions, data->accessors[i].sparse.values_extensions_count); + } + cgltf_free_extensions(data, data->accessors[i].extensions, data->accessors[i].extensions_count); + } data->memory.free(data->memory.user_data, data->accessors); + + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + data->memory.free(data->memory.user_data, data->buffer_views[i].name); + data->memory.free(data->memory.user_data, data->buffer_views[i].data); + + cgltf_free_extensions(data, data->buffer_views[i].extensions, data->buffer_views[i].extensions_count); + } data->memory.free(data->memory.user_data, data->buffer_views); for (cgltf_size i = 0; i < data->buffers_count; ++i) { + data->memory.free(data->memory.user_data, data->buffers[i].name); + if (data->buffers[i].data != data->bin) { file_release(&data->memory, &data->file, data->buffers[i].data); } - data->memory.free(data->memory.user_data, data->buffers[i].uri); + + cgltf_free_extensions(data, data->buffers[i].extensions, data->buffers[i].extensions_count); } data->memory.free(data->memory.user_data, data->buffers); @@ -1459,6 +1690,20 @@ void cgltf_free(cgltf_data* data) } data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].targets); + + if (data->meshes[i].primitives[j].has_draco_mesh_compression) + { + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++k) + { + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes[k].name); + } + + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].draco_mesh_compression.attributes); + } + + data->memory.free(data->memory.user_data, data->meshes[i].primitives[j].mappings); + + cgltf_free_extensions(data, data->meshes[i].primitives[j].extensions, data->meshes[i].primitives[j].extensions_count); } data->memory.free(data->memory.user_data, data->meshes[i].primitives); @@ -1469,6 +1714,8 @@ void cgltf_free(cgltf_data* data) data->memory.free(data->memory.user_data, data->meshes[i].target_names[j]); } + cgltf_free_extensions(data, data->meshes[i].extensions, data->meshes[i].extensions_count); + data->memory.free(data->memory.user_data, data->meshes[i].target_names); } @@ -1477,6 +1724,47 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->materials_count; ++i) { data->memory.free(data->memory.user_data, data->materials[i].name); + + if(data->materials[i].has_pbr_metallic_roughness) + { + cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions, data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions, data->materials[i].pbr_metallic_roughness.base_color_texture.extensions_count); + } + if(data->materials[i].has_pbr_specular_glossiness) + { + cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions, data->materials[i].pbr_specular_glossiness.diffuse_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions, data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.extensions_count); + } + if(data->materials[i].has_clearcoat) + { + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_texture.extensions, data->materials[i].clearcoat.clearcoat_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions, data->materials[i].clearcoat.clearcoat_roughness_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].clearcoat.clearcoat_normal_texture.extensions, data->materials[i].clearcoat.clearcoat_normal_texture.extensions_count); + } + if(data->materials[i].has_specular) + { + cgltf_free_extensions(data, data->materials[i].specular.specular_texture.extensions, data->materials[i].specular.specular_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].specular.specular_color_texture.extensions, data->materials[i].specular.specular_color_texture.extensions_count); + } + if(data->materials[i].has_transmission) + { + cgltf_free_extensions(data, data->materials[i].transmission.transmission_texture.extensions, data->materials[i].transmission.transmission_texture.extensions_count); + } + if (data->materials[i].has_volume) + { + cgltf_free_extensions(data, data->materials[i].volume.thickness_texture.extensions, data->materials[i].volume.thickness_texture.extensions_count); + } + if(data->materials[i].has_sheen) + { + cgltf_free_extensions(data, data->materials[i].sheen.sheen_color_texture.extensions, data->materials[i].sheen.sheen_color_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].sheen.sheen_roughness_texture.extensions, data->materials[i].sheen.sheen_roughness_texture.extensions_count); + } + + cgltf_free_extensions(data, data->materials[i].normal_texture.extensions, data->materials[i].normal_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].occlusion_texture.extensions, data->materials[i].occlusion_texture.extensions_count); + cgltf_free_extensions(data, data->materials[i].emissive_texture.extensions, data->materials[i].emissive_texture.extensions_count); + + cgltf_free_extensions(data, data->materials[i].extensions, data->materials[i].extensions_count); } data->memory.free(data->memory.user_data, data->materials); @@ -1486,6 +1774,8 @@ void cgltf_free(cgltf_data* data) data->memory.free(data->memory.user_data, data->images[i].name); data->memory.free(data->memory.user_data, data->images[i].uri); data->memory.free(data->memory.user_data, data->images[i].mime_type); + + cgltf_free_extensions(data, data->images[i].extensions, data->images[i].extensions_count); } data->memory.free(data->memory.user_data, data->images); @@ -1493,16 +1783,25 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->textures_count; ++i) { data->memory.free(data->memory.user_data, data->textures[i].name); + cgltf_free_extensions(data, data->textures[i].extensions, data->textures[i].extensions_count); } data->memory.free(data->memory.user_data, data->textures); + for (cgltf_size i = 0; i < data->samplers_count; ++i) + { + data->memory.free(data->memory.user_data, data->samplers[i].name); + cgltf_free_extensions(data, data->samplers[i].extensions, data->samplers[i].extensions_count); + } + data->memory.free(data->memory.user_data, data->samplers); for (cgltf_size i = 0; i < data->skins_count; ++i) { data->memory.free(data->memory.user_data, data->skins[i].name); data->memory.free(data->memory.user_data, data->skins[i].joints); + + cgltf_free_extensions(data, data->skins[i].extensions, data->skins[i].extensions_count); } data->memory.free(data->memory.user_data, data->skins); @@ -1510,6 +1809,7 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->cameras_count; ++i) { data->memory.free(data->memory.user_data, data->cameras[i].name); + cgltf_free_extensions(data, data->cameras[i].extensions, data->cameras[i].extensions_count); } data->memory.free(data->memory.user_data, data->cameras); @@ -1526,6 +1826,7 @@ void cgltf_free(cgltf_data* data) data->memory.free(data->memory.user_data, data->nodes[i].name); data->memory.free(data->memory.user_data, data->nodes[i].children); data->memory.free(data->memory.user_data, data->nodes[i].weights); + cgltf_free_extensions(data, data->nodes[i].extensions, data->nodes[i].extensions_count); } data->memory.free(data->memory.user_data, data->nodes); @@ -1534,6 +1835,8 @@ void cgltf_free(cgltf_data* data) { data->memory.free(data->memory.user_data, data->scenes[i].name); data->memory.free(data->memory.user_data, data->scenes[i].nodes); + + cgltf_free_extensions(data, data->scenes[i].extensions, data->scenes[i].extensions_count); } data->memory.free(data->memory.user_data, data->scenes); @@ -1541,12 +1844,32 @@ void cgltf_free(cgltf_data* data) for (cgltf_size i = 0; i < data->animations_count; ++i) { data->memory.free(data->memory.user_data, data->animations[i].name); + for (cgltf_size j = 0; j < data->animations[i].samplers_count; ++j) + { + cgltf_free_extensions(data, data->animations[i].samplers[j].extensions, data->animations[i].samplers[j].extensions_count); + } data->memory.free(data->memory.user_data, data->animations[i].samplers); + + for (cgltf_size j = 0; j < data->animations[i].channels_count; ++j) + { + cgltf_free_extensions(data, data->animations[i].channels[j].extensions, data->animations[i].channels[j].extensions_count); + } data->memory.free(data->memory.user_data, data->animations[i].channels); + + cgltf_free_extensions(data, data->animations[i].extensions, data->animations[i].extensions_count); } data->memory.free(data->memory.user_data, data->animations); + for (cgltf_size i = 0; i < data->variants_count; ++i) + { + data->memory.free(data->memory.user_data, data->variants[i].name); + } + + data->memory.free(data->memory.user_data, data->variants); + + cgltf_free_extensions(data, data->data_extensions, data->data_extensions_count); + for (cgltf_size i = 0; i < data->extensions_used_count; ++i) { data->memory.free(data->memory.user_data, data->extensions_used[i]); @@ -1753,6 +2076,19 @@ static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type ty return 1; } +const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view) +{ + if (view->data) + return (const uint8_t*)view->data; + + if (!view->buffer->data) + return NULL; + + const uint8_t* result = (const uint8_t*)view->buffer->data; + result += view->offset; + return result; +} + cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size index, cgltf_float* out, cgltf_size element_size) { if (accessor->is_sparse) @@ -1764,13 +2100,12 @@ cgltf_bool cgltf_accessor_read_float(const cgltf_accessor* accessor, cgltf_size memset(out, 0, element_size * sizeof(cgltf_float)); return 1; } - if (accessor->buffer_view->buffer->data == NULL) + const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); + if (element == NULL) { return 0; } - cgltf_size offset = accessor->offset + accessor->buffer_view->offset; - const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; - element += offset + accessor->stride * index; + element += accessor->offset + accessor->stride * index; return cgltf_element_read_float(element, accessor->type, accessor->component_type, accessor->normalized, out, element_size); } @@ -1803,16 +2138,18 @@ cgltf_size cgltf_accessor_unpack_floats(const cgltf_accessor* accessor, cgltf_fl { const cgltf_accessor_sparse* sparse = &dense.sparse; - if (sparse->indices_buffer_view->buffer->data == NULL || sparse->values_buffer_view->buffer->data == NULL) + const uint8_t* index_data = cgltf_buffer_view_data(sparse->indices_buffer_view); + const uint8_t* reader_head = cgltf_buffer_view_data(sparse->values_buffer_view); + + if (index_data == NULL || reader_head == NULL) { return 0; } - const uint8_t* index_data = (const uint8_t*) sparse->indices_buffer_view->buffer->data; - index_data += sparse->indices_byte_offset + sparse->indices_buffer_view->offset; + index_data += sparse->indices_byte_offset; + reader_head += sparse->values_byte_offset; + cgltf_size index_stride = cgltf_component_size(sparse->indices_component_type); - const uint8_t* reader_head = (const uint8_t*) sparse->values_buffer_view->buffer->data; - reader_head += sparse->values_byte_offset + sparse->values_buffer_view->offset; for (cgltf_size reader_index = 0; reader_index < sparse->count; reader_index++, index_data += index_stride) { size_t writer_index = cgltf_component_read_index(index_data, sparse->indices_component_type); @@ -1852,53 +2189,50 @@ static cgltf_uint cgltf_component_read_uint(const void* in, cgltf_component_type default: return 0; } - - return 0; } static cgltf_bool cgltf_element_read_uint(const uint8_t* element, cgltf_type type, cgltf_component_type component_type, cgltf_uint* out, cgltf_size element_size) { - cgltf_size num_components = cgltf_num_components(type); + cgltf_size num_components = cgltf_num_components(type); - if (element_size < num_components) - { - return 0; - } + if (element_size < num_components) + { + return 0; + } // Reading integer matrices is not a valid use case - if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4) - { - return 0; - } + if (type == cgltf_type_mat2 || type == cgltf_type_mat3 || type == cgltf_type_mat4) + { + return 0; + } - cgltf_size component_size = cgltf_component_size(component_type); + cgltf_size component_size = cgltf_component_size(component_type); - for (cgltf_size i = 0; i < num_components; ++i) - { - out[i] = cgltf_component_read_uint(element + component_size * i, component_type); - } - return 1; + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_uint(element + component_size * i, component_type); + } + return 1; } cgltf_bool cgltf_accessor_read_uint(const cgltf_accessor* accessor, cgltf_size index, cgltf_uint* out, cgltf_size element_size) { - if (accessor->is_sparse) - { - return 0; - } - if (accessor->buffer_view == NULL) - { - memset(out, 0, element_size * sizeof( cgltf_uint )); - return 1; - } - if (accessor->buffer_view->buffer->data == NULL) - { - return 0; - } - cgltf_size offset = accessor->offset + accessor->buffer_view->offset; - const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; - element += offset + accessor->stride * index; - return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); + if (accessor->is_sparse) + { + return 0; + } + if (accessor->buffer_view == NULL) + { + memset(out, 0, element_size * sizeof( cgltf_uint )); + return 1; + } + const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); + if (element == NULL) + { + return 0; + } + element += accessor->offset + accessor->stride * index; + return cgltf_element_read_uint(element, accessor->type, accessor->component_type, out, element_size); } cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size index) @@ -1911,14 +2245,12 @@ cgltf_size cgltf_accessor_read_index(const cgltf_accessor* accessor, cgltf_size { return 0; } - if (accessor->buffer_view->buffer->data == NULL) + const uint8_t* element = cgltf_buffer_view_data(accessor->buffer_view); + if (element == NULL) { return 0; // This is an error case, but we can't communicate the error with existing interface. } - - cgltf_size offset = accessor->offset + accessor->buffer_view->offset; - const uint8_t* element = (const uint8_t*) accessor->buffer_view->buffer->data; - element += offset + accessor->stride * index; + element += accessor->offset + accessor->stride * index; return cgltf_component_read_index(element, accessor->component_type); } @@ -2065,22 +2397,22 @@ static int cgltf_parse_json_array(cgltf_options* options, jsmntok_t const* token static int cgltf_parse_json_string_array(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, char*** out_array, cgltf_size* out_size) { - CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); - i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); - if (i < 0) - { - return i; - } + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(char*), (void**)out_array, out_size); + if (i < 0) + { + return i; + } - for (cgltf_size j = 0; j < *out_size; ++j) - { - i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); - if (i < 0) - { - return i; - } - } - return i; + for (cgltf_size j = 0; j < *out_size; ++j) + { + i = cgltf_parse_json_string(options, tokens, i, json_chunk, j + (*out_array)); + if (i < 0) + { + return i; + } + } + return i; } static void cgltf_parse_attribute_type(const char* name, cgltf_attribute_type* out_type, int* out_index) @@ -2173,6 +2505,233 @@ static int cgltf_parse_json_extras(jsmntok_t const* tokens, int i, const uint8_t return i; } +static int cgltf_parse_json_unprocessed_extension(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_extension* out_extension) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_STRING); + CGLTF_CHECK_TOKTYPE(tokens[i+1], JSMN_OBJECT); + if (out_extension->name) + { + return CGLTF_ERROR_JSON; + } + + cgltf_size name_length = tokens[i].end - tokens[i].start; + out_extension->name = (char*)options->memory.alloc(options->memory.user_data, name_length + 1); + if (!out_extension->name) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(out_extension->name, (const char*)json_chunk + tokens[i].start, name_length); + out_extension->name[name_length] = 0; + i++; + + size_t start = tokens[i].start; + size_t size = tokens[i].end - start; + out_extension->data = (char*)options->memory.alloc(options->memory.user_data, size + 1); + if (!out_extension->data) + { + return CGLTF_ERROR_NOMEM; + } + strncpy(out_extension->data, (const char*)json_chunk + start, size); + out_extension->data[size] = '\0'; + + i = cgltf_skip_json(tokens, i); + + return i; +} + +static int cgltf_parse_json_unprocessed_extensions(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_size* out_extensions_count, cgltf_extension** out_extensions) +{ + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(*out_extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + *out_extensions_count = 0; + *out_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!*out_extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + + for (int j = 0; j < extensions_size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + cgltf_size extension_index = (*out_extensions_count)++; + cgltf_extension* extension = &((*out_extensions)[extension_index]); + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, extension); + + if (i < 0) + { + return i; + } + } + return i; +} + +static int cgltf_parse_json_draco_mesh_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_draco_mesh_compression* out_draco_mesh_compression) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "attributes") == 0) + { + i = cgltf_parse_json_attribute_list(options, tokens, i + 1, json_chunk, &out_draco_mesh_compression->attributes, &out_draco_mesh_compression->attributes_count); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "bufferView") == 0) + { + ++i; + out_draco_mesh_compression->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_material_mapping_data(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_mapping* out_mappings, cgltf_size* offset) +{ + (void)options; + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_ARRAY); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int obj_size = tokens[i].size; + ++i; + + int material = -1; + int variants_tok = -1; + cgltf_extras extras = {0, 0}; + + for (int k = 0; k < obj_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "material") == 0) + { + ++i; + material = cgltf_json_to_int(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) + { + variants_tok = i+1; + CGLTF_CHECK_TOKTYPE(tokens[variants_tok], JSMN_ARRAY); + + i = cgltf_skip_json(tokens, i+1); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + if (material < 0 || variants_tok < 0) + { + return CGLTF_ERROR_JSON; + } + + if (out_mappings) + { + for (int k = 0; k < tokens[variants_tok].size; ++k) + { + int variant = cgltf_json_to_int(&tokens[variants_tok + 1 + k], json_chunk); + if (variant < 0) + return variant; + + out_mappings[*offset].material = CGLTF_PTRINDEX(cgltf_material, material); + out_mappings[*offset].variant = variant; + out_mappings[*offset].extras = extras; + + (*offset)++; + } + } + else + { + (*offset) += tokens[variants_tok].size; + } + } + + return i; +} + +static int cgltf_parse_json_material_mappings(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "mappings") == 0) + { + if (out_prim->mappings) + { + return CGLTF_ERROR_JSON; + } + + cgltf_size mappings_offset = 0; + int k = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, NULL, &mappings_offset); + if (k < 0) + { + return k; + } + + out_prim->mappings_count = mappings_offset; + out_prim->mappings = (cgltf_material_mapping*)cgltf_calloc(options, sizeof(cgltf_material_mapping), out_prim->mappings_count); + + mappings_offset = 0; + i = cgltf_parse_json_material_mapping_data(options, tokens, i + 1, json_chunk, out_prim->mappings, &mappings_offset); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_primitive* out_prim) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -2231,6 +2790,50 @@ static int cgltf_parse_json_primitive(cgltf_options* options, jsmntok_t const* t { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_prim->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_prim->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_prim->extensions_count = 0; + out_prim->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_prim->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_draco_mesh_compression") == 0) + { + out_prim->has_draco_mesh_compression = 1; + i = cgltf_parse_json_draco_mesh_compression(options, tokens, i + 1, json_chunk, &out_prim->draco_mesh_compression); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) + { + i = cgltf_parse_json_material_mappings(options, tokens, i + 1, json_chunk, out_prim); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_prim->extensions[out_prim->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } else { i = cgltf_skip_json(tokens, i+1); @@ -2303,7 +2906,7 @@ static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens { CGLTF_CHECK_KEY(tokens[i]); - if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0) + if (cgltf_json_strcmp(tokens+i, json_chunk, "targetNames") == 0 && tokens[i+1].type == JSMN_ARRAY) { i = cgltf_parse_json_string_array(options, tokens, i + 1, json_chunk, &out_mesh->target_names, &out_mesh->target_names_count); } @@ -2323,6 +2926,10 @@ static int cgltf_parse_json_mesh(cgltf_options* options, jsmntok_t const* tokens i = cgltf_skip_json(tokens, i); } } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_mesh->extensions_count, &out_mesh->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -2379,7 +2986,7 @@ static cgltf_component_type cgltf_json_to_component_type(jsmntok_t const* tok, c } } -static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) +static int cgltf_parse_json_accessor_sparse(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor_sparse* out_sparse) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -2430,6 +3037,10 @@ static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, cons { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->indices_extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->indices_extensions_count, &out_sparse->indices_extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -2469,6 +3080,10 @@ static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, cons { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->values_extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->values_extensions_count, &out_sparse->values_extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -2484,6 +3099,10 @@ static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, cons { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sparse->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sparse->extensions_count, &out_sparse->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -2498,7 +3117,7 @@ static int cgltf_parse_json_accessor_sparse(jsmntok_t const* tokens, int i, cons return i; } -static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) +static int cgltf_parse_json_accessor(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_accessor* out_accessor) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -2509,7 +3128,11 @@ static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, const uint8 { CGLTF_CHECK_KEY(tokens[i]); - if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_accessor->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "bufferView") == 0) { ++i; out_accessor->buffer_view = CGLTF_PTRINDEX(cgltf_buffer_view, cgltf_json_to_int(tokens + i, json_chunk)); @@ -2593,12 +3216,16 @@ static int cgltf_parse_json_accessor(jsmntok_t const* tokens, int i, const uint8 else if (cgltf_json_strcmp(tokens + i, json_chunk, "sparse") == 0) { out_accessor->is_sparse = 1; - i = cgltf_parse_json_accessor_sparse(tokens, i + 1, json_chunk, &out_accessor->sparse); + i = cgltf_parse_json_accessor_sparse(options, tokens, i + 1, json_chunk, &out_accessor->sparse); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_accessor->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_accessor->extensions_count, &out_accessor->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -2658,7 +3285,7 @@ static int cgltf_parse_json_texture_transform(jsmntok_t const* tokens, int i, co return i; } -static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) +static int cgltf_parse_json_texture_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture_view* out_texture_view) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -2705,8 +3332,20 @@ static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const u ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_texture_view->extensions) + { + return CGLTF_ERROR_JSON; + } int extensions_size = tokens[i].size; + out_texture_view->extensions_count = 0; + out_texture_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_texture_view->extensions) + { + return CGLTF_ERROR_NOMEM; + } + ++i; for (int k = 0; k < extensions_size; ++k) @@ -2720,7 +3359,7 @@ static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const u } else { - i = cgltf_skip_json(tokens, i+1); + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_texture_view->extensions[out_texture_view->extensions_count++])); } if (i < 0) @@ -2743,7 +3382,7 @@ static int cgltf_parse_json_texture_view(jsmntok_t const* tokens, int i, const u return i; } -static int cgltf_parse_json_pbr_metallic_roughness(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) +static int cgltf_parse_json_pbr_metallic_roughness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_metallic_roughness* out_pbr) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -2774,12 +3413,12 @@ static int cgltf_parse_json_pbr_metallic_roughness(jsmntok_t const* tokens, int } else if (cgltf_json_strcmp(tokens+i, json_chunk, "baseColorTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->base_color_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "metallicRoughnessTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->metallic_roughness_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) @@ -2800,7 +3439,7 @@ static int cgltf_parse_json_pbr_metallic_roughness(jsmntok_t const* tokens, int return i; } -static int cgltf_parse_json_pbr_specular_glossiness(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) +static int cgltf_parse_json_pbr_specular_glossiness(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_pbr_specular_glossiness* out_pbr) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; @@ -2826,11 +3465,258 @@ static int cgltf_parse_json_pbr_specular_glossiness(jsmntok_t const* tokens, int } else if (cgltf_json_strcmp(tokens+i, json_chunk, "diffuseTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->diffuse_texture); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularGlossinessTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_pbr->specular_glossiness_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_clearcoat(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_clearcoat* out_clearcoat) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatFactor") == 0) + { + ++i; + out_clearcoat->clearcoat_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessFactor") == 0) + { + ++i; + out_clearcoat->clearcoat_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_roughness_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "clearcoatNormalTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_clearcoat->clearcoat_normal_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_ior(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_ior* out_ior) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + // Default values + out_ior->ior = 1.5f; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "ior") == 0) + { + ++i; + out_ior->ior = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_specular(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_specular* out_specular) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + // Default values + out_specular->specular_factor = 1.0f; + cgltf_fill_float_array(out_specular->specular_color_factor, 3, 1.0f); + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "specularFactor") == 0) + { + ++i; + out_specular->specular_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_specular->specular_color_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "specularTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "specularColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_specular->specular_color_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_transmission(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_transmission* out_transmission) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionFactor") == 0) + { + ++i; + out_transmission->transmission_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "transmissionTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_transmission->transmission_texture); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_volume(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_volume* out_volume) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessFactor") == 0) + { + ++i; + out_volume->thickness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "thicknessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_volume->thickness_texture); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationColor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_volume->attenuation_color, 3); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "attenuationDistance") == 0) + { + ++i; + out_volume->attenuation_distance = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_sheen(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_sheen* out_sheen) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorFactor") == 0) + { + i = cgltf_parse_json_float_array(tokens, i + 1, json_chunk, out_sheen->sheen_color_factor, 3); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenColorTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_color_texture); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessFactor") == 0) + { + ++i; + out_sheen->sheen_roughness_factor = cgltf_json_to_float(tokens + i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "sheenRoughnessTexture") == 0) + { + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_sheen->sheen_roughness_texture); } else { @@ -2879,6 +3765,10 @@ static int cgltf_parse_json_image(cgltf_options* options, jsmntok_t const* token { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_image->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_image->extensions_count, &out_image->extensions); + } else { i = cgltf_skip_json(tokens, i + 1); @@ -2908,7 +3798,11 @@ static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tok { CGLTF_CHECK_KEY(tokens[i]); - if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_sampler->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "magFilter") == 0) { ++i; out_sampler->mag_filter @@ -2940,6 +3834,10 @@ static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tok { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); + } else { i = cgltf_skip_json(tokens, i + 1); @@ -2954,7 +3852,6 @@ static int cgltf_parse_json_sampler(cgltf_options* options, jsmntok_t const* tok return i; } - static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_texture* out_texture) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -2986,6 +3883,10 @@ static int cgltf_parse_json_texture(cgltf_options* options, jsmntok_t const* tok { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_texture->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_texture->extensions_count, &out_texture->extensions); + } else { i = cgltf_skip_json(tokens, i + 1); @@ -3012,6 +3913,9 @@ static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* to cgltf_fill_float_array(out_material->pbr_specular_glossiness.specular_factor, 3, 1.0f); out_material->pbr_specular_glossiness.glossiness_factor = 1.0f; + cgltf_fill_float_array(out_material->volume.attenuation_color, 3, 1.0f); + out_material->volume.attenuation_distance = FLT_MAX; + out_material->alpha_cutoff = 0.5f; int size = tokens[i].size; @@ -3028,7 +3932,7 @@ static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* to else if (cgltf_json_strcmp(tokens+i, json_chunk, "pbrMetallicRoughness") == 0) { out_material->has_pbr_metallic_roughness = 1; - i = cgltf_parse_json_pbr_metallic_roughness(tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); + i = cgltf_parse_json_pbr_metallic_roughness(options, tokens, i + 1, json_chunk, &out_material->pbr_metallic_roughness); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "emissiveFactor") == 0) { @@ -3036,17 +3940,17 @@ static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* to } else if (cgltf_json_strcmp(tokens + i, json_chunk, "normalTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_material->normal_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "occlusionTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_material->occlusion_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "emissiveTexture") == 0) { - i = cgltf_parse_json_texture_view(tokens, i + 1, json_chunk, + i = cgltf_parse_json_texture_view(options, tokens, i + 1, json_chunk, &out_material->emissive_texture); } else if (cgltf_json_strcmp(tokens + i, json_chunk, "alphaMode") == 0) @@ -3088,9 +3992,20 @@ static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* to ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_material->extensions) + { + return CGLTF_ERROR_JSON; + } int extensions_size = tokens[i].size; ++i; + out_material->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + out_material->extensions_count= 0; + + if (!out_material->extensions) + { + return CGLTF_ERROR_NOMEM; + } for (int k = 0; k < extensions_size; ++k) { @@ -3099,16 +4014,46 @@ static int cgltf_parse_json_material(cgltf_options* options, jsmntok_t const* to if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_pbrSpecularGlossiness") == 0) { out_material->has_pbr_specular_glossiness = 1; - i = cgltf_parse_json_pbr_specular_glossiness(tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); + i = cgltf_parse_json_pbr_specular_glossiness(options, tokens, i + 1, json_chunk, &out_material->pbr_specular_glossiness); } else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_unlit") == 0) { out_material->unlit = 1; i = cgltf_skip_json(tokens, i+1); } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_clearcoat") == 0) + { + out_material->has_clearcoat = 1; + i = cgltf_parse_json_clearcoat(options, tokens, i + 1, json_chunk, &out_material->clearcoat); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_ior") == 0) + { + out_material->has_ior = 1; + i = cgltf_parse_json_ior(tokens, i + 1, json_chunk, &out_material->ior); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_specular") == 0) + { + out_material->has_specular = 1; + i = cgltf_parse_json_specular(options, tokens, i + 1, json_chunk, &out_material->specular); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_transmission") == 0) + { + out_material->has_transmission = 1; + i = cgltf_parse_json_transmission(options, tokens, i + 1, json_chunk, &out_material->transmission); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "KHR_materials_volume") == 0) + { + out_material->has_volume = 1; + i = cgltf_parse_json_volume(options, tokens, i + 1, json_chunk, &out_material->volume); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_sheen") == 0) + { + out_material->has_sheen = 1; + i = cgltf_parse_json_sheen(options, tokens, i + 1, json_chunk, &out_material->sheen); + } else { - i = cgltf_skip_json(tokens, i+1); + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_material->extensions[out_material->extensions_count++])); } if (i < 0) @@ -3141,7 +4086,7 @@ static int cgltf_parse_json_accessors(cgltf_options* options, jsmntok_t const* t for (cgltf_size j = 0; j < out_data->accessors_count; ++j) { - i = cgltf_parse_json_accessor(tokens, i, json_chunk, &out_data->accessors[j]); + i = cgltf_parse_json_accessor(options, tokens, i, json_chunk, &out_data->accessors[j]); if (i < 0) { return i; @@ -3226,8 +4171,9 @@ static int cgltf_parse_json_samplers(cgltf_options* options, jsmntok_t const* to return i; } -static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view) +static int cgltf_parse_json_meshopt_compression(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_meshopt_compression* out_meshopt_compression) { + (void)options; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); int size = tokens[i].size; @@ -3238,6 +4184,103 @@ static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, const ui CGLTF_CHECK_KEY(tokens[i]); if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) + { + ++i; + out_meshopt_compression->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteOffset") == 0) + { + ++i; + out_meshopt_compression->offset = cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + { + ++i; + out_meshopt_compression->size = cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteStride") == 0) + { + ++i; + out_meshopt_compression->stride = cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "count") == 0) + { + ++i; + out_meshopt_compression->count = cgltf_json_to_int(tokens+i, json_chunk); + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "mode") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "ATTRIBUTES") == 0) + { + out_meshopt_compression->mode = cgltf_meshopt_compression_mode_attributes; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "TRIANGLES") == 0) + { + out_meshopt_compression->mode = cgltf_meshopt_compression_mode_triangles; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "INDICES") == 0) + { + out_meshopt_compression->mode = cgltf_meshopt_compression_mode_indices; + } + ++i; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "filter") == 0) + { + ++i; + if (cgltf_json_strcmp(tokens+i, json_chunk, "NONE") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_none; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "OCTAHEDRAL") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_octahedral; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "QUATERNION") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_quaternion; + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "EXPONENTIAL") == 0) + { + out_meshopt_compression->filter = cgltf_meshopt_compression_filter_exponential; + } + ++i; + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_buffer_view(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_buffer_view* out_buffer_view) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer_view->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "buffer") == 0) { ++i; out_buffer_view->buffer = CGLTF_PTRINDEX(cgltf_buffer, cgltf_json_to_int(tokens + i, json_chunk)); @@ -3287,6 +4330,46 @@ static int cgltf_parse_json_buffer_view(jsmntok_t const* tokens, int i, const ui { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer_view->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_buffer_view->extensions) + { + return CGLTF_ERROR_JSON; + } + + int extensions_size = tokens[i].size; + out_buffer_view->extensions_count = 0; + out_buffer_view->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_buffer_view->extensions) + { + return CGLTF_ERROR_NOMEM; + } + + ++i; + for (int k = 0; k < extensions_size; ++k) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "EXT_meshopt_compression") == 0) + { + out_buffer_view->has_meshopt_compression = 1; + i = cgltf_parse_json_meshopt_compression(options, tokens, i + 1, json_chunk, &out_buffer_view->meshopt_compression); + } + else + { + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_buffer_view->extensions[out_buffer_view->extensions_count++])); + } + + if (i < 0) + { + return i; + } + } + } else { i = cgltf_skip_json(tokens, i+1); @@ -3311,7 +4394,7 @@ static int cgltf_parse_json_buffer_views(cgltf_options* options, jsmntok_t const for (cgltf_size j = 0; j < out_data->buffer_views_count; ++j) { - i = cgltf_parse_json_buffer_view(tokens, i, json_chunk, &out_data->buffer_views[j]); + i = cgltf_parse_json_buffer_view(options, tokens, i, json_chunk, &out_data->buffer_views[j]); if (i < 0) { return i; @@ -3331,7 +4414,11 @@ static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* toke { CGLTF_CHECK_KEY(tokens[i]); - if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) + if (cgltf_json_strcmp(tokens + i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_buffer->name); + } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "byteLength") == 0) { ++i; out_buffer->size = @@ -3346,6 +4433,10 @@ static int cgltf_parse_json_buffer(cgltf_options* options, jsmntok_t const* toke { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_buffer->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_buffer->extensions_count, &out_buffer->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -3426,6 +4517,10 @@ static int cgltf_parse_json_skin(cgltf_options* options, jsmntok_t const* tokens { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_skin->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_skin->extensions_count, &out_skin->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -3505,6 +4600,7 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke if (cgltf_json_strcmp(tokens+i, json_chunk, "aspectRatio") == 0) { ++i; + out_camera->data.perspective.has_aspect_ratio = 1; out_camera->data.perspective.aspect_ratio = cgltf_json_to_float(tokens + i, json_chunk); ++i; } @@ -3517,6 +4613,7 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke else if (cgltf_json_strcmp(tokens+i, json_chunk, "zfar") == 0) { ++i; + out_camera->data.perspective.has_zfar = 1; out_camera->data.perspective.zfar = cgltf_json_to_float(tokens + i, json_chunk); ++i; } @@ -3599,6 +4696,10 @@ static int cgltf_parse_json_camera(cgltf_options* options, jsmntok_t const* toke { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_camera->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_camera->extensions_count, &out_camera->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -3847,8 +4948,20 @@ static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_node->extensions) + { + return CGLTF_ERROR_JSON; + } int extensions_size = tokens[i].size; + out_node->extensions_count= 0; + out_node->extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_node->extensions) + { + return CGLTF_ERROR_NOMEM; + } + ++i; for (int k = 0; k < extensions_size; ++k) @@ -3888,7 +5001,7 @@ static int cgltf_parse_json_node(cgltf_options* options, jsmntok_t const* tokens } else { - i = cgltf_skip_json(tokens, i+1); + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_node->extensions[out_node->extensions_count++])); } if (i < 0) @@ -3963,6 +5076,10 @@ static int cgltf_parse_json_scene(cgltf_options* options, jsmntok_t const* token { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_scene->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_scene->extensions_count, &out_scene->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -4041,6 +5158,10 @@ static int cgltf_parse_json_animation_sampler(cgltf_options* options, jsmntok_t { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_sampler->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_sampler->extensions_count, &out_sampler->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -4117,6 +5238,10 @@ static int cgltf_parse_json_animation_channel(cgltf_options* options, jsmntok_t { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_channel->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_channel->extensions_count, &out_channel->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -4195,6 +5320,10 @@ static int cgltf_parse_json_animation(cgltf_options* options, jsmntok_t const* t { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_animation->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_animation->extensions_count, &out_animation->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -4228,6 +5357,58 @@ static int cgltf_parse_json_animations(cgltf_options* options, jsmntok_t const* return i; } +static int cgltf_parse_json_variant(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_material_variant* out_variant) +{ + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int size = tokens[i].size; + ++i; + + for (int j = 0; j < size; ++j) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens+i, json_chunk, "name") == 0) + { + i = cgltf_parse_json_string(options, tokens, i + 1, json_chunk, &out_variant->name); + } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extras") == 0) + { + i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_variant->extras); + } + else + { + i = cgltf_skip_json(tokens, i+1); + } + + if (i < 0) + { + return i; + } + } + + return i; +} + +static int cgltf_parse_json_variants(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_data* out_data) +{ + i = cgltf_parse_json_array(options, tokens, i, json_chunk, sizeof(cgltf_material_variant), (void**)&out_data->variants, &out_data->variants_count); + if (i < 0) + { + return i; + } + + for (cgltf_size j = 0; j < out_data->variants_count; ++j) + { + i = cgltf_parse_json_variant(options, tokens, i, json_chunk, &out_data->variants[j]); + if (i < 0) + { + return i; + } + } + return i; +} + static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* tokens, int i, const uint8_t* json_chunk, cgltf_asset* out_asset) { CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); @@ -4259,6 +5440,10 @@ static int cgltf_parse_json_asset(cgltf_options* options, jsmntok_t const* token { i = cgltf_parse_json_extras(tokens, i + 1, json_chunk, &out_asset->extras); } + else if (cgltf_json_strcmp(tokens + i, json_chunk, "extensions") == 0) + { + i = cgltf_parse_json_unprocessed_extensions(options, tokens, i, json_chunk, &out_asset->extensions_count, &out_asset->extensions); + } else { i = cgltf_skip_json(tokens, i+1); @@ -4416,8 +5601,20 @@ static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens ++i; CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + if(out_data->data_extensions) + { + return CGLTF_ERROR_JSON; + } int extensions_size = tokens[i].size; + out_data->data_extensions_count = 0; + out_data->data_extensions = (cgltf_extension*)cgltf_calloc(options, sizeof(cgltf_extension), extensions_size); + + if (!out_data->data_extensions) + { + return CGLTF_ERROR_NOMEM; + } + ++i; for (int k = 0; k < extensions_size; ++k) @@ -4452,9 +5649,37 @@ static int cgltf_parse_json_root(cgltf_options* options, jsmntok_t const* tokens } } } + else if (cgltf_json_strcmp(tokens+i, json_chunk, "KHR_materials_variants") == 0) + { + ++i; + + CGLTF_CHECK_TOKTYPE(tokens[i], JSMN_OBJECT); + + int data_size = tokens[i].size; + ++i; + + for (int m = 0; m < data_size; ++m) + { + CGLTF_CHECK_KEY(tokens[i]); + + if (cgltf_json_strcmp(tokens + i, json_chunk, "variants") == 0) + { + i = cgltf_parse_json_variants(options, tokens, i + 1, json_chunk, out_data); + } + else + { + i = cgltf_skip_json(tokens, i + 1); + } + + if (i < 0) + { + return i; + } + } + } else { - i = cgltf_skip_json(tokens, i + 1); + i = cgltf_parse_json_unprocessed_extension(options, tokens, i, json_chunk, &(out_data->data_extensions[out_data->data_extensions_count++])); } if (i < 0) @@ -4585,6 +5810,20 @@ static int cgltf_fixup_pointers(cgltf_data* data) CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].targets[k].attributes[m].data, data->accessors, data->accessors_count); } } + + if (data->meshes[i].primitives[j].has_draco_mesh_compression) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.buffer_view, data->buffer_views, data->buffer_views_count); + for (cgltf_size m = 0; m < data->meshes[i].primitives[j].draco_mesh_compression.attributes_count; ++m) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].draco_mesh_compression.attributes[m].data, data->accessors, data->accessors_count); + } + } + + for (cgltf_size k = 0; k < data->meshes[i].primitives[j].mappings_count; ++k) + { + CGLTF_PTRFIXUP_REQ(data->meshes[i].primitives[j].mappings[k].material, data->materials, data->materials_count); + } } } @@ -4631,11 +5870,30 @@ static int cgltf_fixup_pointers(cgltf_data* data) CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.diffuse_texture.texture, data->textures, data->textures_count); CGLTF_PTRFIXUP(data->materials[i].pbr_specular_glossiness.specular_glossiness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_roughness_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].clearcoat.clearcoat_normal_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].specular.specular_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].specular.specular_color_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].transmission.transmission_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].volume.thickness_texture.texture, data->textures, data->textures_count); + + CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_color_texture.texture, data->textures, data->textures_count); + CGLTF_PTRFIXUP(data->materials[i].sheen.sheen_roughness_texture.texture, data->textures, data->textures_count); } for (cgltf_size i = 0; i < data->buffer_views_count; ++i) { CGLTF_PTRFIXUP_REQ(data->buffer_views[i].buffer, data->buffers, data->buffers_count); + + if (data->buffer_views[i].has_meshopt_compression) + { + CGLTF_PTRFIXUP_REQ(data->buffer_views[i].meshopt_compression.buffer, data->buffers, data->buffers_count); + } } for (cgltf_size i = 0; i < data->skins_count; ++i) @@ -4750,7 +6008,7 @@ static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, * Fills token type and boundaries. */ static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, - int start, int end) { + int start, int end) { token->type = type; token->start = start; token->end = end; @@ -4810,7 +6068,7 @@ found: * Fills next token with JSON string. */ static int jsmn_parse_string(jsmn_parser *parser, const char *js, - size_t len, jsmntok_t *tokens, size_t num_tokens) { + size_t len, jsmntok_t *tokens, size_t num_tokens) { jsmntok_t *token; int start = parser->pos; @@ -4853,8 +6111,8 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { /* If it isn't a hex character we have an error */ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ - (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ - (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ parser->pos = start; return JSMN_ERROR_INVAL; } @@ -4877,7 +6135,7 @@ static int jsmn_parse_string(jsmn_parser *parser, const char *js, * Parse JSON string and fill tokens. */ static int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, - jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tokens, size_t num_tokens) { int r; int i; jsmntok_t *token; diff --git a/raylib/external/dr_flac.h b/raylib/external/dr_flac.h index 250d0bd..a797350 100644 --- a/raylib/external/dr_flac.h +++ b/raylib/external/dr_flac.h @@ -1,148 +1,305 @@ /* FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_flac - v0.11.10 - 2019-06-26 +dr_flac - v0.12.29 - 2021-04-02 David Reid - mackron@gmail.com + +GitHub: https://github.com/mackron/dr_libs */ /* -USAGE -===== -dr_flac is a single-file library. To use it, do something like the following in one .c file. +RELEASE NOTES - v0.12.0 +======================= +Version 0.12.0 has breaking API changes including changes to the existing API and the removal of deprecated APIs. + + +Improved Client-Defined Memory Allocation +----------------------------------------- +The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The +existing system of DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE are still in place and will be used by default when no custom +allocation callbacks are specified. + +To use the new system, you pass in a pointer to a drflac_allocation_callbacks object to drflac_open() and family, like this: + + void* my_malloc(size_t sz, void* pUserData) + { + return malloc(sz); + } + void* my_realloc(void* p, size_t sz, void* pUserData) + { + return realloc(p, sz); + } + void my_free(void* p, void* pUserData) + { + free(p); + } + + ... + + drflac_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = &myData; + allocationCallbacks.onMalloc = my_malloc; + allocationCallbacks.onRealloc = my_realloc; + allocationCallbacks.onFree = my_free; + drflac* pFlac = drflac_open_file("my_file.flac", &allocationCallbacks); + +The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. + +Passing in null for the allocation callbacks object will cause dr_flac to use defaults which is the same as DRFLAC_MALLOC, +DRFLAC_REALLOC and DRFLAC_FREE and the equivalent of how it worked in previous versions. + +Every API that opens a drflac object now takes this extra parameter. These include the following: + + drflac_open() + drflac_open_relaxed() + drflac_open_with_metadata() + drflac_open_with_metadata_relaxed() + drflac_open_file() + drflac_open_file_with_metadata() + drflac_open_memory() + drflac_open_memory_with_metadata() + drflac_open_and_read_pcm_frames_s32() + drflac_open_and_read_pcm_frames_s16() + drflac_open_and_read_pcm_frames_f32() + drflac_open_file_and_read_pcm_frames_s32() + drflac_open_file_and_read_pcm_frames_s16() + drflac_open_file_and_read_pcm_frames_f32() + drflac_open_memory_and_read_pcm_frames_s32() + drflac_open_memory_and_read_pcm_frames_s16() + drflac_open_memory_and_read_pcm_frames_f32() + + + +Optimizations +------------- +Seeking performance has been greatly improved. A new binary search based seeking algorithm has been introduced which significantly +improves performance over the brute force method which was used when no seek table was present. Seek table based seeking also takes +advantage of the new binary search seeking system to further improve performance there as well. Note that this depends on CRC which +means it will be disabled when DR_FLAC_NO_CRC is used. + +The SSE4.1 pipeline has been cleaned up and optimized. You should see some improvements with decoding speed of 24-bit files in +particular. 16-bit streams should also see some improvement. + +drflac_read_pcm_frames_s16() has been optimized. Previously this sat on top of drflac_read_pcm_frames_s32() and performed it's s32 +to s16 conversion in a second pass. This is now all done in a single pass. This includes SSE2 and ARM NEON optimized paths. + +A minor optimization has been implemented for drflac_read_pcm_frames_s32(). This will now use an SSE2 optimized pipeline for stereo +channel reconstruction which is the last part of the decoding process. + +The ARM build has seen a few improvements. The CLZ (count leading zeroes) and REV (byte swap) instructions are now used when +compiling with GCC and Clang which is achieved using inline assembly. The CLZ instruction requires ARM architecture version 5 at +compile time and the REV instruction requires ARM architecture version 6. + +An ARM NEON optimized pipeline has been implemented. To enable this you'll need to add -mfpu=neon to the command line when compiling. + + +Removed APIs +------------ +The following APIs were deprecated in version 0.11.0 and have been completely removed in version 0.12.0: + + drflac_read_s32() -> drflac_read_pcm_frames_s32() + drflac_read_s16() -> drflac_read_pcm_frames_s16() + drflac_read_f32() -> drflac_read_pcm_frames_f32() + drflac_seek_to_sample() -> drflac_seek_to_pcm_frame() + drflac_open_and_decode_s32() -> drflac_open_and_read_pcm_frames_s32() + drflac_open_and_decode_s16() -> drflac_open_and_read_pcm_frames_s16() + drflac_open_and_decode_f32() -> drflac_open_and_read_pcm_frames_f32() + drflac_open_and_decode_file_s32() -> drflac_open_file_and_read_pcm_frames_s32() + drflac_open_and_decode_file_s16() -> drflac_open_file_and_read_pcm_frames_s16() + drflac_open_and_decode_file_f32() -> drflac_open_file_and_read_pcm_frames_f32() + drflac_open_and_decode_memory_s32() -> drflac_open_memory_and_read_pcm_frames_s32() + drflac_open_and_decode_memory_s16() -> drflac_open_memory_and_read_pcm_frames_s16() + drflac_open_and_decode_memory_f32() -> drflac_open_memroy_and_read_pcm_frames_f32() + +Prior versions of dr_flac operated on a per-sample basis whereas now it operates on PCM frames. The removed APIs all relate +to the old per-sample APIs. You now need to use the "pcm_frame" versions. +*/ + + +/* +Introduction +============ +dr_flac is a single file library. To use it, do something like the following in one .c file. + + ```c #define DR_FLAC_IMPLEMENTATION #include "dr_flac.h" + ``` -You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, -do something like the following: +You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: - drflac* pFlac = drflac_open_file("MySong.flac"); + ```c + drflac* pFlac = drflac_open_file("MySong.flac", NULL); if (pFlac == NULL) { // Failed to open FLAC file } drflac_int32* pSamples = malloc(pFlac->totalPCMFrameCount * pFlac->channels * sizeof(drflac_int32)); drflac_uint64 numberOfInterleavedSamplesActuallyRead = drflac_read_pcm_frames_s32(pFlac, pFlac->totalPCMFrameCount, pSamples); + ``` -The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of -channels and the bits per sample, should be directly accessible - just make sure you don't change their values. Samples are -always output as interleaved signed 32-bit PCM. In the example above a native FLAC stream was opened, however dr_flac has -seamless support for Ogg encapsulated FLAC streams as well. +The drflac object represents the decoder. It is a transparent type so all the information you need, such as the number of channels and the bits per sample, +should be directly accessible - just make sure you don't change their values. Samples are always output as interleaved signed 32-bit PCM. In the example above +a native FLAC stream was opened, however dr_flac has seamless support for Ogg encapsulated FLAC streams as well. -You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and -the decoder will give you as many samples as it can, up to the amount requested. Later on when you need the next batch of -samples, just call it again. Example: +You do not need to decode the entire stream in one go - you just specify how many samples you'd like at any given time and the decoder will give you as many +samples as it can, up to the amount requested. Later on when you need the next batch of samples, just call it again. Example: + ```c while (drflac_read_pcm_frames_s32(pFlac, chunkSizeInPCMFrames, pChunkSamples) > 0) { do_something(); } + ``` -You can seek to a specific sample with drflac_seek_to_sample(). The given sample is based on interleaving. So for example, -if you were to seek to the sample at index 0 in a stereo stream, you'll be seeking to the first sample of the left channel. -The sample at index 1 will be the first sample of the right channel. The sample at index 2 will be the second sample of the -left channel, etc. - +You can seek to a specific PCM frame with `drflac_seek_to_pcm_frame()`. If you just want to quickly decode an entire FLAC file in one go you can do something like this: + ```c unsigned int channels; unsigned int sampleRate; drflac_uint64 totalPCMFrameCount; - drflac_int32* pSampleData = drflac_open_file_and_read_pcm_frames_s32("MySong.flac", &channels, &sampleRate, &totalPCMFrameCount); + drflac_int32* pSampleData = drflac_open_file_and_read_pcm_frames_s32("MySong.flac", &channels, &sampleRate, &totalPCMFrameCount, NULL); if (pSampleData == NULL) { // Failed to open and decode FLAC file. } ... - drflac_free(pSampleData); + drflac_free(pSampleData, NULL); + ``` + +You can read samples as signed 16-bit integer and 32-bit floating-point PCM with the *_s16() and *_f32() family of APIs respectively, but note that these +should be considered lossy. -You can read samples as signed 16-bit integer and 32-bit floating-point PCM with the *_s16() and *_f32() family of APIs -respectively, but note that these should be considered lossy. +If you need access to metadata (album art, etc.), use `drflac_open_with_metadata()`, `drflac_open_file_with_metdata()` or `drflac_open_memory_with_metadata()`. +The rationale for keeping these APIs separate is that they're slightly slower than the normal versions and also just a little bit harder to use. dr_flac +reports metadata to the application through the use of a callback, and every metadata block is reported before `drflac_open_with_metdata()` returns. +The main opening APIs (`drflac_open()`, etc.) will fail if the header is not present. The presents a problem in certain scenarios such as broadcast style +streams or internet radio where the header may not be present because the user has started playback mid-stream. To handle this, use the relaxed APIs: + + `drflac_open_relaxed()` + `drflac_open_with_metadata_relaxed()` -If you need access to metadata (album art, etc.), use drflac_open_with_metadata(), drflac_open_file_with_metdata() or -drflac_open_memory_with_metadata(). The rationale for keeping these APIs separate is that they're slightly slower than the -normal versions and also just a little bit harder to use. - -dr_flac reports metadata to the application through the use of a callback, and every metadata block is reported before -drflac_open_with_metdata() returns. - - -The main opening APIs (drflac_open(), etc.) will fail if the header is not present. The presents a problem in certain -scenarios such as broadcast style streams like internet radio where the header may not be present because the user has -started playback mid-stream. To handle this, use the relaxed APIs: drflac_open_relaxed() and drflac_open_with_metadata_relaxed(). - -It is not recommended to use these APIs for file based streams because a missing header would usually indicate a -corrupted or perverse file. In addition, these APIs can take a long time to initialize because they may need to spend -a lot of time finding the first frame. +It is not recommended to use these APIs for file based streams because a missing header would usually indicate a corrupt or perverse file. In addition, these +APIs can take a long time to initialize because they may need to spend a lot of time finding the first frame. -OPTIONS -======= +Build Options +============= #define these options before including this file. #define DR_FLAC_NO_STDIO - Disable drflac_open_file() and family. + Disable `drflac_open_file()` and family. #define DR_FLAC_NO_OGG Disables support for Ogg/FLAC streams. #define DR_FLAC_BUFFER_SIZE - Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls - back to the client for more data. Larger values means more memory, but better performance. My tests show diminishing - returns after about 4KB (which is the default). Consider reducing this if you have a very efficient implementation of - onRead(), or increase it if it's very inefficient. Must be a multiple of 8. + Defines the size of the internal buffer to store data from onRead(). This buffer is used to reduce the number of calls back to the client for more data. + Larger values means more memory, but better performance. My tests show diminishing returns after about 4KB (which is the default). Consider reducing this if + you have a very efficient implementation of onRead(), or increase it if it's very inefficient. Must be a multiple of 8. #define DR_FLAC_NO_CRC - Disables CRC checks. This will offer a performance boost when CRC is unnecessary. + Disables CRC checks. This will offer a performance boost when CRC is unnecessary. This will disable binary search seeking. When seeking, the seek table will + be used if available. Otherwise the seek will be performed using brute force. #define DR_FLAC_NO_SIMD - Disables SIMD optimizations (SSE on x86/x64 architectures). Use this if you are having compatibility issues with your - compiler. + Disables SIMD optimizations (SSE on x86/x64 architectures, NEON on ARM architectures). Use this if you are having compatibility issues with your compiler. -QUICK NOTES -=========== -- dr_flac does not currently support changing the sample rate nor channel count mid stream. -- Audio data is output as signed 32-bit PCM, regardless of the bits per sample the FLAC stream is encoded as. -- This has not been tested on big-endian architectures. +Notes +===== +- dr_flac does not support changing the sample rate nor channel count mid stream. - dr_flac is not thread-safe, but its APIs can be called from any thread so long as you do your own synchronization. -- When using Ogg encapsulation, a corrupted metadata block will result in drflac_open_with_metadata() and drflac_open() - returning inconsistent samples. +- When using Ogg encapsulation, a corrupted metadata block will result in `drflac_open_with_metadata()` and `drflac_open()` returning inconsistent samples due + to differences in corrupted stream recorvery logic between the two APIs. */ #ifndef dr_flac_h #define dr_flac_h -#include - -#if defined(_MSC_VER) && _MSC_VER < 1600 -typedef signed char drflac_int8; -typedef unsigned char drflac_uint8; -typedef signed short drflac_int16; -typedef unsigned short drflac_uint16; -typedef signed int drflac_int32; -typedef unsigned int drflac_uint32; -typedef signed __int64 drflac_int64; -typedef unsigned __int64 drflac_uint64; -#else -#include -typedef int8_t drflac_int8; -typedef uint8_t drflac_uint8; -typedef int16_t drflac_int16; -typedef uint16_t drflac_uint16; -typedef int32_t drflac_int32; -typedef uint32_t drflac_uint32; -typedef int64_t drflac_int64; -typedef uint64_t drflac_uint64; +#ifdef __cplusplus +extern "C" { +#endif + +#define DRFLAC_STRINGIFY(x) #x +#define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) + +#define DRFLAC_VERSION_MAJOR 0 +#define DRFLAC_VERSION_MINOR 12 +#define DRFLAC_VERSION_REVISION 29 +#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized types. */ +typedef signed char drflac_int8; +typedef unsigned char drflac_uint8; +typedef signed short drflac_int16; +typedef unsigned short drflac_uint16; +typedef signed int drflac_int32; +typedef unsigned int drflac_uint32; +#if defined(_MSC_VER) + typedef signed __int64 drflac_int64; + typedef unsigned __int64 drflac_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drflac_int64; + typedef unsigned long long drflac_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif +#endif +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + typedef drflac_uint64 drflac_uintptr; +#else + typedef drflac_uint32 drflac_uintptr; +#endif +typedef drflac_uint8 drflac_bool8; +typedef drflac_uint32 drflac_bool32; +#define DRFLAC_TRUE 1 +#define DRFLAC_FALSE 0 + +#if !defined(DRFLAC_API) + #if defined(DRFLAC_DLL) + #if defined(_WIN32) + #define DRFLAC_DLL_IMPORT __declspec(dllimport) + #define DRFLAC_DLL_EXPORT __declspec(dllexport) + #define DRFLAC_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRFLAC_DLL_IMPORT __attribute__((visibility("default"))) + #define DRFLAC_DLL_EXPORT __attribute__((visibility("default"))) + #define DRFLAC_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRFLAC_DLL_IMPORT + #define DRFLAC_DLL_EXPORT + #define DRFLAC_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) + #define DRFLAC_API DRFLAC_DLL_EXPORT + #else + #define DRFLAC_API DRFLAC_DLL_IMPORT + #endif + #define DRFLAC_PRIVATE DRFLAC_DLL_PRIVATE + #else + #define DRFLAC_API extern + #define DRFLAC_PRIVATE static + #endif #endif -typedef drflac_uint8 drflac_bool8; -typedef drflac_uint32 drflac_bool32; -#define DRFLAC_TRUE 1 -#define DRFLAC_FALSE 0 #if defined(_MSC_VER) && _MSC_VER >= 1700 /* Visual Studio 2012 */ #define DRFLAC_DEPRECATED __declspec(deprecated) @@ -158,19 +315,17 @@ typedef drflac_uint32 drflac_bool32; #define DRFLAC_DEPRECATED #endif +DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision); +DRFLAC_API const char* drflac_version_string(void); + /* -As data is read from the client it is placed into an internal buffer for fast access. This controls the -size of that buffer. Larger values means more speed, but also more memory. In my testing there is diminishing -returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. +As data is read from the client it is placed into an internal buffer for fast access. This controls the size of that buffer. Larger values means more speed, +but also more memory. In my testing there is diminishing returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8. */ #ifndef DR_FLAC_BUFFER_SIZE #define DR_FLAC_BUFFER_SIZE 4096 #endif -#ifdef __cplusplus -extern "C" { -#endif - /* Check if we can enable 64-bit optimizations. */ #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) #define DRFLAC_64BIT @@ -232,28 +387,31 @@ typedef enum #pragma pack(2) typedef struct { - drflac_uint64 firstSample; - drflac_uint64 frameOffset; /* The offset from the first byte of the header of the first frame. */ - drflac_uint16 sampleCount; + drflac_uint64 firstPCMFrame; + drflac_uint64 flacFrameOffset; /* The offset from the first byte of the header of the first frame. */ + drflac_uint16 pcmFrameCount; } drflac_seekpoint; #pragma pack() typedef struct { - drflac_uint16 minBlockSize; - drflac_uint16 maxBlockSize; - drflac_uint32 minFrameSize; - drflac_uint32 maxFrameSize; + drflac_uint16 minBlockSizeInPCMFrames; + drflac_uint16 maxBlockSizeInPCMFrames; + drflac_uint32 minFrameSizeInPCMFrames; + drflac_uint32 maxFrameSizeInPCMFrames; drflac_uint32 sampleRate; drflac_uint8 channels; drflac_uint8 bitsPerSample; - drflac_uint64 totalSampleCount; + drflac_uint64 totalPCMFrameCount; drflac_uint8 md5[16]; } drflac_streaminfo; typedef struct { - /* The metadata type. Use this to know how to interpret the data below. */ + /* + The metadata type. Use this to know how to interpret the data below. Will be set to one of the + DRFLAC_METADATA_BLOCK_TYPE_* tokens. + */ drflac_uint32 type; /* @@ -326,42 +484,91 @@ typedef struct /* Callback for when data needs to be read from the client. -pUserData [in] The user data that was passed to drflac_open() and family. -pBufferOut [out] The output buffer. -bytesToRead [in] The number of bytes to read. -Returns the number of bytes actually read. +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. -A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until -either the entire bytesToRead is filled or you have reached the end of the stream. +pBufferOut (out) + The output buffer. + +bytesToRead (in) + The number of bytes to read. + + +Return Value +------------ +The number of bytes actually read. + + +Remarks +------- +A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until either the entire bytesToRead is filled or +you have reached the end of the stream. */ typedef size_t (* drflac_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); /* Callback for when data needs to be seeked. -pUserData [in] The user data that was passed to drflac_open() and family. -offset [in] The number of bytes to move, relative to the origin. Will never be negative. -origin [in] The origin of the seek - the current position or the start of the stream. -Returns whether or not the seek was successful. +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. -The offset will never be negative. Whether or not it is relative to the beginning or current position is determined -by the "origin" parameter which will be either drflac_seek_origin_start or drflac_seek_origin_current. +offset (in) + The number of bytes to move, relative to the origin. Will never be negative. + +origin (in) + The origin of the seek - the current position or the start of the stream. + + +Return Value +------------ +Whether or not the seek was successful. + + +Remarks +------- +The offset will never be negative. Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which will be +either drflac_seek_origin_start or drflac_seek_origin_current. + +When seeking to a PCM frame using drflac_seek_to_pcm_frame(), dr_flac may call this with an offset beyond the end of the FLAC stream. This needs to be detected +and handled by returning DRFLAC_FALSE. */ typedef drflac_bool32 (* drflac_seek_proc)(void* pUserData, int offset, drflac_seek_origin origin); /* Callback for when a metadata block is read. -pUserData [in] The user data that was passed to drflac_open() and family. -pMetadata [in] A pointer to a structure containing the data of the metadata block. -Use pMetadata->type to determine which metadata block is being handled and how to read the data. +Parameters +---------- +pUserData (in) + The user data that was passed to drflac_open() and family. + +pMetadata (in) + A pointer to a structure containing the data of the metadata block. + + +Remarks +------- +Use pMetadata->type to determine which metadata block is being handled and how to read the data. This +will be set to one of the DRFLAC_METADATA_BLOCK_TYPE_* tokens. */ typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata); +typedef struct +{ + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drflac_allocation_callbacks; + /* Structure for internal use. Only used for decoders opened with drflac_open_memory. */ typedef struct { @@ -426,35 +633,29 @@ typedef struct /* The order to use for the prediction stage for SUBFRAME_FIXED and SUBFRAME_LPC. */ drflac_uint8 lpcOrder; - /* - The number of bits per sample for this subframe. This is not always equal to the current frame's bit per sample because - an extra bit is required for side channels when interchannel decorrelation is being used. - */ - drflac_uint32 bitsPerSample; - - /* - A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pExtraData. Note that - it's a signed 32-bit integer for each value. - */ - drflac_int32* pDecodedSamples; + /* A pointer to the buffer containing the decoded samples in the subframe. This pointer is an offset from drflac::pExtraData. */ + drflac_int32* pSamplesS32; } drflac_subframe; typedef struct { /* - If the stream uses variable block sizes, this will be set to the index of the first sample. If fixed block sizes are used, this will - always be set to 0. + If the stream uses variable block sizes, this will be set to the index of the first PCM frame. If fixed block sizes are used, this will + always be set to 0. This is 64-bit because the decoded PCM frame number will be 36 bits. */ - drflac_uint64 sampleNumber; + drflac_uint64 pcmFrameNumber; - /* If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. */ - drflac_uint32 frameNumber; + /* + If the stream uses fixed block sizes, this will be set to the frame number. If variable block sizes are used, this will always be 0. This + is 32-bit because in fixed block sizes, the maximum frame number will be 31 bits. + */ + drflac_uint32 flacFrameNumber; /* The sample rate of this frame. */ drflac_uint32 sampleRate; - /* The number of samples in each sub-frame within this frame. */ - drflac_uint16 blockSize; + /* The number of PCM frames in each sub-frame within this frame. */ + drflac_uint16 blockSizeInPCMFrames; /* The channel assignment of this frame. This is not always set to the channel count. If interchannel decorrelation is being used this @@ -475,10 +676,10 @@ typedef struct drflac_frame_header header; /* - The number of samples left to be read in this frame. This is initially set to the block size multiplied by the channel count. As samples - are read, this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. + The number of PCM frames left to be read in this FLAC frame. This is initially set to the block size. As PCM frames are read, + this will be decremented. When it reaches 0, the decoder will see this frame as fully consumed and load the next frame. */ - drflac_uint32 samplesRemaining; + drflac_uint32 pcmFramesRemaining; /* The list of sub-frames within the frame. There is one sub-frame for each channel, and there's a maximum of 8 channels. */ drflac_subframe subframes[8]; @@ -492,6 +693,9 @@ typedef struct /* The user data posted to the metadata callback function. */ void* pUserDataMD; + /* Memory allocation callbacks. */ + drflac_allocation_callbacks allocationCallbacks; + /* The sample rate. Will be set to something like 44100. */ drflac_uint32 sampleRate; @@ -506,15 +710,13 @@ typedef struct drflac_uint8 bitsPerSample; /* The maximum block size, in samples. This number represents the number of samples in each channel (not combined). */ - drflac_uint16 maxBlockSize; + drflac_uint16 maxBlockSizeInPCMFrames; /* - The total number of samples making up the stream. This includes every channel. For example, if the stream has 2 channels, - with each channel having a total of 4096, this value will be set to 2*4096 = 8192. Can be 0 in which case it's still a - valid stream, but just means the total sample count is unknown. Likely the case with streams like internet radio. + The total number of PCM Frames making up the stream. Can be 0 in which case it's still a valid stream, but just means + the total PCM frame count is unknown. Likely the case with streams like internet radio. */ - drflac_uint64 totalSampleCount; - drflac_uint64 totalPCMFrameCount; /* <-- Equal to totalSampleCount / channels. */ + drflac_uint64 totalPCMFrameCount; /* The container type. This is set based on whether or not the decoder was opened from a native or Ogg stream. */ @@ -525,13 +727,14 @@ typedef struct /* Information about the frame the decoder is currently sitting on. */ - drflac_frame currentFrame; + drflac_frame currentFLACFrame; - /* The index of the sample the decoder is currently sitting on. This is only used for seeking. */ - drflac_uint64 currentSample; - /* The position of the first frame in the stream. This is only ever used for seeking. */ - drflac_uint64 firstFramePos; + /* The index of the PCM frame the decoder is currently sitting on. This is only used for seeking. */ + drflac_uint64 currentPCMFrame; + + /* The position of the first FLAC frame in the stream. This is only ever used for seeking. */ + drflac_uint64 firstFLACFramePosInBytes; /* A hack to avoid a malloc() when opening a decoder with drflac_open_memory(). */ @@ -547,6 +750,11 @@ typedef struct /* Internal use only. Only used with Ogg containers. Points to a drflac_oggbs object. This is an offset of pExtraData. */ void* _oggbs; + /* Internal use only. Used for profiling and testing different seeking modes. */ + drflac_bool32 _noSeekTableSeek : 1; + drflac_bool32 _noBinarySearchSeek : 1; + drflac_bool32 _noBruteForceSeek : 1; + /* The bit streamer. The raw FLAC data is fed through this object. */ drflac_bs bs; @@ -558,126 +766,294 @@ typedef struct /* Opens a FLAC decoder. -onRead [in] The function to call when data needs to be read from the client. -onSeek [in] The function to call when the read position of the client data needs to move. -pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead and onSeek. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ Returns a pointer to an object representing the decoder. -Close the decoder with drflac_close(). -This function will automatically detect whether or not you are attempting to open a native or Ogg encapsulated -FLAC, both of which should work seamlessly without any manual intervention. Ogg encapsulation also works with -multiplexed streams which basically means it can play FLAC encoded audio tracks in videos. +Remarks +------- +Close the decoder with `drflac_close()`. -This is the lowest level function for opening a FLAC stream. You can also use drflac_open_file() and drflac_open_memory() -to open the stream from a file or from a block of memory respectively. +`pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. -The STREAMINFO block must be present for this to succeed. Use drflac_open_relaxed() to open a FLAC stream where -the header may not be present. +This function will automatically detect whether or not you are attempting to open a native or Ogg encapsulated FLAC, both of which should work seamlessly +without any manual intervention. Ogg encapsulation also works with multiplexed streams which basically means it can play FLAC encoded audio tracks in videos. -See also: drflac_open_file(), drflac_open_memory(), drflac_open_with_metadata(), drflac_close() +This is the lowest level function for opening a FLAC stream. You can also use `drflac_open_file()` and `drflac_open_memory()` to open the stream from a file or +from a block of memory respectively. + +The STREAMINFO block must be present for this to succeed. Use `drflac_open_relaxed()` to open a FLAC stream where the header may not be present. + +Use `drflac_open_with_metadata()` if you need access to metadata. + + +Seek Also +--------- +drflac_open_file() +drflac_open_memory() +drflac_open_with_metadata() +drflac_close() */ -drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData); +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* +Opens a FLAC stream with relaxed validation of the header block. + + +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. + +onSeek (in) + The function to call when the read position of the client data needs to move. + +container (in) + Whether or not the FLAC stream is encapsulated using standard FLAC encapsulation or Ogg encapsulation. + +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead and onSeek. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- The same as drflac_open(), except attempts to open the stream even when a header block is not present. -Because the header is not necessarily available, the caller must explicitly define the container (Native or Ogg). Do -not set this to drflac_container_unknown - that is for internal use only. +Because the header is not necessarily available, the caller must explicitly define the container (Native or Ogg). Do not set this to `drflac_container_unknown` +as that is for internal use only. -Opening in relaxed mode will continue reading data from onRead until it finds a valid frame. If a frame is never -found it will continue forever. To abort, force your onRead callback to return 0, which dr_flac will use as an -indicator that the end of the stream was found. +Opening in relaxed mode will continue reading data from onRead until it finds a valid frame. If a frame is never found it will continue forever. To abort, +force your `onRead` callback to return 0, which dr_flac will use as an indicator that the end of the stream was found. + +Use `drflac_open_with_metadata_relaxed()` if you need access to metadata. */ -drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData); +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder and notifies the caller of the metadata chunks (album art, etc.). -onRead [in] The function to call when data needs to be read from the client. -onSeek [in] The function to call when the read position of the client data needs to move. -onMeta [in] The function to call for every metadata block. -pUserData [in, optional] A pointer to application defined data that will be passed to onRead, onSeek and onMeta. -Returns a pointer to an object representing the decoder. +Parameters +---------- +onRead (in) + The function to call when data needs to be read from the client. -Close the decoder with drflac_close(). +onSeek (in) + The function to call when the read position of the client data needs to move. -This is slower than drflac_open(), so avoid this one if you don't need metadata. Internally, this will do a DRFLAC_MALLOC() -and DRFLAC_FREE() for every metadata block except for STREAMINFO and PADDING blocks. +onMeta (in) + The function to call for every metadata block. -The caller is notified of the metadata via the onMeta callback. All metadata blocks will be handled before the function -returns. +pUserData (in, optional) + A pointer to application defined data that will be passed to onRead, onSeek and onMeta. -The STREAMINFO block must be present for this to succeed. Use drflac_open_with_metadata_relaxed() to open a FLAC -stream where the header may not be present. +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. -Note that this will behave inconsistently with drflac_open() if the stream is an Ogg encapsulated stream and a metadata -block is corrupted. This is due to the way the Ogg stream recovers from corrupted pages. When drflac_open_with_metadata() -is being used, the open routine will try to read the contents of the metadata block, whereas drflac_open() will simply -seek past it (for the sake of efficiency). This inconsistency can result in different samples being returned depending on -whether or not the stream is being opened with metadata. -See also: drflac_open_file_with_metadata(), drflac_open_memory_with_metadata(), drflac_open(), drflac_close() +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +Close the decoder with `drflac_close()`. + +`pAllocationCallbacks` can be NULL in which case it will use `DRFLAC_MALLOC`, `DRFLAC_REALLOC` and `DRFLAC_FREE`. + +This is slower than `drflac_open()`, so avoid this one if you don't need metadata. Internally, this will allocate and free memory on the heap for every +metadata block except for STREAMINFO and PADDING blocks. + +The caller is notified of the metadata via the `onMeta` callback. All metadata blocks will be handled before the function returns. This callback takes a +pointer to a `drflac_metadata` object which is a union containing the data of all relevant metadata blocks. Use the `type` member to discriminate against +the different metadata types. + +The STREAMINFO block must be present for this to succeed. Use `drflac_open_with_metadata_relaxed()` to open a FLAC stream where the header may not be present. + +Note that this will behave inconsistently with `drflac_open()` if the stream is an Ogg encapsulated stream and a metadata block is corrupted. This is due to +the way the Ogg stream recovers from corrupted pages. When `drflac_open_with_metadata()` is being used, the open routine will try to read the contents of the +metadata block, whereas `drflac_open()` will simply seek past it (for the sake of efficiency). This inconsistency can result in different samples being +returned depending on whether or not the stream is being opened with metadata. + + +Seek Also +--------- +drflac_open_file_with_metadata() +drflac_open_memory_with_metadata() +drflac_open() +drflac_close() */ -drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData); +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* The same as drflac_open_with_metadata(), except attempts to open the stream even when a header block is not present. -See also: drflac_open_with_metadata(), drflac_open_relaxed() +See Also +-------- +drflac_open_with_metadata() +drflac_open_relaxed() */ -drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData); +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); /* Closes the given FLAC decoder. -pFlac [in] The decoder to close. +Parameters +---------- +pFlac (in) + The decoder to close. + + +Remarks +------- This will destroy the decoder object. + + +See Also +-------- +drflac_open() +drflac_open_with_metadata() +drflac_open_file() +drflac_open_file_w() +drflac_open_file_with_metadata() +drflac_open_file_with_metadata_w() +drflac_open_memory() +drflac_open_memory_with_metadata() */ -void drflac_close(drflac* pFlac); +DRFLAC_API void drflac_close(drflac* pFlac); /* Reads sample data from the given FLAC decoder, output as interleaved signed 32-bit PCM. -pFlac [in] The decoder. -framesToRead [in] The number of PCM frames to read. -pBufferOut [out, optional] A pointer to the buffer that will receive the decoded samples. -Returns the number of PCM frames actually read. +Parameters +---------- +pFlac (in) + The decoder. -pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames -seeked. +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. */ -drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut); +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut); + /* -Same as drflac_read_pcm_frames_s32(), except outputs samples as 16-bit integer PCM rather than 32-bit. +Reads sample data from the given FLAC decoder, output as interleaved signed 16-bit PCM. + + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. Note that this is lossy for streams where the bits per sample is larger than 16. */ -drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut); +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut); /* -Same as drflac_read_pcm_frames_s32(), except outputs samples as 32-bit floating-point PCM. +Reads sample data from the given FLAC decoder, output as interleaved 32-bit floating point PCM. -Note that this should be considered lossy due to the nature of floating point numbers not being able to exactly -represent every possible number. + +Parameters +---------- +pFlac (in) + The decoder. + +framesToRead (in) + The number of PCM frames to read. + +pBufferOut (out, optional) + A pointer to the buffer that will receive the decoded samples. + + +Return Value +------------ +Returns the number of PCM frames actually read. If the return value is less than `framesToRead` it has reached the end. + + +Remarks +------- +pBufferOut can be null, in which case the call will act as a seek, and the return value will be the number of frames seeked. + +Note that this should be considered lossy due to the nature of floating point numbers not being able to exactly represent every possible number. */ -drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut); +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut); /* Seeks to the PCM frame at the given index. -pFlac [in] The decoder. -pcmFrameIndex [in] The index of the PCM frame to seek to. See notes below. -Returns DRFLAC_TRUE if successful; DRFLAC_FALSE otherwise. +Parameters +---------- +pFlac (in) + The decoder. + +pcmFrameIndex (in) + The index of the PCM frame to seek to. See notes below. + + +Return Value +------------- +`DRFLAC_TRUE` if successful; `DRFLAC_FALSE` otherwise. */ -drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); +DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex); @@ -685,42 +1061,145 @@ drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameInde /* Opens a FLAC decoder from the file at the given path. -filename [in] The path of the file to open, either absolute or relative to the current directory. -Returns a pointer to an object representing the decoder. +Parameters +---------- +pFileName (in) + The path of the file to open, either absolute or relative to the current directory. +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- Close the decoder with drflac_close(). -This will hold a handle to the file until the decoder is closed with drflac_close(). Some platforms will restrict the -number of files a process can have open at any given time, so keep this mind if you have many decoders open at the -same time. -See also: drflac_open(), drflac_open_file_with_metadata(), drflac_close() +Remarks +------- +This will hold a handle to the file until the decoder is closed with drflac_close(). Some platforms will restrict the number of files a process can have open +at any given time, so keep this mind if you have many decoders open at the same time. + + +See Also +-------- +drflac_open_file_with_metadata() +drflac_open() +drflac_close() */ -drflac* drflac_open_file(const char* filename); +DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder from the file at the given path and notifies the caller of the metadata chunks (album art, etc.) + +Parameters +---------- +pFileName (in) + The path of the file to open, either absolute or relative to the current directory. + +pAllocationCallbacks (in, optional) + A pointer to application defined callbacks for managing memory allocations. + +onMeta (in) + The callback to fire for each metadata block. + +pUserData (in) + A pointer to the user data to pass to the metadata callback. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Remarks +------- Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. + + +See Also +-------- +drflac_open_with_metadata() +drflac_open() +drflac_close() */ -drflac* drflac_open_file_with_metadata(const char* filename, drflac_meta_proc onMeta, void* pUserData); +DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); +DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); #endif /* Opens a FLAC decoder from a pre-allocated block of memory -This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for -the lifetime of the decoder. + +Parameters +---------- +pData (in) + A pointer to the raw encoded FLAC data. + +dataSize (in) + The size in bytes of `data`. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Return Value +------------ +A pointer to an object representing the decoder. + + +Remarks +------- +This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for the lifetime of the decoder. + + +See Also +-------- +drflac_open() +drflac_close() */ -drflac* drflac_open_memory(const void* data, size_t dataSize); +DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks); /* Opens a FLAC decoder from a pre-allocated block of memory and notifies the caller of the metadata chunks (album art, etc.) + +Parameters +---------- +pData (in) + A pointer to the raw encoded FLAC data. + +dataSize (in) + The size in bytes of `data`. + +onMeta (in) + The callback to fire for each metadata block. + +pUserData (in) + A pointer to the user data to pass to the metadata callback. + +pAllocationCallbacks (in) + A pointer to application defined callbacks for managing memory allocations. + + +Remarks +------- Look at the documentation for drflac_open_with_metadata() for more information on how metadata is handled. + + +See Also +------- +drflac_open_with_metadata() +drflac_open() +drflac_close() */ -drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drflac_meta_proc onMeta, void* pUserData); +DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks); @@ -728,43 +1207,50 @@ drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drfl /* Opens a FLAC stream from the given callbacks and fully decodes it in a single operation. The return value is a -pointer to the sample data as interleaved signed 32-bit PCM. The returned data must be freed with DRFLAC_FREE(). +pointer to the sample data as interleaved signed 32-bit PCM. The returned data must be freed with drflac_free(). + +You can pass in custom memory allocation callbacks via the pAllocationCallbacks parameter. This can be NULL in which +case it will use DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. Sometimes a FLAC file won't keep track of the total sample count. In this situation the function will continuously read samples into a dynamically sized buffer on the heap until no samples are left. Do not call this function on a broadcast type of stream (like internet radio streams and whatnot). */ -drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ -drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ -float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); #ifndef DR_FLAC_NO_STDIO /* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a file. */ -drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ -drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_file_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ -float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); #endif /* Same as drflac_open_and_read_pcm_frames_s32() except opens the decoder from a block of memory. */ -drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns signed 16-bit integer samples. */ -drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); /* Same as drflac_open_memory_and_read_pcm_frames_s32(), except returns 32-bit floating-point samples. */ -float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount); +DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks); -/* Frees memory that was allocated internally by dr_flac. */ -void drflac_free(void* p); +/* +Frees memory that was allocated internally by dr_flac. + +Set pAllocationCallbacks to the same object that was passed to drflac_open_*_and_read_pcm_frames_*(). If you originally passed in NULL, pass in NULL for this. +*/ +DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks); /* Structure representing an iterator for vorbis comments in a VORBIS_COMMENT metadata block. */ @@ -778,13 +1264,13 @@ typedef struct Initializes a vorbis comment iterator. This can be used for iterating over the vorbis comments in a VORBIS_COMMENT metadata block. */ -void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments); +DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments); /* Goes to the next vorbis comment in the given iterator. If null is returned it means there are no more comments. The returned string is NOT null terminated. */ -const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut); +DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut); /* Structure representing an iterator for cuesheet tracks in a CUESHEET metadata block. */ @@ -819,27 +1305,12 @@ typedef struct Initializes a cuesheet track iterator. This can be used for iterating over the cuesheet tracks in a CUESHEET metadata block. */ -void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData); +DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData); /* Goes to the next cuesheet track in the given iterator. If DRFLAC_FALSE is returned it means there are no more comments. */ -drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack); +DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack); -/* Deprecated APIs */ -DRFLAC_DEPRECATED drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* pBufferOut); /* Use drflac_read_pcm_frames_s32() instead. */ -DRFLAC_DEPRECATED drflac_uint64 drflac_read_s16(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int16* pBufferOut); /* Use drflac_read_pcm_frames_s16() instead. */ -DRFLAC_DEPRECATED drflac_uint64 drflac_read_f32(drflac* pFlac, drflac_uint64 samplesToRead, float* pBufferOut); /* Use drflac_read_pcm_frames_f32() instead. */ -DRFLAC_DEPRECATED drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex); /* Use drflac_seek_to_pcm_frame() instead. */ -DRFLAC_DEPRECATED drflac_int32* drflac_open_and_decode_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_and_read_pcm_frames_s32(). */ -DRFLAC_DEPRECATED drflac_int16* drflac_open_and_decode_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_and_read_pcm_frames_s16(). */ -DRFLAC_DEPRECATED float* drflac_open_and_decode_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_and_read_pcm_frames_f32(). */ -DRFLAC_DEPRECATED drflac_int32* drflac_open_and_decode_file_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_file_and_read_pcm_frames_s32(). */ -DRFLAC_DEPRECATED drflac_int16* drflac_open_and_decode_file_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_file_and_read_pcm_frames_s16(). */ -DRFLAC_DEPRECATED float* drflac_open_and_decode_file_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_file_and_read_pcm_frames_f32(). */ -DRFLAC_DEPRECATED drflac_int32* drflac_open_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_memory_and_read_pcm_frames_s32(). */ -DRFLAC_DEPRECATED drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_memory_and_read_pcm_frames_s16(). */ -DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalSampleCount); /* Use drflac_open_memory_and_read_pcm_frames_f32(). */ - #ifdef __cplusplus } #endif @@ -853,10 +1324,12 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ -#ifdef DR_FLAC_IMPLEMENTATION +#if defined(DR_FLAC_IMPLEMENTATION) || defined(DRFLAC_IMPLEMENTATION) +#ifndef dr_flac_c +#define dr_flac_c /* Disable some annoying warnings. */ -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #if __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -867,6 +1340,9 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz #ifndef _BSD_SOURCE #define _BSD_SOURCE #endif + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif #ifndef __USE_BSD #define __USE_BSD #endif @@ -877,13 +1353,24 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz #include #ifdef _MSC_VER -#define DRFLAC_INLINE __forceinline + #define DRFLAC_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRFLAC_INLINE __inline__ __attribute__((always_inline)) + #else + #define DRFLAC_INLINE inline __attribute__((always_inline)) + #endif +#elif defined(__WATCOMC__) + #define DRFLAC_INLINE __inline #else -#ifdef __GNUC__ -#define DRFLAC_INLINE __inline__ __attribute__((always_inline)) -#else -#define DRFLAC_INLINE -#endif + #define DRFLAC_INLINE #endif /* CPU architecture. */ @@ -891,11 +1378,19 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) #define DRFLAC_ARM #endif -/* Intrinsics Support */ +/* +Intrinsics Support + +There's a bug in GCC 4.2.x which results in an incorrect compilation error when using _mm_slli_epi32() where it complains with + + "error: shift must be an immediate" + +Unfortuantely dr_flac depends on this for a few things so we're just going to disable SSE on GCC 4.2 and below. +*/ #if !defined(DR_FLAC_NO_SIMD) #if defined(DRFLAC_X64) || defined(DRFLAC_X86) #if defined(_MSC_VER) && !defined(__clang__) @@ -906,7 +1401,7 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) /* 2010 */ #define DRFLAC_SUPPORT_SSE41 #endif - #else + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) /* Assume GNUC-style. */ #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 @@ -971,7 +1466,7 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for supporting different assembly dialects. - + What's basically happening is that we're saving and restoring the ebx register manually. */ #if defined(DRFLAC_X86) && defined(__PIC__) @@ -995,7 +1490,7 @@ DRFLAC_DEPRECATED float* drflac_open_and_decode_memory_f32(const void* data, siz #define DRFLAC_NO_CPUID #endif -static DRFLAC_INLINE drflac_bool32 drflac_has_sse2() +static DRFLAC_INLINE drflac_bool32 drflac_has_sse2(void) { #if defined(DRFLAC_SUPPORT_SSE2) #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE2) @@ -1020,7 +1515,7 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse2() #endif } -static DRFLAC_INLINE drflac_bool32 drflac_has_sse41() +static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void) { #if defined(DRFLAC_SUPPORT_SSE41) #if (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(DRFLAC_NO_SSE41) @@ -1046,29 +1541,33 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41() } -#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) +#if defined(_MSC_VER) && _MSC_VER >= 1500 && (defined(DRFLAC_X86) || defined(DRFLAC_X64)) && !defined(__clang__) #define DRFLAC_HAS_LZCNT_INTRINSIC #elif (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) #define DRFLAC_HAS_LZCNT_INTRINSIC #elif defined(__clang__) - #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) - #define DRFLAC_HAS_LZCNT_INTRINSIC + #if defined(__has_builtin) + #if __has_builtin(__builtin_clzll) || __has_builtin(__builtin_clzl) + #define DRFLAC_HAS_LZCNT_INTRINSIC + #endif #endif #endif -#if defined(_MSC_VER) && _MSC_VER >= 1300 +#if defined(_MSC_VER) && _MSC_VER >= 1400 && !defined(__clang__) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #define DRFLAC_HAS_BYTESWAP32_INTRINSIC #define DRFLAC_HAS_BYTESWAP64_INTRINSIC #elif defined(__clang__) - #if __has_builtin(__builtin_bswap16) - #define DRFLAC_HAS_BYTESWAP16_INTRINSIC - #endif - #if __has_builtin(__builtin_bswap32) - #define DRFLAC_HAS_BYTESWAP32_INTRINSIC - #endif - #if __has_builtin(__builtin_bswap64) - #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #if defined(__has_builtin) + #if __has_builtin(__builtin_bswap16) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap32) + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #endif + #if __has_builtin(__builtin_bswap64) + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + #endif #endif #elif defined(__GNUC__) #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) @@ -1078,6 +1577,27 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41() #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #endif +#elif defined(__WATCOMC__) && defined(__386__) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + extern __inline drflac_uint16 _watcom_bswap16(drflac_uint16); + extern __inline drflac_uint32 _watcom_bswap32(drflac_uint32); + extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64); +#pragma aux _watcom_bswap16 = \ + "xchg al, ah" \ + parm [ax] \ + modify [ax]; +#pragma aux _watcom_bswap32 = \ + "bswap eax" \ + parm [eax] \ + modify [eax]; +#pragma aux _watcom_bswap64 = \ + "bswap eax" \ + "bswap edx" \ + "xchg eax,edx" \ + parm [eax edx] \ + modify [eax edx]; #endif @@ -1101,15 +1621,68 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41() #ifndef DRFLAC_ZERO_MEMORY #define DRFLAC_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) #endif +#ifndef DRFLAC_ZERO_OBJECT +#define DRFLAC_ZERO_OBJECT(p) DRFLAC_ZERO_MEMORY((p), sizeof(*(p))) +#endif #define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */ typedef drflac_int32 drflac_result; -#define DRFLAC_SUCCESS 0 -#define DRFLAC_ERROR -1 /* A generic error. */ +#define DRFLAC_SUCCESS 0 +#define DRFLAC_ERROR -1 /* A generic error. */ #define DRFLAC_INVALID_ARGS -2 -#define DRFLAC_END_OF_STREAM -128 -#define DRFLAC_CRC_MISMATCH -129 +#define DRFLAC_INVALID_OPERATION -3 +#define DRFLAC_OUT_OF_MEMORY -4 +#define DRFLAC_OUT_OF_RANGE -5 +#define DRFLAC_ACCESS_DENIED -6 +#define DRFLAC_DOES_NOT_EXIST -7 +#define DRFLAC_ALREADY_EXISTS -8 +#define DRFLAC_TOO_MANY_OPEN_FILES -9 +#define DRFLAC_INVALID_FILE -10 +#define DRFLAC_TOO_BIG -11 +#define DRFLAC_PATH_TOO_LONG -12 +#define DRFLAC_NAME_TOO_LONG -13 +#define DRFLAC_NOT_DIRECTORY -14 +#define DRFLAC_IS_DIRECTORY -15 +#define DRFLAC_DIRECTORY_NOT_EMPTY -16 +#define DRFLAC_END_OF_FILE -17 +#define DRFLAC_NO_SPACE -18 +#define DRFLAC_BUSY -19 +#define DRFLAC_IO_ERROR -20 +#define DRFLAC_INTERRUPT -21 +#define DRFLAC_UNAVAILABLE -22 +#define DRFLAC_ALREADY_IN_USE -23 +#define DRFLAC_BAD_ADDRESS -24 +#define DRFLAC_BAD_SEEK -25 +#define DRFLAC_BAD_PIPE -26 +#define DRFLAC_DEADLOCK -27 +#define DRFLAC_TOO_MANY_LINKS -28 +#define DRFLAC_NOT_IMPLEMENTED -29 +#define DRFLAC_NO_MESSAGE -30 +#define DRFLAC_BAD_MESSAGE -31 +#define DRFLAC_NO_DATA_AVAILABLE -32 +#define DRFLAC_INVALID_DATA -33 +#define DRFLAC_TIMEOUT -34 +#define DRFLAC_NO_NETWORK -35 +#define DRFLAC_NOT_UNIQUE -36 +#define DRFLAC_NOT_SOCKET -37 +#define DRFLAC_NO_ADDRESS -38 +#define DRFLAC_BAD_PROTOCOL -39 +#define DRFLAC_PROTOCOL_UNAVAILABLE -40 +#define DRFLAC_PROTOCOL_NOT_SUPPORTED -41 +#define DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRFLAC_SOCKET_NOT_SUPPORTED -44 +#define DRFLAC_CONNECTION_RESET -45 +#define DRFLAC_ALREADY_CONNECTED -46 +#define DRFLAC_NOT_CONNECTED -47 +#define DRFLAC_CONNECTION_REFUSED -48 +#define DRFLAC_NO_HOST -49 +#define DRFLAC_IN_PROGRESS -50 +#define DRFLAC_CANCELLED -51 +#define DRFLAC_MEMORY_ALREADY_MAPPED -52 +#define DRFLAC_AT_END -53 +#define DRFLAC_CRC_MISMATCH -128 #define DRFLAC_SUBFRAME_CONSTANT 0 #define DRFLAC_SUBFRAME_VERBATIM 1 @@ -1125,28 +1698,31 @@ typedef drflac_int32 drflac_result; #define DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE 9 #define DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE 10 -/* -Keeps track of the number of leading samples for each sub-frame. This is required because the SSE pipeline will occasionally -reference excess prior samples. -*/ -#define DRFLAC_LEADING_SAMPLES 32 - - #define drflac_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) -#define drflac_assert DRFLAC_ASSERT -#define drflac_copy_memory DRFLAC_COPY_MEMORY -#define drflac_zero_memory DRFLAC_ZERO_MEMORY + + +DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRFLAC_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRFLAC_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRFLAC_VERSION_REVISION; + } +} + +DRFLAC_API const char* drflac_version_string(void) +{ + return DRFLAC_VERSION_STRING; +} /* CPU caps. */ -static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; -#ifndef DRFLAC_NO_CPUID -/* -I've had a bug report that Clang's ThreadSanitizer presents a warning in this function. Having reviewed this, this does -actually make sense. However, since CPU caps should never differ for a running process, I don't think the trade off of -complicating internal API's by passing around CPU caps versus just disabling the warnings is worthwhile. I'm therefore -just going to disable these warnings. -*/ #if defined(__has_feature) #if __has_feature(thread_sanitizer) #define DRFLAC_NO_THREAD_SANITIZE __attribute__((no_sanitize("thread"))) @@ -1156,18 +1732,32 @@ just going to disable these warnings. #else #define DRFLAC_NO_THREAD_SANITIZE #endif + +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) +static drflac_bool32 drflac__gIsLZCNTSupported = DRFLAC_FALSE; +#endif + +#ifndef DRFLAC_NO_CPUID static drflac_bool32 drflac__gIsSSE2Supported = DRFLAC_FALSE; static drflac_bool32 drflac__gIsSSE41Supported = DRFLAC_FALSE; -DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps() + +/* +I've had a bug report that Clang's ThreadSanitizer presents a warning in this function. Having reviewed this, this does +actually make sense. However, since CPU caps should never differ for a running process, I don't think the trade off of +complicating internal API's by passing around CPU caps versus just disabling the warnings is worthwhile. I'm therefore +just going to disable these warnings. This is disabled via the DRFLAC_NO_THREAD_SANITIZE attribute. +*/ +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) { static drflac_bool32 isCPUCapsInitialized = DRFLAC_FALSE; if (!isCPUCapsInitialized) { - int info[4] = {0}; - /* LZCNT */ +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) + int info[4] = {0}; drflac__cpuid(info, 0x80000001); drflac__gIsLZCNTSupported = (info[2] & (1 << 5)) != 0; +#endif /* SSE2 */ drflac__gIsSSE2Supported = drflac_has_sse2(); @@ -1179,11 +1769,40 @@ DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps() isCPUCapsInitialized = DRFLAC_TRUE; } } +#else +static drflac_bool32 drflac__gIsNEONSupported = DRFLAC_FALSE; + +static DRFLAC_INLINE drflac_bool32 drflac__has_neon(void) +{ +#if defined(DRFLAC_SUPPORT_NEON) + #if defined(DRFLAC_ARM) && !defined(DRFLAC_NO_NEON) + #if (defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)) + return DRFLAC_TRUE; /* If the compiler is allowed to freely generate NEON code we can assume support. */ + #else + /* TODO: Runtime check. */ + return DRFLAC_FALSE; + #endif + #else + return DRFLAC_FALSE; /* NEON is only supported on ARM architectures. */ + #endif +#else + return DRFLAC_FALSE; /* No compiler support. */ +#endif +} + +DRFLAC_NO_THREAD_SANITIZE static void drflac__init_cpu_caps(void) +{ + drflac__gIsNEONSupported = drflac__has_neon(); + +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) + drflac__gIsLZCNTSupported = DRFLAC_TRUE; +#endif +} #endif /* Endian Management */ -static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian() +static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian(void) { #if defined(DRFLAC_X86) || defined(DRFLAC_X64) return DRFLAC_TRUE; @@ -1198,10 +1817,12 @@ static DRFLAC_INLINE drflac_bool32 drflac__is_little_endian() static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) { #ifdef DRFLAC_HAS_BYTESWAP16_INTRINSIC - #if defined(_MSC_VER) + #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_ushort(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap16(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap16(n); #else #error "This compiler does not support the byte swap intrinsic." #endif @@ -1214,10 +1835,25 @@ static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) { #ifdef DRFLAC_HAS_BYTESWAP32_INTRINSIC - #if defined(_MSC_VER) + #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_ulong(n); #elif defined(__GNUC__) || defined(__clang__) - return __builtin_bswap32(n); + #if defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRFLAC_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */ + /* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */ + drflac_uint32 r; + __asm__ __volatile__ ( + #if defined(DRFLAC_64BIT) + "rev %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(n) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "rev %[out], %[in]" : [out]"=r"(r) : [in]"r"(n) + #endif + ); + return r; + #else + return __builtin_bswap32(n); + #endif + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap32(n); #else #error "This compiler does not support the byte swap intrinsic." #endif @@ -1232,22 +1868,25 @@ static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) { #ifdef DRFLAC_HAS_BYTESWAP64_INTRINSIC - #if defined(_MSC_VER) + #if defined(_MSC_VER) && !defined(__clang__) return _byteswap_uint64(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap64(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap64(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drflac_uint64)0xFF00000000000000) >> 56) | - ((n & (drflac_uint64)0x00FF000000000000) >> 40) | - ((n & (drflac_uint64)0x0000FF0000000000) >> 24) | - ((n & (drflac_uint64)0x000000FF00000000) >> 8) | - ((n & (drflac_uint64)0x00000000FF000000) << 8) | - ((n & (drflac_uint64)0x0000000000FF0000) << 24) | - ((n & (drflac_uint64)0x000000000000FF00) << 40) | - ((n & (drflac_uint64)0x00000000000000FF) << 56); + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } @@ -1391,8 +2030,8 @@ static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 da static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; - - drflac_assert(count <= 32); + + DRFLAC_ASSERT(count <= 32); wholeBytes = count >> 3; leftoverBits = count - (wholeBytes*8); @@ -1403,7 +2042,7 @@ static DRFLAC_INLINE drflac_uint8 drflac_crc8(drflac_uint8 crc, drflac_uint32 da case 3: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x00FF0000UL << leftoverBits)) >> (16 + leftoverBits))); case 2: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x0000FF00UL << leftoverBits)) >> ( 8 + leftoverBits))); case 1: crc = drflac_crc8_byte(crc, (drflac_uint8)((data & (0x000000FFUL << leftoverBits)) >> ( 0 + leftoverBits))); - case 0: if (leftoverBits > 0) crc = (crc << leftoverBits) ^ drflac__crc8_table[(crc >> (8 - leftoverBits)) ^ (data & leftoverDataMask)]; + case 0: if (leftoverBits > 0) crc = (drflac_uint8)((crc << leftoverBits) ^ drflac__crc8_table[(crc >> (8 - leftoverBits)) ^ (data & leftoverDataMask)]); } return crc; #endif @@ -1415,6 +2054,22 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16_byte(drflac_uint16 crc, drflac_u return (crc << 8) ^ drflac__crc16_table[(drflac_uint8)(crc >> 8) ^ data]; } +static DRFLAC_INLINE drflac_uint16 drflac_crc16_cache(drflac_uint16 crc, drflac_cache_t data) +{ +#ifdef DRFLAC_64BIT + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 56) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 48) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 40) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 32) & 0xFF)); +#endif + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 24) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 16) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 8) & 0xFF)); + crc = drflac_crc16_byte(crc, (drflac_uint8)((data >> 0) & 0xFF)); + + return crc; +} + static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_cache_t data, drflac_uint32 byteCount) { switch (byteCount) @@ -1434,6 +2089,7 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16_bytes(drflac_uint16 crc, drflac_ return crc; } +#if 0 static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac_uint32 data, drflac_uint32 count) { #ifdef DR_FLAC_NO_CRC @@ -1464,10 +2120,10 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16__32bit(drflac_uint16 crc, drflac 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; - drflac_assert(count <= 64); - + DRFLAC_ASSERT(count <= 64); + wholeBytes = count >> 3; - leftoverBits = count - (wholeBytes*8); + leftoverBits = count & 7; leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { @@ -1498,11 +2154,11 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16__64bit(drflac_uint16 crc, drflac static drflac_uint64 leftoverDataMaskTable[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F }; - - drflac_assert(count <= 64); + + DRFLAC_ASSERT(count <= 64); wholeBytes = count >> 3; - leftoverBits = count - (wholeBytes*8); + leftoverBits = count & 7; leftoverDataMask = leftoverDataMaskTable[leftoverBits]; switch (wholeBytes) { @@ -1530,6 +2186,7 @@ static DRFLAC_INLINE drflac_uint16 drflac_crc16(drflac_uint16 crc, drflac_cache_ return drflac_crc16__32bit(crc, data, count); #endif } +#endif #ifdef DRFLAC_64BIT @@ -1569,14 +2226,18 @@ static DRFLAC_INLINE void drflac__reset_crc16(drflac_bs* bs) static DRFLAC_INLINE void drflac__update_crc16(drflac_bs* bs) { - bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache, DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bs->crc16CacheIgnoredBytes); - bs->crc16CacheIgnoredBytes = 0; + if (bs->crc16CacheIgnoredBytes == 0) { + bs->crc16 = drflac_crc16_cache(bs->crc16, bs->crc16Cache); + } else { + bs->crc16 = drflac_crc16_bytes(bs->crc16, bs->crc16Cache, DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bs->crc16CacheIgnoredBytes); + bs->crc16CacheIgnoredBytes = 0; + } } static DRFLAC_INLINE drflac_uint16 drflac__flush_crc16(drflac_bs* bs) { /* We should never be flushing in a situation where we are not aligned on a byte boundary. */ - drflac_assert((DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7) == 0); + DRFLAC_ASSERT((DRFLAC_CACHE_L1_BITS_REMAINING(bs) & 7) == 0); /* The bits that were read from the L1 cache need to be accumulated. The number of bytes needing to be accumulated is determined @@ -1689,7 +2350,7 @@ static drflac_bool32 drflac__reload_cache(drflac_bs* bs) return DRFLAC_FALSE; } - drflac_assert(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES(bs)); + DRFLAC_ASSERT(bytesRead < DRFLAC_CACHE_L1_SIZE_BYTES(bs)); bs->consumedBits = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BYTES(bs) - bytesRead) * 8; bs->cache = drflac__be2host__cache_line(bs->unalignedCache); @@ -1720,10 +2381,10 @@ static void drflac__reset_cache(drflac_bs* bs) static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned int bitCount, drflac_uint32* pResultOut) { - drflac_assert(bs != NULL); - drflac_assert(pResultOut != NULL); - drflac_assert(bitCount > 0); - drflac_assert(bitCount <= 32); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResultOut != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 32); if (bs->consumedBits == DRFLAC_CACHE_L1_SIZE_BITS(bs)) { if (!drflac__reload_cache(bs)) { @@ -1759,7 +2420,11 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i /* It straddles the cached data. It will never cover more than the next chunk. We just read the number in two parts and combine them. */ drflac_uint32 bitCountHi = DRFLAC_CACHE_L1_BITS_REMAINING(bs); drflac_uint32 bitCountLo = bitCount - bitCountHi; - drflac_uint32 resultHi = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountHi); + drflac_uint32 resultHi; + + DRFLAC_ASSERT(bitCountHi > 0); + DRFLAC_ASSERT(bitCountHi < 32); + resultHi = (drflac_uint32)DRFLAC_CACHE_L1_SELECT_AND_SHIFT(bs, bitCountHi); if (!drflac__reload_cache(bs)) { return DRFLAC_FALSE; @@ -1775,19 +2440,22 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_uint32(drflac_bs* bs, unsigned i static drflac_bool32 drflac__read_int32(drflac_bs* bs, unsigned int bitCount, drflac_int32* pResult) { drflac_uint32 result; - drflac_uint32 signbit; - drflac_assert(bs != NULL); - drflac_assert(pResult != NULL); - drflac_assert(bitCount > 0); - drflac_assert(bitCount <= 32); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 32); if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; } - signbit = ((result >> (bitCount-1)) & 0x01); - result |= (~signbit + 1) << bitCount; + /* Do not attempt to shift by 32 as it's undefined. */ + if (bitCount < 32) { + drflac_uint32 signbit; + signbit = ((result >> (bitCount-1)) & 0x01); + result |= (~signbit + 1) << bitCount; + } *pResult = (drflac_int32)result; return DRFLAC_TRUE; @@ -1799,8 +2467,8 @@ static drflac_bool32 drflac__read_uint64(drflac_bs* bs, unsigned int bitCount, d drflac_uint32 resultHi; drflac_uint32 resultLo; - drflac_assert(bitCount <= 64); - drflac_assert(bitCount > 32); + DRFLAC_ASSERT(bitCount <= 64); + DRFLAC_ASSERT(bitCount > 32); if (!drflac__read_uint32(bs, bitCount - 32, &resultHi)) { return DRFLAC_FALSE; @@ -1822,7 +2490,7 @@ static drflac_bool32 drflac__read_int64(drflac_bs* bs, unsigned int bitCount, dr drflac_uint64 result; drflac_uint64 signbit; - drflac_assert(bitCount <= 64); + DRFLAC_ASSERT(bitCount <= 64); if (!drflac__read_uint64(bs, bitCount, &result)) { return DRFLAC_FALSE; @@ -1840,10 +2508,10 @@ static drflac_bool32 drflac__read_uint16(drflac_bs* bs, unsigned int bitCount, d { drflac_uint32 result; - drflac_assert(bs != NULL); - drflac_assert(pResult != NULL); - drflac_assert(bitCount > 0); - drflac_assert(bitCount <= 16); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 16); if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; @@ -1858,10 +2526,10 @@ static drflac_bool32 drflac__read_int16(drflac_bs* bs, unsigned int bitCount, dr { drflac_int32 result; - drflac_assert(bs != NULL); - drflac_assert(pResult != NULL); - drflac_assert(bitCount > 0); - drflac_assert(bitCount <= 16); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 16); if (!drflac__read_int32(bs, bitCount, &result)) { return DRFLAC_FALSE; @@ -1876,10 +2544,10 @@ static drflac_bool32 drflac__read_uint8(drflac_bs* bs, unsigned int bitCount, dr { drflac_uint32 result; - drflac_assert(bs != NULL); - drflac_assert(pResult != NULL); - drflac_assert(bitCount > 0); - drflac_assert(bitCount <= 8); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 8); if (!drflac__read_uint32(bs, bitCount, &result)) { return DRFLAC_FALSE; @@ -1893,10 +2561,10 @@ static drflac_bool32 drflac__read_int8(drflac_bs* bs, unsigned int bitCount, drf { drflac_int32 result; - drflac_assert(bs != NULL); - drflac_assert(pResult != NULL); - drflac_assert(bitCount > 0); - drflac_assert(bitCount <= 8); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pResult != NULL); + DRFLAC_ASSERT(bitCount > 0); + DRFLAC_ASSERT(bitCount <= 8); if (!drflac__read_int32(bs, bitCount, &result)) { return DRFLAC_FALSE; @@ -1956,7 +2624,7 @@ static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) bitsToSeek = 0; /* <-- Necessary for the assert below. */ } - drflac_assert(bitsToSeek == 0); + DRFLAC_ASSERT(bitsToSeek == 0); return DRFLAC_TRUE; } } @@ -1965,7 +2633,7 @@ static drflac_bool32 drflac__seek_bits(drflac_bs* bs, size_t bitsToSeek) /* This function moves the bit streamer to the first bit after the sync code (bit 15 of the of the frame header). It will also update the CRC-16. */ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) { - drflac_assert(bs != NULL); + DRFLAC_ASSERT(bs != NULL); /* The sync code is always aligned to 8 bits. This is convenient for us because it means we can do byte-aligned movements. The first @@ -2007,12 +2675,15 @@ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) } -#if !defined(DR_FLAC_NO_SIMD) && defined(DRFLAC_HAS_LZCNT_INTRINSIC) +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) #define DRFLAC_IMPLEMENT_CLZ_LZCNT #endif -#if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) +#if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(__clang__) #define DRFLAC_IMPLEMENT_CLZ_MSVC #endif +#if defined(__WATCOMC__) && defined(__386__) +#define DRFLAC_IMPLEMENT_CLZ_WATCOM +#endif static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) { @@ -2048,19 +2719,42 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) } #ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT -static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported() +static DRFLAC_INLINE drflac_bool32 drflac__is_lzcnt_supported(void) { - /* If the compiler itself does not support the intrinsic then we'll need to return false. */ -#ifdef DRFLAC_HAS_LZCNT_INTRINSIC - return drflac__gIsLZCNTSupported; + /* Fast compile time check for ARM. */ +#if defined(DRFLAC_HAS_LZCNT_INTRINSIC) && defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) + return DRFLAC_TRUE; #else - return DRFLAC_FALSE; + /* If the compiler itself does not support the intrinsic then we'll need to return false. */ + #ifdef DRFLAC_HAS_LZCNT_INTRINSIC + return drflac__gIsLZCNTSupported; + #else + return DRFLAC_FALSE; + #endif #endif } static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) { -#if defined(_MSC_VER) && !defined(__clang__) + /* + It's critical for competitive decoding performance that this function be highly optimal. With MSVC we can use the __lzcnt64() and __lzcnt() intrinsics + to achieve good performance, however on GCC and Clang it's a little bit more annoying. The __builtin_clzl() and __builtin_clzll() intrinsics leave + it undefined as to the return value when `x` is 0. We need this to be well defined as returning 32 or 64, depending on whether or not it's a 32- or + 64-bit build. To work around this we would need to add a conditional to check for the x = 0 case, but this creates unnecessary inefficiency. To work + around this problem I have written some inline assembly to emit the LZCNT (x86) or CLZ (ARM) instruction directly which removes the need to include + the conditional. This has worked well in the past, but for some reason Clang's MSVC compatible driver, clang-cl, does not seem to be handling this + in the same way as the normal Clang driver. It seems that `clang-cl` is just outputting the wrong results sometimes, maybe due to some register + getting clobbered? + + I'm not sure if this is a bug with dr_flac's inlined assembly (most likely), a bug in `clang-cl` or just a misunderstanding on my part with inline + assembly rules for `clang-cl`. If somebody can identify an error in dr_flac's inlined assembly I'm happy to get that fixed. + + Fortunately there is an easy workaround for this. Clang implements MSVC-specific intrinsics for compatibility. It also defines _MSC_VER for extra + compatibility. We can therefore just check for _MSC_VER and use the MSVC intrinsic which, fortunately for us, Clang supports. It would still be nice + to know how to fix the inlined assembly for correctness sake, however. + */ + +#if defined(_MSC_VER) /*&& !defined(__clang__)*/ /* <-- Intentionally wanting Clang to use the MSVC __lzcnt64/__lzcnt intrinsics due to above ^. */ #ifdef DRFLAC_64BIT return (drflac_uint32)__lzcnt64(x); #else @@ -2068,13 +2762,46 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x) #endif #else #if defined(__GNUC__) || defined(__clang__) - if (x == 0) { - return sizeof(x)*8; - } - #ifdef DRFLAC_64BIT - return (drflac_uint32)__builtin_clzll((drflac_uint64)x); + #if defined(DRFLAC_X64) + { + drflac_uint64 r; + __asm__ __volatile__ ( + "lzcnt{ %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + ); + + return (drflac_uint32)r; + } + #elif defined(DRFLAC_X86) + { + drflac_uint32 r; + __asm__ __volatile__ ( + "lzcnt{l %1, %0| %0, %1}" : "=r"(r) : "r"(x) : "cc" + ); + + return r; + } + #elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */ + { + unsigned int r; + __asm__ __volatile__ ( + #if defined(DRFLAC_64BIT) + "clz %w[out], %w[in]" : [out]"=r"(r) : [in]"r"(x) /* <-- This is untested. If someone in the community could test this, that would be appreciated! */ + #else + "clz %[out], %[in]" : [out]"=r"(r) : [in]"r"(x) + #endif + ); + + return r; + } #else - return (drflac_uint32)__builtin_clzl((drflac_uint32)x); + if (x == 0) { + return sizeof(x)*8; + } + #ifdef DRFLAC_64BIT + return (drflac_uint32)__builtin_clzll((drflac_uint64)x); + #else + return (drflac_uint32)__builtin_clzl((drflac_uint32)x); + #endif #endif #else /* Unsupported compiler. */ @@ -2104,6 +2831,16 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) } #endif +#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM +static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32); +#pragma aux drflac__clz_watcom = \ + "bsr eax, eax" \ + "xor eax, 31" \ + parm [eax] nomemory \ + value [eax] \ + modify exact [eax] nomemory; +#endif + static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) { #ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT @@ -2114,6 +2851,8 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) { #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC return drflac__clz_msvc(x); +#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM) + return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x); #else return drflac__clz_software(x); #endif @@ -2147,8 +2886,8 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_past_next_set_bit(drflac_bs* bs, static drflac_bool32 drflac__seek_to_byte(drflac_bs* bs, drflac_uint64 offsetFromStart) { - drflac_assert(bs != NULL); - drflac_assert(offsetFromStart > 0); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(offsetFromStart > 0); /* Seeking from the start is not quite as trivial as it sounds because the onSeek callback takes a signed 32-bit integer (which @@ -2190,19 +2929,19 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 { drflac_uint8 crc; drflac_uint64 result; - unsigned char utf8[7] = {0}; + drflac_uint8 utf8[7] = {0}; int byteCount; int i; - drflac_assert(bs != NULL); - drflac_assert(pNumberOut != NULL); - drflac_assert(pCRCOut != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pNumberOut != NULL); + DRFLAC_ASSERT(pCRCOut != NULL); crc = *pCRCOut; if (!drflac__read_uint8(bs, 8, utf8)) { *pNumberOut = 0; - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } crc = drflac_crc8(crc, utf8[0], 8); @@ -2212,7 +2951,7 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 return DRFLAC_SUCCESS; } - byteCount = 1; + /*byteCount = 1;*/ if ((utf8[0] & 0xE0) == 0xC0) { byteCount = 2; } else if ((utf8[0] & 0xF0) == 0xE0) { @@ -2231,13 +2970,13 @@ static drflac_result drflac__read_utf8_coded_number(drflac_bs* bs, drflac_uint64 } /* Read extra bytes. */ - drflac_assert(byteCount > 1); + DRFLAC_ASSERT(byteCount > 1); result = (drflac_uint64)(utf8[0] & (0xFF >> (byteCount + 1))); for (i = 1; i < byteCount; ++i) { if (!drflac__read_uint8(bs, 8, utf8 + i)) { *pNumberOut = 0; - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } crc = drflac_crc8(crc, utf8[i], 8); @@ -2261,7 +3000,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_32(drflac_uint32 { drflac_int32 prediction = 0; - drflac_assert(order <= 32); + DRFLAC_ASSERT(order <= 32); /* 32-bit version. */ @@ -2309,7 +3048,7 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 { drflac_int64 prediction; - drflac_assert(order <= 32); + DRFLAC_ASSERT(order <= 32); /* 64-bit version. */ @@ -2486,436 +3225,6 @@ static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64(drflac_uint32 return (drflac_int32)(prediction >> shift); } -static DRFLAC_INLINE void drflac__calculate_prediction_64_x4(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, const drflac_uint32 riceParamParts[4], drflac_int32* pDecodedSamples) -{ - drflac_int64 prediction0 = 0; - drflac_int64 prediction1 = 0; - drflac_int64 prediction2 = 0; - drflac_int64 prediction3 = 0; - - drflac_assert(order <= 32); - - switch (order) - { - case 32: - prediction0 += coefficients[31] * (drflac_int64)pDecodedSamples[-32]; - prediction1 += coefficients[31] * (drflac_int64)pDecodedSamples[-31]; - prediction2 += coefficients[31] * (drflac_int64)pDecodedSamples[-30]; - prediction3 += coefficients[31] * (drflac_int64)pDecodedSamples[-29]; - case 31: - prediction0 += coefficients[30] * (drflac_int64)pDecodedSamples[-31]; - prediction1 += coefficients[30] * (drflac_int64)pDecodedSamples[-30]; - prediction2 += coefficients[30] * (drflac_int64)pDecodedSamples[-29]; - prediction3 += coefficients[30] * (drflac_int64)pDecodedSamples[-28]; - case 30: - prediction0 += coefficients[29] * (drflac_int64)pDecodedSamples[-30]; - prediction1 += coefficients[29] * (drflac_int64)pDecodedSamples[-29]; - prediction2 += coefficients[29] * (drflac_int64)pDecodedSamples[-28]; - prediction3 += coefficients[29] * (drflac_int64)pDecodedSamples[-27]; - case 29: - prediction0 += coefficients[28] * (drflac_int64)pDecodedSamples[-29]; - prediction1 += coefficients[28] * (drflac_int64)pDecodedSamples[-28]; - prediction2 += coefficients[28] * (drflac_int64)pDecodedSamples[-27]; - prediction3 += coefficients[28] * (drflac_int64)pDecodedSamples[-26]; - case 28: - prediction0 += coefficients[27] * (drflac_int64)pDecodedSamples[-28]; - prediction1 += coefficients[27] * (drflac_int64)pDecodedSamples[-27]; - prediction2 += coefficients[27] * (drflac_int64)pDecodedSamples[-26]; - prediction3 += coefficients[27] * (drflac_int64)pDecodedSamples[-25]; - case 27: - prediction0 += coefficients[26] * (drflac_int64)pDecodedSamples[-27]; - prediction1 += coefficients[26] * (drflac_int64)pDecodedSamples[-26]; - prediction2 += coefficients[26] * (drflac_int64)pDecodedSamples[-25]; - prediction3 += coefficients[26] * (drflac_int64)pDecodedSamples[-24]; - case 26: - prediction0 += coefficients[25] * (drflac_int64)pDecodedSamples[-26]; - prediction1 += coefficients[25] * (drflac_int64)pDecodedSamples[-25]; - prediction2 += coefficients[25] * (drflac_int64)pDecodedSamples[-24]; - prediction3 += coefficients[25] * (drflac_int64)pDecodedSamples[-23]; - case 25: - prediction0 += coefficients[24] * (drflac_int64)pDecodedSamples[-25]; - prediction1 += coefficients[24] * (drflac_int64)pDecodedSamples[-24]; - prediction2 += coefficients[24] * (drflac_int64)pDecodedSamples[-23]; - prediction3 += coefficients[24] * (drflac_int64)pDecodedSamples[-22]; - case 24: - prediction0 += coefficients[23] * (drflac_int64)pDecodedSamples[-24]; - prediction1 += coefficients[23] * (drflac_int64)pDecodedSamples[-23]; - prediction2 += coefficients[23] * (drflac_int64)pDecodedSamples[-22]; - prediction3 += coefficients[23] * (drflac_int64)pDecodedSamples[-21]; - case 23: - prediction0 += coefficients[22] * (drflac_int64)pDecodedSamples[-23]; - prediction1 += coefficients[22] * (drflac_int64)pDecodedSamples[-22]; - prediction2 += coefficients[22] * (drflac_int64)pDecodedSamples[-21]; - prediction3 += coefficients[22] * (drflac_int64)pDecodedSamples[-20]; - case 22: - prediction0 += coefficients[21] * (drflac_int64)pDecodedSamples[-22]; - prediction1 += coefficients[21] * (drflac_int64)pDecodedSamples[-21]; - prediction2 += coefficients[21] * (drflac_int64)pDecodedSamples[-20]; - prediction3 += coefficients[21] * (drflac_int64)pDecodedSamples[-19]; - case 21: - prediction0 += coefficients[20] * (drflac_int64)pDecodedSamples[-21]; - prediction1 += coefficients[20] * (drflac_int64)pDecodedSamples[-20]; - prediction2 += coefficients[20] * (drflac_int64)pDecodedSamples[-19]; - prediction3 += coefficients[20] * (drflac_int64)pDecodedSamples[-18]; - case 20: - prediction0 += coefficients[19] * (drflac_int64)pDecodedSamples[-20]; - prediction1 += coefficients[19] * (drflac_int64)pDecodedSamples[-19]; - prediction2 += coefficients[19] * (drflac_int64)pDecodedSamples[-18]; - prediction3 += coefficients[19] * (drflac_int64)pDecodedSamples[-17]; - case 19: - prediction0 += coefficients[18] * (drflac_int64)pDecodedSamples[-19]; - prediction1 += coefficients[18] * (drflac_int64)pDecodedSamples[-18]; - prediction2 += coefficients[18] * (drflac_int64)pDecodedSamples[-17]; - prediction3 += coefficients[18] * (drflac_int64)pDecodedSamples[-16]; - case 18: - prediction0 += coefficients[17] * (drflac_int64)pDecodedSamples[-18]; - prediction1 += coefficients[17] * (drflac_int64)pDecodedSamples[-17]; - prediction2 += coefficients[17] * (drflac_int64)pDecodedSamples[-16]; - prediction3 += coefficients[17] * (drflac_int64)pDecodedSamples[-15]; - case 17: - prediction0 += coefficients[16] * (drflac_int64)pDecodedSamples[-17]; - prediction1 += coefficients[16] * (drflac_int64)pDecodedSamples[-16]; - prediction2 += coefficients[16] * (drflac_int64)pDecodedSamples[-15]; - prediction3 += coefficients[16] * (drflac_int64)pDecodedSamples[-14]; - - case 16: - prediction0 += coefficients[15] * (drflac_int64)pDecodedSamples[-16]; - prediction1 += coefficients[15] * (drflac_int64)pDecodedSamples[-15]; - prediction2 += coefficients[15] * (drflac_int64)pDecodedSamples[-14]; - prediction3 += coefficients[15] * (drflac_int64)pDecodedSamples[-13]; - case 15: - prediction0 += coefficients[14] * (drflac_int64)pDecodedSamples[-15]; - prediction1 += coefficients[14] * (drflac_int64)pDecodedSamples[-14]; - prediction2 += coefficients[14] * (drflac_int64)pDecodedSamples[-13]; - prediction3 += coefficients[14] * (drflac_int64)pDecodedSamples[-12]; - case 14: - prediction0 += coefficients[13] * (drflac_int64)pDecodedSamples[-14]; - prediction1 += coefficients[13] * (drflac_int64)pDecodedSamples[-13]; - prediction2 += coefficients[13] * (drflac_int64)pDecodedSamples[-12]; - prediction3 += coefficients[13] * (drflac_int64)pDecodedSamples[-11]; - case 13: - prediction0 += coefficients[12] * (drflac_int64)pDecodedSamples[-13]; - prediction1 += coefficients[12] * (drflac_int64)pDecodedSamples[-12]; - prediction2 += coefficients[12] * (drflac_int64)pDecodedSamples[-11]; - prediction3 += coefficients[12] * (drflac_int64)pDecodedSamples[-10]; - case 12: - prediction0 += coefficients[11] * (drflac_int64)pDecodedSamples[-12]; - prediction1 += coefficients[11] * (drflac_int64)pDecodedSamples[-11]; - prediction2 += coefficients[11] * (drflac_int64)pDecodedSamples[-10]; - prediction3 += coefficients[11] * (drflac_int64)pDecodedSamples[- 9]; - case 11: - prediction0 += coefficients[10] * (drflac_int64)pDecodedSamples[-11]; - prediction1 += coefficients[10] * (drflac_int64)pDecodedSamples[-10]; - prediction2 += coefficients[10] * (drflac_int64)pDecodedSamples[- 9]; - prediction3 += coefficients[10] * (drflac_int64)pDecodedSamples[- 8]; - case 10: - prediction0 += coefficients[9] * (drflac_int64)pDecodedSamples[-10]; - prediction1 += coefficients[9] * (drflac_int64)pDecodedSamples[- 9]; - prediction2 += coefficients[9] * (drflac_int64)pDecodedSamples[- 8]; - prediction3 += coefficients[9] * (drflac_int64)pDecodedSamples[- 7]; - case 9: - prediction0 += coefficients[8] * (drflac_int64)pDecodedSamples[- 9]; - prediction1 += coefficients[8] * (drflac_int64)pDecodedSamples[- 8]; - prediction2 += coefficients[8] * (drflac_int64)pDecodedSamples[- 7]; - prediction3 += coefficients[8] * (drflac_int64)pDecodedSamples[- 6]; - case 8: - prediction0 += coefficients[7] * (drflac_int64)pDecodedSamples[- 8]; - prediction1 += coefficients[7] * (drflac_int64)pDecodedSamples[- 7]; - prediction2 += coefficients[7] * (drflac_int64)pDecodedSamples[- 6]; - prediction3 += coefficients[7] * (drflac_int64)pDecodedSamples[- 5]; - case 7: - prediction0 += coefficients[6] * (drflac_int64)pDecodedSamples[- 7]; - prediction1 += coefficients[6] * (drflac_int64)pDecodedSamples[- 6]; - prediction2 += coefficients[6] * (drflac_int64)pDecodedSamples[- 5]; - prediction3 += coefficients[6] * (drflac_int64)pDecodedSamples[- 4]; - case 6: - prediction0 += coefficients[5] * (drflac_int64)pDecodedSamples[- 6]; - prediction1 += coefficients[5] * (drflac_int64)pDecodedSamples[- 5]; - prediction2 += coefficients[5] * (drflac_int64)pDecodedSamples[- 4]; - prediction3 += coefficients[5] * (drflac_int64)pDecodedSamples[- 3]; - case 5: - prediction0 += coefficients[4] * (drflac_int64)pDecodedSamples[- 5]; - prediction1 += coefficients[4] * (drflac_int64)pDecodedSamples[- 4]; - prediction2 += coefficients[4] * (drflac_int64)pDecodedSamples[- 3]; - prediction3 += coefficients[4] * (drflac_int64)pDecodedSamples[- 2]; - case 4: - prediction0 += coefficients[3] * (drflac_int64)pDecodedSamples[- 4]; - prediction1 += coefficients[3] * (drflac_int64)pDecodedSamples[- 3]; - prediction2 += coefficients[3] * (drflac_int64)pDecodedSamples[- 2]; - prediction3 += coefficients[3] * (drflac_int64)pDecodedSamples[- 1]; - order = 3; - } - - switch (order) - { - case 3: prediction0 += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 3]; - case 2: prediction0 += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 2]; - case 1: prediction0 += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; - } - pDecodedSamples[0] = riceParamParts[0] + (drflac_int32)(prediction0 >> shift); - - switch (order) - { - case 3: prediction1 += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 2]; - case 2: prediction1 += coefficients[ 1] * (drflac_int64)pDecodedSamples[- 1]; - case 1: prediction1 += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 0]; - } - pDecodedSamples[1] = riceParamParts[1] + (drflac_int32)(prediction1 >> shift); - - switch (order) - { - case 3: prediction2 += coefficients[ 2] * (drflac_int64)pDecodedSamples[- 1]; - case 2: prediction2 += coefficients[ 1] * (drflac_int64)pDecodedSamples[ 0]; - case 1: prediction2 += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 1]; - } - pDecodedSamples[2] = riceParamParts[2] + (drflac_int32)(prediction2 >> shift); - - switch (order) - { - case 3: prediction3 += coefficients[ 2] * (drflac_int64)pDecodedSamples[ 0]; - case 2: prediction3 += coefficients[ 1] * (drflac_int64)pDecodedSamples[ 1]; - case 1: prediction3 += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 2]; - } - pDecodedSamples[3] = riceParamParts[3] + (drflac_int32)(prediction3 >> shift); -} - -#if defined(DRFLAC_SUPPORT_SSE41) -static DRFLAC_INLINE drflac_int32 drflac__calculate_prediction_64__sse41(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pDecodedSamples) -{ - __m128i prediction = _mm_setzero_si128(); - - drflac_assert(order <= 32); - - switch (order) - { - case 32: - case 31: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[31], 0, coefficients[30]), _mm_set_epi32(0, pDecodedSamples[-32], 0, pDecodedSamples[-31]))); - case 30: - case 29: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[29], 0, coefficients[28]), _mm_set_epi32(0, pDecodedSamples[-30], 0, pDecodedSamples[-29]))); - case 28: - case 27: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[27], 0, coefficients[26]), _mm_set_epi32(0, pDecodedSamples[-28], 0, pDecodedSamples[-27]))); - case 26: - case 25: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[25], 0, coefficients[24]), _mm_set_epi32(0, pDecodedSamples[-26], 0, pDecodedSamples[-25]))); - case 24: - case 23: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[23], 0, coefficients[22]), _mm_set_epi32(0, pDecodedSamples[-24], 0, pDecodedSamples[-23]))); - case 22: - case 21: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[21], 0, coefficients[20]), _mm_set_epi32(0, pDecodedSamples[-22], 0, pDecodedSamples[-21]))); - case 20: - case 19: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[19], 0, coefficients[18]), _mm_set_epi32(0, pDecodedSamples[-20], 0, pDecodedSamples[-19]))); - case 18: - case 17: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[17], 0, coefficients[16]), _mm_set_epi32(0, pDecodedSamples[-18], 0, pDecodedSamples[-17]))); - case 16: - case 15: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[15], 0, coefficients[14]), _mm_set_epi32(0, pDecodedSamples[-16], 0, pDecodedSamples[-15]))); - case 14: - case 13: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[13], 0, coefficients[12]), _mm_set_epi32(0, pDecodedSamples[-14], 0, pDecodedSamples[-13]))); - case 12: - case 11: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[11], 0, coefficients[10]), _mm_set_epi32(0, pDecodedSamples[-12], 0, pDecodedSamples[-11]))); - case 10: - case 9: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 9], 0, coefficients[ 8]), _mm_set_epi32(0, pDecodedSamples[-10], 0, pDecodedSamples[- 9]))); - case 8: - case 7: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 7], 0, coefficients[ 6]), _mm_set_epi32(0, pDecodedSamples[- 8], 0, pDecodedSamples[- 7]))); - case 6: - case 5: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 5], 0, coefficients[ 4]), _mm_set_epi32(0, pDecodedSamples[- 6], 0, pDecodedSamples[- 5]))); - case 4: - case 3: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 3], 0, coefficients[ 2]), _mm_set_epi32(0, pDecodedSamples[- 4], 0, pDecodedSamples[- 3]))); - case 2: - case 1: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 1], 0, coefficients[ 0]), _mm_set_epi32(0, pDecodedSamples[- 2], 0, pDecodedSamples[- 1]))); - } - - return (drflac_int32)(( - ((drflac_uint64*)&prediction)[0] + - ((drflac_uint64*)&prediction)[1]) >> shift); -} - -static DRFLAC_INLINE void drflac__calculate_prediction_64_x2__sse41(drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, const drflac_uint32 riceParamParts[4], drflac_int32* pDecodedSamples) -{ - __m128i prediction = _mm_setzero_si128(); - drflac_int64 predictions[2] = {0, 0}; - - drflac_assert(order <= 32); - - switch (order) - { - case 32: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[31], 0, coefficients[31]), _mm_set_epi32(0, pDecodedSamples[-31], 0, pDecodedSamples[-32]))); - case 31: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[30], 0, coefficients[30]), _mm_set_epi32(0, pDecodedSamples[-30], 0, pDecodedSamples[-31]))); - case 30: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[29], 0, coefficients[29]), _mm_set_epi32(0, pDecodedSamples[-29], 0, pDecodedSamples[-30]))); - case 29: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[28], 0, coefficients[28]), _mm_set_epi32(0, pDecodedSamples[-28], 0, pDecodedSamples[-29]))); - case 28: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[27], 0, coefficients[27]), _mm_set_epi32(0, pDecodedSamples[-27], 0, pDecodedSamples[-28]))); - case 27: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[26], 0, coefficients[26]), _mm_set_epi32(0, pDecodedSamples[-26], 0, pDecodedSamples[-27]))); - case 26: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[25], 0, coefficients[25]), _mm_set_epi32(0, pDecodedSamples[-25], 0, pDecodedSamples[-26]))); - case 25: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[24], 0, coefficients[24]), _mm_set_epi32(0, pDecodedSamples[-24], 0, pDecodedSamples[-25]))); - case 24: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[23], 0, coefficients[23]), _mm_set_epi32(0, pDecodedSamples[-23], 0, pDecodedSamples[-24]))); - case 23: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[22], 0, coefficients[22]), _mm_set_epi32(0, pDecodedSamples[-22], 0, pDecodedSamples[-23]))); - case 22: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[21], 0, coefficients[21]), _mm_set_epi32(0, pDecodedSamples[-21], 0, pDecodedSamples[-22]))); - case 21: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[20], 0, coefficients[20]), _mm_set_epi32(0, pDecodedSamples[-20], 0, pDecodedSamples[-21]))); - case 20: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[19], 0, coefficients[19]), _mm_set_epi32(0, pDecodedSamples[-19], 0, pDecodedSamples[-20]))); - case 19: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[18], 0, coefficients[18]), _mm_set_epi32(0, pDecodedSamples[-18], 0, pDecodedSamples[-19]))); - case 18: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[17], 0, coefficients[17]), _mm_set_epi32(0, pDecodedSamples[-17], 0, pDecodedSamples[-18]))); - case 17: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[16], 0, coefficients[16]), _mm_set_epi32(0, pDecodedSamples[-16], 0, pDecodedSamples[-17]))); - case 16: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[15], 0, coefficients[15]), _mm_set_epi32(0, pDecodedSamples[-15], 0, pDecodedSamples[-16]))); - case 15: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[14], 0, coefficients[14]), _mm_set_epi32(0, pDecodedSamples[-14], 0, pDecodedSamples[-15]))); - case 14: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[13], 0, coefficients[13]), _mm_set_epi32(0, pDecodedSamples[-13], 0, pDecodedSamples[-14]))); - case 13: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[12], 0, coefficients[12]), _mm_set_epi32(0, pDecodedSamples[-12], 0, pDecodedSamples[-13]))); - case 12: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[11], 0, coefficients[11]), _mm_set_epi32(0, pDecodedSamples[-11], 0, pDecodedSamples[-12]))); - case 11: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[10], 0, coefficients[10]), _mm_set_epi32(0, pDecodedSamples[-10], 0, pDecodedSamples[-11]))); - case 10: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 9], 0, coefficients[ 9]), _mm_set_epi32(0, pDecodedSamples[- 9], 0, pDecodedSamples[-10]))); - case 9: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 8], 0, coefficients[ 8]), _mm_set_epi32(0, pDecodedSamples[- 8], 0, pDecodedSamples[- 9]))); - case 8: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 7], 0, coefficients[ 7]), _mm_set_epi32(0, pDecodedSamples[- 7], 0, pDecodedSamples[- 8]))); - case 7: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 6], 0, coefficients[ 6]), _mm_set_epi32(0, pDecodedSamples[- 6], 0, pDecodedSamples[- 7]))); - case 6: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 5], 0, coefficients[ 5]), _mm_set_epi32(0, pDecodedSamples[- 5], 0, pDecodedSamples[- 6]))); - case 5: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 4], 0, coefficients[ 4]), _mm_set_epi32(0, pDecodedSamples[- 4], 0, pDecodedSamples[- 5]))); - case 4: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 3], 0, coefficients[ 3]), _mm_set_epi32(0, pDecodedSamples[- 3], 0, pDecodedSamples[- 4]))); - case 3: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 2], 0, coefficients[ 2]), _mm_set_epi32(0, pDecodedSamples[- 2], 0, pDecodedSamples[- 3]))); - case 2: prediction = _mm_add_epi64(prediction, _mm_mul_epi32(_mm_set_epi32(0, coefficients[ 1], 0, coefficients[ 1]), _mm_set_epi32(0, pDecodedSamples[- 1], 0, pDecodedSamples[- 2]))); - order = 1; - } - - _mm_storeu_si128((__m128i*)predictions, prediction); - - switch (order) - { - case 1: predictions[0] += coefficients[ 0] * (drflac_int64)pDecodedSamples[- 1]; - } - pDecodedSamples[0] = riceParamParts[0] + (drflac_int32)(predictions[0] >> shift); - - switch (order) - { - case 1: predictions[1] += coefficients[ 0] * (drflac_int64)pDecodedSamples[ 0]; - } - pDecodedSamples[1] = riceParamParts[1] + (drflac_int32)(predictions[1] >> shift); -} - - -static DRFLAC_INLINE __m128i drflac__mm_not_si128(__m128i a) -{ - return _mm_xor_si128(a, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); -} - -static DRFLAC_INLINE __m128i drflac__mm_slide1_epi32(__m128i a, __m128i b) -{ - /* a3a2a1a0/b3b2b1b0 -> a2a1a0b3 */ - - /* Result = a2a1a0b3 */ - __m128i b3a3b2a2 = _mm_unpackhi_epi32(a, b); - __m128i a2b3a2b3 = _mm_shuffle_epi32(b3a3b2a2, _MM_SHUFFLE(0, 3, 0, 3)); - __m128i a1a2a0b3 = _mm_unpacklo_epi32(a2b3a2b3, a); - __m128i a2a1a0b3 = _mm_shuffle_epi32(a1a2a0b3, _MM_SHUFFLE(2, 3, 1, 0)); - return a2a1a0b3; -} - -static DRFLAC_INLINE __m128i drflac__mm_slide2_epi32(__m128i a, __m128i b) -{ - /* Result = a1a0b3b2 */ - __m128i b1b0b3b2 = _mm_shuffle_epi32(b, _MM_SHUFFLE(1, 0, 3, 2)); - __m128i a1b3a0b2 = _mm_unpacklo_epi32(b1b0b3b2, a); - __m128i a1a0b3b2 = _mm_shuffle_epi32(a1b3a0b2, _MM_SHUFFLE(3, 1, 2, 0)); - return a1a0b3b2; -} - -static DRFLAC_INLINE __m128i drflac__mm_slide3_epi32(__m128i a, __m128i b) -{ - /* Result = a0b3b2b1 */ - __m128i b1a1b0a0 = _mm_unpacklo_epi32(a, b); - __m128i a0b1a0b1 = _mm_shuffle_epi32(b1a1b0a0, _MM_SHUFFLE(0, 3, 0, 3)); - __m128i b3a0b2b1 = _mm_unpackhi_epi32(a0b1a0b1, b); - __m128i a0b3b2b1 = _mm_shuffle_epi32(b3a0b2b1, _MM_SHUFFLE(2, 3, 1, 0)); - return a0b3b2b1; -} - -static DRFLAC_INLINE void drflac__calculate_prediction_32_x4__sse41(drflac_uint32 order, drflac_int32 shift, const __m128i* coefficients128, const __m128i riceParamParts128, drflac_int32* pDecodedSamples) -{ - drflac_assert(order <= 32); - - /* I don't think this is as efficient as it could be. More work needs to be done on this. */ - if (order > 0) { - drflac_int32 predictions[4]; - drflac_uint32 riceParamParts[4]; - - __m128i s_09_10_11_12 = _mm_loadu_si128((const __m128i*)(pDecodedSamples - 12)); - __m128i s_05_06_07_08 = _mm_loadu_si128((const __m128i*)(pDecodedSamples - 8)); - __m128i s_01_02_03_04 = _mm_loadu_si128((const __m128i*)(pDecodedSamples - 4)); - - __m128i prediction = _mm_setzero_si128(); - - /* - The idea with this switch is to do do a single jump based on the value of "order". In my test library, "order" is never larger than 12, so - I have decided to do a less optimal, but simpler solution in the order > 12 case. - */ - switch (order) - { - case 32: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[31], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 32)))); - case 31: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[30], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 31)))); - case 30: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[29], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 30)))); - case 29: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[28], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 29)))); - case 28: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[27], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 28)))); - case 27: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[26], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 27)))); - case 26: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[25], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 26)))); - case 25: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[24], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 25)))); - case 24: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[23], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 24)))); - case 23: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[22], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 23)))); - case 22: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[21], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 22)))); - case 21: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[20], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 21)))); - case 20: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[19], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 20)))); - case 19: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[18], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 19)))); - case 18: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[17], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 18)))); - case 17: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[16], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 17)))); - case 16: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[15], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 16)))); - case 15: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[14], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 15)))); - case 14: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[13], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 14)))); - case 13: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[12], _mm_loadu_si128((const __m128i*)(pDecodedSamples - 13)))); - - case 12: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[11], s_09_10_11_12)); - case 11: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[10], drflac__mm_slide3_epi32(s_05_06_07_08, s_09_10_11_12))); - case 10: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 9], drflac__mm_slide2_epi32(s_05_06_07_08, s_09_10_11_12))); - case 9: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 8], drflac__mm_slide1_epi32(s_05_06_07_08, s_09_10_11_12))); - case 8: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 7], s_05_06_07_08)); - case 7: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 6], drflac__mm_slide3_epi32(s_01_02_03_04, s_05_06_07_08))); - case 6: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 5], drflac__mm_slide2_epi32(s_01_02_03_04, s_05_06_07_08))); - case 5: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 4], drflac__mm_slide1_epi32(s_01_02_03_04, s_05_06_07_08))); - case 4: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 3], s_01_02_03_04)); order = 3; /* <-- Don't forget to set order to 3 here! */ - case 3: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 2], drflac__mm_slide3_epi32(_mm_setzero_si128(), s_01_02_03_04))); - case 2: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 1], drflac__mm_slide2_epi32(_mm_setzero_si128(), s_01_02_03_04))); - case 1: prediction = _mm_add_epi32(prediction, _mm_mullo_epi32(coefficients128[ 0], drflac__mm_slide1_epi32(_mm_setzero_si128(), s_01_02_03_04))); - } - - _mm_storeu_si128((__m128i*)predictions, prediction); - _mm_storeu_si128((__m128i*)riceParamParts, riceParamParts128); - - predictions[0] = riceParamParts[0] + (predictions[0] >> shift); - - switch (order) - { - case 3: predictions[3] += ((const drflac_int32*)&coefficients128[ 2])[0] * predictions[ 0]; - case 2: predictions[2] += ((const drflac_int32*)&coefficients128[ 1])[0] * predictions[ 0]; - case 1: predictions[1] += ((const drflac_int32*)&coefficients128[ 0])[0] * predictions[ 0]; - } - predictions[1] = riceParamParts[1] + (predictions[1] >> shift); - - switch (order) - { - case 3: - case 2: predictions[3] += ((const drflac_int32*)&coefficients128[ 1])[0] * predictions[ 1]; - case 1: predictions[2] += ((const drflac_int32*)&coefficients128[ 0])[0] * predictions[ 1]; - } - predictions[2] = riceParamParts[2] + (predictions[2] >> shift); - - switch (order) - { - case 3: - case 2: - case 1: predictions[3] += ((const drflac_int32*)&coefficients128[ 0])[0] * predictions[ 2]; - } - predictions[3] = riceParamParts[3] + (predictions[3] >> shift); - - pDecodedSamples[0] = predictions[0]; - pDecodedSamples[1] = predictions[1]; - pDecodedSamples[2] = predictions[2]; - pDecodedSamples[3] = predictions[3]; - } else { - _mm_storeu_si128((__m128i*)pDecodedSamples, riceParamParts128); - } -} -#endif #if 0 /* @@ -2926,9 +3235,8 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla { drflac_uint32 i; - drflac_assert(bs != NULL); - drflac_assert(count > 0); - drflac_assert(pSamplesOut != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); for (i = 0; i < count; ++i) { drflac_uint32 zeroCounter = 0; @@ -2962,7 +3270,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla } - if (bitsPerSample > 16) { + if (bitsPerSample+shift >= 32) { pSamplesOut[i] = decodedRice + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); } else { pSamplesOut[i] = decodedRice + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); @@ -3015,7 +3323,7 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts(drflac_bs* bs, drflac drflac_uint32 riceParamPart; drflac_uint32 riceLength; - drflac_assert(riceParam > 0); /* <-- riceParam should never be 0. drflac__read_rice_parts__param_equals_zero() should be used instead for this case. */ + DRFLAC_ASSERT(riceParam > 0); /* <-- riceParam should never be 0. drflac__read_rice_parts__param_equals_zero() should be used instead for this case. */ riceParamMask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParam); @@ -3125,8 +3433,8 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf /* Before reloading the cache we need to grab the size in bits of the low part. */ riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; - drflac_assert(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); - + DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + /* Now reload the cache. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC @@ -3198,133 +3506,6 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x1(drflac_bs* bs, drf return DRFLAC_TRUE; } -static DRFLAC_INLINE drflac_bool32 drflac__read_rice_parts_x4(drflac_bs* bs, drflac_uint8 riceParam, drflac_uint32* pZeroCounterOut, drflac_uint32* pRiceParamPartOut) -{ - drflac_uint32 riceParamPlus1 = riceParam + 1; - /*drflac_cache_t riceParamPlus1Mask = DRFLAC_CACHE_L1_SELECTION_MASK(riceParamPlus1);*/ - drflac_uint32 riceParamPlus1Shift = DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPlus1); - drflac_uint32 riceParamPlus1MaxConsumedBits = DRFLAC_CACHE_L1_SIZE_BITS(bs) - riceParamPlus1; - - /* - The idea here is to use local variables for the cache in an attempt to encourage the compiler to store them in registers. I have - no idea how this will work in practice... - */ - drflac_cache_t bs_cache = bs->cache; - drflac_uint32 bs_consumedBits = bs->consumedBits; - - /* - What this is doing is trying to efficiently extract 4 rice parts at a time, the idea being that we can exploit certain properties - to our advantage to make things more efficient. - */ - int i; - for (i = 0; i < 4; ++i) { - /* The first thing to do is find the first unset bit. Most likely a bit will be set in the current cache line. */ - drflac_uint32 lzcount = drflac__clz(bs_cache); - if (lzcount < sizeof(bs_cache)*8) { - pZeroCounterOut[i] = lzcount; - - /* - It is most likely that the riceParam part (which comes after the zero counter) is also on this cache line. When extracting - this, we include the set bit from the unary coded part because it simplifies cache management. This bit will be handled - outside of this function at a higher level. - */ - extract_rice_param_part: - bs_cache <<= lzcount; - bs_consumedBits += lzcount; - - if (bs_consumedBits <= riceParamPlus1MaxConsumedBits) { - /* Getting here means the rice parameter part is wholly contained within the current cache line. */ - pRiceParamPartOut[i] = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); - bs_cache <<= riceParamPlus1; - bs_consumedBits += riceParamPlus1; - } else { - drflac_uint32 riceParamPartHi; - drflac_uint32 riceParamPartLo; - drflac_uint32 riceParamPartLoBitCount; - - /* - Getting here means the rice parameter part straddles the cache line. We need to read from the tail of the current cache - line, reload the cache, and then combine it with the head of the next cache line. - */ - - /* Grab the high part of the rice parameter part. */ - riceParamPartHi = (drflac_uint32)(bs_cache >> riceParamPlus1Shift); - - /* Before reloading the cache we need to grab the size in bits of the low part. */ - riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; - - /* Now reload the cache. */ - if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { - #ifndef DR_FLAC_NO_CRC - drflac__update_crc16(bs); - #endif - bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); - bs_consumedBits = riceParamPartLoBitCount; - #ifndef DR_FLAC_NO_CRC - bs->crc16Cache = bs_cache; - #endif - } else { - /* Slow path. We need to fetch more data from the client. */ - if (!drflac__reload_cache(bs)) { - return DRFLAC_FALSE; - } - - bs_cache = bs->cache; - bs_consumedBits = bs->consumedBits + riceParamPartLoBitCount; - } - - /* We should now have enough information to construct the rice parameter part. */ - riceParamPartLo = (drflac_uint32)(bs_cache >> (DRFLAC_CACHE_L1_SELECTION_SHIFT(bs, riceParamPartLoBitCount))); - pRiceParamPartOut[i] = riceParamPartHi | riceParamPartLo; - - bs_cache <<= riceParamPartLoBitCount; - } - } else { - /* - Getting here means there are no bits set on the cache line. This is a less optimal case because we just wasted a call - to drflac__clz() and we need to reload the cache. - */ - drflac_uint32 zeroCounter = (drflac_uint32)(DRFLAC_CACHE_L1_SIZE_BITS(bs) - bs_consumedBits); - for (;;) { - if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { - #ifndef DR_FLAC_NO_CRC - drflac__update_crc16(bs); - #endif - bs_cache = drflac__be2host__cache_line(bs->cacheL2[bs->nextL2Line++]); - bs_consumedBits = 0; - #ifndef DR_FLAC_NO_CRC - bs->crc16Cache = bs_cache; - #endif - } else { - /* Slow path. We need to fetch more data from the client. */ - if (!drflac__reload_cache(bs)) { - return DRFLAC_FALSE; - } - - bs_cache = bs->cache; - bs_consumedBits = bs->consumedBits; - } - - lzcount = drflac__clz(bs_cache); - zeroCounter += lzcount; - - if (lzcount < sizeof(bs_cache)*8) { - break; - } - } - - pZeroCounterOut[i] = zeroCounter; - goto extract_rice_param_part; - } - } - - /* Make sure the cache is restored at the end of it all. */ - bs->cache = bs_cache; - bs->consumedBits = bs_consumedBits; - - return DRFLAC_TRUE; -} - static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac_uint8 riceParam) { drflac_uint32 riceParamPlus1 = riceParam + 1; @@ -3361,8 +3542,8 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac /* Before reloading the cache we need to grab the size in bits of the low part. */ drflac_uint32 riceParamPartLoBitCount = bs_consumedBits - riceParamPlus1MaxConsumedBits; - drflac_assert(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); - + DRFLAC_ASSERT(riceParamPartLoBitCount > 0 && riceParamPartLoBitCount < 32); + /* Now reload the cache. */ if (bs->nextL2Line < DRFLAC_CACHE_L2_LINE_COUNT(bs)) { #ifndef DR_FLAC_NO_CRC @@ -3427,29 +3608,70 @@ static DRFLAC_INLINE drflac_bool32 drflac__seek_rice_parts(drflac_bs* bs, drflac } -static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorder(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; drflac_uint32 zeroCountPart0; - drflac_uint32 zeroCountPart1; - drflac_uint32 zeroCountPart2; - drflac_uint32 zeroCountPart3; drflac_uint32 riceParamPart0; - drflac_uint32 riceParamPart1; - drflac_uint32 riceParamPart2; - drflac_uint32 riceParamPart3; + drflac_uint32 riceParamMask; + drflac_uint32 i; + + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + (void)bitsPerSample; + (void)order; + (void)shift; + (void)coefficients; + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + + i = 0; + while (i < count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamPart0 &= riceParamMask; + riceParamPart0 |= (zeroCountPart0 << riceParam); + riceParamPart0 = (riceParamPart0 >> 1) ^ t[riceParamPart0 & 0x01]; + + pSamplesOut[i] = riceParamPart0; + + i += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + drflac_uint32 zeroCountPart0 = 0; + drflac_uint32 zeroCountPart1 = 0; + drflac_uint32 zeroCountPart2 = 0; + drflac_uint32 zeroCountPart3 = 0; + drflac_uint32 riceParamPart0 = 0; + drflac_uint32 riceParamPart1 = 0; + drflac_uint32 riceParamPart2 = 0; + drflac_uint32 riceParamPart3 = 0; drflac_uint32 riceParamMask; const drflac_int32* pSamplesOutEnd; drflac_uint32 i; - drflac_assert(bs != NULL); - drflac_assert(count > 0); - drflac_assert(pSamplesOut != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); - riceParamMask = ~((~0UL) << riceParam); - pSamplesOutEnd = pSamplesOut + ((count >> 2) << 2); + if (order == 0) { + return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + } - if (bitsPerSample >= 24) { + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + pSamplesOutEnd = pSamplesOut + (count & ~3); + + if (bitsPerSample+shift > 32) { while (pSamplesOut < pSamplesOutEnd) { /* Rice extraction. It's faster to do this one at a time against local variables than it is to use the x4 version @@ -3517,7 +3739,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b } } - i = ((count >> 2) << 2); + i = (count & ~3); while (i < count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountPart0, &riceParamPart0)) { @@ -3531,7 +3753,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b /*riceParamPart0 = (riceParamPart0 >> 1) ^ (~(riceParamPart0 & 0x01) + 1);*/ /* Sample reconstruction. */ - if (bitsPerSample >= 24) { + if (bitsPerSample+shift > 32) { pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); } else { pSamplesOut[0] = riceParamPart0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); @@ -3540,136 +3762,246 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b i += 1; pSamplesOut += 1; } - + return DRFLAC_TRUE; } -#if defined(DRFLAC_SUPPORT_SSE41) -static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE __m128i drflac__mm_packs_interleaved_epi32(__m128i a, __m128i b) { - static drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + __m128i r; - /*drflac_uint32 zeroCountParts[4];*/ - /*drflac_uint32 riceParamParts[4];*/ + /* Pack. */ + r = _mm_packs_epi32(a, b); - drflac_uint32 zeroCountParts0; - drflac_uint32 zeroCountParts1; - drflac_uint32 zeroCountParts2; - drflac_uint32 zeroCountParts3; - drflac_uint32 riceParamParts0; - drflac_uint32 riceParamParts1; - drflac_uint32 riceParamParts2; - drflac_uint32 riceParamParts3; + /* a3a2 a1a0 b3b2 b1b0 -> a3a2 b3b2 a1a0 b1b0 */ + r = _mm_shuffle_epi32(r, _MM_SHUFFLE(3, 1, 2, 0)); + + /* a3a2 b3b2 a1a0 b1b0 -> a3b3 a2b2 a1b1 a0b0 */ + r = _mm_shufflehi_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); + r = _mm_shufflelo_epi16(r, _MM_SHUFFLE(3, 1, 2, 0)); + + return r; +} +#endif + +#if defined(DRFLAC_SUPPORT_SSE41) +static DRFLAC_INLINE __m128i drflac__mm_not_si128(__m128i a) +{ + return _mm_xor_si128(a, _mm_cmpeq_epi32(_mm_setzero_si128(), _mm_setzero_si128())); +} + +static DRFLAC_INLINE __m128i drflac__mm_hadd_epi32(__m128i x) +{ + __m128i x64 = _mm_add_epi32(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); + __m128i x32 = _mm_shufflelo_epi16(x64, _MM_SHUFFLE(1, 0, 3, 2)); + return _mm_add_epi32(x64, x32); +} + +static DRFLAC_INLINE __m128i drflac__mm_hadd_epi64(__m128i x) +{ + return _mm_add_epi64(x, _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2))); +} + +static DRFLAC_INLINE __m128i drflac__mm_srai_epi64(__m128i x, int count) +{ + /* + To simplify this we are assuming count < 32. This restriction allows us to work on a low side and a high side. The low side + is shifted with zero bits, whereas the right side is shifted with sign bits. + */ + __m128i lo = _mm_srli_epi64(x, count); + __m128i hi = _mm_srai_epi32(x, count); + + hi = _mm_and_si128(hi, _mm_set_epi32(0xFFFFFFFF, 0, 0xFFFFFFFF, 0)); /* The high part needs to have the low part cleared. */ + + return _mm_or_si128(lo, hi); +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; drflac_uint32 riceParamMask; - const drflac_int32* pSamplesOutEnd; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts0 = 0; + drflac_uint32 zeroCountParts1 = 0; + drflac_uint32 zeroCountParts2 = 0; + drflac_uint32 zeroCountParts3 = 0; + drflac_uint32 riceParamParts0 = 0; + drflac_uint32 riceParamParts1 = 0; + drflac_uint32 riceParamParts2 = 0; + drflac_uint32 riceParamParts3 = 0; + __m128i coefficients128_0; + __m128i coefficients128_4; + __m128i coefficients128_8; + __m128i samples128_0; + __m128i samples128_4; + __m128i samples128_8; __m128i riceParamMask128; - __m128i one; - drflac_uint32 i; - drflac_assert(bs != NULL); - drflac_assert(count > 0); - drflac_assert(pSamplesOut != NULL); + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; - riceParamMask = ~((~0UL) << riceParam); + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); riceParamMask128 = _mm_set1_epi32(riceParamMask); - one = _mm_set1_epi32(0x01); - pSamplesOutEnd = pSamplesOut + ((count >> 2) << 2); + /* Pre-load. */ + coefficients128_0 = _mm_setzero_si128(); + coefficients128_4 = _mm_setzero_si128(); + coefficients128_8 = _mm_setzero_si128(); - if (bitsPerSample >= 24) { - while (pSamplesOut < pSamplesOutEnd) { - __m128i zeroCountPart128; - __m128i riceParamPart128; - drflac_uint32 riceParamParts[4]; + samples128_0 = _mm_setzero_si128(); + samples128_4 = _mm_setzero_si128(); + samples128_8 = _mm_setzero_si128(); - /* Rice extraction. */ - if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || - !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || - !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || - !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { - return DRFLAC_FALSE; - } - - zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); - riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); - - riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); - riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); - riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, one), _mm_set1_epi32(0xFFFFFFFF))); /* <-- Only supported from SSE4.1 */ - /*riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, one)), one));*/ /* <-- SSE2 compatible */ - - _mm_storeu_si128((__m128i*)riceParamParts, riceParamPart128); - - #if defined(DRFLAC_64BIT) - /* The scalar implementation seems to be faster on 64-bit in my testing. */ - drflac__calculate_prediction_64_x4(order, shift, coefficients, riceParamParts, pSamplesOut); - #else - pSamplesOut[0] = riceParamParts[0] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamParts[1] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamParts[2] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamParts[3] + drflac__calculate_prediction_64__sse41(order, shift, coefficients, pSamplesOut + 3); - #endif - - pSamplesOut += 4; - } - } else { - drflac_int32 coefficientsUnaligned[32*4 + 4] = {0}; - drflac_int32* coefficients128 = (drflac_int32*)(((size_t)coefficientsUnaligned + 15) & ~15); - - for (i = 0; i < order; ++i) { - coefficients128[i*4+0] = coefficients[i]; - coefficients128[i*4+1] = coefficients[i]; - coefficients128[i*4+2] = coefficients[i]; - coefficients128[i*4+3] = coefficients[i]; - } - - while (pSamplesOut < pSamplesOutEnd) { - __m128i zeroCountPart128; - __m128i riceParamPart128; - /*drflac_int32 riceParamParts[4];*/ - - /* Rice extraction. */ + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be convenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ #if 1 - if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || - !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || - !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || - !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { - return DRFLAC_FALSE; - } + { + int runningOrder = order; - zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); - riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); + samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; + case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; + case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); + samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; + case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; + case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); + samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; + case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; + case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); + } #else - if (!drflac__read_rice_parts_x4(bs, riceParam, zeroCountParts, riceParamParts)) { - return DRFLAC_FALSE; - } - - zeroCountPart128 = _mm_set_epi32(zeroCountParts[3], zeroCountParts[2], zeroCountParts[1], zeroCountParts[0]); - riceParamPart128 = _mm_set_epi32(riceParamParts[3], riceParamParts[2], riceParamParts[1], riceParamParts[0]); + /* This causes strict-aliasing warnings with GCC. */ + switch (order) + { + case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; + case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; + case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; + case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; + case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; + case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; + case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; + case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; + case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; + case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; + case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; + case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; + } #endif - riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); - riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); - riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, one), _mm_set1_epi32(0xFFFFFFFF))); + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + __m128i prediction128; + __m128i zeroCountPart128; + __m128i riceParamPart128; -#if 1 - drflac__calculate_prediction_32_x4__sse41(order, shift, (const __m128i*)coefficients128, riceParamPart128, pSamplesOut); -#else - _mm_storeu_si128((__m128i*)riceParamParts, riceParamPart128); - - pSamplesOut[0] = riceParamParts[0] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); - pSamplesOut[1] = riceParamParts[1] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 1); - pSamplesOut[2] = riceParamParts[2] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 2); - pSamplesOut[3] = riceParamParts[3] + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 3); -#endif - - pSamplesOut += 4; + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01))), _mm_set1_epi32(0x01))); /* <-- SSE2 compatible */ + /*riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_mullo_epi32(_mm_and_si128(riceParamPart128, _mm_set1_epi32(0x01)), _mm_set1_epi32(0xFFFFFFFF)));*/ /* <-- Only supported from SSE4.1 and is slower in my testing... */ + + if (order <= 4) { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } else if (order <= 8) { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_4, samples128_4); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } else { + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_mullo_epi32(coefficients128_8, samples128_8); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_4, samples128_4)); + prediction128 = _mm_add_epi32(prediction128, _mm_mullo_epi32(coefficients128_0, samples128_0)); + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi32(prediction128); + prediction128 = _mm_srai_epi32(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + } + + /* We store samples in groups of 4. */ + _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); + pDecodedSamples += 4; } - - i = ((count >> 2) << 2); - while (i < count) { + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { /* Rice extraction. */ if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { return DRFLAC_FALSE; @@ -3681,18 +4013,719 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; /* Sample reconstruction. */ - if (bitsPerSample >= 24) { - pSamplesOut[0] = riceParamParts0 + drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + 0); - } else { - pSamplesOut[0] = riceParamParts0 + drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + 0); - } + pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); i += 1; - pSamplesOut += 1; + pDecodedSamples += 1; } return DRFLAC_TRUE; } + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts0 = 0; + drflac_uint32 zeroCountParts1 = 0; + drflac_uint32 zeroCountParts2 = 0; + drflac_uint32 zeroCountParts3 = 0; + drflac_uint32 riceParamParts0 = 0; + drflac_uint32 riceParamParts1 = 0; + drflac_uint32 riceParamParts2 = 0; + drflac_uint32 riceParamParts3 = 0; + __m128i coefficients128_0; + __m128i coefficients128_4; + __m128i coefficients128_8; + __m128i samples128_0; + __m128i samples128_4; + __m128i samples128_8; + __m128i prediction128; + __m128i riceParamMask128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + DRFLAC_ASSERT(order <= 12); + + riceParamMask = (drflac_uint32)~((~0UL) << riceParam); + riceParamMask128 = _mm_set1_epi32(riceParamMask); + + prediction128 = _mm_setzero_si128(); + + /* Pre-load. */ + coefficients128_0 = _mm_setzero_si128(); + coefficients128_4 = _mm_setzero_si128(); + coefficients128_8 = _mm_setzero_si128(); + + samples128_0 = _mm_setzero_si128(); + samples128_4 = _mm_setzero_si128(); + samples128_8 = _mm_setzero_si128(); + +#if 1 + { + int runningOrder = order; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = _mm_loadu_si128((const __m128i*)(coefficients + 0)); + samples128_0 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 4)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_0 = _mm_set_epi32(0, coefficients[2], coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], pSamplesOut[-3], 0); break; + case 2: coefficients128_0 = _mm_set_epi32(0, 0, coefficients[1], coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], pSamplesOut[-2], 0, 0); break; + case 1: coefficients128_0 = _mm_set_epi32(0, 0, 0, coefficients[0]); samples128_0 = _mm_set_epi32(pSamplesOut[-1], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = _mm_loadu_si128((const __m128i*)(coefficients + 4)); + samples128_4 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 8)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_4 = _mm_set_epi32(0, coefficients[6], coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], pSamplesOut[-7], 0); break; + case 2: coefficients128_4 = _mm_set_epi32(0, 0, coefficients[5], coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], pSamplesOut[-6], 0, 0); break; + case 1: coefficients128_4 = _mm_set_epi32(0, 0, 0, coefficients[4]); samples128_4 = _mm_set_epi32(pSamplesOut[-5], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = _mm_loadu_si128((const __m128i*)(coefficients + 8)); + samples128_8 = _mm_loadu_si128((const __m128i*)(pSamplesOut - 12)); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: coefficients128_8 = _mm_set_epi32(0, coefficients[10], coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], pSamplesOut[-11], 0); break; + case 2: coefficients128_8 = _mm_set_epi32(0, 0, coefficients[9], coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], pSamplesOut[-10], 0, 0); break; + case 1: coefficients128_8 = _mm_set_epi32(0, 0, 0, coefficients[8]); samples128_8 = _mm_set_epi32(pSamplesOut[-9], 0, 0, 0); break; + } + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = _mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_4 = _mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(0, 1, 2, 3)); + coefficients128_8 = _mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(0, 1, 2, 3)); + } +#else + switch (order) + { + case 12: ((drflac_int32*)&coefficients128_8)[0] = coefficients[11]; ((drflac_int32*)&samples128_8)[0] = pDecodedSamples[-12]; + case 11: ((drflac_int32*)&coefficients128_8)[1] = coefficients[10]; ((drflac_int32*)&samples128_8)[1] = pDecodedSamples[-11]; + case 10: ((drflac_int32*)&coefficients128_8)[2] = coefficients[ 9]; ((drflac_int32*)&samples128_8)[2] = pDecodedSamples[-10]; + case 9: ((drflac_int32*)&coefficients128_8)[3] = coefficients[ 8]; ((drflac_int32*)&samples128_8)[3] = pDecodedSamples[- 9]; + case 8: ((drflac_int32*)&coefficients128_4)[0] = coefficients[ 7]; ((drflac_int32*)&samples128_4)[0] = pDecodedSamples[- 8]; + case 7: ((drflac_int32*)&coefficients128_4)[1] = coefficients[ 6]; ((drflac_int32*)&samples128_4)[1] = pDecodedSamples[- 7]; + case 6: ((drflac_int32*)&coefficients128_4)[2] = coefficients[ 5]; ((drflac_int32*)&samples128_4)[2] = pDecodedSamples[- 6]; + case 5: ((drflac_int32*)&coefficients128_4)[3] = coefficients[ 4]; ((drflac_int32*)&samples128_4)[3] = pDecodedSamples[- 5]; + case 4: ((drflac_int32*)&coefficients128_0)[0] = coefficients[ 3]; ((drflac_int32*)&samples128_0)[0] = pDecodedSamples[- 4]; + case 3: ((drflac_int32*)&coefficients128_0)[1] = coefficients[ 2]; ((drflac_int32*)&samples128_0)[1] = pDecodedSamples[- 3]; + case 2: ((drflac_int32*)&coefficients128_0)[2] = coefficients[ 1]; ((drflac_int32*)&samples128_0)[2] = pDecodedSamples[- 2]; + case 1: ((drflac_int32*)&coefficients128_0)[3] = coefficients[ 0]; ((drflac_int32*)&samples128_0)[3] = pDecodedSamples[- 1]; + } +#endif + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + __m128i zeroCountPart128; + __m128i riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts1, &riceParamParts1) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts2, &riceParamParts2) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts3, &riceParamParts3)) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = _mm_set_epi32(zeroCountParts3, zeroCountParts2, zeroCountParts1, zeroCountParts0); + riceParamPart128 = _mm_set_epi32(riceParamParts3, riceParamParts2, riceParamParts1, riceParamParts0); + + riceParamPart128 = _mm_and_si128(riceParamPart128, riceParamMask128); + riceParamPart128 = _mm_or_si128(riceParamPart128, _mm_slli_epi32(zeroCountPart128, riceParam)); + riceParamPart128 = _mm_xor_si128(_mm_srli_epi32(riceParamPart128, 1), _mm_add_epi32(drflac__mm_not_si128(_mm_and_si128(riceParamPart128, _mm_set1_epi32(1))), _mm_set1_epi32(1))); + + for (i = 0; i < 4; i += 1) { + prediction128 = _mm_xor_si128(prediction128, prediction128); /* Reset to 0. */ + + switch (order) + { + case 12: + case 11: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(1, 1, 0, 0)))); + case 10: + case 9: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_8, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_8, _MM_SHUFFLE(3, 3, 2, 2)))); + case 8: + case 7: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(1, 1, 0, 0)))); + case 6: + case 5: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_4, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_4, _MM_SHUFFLE(3, 3, 2, 2)))); + case 4: + case 3: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(1, 1, 0, 0)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(1, 1, 0, 0)))); + case 2: + case 1: prediction128 = _mm_add_epi64(prediction128, _mm_mul_epi32(_mm_shuffle_epi32(coefficients128_0, _MM_SHUFFLE(3, 3, 2, 2)), _mm_shuffle_epi32(samples128_0, _MM_SHUFFLE(3, 3, 2, 2)))); + } + + /* Horizontal add and shift. */ + prediction128 = drflac__mm_hadd_epi64(prediction128); + prediction128 = drflac__mm_srai_epi64(prediction128, shift); + prediction128 = _mm_add_epi32(riceParamPart128, prediction128); + + /* Our value should be sitting in prediction128[0]. We need to combine this with our SSE samples. */ + samples128_8 = _mm_alignr_epi8(samples128_4, samples128_8, 4); + samples128_4 = _mm_alignr_epi8(samples128_0, samples128_4, 4); + samples128_0 = _mm_alignr_epi8(prediction128, samples128_0, 4); + + /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ + riceParamPart128 = _mm_alignr_epi8(_mm_setzero_si128(), riceParamPart128, 4); + } + + /* We store samples in groups of 4. */ + _mm_storeu_si128((__m128i*)pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts0, &riceParamParts0)) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts0 &= riceParamMask; + riceParamParts0 |= (zeroCountParts0 << riceParam); + riceParamParts0 = (riceParamParts0 >> 1) ^ t[riceParamParts0 & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts0 + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + /* In my testing the order is rarely > 12, so in this case I'm going to simplify the SSE implementation by only handling order <= 12. */ + if (order > 0 && order <= 12) { + if (bitsPerSample+shift > 32) { + return drflac__decode_samples_with_residual__rice__sse41_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + } else { + return drflac__decode_samples_with_residual__rice__sse41_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + } + } else { + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac__vst2q_s32(drflac_int32* p, int32x4x2_t x) +{ + vst1q_s32(p+0, x.val[0]); + vst1q_s32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_u32(drflac_uint32* p, uint32x4x2_t x) +{ + vst1q_u32(p+0, x.val[0]); + vst1q_u32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_f32(float* p, float32x4x2_t x) +{ + vst1q_f32(p+0, x.val[0]); + vst1q_f32(p+4, x.val[1]); +} + +static DRFLAC_INLINE void drflac__vst2q_s16(drflac_int16* p, int16x4x2_t x) +{ + vst1q_s16(p, vcombine_s16(x.val[0], x.val[1])); +} + +static DRFLAC_INLINE void drflac__vst2q_u16(drflac_uint16* p, uint16x4x2_t x) +{ + vst1q_u16(p, vcombine_u16(x.val[0], x.val[1])); +} + +static DRFLAC_INLINE int32x4_t drflac__vdupq_n_s32x4(drflac_int32 x3, drflac_int32 x2, drflac_int32 x1, drflac_int32 x0) +{ + drflac_int32 x[4]; + x[3] = x3; + x[2] = x2; + x[1] = x1; + x[0] = x0; + return vld1q_s32(x); +} + +static DRFLAC_INLINE int32x4_t drflac__valignrq_s32_1(int32x4_t a, int32x4_t b) +{ + /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ + + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(a, 0), + vgetq_lane_s32(b, 3), + vgetq_lane_s32(b, 2), + vgetq_lane_s32(b, 1) + );*/ + + return vextq_s32(b, a, 1); +} + +static DRFLAC_INLINE uint32x4_t drflac__valignrq_u32_1(uint32x4_t a, uint32x4_t b) +{ + /* Equivalent to SSE's _mm_alignr_epi8(a, b, 4) */ + + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(a, 0), + vgetq_lane_s32(b, 3), + vgetq_lane_s32(b, 2), + vgetq_lane_s32(b, 1) + );*/ + + return vextq_u32(b, a, 1); +} + +static DRFLAC_INLINE int32x2_t drflac__vhaddq_s32(int32x4_t x) +{ + /* The sum must end up in position 0. */ + + /* Reference */ + /*return vdupq_n_s32( + vgetq_lane_s32(x, 3) + + vgetq_lane_s32(x, 2) + + vgetq_lane_s32(x, 1) + + vgetq_lane_s32(x, 0) + );*/ + + int32x2_t r = vadd_s32(vget_high_s32(x), vget_low_s32(x)); + return vpadd_s32(r, r); +} + +static DRFLAC_INLINE int64x1_t drflac__vhaddq_s64(int64x2_t x) +{ + return vadd_s64(vget_high_s64(x), vget_low_s64(x)); +} + +static DRFLAC_INLINE int32x4_t drflac__vrevq_s32(int32x4_t x) +{ + /* Reference */ + /*return drflac__vdupq_n_s32x4( + vgetq_lane_s32(x, 0), + vgetq_lane_s32(x, 1), + vgetq_lane_s32(x, 2), + vgetq_lane_s32(x, 3) + );*/ + + return vrev64q_s32(vcombine_s32(vget_high_s32(x), vget_low_s32(x))); +} + +static DRFLAC_INLINE int32x4_t drflac__vnotq_s32(int32x4_t x) +{ + return veorq_s32(x, vdupq_n_s32(0xFFFFFFFF)); +} + +static DRFLAC_INLINE uint32x4_t drflac__vnotq_u32(uint32x4_t x) +{ + return veorq_u32(x, vdupq_n_u32(0xFFFFFFFF)); +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_32(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts[4]; + drflac_uint32 riceParamParts[4]; + int32x4_t coefficients128_0; + int32x4_t coefficients128_4; + int32x4_t coefficients128_8; + int32x4_t samples128_0; + int32x4_t samples128_4; + int32x4_t samples128_8; + uint32x4_t riceParamMask128; + int32x4_t riceParam128; + int32x2_t shift64; + uint32x4_t one128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = ~((~0UL) << riceParam); + riceParamMask128 = vdupq_n_u32(riceParamMask); + + riceParam128 = vdupq_n_s32(riceParam); + shift64 = vdup_n_s32(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ + one128 = vdupq_n_u32(1); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ + { + int runningOrder = order; + drflac_int32 tempC[4] = {0, 0, 0, 0}; + drflac_int32 tempS[4] = {0, 0, 0, 0}; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = vld1q_s32(coefficients + 0); + samples128_0 = vld1q_s32(pSamplesOut - 4); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ + case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ + case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ + } + + coefficients128_0 = vld1q_s32(tempC); + samples128_0 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = vld1q_s32(coefficients + 4); + samples128_4 = vld1q_s32(pSamplesOut - 8); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ + case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ + case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ + } + + coefficients128_4 = vld1q_s32(tempC); + samples128_4 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = vld1q_s32(coefficients + 8); + samples128_8 = vld1q_s32(pSamplesOut - 12); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ + case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ + case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ + } + + coefficients128_8 = vld1q_s32(tempC); + samples128_8 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = drflac__vrevq_s32(coefficients128_0); + coefficients128_4 = drflac__vrevq_s32(coefficients128_4); + coefficients128_8 = drflac__vrevq_s32(coefficients128_8); + } + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + int32x4_t prediction128; + int32x2_t prediction64; + uint32x4_t zeroCountPart128; + uint32x4_t riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = vld1q_u32(zeroCountParts); + riceParamPart128 = vld1q_u32(riceParamParts); + + riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); + riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); + riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); + + if (order <= 4) { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } else if (order <= 8) { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_4, samples128_4); + prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } else { + for (i = 0; i < 4; i += 1) { + prediction128 = vmulq_s32(coefficients128_8, samples128_8); + prediction128 = vmlaq_s32(prediction128, coefficients128_4, samples128_4); + prediction128 = vmlaq_s32(prediction128, coefficients128_0, samples128_0); + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s32(prediction128); + prediction64 = vshl_s32(prediction64, shift64); + prediction64 = vadd_s32(prediction64, vget_low_s32(vreinterpretq_s32_u32(riceParamPart128))); + + samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(prediction64, vdup_n_s32(0)), samples128_0); + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + } + + /* We store samples in groups of 4. */ + vst1q_s32(pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts[0] &= riceParamMask; + riceParamParts[0] |= (zeroCountParts[0] << riceParam); + riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_32(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_bs* bs, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + int i; + drflac_uint32 riceParamMask; + drflac_int32* pDecodedSamples = pSamplesOut; + drflac_int32* pDecodedSamplesEnd = pSamplesOut + (count & ~3); + drflac_uint32 zeroCountParts[4]; + drflac_uint32 riceParamParts[4]; + int32x4_t coefficients128_0; + int32x4_t coefficients128_4; + int32x4_t coefficients128_8; + int32x4_t samples128_0; + int32x4_t samples128_4; + int32x4_t samples128_8; + uint32x4_t riceParamMask128; + int32x4_t riceParam128; + int64x1_t shift64; + uint32x4_t one128; + + const drflac_uint32 t[2] = {0x00000000, 0xFFFFFFFF}; + + riceParamMask = ~((~0UL) << riceParam); + riceParamMask128 = vdupq_n_u32(riceParamMask); + + riceParam128 = vdupq_n_s32(riceParam); + shift64 = vdup_n_s64(-shift); /* Negate the shift because we'll be doing a variable shift using vshlq_s32(). */ + one128 = vdupq_n_u32(1); + + /* + Pre-loading the coefficients and prior samples is annoying because we need to ensure we don't try reading more than + what's available in the input buffers. It would be conenient to use a fall-through switch to do this, but this results + in strict aliasing warnings with GCC. To work around this I'm just doing something hacky. This feels a bit convoluted + so I think there's opportunity for this to be simplified. + */ + { + int runningOrder = order; + drflac_int32 tempC[4] = {0, 0, 0, 0}; + drflac_int32 tempS[4] = {0, 0, 0, 0}; + + /* 0 - 3. */ + if (runningOrder >= 4) { + coefficients128_0 = vld1q_s32(coefficients + 0); + samples128_0 = vld1q_s32(pSamplesOut - 4); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[2]; tempS[1] = pSamplesOut[-3]; /* fallthrough */ + case 2: tempC[1] = coefficients[1]; tempS[2] = pSamplesOut[-2]; /* fallthrough */ + case 1: tempC[0] = coefficients[0]; tempS[3] = pSamplesOut[-1]; /* fallthrough */ + } + + coefficients128_0 = vld1q_s32(tempC); + samples128_0 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 4 - 7 */ + if (runningOrder >= 4) { + coefficients128_4 = vld1q_s32(coefficients + 4); + samples128_4 = vld1q_s32(pSamplesOut - 8); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[6]; tempS[1] = pSamplesOut[-7]; /* fallthrough */ + case 2: tempC[1] = coefficients[5]; tempS[2] = pSamplesOut[-6]; /* fallthrough */ + case 1: tempC[0] = coefficients[4]; tempS[3] = pSamplesOut[-5]; /* fallthrough */ + } + + coefficients128_4 = vld1q_s32(tempC); + samples128_4 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* 8 - 11 */ + if (runningOrder == 4) { + coefficients128_8 = vld1q_s32(coefficients + 8); + samples128_8 = vld1q_s32(pSamplesOut - 12); + runningOrder -= 4; + } else { + switch (runningOrder) { + case 3: tempC[2] = coefficients[10]; tempS[1] = pSamplesOut[-11]; /* fallthrough */ + case 2: tempC[1] = coefficients[ 9]; tempS[2] = pSamplesOut[-10]; /* fallthrough */ + case 1: tempC[0] = coefficients[ 8]; tempS[3] = pSamplesOut[- 9]; /* fallthrough */ + } + + coefficients128_8 = vld1q_s32(tempC); + samples128_8 = vld1q_s32(tempS); + runningOrder = 0; + } + + /* Coefficients need to be shuffled for our streaming algorithm below to work. Samples are already in the correct order from the loading routine above. */ + coefficients128_0 = drflac__vrevq_s32(coefficients128_0); + coefficients128_4 = drflac__vrevq_s32(coefficients128_4); + coefficients128_8 = drflac__vrevq_s32(coefficients128_8); + } + + /* For this version we are doing one sample at a time. */ + while (pDecodedSamples < pDecodedSamplesEnd) { + int64x2_t prediction128; + uint32x4_t zeroCountPart128; + uint32x4_t riceParamPart128; + + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[1], &riceParamParts[1]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[2], &riceParamParts[2]) || + !drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[3], &riceParamParts[3])) { + return DRFLAC_FALSE; + } + + zeroCountPart128 = vld1q_u32(zeroCountParts); + riceParamPart128 = vld1q_u32(riceParamParts); + + riceParamPart128 = vandq_u32(riceParamPart128, riceParamMask128); + riceParamPart128 = vorrq_u32(riceParamPart128, vshlq_u32(zeroCountPart128, riceParam128)); + riceParamPart128 = veorq_u32(vshrq_n_u32(riceParamPart128, 1), vaddq_u32(drflac__vnotq_u32(vandq_u32(riceParamPart128, one128)), one128)); + + for (i = 0; i < 4; i += 1) { + int64x1_t prediction64; + + prediction128 = veorq_s64(prediction128, prediction128); /* Reset to 0. */ + switch (order) + { + case 12: + case 11: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_8), vget_low_s32(samples128_8))); + case 10: + case 9: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_8), vget_high_s32(samples128_8))); + case 8: + case 7: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_4), vget_low_s32(samples128_4))); + case 6: + case 5: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_4), vget_high_s32(samples128_4))); + case 4: + case 3: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_low_s32(coefficients128_0), vget_low_s32(samples128_0))); + case 2: + case 1: prediction128 = vaddq_s64(prediction128, vmull_s32(vget_high_s32(coefficients128_0), vget_high_s32(samples128_0))); + } + + /* Horizontal add and shift. */ + prediction64 = drflac__vhaddq_s64(prediction128); + prediction64 = vshl_s64(prediction64, shift64); + prediction64 = vadd_s64(prediction64, vdup_n_s64(vgetq_lane_u32(riceParamPart128, 0))); + + /* Our value should be sitting in prediction64[0]. We need to combine this with our SSE samples. */ + samples128_8 = drflac__valignrq_s32_1(samples128_4, samples128_8); + samples128_4 = drflac__valignrq_s32_1(samples128_0, samples128_4); + samples128_0 = drflac__valignrq_s32_1(vcombine_s32(vreinterpret_s32_s64(prediction64), vdup_n_s32(0)), samples128_0); + + /* Slide our rice parameter down so that the value in position 0 contains the next one to process. */ + riceParamPart128 = drflac__valignrq_u32_1(vdupq_n_u32(0), riceParamPart128); + } + + /* We store samples in groups of 4. */ + vst1q_s32(pDecodedSamples, samples128_0); + pDecodedSamples += 4; + } + + /* Make sure we process the last few samples. */ + i = (count & ~3); + while (i < (int)count) { + /* Rice extraction. */ + if (!drflac__read_rice_parts_x1(bs, riceParam, &zeroCountParts[0], &riceParamParts[0])) { + return DRFLAC_FALSE; + } + + /* Rice reconstruction. */ + riceParamParts[0] &= riceParamMask; + riceParamParts[0] |= (zeroCountParts[0] << riceParam); + riceParamParts[0] = (riceParamParts[0] >> 1) ^ t[riceParamParts[0] & 0x01]; + + /* Sample reconstruction. */ + pDecodedSamples[0] = riceParamParts[0] + drflac__calculate_prediction_64(order, shift, coefficients, pDecodedSamples); + + i += 1; + pDecodedSamples += 1; + } + + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) +{ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(pSamplesOut != NULL); + + /* In my testing the order is rarely > 12, so in this case I'm going to simplify the NEON implementation by only handling order <= 12. */ + if (order > 0 && order <= 12) { + if (bitsPerSample+shift > 32) { + return drflac__decode_samples_with_residual__rice__neon_64(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + } else { + return drflac__decode_samples_with_residual__rice__neon_32(bs, count, riceParam, order, shift, coefficients, pSamplesOut); + } + } else { + return drflac__decode_samples_with_residual__rice__scalar(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + } +} #endif static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) @@ -3701,6 +4734,10 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice(drflac_bs* bs, d if (drflac__gIsSSE41Supported) { return drflac__decode_samples_with_residual__rice__sse41(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported) { + return drflac__decode_samples_with_residual__rice__neon(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); + } else #endif { /* Scalar fallback. */ @@ -3717,8 +4754,7 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_ { drflac_uint32 i; - drflac_assert(bs != NULL); - drflac_assert(count > 0); + DRFLAC_ASSERT(bs != NULL); for (i = 0; i < count; ++i) { if (!drflac__seek_rice_parts(bs, riceParam)) { @@ -3733,10 +4769,9 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* { drflac_uint32 i; - drflac_assert(bs != NULL); - drflac_assert(count > 0); - drflac_assert(unencodedBitsPerSample <= 31); /* <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. */ - drflac_assert(pSamplesOut != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(unencodedBitsPerSample <= 31); /* <-- unencodedBitsPerSample is a 5 bit number, so cannot exceed 31. */ + DRFLAC_ASSERT(pSamplesOut != NULL); for (i = 0; i < count; ++i) { if (unencodedBitsPerSample > 0) { @@ -3747,7 +4782,7 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* pSamplesOut[i] = 0; } - if (bitsPerSample > 16) { + if (bitsPerSample >= 24) { pSamplesOut[i] += drflac__calculate_prediction_64(order, shift, coefficients, pSamplesOut + i); } else { pSamplesOut[i] += drflac__calculate_prediction_32(order, shift, coefficients, pSamplesOut + i); @@ -3770,9 +4805,9 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ drflac_uint32 samplesInPartition; drflac_uint32 partitionsRemaining; - drflac_assert(bs != NULL); - drflac_assert(blockSize != 0); - drflac_assert(pDecodedSamples != NULL); /* <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? */ + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(blockSize != 0); + DRFLAC_ASSERT(pDecodedSamples != NULL); /* <-- Should we allow NULL, in which case we just seek past the residual rather than do a full decode? */ if (!drflac__read_uint8(bs, 2, &residualMethod)) { return DRFLAC_FALSE; @@ -3798,7 +4833,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ } /* Validation check. */ - if ((blockSize / (1 << partitionOrder)) <= order) { + if ((blockSize / (1 << partitionOrder)) < order) { return DRFLAC_FALSE; } @@ -3827,7 +4862,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ return DRFLAC_FALSE; } } else { - unsigned char unencodedBitsPerSample = 0; + drflac_uint8 unencodedBitsPerSample = 0; if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { return DRFLAC_FALSE; } @@ -3865,8 +4900,8 @@ static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 drflac_uint32 samplesInPartition; drflac_uint32 partitionsRemaining; - drflac_assert(bs != NULL); - drflac_assert(blockSize != 0); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(blockSize != 0); if (!drflac__read_uint8(bs, 2, &residualMethod)) { return DRFLAC_FALSE; @@ -3919,7 +4954,7 @@ static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 return DRFLAC_FALSE; } } else { - unsigned char unencodedBitsPerSample = 0; + drflac_uint8 unencodedBitsPerSample = 0; if (!drflac__read_uint8(bs, 5, &unencodedBitsPerSample)) { return DRFLAC_FALSE; } @@ -3942,13 +4977,13 @@ static drflac_bool32 drflac__read_and_seek_residual(drflac_bs* bs, drflac_uint32 } -static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_int32* pDecodedSamples) +static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) { drflac_uint32 i; /* Only a single sample needs to be decoded here. */ drflac_int32 sample; - if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { return DRFLAC_FALSE; } @@ -3963,13 +4998,13 @@ static drflac_bool32 drflac__decode_samples__constant(drflac_bs* bs, drflac_uint return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_int32* pDecodedSamples) +static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_int32* pDecodedSamples) { drflac_uint32 i; for (i = 0; i < blockSize; ++i) { drflac_int32 sample; - if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { return DRFLAC_FALSE; } @@ -3979,7 +5014,7 @@ static drflac_bool32 drflac__decode_samples__verbatim(drflac_bs* bs, drflac_uint return DRFLAC_TRUE; } -static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 bitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) +static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 blockSize, drflac_uint32 subframeBitsPerSample, drflac_uint8 lpcOrder, drflac_int32* pDecodedSamples) { drflac_uint32 i; @@ -3994,14 +5029,14 @@ static drflac_bool32 drflac__decode_samples__fixed(drflac_bs* bs, drflac_uint32 /* Warm up samples and coefficients. */ for (i = 0; i < lpcOrder; ++i) { drflac_int32 sample; - if (!drflac__read_int32(bs, bitsPerSample, &sample)) { + if (!drflac__read_int32(bs, subframeBitsPerSample, &sample)) { return DRFLAC_FALSE; } pDecodedSamples[i] = sample; } - if (!drflac__decode_samples_with_residual(bs, bitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { + if (!drflac__decode_samples_with_residual(bs, subframeBitsPerSample, blockSize, lpcOrder, 0, lpcCoefficientsTable[lpcOrder], pDecodedSamples)) { return DRFLAC_FALSE; } @@ -4037,7 +5072,19 @@ static drflac_bool32 drflac__decode_samples__lpc(drflac_bs* bs, drflac_uint32 bl return DRFLAC_FALSE; } - drflac_zero_memory(coefficients, sizeof(coefficients)); + /* + From the FLAC specification: + + Quantized linear predictor coefficient shift needed in bits (NOTE: this number is signed two's-complement) + + Emphasis on the "signed two's-complement". In practice there does not seem to be any encoders nor decoders supporting negative shifts. For now dr_flac is + not going to support negative shifts as I don't have any reference files. However, when a reference file comes through I will consider adding support. + */ + if (lpcShift < 0) { + return DRFLAC_FALSE; + } + + DRFLAC_ZERO_MEMORY(coefficients, sizeof(coefficients)); for (i = 0; i < lpcOrder; ++i) { if (!drflac__read_int32(bs, lpcPrecision, coefficients + i)) { return DRFLAC_FALSE; @@ -4057,8 +5104,8 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u const drflac_uint32 sampleRateTable[12] = {0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000}; const drflac_uint8 bitsPerSampleTable[8] = {0, 8, 12, (drflac_uint8)-1, 16, 20, 24, (drflac_uint8)-1}; /* -1 = reserved. */ - drflac_assert(bs != NULL); - drflac_assert(header != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(header != NULL); /* Keep looping until we find a valid sync code. */ for (;;) { @@ -4129,50 +5176,53 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u isVariableBlockSize = blockingStrategy == 1; if (isVariableBlockSize) { - drflac_uint64 sampleNumber; - drflac_result result = drflac__read_utf8_coded_number(bs, &sampleNumber, &crc8); + drflac_uint64 pcmFrameNumber; + drflac_result result = drflac__read_utf8_coded_number(bs, &pcmFrameNumber, &crc8); if (result != DRFLAC_SUCCESS) { - if (result == DRFLAC_END_OF_STREAM) { + if (result == DRFLAC_AT_END) { return DRFLAC_FALSE; } else { continue; } } - header->frameNumber = 0; - header->sampleNumber = sampleNumber; + header->flacFrameNumber = 0; + header->pcmFrameNumber = pcmFrameNumber; } else { - drflac_uint64 frameNumber = 0; - drflac_result result = drflac__read_utf8_coded_number(bs, &frameNumber, &crc8); + drflac_uint64 flacFrameNumber = 0; + drflac_result result = drflac__read_utf8_coded_number(bs, &flacFrameNumber, &crc8); if (result != DRFLAC_SUCCESS) { - if (result == DRFLAC_END_OF_STREAM) { + if (result == DRFLAC_AT_END) { return DRFLAC_FALSE; } else { continue; } } - header->frameNumber = (drflac_uint32)frameNumber; /* <-- Safe cast. */ - header->sampleNumber = 0; + header->flacFrameNumber = (drflac_uint32)flacFrameNumber; /* <-- Safe cast. */ + header->pcmFrameNumber = 0; } + DRFLAC_ASSERT(blockSize > 0); if (blockSize == 1) { - header->blockSize = 192; - } else if (blockSize >= 2 && blockSize <= 5) { - header->blockSize = 576 * (1 << (blockSize - 2)); + header->blockSizeInPCMFrames = 192; + } else if (blockSize <= 5) { + DRFLAC_ASSERT(blockSize >= 2); + header->blockSizeInPCMFrames = 576 * (1 << (blockSize - 2)); } else if (blockSize == 6) { - if (!drflac__read_uint16(bs, 8, &header->blockSize)) { + if (!drflac__read_uint16(bs, 8, &header->blockSizeInPCMFrames)) { return DRFLAC_FALSE; } - crc8 = drflac_crc8(crc8, header->blockSize, 8); - header->blockSize += 1; + crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 8); + header->blockSizeInPCMFrames += 1; } else if (blockSize == 7) { - if (!drflac__read_uint16(bs, 16, &header->blockSize)) { + if (!drflac__read_uint16(bs, 16, &header->blockSizeInPCMFrames)) { return DRFLAC_FALSE; } - crc8 = drflac_crc8(crc8, header->blockSize, 16); - header->blockSize += 1; + crc8 = drflac_crc8(crc8, header->blockSizeInPCMFrames, 16); + header->blockSizeInPCMFrames += 1; } else { - header->blockSize = 256 * (1 << (blockSize - 8)); + DRFLAC_ASSERT(blockSize >= 8); + header->blockSizeInPCMFrames = 256 * (1 << (blockSize - 8)); } @@ -4242,10 +5292,10 @@ static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe } else { if ((type & 0x20) != 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_LPC; - pSubframe->lpcOrder = (type & 0x1F) + 1; + pSubframe->lpcOrder = (drflac_uint8)(type & 0x1F) + 1; } else if ((type & 0x08) != 0) { pSubframe->subframeType = DRFLAC_SUBFRAME_FIXED; - pSubframe->lpcOrder = (type & 0x07); + pSubframe->lpcOrder = (drflac_uint8)(type & 0x07); if (pSubframe->lpcOrder > 4) { pSubframe->subframeType = DRFLAC_SUBFRAME_RESERVED; pSubframe->lpcOrder = 0; @@ -4266,7 +5316,7 @@ static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe if (!drflac__seek_past_next_set_bit(bs, &wastedBitsPerSample)) { return DRFLAC_FALSE; } - pSubframe->wastedBitsPerSample = (unsigned char)wastedBitsPerSample + 1; + pSubframe->wastedBitsPerSample = (drflac_uint8)wastedBitsPerSample + 1; } return DRFLAC_TRUE; @@ -4275,9 +5325,10 @@ static drflac_bool32 drflac__read_subframe_header(drflac_bs* bs, drflac_subframe static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex, drflac_int32* pDecodedSamplesOut) { drflac_subframe* pSubframe; + drflac_uint32 subframeBitsPerSample; - drflac_assert(bs != NULL); - drflac_assert(frame != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(frame != NULL); pSubframe = frame->subframes + subframeIndex; if (!drflac__read_subframe_header(bs, pSubframe)) { @@ -4285,40 +5336,41 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, } /* Side channels require an extra bit per sample. Took a while to figure that one out... */ - pSubframe->bitsPerSample = frame->header.bitsPerSample; + subframeBitsPerSample = frame->header.bitsPerSample; if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { - pSubframe->bitsPerSample += 1; + subframeBitsPerSample += 1; } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { - pSubframe->bitsPerSample += 1; + subframeBitsPerSample += 1; } /* Need to handle wasted bits per sample. */ - if (pSubframe->wastedBitsPerSample >= pSubframe->bitsPerSample) { + if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; } - pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; - pSubframe->pDecodedSamples = pDecodedSamplesOut; + subframeBitsPerSample -= pSubframe->wastedBitsPerSample; + + pSubframe->pSamplesS32 = pDecodedSamplesOut; switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: { - drflac__decode_samples__constant(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->pDecodedSamples); + drflac__decode_samples__constant(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_VERBATIM: { - drflac__decode_samples__verbatim(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->pDecodedSamples); + drflac__decode_samples__verbatim(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_FIXED: { - drflac__decode_samples__fixed(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->lpcOrder, pSubframe->pDecodedSamples); + drflac__decode_samples__fixed(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); } break; case DRFLAC_SUBFRAME_LPC: { - drflac__decode_samples__lpc(bs, frame->header.blockSize, pSubframe->bitsPerSample, pSubframe->lpcOrder, pSubframe->pDecodedSamples); + drflac__decode_samples__lpc(bs, frame->header.blockSizeInPCMFrames, subframeBitsPerSample, pSubframe->lpcOrder, pSubframe->pSamplesS32); } break; default: return DRFLAC_FALSE; @@ -4330,9 +5382,10 @@ static drflac_bool32 drflac__decode_subframe(drflac_bs* bs, drflac_frame* frame, static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, int subframeIndex) { drflac_subframe* pSubframe; + drflac_uint32 subframeBitsPerSample; - drflac_assert(bs != NULL); - drflac_assert(frame != NULL); + DRFLAC_ASSERT(bs != NULL); + DRFLAC_ASSERT(frame != NULL); pSubframe = frame->subframes + subframeIndex; if (!drflac__read_subframe_header(bs, pSubframe)) { @@ -4340,32 +5393,33 @@ static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, i } /* Side channels require an extra bit per sample. Took a while to figure that one out... */ - pSubframe->bitsPerSample = frame->header.bitsPerSample; + subframeBitsPerSample = frame->header.bitsPerSample; if ((frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE || frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE) && subframeIndex == 1) { - pSubframe->bitsPerSample += 1; + subframeBitsPerSample += 1; } else if (frame->header.channelAssignment == DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE && subframeIndex == 0) { - pSubframe->bitsPerSample += 1; + subframeBitsPerSample += 1; } /* Need to handle wasted bits per sample. */ - if (pSubframe->wastedBitsPerSample >= pSubframe->bitsPerSample) { + if (pSubframe->wastedBitsPerSample >= subframeBitsPerSample) { return DRFLAC_FALSE; } - pSubframe->bitsPerSample -= pSubframe->wastedBitsPerSample; - pSubframe->pDecodedSamples = NULL; + subframeBitsPerSample -= pSubframe->wastedBitsPerSample; + + pSubframe->pSamplesS32 = NULL; switch (pSubframe->subframeType) { case DRFLAC_SUBFRAME_CONSTANT: { - if (!drflac__seek_bits(bs, pSubframe->bitsPerSample)) { + if (!drflac__seek_bits(bs, subframeBitsPerSample)) { return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_VERBATIM: { - unsigned int bitsToSeek = frame->header.blockSize * pSubframe->bitsPerSample; + unsigned int bitsToSeek = frame->header.blockSizeInPCMFrames * subframeBitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } @@ -4373,21 +5427,21 @@ static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, i case DRFLAC_SUBFRAME_FIXED: { - unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; + unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } - if (!drflac__read_and_seek_residual(bs, frame->header.blockSize, pSubframe->lpcOrder)) { + if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { return DRFLAC_FALSE; } } break; case DRFLAC_SUBFRAME_LPC: { - unsigned char lpcPrecision; + drflac_uint8 lpcPrecision; - unsigned int bitsToSeek = pSubframe->lpcOrder * pSubframe->bitsPerSample; + unsigned int bitsToSeek = pSubframe->lpcOrder * subframeBitsPerSample; if (!drflac__seek_bits(bs, bitsToSeek)) { return DRFLAC_FALSE; } @@ -4406,7 +5460,7 @@ static drflac_bool32 drflac__seek_subframe(drflac_bs* bs, drflac_frame* frame, i return DRFLAC_FALSE; } - if (!drflac__read_and_seek_residual(bs, frame->header.blockSize, pSubframe->lpcOrder)) { + if (!drflac__read_and_seek_residual(bs, frame->header.blockSizeInPCMFrames, pSubframe->lpcOrder)) { return DRFLAC_FALSE; } } break; @@ -4422,7 +5476,7 @@ static DRFLAC_INLINE drflac_uint8 drflac__get_channel_count_from_channel_assignm { drflac_uint8 lookup[] = {1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 2}; - drflac_assert(channelAssignment <= 10); + DRFLAC_ASSERT(channelAssignment <= 10); return lookup[channelAssignment]; } @@ -4437,30 +5491,30 @@ static drflac_result drflac__decode_flac_frame(drflac* pFlac) #endif /* This function should be called while the stream is sitting on the first byte after the frame header. */ - drflac_zero_memory(pFlac->currentFrame.subframes, sizeof(pFlac->currentFrame.subframes)); + DRFLAC_ZERO_MEMORY(pFlac->currentFLACFrame.subframes, sizeof(pFlac->currentFLACFrame.subframes)); /* The frame block size must never be larger than the maximum block size defined by the FLAC stream. */ - if (pFlac->currentFrame.header.blockSize > pFlac->maxBlockSize) { + if (pFlac->currentFLACFrame.header.blockSizeInPCMFrames > pFlac->maxBlockSizeInPCMFrames) { return DRFLAC_ERROR; } /* The number of channels in the frame must match the channel count from the STREAMINFO block. */ - channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); if (channelCount != (int)pFlac->channels) { return DRFLAC_ERROR; } for (i = 0; i < channelCount; ++i) { - if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFrame, i, pFlac->pDecodedSamples + ((pFlac->currentFrame.header.blockSize+DRFLAC_LEADING_SAMPLES) * i) + DRFLAC_LEADING_SAMPLES)) { + if (!drflac__decode_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i, pFlac->pDecodedSamples + (pFlac->currentFLACFrame.header.blockSizeInPCMFrames * i))) { return DRFLAC_ERROR; } } - paddingSizeInBits = DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7; + paddingSizeInBits = (drflac_uint8)(DRFLAC_CACHE_L1_BITS_REMAINING(&pFlac->bs) & 7); if (paddingSizeInBits > 0) { drflac_uint8 padding = 0; if (!drflac__read_uint8(&pFlac->bs, paddingSizeInBits, &padding)) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } } @@ -4468,7 +5522,7 @@ static drflac_result drflac__decode_flac_frame(drflac* pFlac) actualCRC16 = drflac__flush_crc16(&pFlac->bs); #endif if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } #ifndef DR_FLAC_NO_CRC @@ -4477,7 +5531,7 @@ static drflac_result drflac__decode_flac_frame(drflac* pFlac) } #endif - pFlac->currentFrame.samplesRemaining = pFlac->currentFrame.header.blockSize * channelCount; + pFlac->currentFLACFrame.pcmFramesRemaining = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; return DRFLAC_SUCCESS; } @@ -4491,9 +5545,9 @@ static drflac_result drflac__seek_flac_frame(drflac* pFlac) drflac_uint16 actualCRC16; #endif - channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); + channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); for (i = 0; i < channelCount; ++i) { - if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFrame, i)) { + if (!drflac__seek_subframe(&pFlac->bs, &pFlac->currentFLACFrame, i)) { return DRFLAC_ERROR; } } @@ -4508,7 +5562,7 @@ static drflac_result drflac__seek_flac_frame(drflac* pFlac) actualCRC16 = drflac__flush_crc16(&pFlac->bs); #endif if (!drflac__read_uint16(&pFlac->bs, 16, &desiredCRC16)) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } #ifndef DR_FLAC_NO_CRC @@ -4522,12 +5576,12 @@ static drflac_result drflac__seek_flac_frame(drflac* pFlac) static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) { - drflac_assert(pFlac != NULL); + DRFLAC_ASSERT(pFlac != NULL); for (;;) { drflac_result result; - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } @@ -4544,50 +5598,19 @@ static drflac_bool32 drflac__read_and_decode_next_flac_frame(drflac* pFlac) } } - -static void drflac__get_current_frame_sample_range(drflac* pFlac, drflac_uint64* pFirstSampleInFrameOut, drflac_uint64* pLastSampleInFrameOut) -{ - unsigned int channelCount; - drflac_uint64 firstSampleInFrame; - drflac_uint64 lastSampleInFrame; - - drflac_assert(pFlac != NULL); - - channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - - firstSampleInFrame = pFlac->currentFrame.header.sampleNumber*channelCount; - if (firstSampleInFrame == 0) { - firstSampleInFrame = pFlac->currentFrame.header.frameNumber * pFlac->maxBlockSize*channelCount; - } - - lastSampleInFrame = firstSampleInFrame + (pFlac->currentFrame.header.blockSize*channelCount); - if (lastSampleInFrame > 0) { - lastSampleInFrame -= 1; /* Needs to be zero based. */ - } - - if (pFirstSampleInFrameOut) { - *pFirstSampleInFrameOut = firstSampleInFrame; - } - if (pLastSampleInFrameOut) { - *pLastSampleInFrameOut = lastSampleInFrame; - } -} - -/* This function will be replacing drflac__get_current_frame_sample_range(), but it's not currently used so I have commented it out to silence a compiler warning. */ -#if 0 static void drflac__get_pcm_frame_range_of_current_flac_frame(drflac* pFlac, drflac_uint64* pFirstPCMFrame, drflac_uint64* pLastPCMFrame) { drflac_uint64 firstPCMFrame; drflac_uint64 lastPCMFrame; - drflac_assert(pFlac != NULL); + DRFLAC_ASSERT(pFlac != NULL); - firstPCMFrame = pFlac->currentFrame.header.sampleNumber; + firstPCMFrame = pFlac->currentFLACFrame.header.pcmFrameNumber; if (firstPCMFrame == 0) { - firstPCMFrame = pFlac->currentFrame.header.frameNumber * pFlac->maxBlockSize; + firstPCMFrame = ((drflac_uint64)pFlac->currentFLACFrame.header.flacFrameNumber) * pFlac->maxBlockSizeInPCMFrames; } - lastPCMFrame = firstPCMFrame + (pFlac->currentFrame.header.blockSize); + lastPCMFrame = firstPCMFrame + pFlac->currentFLACFrame.header.blockSizeInPCMFrames; if (lastPCMFrame > 0) { lastPCMFrame -= 1; /* Needs to be zero based. */ } @@ -4599,18 +5622,17 @@ static void drflac__get_pcm_frame_range_of_current_flac_frame(drflac* pFlac, drf *pLastPCMFrame = lastPCMFrame; } } -#endif static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) { drflac_bool32 result; - drflac_assert(pFlac != NULL); + DRFLAC_ASSERT(pFlac != NULL); - result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos); + result = drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes); - drflac_zero_memory(&pFlac->currentFrame, sizeof(pFlac->currentFrame)); - pFlac->currentSample = 0; + DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); + pFlac->currentPCMFrame = 0; return result; } @@ -4618,55 +5640,52 @@ static drflac_bool32 drflac__seek_to_first_frame(drflac* pFlac) static DRFLAC_INLINE drflac_result drflac__seek_to_next_flac_frame(drflac* pFlac) { /* This function should only ever be called while the decoder is sitting on the first byte past the FRAME_HEADER section. */ - drflac_assert(pFlac != NULL); + DRFLAC_ASSERT(pFlac != NULL); return drflac__seek_flac_frame(pFlac); } -drflac_uint64 drflac__seek_forward_by_samples(drflac* pFlac, drflac_uint64 samplesToRead) + +static drflac_uint64 drflac__seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 pcmFramesToSeek) { - drflac_uint64 samplesRead = 0; - while (samplesToRead > 0) { - if (pFlac->currentFrame.samplesRemaining == 0) { + drflac_uint64 pcmFramesRead = 0; + while (pcmFramesToSeek > 0) { + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { - if (pFlac->currentFrame.samplesRemaining > samplesToRead) { - samplesRead += samplesToRead; - pFlac->currentFrame.samplesRemaining -= (drflac_uint32)samplesToRead; /* <-- Safe cast. Will always be < currentFrame.samplesRemaining < 65536. */ - samplesToRead = 0; + if (pFlac->currentFLACFrame.pcmFramesRemaining > pcmFramesToSeek) { + pcmFramesRead += pcmFramesToSeek; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)pcmFramesToSeek; /* <-- Safe cast. Will always be < currentFrame.pcmFramesRemaining < 65536. */ + pcmFramesToSeek = 0; } else { - samplesRead += pFlac->currentFrame.samplesRemaining; - samplesToRead -= pFlac->currentFrame.samplesRemaining; - pFlac->currentFrame.samplesRemaining = 0; + pcmFramesRead += pFlac->currentFLACFrame.pcmFramesRemaining; + pcmFramesToSeek -= pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; } } } - pFlac->currentSample += samplesRead; - return samplesRead; + pFlac->currentPCMFrame += pcmFramesRead; + return pcmFramesRead; } -drflac_uint64 drflac__seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 pcmFramesToSeek) -{ - return drflac__seek_forward_by_samples(pFlac, pcmFramesToSeek*pFlac->channels); -} -static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_uint64 sampleIndex) +static drflac_bool32 drflac__seek_to_pcm_frame__brute_force(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_bool32 isMidFrame = DRFLAC_FALSE; - drflac_uint64 runningSampleCount; + drflac_uint64 runningPCMFrameCount; - drflac_assert(pFlac != NULL); + DRFLAC_ASSERT(pFlac != NULL); /* If we are seeking forward we start from the current position. Otherwise we need to start all the way from the start of the file. */ - if (sampleIndex >= pFlac->currentSample) { + if (pcmFrameIndex >= pFlac->currentPCMFrame) { /* Seeking forward. Need to seek from the current position. */ - runningSampleCount = pFlac->currentSample; + runningPCMFrameCount = pFlac->currentPCMFrame; /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ - if (pFlac->currentSample == 0 && pFlac->currentFrame.samplesRemaining == 0) { - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } else { @@ -4674,7 +5693,7 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u } } else { /* Seeking backwards. Need to seek from the start of the file. */ - runningSampleCount = 0; + runningPCMFrameCount = 0; /* Move back to the start. */ if (!drflac__seek_to_first_frame(pFlac)) { @@ -4682,7 +5701,7 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u } /* Decode the first frame in preparation for sample-exact seeking below. */ - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } @@ -4692,25 +5711,25 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u header. If based on the header we can determine that the frame contains the sample, we do a full decode of that frame. */ for (;;) { - drflac_uint64 sampleCountInThisFrame; - drflac_uint64 firstSampleInFrame = 0; - drflac_uint64 lastSampleInFrame = 0; + drflac_uint64 pcmFrameCountInThisFLACFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; - drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); - sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; - if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { + pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { /* The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend it never existed and keep iterating. */ - drflac_uint64 samplesToDecode = sampleIndex - runningSampleCount; + drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; if (!isMidFrame) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ - return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ @@ -4720,7 +5739,7 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u } } else { /* We started seeking mid-frame which means we need to skip the frame decoding part. */ - return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; } } else { /* @@ -4730,7 +5749,7 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u if (!isMidFrame) { drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { - runningSampleCount += sampleCountInThisFrame; + runningPCMFrameCount += pcmFrameCountInThisFLACFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ @@ -4743,53 +5762,327 @@ static drflac_bool32 drflac__seek_to_sample__brute_force(drflac* pFlac, drflac_u We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. */ - runningSampleCount += pFlac->currentFrame.samplesRemaining; - pFlac->currentFrame.samplesRemaining = 0; + runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; isMidFrame = DRFLAC_FALSE; } + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { + return DRFLAC_TRUE; + } } next_iteration: /* Grab the next frame in preparation for the next iteration. */ - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } } -static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_uint64 sampleIndex) +#if !defined(DR_FLAC_NO_CRC) +/* +We use an average compression ratio to determine our approximate start location. FLAC files are generally about 50%-70% the size of their +uncompressed counterparts so we'll use this as a basis. I'm going to split the middle and use a factor of 0.6 to determine the starting +location. +*/ +#define DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO 0.6f + +static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFlac, drflac_uint64 targetByte, drflac_uint64 rangeLo, drflac_uint64 rangeHi, drflac_uint64* pLastSuccessfulSeekOffset) +{ + DRFLAC_ASSERT(pFlac != NULL); + DRFLAC_ASSERT(pLastSuccessfulSeekOffset != NULL); + DRFLAC_ASSERT(targetByte >= rangeLo); + DRFLAC_ASSERT(targetByte <= rangeHi); + + *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; + + for (;;) { + /* After rangeLo == rangeHi == targetByte fails, we need to break out. */ + drflac_uint64 lastTargetByte = targetByte; + + /* When seeking to a byte, failure probably means we've attempted to seek beyond the end of the stream. To counter this we just halve it each attempt. */ + if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { + /* If we couldn't even seek to the first byte in the stream we have a problem. Just abandon the whole thing. */ + if (targetByte == 0) { + drflac__seek_to_first_frame(pFlac); /* Try to recover. */ + return DRFLAC_FALSE; + } + + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + /* Getting here should mean that we have seeked to an appropriate byte. */ + + /* Clear the details of the FLAC frame so we don't misreport data. */ + DRFLAC_ZERO_MEMORY(&pFlac->currentFLACFrame, sizeof(pFlac->currentFLACFrame)); + + /* + Now seek to the next FLAC frame. We need to decode the entire frame (not just the header) because it's possible for the header to incorrectly pass the + CRC check and return bad data. We need to decode the entire frame to be more certain. Although this seems unlikely, this has happened to me in testing + so it needs to stay this way for now. + */ +#if 1 + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + break; + } +#else + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + /* Halve the byte location and continue. */ + targetByte = rangeLo + ((rangeHi - rangeLo)/2); + rangeHi = targetByte; + } else { + break; + } +#endif + } + + /* We already tried this byte and there are no more to try, break out. */ + if(targetByte == lastTargetByte) { + return DRFLAC_FALSE; + } + } + + /* The current PCM frame needs to be updated based on the frame we just seeked to. */ + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); + + DRFLAC_ASSERT(targetByte <= rangeHi); + + *pLastSuccessfulSeekOffset = targetByte; + return DRFLAC_TRUE; +} + +static drflac_bool32 drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(drflac* pFlac, drflac_uint64 offset) +{ + /* This section of code would be used if we were only decoding the FLAC frame header when calling drflac__seek_to_approximate_flac_frame_to_byte(). */ +#if 0 + if (drflac__decode_flac_frame(pFlac) != DRFLAC_SUCCESS) { + /* We failed to decode this frame which may be due to it being corrupt. We'll just use the next valid FLAC frame. */ + if (drflac__read_and_decode_next_flac_frame(pFlac) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + } +#endif + + return drflac__seek_forward_by_pcm_frames(pFlac, offset) == offset; +} + + +static drflac_bool32 drflac__seek_to_pcm_frame__binary_search_internal(drflac* pFlac, drflac_uint64 pcmFrameIndex, drflac_uint64 byteRangeLo, drflac_uint64 byteRangeHi) +{ + /* This assumes pFlac->currentPCMFrame is sitting on byteRangeLo upon entry. */ + + drflac_uint64 targetByte; + drflac_uint64 pcmRangeLo = pFlac->totalPCMFrameCount; + drflac_uint64 pcmRangeHi = 0; + drflac_uint64 lastSuccessfulSeekOffset = (drflac_uint64)-1; + drflac_uint64 closestSeekOffsetBeforeTargetPCMFrame = byteRangeLo; + drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; + + targetByte = byteRangeLo + (drflac_uint64)(((drflac_int64)((pcmFrameIndex - pFlac->currentPCMFrame) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * DRFLAC_BINARY_SEARCH_APPROX_COMPRESSION_RATIO); + if (targetByte > byteRangeHi) { + targetByte = byteRangeHi; + } + + for (;;) { + if (drflac__seek_to_approximate_flac_frame_to_byte(pFlac, targetByte, byteRangeLo, byteRangeHi, &lastSuccessfulSeekOffset)) { + /* We found a FLAC frame. We need to check if it contains the sample we're looking for. */ + drflac_uint64 newPCMRangeLo; + drflac_uint64 newPCMRangeHi; + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &newPCMRangeLo, &newPCMRangeHi); + + /* If we selected the same frame, it means we should be pretty close. Just decode the rest. */ + if (pcmRangeLo == newPCMRangeLo) { + if (!drflac__seek_to_approximate_flac_frame_to_byte(pFlac, closestSeekOffsetBeforeTargetPCMFrame, closestSeekOffsetBeforeTargetPCMFrame, byteRangeHi, &lastSuccessfulSeekOffset)) { + break; /* Failed to seek to closest frame. */ + } + + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek forward. */ + } + } + + pcmRangeLo = newPCMRangeLo; + pcmRangeHi = newPCMRangeHi; + + if (pcmRangeLo <= pcmFrameIndex && pcmRangeHi >= pcmFrameIndex) { + /* The target PCM frame is in this FLAC frame. */ + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame) ) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek to FLAC frame. */ + } + } else { + const float approxCompressionRatio = (drflac_int64)(lastSuccessfulSeekOffset - pFlac->firstFLACFramePosInBytes) / ((drflac_int64)(pcmRangeLo * pFlac->channels * pFlac->bitsPerSample)/8.0f); + + if (pcmRangeLo > pcmFrameIndex) { + /* We seeked too far forward. We need to move our target byte backward and try again. */ + byteRangeHi = lastSuccessfulSeekOffset; + if (byteRangeLo > byteRangeHi) { + byteRangeLo = byteRangeHi; + } + + targetByte = byteRangeLo + ((byteRangeHi - byteRangeLo) / 2); + if (targetByte < byteRangeLo) { + targetByte = byteRangeLo; + } + } else /*if (pcmRangeHi < pcmFrameIndex)*/ { + /* We didn't seek far enough. We need to move our target byte forward and try again. */ + + /* If we're close enough we can just seek forward. */ + if ((pcmFrameIndex - pcmRangeLo) < seekForwardThreshold) { + if (drflac__decode_flac_frame_and_seek_forward_by_pcm_frames(pFlac, pcmFrameIndex - pFlac->currentPCMFrame)) { + return DRFLAC_TRUE; + } else { + break; /* Failed to seek to FLAC frame. */ + } + } else { + byteRangeLo = lastSuccessfulSeekOffset; + if (byteRangeHi < byteRangeLo) { + byteRangeHi = byteRangeLo; + } + + targetByte = lastSuccessfulSeekOffset + (drflac_uint64)(((drflac_int64)((pcmFrameIndex-pcmRangeLo) * pFlac->channels * pFlac->bitsPerSample)/8.0f) * approxCompressionRatio); + if (targetByte > byteRangeHi) { + targetByte = byteRangeHi; + } + + if (closestSeekOffsetBeforeTargetPCMFrame < lastSuccessfulSeekOffset) { + closestSeekOffsetBeforeTargetPCMFrame = lastSuccessfulSeekOffset; + } + } + } + } + } else { + /* Getting here is really bad. We just recover as best we can, but moving to the first frame in the stream, and then abort. */ + break; + } + } + + drflac__seek_to_first_frame(pFlac); /* <-- Try to recover. */ + return DRFLAC_FALSE; +} + +static drflac_bool32 drflac__seek_to_pcm_frame__binary_search(drflac* pFlac, drflac_uint64 pcmFrameIndex) +{ + drflac_uint64 byteRangeLo; + drflac_uint64 byteRangeHi; + drflac_uint32 seekForwardThreshold = (pFlac->maxBlockSizeInPCMFrames != 0) ? pFlac->maxBlockSizeInPCMFrames*2 : 4096; + + /* Our algorithm currently assumes the FLAC stream is currently sitting at the start. */ + if (drflac__seek_to_first_frame(pFlac) == DRFLAC_FALSE) { + return DRFLAC_FALSE; + } + + /* If we're close enough to the start, just move to the start and seek forward. */ + if (pcmFrameIndex < seekForwardThreshold) { + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFrameIndex) == pcmFrameIndex; + } + + /* + Our starting byte range is the byte position of the first FLAC frame and the approximate end of the file as if it were completely uncompressed. This ensures + the entire file is included, even though most of the time it'll exceed the end of the actual stream. This is OK as the frame searching logic will handle it. + */ + byteRangeLo = pFlac->firstFLACFramePosInBytes; + byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); + + return drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi); +} +#endif /* !DR_FLAC_NO_CRC */ + +static drflac_bool32 drflac__seek_to_pcm_frame__seek_table(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_uint32 iClosestSeekpoint = 0; drflac_bool32 isMidFrame = DRFLAC_FALSE; - drflac_uint64 runningSampleCount; + drflac_uint64 runningPCMFrameCount; drflac_uint32 iSeekpoint; - drflac_assert(pFlac != NULL); + + DRFLAC_ASSERT(pFlac != NULL); if (pFlac->pSeekpoints == NULL || pFlac->seekpointCount == 0) { return DRFLAC_FALSE; } for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { - if (pFlac->pSeekpoints[iSeekpoint].firstSample*pFlac->channels >= sampleIndex) { + if (pFlac->pSeekpoints[iSeekpoint].firstPCMFrame >= pcmFrameIndex) { break; } iClosestSeekpoint = iSeekpoint; } + /* There's been cases where the seek table contains only zeros. We need to do some basic validation on the closest seekpoint. */ + if (pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount == 0 || pFlac->pSeekpoints[iClosestSeekpoint].pcmFrameCount > pFlac->maxBlockSizeInPCMFrames) { + return DRFLAC_FALSE; + } + if (pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame > pFlac->totalPCMFrameCount && pFlac->totalPCMFrameCount > 0) { + return DRFLAC_FALSE; + } + +#if !defined(DR_FLAC_NO_CRC) + /* At this point we should know the closest seek point. We can use a binary search for this. We need to know the total sample count for this. */ + if (pFlac->totalPCMFrameCount > 0) { + drflac_uint64 byteRangeLo; + drflac_uint64 byteRangeHi; + + byteRangeHi = pFlac->firstFLACFramePosInBytes + (drflac_uint64)((drflac_int64)(pFlac->totalPCMFrameCount * pFlac->channels * pFlac->bitsPerSample)/8.0f); + byteRangeLo = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset; + + /* + If our closest seek point is not the last one, we only need to search between it and the next one. The section below calculates an appropriate starting + value for byteRangeHi which will clamp it appropriately. + + Note that the next seekpoint must have an offset greater than the closest seekpoint because otherwise our binary search algorithm will break down. There + have been cases where a seektable consists of seek points where every byte offset is set to 0 which causes problems. If this happens we need to abort. + */ + if (iClosestSeekpoint < pFlac->seekpointCount-1) { + drflac_uint32 iNextSeekpoint = iClosestSeekpoint + 1; + + /* Basic validation on the seekpoints to ensure they're usable. */ + if (pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset >= pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset || pFlac->pSeekpoints[iNextSeekpoint].pcmFrameCount == 0) { + return DRFLAC_FALSE; /* The next seekpoint doesn't look right. The seek table cannot be trusted from here. Abort. */ + } + + if (pFlac->pSeekpoints[iNextSeekpoint].firstPCMFrame != (((drflac_uint64)0xFFFFFFFF << 32) | 0xFFFFFFFF)) { /* Make sure it's not a placeholder seekpoint. */ + byteRangeHi = pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iNextSeekpoint].flacFrameOffset - 1; /* byteRangeHi must be zero based. */ + } + } + + if (drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { + if (drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); + + if (drflac__seek_to_pcm_frame__binary_search_internal(pFlac, pcmFrameIndex, byteRangeLo, byteRangeHi)) { + return DRFLAC_TRUE; + } + } + } + } +#endif /* !DR_FLAC_NO_CRC */ + + /* Getting here means we need to use a slower algorithm because the binary search method failed or cannot be used. */ + /* - At this point we should have found the seekpoint closest to our sample. If we are seeking forward and the closest seekpoint is _before_ the current sample, we - just seek forward from where we are. Otherwise we start seeking from the seekpoint's first sample. + If we are seeking forward and the closest seekpoint is _before_ the current sample, we just seek forward from where we are. Otherwise we start seeking + from the seekpoint's first sample. */ - if ((sampleIndex >= pFlac->currentSample) && (pFlac->pSeekpoints[iClosestSeekpoint].firstSample*pFlac->channels <= pFlac->currentSample)) { + if (pcmFrameIndex >= pFlac->currentPCMFrame && pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame <= pFlac->currentPCMFrame) { /* Optimized case. Just seek forward from where we are. */ - runningSampleCount = pFlac->currentSample; + runningPCMFrameCount = pFlac->currentPCMFrame; /* The frame header for the first frame may not yet have been read. We need to do that if necessary. */ - if (pFlac->currentSample == 0 && pFlac->currentFrame.samplesRemaining == 0) { - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (pFlac->currentPCMFrame == 0 && pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } else { @@ -4797,37 +6090,38 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui } } else { /* Slower case. Seek to the start of the seekpoint and then seek forward from there. */ - runningSampleCount = pFlac->pSeekpoints[iClosestSeekpoint].firstSample*pFlac->channels; + runningPCMFrameCount = pFlac->pSeekpoints[iClosestSeekpoint].firstPCMFrame; - if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos + pFlac->pSeekpoints[iClosestSeekpoint].frameOffset)) { + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes + pFlac->pSeekpoints[iClosestSeekpoint].flacFrameOffset)) { return DRFLAC_FALSE; } /* Grab the frame the seekpoint is sitting on in preparation for the sample-exact seeking below. */ - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } for (;;) { - drflac_uint64 sampleCountInThisFrame; - drflac_uint64 firstSampleInFrame = 0; - drflac_uint64 lastSampleInFrame = 0; - drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); + drflac_uint64 pcmFrameCountInThisFLACFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; - sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; - if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); + + pcmFrameCountInThisFLACFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFLACFrame)) { /* The sample should be in this frame. We need to fully decode it, but if it's an invalid frame (a CRC mismatch) we need to pretend it never existed and keep iterating. */ - drflac_uint64 samplesToDecode = sampleIndex - runningSampleCount; + drflac_uint64 pcmFramesToDecode = pcmFrameIndex - runningPCMFrameCount; if (!isMidFrame) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ - return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ @@ -4837,7 +6131,7 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui } } else { /* We started seeking mid-frame which means we need to skip the frame decoding part. */ - return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; } } else { /* @@ -4847,7 +6141,7 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui if (!isMidFrame) { drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { - runningSampleCount += sampleCountInThisFrame; + runningPCMFrameCount += pcmFrameCountInThisFLACFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { goto next_iteration; /* CRC mismatch. Pretend this frame never existed. */ @@ -4860,15 +6154,20 @@ static drflac_bool32 drflac__seek_to_sample__seek_table(drflac* pFlac, drflac_ui We started seeking mid-frame which means we need to seek by reading to the end of the frame instead of with drflac__seek_to_next_flac_frame() which only works if the decoder is sitting on the byte just after the frame header. */ - runningSampleCount += pFlac->currentFrame.samplesRemaining; - pFlac->currentFrame.samplesRemaining = 0; + runningPCMFrameCount += pFlac->currentFLACFrame.pcmFramesRemaining; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; isMidFrame = DRFLAC_FALSE; } + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && runningPCMFrameCount == pFlac->totalPCMFrameCount) { + return DRFLAC_TRUE; + } } next_iteration: /* Grab the next frame in preparation for the next iteration. */ - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } } @@ -4901,8 +6200,8 @@ typedef struct drflac_uint32 sampleRate; drflac_uint8 channels; drflac_uint8 bitsPerSample; - drflac_uint64 totalSampleCount; - drflac_uint16 maxBlockSize; + drflac_uint64 totalPCMFrameCount; + drflac_uint16 maxBlockSizeInPCMFrames; drflac_uint64 runningFilePos; drflac_bool32 hasStreamInfoBlock; drflac_bool32 hasMetadataBlocks; @@ -4919,14 +6218,16 @@ typedef struct static DRFLAC_INLINE void drflac__decode_block_header(drflac_uint32 blockHeader, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) { blockHeader = drflac__be2host_32(blockHeader); - *isLastBlock = (blockHeader & 0x80000000UL) >> 31; - *blockType = (blockHeader & 0x7F000000UL) >> 24; - *blockSize = (blockHeader & 0x00FFFFFFUL); + *isLastBlock = (drflac_uint8)((blockHeader & 0x80000000UL) >> 31); + *blockType = (drflac_uint8)((blockHeader & 0x7F000000UL) >> 24); + *blockSize = (blockHeader & 0x00FFFFFFUL); } static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_read_proc onRead, void* pUserData, drflac_uint8* isLastBlock, drflac_uint8* blockType, drflac_uint32* blockSize) { drflac_uint32 blockHeader; + + *blockSize = 0; if (onRead(pUserData, &blockHeader, 4) != 4) { return DRFLAC_FALSE; } @@ -4935,7 +6236,7 @@ static DRFLAC_INLINE drflac_bool32 drflac__read_and_decode_block_header(drflac_r return DRFLAC_TRUE; } -drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) +static drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, drflac_streaminfo* pStreamInfo) { drflac_uint32 blockSizes; drflac_uint64 frameSizes = 0; @@ -4966,20 +6267,100 @@ drflac_bool32 drflac__read_streaminfo(drflac_read_proc onRead, void* pUserData, frameSizes = drflac__be2host_64(frameSizes); importantProps = drflac__be2host_64(importantProps); - pStreamInfo->minBlockSize = (blockSizes & 0xFFFF0000) >> 16; - pStreamInfo->maxBlockSize = (blockSizes & 0x0000FFFF); - pStreamInfo->minFrameSize = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 24)) >> 40); - pStreamInfo->maxFrameSize = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 0)) >> 16); - pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (((drflac_uint64)0x000FFFFF << 16) << 28)) >> 44); - pStreamInfo->channels = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000000E << 16) << 24)) >> 41) + 1; - pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000001F << 16) << 20)) >> 36) + 1; - pStreamInfo->totalSampleCount = ((importantProps & ((((drflac_uint64)0x0000000F << 16) << 16) | 0xFFFFFFFF))) * pStreamInfo->channels; - drflac_copy_memory(pStreamInfo->md5, md5, sizeof(md5)); + pStreamInfo->minBlockSizeInPCMFrames = (drflac_uint16)((blockSizes & 0xFFFF0000) >> 16); + pStreamInfo->maxBlockSizeInPCMFrames = (drflac_uint16) (blockSizes & 0x0000FFFF); + pStreamInfo->minFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 24)) >> 40); + pStreamInfo->maxFrameSizeInPCMFrames = (drflac_uint32)((frameSizes & (((drflac_uint64)0x00FFFFFF << 16) << 0)) >> 16); + pStreamInfo->sampleRate = (drflac_uint32)((importantProps & (((drflac_uint64)0x000FFFFF << 16) << 28)) >> 44); + pStreamInfo->channels = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000000E << 16) << 24)) >> 41) + 1; + pStreamInfo->bitsPerSample = (drflac_uint8 )((importantProps & (((drflac_uint64)0x0000001F << 16) << 20)) >> 36) + 1; + pStreamInfo->totalPCMFrameCount = ((importantProps & ((((drflac_uint64)0x0000000F << 16) << 16) | 0xFFFFFFFF))); + DRFLAC_COPY_MEMORY(pStreamInfo->md5, md5, sizeof(md5)); return DRFLAC_TRUE; } -drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeektableSize) + +static void* drflac__malloc_default(size_t sz, void* pUserData) +{ + (void)pUserData; + return DRFLAC_MALLOC(sz); +} + +static void* drflac__realloc_default(void* p, size_t sz, void* pUserData) +{ + (void)pUserData; + return DRFLAC_REALLOC(p, sz); +} + +static void drflac__free_default(void* p, void* pUserData) +{ + (void)pUserData; + DRFLAC_FREE(p); +} + + +static void* drflac__malloc_from_callbacks(size_t sz, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } + + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } + + return NULL; +} + +static void* drflac__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } + + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } + + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; + + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; + } + + if (p != NULL) { + DRFLAC_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } + + return p2; + } + + return NULL; +} + +static void drflac__free_from_callbacks(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_uint64* pFirstFramePos, drflac_uint64* pSeektablePos, drflac_uint32* pSeektableSize, drflac_allocation_callbacks* pAllocationCallbacks) { /* We want to keep track of the byte position in the stream of the seektable. At the time of calling this function we know that @@ -4994,7 +6375,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s drflac_uint8 isLastBlock = 0; drflac_uint8 blockType; drflac_uint32 blockSize; - if (!drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize)) { + if (drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == DRFLAC_FALSE) { return DRFLAC_FALSE; } runningFilePos += 4; @@ -5012,13 +6393,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s } if (onMeta) { - void* pRawData = DRFLAC_MALLOC(blockSize); + void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5029,7 +6410,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s metadata.data.application.dataSize = blockSize - sizeof(drflac_uint32); onMeta(pUserDataMD, &metadata); - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; @@ -5042,13 +6423,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s drflac_uint32 iSeekpoint; void* pRawData; - pRawData = DRFLAC_MALLOC(blockSize); + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5060,14 +6441,14 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s /* Endian swap. */ for (iSeekpoint = 0; iSeekpoint < metadata.data.seektable.seekpointCount; ++iSeekpoint) { drflac_seekpoint* pSeekpoint = (drflac_seekpoint*)pRawData + iSeekpoint; - pSeekpoint->firstSample = drflac__be2host_64(pSeekpoint->firstSample); - pSeekpoint->frameOffset = drflac__be2host_64(pSeekpoint->frameOffset); - pSeekpoint->sampleCount = drflac__be2host_16(pSeekpoint->sampleCount); + pSeekpoint->firstPCMFrame = drflac__be2host_64(pSeekpoint->firstPCMFrame); + pSeekpoint->flacFrameOffset = drflac__be2host_64(pSeekpoint->flacFrameOffset); + pSeekpoint->pcmFrameCount = drflac__be2host_16(pSeekpoint->pcmFrameCount); } onMeta(pUserDataMD, &metadata); - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; @@ -5083,13 +6464,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s const char* pRunningDataEnd; drflac_uint32 i; - pRawData = DRFLAC_MALLOC(blockSize); + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5103,7 +6484,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 4 < (drflac_int64)metadata.data.vorbis_comment.vendorLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.vorbis_comment.vendor = pRunningData; pRunningData += metadata.data.vorbis_comment.vendorLength; @@ -5111,7 +6492,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s /* Need space for 'commentCount' comments after the block, which at minimum is a drflac_uint32 per comment */ if ((pRunningDataEnd - pRunningData) / sizeof(drflac_uint32) < metadata.data.vorbis_comment.commentCount) { /* <-- Note the order of operations to avoid overflow to a valid value */ - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.vorbis_comment.pComments = pRunningData; @@ -5121,13 +6502,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s drflac_uint32 commentLength; if (pRunningDataEnd - pRunningData < 4) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } commentLength = drflac__le2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; if (pRunningDataEnd - pRunningData < (drflac_int64)commentLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } pRunningData += commentLength; @@ -5135,7 +6516,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s onMeta(pUserDataMD, &metadata); - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; @@ -5152,13 +6533,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s drflac_uint8 iTrack; drflac_uint8 iIndex; - pRawData = DRFLAC_MALLOC(blockSize); + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5168,7 +6549,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s pRunningData = (const char*)pRawData; pRunningDataEnd = (const char*)pRawData + blockSize; - drflac_copy_memory(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; + DRFLAC_COPY_MEMORY(metadata.data.cuesheet.catalog, pRunningData, 128); pRunningData += 128; metadata.data.cuesheet.leadInSampleCount = drflac__be2host_64(*(const drflac_uint64*)pRunningData); pRunningData += 8; metadata.data.cuesheet.isCD = (pRunningData[0] & 0x80) != 0; pRunningData += 259; metadata.data.cuesheet.trackCount = pRunningData[0]; pRunningData += 1; @@ -5180,7 +6561,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s drflac_uint32 indexPointSize; if (pRunningDataEnd - pRunningData < 36) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5189,7 +6570,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s indexCount = pRunningData[0]; pRunningData += 1; indexPointSize = indexCount * sizeof(drflac_cuesheet_track_index); if (pRunningDataEnd - pRunningData < (drflac_int64)indexPointSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5203,7 +6584,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s onMeta(pUserDataMD, &metadata); - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; @@ -5218,13 +6599,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s const char* pRunningData; const char* pRunningDataEnd; - pRawData = DRFLAC_MALLOC(blockSize); + pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5239,7 +6620,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 24 < (drflac_int64)metadata.data.picture.mimeLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.mime = pRunningData; pRunningData += metadata.data.picture.mimeLength; @@ -5247,7 +6628,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s /* Need space for the rest of the block */ if ((pRunningDataEnd - pRunningData) - 20 < (drflac_int64)metadata.data.picture.descriptionLength) { /* <-- Note the order of operations to avoid overflow to a valid value */ - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } metadata.data.picture.description = pRunningData; pRunningData += metadata.data.picture.descriptionLength; @@ -5260,13 +6641,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s /* Need space for the picture after the block */ if (pRunningDataEnd - pRunningData < (drflac_int64)metadata.data.picture.pictureDataSize) { /* <-- Note the order of operations to avoid overflow to a valid value */ - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } onMeta(pUserDataMD, &metadata); - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; @@ -5301,13 +6682,13 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s can at the very least report the chunk to the application and let it look at the raw data. */ if (onMeta) { - void* pRawData = DRFLAC_MALLOC(blockSize); + void* pRawData = drflac__malloc_from_callbacks(blockSize, pAllocationCallbacks); if (pRawData == NULL) { return DRFLAC_FALSE; } if (onRead(pUserData, pRawData, blockSize) != blockSize) { - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); return DRFLAC_FALSE; } @@ -5315,7 +6696,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s metadata.rawDataSize = blockSize; onMeta(pUserDataMD, &metadata); - DRFLAC_FREE(pRawData); + drflac__free_from_callbacks(pRawData, pAllocationCallbacks); } } break; } @@ -5340,7 +6721,7 @@ drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, drflac_s return DRFLAC_TRUE; } -drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) +static drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) { /* Pre Condition: The bit stream should be sitting just past the 4-byte id header. */ @@ -5377,10 +6758,10 @@ drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_ return DRFLAC_FALSE; /* Failed to initialize because the first frame depends on the STREAMINFO block, which does not exist. */ } - pInit->sampleRate = pInit->firstFrameHeader.sampleRate; - pInit->channels = drflac__get_channel_count_from_channel_assignment(pInit->firstFrameHeader.channelAssignment); - pInit->bitsPerSample = pInit->firstFrameHeader.bitsPerSample; - pInit->maxBlockSize = 65535; /* <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo */ + pInit->sampleRate = pInit->firstFrameHeader.sampleRate; + pInit->channels = drflac__get_channel_count_from_channel_assignment(pInit->firstFrameHeader.channelAssignment); + pInit->bitsPerSample = pInit->firstFrameHeader.bitsPerSample; + pInit->maxBlockSizeInPCMFrames = 65535; /* <-- See notes here: https://xiph.org/flac/format.html#metadata_block_streaminfo */ return DRFLAC_TRUE; } } else { @@ -5389,13 +6770,13 @@ drflac_bool32 drflac__init_private__native(drflac_init_info* pInit, drflac_read_ return DRFLAC_FALSE; } - pInit->hasStreamInfoBlock = DRFLAC_TRUE; - pInit->sampleRate = streaminfo.sampleRate; - pInit->channels = streaminfo.channels; - pInit->bitsPerSample = streaminfo.bitsPerSample; - pInit->totalSampleCount = streaminfo.totalSampleCount; - pInit->maxBlockSize = streaminfo.maxBlockSize; /* Don't care about the min block size - only the max (used for determining the size of the memory allocation). */ - pInit->hasMetadataBlocks = !isLastBlock; + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; + pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; /* Don't care about the min block size - only the max (used for determining the size of the memory allocation). */ + pInit->hasMetadataBlocks = !isLastBlock; if (onMeta) { drflac_metadata metadata; @@ -5550,24 +6931,34 @@ static DRFLAC_INLINE drflac_uint32 drflac_ogg__get_page_body_size(drflac_ogg_pag return pageBodySize; } -drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +static drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) { drflac_uint8 data[23]; drflac_uint32 i; - drflac_assert(*pCRC32 == DRFLAC_OGG_CAPTURE_PATTERN_CRC32); + DRFLAC_ASSERT(*pCRC32 == DRFLAC_OGG_CAPTURE_PATTERN_CRC32); if (onRead(pUserData, data, 23) != 23) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } *pBytesRead += 23; + /* + It's not actually used, but set the capture pattern to 'OggS' for completeness. Not doing this will cause static analysers to complain about + us trying to access uninitialized data. We could alternatively just comment out this member of the drflac_ogg_page_header structure, but I + like to have it map to the structure of the underlying data. + */ + pHeader->capturePattern[0] = 'O'; + pHeader->capturePattern[1] = 'g'; + pHeader->capturePattern[2] = 'g'; + pHeader->capturePattern[3] = 'S'; + pHeader->structureVersion = data[0]; pHeader->headerType = data[1]; - drflac_copy_memory(&pHeader->granulePosition, &data[ 2], 8); - drflac_copy_memory(&pHeader->serialNumber, &data[10], 4); - drflac_copy_memory(&pHeader->sequenceNumber, &data[14], 4); - drflac_copy_memory(&pHeader->checksum, &data[18], 4); + DRFLAC_COPY_MEMORY(&pHeader->granulePosition, &data[ 2], 8); + DRFLAC_COPY_MEMORY(&pHeader->serialNumber, &data[10], 4); + DRFLAC_COPY_MEMORY(&pHeader->sequenceNumber, &data[14], 4); + DRFLAC_COPY_MEMORY(&pHeader->checksum, &data[18], 4); pHeader->segmentCount = data[22]; /* Calculate the CRC. Note that for the calculation the checksum part of the page needs to be set to 0. */ @@ -5582,7 +6973,7 @@ drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_pro if (onRead(pUserData, pHeader->segmentTable, pHeader->segmentCount) != pHeader->segmentCount) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } *pBytesRead += pHeader->segmentCount; @@ -5593,14 +6984,14 @@ drflac_result drflac_ogg__read_page_header_after_capture_pattern(drflac_read_pro return DRFLAC_SUCCESS; } -drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) +static drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserData, drflac_ogg_page_header* pHeader, drflac_uint32* pBytesRead, drflac_uint32* pCRC32) { drflac_uint8 id[4]; *pBytesRead = 0; if (onRead(pUserData, id, 4) != 4) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } *pBytesRead += 4; @@ -5627,7 +7018,7 @@ drflac_result drflac_ogg__read_page_header(drflac_read_proc onRead, void* pUserD id[1] = id[2]; id[2] = id[3]; if (onRead(pUserData, &id[3], 1) != 1) { - return DRFLAC_END_OF_STREAM; + return DRFLAC_AT_END; } *pBytesRead += 1; } @@ -5848,15 +7239,15 @@ static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytes drflac_uint8* pRunningBufferOut = (drflac_uint8*)bufferOut; size_t bytesRead = 0; - drflac_assert(oggbs != NULL); - drflac_assert(pRunningBufferOut != NULL); + DRFLAC_ASSERT(oggbs != NULL); + DRFLAC_ASSERT(pRunningBufferOut != NULL); /* Reading is done page-by-page. If we've run out of bytes in the page we need to move to the next one. */ while (bytesRead < bytesToRead) { size_t bytesRemainingToRead = bytesToRead - bytesRead; if (oggbs->bytesRemainingInPage >= bytesRemainingToRead) { - drflac_copy_memory(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), bytesRemainingToRead); + DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), bytesRemainingToRead); bytesRead += bytesRemainingToRead; oggbs->bytesRemainingInPage -= (drflac_uint32)bytesRemainingToRead; break; @@ -5864,13 +7255,13 @@ static size_t drflac__on_read_ogg(void* pUserData, void* bufferOut, size_t bytes /* If we get here it means some of the requested data is contained in the next pages. */ if (oggbs->bytesRemainingInPage > 0) { - drflac_copy_memory(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), oggbs->bytesRemainingInPage); + DRFLAC_COPY_MEMORY(pRunningBufferOut, oggbs->pageData + (oggbs->pageDataSize - oggbs->bytesRemainingInPage), oggbs->bytesRemainingInPage); bytesRead += oggbs->bytesRemainingInPage; pRunningBufferOut += oggbs->bytesRemainingInPage; oggbs->bytesRemainingInPage = 0; } - drflac_assert(bytesRemainingToRead > 0); + DRFLAC_ASSERT(bytesRemainingToRead > 0); if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { break; /* Failed to go to the next page. Might have simply hit the end of the stream. */ } @@ -5884,8 +7275,8 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see drflac_oggbs* oggbs = (drflac_oggbs*)pUserData; int bytesSeeked = 0; - drflac_assert(oggbs != NULL); - drflac_assert(offset >= 0); /* <-- Never seek backwards. */ + DRFLAC_ASSERT(oggbs != NULL); + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ /* Seeking is always forward which makes things a lot simpler. */ if (origin == drflac_seek_origin_start) { @@ -5900,14 +7291,15 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see return drflac__on_seek_ogg(pUserData, offset, drflac_seek_origin_current); } - drflac_assert(origin == drflac_seek_origin_current); + DRFLAC_ASSERT(origin == drflac_seek_origin_current); while (bytesSeeked < offset) { int bytesRemainingToSeek = offset - bytesSeeked; - drflac_assert(bytesRemainingToSeek >= 0); + DRFLAC_ASSERT(bytesRemainingToSeek >= 0); if (oggbs->bytesRemainingInPage >= (size_t)bytesRemainingToSeek) { bytesSeeked += bytesRemainingToSeek; + (void)bytesSeeked; /* <-- Silence a dead store warning emitted by Clang Static Analyzer. */ oggbs->bytesRemainingInPage -= bytesRemainingToSeek; break; } @@ -5918,7 +7310,7 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see oggbs->bytesRemainingInPage = 0; } - drflac_assert(bytesRemainingToSeek > 0); + DRFLAC_ASSERT(bytesRemainingToSeek > 0); if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_fail_on_crc_mismatch)) { /* Failed to go to the next page. We either hit the end of the stream or had a CRC mismatch. */ return DRFLAC_FALSE; @@ -5928,26 +7320,26 @@ static drflac_bool32 drflac__on_seek_ogg(void* pUserData, int offset, drflac_see return DRFLAC_TRUE; } -drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) + +static drflac_bool32 drflac_ogg__seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; drflac_uint64 originalBytePos; drflac_uint64 runningGranulePosition; drflac_uint64 runningFrameBytePos; - drflac_uint64 runningSampleCount; + drflac_uint64 runningPCMFrameCount; - drflac_assert(oggbs != NULL); + DRFLAC_ASSERT(oggbs != NULL); - originalBytePos = oggbs->currentBytePos; /* For recovery. */ + originalBytePos = oggbs->currentBytePos; /* For recovery. Points to the OggS identifier. */ /* First seek to the first frame. */ - if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFramePos)) { + if (!drflac__seek_to_byte(&pFlac->bs, pFlac->firstFLACFramePosInBytes)) { return DRFLAC_FALSE; } oggbs->bytesRemainingInPage = 0; runningGranulePosition = 0; - runningFrameBytePos = oggbs->currentBytePos; /* <-- Points to the OggS identifier. */ for (;;) { if (!drflac_oggbs__goto_next_page(oggbs, drflac_ogg_recover_on_crc_mismatch)) { drflac_oggbs__seek_physical(oggbs, originalBytePos, drflac_seek_origin_start); @@ -5955,7 +7347,7 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde } runningFrameBytePos = oggbs->currentBytePos - drflac_ogg__get_page_header_size(&oggbs->currentPageHeader) - oggbs->pageDataSize; - if (oggbs->currentPageHeader.granulePosition*pFlac->channels >= sampleIndex) { + if (oggbs->currentPageHeader.granulePosition >= pcmFrameIndex) { break; /* The sample is somewhere in the previous page. */ } @@ -5970,7 +7362,7 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde firstBytesInPage[1] = oggbs->pageData[1]; if ((firstBytesInPage[0] == 0xFF) && (firstBytesInPage[1] & 0xFC) == 0xF8) { /* <-- Does the page begin with a frame's sync code? */ - runningGranulePosition = oggbs->currentPageHeader.granulePosition*pFlac->channels; + runningGranulePosition = oggbs->currentPageHeader.granulePosition; } continue; @@ -5995,53 +7387,69 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde At this point we'll be sitting on the first byte of the frame header of the first frame in the page. We just keep looping over these frames until we find the one containing the sample we're after. */ - runningSampleCount = runningGranulePosition; + runningPCMFrameCount = runningGranulePosition; for (;;) { /* There are two ways to find the sample and seek past irrelevant frames: 1) Use the native FLAC decoder. 2) Use Ogg's framing system. - + Both of these options have their own pros and cons. Using the native FLAC decoder is slower because it needs to do a full decode of the frame. Using Ogg's framing system is faster, but more complicated and involves some code duplication for the decoding of frame headers. - + Another thing to consider is that using the Ogg framing system will perform direct seeking of the physical Ogg bitstream. This is important to consider because it means we cannot read data from the drflac_bs object using the standard drflac__*() APIs because that will read in extra data for its own internal caching which in turn breaks the positioning of the read pointer of the physical Ogg bitstream. Therefore, anything that would normally be read using the native FLAC decoding APIs, such as drflac__read_next_flac_frame_header(), need to be re-implemented so as to avoid the use of the drflac_bs object. - + Considering these issues, I have decided to use the slower native FLAC decoding method for the following reasons: 1) Seeking is already partially accelerated using Ogg's paging system in the code block above. 2) Seeking in an Ogg encapsulated FLAC stream is probably quite uncommon. 3) Simplicity. */ - drflac_uint64 firstSampleInFrame = 0; - drflac_uint64 lastSampleInFrame = 0; - drflac_uint64 sampleCountInThisFrame; + drflac_uint64 firstPCMFrameInFLACFrame = 0; + drflac_uint64 lastPCMFrameInFLACFrame = 0; + drflac_uint64 pcmFrameCountInThisFrame; - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { return DRFLAC_FALSE; } - drflac__get_current_frame_sample_range(pFlac, &firstSampleInFrame, &lastSampleInFrame); + drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &firstPCMFrameInFLACFrame, &lastPCMFrameInFLACFrame); - sampleCountInThisFrame = (lastSampleInFrame - firstSampleInFrame) + 1; - if (sampleIndex < (runningSampleCount + sampleCountInThisFrame)) { + pcmFrameCountInThisFrame = (lastPCMFrameInFLACFrame - firstPCMFrameInFLACFrame) + 1; + + /* If we are seeking to the end of the file and we've just hit it, we're done. */ + if (pcmFrameIndex == pFlac->totalPCMFrameCount && (runningPCMFrameCount + pcmFrameCountInThisFrame) == pFlac->totalPCMFrameCount) { + drflac_result result = drflac__decode_flac_frame(pFlac); + if (result == DRFLAC_SUCCESS) { + pFlac->currentPCMFrame = pcmFrameIndex; + pFlac->currentFLACFrame.pcmFramesRemaining = 0; + return DRFLAC_TRUE; + } else { + return DRFLAC_FALSE; + } + } + + if (pcmFrameIndex < (runningPCMFrameCount + pcmFrameCountInThisFrame)) { /* - The sample should be in this frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend + The sample should be in this FLAC frame. We need to fully decode it, however if it's an invalid frame (a CRC mismatch), we need to pretend it never existed and keep iterating. */ drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { /* The frame is valid. We just need to skip over some samples to ensure it's sample-exact. */ - drflac_uint64 samplesToDecode = (size_t)(sampleIndex - runningSampleCount); /* <-- Safe cast because the maximum number of samples in a frame is 65535. */ - if (samplesToDecode == 0) { + drflac_uint64 pcmFramesToDecode = (size_t)(pcmFrameIndex - runningPCMFrameCount); /* <-- Safe cast because the maximum number of samples in a frame is 65535. */ + if (pcmFramesToDecode == 0) { return DRFLAC_TRUE; } - return drflac__seek_forward_by_samples(pFlac, samplesToDecode) == samplesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ + + pFlac->currentPCMFrame = runningPCMFrameCount; + + return drflac__seek_forward_by_pcm_frames(pFlac, pcmFramesToDecode) == pcmFramesToDecode; /* <-- If this fails, something bad has happened (it should never fail). */ } else { if (result == DRFLAC_CRC_MISMATCH) { continue; /* CRC mismatch. Pretend this frame never existed. */ @@ -6056,7 +7464,7 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde */ drflac_result result = drflac__seek_to_next_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { - runningSampleCount += sampleCountInThisFrame; + runningPCMFrameCount += pcmFrameCountInThisFrame; } else { if (result == DRFLAC_CRC_MISMATCH) { continue; /* CRC mismatch. Pretend this frame never existed. */ @@ -6069,7 +7477,8 @@ drflac_bool32 drflac_ogg__seek_to_sample(drflac* pFlac, drflac_uint64 sampleInde } -drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) + +static drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, void* pUserDataMD, drflac_bool32 relaxed) { drflac_ogg_page_header header; drflac_uint32 crc32 = DRFLAC_OGG_CAPTURE_PATTERN_CRC32; @@ -6159,13 +7568,13 @@ drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_pro if (drflac__read_streaminfo(onRead, pUserData, &streaminfo)) { /* Success! */ - pInit->hasStreamInfoBlock = DRFLAC_TRUE; - pInit->sampleRate = streaminfo.sampleRate; - pInit->channels = streaminfo.channels; - pInit->bitsPerSample = streaminfo.bitsPerSample; - pInit->totalSampleCount = streaminfo.totalSampleCount; - pInit->maxBlockSize = streaminfo.maxBlockSize; - pInit->hasMetadataBlocks = !isLastBlock; + pInit->hasStreamInfoBlock = DRFLAC_TRUE; + pInit->sampleRate = streaminfo.sampleRate; + pInit->channels = streaminfo.channels; + pInit->bitsPerSample = streaminfo.bitsPerSample; + pInit->totalPCMFrameCount = streaminfo.totalPCMFrameCount; + pInit->maxBlockSizeInPCMFrames = streaminfo.maxBlockSizeInPCMFrames; + pInit->hasMetadataBlocks = !isLastBlock; if (onMeta) { drflac_metadata metadata; @@ -6227,7 +7636,7 @@ drflac_bool32 drflac__init_private__ogg(drflac_init_info* pInit, drflac_read_pro } #endif -drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) +static drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) { drflac_bool32 relaxed; drflac_uint8 id[4]; @@ -6236,7 +7645,7 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR return DRFLAC_FALSE; } - drflac_zero_memory(pInit, sizeof(*pInit)); + DRFLAC_ZERO_MEMORY(pInit, sizeof(*pInit)); pInit->onRead = onRead; pInit->onSeek = onSeek; pInit->onMeta = onMeta; @@ -6272,7 +7681,7 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR flags = header[1]; - drflac_copy_memory(&headerSize, header+2, 4); + DRFLAC_COPY_MEMORY(&headerSize, header+2, 4); headerSize = drflac__unsynchsafe_32(drflac__be2host_32(headerSize)); if (flags & 0x10) { headerSize += 10; @@ -6312,54 +7721,65 @@ drflac_bool32 drflac__init_private(drflac_init_info* pInit, drflac_read_proc onR return DRFLAC_FALSE; } -void drflac__init_from_info(drflac* pFlac, drflac_init_info* pInit) +static void drflac__init_from_info(drflac* pFlac, const drflac_init_info* pInit) { - drflac_assert(pFlac != NULL); - drflac_assert(pInit != NULL); + DRFLAC_ASSERT(pFlac != NULL); + DRFLAC_ASSERT(pInit != NULL); - drflac_zero_memory(pFlac, sizeof(*pFlac)); - pFlac->bs = pInit->bs; - pFlac->onMeta = pInit->onMeta; - pFlac->pUserDataMD = pInit->pUserDataMD; - pFlac->maxBlockSize = pInit->maxBlockSize; - pFlac->sampleRate = pInit->sampleRate; - pFlac->channels = (drflac_uint8)pInit->channels; - pFlac->bitsPerSample = (drflac_uint8)pInit->bitsPerSample; - pFlac->totalSampleCount = pInit->totalSampleCount; - pFlac->totalPCMFrameCount = pInit->totalSampleCount / pFlac->channels; - pFlac->container = pInit->container; + DRFLAC_ZERO_MEMORY(pFlac, sizeof(*pFlac)); + pFlac->bs = pInit->bs; + pFlac->onMeta = pInit->onMeta; + pFlac->pUserDataMD = pInit->pUserDataMD; + pFlac->maxBlockSizeInPCMFrames = pInit->maxBlockSizeInPCMFrames; + pFlac->sampleRate = pInit->sampleRate; + pFlac->channels = (drflac_uint8)pInit->channels; + pFlac->bitsPerSample = (drflac_uint8)pInit->bitsPerSample; + pFlac->totalPCMFrameCount = pInit->totalPCMFrameCount; + pFlac->container = pInit->container; } -drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD) + +static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, void* pUserDataMD, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac_init_info init; drflac_uint32 allocationSize; drflac_uint32 wholeSIMDVectorCountPerChannel; drflac_uint32 decodedSamplesAllocationSize; #ifndef DR_FLAC_NO_OGG - drflac_uint32 oggbsAllocationSize; drflac_oggbs oggbs; #endif drflac_uint64 firstFramePos; drflac_uint64 seektablePos; drflac_uint32 seektableSize; + drflac_allocation_callbacks allocationCallbacks; drflac* pFlac; -#ifndef DRFLAC_NO_CPUID /* CPU support first. */ drflac__init_cpu_caps(); -#endif if (!drflac__init_private(&init, onRead, onSeek, onMeta, container, pUserData, pUserDataMD)) { return NULL; } + if (pAllocationCallbacks != NULL) { + allocationCallbacks = *pAllocationCallbacks; + if (allocationCallbacks.onFree == NULL || (allocationCallbacks.onMalloc == NULL && allocationCallbacks.onRealloc == NULL)) { + return NULL; /* Invalid allocation callbacks. */ + } + } else { + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drflac__malloc_default; + allocationCallbacks.onRealloc = drflac__realloc_default; + allocationCallbacks.onFree = drflac__free_default; + } + + /* The size of the allocation for the drflac object needs to be large enough to fit the following: 1) The main members of the drflac structure 2) A block of memory large enough to store the decoded samples of the largest frame in the stream 3) If the container is Ogg, a drflac_oggbs object - + The complicated part of the allocation is making sure there's enough room the decoded samples, taking into consideration the different SIMD instruction sets. */ @@ -6369,10 +7789,10 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p The allocation size for decoded frames depends on the number of 32-bit integers that fit inside the largest SIMD vector we are supporting. */ - if (((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { - wholeSIMDVectorCountPerChannel = ((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); + if ((init.maxBlockSizeInPCMFrames % (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) == 0) { + wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))); } else { - wholeSIMDVectorCountPerChannel = ((init.maxBlockSize+DRFLAC_LEADING_SAMPLES) / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; + wholeSIMDVectorCountPerChannel = (init.maxBlockSizeInPCMFrames / (DRFLAC_MAX_SIMD_VECTOR_SIZE / sizeof(drflac_int32))) + 1; } decodedSamplesAllocationSize = wholeSIMDVectorCountPerChannel * DRFLAC_MAX_SIMD_VECTOR_SIZE * init.channels; @@ -6382,13 +7802,11 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p #ifndef DR_FLAC_NO_OGG /* There's additional data required for Ogg streams. */ - oggbsAllocationSize = 0; if (init.container == drflac_container_ogg) { - oggbsAllocationSize = sizeof(drflac_oggbs); - allocationSize += oggbsAllocationSize; + allocationSize += sizeof(drflac_oggbs); } - drflac_zero_memory(&oggbs, sizeof(oggbs)); + DRFLAC_ZERO_MEMORY(&oggbs, sizeof(oggbs)); if (init.container == drflac_container_ogg) { oggbs.onRead = onRead; oggbs.onSeek = onSeek; @@ -6422,7 +7840,7 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } #endif - if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seektableSize)) { + if (!drflac__read_and_decode_metadata(onReadOverride, onSeekOverride, onMeta, pUserDataOverride, pUserDataMD, &firstFramePos, &seektablePos, &seektableSize, &allocationCallbacks)) { return NULL; } @@ -6430,8 +7848,13 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } - pFlac = (drflac*)DRFLAC_MALLOC(allocationSize); + pFlac = (drflac*)drflac__malloc_from_callbacks(allocationSize, &allocationCallbacks); + if (pFlac == NULL) { + return NULL; + } + drflac__init_from_info(pFlac, &init); + pFlac->allocationCallbacks = allocationCallbacks; pFlac->pDecodedSamples = (drflac_int32*)drflac_align((size_t)pFlac->pExtraData, DRFLAC_MAX_SIMD_VECTOR_SIZE); #ifndef DR_FLAC_NO_OGG @@ -6447,7 +7870,7 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } #endif - pFlac->firstFramePos = firstFramePos; + pFlac->firstFLACFramePosInBytes = firstFramePos; /* NOTE: Seektables are not currently compatible with Ogg encapsulation (Ogg has its own accelerated seeking system). I may change this later, so I'm leaving this here for now. */ #ifndef DR_FLAC_NO_OGG @@ -6464,15 +7887,18 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p pFlac->seekpointCount = seektableSize / sizeof(*pFlac->pSeekpoints); pFlac->pSeekpoints = (drflac_seekpoint*)((drflac_uint8*)pFlac->pDecodedSamples + decodedSamplesAllocationSize); + DRFLAC_ASSERT(pFlac->bs.onSeek != NULL); + DRFLAC_ASSERT(pFlac->bs.onRead != NULL); + /* Seek to the seektable, then just read directly into our seektable buffer. */ if (pFlac->bs.onSeek(pFlac->bs.pUserData, (int)seektablePos, drflac_seek_origin_start)) { if (pFlac->bs.onRead(pFlac->bs.pUserData, pFlac->pSeekpoints, seektableSize) == seektableSize) { /* Endian swap. */ drflac_uint32 iSeekpoint; for (iSeekpoint = 0; iSeekpoint < pFlac->seekpointCount; ++iSeekpoint) { - pFlac->pSeekpoints[iSeekpoint].firstSample = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstSample); - pFlac->pSeekpoints[iSeekpoint].frameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].frameOffset); - pFlac->pSeekpoints[iSeekpoint].sampleCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].sampleCount); + pFlac->pSeekpoints[iSeekpoint].firstPCMFrame = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].firstPCMFrame); + pFlac->pSeekpoints[iSeekpoint].flacFrameOffset = drflac__be2host_64(pFlac->pSeekpoints[iSeekpoint].flacFrameOffset); + pFlac->pSeekpoints[iSeekpoint].pcmFrameCount = drflac__be2host_16(pFlac->pSeekpoints[iSeekpoint].pcmFrameCount); } } else { /* Failed to read the seektable. Pretend we don't have one. */ @@ -6481,8 +7907,8 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } /* We need to seek back to where we were. If this fails it's a critical error. */ - if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFramePos, drflac_seek_origin_start)) { - DRFLAC_FREE(pFlac); + if (!pFlac->bs.onSeek(pFlac->bs.pUserData, (int)pFlac->firstFLACFramePosInBytes, drflac_seek_origin_start)) { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } } else { @@ -6493,31 +7919,30 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p } } - + /* If we get here, but don't have a STREAMINFO block, it means we've opened the stream in relaxed mode and need to decode the first frame. */ if (!init.hasStreamInfoBlock) { - pFlac->currentFrame.header = init.firstFrameHeader; - do - { + pFlac->currentFLACFrame.header = init.firstFrameHeader; + for (;;) { drflac_result result = drflac__decode_flac_frame(pFlac); if (result == DRFLAC_SUCCESS) { break; } else { if (result == DRFLAC_CRC_MISMATCH) { - if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFrame.header)) { - DRFLAC_FREE(pFlac); + if (!drflac__read_next_flac_frame_header(&pFlac->bs, pFlac->bitsPerSample, &pFlac->currentFLACFrame.header)) { + drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } continue; } else { - DRFLAC_FREE(pFlac); + drflac__free_from_callbacks(pFlac, &allocationCallbacks); return NULL; } } - } while (1); + } } return pFlac; @@ -6527,6 +7952,553 @@ drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac_seek_p #ifndef DR_FLAC_NO_STDIO #include +#include /* For wcslen(), wcsrtombs() */ + +/* drflac_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ +#include +static drflac_result drflac_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRFLAC_SUCCESS; + #ifdef EPERM + case EPERM: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRFLAC_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRFLAC_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRFLAC_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRFLAC_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRFLAC_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRFLAC_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRFLAC_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRFLAC_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRFLAC_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRFLAC_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRFLAC_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRFLAC_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRFLAC_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRFLAC_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRFLAC_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRFLAC_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRFLAC_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRFLAC_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRFLAC_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRFLAC_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRFLAC_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRFLAC_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRFLAC_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRFLAC_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRFLAC_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRFLAC_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRFLAC_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRFLAC_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRFLAC_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRFLAC_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRFLAC_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRFLAC_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRFLAC_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRFLAC_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRFLAC_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRFLAC_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRFLAC_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRFLAC_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRFLAC_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRFLAC_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRFLAC_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRFLAC_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRFLAC_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRFLAC_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRFLAC_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRFLAC_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRFLAC_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRFLAC_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRFLAC_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRFLAC_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRFLAC_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRFLAC_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRFLAC_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRFLAC_ERROR; + #endif + #ifdef EADV + case EADV: return DRFLAC_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRFLAC_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRFLAC_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRFLAC_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRFLAC_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRFLAC_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRFLAC_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRFLAC_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRFLAC_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRFLAC_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRFLAC_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRFLAC_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRFLAC_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRFLAC_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRFLAC_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRFLAC_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRFLAC_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRFLAC_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRFLAC_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRFLAC_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRFLAC_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRFLAC_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRFLAC_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRFLAC_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRFLAC_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRFLAC_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRFLAC_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRFLAC_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRFLAC_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRFLAC_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRFLAC_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRFLAC_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRFLAC_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRFLAC_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRFLAC_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRFLAC_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRFLAC_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRFLAC_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRFLAC_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRFLAC_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRFLAC_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRFLAC_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRFLAC_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRFLAC_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRFLAC_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRFLAC_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRFLAC_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRFLAC_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRFLAC_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRFLAC_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRFLAC_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRFLAC_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRFLAC_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRFLAC_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRFLAC_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRFLAC_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRFLAC_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRFLAC_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRFLAC_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRFLAC_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRFLAC_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRFLAC_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRFLAC_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRFLAC_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRFLAC_ERROR; + #endif + default: return DRFLAC_ERROR; + } +} + +static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRFLAC_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drflac_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drflac_result result = drflac_result_from_errno(errno); + if (result == DRFLAC_SUCCESS) { + result = DRFLAC_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRFLAC_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRFLAC_HAS_WFOPEN + #endif +#endif + +static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRFLAC_INVALID_ARGS; + } + +#if defined(DRFLAC_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drflac_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drflac_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can + think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. + */ + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRFLAC_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drflac_result_from_errno(errno); + } + + pFilePathMB = (char*)drflac__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRFLAC_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRFLAC_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drflac__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + + if (*ppFile == NULL) { + return DRFLAC_ERROR; + } +#endif + + return DRFLAC_SUCCESS; +} static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead) { @@ -6535,40 +8507,22 @@ static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t byt static drflac_bool32 drflac__on_seek_stdio(void* pUserData, int offset, drflac_seek_origin origin) { - drflac_assert(offset >= 0); /* <-- Never seek backwards. */ + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ return fseek((FILE*)pUserData, offset, (origin == drflac_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } -static FILE* drflac__fopen(const char* filename) -{ - FILE* pFile; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (fopen_s(&pFile, filename, "rb") != 0) { - return NULL; - } -#else - pFile = fopen(filename, "rb"); - if (pFile == NULL) { - return NULL; - } -#endif - return pFile; -} - - -drflac* drflac_open_file(const char* filename) +DRFLAC_API drflac* drflac_open_file(const char* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; FILE* pFile; - pFile = drflac__fopen(filename); - if (pFile == NULL) { + if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { return NULL; } - pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return NULL; @@ -6577,17 +8531,52 @@ drflac* drflac_open_file(const char* filename) return pFlac; } -drflac* drflac_open_file_with_metadata(const char* filename, drflac_meta_proc onMeta, void* pUserData) +DRFLAC_API drflac* drflac_open_file_w(const wchar_t* pFileName, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; FILE* pFile; - pFile = drflac__fopen(filename); - if (pFile == NULL) { + if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { return NULL; } - pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData); + pFlac = drflac_open(drflac__on_read_stdio, drflac__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return NULL; + } + + return pFlac; +} + +DRFLAC_API drflac* drflac_open_file_with_metadata(const char* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_fopen(&pFile, pFileName, "rb") != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); + if (pFlac == NULL) { + fclose(pFile); + return pFlac; + } + + return pFlac; +} + +DRFLAC_API drflac* drflac_open_file_with_metadata_w(const wchar_t* pFileName, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) +{ + drflac* pFlac; + FILE* pFile; + + if (drflac_wfopen(&pFile, pFileName, L"rb", pAllocationCallbacks) != DRFLAC_SUCCESS) { + return NULL; + } + + pFlac = drflac_open_with_metadata_private(drflac__on_read_stdio, drflac__on_seek_stdio, onMeta, drflac_container_unknown, (void*)pFile, pUserData, pAllocationCallbacks); if (pFlac == NULL) { fclose(pFile); return pFlac; @@ -6602,8 +8591,8 @@ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t by drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; size_t bytesRemaining; - drflac_assert(memoryStream != NULL); - drflac_assert(memoryStream->dataSize >= memoryStream->currentReadPos); + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(memoryStream->dataSize >= memoryStream->currentReadPos); bytesRemaining = memoryStream->dataSize - memoryStream->currentReadPos; if (bytesToRead > bytesRemaining) { @@ -6611,7 +8600,7 @@ static size_t drflac__on_read_memory(void* pUserData, void* bufferOut, size_t by } if (bytesToRead > 0) { - drflac_copy_memory(bufferOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); + DRFLAC_COPY_MEMORY(bufferOut, memoryStream->data + memoryStream->currentReadPos, bytesToRead); memoryStream->currentReadPos += bytesToRead; } @@ -6622,8 +8611,8 @@ static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_ { drflac__memory_stream* memoryStream = (drflac__memory_stream*)pUserData; - drflac_assert(memoryStream != NULL); - drflac_assert(offset >= 0); /* <-- Never seek backwards. */ + DRFLAC_ASSERT(memoryStream != NULL); + DRFLAC_ASSERT(offset >= 0); /* <-- Never seek backwards. */ if (offset > (drflac_int64)memoryStream->dataSize) { return DRFLAC_FALSE; @@ -6646,15 +8635,15 @@ static drflac_bool32 drflac__on_seek_memory(void* pUserData, int offset, drflac_ return DRFLAC_TRUE; } -drflac* drflac_open_memory(const void* data, size_t dataSize) +DRFLAC_API drflac* drflac_open_memory(const void* pData, size_t dataSize, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac__memory_stream memoryStream; drflac* pFlac; - memoryStream.data = (const unsigned char*)data; + memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream); + pFlac = drflac_open(drflac__on_read_memory, drflac__on_seek_memory, &memoryStream, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -6677,15 +8666,15 @@ drflac* drflac_open_memory(const void* data, size_t dataSize) return pFlac; } -drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drflac_meta_proc onMeta, void* pUserData) +DRFLAC_API drflac* drflac_open_memory_with_metadata(const void* pData, size_t dataSize, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac__memory_stream memoryStream; drflac* pFlac; - memoryStream.data = (const unsigned char*)data; + memoryStream.data = (const drflac_uint8*)pData; memoryStream.dataSize = dataSize; memoryStream.currentReadPos = 0; - pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData); + pFlac = drflac_open_with_metadata_private(drflac__on_read_memory, drflac__on_seek_memory, onMeta, drflac_container_unknown, &memoryStream, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -6710,25 +8699,25 @@ drflac* drflac_open_memory_with_metadata(const void* data, size_t dataSize, drfl -drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData) +DRFLAC_API drflac* drflac_open(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData); + return drflac_open_with_metadata_private(onRead, onSeek, NULL, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData) +DRFLAC_API drflac* drflac_open_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData); + return drflac_open_with_metadata_private(onRead, onSeek, NULL, container, pUserData, pUserData, pAllocationCallbacks); } -drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData) +DRFLAC_API drflac* drflac_open_with_metadata(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData); + return drflac_open_with_metadata_private(onRead, onSeek, onMeta, drflac_container_unknown, pUserData, pUserData, pAllocationCallbacks); } -drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData) +DRFLAC_API drflac* drflac_open_with_metadata_relaxed(drflac_read_proc onRead, drflac_seek_proc onSeek, drflac_meta_proc onMeta, drflac_container container, void* pUserData, const drflac_allocation_callbacks* pAllocationCallbacks) { - return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData); + return drflac_open_with_metadata_private(onRead, onSeek, onMeta, container, pUserData, pUserData, pAllocationCallbacks); } -void drflac_close(drflac* pFlac) +DRFLAC_API void drflac_close(drflac* pFlac) { if (pFlac == NULL) { return; @@ -6747,7 +8736,7 @@ void drflac_close(drflac* pFlac) /* Need to clean up Ogg streams a bit differently due to the way the bit streaming is chained. */ if (pFlac->container == drflac_container_ogg) { drflac_oggbs* oggbs = (drflac_oggbs*)pFlac->_oggbs; - drflac_assert(pFlac->bs.onRead == drflac__on_read_ogg); + DRFLAC_ASSERT(pFlac->bs.onRead == drflac__on_read_ogg); if (oggbs->onRead == drflac__on_read_stdio) { fclose((FILE*)oggbs->pUserData); @@ -6756,492 +8745,1893 @@ void drflac_close(drflac* pFlac) #endif #endif - DRFLAC_FREE(pFlac); + drflac__free_from_callbacks(pFlac, &pFlac->allocationCallbacks); } -drflac_uint64 drflac__read_s32__misaligned(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* bufferOut) + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - drflac_uint64 samplesRead; + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; - /* We should never be calling this when the number of samples to read is >= the sample count. */ - drflac_assert(samplesToRead < channelCount); - drflac_assert(pFlac->currentFrame.samplesRemaining > 0 && samplesToRead <= pFlac->currentFrame.samplesRemaining); + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif - samplesRead = 0; - while (samplesToRead > 0) { - drflac_uint64 totalSamplesInFrame = pFlac->currentFrame.header.blockSize * channelCount; - drflac_uint64 samplesReadFromFrameSoFar = totalSamplesInFrame - pFlac->currentFrame.samplesRemaining; - drflac_uint64 channelIndex = samplesReadFromFrameSoFar % channelCount; - drflac_uint64 nextSampleInFrame = samplesReadFromFrameSoFar / channelCount; - int decodedSample = 0; +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - switch (pFlac->currentFrame.header.channelAssignment) - { - case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: - { - if (channelIndex == 0) { - decodedSample = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - } else { - int side = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - int left = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex - 1].wastedBitsPerSample); - decodedSample = left - side; - } - } break; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; - case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: - { - if (channelIndex == 0) { - int side = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - int right = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 1].wastedBitsPerSample); - decodedSample = side + right; - } else { - decodedSample = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - } - } break; + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; - case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: - { - int mid; - int side; - if (channelIndex == 0) { - mid = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - side = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 1].wastedBitsPerSample); + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; - mid = (((unsigned int)mid) << 1) | (side & 0x01); - decodedSample = (mid + side) >> 1; - } else { - mid = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex - 1].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex - 1].wastedBitsPerSample); - side = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - - mid = (((unsigned int)mid) << 1) | (side & 0x01); - decodedSample = (mid - side) >> 1; - } - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: - default: - { - decodedSample = (int)((drflac_uint32)pFlac->currentFrame.subframes[channelIndex + 0].pDecodedSamples[nextSampleInFrame] << pFlac->currentFrame.subframes[channelIndex + 0].wastedBitsPerSample); - } break; - } - - decodedSample = (int)((drflac_uint32)decodedSample << (32 - pFlac->bitsPerSample)); - - if (bufferOut) { - *bufferOut++ = decodedSample; - } - - samplesRead += 1; - pFlac->currentFrame.samplesRemaining -= 1; - samplesToRead -= 1; + pOutputSamples[i*8+0] = (drflac_int32)left0; + pOutputSamples[i*8+1] = (drflac_int32)right0; + pOutputSamples[i*8+2] = (drflac_int32)left1; + pOutputSamples[i*8+3] = (drflac_int32)right1; + pOutputSamples[i*8+4] = (drflac_int32)left2; + pOutputSamples[i*8+5] = (drflac_int32)right2; + pOutputSamples[i*8+6] = (drflac_int32)left3; + pOutputSamples[i*8+7] = (drflac_int32)right3; } - return samplesRead; + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } } -drflac_uint64 drflac_read_s32(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int32* bufferOut) +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) { - drflac_uint64 samplesRead; + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - /* Note that is allowed to be null, in which case this will act like a seek. */ - if (pFlac == NULL || samplesToRead == 0) { + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + + drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0; + pOutputSamples[i*8+1] = (drflac_int32)right0; + pOutputSamples[i*8+2] = (drflac_int32)left1; + pOutputSamples[i*8+3] = (drflac_int32)right1; + pOutputSamples[i*8+4] = (drflac_int32)left2; + pOutputSamples[i*8+5] = (drflac_int32)right2; + pOutputSamples[i*8+6] = (drflac_int32)left3; + pOutputSamples[i*8+7] = (drflac_int32)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + + drflac__vst2q_u32((drflac_uint32*)pOutputSamples + i*8, vzipq_u32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left; + pOutputSamples[i*2+1] = (drflac_int32)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + pOutputSamples[i*8+0] = (drflac_int32)temp0L; + pOutputSamples[i*8+1] = (drflac_int32)temp0R; + pOutputSamples[i*8+2] = (drflac_int32)temp1L; + pOutputSamples[i*8+3] = (drflac_int32)temp1R; + pOutputSamples[i*8+4] = (drflac_int32)temp2L; + pOutputSamples[i*8+5] = (drflac_int32)temp2R; + pOutputSamples[i*8+6] = (drflac_int32)temp3L; + pOutputSamples[i*8+7] = (drflac_int32)temp3R; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); + temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); + temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); + temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); + + temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); + temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); + temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); + temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); + + pOutputSamples[i*8+0] = (drflac_int32)temp0L; + pOutputSamples[i*8+1] = (drflac_int32)temp0R; + pOutputSamples[i*8+2] = (drflac_int32)temp1L; + pOutputSamples[i*8+3] = (drflac_int32)temp1R; + pOutputSamples[i*8+4] = (drflac_int32)temp2L; + pOutputSamples[i*8+5] = (drflac_int32)temp2R; + pOutputSamples[i*8+6] = (drflac_int32)temp3L; + pOutputSamples[i*8+7] = (drflac_int32)temp3R; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; + pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_int32 shift = unusedBitsPerSample; + int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ + int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ + uint32x4_t one4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + one4 = vdupq_n_u32(1); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); + + left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)(mid + side) >> 1; + pOutputSamples[i*2+1] = (drflac_int32)(mid - side) >> 1; + } + } else { + int32x4_t shift4; + + shift -= 1; + shift4 = vdupq_n_s32(shift); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, one4)); + + left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift); + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift); + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)); + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + pOutputSamples[i*8+0] = (drflac_int32)tempL0; + pOutputSamples[i*8+1] = (drflac_int32)tempR0; + pOutputSamples[i*8+2] = (drflac_int32)tempL1; + pOutputSamples[i*8+3] = (drflac_int32)tempR1; + pOutputSamples[i*8+4] = (drflac_int32)tempL2; + pOutputSamples[i*8+5] = (drflac_int32)tempR2; + pOutputSamples[i*8+6] = (drflac_int32)tempL3; + pOutputSamples[i*8+7] = (drflac_int32)tempR3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 0), _mm_unpacklo_epi32(left, right)); + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8 + 4), _mm_unpackhi_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + int32x4_t shift4_0 = vdupq_n_s32(shift0); + int32x4_t shift4_1 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t left; + int32x4_t right; + + left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift4_0)); + right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift4_1)); + + drflac__vst2q_s32(pOutputSamples + i*8, vzipq_s32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0); + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int32* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s32__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s32__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { return 0; } - if (bufferOut == NULL) { - return drflac__seek_forward_by_samples(pFlac, samplesToRead); + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); } - samplesRead = 0; - while (samplesToRead > 0) { + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { /* If we've run out of samples in this frame, go to the next. */ - if (pFlac->currentFrame.samplesRemaining == 0) { + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { - /* Here is where we grab the samples and interleave them. */ - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - drflac_uint64 totalSamplesInFrame = pFlac->currentFrame.header.blockSize * channelCount; - drflac_uint64 samplesReadFromFrameSoFar = totalSamplesInFrame - pFlac->currentFrame.samplesRemaining; - drflac_uint64 misalignedSampleCount = samplesReadFromFrameSoFar % channelCount; - drflac_uint64 alignedSampleCountPerChannel; - drflac_uint64 firstAlignedSampleInFrame; - unsigned int unusedBitsPerSample; - drflac_uint64 alignedSamplesRead; + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; - if (misalignedSampleCount > 0) { - drflac_uint64 misalignedSamplesRead = drflac__read_s32__misaligned(pFlac, misalignedSampleCount, bufferOut); - samplesRead += misalignedSamplesRead; - samplesReadFromFrameSoFar += misalignedSamplesRead; - bufferOut += misalignedSamplesRead; - samplesToRead -= misalignedSamplesRead; - pFlac->currentSample += misalignedSamplesRead; + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; } + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; - alignedSampleCountPerChannel = samplesToRead / channelCount; - if (alignedSampleCountPerChannel > pFlac->currentFrame.samplesRemaining / channelCount) { - alignedSampleCountPerChannel = pFlac->currentFrame.samplesRemaining / channelCount; - } - - firstAlignedSampleInFrame = samplesReadFromFrameSoFar / channelCount; - unusedBitsPerSample = 32 - pFlac->bitsPerSample; - - switch (pFlac->currentFrame.header.channelAssignment) - { - case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + switch (pFlac->currentFLACFrame.header.channelAssignment) { - drflac_uint64 i; - const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (i = 0; i < alignedSampleCountPerChannel; ++i) { - int left = (int)((drflac_uint32)pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample)); - int side = (int)((drflac_uint32)pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample)); - int right = left - side; - - bufferOut[i*2+0] = left; - bufferOut[i*2+1] = right; - } - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: - { - drflac_uint64 i; - const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (i = 0; i < alignedSampleCountPerChannel; ++i) { - int side = (int)((drflac_uint32)pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample)); - int right = (int)((drflac_uint32)pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample)); - int left = right + side; - - bufferOut[i*2+0] = left; - bufferOut[i*2+1] = right; - } - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: - { - drflac_uint64 i; - const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; - - for (i = 0; i < alignedSampleCountPerChannel; ++i) { - int mid = (int)((drflac_uint32)pDecodedSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample); - int side = (int)((drflac_uint32)pDecodedSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample); - - mid = (((drflac_uint32)mid) << 1) | (side & 0x01); - - bufferOut[i*2+0] = (drflac_int32)((drflac_uint32)((mid + side) >> 1) << (unusedBitsPerSample)); - bufferOut[i*2+1] = (drflac_int32)((drflac_uint32)((mid - side) >> 1) << (unusedBitsPerSample)); - } - } break; - - case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: - default: - { - if (pFlac->currentFrame.header.channelAssignment == 1) /* 1 = Stereo */ + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { - /* Stereo optimized inner loop unroll. */ - drflac_uint64 i; - const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + firstAlignedSampleInFrame; - const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + firstAlignedSampleInFrame; + drflac_read_pcm_frames_s32__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; - for (i = 0; i < alignedSampleCountPerChannel; ++i) { - bufferOut[i*2+0] = (drflac_int32)((drflac_uint32)pDecodedSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample)); - bufferOut[i*2+1] = (drflac_int32)((drflac_uint32)pDecodedSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample)); - } - } - else + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: { - /* Generic interleaving. */ - drflac_uint64 i; - for (i = 0; i < alignedSampleCountPerChannel; ++i) { - unsigned int j; - for (j = 0; j < channelCount; ++j) { - bufferOut[(i*channelCount)+j] = (drflac_int32)((drflac_uint32)(pFlac->currentFrame.subframes[j].pDecodedSamples[firstAlignedSampleInFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample)); - } - } - } - } break; - } + drflac_read_pcm_frames_s32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; - alignedSamplesRead = alignedSampleCountPerChannel * channelCount; - samplesRead += alignedSamplesRead; - samplesReadFromFrameSoFar += alignedSamplesRead; - bufferOut += alignedSamplesRead; - samplesToRead -= alignedSamplesRead; - pFlac->currentSample += alignedSamplesRead; - pFlac->currentFrame.samplesRemaining -= (unsigned int)alignedSamplesRead; + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_s32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; - - /* At this point we may still have some excess samples left to read. */ - if (samplesToRead > 0 && pFlac->currentFrame.samplesRemaining > 0) { - drflac_uint64 excessSamplesRead = 0; - if (samplesToRead < pFlac->currentFrame.samplesRemaining) { - excessSamplesRead = drflac__read_s32__misaligned(pFlac, samplesToRead, bufferOut); - } else { - excessSamplesRead = drflac__read_s32__misaligned(pFlac, pFlac->currentFrame.samplesRemaining, bufferOut); + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_s32__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + pBufferOut[(i*channelCount)+j] = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + } } - - samplesRead += excessSamplesRead; - samplesReadFromFrameSoFar += excessSamplesRead; - bufferOut += excessSamplesRead; - samplesToRead -= excessSamplesRead; - pFlac->currentSample += excessSamplesRead; } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; } } - return samplesRead; + return framesRead; } -drflac_uint64 drflac_read_pcm_frames_s32(drflac* pFlac, drflac_uint64 framesToRead, drflac_int32* pBufferOut) -{ -#if defined(_MSC_VER) && !defined(__clang__) - #pragma warning(push) - #pragma warning(disable:4996) /* was declared deprecated */ -#elif defined(__GNUC__) || defined(__clang__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - return drflac_read_s32(pFlac, framesToRead*pFlac->channels, pBufferOut) / pFlac->channels; -#if defined(_MSC_VER) && !defined(__clang__) - #pragma warning(pop) -#elif defined(__GNUC__) || defined(__clang__) - #pragma GCC diagnostic pop -#endif -} - - -drflac_uint64 drflac_read_s16(drflac* pFlac, drflac_uint64 samplesToRead, drflac_int16* pBufferOut) -{ - /* This reads samples in 2 passes and can probably be optimized. */ - drflac_uint64 totalSamplesRead = 0; - -#if defined(_MSC_VER) && !defined(__clang__) - #pragma warning(push) - #pragma warning(disable:4996) /* was declared deprecated */ -#elif defined(__GNUC__) || defined(__clang__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - - while (samplesToRead > 0) { - drflac_uint64 i; - drflac_int32 samples32[4096]; - drflac_uint64 samplesJustRead = drflac_read_s32(pFlac, (samplesToRead > 4096) ? 4096 : samplesToRead, samples32); - if (samplesJustRead == 0) { - break; /* Reached the end. */ - } - - /* s32 -> s16 */ - for (i = 0; i < samplesJustRead; ++i) { - pBufferOut[i] = (drflac_int16)(samples32[i] >> 16); - } - - totalSamplesRead += samplesJustRead; - samplesToRead -= samplesJustRead; - pBufferOut += samplesJustRead; - } - -#if defined(_MSC_VER) && !defined(__clang__) - #pragma warning(pop) -#elif defined(__GNUC__) || defined(__clang__) - #pragma GCC diagnostic pop -#endif - - return totalSamplesRead; -} - -drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut) -{ - /* This reads samples in 2 passes and can probably be optimized. */ - drflac_uint64 totalPCMFramesRead = 0; - - while (framesToRead > 0) { - drflac_uint64 iFrame; - drflac_int32 samples32[4096]; - drflac_uint64 framesJustRead = drflac_read_pcm_frames_s32(pFlac, (framesToRead > 4096/pFlac->channels) ? 4096/pFlac->channels : framesToRead, samples32); - if (framesJustRead == 0) { - break; /* Reached the end. */ - } - - /* s32 -> s16 */ - for (iFrame = 0; iFrame < framesJustRead; ++iFrame) { - drflac_uint32 iChannel; - for (iChannel = 0; iChannel < pFlac->channels; ++iChannel) { - drflac_uint64 iSample = iFrame*pFlac->channels + iChannel; - pBufferOut[iSample] = (drflac_int16)(samples32[iSample] >> 16); - } - } - - totalPCMFramesRead += framesJustRead; - framesToRead -= framesJustRead; - pBufferOut += framesJustRead * pFlac->channels; - } - - return totalPCMFramesRead; -} - - -drflac_uint64 drflac_read_f32(drflac* pFlac, drflac_uint64 samplesToRead, float* pBufferOut) -{ - /* This reads samples in 2 passes and can probably be optimized. */ - drflac_uint64 totalSamplesRead = 0; - -#if defined(_MSC_VER) && !defined(__clang__) - #pragma warning(push) - #pragma warning(disable:4996) /* was declared deprecated */ -#elif defined(__GNUC__) || defined(__clang__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif - - while (samplesToRead > 0) { - drflac_uint64 i; - drflac_int32 samples32[4096]; - drflac_uint64 samplesJustRead = drflac_read_s32(pFlac, (samplesToRead > 4096) ? 4096 : samplesToRead, samples32); - if (samplesJustRead == 0) { - break; /* Reached the end. */ - } - - /* s32 -> f32 */ - for (i = 0; i < samplesJustRead; ++i) { - pBufferOut[i] = (float)(samples32[i] / 2147483648.0); - } - - totalSamplesRead += samplesJustRead; - samplesToRead -= samplesJustRead; - pBufferOut += samplesJustRead; - } - -#if defined(_MSC_VER) && !defined(__clang__) - #pragma warning(pop) -#elif defined(__GNUC__) || defined(__clang__) - #pragma GCC diagnostic pop -#endif - - return totalSamplesRead; -} #if 0 -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { - int left = pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - int side = pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); - int right = left - side; + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; - pOutputSamples[i*2+0] = (float)(left / 2147483648.0); - pOutputSamples[i*2+1] = (float)(right / 2147483648.0); + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - float factor = 1 / 2147483648.0; - - drflac_int32 shift0 = unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample; - drflac_int32 shift1 = unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { - drflac_int32 left0 = pInputSamples0[i*4+0] << shift0; - drflac_int32 left1 = pInputSamples0[i*4+1] << shift0; - drflac_int32 left2 = pInputSamples0[i*4+2] << shift0; - drflac_int32 left3 = pInputSamples0[i*4+3] << shift0; + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; - drflac_int32 side0 = pInputSamples1[i*4+0] << shift1; - drflac_int32 side1 = pInputSamples1[i*4+1] << shift1; - drflac_int32 side2 = pInputSamples1[i*4+2] << shift1; - drflac_int32 side3 = pInputSamples1[i*4+3] << shift1; + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; - drflac_int32 right0 = left0 - side0; - drflac_int32 right1 = left1 - side1; - drflac_int32 right2 = left2 - side2; - drflac_int32 right3 = left3 - side3; + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; - pOutputSamples[i*8+0] = left0 * factor; - pOutputSamples[i*8+1] = right0 * factor; - pOutputSamples[i*8+2] = left1 * factor; - pOutputSamples[i*8+3] = right1 * factor; - pOutputSamples[i*8+4] = left2 * factor; - pOutputSamples[i*8+5] = right2 * factor; - pOutputSamples[i*8+6] = left3 * factor; - pOutputSamples[i*8+7] = right3 * factor; + left0 >>= 16; + left1 >>= 16; + left2 >>= 16; + left3 >>= 16; + + right0 >>= 16; + right1 >>= 16; + right2 >>= 16; + right3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)left0; + pOutputSamples[i*8+1] = (drflac_int16)right0; + pOutputSamples[i*8+2] = (drflac_int16)left1; + pOutputSamples[i*8+3] = (drflac_int16)right1; + pOutputSamples[i*8+4] = (drflac_int16)left2; + pOutputSamples[i*8+5] = (drflac_int16)right2; + pOutputSamples[i*8+6] = (drflac_int16)left3; + pOutputSamples[i*8+7] = (drflac_int16)right3; } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int left = pInputSamples0[i] << shift0; - int side = pInputSamples1[i] << shift1; - int right = left - side; + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; - pOutputSamples[i*2+0] = (float)(left * factor); - pOutputSamples[i*2+1] = (float)(right * factor); + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; } } #if defined(DRFLAC_SUPPORT_SSE2) -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) { - drflac_uint64 frameCount4; - __m128 factor; - int shift0; - int shift1; drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - drflac_assert(pFlac->bitsPerSample <= 24); - - frameCount4 = frameCount >> 2; - - factor = _mm_set1_ps(1.0f / 8388608.0f); - shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample) - 8; - shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample) - 8; + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); for (i = 0; i < frameCount4; ++i) { - __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); - __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i right = _mm_sub_epi32(left, side); - __m128i left = _mm_slli_epi32(inputSample0, shift0); - __m128i side = _mm_slli_epi32(inputSample1, shift1); + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + + left = vshrq_n_u32(left, 16); + right = vshrq_n_u32(right, 16); + + drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_left_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_left_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; + + left0 >>= 16; + left1 >>= 16; + left2 >>= 16; + left3 >>= 16; + + right0 >>= 16; + right1 >>= 16; + right2 >>= 16; + right3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)left0; + pOutputSamples[i*8+1] = (drflac_int16)right0; + pOutputSamples[i*8+2] = (drflac_int16)left1; + pOutputSamples[i*8+3] = (drflac_int16)right1; + pOutputSamples[i*8+4] = (drflac_int16)left2; + pOutputSamples[i*8+5] = (drflac_int16)right2; + pOutputSamples[i*8+6] = (drflac_int16)left3; + pOutputSamples[i*8+7] = (drflac_int16)right3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + for (i = 0; i < frameCount4; ++i) { + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + __m128i left = _mm_add_epi32(right, side); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + + left = vshrq_n_u32(left, 16); + right = vshrq_n_u32(right, 16); + + drflac__vst2q_u16((drflac_uint16*)pOutputSamples + i*8, vzip_u16(vmovn_u32(left), vmovn_u32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + left >>= 16; + right >>= 16; + + pOutputSamples[i*2+0] = (drflac_int16)left; + pOutputSamples[i*2+1] = (drflac_int16)right; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_right_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_right_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + + if (shift > 0) { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; + + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; + + temp0L >>= 16; + temp1L >>= 16; + temp2L >>= 16; + temp3L >>= 16; + + temp0R >>= 16; + temp1R >>= 16; + temp2R >>= 16; + temp3R >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)temp0L; + pOutputSamples[i*8+1] = (drflac_int16)temp0R; + pOutputSamples[i*8+2] = (drflac_int16)temp1L; + pOutputSamples[i*8+3] = (drflac_int16)temp1R; + pOutputSamples[i*8+4] = (drflac_int16)temp2L; + pOutputSamples[i*8+5] = (drflac_int16)temp2R; + pOutputSamples[i*8+6] = (drflac_int16)temp3L; + pOutputSamples[i*8+7] = (drflac_int16)temp3R; + } + } else { + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; + + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); + + temp0L = ((drflac_int32)(mid0 + side0) >> 1); + temp1L = ((drflac_int32)(mid1 + side1) >> 1); + temp2L = ((drflac_int32)(mid2 + side2) >> 1); + temp3L = ((drflac_int32)(mid3 + side3) >> 1); + + temp0R = ((drflac_int32)(mid0 - side0) >> 1); + temp1R = ((drflac_int32)(mid1 - side1) >> 1); + temp2R = ((drflac_int32)(mid2 - side2) >> 1); + temp3R = ((drflac_int32)(mid3 - side3) >> 1); + + temp0L >>= 16; + temp1L >>= 16; + temp2L >>= 16; + temp3L >>= 16; + + temp0R >>= 16; + temp1R >>= 16; + temp2R >>= 16; + temp3R >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)temp0L; + pOutputSamples[i*8+1] = (drflac_int16)temp0R; + pOutputSamples[i*8+2] = (drflac_int16)temp1L; + pOutputSamples[i*8+3] = (drflac_int16)temp1R; + pOutputSamples[i*8+4] = (drflac_int16)temp2L; + pOutputSamples[i*8+5] = (drflac_int16)temp2R; + pOutputSamples[i*8+6] = (drflac_int16)temp3L; + pOutputSamples[i*8+7] = (drflac_int16)temp3R; + } + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) >> 16); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + right = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); + } + } else { + shift -= 1; + for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; + __m128i left; + __m128i right; + + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); + + left = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + right = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); + } + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; + int32x4_t wbpsShift0_4; /* wbps = Wasted Bits Per Sample */ + int32x4_t wbpsShift1_4; /* wbps = Wasted Bits Per Sample */ + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + wbpsShift0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbpsShift1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + left = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + right = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((drflac_int32)(mid + side) >> 1) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((drflac_int32)(mid - side) >> 1) >> 16); + } + } else { + int32x4_t shift4; + + shift -= 1; + shift4 = vdupq_n_s32(shift); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t left; + int32x4_t right; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbpsShift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbpsShift1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + left = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + right = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int16)(((mid + side) << shift) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)(((mid - side) << shift) >> 16); + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_mid_side__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_mid_side__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + for (drflac_uint64 i = 0; i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; + + tempL0 >>= 16; + tempL1 >>= 16; + tempL2 >>= 16; + tempL3 >>= 16; + + tempR0 >>= 16; + tempR1 >>= 16; + tempR2 >>= 16; + tempR3 >>= 16; + + pOutputSamples[i*8+0] = (drflac_int16)tempL0; + pOutputSamples[i*8+1] = (drflac_int16)tempR0; + pOutputSamples[i*8+2] = (drflac_int16)tempL1; + pOutputSamples[i*8+3] = (drflac_int16)tempR1; + pOutputSamples[i*8+4] = (drflac_int16)tempL2; + pOutputSamples[i*8+5] = (drflac_int16)tempR2; + pOutputSamples[i*8+6] = (drflac_int16)tempL3; + pOutputSamples[i*8+7] = (drflac_int16)tempR3; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); + + left = _mm_srai_epi32(left, 16); + right = _mm_srai_epi32(right, 16); + + /* At this point we have results. We can now pack and interleave these into a single __m128i object and then store the in the output buffer. */ + _mm_storeu_si128((__m128i*)(pOutputSamples + i*8), drflac__mm_packs_interleaved_epi32(left, right)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} +#endif + +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + int32x4_t shift0_4 = vdupq_n_s32(shift0); + int32x4_t shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t left; + int32x4_t right; + + left = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); + right = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); + + left = vshrq_n_s32(left, 16); + right = vshrq_n_s32(right, 16); + + drflac__vst2q_s16(pOutputSamples + i*8, vzip_s16(vmovn_s32(left), vmovn_s32(right))); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int16)((pInputSamples0U32[i] << shift0) >> 16); + pOutputSamples[i*2+1] = (drflac_int16)((pInputSamples1U32[i] << shift1) >> 16); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_s16__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, drflac_int16* pOutputSamples) +{ +#if defined(DRFLAC_SUPPORT_SSE2) + if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_s16__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else +#endif + { + /* Scalar fallback. */ +#if 0 + drflac_read_pcm_frames_s16__decode_independent_stereo__reference(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#else + drflac_read_pcm_frames_s16__decode_independent_stereo__scalar(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); +#endif + } +} + +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_s16(drflac* pFlac, drflac_uint64 framesToRead, drflac_int16* pBufferOut) +{ + drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; + + if (pFlac == NULL || framesToRead == 0) { + return 0; + } + + if (pBufferOut == NULL) { + return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); + } + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + + framesRead = 0; + while (framesToRead > 0) { + /* If we've run out of samples in this frame, go to the next. */ + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { + if (!drflac__read_and_decode_next_flac_frame(pFlac)) { + break; /* Couldn't read the next frame, so just break from the loop and return. */ + } + } else { + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; + drflac_uint64 frameCountThisIteration = framesToRead; + + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; + } + + if (channelCount == 2) { + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; + + switch (pFlac->currentFLACFrame.header.channelAssignment) + { + case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: + { + drflac_read_pcm_frames_s16__decode_left_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_RIGHT_SIDE: + { + drflac_read_pcm_frames_s16__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: + { + drflac_read_pcm_frames_s16__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + + case DRFLAC_CHANNEL_ASSIGNMENT_INDEPENDENT: + default: + { + drflac_read_pcm_frames_s16__decode_independent_stereo(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); + } break; + } + } else { + /* Generic interleaving. */ + drflac_uint64 i; + for (i = 0; i < frameCountThisIteration; ++i) { + unsigned int j; + for (j = 0; j < channelCount; ++j) { + drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + pBufferOut[(i*channelCount)+j] = (drflac_int16)(sampleS32 >> 16); + } + } + } + + framesRead += frameCountThisIteration; + pBufferOut += frameCountThisIteration * channelCount; + framesToRead -= frameCountThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (drflac_uint32)frameCountThisIteration; + } + } + + return framesRead; +} + + +#if 0 +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + for (i = 0; i < frameCount; ++i) { + drflac_uint32 left = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + float factor = 1 / 2147483648.0; + + for (i = 0; i < frameCount4; ++i) { + drflac_uint32 left0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 left1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 left2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 left3 = pInputSamples0U32[i*4+3] << shift0; + + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << shift1; + + drflac_uint32 right0 = left0 - side0; + drflac_uint32 right1 = left1 - side1; + drflac_uint32 right2 = left2 - side2; + drflac_uint32 right3 = left3 - side3; + + pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left * factor; + pOutputSamples[i*2+1] = (drflac_int32)right * factor; + } +} + +#if defined(DRFLAC_SUPPORT_SSE2) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = _mm_set1_ps(1.0f / 8388608.0f); + + for (i = 0; i < frameCount4; ++i) { + __m128i left = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i right = _mm_sub_epi32(left, side); __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); - pOutputSamples[i*8+0] = ((float*)&leftf)[0]; - pOutputSamples[i*8+1] = ((float*)&rightf)[0]; - pOutputSamples[i*8+2] = ((float*)&leftf)[1]; - pOutputSamples[i*8+3] = ((float*)&rightf)[1]; - pOutputSamples[i*8+4] = ((float*)&leftf)[2]; - pOutputSamples[i*8+5] = ((float*)&rightf)[2]; - pOutputSamples[i*8+6] = ((float*)&leftf)[3]; - pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int left = pInputSamples0[i] << shift0; - int side = pInputSamples1[i] << shift1; - int right = left - side; + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; - pOutputSamples[i*2+0] = (float)(left / 8388608.0f); - pOutputSamples[i*2+1] = (float)(right / 8388608.0f); + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + float32x4_t factor4; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor4 = vdupq_n_f32(1.0f / 8388608.0f); + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t left; + uint32x4_t side; + uint32x4_t right; + float32x4_t leftf; + float32x4_t rightf; + + left = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + right = vsubq_u32(left, side); + leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 left = pInputSamples0U32[i] << shift0; + drflac_uint32 side = pInputSamples1U32[i] << shift1; + drflac_uint32 right = left - side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_left_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_left_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else #endif { /* Scalar fallback. */ @@ -7255,119 +10645,159 @@ static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_left_side(drflac* p #if 0 -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; for (i = 0; i < frameCount; ++i) { - int side = pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - int right = pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); - int left = right + side; + drflac_uint32 side = (drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + drflac_uint32 right = (drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + drflac_uint32 left = right + side; - pOutputSamples[i*2+0] = (float)(left / 2147483648.0); - pOutputSamples[i*2+1] = (float)(right / 2147483648.0); + pOutputSamples[i*2+0] = (float)((drflac_int32)left / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)right / 2147483648.0); } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; - + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; float factor = 1 / 2147483648.0; - drflac_int32 shift0 = unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample; - drflac_int32 shift1 = unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample; for (i = 0; i < frameCount4; ++i) { - drflac_int32 side0 = pInputSamples0[i*4+0] << shift0; - drflac_int32 side1 = pInputSamples0[i*4+1] << shift0; - drflac_int32 side2 = pInputSamples0[i*4+2] << shift0; - drflac_int32 side3 = pInputSamples0[i*4+3] << shift0; + drflac_uint32 side0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 side1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 side2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 side3 = pInputSamples0U32[i*4+3] << shift0; - drflac_int32 right0 = pInputSamples1[i*4+0] << shift1; - drflac_int32 right1 = pInputSamples1[i*4+1] << shift1; - drflac_int32 right2 = pInputSamples1[i*4+2] << shift1; - drflac_int32 right3 = pInputSamples1[i*4+3] << shift1; + drflac_uint32 right0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 right1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 right2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 right3 = pInputSamples1U32[i*4+3] << shift1; - drflac_int32 left0 = right0 + side0; - drflac_int32 left1 = right1 + side1; - drflac_int32 left2 = right2 + side2; - drflac_int32 left3 = right3 + side3; + drflac_uint32 left0 = right0 + side0; + drflac_uint32 left1 = right1 + side1; + drflac_uint32 left2 = right2 + side2; + drflac_uint32 left3 = right3 + side3; - pOutputSamples[i*8+0] = left0 * factor; - pOutputSamples[i*8+1] = right0 * factor; - pOutputSamples[i*8+2] = left1 * factor; - pOutputSamples[i*8+3] = right1 * factor; - pOutputSamples[i*8+4] = left2 * factor; - pOutputSamples[i*8+5] = right2 * factor; - pOutputSamples[i*8+6] = left3 * factor; - pOutputSamples[i*8+7] = right3 * factor; + pOutputSamples[i*8+0] = (drflac_int32)left0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)right0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)left1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)right1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)left2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)right2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)left3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)right3 * factor; } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int side = pInputSamples0[i] << shift0; - int right = pInputSamples1[i] << shift1; - int left = right + side; + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; - pOutputSamples[i*2+0] = (float)(left * factor); - pOutputSamples[i*2+1] = (float)(right * factor); + pOutputSamples[i*2+0] = (drflac_int32)left * factor; + pOutputSamples[i*2+1] = (drflac_int32)right * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { - drflac_uint64 frameCount4; - __m128 factor; - int shift0; - int shift1; drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor; - drflac_assert(pFlac->bitsPerSample <= 24); - - frameCount4 = frameCount >> 2; + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor = _mm_set1_ps(1.0f / 8388608.0f); - shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample) - 8; - shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample) - 8; for (i = 0; i < frameCount4; ++i) { - __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); - __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); - - __m128i side = _mm_slli_epi32(inputSample0, shift0); - __m128i right = _mm_slli_epi32(inputSample1, shift1); + __m128i side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + __m128i right = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); __m128i left = _mm_add_epi32(right, side); __m128 leftf = _mm_mul_ps(_mm_cvtepi32_ps(left), factor); __m128 rightf = _mm_mul_ps(_mm_cvtepi32_ps(right), factor); - pOutputSamples[i*8+0] = ((float*)&leftf)[0]; - pOutputSamples[i*8+1] = ((float*)&rightf)[0]; - pOutputSamples[i*8+2] = ((float*)&leftf)[1]; - pOutputSamples[i*8+3] = ((float*)&rightf)[1]; - pOutputSamples[i*8+4] = ((float*)&leftf)[2]; - pOutputSamples[i*8+5] = ((float*)&rightf)[2]; - pOutputSamples[i*8+6] = ((float*)&leftf)[3]; - pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int side = pInputSamples0[i] << shift0; - int right = pInputSamples1[i] << shift1; - int left = right + side; + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; - pOutputSamples[i*2+0] = (float)(left / 8388608.0f); - pOutputSamples[i*2+1] = (float)(right / 8388608.0f); + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + float32x4_t factor4; + int32x4_t shift0_4; + int32x4_t shift1_4; + + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor4 = vdupq_n_f32(1.0f / 8388608.0f); + shift0_4 = vdupq_n_s32(shift0); + shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + uint32x4_t side; + uint32x4_t right; + uint32x4_t left; + float32x4_t leftf; + float32x4_t rightf; + + side = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4); + right = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4); + left = vaddq_u32(right, side); + leftf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(left)), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(vreinterpretq_s32_u32(right)), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 side = pInputSamples0U32[i] << shift0; + drflac_uint32 right = pInputSamples1U32[i] << shift1; + drflac_uint32 left = right + side; + + pOutputSamples[i*2+0] = (drflac_int32)left / 8388608.0f; + pOutputSamples[i*2+1] = (drflac_int32)right / 8388608.0f; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_right_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_right_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else #endif { /* Scalar fallback. */ @@ -7381,197 +10811,185 @@ static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_right_side(drflac* #if 0 -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { - int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - - mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + drflac_uint32 mid = (drflac_uint32)pInputSamples0[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = (drflac_uint32)pInputSamples1[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); - pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (float)((((drflac_int32)(mid + side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((((drflac_int32)(mid - side) >> 1) << (unusedBitsPerSample)) / 2147483648.0); } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; - + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample; float factor = 1 / 2147483648.0; - int shift = unusedBitsPerSample; if (shift > 0) { shift -= 1; for (i = 0; i < frameCount4; ++i) { - int temp0L; - int temp1L; - int temp2L; - int temp3L; - int temp0R; - int temp1R; - int temp2R; - int temp3R; + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; - int mid0 = pInputSamples0[i*4+0] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int mid1 = pInputSamples0[i*4+1] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int mid2 = pInputSamples0[i*4+2] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int mid3 = pInputSamples0[i*4+3] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; - int side0 = pInputSamples1[i*4+0] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - int side1 = pInputSamples1[i*4+1] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - int side2 = pInputSamples1[i*4+2] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - int side3 = pInputSamples1[i*4+3] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - mid0 = (((drflac_uint32)mid0) << 1) | (side0 & 0x01); - mid1 = (((drflac_uint32)mid1) << 1) | (side1 & 0x01); - mid2 = (((drflac_uint32)mid2) << 1) | (side2 & 0x01); - mid3 = (((drflac_uint32)mid3) << 1) | (side3 & 0x01); + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); - temp0L = ((mid0 + side0) << shift); - temp1L = ((mid1 + side1) << shift); - temp2L = ((mid2 + side2) << shift); - temp3L = ((mid3 + side3) << shift); + temp0L = (mid0 + side0) << shift; + temp1L = (mid1 + side1) << shift; + temp2L = (mid2 + side2) << shift; + temp3L = (mid3 + side3) << shift; - temp0R = ((mid0 - side0) << shift); - temp1R = ((mid1 - side1) << shift); - temp2R = ((mid2 - side2) << shift); - temp3R = ((mid3 - side3) << shift); + temp0R = (mid0 - side0) << shift; + temp1R = (mid1 - side1) << shift; + temp2R = (mid2 - side2) << shift; + temp3R = (mid3 - side3) << shift; - pOutputSamples[i*8+0] = (float)(temp0L * factor); - pOutputSamples[i*8+1] = (float)(temp0R * factor); - pOutputSamples[i*8+2] = (float)(temp1L * factor); - pOutputSamples[i*8+3] = (float)(temp1R * factor); - pOutputSamples[i*8+4] = (float)(temp2L * factor); - pOutputSamples[i*8+5] = (float)(temp2R * factor); - pOutputSamples[i*8+6] = (float)(temp3L * factor); - pOutputSamples[i*8+7] = (float)(temp3R * factor); + pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; + pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; + pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; + pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; + pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; + pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; + pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; + pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; } } else { for (i = 0; i < frameCount4; ++i) { - int temp0L; - int temp1L; - int temp2L; - int temp3L; - int temp0R; - int temp1R; - int temp2R; - int temp3R; + drflac_uint32 temp0L; + drflac_uint32 temp1L; + drflac_uint32 temp2L; + drflac_uint32 temp3L; + drflac_uint32 temp0R; + drflac_uint32 temp1R; + drflac_uint32 temp2R; + drflac_uint32 temp3R; - int mid0 = pInputSamples0[i*4+0] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int mid1 = pInputSamples0[i*4+1] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int mid2 = pInputSamples0[i*4+2] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int mid3 = pInputSamples0[i*4+3] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid0 = pInputSamples0U32[i*4+0] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid1 = pInputSamples0U32[i*4+1] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid2 = pInputSamples0U32[i*4+2] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 mid3 = pInputSamples0U32[i*4+3] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; - int side0 = pInputSamples1[i*4+0] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - int side1 = pInputSamples1[i*4+1] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - int side2 = pInputSamples1[i*4+2] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - int side3 = pInputSamples1[i*4+3] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side0 = pInputSamples1U32[i*4+0] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side1 = pInputSamples1U32[i*4+1] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side2 = pInputSamples1U32[i*4+2] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + drflac_uint32 side3 = pInputSamples1U32[i*4+3] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - mid0 = (((drflac_uint32)mid0) << 1) | (side0 & 0x01); - mid1 = (((drflac_uint32)mid1) << 1) | (side1 & 0x01); - mid2 = (((drflac_uint32)mid2) << 1) | (side2 & 0x01); - mid3 = (((drflac_uint32)mid3) << 1) | (side3 & 0x01); + mid0 = (mid0 << 1) | (side0 & 0x01); + mid1 = (mid1 << 1) | (side1 & 0x01); + mid2 = (mid2 << 1) | (side2 & 0x01); + mid3 = (mid3 << 1) | (side3 & 0x01); - temp0L = ((mid0 + side0) >> 1); - temp1L = ((mid1 + side1) >> 1); - temp2L = ((mid2 + side2) >> 1); - temp3L = ((mid3 + side3) >> 1); + temp0L = (drflac_uint32)((drflac_int32)(mid0 + side0) >> 1); + temp1L = (drflac_uint32)((drflac_int32)(mid1 + side1) >> 1); + temp2L = (drflac_uint32)((drflac_int32)(mid2 + side2) >> 1); + temp3L = (drflac_uint32)((drflac_int32)(mid3 + side3) >> 1); - temp0R = ((mid0 - side0) >> 1); - temp1R = ((mid1 - side1) >> 1); - temp2R = ((mid2 - side2) >> 1); - temp3R = ((mid3 - side3) >> 1); + temp0R = (drflac_uint32)((drflac_int32)(mid0 - side0) >> 1); + temp1R = (drflac_uint32)((drflac_int32)(mid1 - side1) >> 1); + temp2R = (drflac_uint32)((drflac_int32)(mid2 - side2) >> 1); + temp3R = (drflac_uint32)((drflac_int32)(mid3 - side3) >> 1); - pOutputSamples[i*8+0] = (float)(temp0L * factor); - pOutputSamples[i*8+1] = (float)(temp0R * factor); - pOutputSamples[i*8+2] = (float)(temp1L * factor); - pOutputSamples[i*8+3] = (float)(temp1R * factor); - pOutputSamples[i*8+4] = (float)(temp2L * factor); - pOutputSamples[i*8+5] = (float)(temp2R * factor); - pOutputSamples[i*8+6] = (float)(temp3L * factor); - pOutputSamples[i*8+7] = (float)(temp3R * factor); + pOutputSamples[i*8+0] = (drflac_int32)temp0L * factor; + pOutputSamples[i*8+1] = (drflac_int32)temp0R * factor; + pOutputSamples[i*8+2] = (drflac_int32)temp1L * factor; + pOutputSamples[i*8+3] = (drflac_int32)temp1R * factor; + pOutputSamples[i*8+4] = (drflac_int32)temp2L * factor; + pOutputSamples[i*8+5] = (drflac_int32)temp2R * factor; + pOutputSamples[i*8+6] = (drflac_int32)temp3L * factor; + pOutputSamples[i*8+7] = (drflac_int32)temp3R * factor; } } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - - mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << unusedBitsPerSample) * factor); - pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << unusedBitsPerSample) * factor); + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid + side) >> 1) << unusedBitsPerSample) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((drflac_uint32)((drflac_int32)(mid - side) >> 1) << unusedBitsPerSample) * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; - drflac_uint64 frameCount4; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample - 8; float factor; - int shift; __m128 factor128; - drflac_assert(pFlac->bitsPerSample <= 24); - - frameCount4 = frameCount >> 2; + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); factor = 1.0f / 8388608.0f; - factor128 = _mm_set1_ps(1.0f / 8388608.0f); + factor128 = _mm_set1_ps(factor); - shift = unusedBitsPerSample - 8; if (shift == 0) { for (i = 0; i < frameCount4; ++i) { + __m128i mid; + __m128i side; __m128i tempL; __m128i tempR; __m128 leftf; __m128 rightf; - __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); - __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); - __m128i mid = _mm_slli_epi32(inputSample0, pFlac->currentFrame.subframes[0].wastedBitsPerSample); - __m128i side = _mm_slli_epi32(inputSample1, pFlac->currentFrame.subframes[1].wastedBitsPerSample); + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); - mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); - - tempL = _mm_add_epi32(mid, side); - tempR = _mm_sub_epi32(mid, side); - - /* Signed bit shift. */ - tempL = _mm_or_si128(_mm_srli_epi32(tempL, 1), _mm_and_si128(tempL, _mm_set1_epi32(0x80000000))); - tempR = _mm_or_si128(_mm_srli_epi32(tempR, 1), _mm_and_si128(tempR, _mm_set1_epi32(0x80000000))); + tempL = _mm_srai_epi32(_mm_add_epi32(mid, side), 1); + tempR = _mm_srai_epi32(_mm_sub_epi32(mid, side), 1); leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); - pOutputSamples[i*8+0] = ((float*)&leftf)[0]; - pOutputSamples[i*8+1] = ((float*)&rightf)[0]; - pOutputSamples[i*8+2] = ((float*)&leftf)[1]; - pOutputSamples[i*8+3] = ((float*)&rightf)[1]; - pOutputSamples[i*8+4] = ((float*)&leftf)[2]; - pOutputSamples[i*8+5] = ((float*)&rightf)[2]; - pOutputSamples[i*8+6] = ((float*)&leftf)[3]; - pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - - mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - pOutputSamples[i*2+0] = (float)(((mid + side) >> 1) * factor); - pOutputSamples[i*2+1] = (float)(((mid - side) >> 1) * factor); + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; + pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; } } else { + shift -= 1; for (i = 0; i < frameCount4; ++i) { - __m128i inputSample0; - __m128i inputSample1; __m128i mid; __m128i side; __m128i tempL; @@ -7579,50 +10997,133 @@ static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__sse2(drfl __m128 leftf; __m128 rightf; - inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); - inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + mid = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + side = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); - mid = _mm_slli_epi32(inputSample0, pFlac->currentFrame.subframes[0].wastedBitsPerSample); - side = _mm_slli_epi32(inputSample1, pFlac->currentFrame.subframes[1].wastedBitsPerSample); + mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); - mid = _mm_or_si128(_mm_slli_epi32(mid, 1), _mm_and_si128(side, _mm_set1_epi32(0x01))); - - tempL = _mm_slli_epi32(_mm_srli_epi32(_mm_add_epi32(mid, side), 1), shift); - tempR = _mm_slli_epi32(_mm_srli_epi32(_mm_sub_epi32(mid, side), 1), shift); + tempL = _mm_slli_epi32(_mm_add_epi32(mid, side), shift); + tempR = _mm_slli_epi32(_mm_sub_epi32(mid, side), shift); leftf = _mm_mul_ps(_mm_cvtepi32_ps(tempL), factor128); rightf = _mm_mul_ps(_mm_cvtepi32_ps(tempR), factor128); - pOutputSamples[i*8+0] = ((float*)&leftf)[0]; - pOutputSamples[i*8+1] = ((float*)&rightf)[0]; - pOutputSamples[i*8+2] = ((float*)&leftf)[1]; - pOutputSamples[i*8+3] = ((float*)&rightf)[1]; - pOutputSamples[i*8+4] = ((float*)&leftf)[2]; - pOutputSamples[i*8+5] = ((float*)&rightf)[2]; - pOutputSamples[i*8+6] = ((float*)&leftf)[3]; - pOutputSamples[i*8+7] = ((float*)&rightf)[3]; + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { - int mid = pInputSamples0[i] << pFlac->currentFrame.subframes[0].wastedBitsPerSample; - int side = pInputSamples1[i] << pFlac->currentFrame.subframes[1].wastedBitsPerSample; - - mid = (((drflac_uint32)mid) << 1) | (side & 0x01); + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; - pOutputSamples[i*2+0] = (float)((((mid + side) >> 1) << shift) * factor); - pOutputSamples[i*2+1] = (float)((((mid - side) >> 1) << shift) * factor); + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; } } } #endif +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift = unusedBitsPerSample - 8; + float factor; + float32x4_t factor4; + int32x4_t shift4; + int32x4_t wbps0_4; /* Wasted Bits Per Sample */ + int32x4_t wbps1_4; /* Wasted Bits Per Sample */ -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) + DRFLAC_ASSERT(pFlac->bitsPerSample <= 24); + + factor = 1.0f / 8388608.0f; + factor4 = vdupq_n_f32(factor); + wbps0_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample); + wbps1_4 = vdupq_n_s32(pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample); + + if (shift == 0) { + for (i = 0; i < frameCount4; ++i) { + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + uint32x4_t mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); + uint32x4_t side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + lefti = vshrq_n_s32(vreinterpretq_s32_u32(vaddq_u32(mid, side)), 1); + righti = vshrq_n_s32(vreinterpretq_s32_u32(vsubq_u32(mid, side)), 1); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = ((drflac_int32)(mid + side) >> 1) * factor; + pOutputSamples[i*2+1] = ((drflac_int32)(mid - side) >> 1) * factor; + } + } else { + shift -= 1; + shift4 = vdupq_n_s32(shift); + for (i = 0; i < frameCount4; ++i) { + uint32x4_t mid; + uint32x4_t side; + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + mid = vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), wbps0_4); + side = vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), wbps1_4); + + mid = vorrq_u32(vshlq_n_u32(mid, 1), vandq_u32(side, vdupq_n_u32(1))); + + lefti = vreinterpretq_s32_u32(vshlq_u32(vaddq_u32(mid, side), shift4)); + righti = vreinterpretq_s32_u32(vshlq_u32(vsubq_u32(mid, side), shift4)); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + drflac_uint32 mid = pInputSamples0U32[i] << pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 side = pInputSamples1U32[i] << pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; + + mid = (mid << 1) | (side & 0x01); + + pOutputSamples[i*2+0] = (drflac_int32)((mid + side) << shift) * factor; + pOutputSamples[i*2+1] = (drflac_int32)((mid - side) << shift) * factor; + } + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_mid_side__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_mid_side__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else #endif { /* Scalar fallback. */ @@ -7635,97 +11136,135 @@ static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_mid_side(drflac* pF } #if 0 -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__reference(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { for (drflac_uint64 i = 0; i < frameCount; ++i) { - pOutputSamples[i*2+0] = (float)((pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample)) / 2147483648.0); - pOutputSamples[i*2+1] = (float)((pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+0] = (float)((drflac_int32)((drflac_uint32)pInputSamples0[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample)) / 2147483648.0); + pOutputSamples[i*2+1] = (float)((drflac_int32)((drflac_uint32)pInputSamples1[i] << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample)) / 2147483648.0); } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__scalar(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; - + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample; + drflac_uint32 shift1 = unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample; float factor = 1 / 2147483648.0; - int shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample); - int shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample); - for (i = 0; i < frameCount4; ++i) { - int tempL0 = pInputSamples0[i*4+0] << shift0; - int tempL1 = pInputSamples0[i*4+1] << shift0; - int tempL2 = pInputSamples0[i*4+2] << shift0; - int tempL3 = pInputSamples0[i*4+3] << shift0; + drflac_uint32 tempL0 = pInputSamples0U32[i*4+0] << shift0; + drflac_uint32 tempL1 = pInputSamples0U32[i*4+1] << shift0; + drflac_uint32 tempL2 = pInputSamples0U32[i*4+2] << shift0; + drflac_uint32 tempL3 = pInputSamples0U32[i*4+3] << shift0; - int tempR0 = pInputSamples1[i*4+0] << shift1; - int tempR1 = pInputSamples1[i*4+1] << shift1; - int tempR2 = pInputSamples1[i*4+2] << shift1; - int tempR3 = pInputSamples1[i*4+3] << shift1; + drflac_uint32 tempR0 = pInputSamples1U32[i*4+0] << shift1; + drflac_uint32 tempR1 = pInputSamples1U32[i*4+1] << shift1; + drflac_uint32 tempR2 = pInputSamples1U32[i*4+2] << shift1; + drflac_uint32 tempR3 = pInputSamples1U32[i*4+3] << shift1; - pOutputSamples[i*8+0] = (float)(tempL0 * factor); - pOutputSamples[i*8+1] = (float)(tempR0 * factor); - pOutputSamples[i*8+2] = (float)(tempL1 * factor); - pOutputSamples[i*8+3] = (float)(tempR1 * factor); - pOutputSamples[i*8+4] = (float)(tempL2 * factor); - pOutputSamples[i*8+5] = (float)(tempR2 * factor); - pOutputSamples[i*8+6] = (float)(tempL3 * factor); - pOutputSamples[i*8+7] = (float)(tempR3 * factor); + pOutputSamples[i*8+0] = (drflac_int32)tempL0 * factor; + pOutputSamples[i*8+1] = (drflac_int32)tempR0 * factor; + pOutputSamples[i*8+2] = (drflac_int32)tempL1 * factor; + pOutputSamples[i*8+3] = (drflac_int32)tempR1 * factor; + pOutputSamples[i*8+4] = (drflac_int32)tempL2 * factor; + pOutputSamples[i*8+5] = (drflac_int32)tempR2 * factor; + pOutputSamples[i*8+6] = (drflac_int32)tempL3 * factor; + pOutputSamples[i*8+7] = (drflac_int32)tempR3 * factor; } for (i = (frameCount4 << 2); i < frameCount; ++i) { - pOutputSamples[i*2+0] = (float)((pInputSamples0[i] << shift0) * factor); - pOutputSamples[i*2+1] = (float)((pInputSamples1[i] << shift1) * factor); + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; } } #if defined(DRFLAC_SUPPORT_SSE2) -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { drflac_uint64 i; drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; float factor = 1.0f / 8388608.0f; - __m128 factor128 = _mm_set1_ps(1.0f / 8388608.0f); - - int shift0 = (unusedBitsPerSample + pFlac->currentFrame.subframes[0].wastedBitsPerSample) - 8; - int shift1 = (unusedBitsPerSample + pFlac->currentFrame.subframes[1].wastedBitsPerSample) - 8; + __m128 factor128 = _mm_set1_ps(factor); for (i = 0; i < frameCount4; ++i) { - __m128i inputSample0 = _mm_loadu_si128((const __m128i*)pInputSamples0 + i); - __m128i inputSample1 = _mm_loadu_si128((const __m128i*)pInputSamples1 + i); + __m128i lefti; + __m128i righti; + __m128 leftf; + __m128 rightf; - __m128i i32L = _mm_slli_epi32(inputSample0, shift0); - __m128i i32R = _mm_slli_epi32(inputSample1, shift1); + lefti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples0 + i), shift0); + righti = _mm_slli_epi32(_mm_loadu_si128((const __m128i*)pInputSamples1 + i), shift1); - __m128 f32L = _mm_mul_ps(_mm_cvtepi32_ps(i32L), factor128); - __m128 f32R = _mm_mul_ps(_mm_cvtepi32_ps(i32R), factor128); + leftf = _mm_mul_ps(_mm_cvtepi32_ps(lefti), factor128); + rightf = _mm_mul_ps(_mm_cvtepi32_ps(righti), factor128); - pOutputSamples[i*8+0] = ((float*)&f32L)[0]; - pOutputSamples[i*8+1] = ((float*)&f32R)[0]; - pOutputSamples[i*8+2] = ((float*)&f32L)[1]; - pOutputSamples[i*8+3] = ((float*)&f32R)[1]; - pOutputSamples[i*8+4] = ((float*)&f32L)[2]; - pOutputSamples[i*8+5] = ((float*)&f32R)[2]; - pOutputSamples[i*8+6] = ((float*)&f32L)[3]; - pOutputSamples[i*8+7] = ((float*)&f32R)[3]; + _mm_storeu_ps(pOutputSamples + i*8 + 0, _mm_unpacklo_ps(leftf, rightf)); + _mm_storeu_ps(pOutputSamples + i*8 + 4, _mm_unpackhi_ps(leftf, rightf)); } for (i = (frameCount4 << 2); i < frameCount; ++i) { - pOutputSamples[i*2+0] = (float)((pInputSamples0[i] << shift0) * factor); - pOutputSamples[i*2+1] = (float)((pInputSamples1[i] << shift1) * factor); + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; } } #endif -static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_int32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +#if defined(DRFLAC_SUPPORT_NEON) +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo__neon(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) +{ + drflac_uint64 i; + drflac_uint64 frameCount4 = frameCount >> 2; + const drflac_uint32* pInputSamples0U32 = (const drflac_uint32*)pInputSamples0; + const drflac_uint32* pInputSamples1U32 = (const drflac_uint32*)pInputSamples1; + drflac_uint32 shift0 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[0].wastedBitsPerSample) - 8; + drflac_uint32 shift1 = (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[1].wastedBitsPerSample) - 8; + + float factor = 1.0f / 8388608.0f; + float32x4_t factor4 = vdupq_n_f32(factor); + int32x4_t shift0_4 = vdupq_n_s32(shift0); + int32x4_t shift1_4 = vdupq_n_s32(shift1); + + for (i = 0; i < frameCount4; ++i) { + int32x4_t lefti; + int32x4_t righti; + float32x4_t leftf; + float32x4_t rightf; + + lefti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples0U32 + i*4), shift0_4)); + righti = vreinterpretq_s32_u32(vshlq_u32(vld1q_u32(pInputSamples1U32 + i*4), shift1_4)); + + leftf = vmulq_f32(vcvtq_f32_s32(lefti), factor4); + rightf = vmulq_f32(vcvtq_f32_s32(righti), factor4); + + drflac__vst2q_f32(pOutputSamples + i*8, vzipq_f32(leftf, rightf)); + } + + for (i = (frameCount4 << 2); i < frameCount; ++i) { + pOutputSamples[i*2+0] = (drflac_int32)(pInputSamples0U32[i] << shift0) * factor; + pOutputSamples[i*2+1] = (drflac_int32)(pInputSamples1U32[i] << shift1) * factor; + } +} +#endif + +static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo(drflac* pFlac, drflac_uint64 frameCount, drflac_uint32 unusedBitsPerSample, const drflac_int32* pInputSamples0, const drflac_int32* pInputSamples1, float* pOutputSamples) { #if defined(DRFLAC_SUPPORT_SSE2) if (drflac__gIsSSE2Supported && pFlac->bitsPerSample <= 24) { drflac_read_pcm_frames_f32__decode_independent_stereo__sse2(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); } else +#elif defined(DRFLAC_SUPPORT_NEON) + if (drflac__gIsNEONSupported && pFlac->bitsPerSample <= 24) { + drflac_read_pcm_frames_f32__decode_independent_stereo__neon(pFlac, frameCount, unusedBitsPerSample, pInputSamples0, pInputSamples1, pOutputSamples); + } else #endif { /* Scalar fallback. */ @@ -7737,9 +11276,10 @@ static DRFLAC_INLINE void drflac_read_pcm_frames_f32__decode_independent_stereo( } } -drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) +DRFLAC_API drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRead, float* pBufferOut) { drflac_uint64 framesRead; + drflac_uint32 unusedBitsPerSample; if (pFlac == NULL || framesToRead == 0) { return 0; @@ -7749,31 +11289,30 @@ drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRe return drflac__seek_forward_by_pcm_frames(pFlac, framesToRead); } + DRFLAC_ASSERT(pFlac->bitsPerSample <= 32); + unusedBitsPerSample = 32 - pFlac->bitsPerSample; + framesRead = 0; while (framesToRead > 0) { /* If we've run out of samples in this frame, go to the next. */ - if (pFlac->currentFrame.samplesRemaining == 0) { + if (pFlac->currentFLACFrame.pcmFramesRemaining == 0) { if (!drflac__read_and_decode_next_flac_frame(pFlac)) { break; /* Couldn't read the next frame, so just break from the loop and return. */ } } else { - unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - drflac_uint64 totalFramesInPacket = pFlac->currentFrame.header.blockSize; - drflac_uint64 framesReadFromPacketSoFar = totalFramesInPacket - (pFlac->currentFrame.samplesRemaining/channelCount); - drflac_uint64 iFirstPCMFrame = framesReadFromPacketSoFar; - drflac_int32 unusedBitsPerSample = 32 - pFlac->bitsPerSample; + unsigned int channelCount = drflac__get_channel_count_from_channel_assignment(pFlac->currentFLACFrame.header.channelAssignment); + drflac_uint64 iFirstPCMFrame = pFlac->currentFLACFrame.header.blockSizeInPCMFrames - pFlac->currentFLACFrame.pcmFramesRemaining; drflac_uint64 frameCountThisIteration = framesToRead; - drflac_uint64 samplesReadThisIteration; - if (frameCountThisIteration > pFlac->currentFrame.samplesRemaining / channelCount) { - frameCountThisIteration = pFlac->currentFrame.samplesRemaining / channelCount; + if (frameCountThisIteration > pFlac->currentFLACFrame.pcmFramesRemaining) { + frameCountThisIteration = pFlac->currentFLACFrame.pcmFramesRemaining; } if (channelCount == 2) { - const drflac_int32* pDecodedSamples0 = pFlac->currentFrame.subframes[0].pDecodedSamples + iFirstPCMFrame; - const drflac_int32* pDecodedSamples1 = pFlac->currentFrame.subframes[1].pDecodedSamples + iFirstPCMFrame; + const drflac_int32* pDecodedSamples0 = pFlac->currentFLACFrame.subframes[0].pSamplesS32 + iFirstPCMFrame; + const drflac_int32* pDecodedSamples1 = pFlac->currentFLACFrame.subframes[1].pSamplesS32 + iFirstPCMFrame; - switch (pFlac->currentFrame.header.channelAssignment) + switch (pFlac->currentFLACFrame.header.channelAssignment) { case DRFLAC_CHANNEL_ASSIGNMENT_LEFT_SIDE: { @@ -7784,7 +11323,7 @@ drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRe { drflac_read_pcm_frames_f32__decode_right_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); } break; - + case DRFLAC_CHANNEL_ASSIGNMENT_MID_SIDE: { drflac_read_pcm_frames_f32__decode_mid_side(pFlac, frameCountThisIteration, unusedBitsPerSample, pDecodedSamples0, pDecodedSamples1, pBufferOut); @@ -7802,136 +11341,72 @@ drflac_uint64 drflac_read_pcm_frames_f32(drflac* pFlac, drflac_uint64 framesToRe for (i = 0; i < frameCountThisIteration; ++i) { unsigned int j; for (j = 0; j < channelCount; ++j) { - pBufferOut[(i*channelCount)+j] = (float)(((pFlac->currentFrame.subframes[j].pDecodedSamples[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFrame.subframes[j].wastedBitsPerSample)) / 2147483648.0); + drflac_int32 sampleS32 = (drflac_int32)((drflac_uint32)(pFlac->currentFLACFrame.subframes[j].pSamplesS32[iFirstPCMFrame + i]) << (unusedBitsPerSample + pFlac->currentFLACFrame.subframes[j].wastedBitsPerSample)); + pBufferOut[(i*channelCount)+j] = (float)(sampleS32 / 2147483648.0); } } } - samplesReadThisIteration = frameCountThisIteration * channelCount; framesRead += frameCountThisIteration; - framesReadFromPacketSoFar += frameCountThisIteration; - pBufferOut += samplesReadThisIteration; + pBufferOut += frameCountThisIteration * channelCount; framesToRead -= frameCountThisIteration; - pFlac->currentSample += samplesReadThisIteration; - pFlac->currentFrame.samplesRemaining -= (unsigned int)samplesReadThisIteration; + pFlac->currentPCMFrame += frameCountThisIteration; + pFlac->currentFLACFrame.pcmFramesRemaining -= (unsigned int)frameCountThisIteration; } } return framesRead; } -drflac_bool32 drflac_seek_to_sample(drflac* pFlac, drflac_uint64 sampleIndex) + +DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) { if (pFlac == NULL) { return DRFLAC_FALSE; } + /* Don't do anything if we're already on the seek point. */ + if (pFlac->currentPCMFrame == pcmFrameIndex) { + return DRFLAC_TRUE; + } + /* If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present when the decoder was opened. */ - if (pFlac->firstFramePos == 0) { - return DRFLAC_FALSE; - } - - if (sampleIndex == 0) { - pFlac->currentSample = 0; - return drflac__seek_to_first_frame(pFlac); - } else { - drflac_bool32 wasSuccessful = DRFLAC_FALSE; - - /* Clamp the sample to the end. */ - if (sampleIndex >= pFlac->totalSampleCount) { - sampleIndex = pFlac->totalSampleCount - 1; - } - - /* If the target sample and the current sample are in the same frame we just move the position forward. */ - if (sampleIndex > pFlac->currentSample) { - /* Forward. */ - drflac_uint32 offset = (drflac_uint32)(sampleIndex - pFlac->currentSample); - if (pFlac->currentFrame.samplesRemaining > offset) { - pFlac->currentFrame.samplesRemaining -= offset; - pFlac->currentSample = sampleIndex; - return DRFLAC_TRUE; - } - } else { - /* Backward. */ - drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentSample - sampleIndex); - drflac_uint32 currentFrameSampleCount = pFlac->currentFrame.header.blockSize * drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - drflac_uint32 currentFrameSamplesConsumed = (drflac_uint32)(currentFrameSampleCount - pFlac->currentFrame.samplesRemaining); - if (currentFrameSamplesConsumed > offsetAbs) { - pFlac->currentFrame.samplesRemaining += offsetAbs; - pFlac->currentSample = sampleIndex; - return DRFLAC_TRUE; - } - } - - /* - Different techniques depending on encapsulation. Using the native FLAC seektable with Ogg encapsulation is a bit awkward so - we'll instead use Ogg's natural seeking facility. - */ -#ifndef DR_FLAC_NO_OGG - if (pFlac->container == drflac_container_ogg) - { - wasSuccessful = drflac_ogg__seek_to_sample(pFlac, sampleIndex); - } - else -#endif - { - /* First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. */ - wasSuccessful = drflac__seek_to_sample__seek_table(pFlac, sampleIndex); - if (!wasSuccessful) { - wasSuccessful = drflac__seek_to_sample__brute_force(pFlac, sampleIndex); - } - } - - pFlac->currentSample = sampleIndex; - return wasSuccessful; - } -} - -drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameIndex) -{ - if (pFlac == NULL) { - return DRFLAC_FALSE; - } - - /* - If we don't know where the first frame begins then we can't seek. This will happen when the STREAMINFO block was not present - when the decoder was opened. - */ - if (pFlac->firstFramePos == 0) { + if (pFlac->firstFLACFramePosInBytes == 0) { return DRFLAC_FALSE; } if (pcmFrameIndex == 0) { - pFlac->currentSample = 0; + pFlac->currentPCMFrame = 0; return drflac__seek_to_first_frame(pFlac); } else { drflac_bool32 wasSuccessful = DRFLAC_FALSE; + drflac_uint64 originalPCMFrame = pFlac->currentPCMFrame; /* Clamp the sample to the end. */ - if (pcmFrameIndex >= pFlac->totalPCMFrameCount) { - pcmFrameIndex = pFlac->totalPCMFrameCount - 1; + if (pcmFrameIndex > pFlac->totalPCMFrameCount) { + pcmFrameIndex = pFlac->totalPCMFrameCount; } /* If the target sample and the current sample are in the same frame we just move the position forward. */ - if (pcmFrameIndex*pFlac->channels > pFlac->currentSample) { + if (pcmFrameIndex > pFlac->currentPCMFrame) { /* Forward. */ - drflac_uint32 offset = (drflac_uint32)(pcmFrameIndex*pFlac->channels - pFlac->currentSample); - if (pFlac->currentFrame.samplesRemaining > offset) { - pFlac->currentFrame.samplesRemaining -= offset; - pFlac->currentSample = pcmFrameIndex*pFlac->channels; + drflac_uint32 offset = (drflac_uint32)(pcmFrameIndex - pFlac->currentPCMFrame); + if (pFlac->currentFLACFrame.pcmFramesRemaining > offset) { + pFlac->currentFLACFrame.pcmFramesRemaining -= offset; + pFlac->currentPCMFrame = pcmFrameIndex; return DRFLAC_TRUE; } } else { /* Backward. */ - drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentSample - pcmFrameIndex*pFlac->channels); - drflac_uint32 currentFrameSampleCount = pFlac->currentFrame.header.blockSize * drflac__get_channel_count_from_channel_assignment(pFlac->currentFrame.header.channelAssignment); - drflac_uint32 currentFrameSamplesConsumed = (drflac_uint32)(currentFrameSampleCount - pFlac->currentFrame.samplesRemaining); - if (currentFrameSamplesConsumed > offsetAbs) { - pFlac->currentFrame.samplesRemaining += offsetAbs; - pFlac->currentSample = pcmFrameIndex*pFlac->channels; + drflac_uint32 offsetAbs = (drflac_uint32)(pFlac->currentPCMFrame - pcmFrameIndex); + drflac_uint32 currentFLACFramePCMFrameCount = pFlac->currentFLACFrame.header.blockSizeInPCMFrames; + drflac_uint32 currentFLACFramePCMFramesConsumed = currentFLACFramePCMFrameCount - pFlac->currentFLACFrame.pcmFramesRemaining; + if (currentFLACFramePCMFramesConsumed > offsetAbs) { + pFlac->currentFLACFrame.pcmFramesRemaining += offsetAbs; + pFlac->currentPCMFrame = pcmFrameIndex; return DRFLAC_TRUE; } } @@ -7943,19 +11418,39 @@ drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 pcmFrameInde #ifndef DR_FLAC_NO_OGG if (pFlac->container == drflac_container_ogg) { - wasSuccessful = drflac_ogg__seek_to_sample(pFlac, pcmFrameIndex*pFlac->channels); + wasSuccessful = drflac_ogg__seek_to_pcm_frame(pFlac, pcmFrameIndex); } else #endif { /* First try seeking via the seek table. If this fails, fall back to a brute force seek which is much slower. */ - wasSuccessful = drflac__seek_to_sample__seek_table(pFlac, pcmFrameIndex*pFlac->channels); - if (!wasSuccessful) { - wasSuccessful = drflac__seek_to_sample__brute_force(pFlac, pcmFrameIndex*pFlac->channels); + if (/*!wasSuccessful && */!pFlac->_noSeekTableSeek) { + wasSuccessful = drflac__seek_to_pcm_frame__seek_table(pFlac, pcmFrameIndex); + } + +#if !defined(DR_FLAC_NO_CRC) + /* Fall back to binary search if seek table seeking fails. This requires the length of the stream to be known. */ + if (!wasSuccessful && !pFlac->_noBinarySearchSeek && pFlac->totalPCMFrameCount > 0) { + wasSuccessful = drflac__seek_to_pcm_frame__binary_search(pFlac, pcmFrameIndex); + } +#endif + + /* Fall back to brute force if all else fails. */ + if (!wasSuccessful && !pFlac->_noBruteForceSeek) { + wasSuccessful = drflac__seek_to_pcm_frame__brute_force(pFlac, pcmFrameIndex); + } + } + + if (wasSuccessful) { + pFlac->currentPCMFrame = pcmFrameIndex; + } else { + /* Seek failed. Try putting the decoder back to it's original state. */ + if (drflac_seek_to_pcm_frame(pFlac, originalPCMFrame) == DRFLAC_FALSE) { + /* Failed to seek back to the original PCM frame. Fall back to 0. */ + drflac_seek_to_pcm_frame(pFlac, 0); } } - pFlac->currentSample = pcmFrameIndex*pFlac->channels; return wasSuccessful; } } @@ -7982,7 +11477,7 @@ static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned type* pSampleData = NULL; \ drflac_uint64 totalPCMFrameCount; \ \ - drflac_assert(pFlac != NULL); \ + DRFLAC_ASSERT(pFlac != NULL); \ \ totalPCMFrameCount = pFlac->totalPCMFrameCount; \ \ @@ -7991,7 +11486,7 @@ static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned drflac_uint64 pcmFramesRead; \ size_t sampleDataBufferSize = sizeof(buffer); \ \ - pSampleData = (type*)DRFLAC_MALLOC(sampleDataBufferSize); \ + pSampleData = (type*)drflac__malloc_from_callbacks(sampleDataBufferSize, &pFlac->allocationCallbacks); \ if (pSampleData == NULL) { \ goto on_error; \ } \ @@ -7999,31 +11494,33 @@ static type* drflac__full_read_and_close_ ## extension (drflac* pFlac, unsigned while ((pcmFramesRead = (drflac_uint64)drflac_read_pcm_frames_##extension(pFlac, sizeof(buffer)/sizeof(buffer[0])/pFlac->channels, buffer)) > 0) { \ if (((totalPCMFrameCount + pcmFramesRead) * pFlac->channels * sizeof(type)) > sampleDataBufferSize) { \ type* pNewSampleData; \ + size_t newSampleDataBufferSize; \ \ - sampleDataBufferSize *= 2; \ - pNewSampleData = (type*)DRFLAC_REALLOC(pSampleData, sampleDataBufferSize); \ + newSampleDataBufferSize = sampleDataBufferSize * 2; \ + pNewSampleData = (type*)drflac__realloc_from_callbacks(pSampleData, newSampleDataBufferSize, sampleDataBufferSize, &pFlac->allocationCallbacks); \ if (pNewSampleData == NULL) { \ - DRFLAC_FREE(pSampleData); \ + drflac__free_from_callbacks(pSampleData, &pFlac->allocationCallbacks); \ goto on_error; \ } \ \ + sampleDataBufferSize = newSampleDataBufferSize; \ pSampleData = pNewSampleData; \ } \ \ - drflac_copy_memory(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ + DRFLAC_COPY_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), buffer, (size_t)(pcmFramesRead*pFlac->channels*sizeof(type))); \ totalPCMFrameCount += pcmFramesRead; \ } \ \ /* At this point everything should be decoded, but we just want to fill the unused part buffer with silence - need to \ protect those ears from random noise! */ \ - drflac_zero_memory(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ + DRFLAC_ZERO_MEMORY(pSampleData + (totalPCMFrameCount*pFlac->channels), (size_t)(sampleDataBufferSize - totalPCMFrameCount*pFlac->channels*sizeof(type))); \ } else { \ drflac_uint64 dataSize = totalPCMFrameCount*pFlac->channels*sizeof(type); \ if (dataSize > DRFLAC_SIZE_MAX) { \ goto on_error; /* The decoded data is too big. */ \ } \ \ - pSampleData = (type*)DRFLAC_MALLOC((size_t)dataSize); /* <-- Safe cast as per the check above. */ \ + pSampleData = (type*)drflac__malloc_from_callbacks((size_t)dataSize, &pFlac->allocationCallbacks); /* <-- Safe cast as per the check above. */ \ if (pSampleData == NULL) { \ goto on_error; \ } \ @@ -8047,7 +11544,7 @@ DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s32, drflac_int32) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(s16, drflac_int16) DRFLAC_DEFINE_FULL_READ_AND_CLOSE(f32, float) -drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut) +DRFLAC_API drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8061,7 +11558,7 @@ drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drfla *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData); + pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8069,44 +11566,7 @@ drflac_int32* drflac_open_and_read_pcm_frames_s32(drflac_read_proc onRead, drfla return drflac__full_read_and_close_s32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -drflac_int32* drflac_open_and_decode_s32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - drflac_int32* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_and_read_pcm_frames_s32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - - -drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut) +DRFLAC_API drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8120,7 +11580,7 @@ drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drfla *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData); + pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8128,43 +11588,7 @@ drflac_int16* drflac_open_and_read_pcm_frames_s16(drflac_read_proc onRead, drfla return drflac__full_read_and_close_s16(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -drflac_int16* drflac_open_and_decode_s16(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - drflac_int16* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_and_read_pcm_frames_s16(onRead, onSeek, pUserData, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - -float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut) +DRFLAC_API float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalPCMFrameCountOut, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8178,7 +11602,7 @@ float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_ *totalPCMFrameCountOut = 0; } - pFlac = drflac_open(onRead, onSeek, pUserData); + pFlac = drflac_open(onRead, onSeek, pUserData, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8186,43 +11610,8 @@ float* drflac_open_and_read_pcm_frames_f32(drflac_read_proc onRead, drflac_seek_ return drflac__full_read_and_close_f32(pFlac, channelsOut, sampleRateOut, totalPCMFrameCountOut); } -float* drflac_open_and_decode_f32(drflac_read_proc onRead, drflac_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - float* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_and_read_pcm_frames_f32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - #ifndef DR_FLAC_NO_STDIO -drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +DRFLAC_API drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8236,7 +11625,7 @@ drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, uns *totalPCMFrameCount = 0; } - pFlac = drflac_open_file(filename); + pFlac = drflac_open_file(filename, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8244,43 +11633,7 @@ drflac_int32* drflac_open_file_and_read_pcm_frames_s32(const char* filename, uns return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); } -drflac_int32* drflac_open_and_decode_file_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - drflac_int32* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_file_and_read_pcm_frames_s32(filename, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - -drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +DRFLAC_API drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8294,7 +11647,7 @@ drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, uns *totalPCMFrameCount = 0; } - pFlac = drflac_open_file(filename); + pFlac = drflac_open_file(filename, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8302,43 +11655,7 @@ drflac_int16* drflac_open_file_and_read_pcm_frames_s16(const char* filename, uns return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); } -drflac_int16* drflac_open_and_decode_file_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - drflac_int16* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_file_and_read_pcm_frames_s16(filename, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - -float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +DRFLAC_API float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8352,51 +11669,16 @@ float* drflac_open_file_and_read_pcm_frames_f32(const char* filename, unsigned i *totalPCMFrameCount = 0; } - pFlac = drflac_open_file(filename); + pFlac = drflac_open_file(filename, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); } - -float* drflac_open_and_decode_file_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - float* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_file_and_read_pcm_frames_f32(filename, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} #endif -drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +DRFLAC_API drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8410,7 +11692,7 @@ drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_ *totalPCMFrameCount = 0; } - pFlac = drflac_open_memory(data, dataSize); + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8418,43 +11700,7 @@ drflac_int32* drflac_open_memory_and_read_pcm_frames_s32(const void* data, size_ return drflac__full_read_and_close_s32(pFlac, channels, sampleRate, totalPCMFrameCount); } -drflac_int32* drflac_open_and_decode_memory_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - drflac_int32* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_memory_and_read_pcm_frames_s32(data, dataSize, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - -drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +DRFLAC_API drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8468,7 +11714,7 @@ drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_ *totalPCMFrameCount = 0; } - pFlac = drflac_open_memory(data, dataSize); + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8476,43 +11722,7 @@ drflac_int16* drflac_open_memory_and_read_pcm_frames_s16(const void* data, size_ return drflac__full_read_and_close_s16(pFlac, channels, sampleRate, totalPCMFrameCount); } -drflac_int16* drflac_open_and_decode_memory_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) -{ - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - drflac_int16* pResult; - - if (channelsOut) { - *channelsOut = 0; - } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_memory_and_read_pcm_frames_s16(data, dataSize, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - -float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount) +DRFLAC_API float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drflac_uint64* totalPCMFrameCount, const drflac_allocation_callbacks* pAllocationCallbacks) { drflac* pFlac; @@ -8526,7 +11736,7 @@ float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataS *totalPCMFrameCount = 0; } - pFlac = drflac_open_memory(data, dataSize); + pFlac = drflac_open_memory(data, dataSize, pAllocationCallbacks); if (pFlac == NULL) { return NULL; } @@ -8534,51 +11744,20 @@ float* drflac_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataS return drflac__full_read_and_close_f32(pFlac, channels, sampleRate, totalPCMFrameCount); } -float* drflac_open_and_decode_memory_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drflac_uint64* totalSampleCountOut) + +DRFLAC_API void drflac_free(void* p, const drflac_allocation_callbacks* pAllocationCallbacks) { - unsigned int channels; - unsigned int sampleRate; - drflac_uint64 totalPCMFrameCount; - float* pResult; - - if (channelsOut) { - *channelsOut = 0; + if (pAllocationCallbacks != NULL) { + drflac__free_from_callbacks(p, pAllocationCallbacks); + } else { + drflac__free_default(p, NULL); } - if (sampleRateOut) { - *sampleRateOut = 0; - } - if (totalSampleCountOut) { - *totalSampleCountOut = 0; - } - - pResult = drflac_open_memory_and_read_pcm_frames_f32(data, dataSize, &channels, &sampleRate, &totalPCMFrameCount); - if (pResult == NULL) { - return NULL; - } - - if (channelsOut) { - *channelsOut = channels; - } - if (sampleRateOut) { - *sampleRateOut = sampleRate; - } - if (totalSampleCountOut) { - *totalSampleCountOut = totalPCMFrameCount * channels; - } - - return pResult; -} - - -void drflac_free(void* pSampleDataReturnedByOpenAndDecode) -{ - DRFLAC_FREE(pSampleDataReturnedByOpenAndDecode); } -void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) +DRFLAC_API void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, drflac_uint32 commentCount, const void* pComments) { if (pIter == NULL) { return; @@ -8588,11 +11767,11 @@ void drflac_init_vorbis_comment_iterator(drflac_vorbis_comment_iterator* pIter, pIter->pRunningData = (const char*)pComments; } -const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) +DRFLAC_API const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, drflac_uint32* pCommentLengthOut) { drflac_int32 length; const char* pComment; - + /* Safety. */ if (pCommentLengthOut) { *pCommentLengthOut = 0; @@ -8619,7 +11798,7 @@ const char* drflac_next_vorbis_comment(drflac_vorbis_comment_iterator* pIter, dr -void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) +DRFLAC_API void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, drflac_uint32 trackCount, const void* pTrackData) { if (pIter == NULL) { return; @@ -8629,7 +11808,7 @@ void drflac_init_cuesheet_track_iterator(drflac_cuesheet_track_iterator* pIter, pIter->pRunningData = (const char*)pTrackData; } -drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) +DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, drflac_cuesheet_track* pCuesheetTrack) { drflac_cuesheet_track cuesheetTrack; const char* pRunningData; @@ -8646,7 +11825,7 @@ drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, offsetLo = drflac__be2host_32(*(const drflac_uint32*)pRunningData); pRunningData += 4; cuesheetTrack.offset = offsetLo | (offsetHi << 32); cuesheetTrack.trackNumber = pRunningData[0]; pRunningData += 1; - drflac_copy_memory(cuesheetTrack.ISRC, pRunningData, sizeof(cuesheetTrack.ISRC)); pRunningData += 12; + DRFLAC_COPY_MEMORY(cuesheetTrack.ISRC, pRunningData, sizeof(cuesheetTrack.ISRC)); pRunningData += 12; cuesheetTrack.isAudio = (pRunningData[0] & 0x80) != 0; cuesheetTrack.preEmphasis = (pRunningData[0] & 0x40) != 0; pRunningData += 14; cuesheetTrack.indexCount = pRunningData[0]; pRunningData += 1; @@ -8662,15 +11841,166 @@ drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterator* pIter, return DRFLAC_TRUE; } -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif +#endif /* dr_flac_c */ #endif /* DR_FLAC_IMPLEMENTATION */ /* REVISION HISTORY ================ +v0.12.29 - 2021-04-02 + - Fix a bug where the running PCM frame index is set to an invalid value when over-seeking. + - Fix a decoding error due to an incorrect validation check. + +v0.12.28 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + +v0.12.27 - 2021-01-31 + - Fix a static analysis warning. + +v0.12.26 - 2021-01-17 + - Fix a compilation warning due to _BSD_SOURCE being deprecated. + +v0.12.25 - 2020-12-26 + - Update documentation. + +v0.12.24 - 2020-11-29 + - Fix ARM64/NEON detection when compiling with MSVC. + +v0.12.23 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.22 - 2020-11-01 + - Fix an error with the previous release. + +v0.12.21 - 2020-11-01 + - Fix a possible deadlock when seeking. + - Improve compiler support for older versions of GCC. + +v0.12.20 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.19 - 2020-08-30 + - Fix a bug due to an undefined 32-bit shift. + +v0.12.18 - 2020-08-14 + - Fix a crash when compiling with clang-cl. + +v0.12.17 - 2020-08-02 + - Simplify sized types. + +v0.12.16 - 2020-07-25 + - Fix a compilation warning. + +v0.12.15 - 2020-07-06 + - Check for negative LPC shifts and return an error. + +v0.12.14 - 2020-06-23 + - Add include guard for the implementation section. + +v0.12.13 - 2020-05-16 + - Add compile-time and run-time version querying. + - DRFLAC_VERSION_MINOR + - DRFLAC_VERSION_MAJOR + - DRFLAC_VERSION_REVISION + - DRFLAC_VERSION_STRING + - drflac_version() + - drflac_version_string() + +v0.12.12 - 2020-04-30 + - Fix compilation errors with VC6. + +v0.12.11 - 2020-04-19 + - Fix some pedantic warnings. + - Fix some undefined behaviour warnings. + +v0.12.10 - 2020-04-10 + - Fix some bugs when trying to seek with an invalid seek table. + +v0.12.9 - 2020-04-05 + - Fix warnings. + +v0.12.8 - 2020-04-04 + - Add drflac_open_file_w() and drflac_open_file_with_metadata_w(). + - Fix some static analysis warnings. + - Minor documentation updates. + +v0.12.7 - 2020-03-14 + - Fix compilation errors with VC6. + +v0.12.6 - 2020-03-07 + - Fix compilation error with Visual Studio .NET 2003. + +v0.12.5 - 2020-01-30 + - Silence some static analysis warnings. + +v0.12.4 - 2020-01-29 + - Silence some static analysis warnings. + +v0.12.3 - 2019-12-02 + - Fix some warnings when compiling with GCC and the -Og flag. + - Fix a crash in out-of-memory situations. + - Fix potential integer overflow bug. + - Fix some static analysis warnings. + - Fix a possible crash when using custom memory allocators without a custom realloc() implementation. + - Fix a bug with binary search seeking where the bits per sample is not a multiple of 8. + +v0.12.2 - 2019-10-07 + - Internal code clean up. + +v0.12.1 - 2019-09-29 + - Fix some Clang Static Analyzer warnings. + - Fix an unused variable warning. + +v0.12.0 - 2019-09-23 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drflac_open() + - drflac_open_relaxed() + - drflac_open_with_metadata() + - drflac_open_with_metadata_relaxed() + - drflac_open_file() + - drflac_open_file_with_metadata() + - drflac_open_memory() + - drflac_open_memory_with_metadata() + - drflac_open_and_read_pcm_frames_s32() + - drflac_open_and_read_pcm_frames_s16() + - drflac_open_and_read_pcm_frames_f32() + - drflac_open_file_and_read_pcm_frames_s32() + - drflac_open_file_and_read_pcm_frames_s16() + - drflac_open_file_and_read_pcm_frames_f32() + - drflac_open_memory_and_read_pcm_frames_s32() + - drflac_open_memory_and_read_pcm_frames_s16() + - drflac_open_memory_and_read_pcm_frames_f32() + Set this extra parameter to NULL to use defaults which is the same as the previous behaviour. Setting this NULL will use + DRFLAC_MALLOC, DRFLAC_REALLOC and DRFLAC_FREE. + - Remove deprecated APIs: + - drflac_read_s32() + - drflac_read_s16() + - drflac_read_f32() + - drflac_seek_to_sample() + - drflac_open_and_decode_s32() + - drflac_open_and_decode_s16() + - drflac_open_and_decode_f32() + - drflac_open_and_decode_file_s32() + - drflac_open_and_decode_file_s16() + - drflac_open_and_decode_file_f32() + - drflac_open_and_decode_memory_s32() + - drflac_open_and_decode_memory_s16() + - drflac_open_and_decode_memory_f32() + - Remove drflac.totalSampleCount which is now replaced with drflac.totalPCMFrameCount. You can emulate drflac.totalSampleCount + by doing pFlac->totalPCMFrameCount*pFlac->channels. + - Rename drflac.currentFrame to drflac.currentFLACFrame to remove ambiguity with PCM frames. + - Fix errors when seeking to the end of a stream. + - Optimizations to seeking. + - SSE improvements and optimizations. + - ARM NEON optimizations. + - Optimizations to drflac_read_pcm_frames_s16(). + - Optimizations to drflac_read_pcm_frames_s32(). + v0.11.10 - 2019-06-26 - Fix a compiler error. @@ -8689,7 +12019,7 @@ v0.11.6 - 2019-05-05 - Change license to choice of public domain or MIT-0. v0.11.5 - 2019-04-19 - - Fix a compiler error with GCC. + - Fix a compiler error with GCC. v0.11.4 - 2019-04-17 - Fix some warnings with GCC when compiling with -std=c99. @@ -8704,7 +12034,7 @@ v0.11.1 - 2019-02-17 - Fix a potential bug with seeking. v0.11.0 - 2018-12-16 - - API CHANGE: Deprecated drflac_read_s32(), drflac_read_s16() and drflac_read_f32() and replaced them with + - API CHANGE: Deprecated drflac_read_s32(), drflac_read_s16() and drflac_read_f32() and replaced them with drflac_read_pcm_frames_s32(), drflac_read_pcm_frames_s16() and drflac_read_pcm_frames_f32(). The new APIs take and return PCM frame counts instead of sample counts. To upgrade you will need to change the input count by dividing it by the channel count, and then do the same with the return value. @@ -8914,7 +12244,7 @@ For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== -Copyright 2018 David Reid +Copyright 2020 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/raylib/external/dr_mp3.h b/raylib/external/dr_mp3.h index 0ecd0d3..e28f318 100644 --- a/raylib/external/dr_mp3.h +++ b/raylib/external/dr_mp3.h @@ -1,23 +1,50 @@ /* MP3 audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_mp3 - v0.4.7 - 2019-07-28 +dr_mp3 - v0.6.27 - 2021-02-21 David Reid - mackron@gmail.com -Based off minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for -differences between minimp3 and dr_mp3. +GitHub: https://github.com/mackron/dr_libs + +Based on minimp3 (https://github.com/lieff/minimp3) which is where the real work was done. See the bottom of this file for differences between minimp3 and dr_mp3. */ /* -USAGE -===== -dr_mp3 is a single-file library. To use it, do something like the following in one .c file. +RELEASE NOTES - VERSION 0.6 +=========================== +Version 0.6 includes breaking changes with the configuration of decoders. The ability to customize the number of output channels and the sample rate has been +removed. You must now use the channel count and sample rate reported by the MP3 stream itself, and all channel and sample rate conversion must be done +yourself. + + +Changes to Initialization +------------------------- +Previously, `drmp3_init()`, etc. took a pointer to a `drmp3_config` object that allowed you to customize the output channels and sample rate. This has been +removed. If you need the old behaviour you will need to convert the data yourself or just not upgrade. The following APIs have changed. + + `drmp3_init()` + `drmp3_init_memory()` + `drmp3_init_file()` + + +Miscellaneous Changes +--------------------- +Support for loading a file from a `wchar_t` string has been added via the `drmp3_init_file_w()` API. +*/ + +/* +Introducation +============= +dr_mp3 is a single file library. To use it, do something like the following in one .c file. + + ```c #define DR_MP3_IMPLEMENTATION #include "dr_mp3.h" + ``` -You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, -do something like the following: +You can then #include this file in other parts of the program as you would with any other header file. To decode audio data, do something like the following: + ```c drmp3 mp3; if (!drmp3_init_file(&mp3, "MySong.mp3", NULL)) { // Failed to open file @@ -26,28 +53,27 @@ do something like the following: ... drmp3_uint64 framesRead = drmp3_read_pcm_frames_f32(pMP3, framesToRead, pFrames); + ``` The drmp3 object is transparent so you can get access to the channel count and sample rate like so: + ``` drmp3_uint32 channels = mp3.channels; drmp3_uint32 sampleRate = mp3.sampleRate; + ``` -The third parameter of drmp3_init_file() in the example above allows you to control the output channel count and sample rate. It -is a pointer to a drmp3_config object. Setting any of the variables of this object to 0 will cause dr_mp3 to use defaults. +The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek callbacks with +`drmp3_init_memory()` and `drmp3_init()` respectively. -The example above initializes a decoder from a file, but you can also initialize it from a block of memory and read and seek -callbacks with drmp3_init_memory() and drmp3_init() respectively. +You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request any number of PCM frames in each +call to `drmp3_read_pcm_frames_f32()` and it will return as many PCM frames as it can, up to the requested amount. -You do not need to do any annoying memory management when reading PCM frames - this is all managed internally. You can request -any number of PCM frames in each call to drmp3_read_pcm_frames_f32() and it will return as many PCM frames as it can, up to the -requested amount. - -You can also decode an entire file in one go with drmp3_open_and_read_f32(), drmp3_open_memory_and_read_f32() and -drmp3_open_file_and_read_f32(). +You can also decode an entire file in one go with `drmp3_open_and_read_pcm_frames_f32()`, `drmp3_open_memory_and_read_pcm_frames_f32()` and +`drmp3_open_file_and_read_pcm_frames_f32()`. -OPTIONS -======= +Build Options +============= #define these options before including this file. #define DR_MP3_NO_STDIO @@ -64,46 +90,166 @@ OPTIONS extern "C" { #endif -#include +#define DRMP3_STRINGIFY(x) #x +#define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) -#if defined(_MSC_VER) && _MSC_VER < 1600 -typedef signed char drmp3_int8; -typedef unsigned char drmp3_uint8; -typedef signed short drmp3_int16; -typedef unsigned short drmp3_uint16; -typedef signed int drmp3_int32; -typedef unsigned int drmp3_uint32; -typedef signed __int64 drmp3_int64; -typedef unsigned __int64 drmp3_uint64; +#define DRMP3_VERSION_MAJOR 0 +#define DRMP3_VERSION_MINOR 6 +#define DRMP3_VERSION_REVISION 27 +#define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) + +#include /* For size_t. */ + +/* Sized types. */ +typedef signed char drmp3_int8; +typedef unsigned char drmp3_uint8; +typedef signed short drmp3_int16; +typedef unsigned short drmp3_uint16; +typedef signed int drmp3_int32; +typedef unsigned int drmp3_uint32; +#if defined(_MSC_VER) + typedef signed __int64 drmp3_int64; + typedef unsigned __int64 drmp3_uint64; #else -#include -typedef int8_t drmp3_int8; -typedef uint8_t drmp3_uint8; -typedef int16_t drmp3_int16; -typedef uint16_t drmp3_uint16; -typedef int32_t drmp3_int32; -typedef uint32_t drmp3_uint32; -typedef int64_t drmp3_int64; -typedef uint64_t drmp3_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif + #endif + typedef signed long long drmp3_int64; + typedef unsigned long long drmp3_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) + #pragma GCC diagnostic pop + #endif #endif -typedef drmp3_uint8 drmp3_bool8; -typedef drmp3_uint32 drmp3_bool32; -#define DRMP3_TRUE 1 -#define DRMP3_FALSE 0 +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + typedef drmp3_uint64 drmp3_uintptr; +#else + typedef drmp3_uint32 drmp3_uintptr; +#endif +typedef drmp3_uint8 drmp3_bool8; +typedef drmp3_uint32 drmp3_bool32; +#define DRMP3_TRUE 1 +#define DRMP3_FALSE 0 + +#if !defined(DRMP3_API) + #if defined(DRMP3_DLL) + #if defined(_WIN32) + #define DRMP3_DLL_IMPORT __declspec(dllimport) + #define DRMP3_DLL_EXPORT __declspec(dllexport) + #define DRMP3_DLL_PRIVATE static + #else + #if defined(__GNUC__) && __GNUC__ >= 4 + #define DRMP3_DLL_IMPORT __attribute__((visibility("default"))) + #define DRMP3_DLL_EXPORT __attribute__((visibility("default"))) + #define DRMP3_DLL_PRIVATE __attribute__((visibility("hidden"))) + #else + #define DRMP3_DLL_IMPORT + #define DRMP3_DLL_EXPORT + #define DRMP3_DLL_PRIVATE static + #endif + #endif + + #if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) + #define DRMP3_API DRMP3_DLL_EXPORT + #else + #define DRMP3_API DRMP3_DLL_IMPORT + #endif + #define DRMP3_PRIVATE DRMP3_DLL_PRIVATE + #else + #define DRMP3_API extern + #define DRMP3_PRIVATE static + #endif +#endif + +typedef drmp3_int32 drmp3_result; +#define DRMP3_SUCCESS 0 +#define DRMP3_ERROR -1 /* A generic error. */ +#define DRMP3_INVALID_ARGS -2 +#define DRMP3_INVALID_OPERATION -3 +#define DRMP3_OUT_OF_MEMORY -4 +#define DRMP3_OUT_OF_RANGE -5 +#define DRMP3_ACCESS_DENIED -6 +#define DRMP3_DOES_NOT_EXIST -7 +#define DRMP3_ALREADY_EXISTS -8 +#define DRMP3_TOO_MANY_OPEN_FILES -9 +#define DRMP3_INVALID_FILE -10 +#define DRMP3_TOO_BIG -11 +#define DRMP3_PATH_TOO_LONG -12 +#define DRMP3_NAME_TOO_LONG -13 +#define DRMP3_NOT_DIRECTORY -14 +#define DRMP3_IS_DIRECTORY -15 +#define DRMP3_DIRECTORY_NOT_EMPTY -16 +#define DRMP3_END_OF_FILE -17 +#define DRMP3_NO_SPACE -18 +#define DRMP3_BUSY -19 +#define DRMP3_IO_ERROR -20 +#define DRMP3_INTERRUPT -21 +#define DRMP3_UNAVAILABLE -22 +#define DRMP3_ALREADY_IN_USE -23 +#define DRMP3_BAD_ADDRESS -24 +#define DRMP3_BAD_SEEK -25 +#define DRMP3_BAD_PIPE -26 +#define DRMP3_DEADLOCK -27 +#define DRMP3_TOO_MANY_LINKS -28 +#define DRMP3_NOT_IMPLEMENTED -29 +#define DRMP3_NO_MESSAGE -30 +#define DRMP3_BAD_MESSAGE -31 +#define DRMP3_NO_DATA_AVAILABLE -32 +#define DRMP3_INVALID_DATA -33 +#define DRMP3_TIMEOUT -34 +#define DRMP3_NO_NETWORK -35 +#define DRMP3_NOT_UNIQUE -36 +#define DRMP3_NOT_SOCKET -37 +#define DRMP3_NO_ADDRESS -38 +#define DRMP3_BAD_PROTOCOL -39 +#define DRMP3_PROTOCOL_UNAVAILABLE -40 +#define DRMP3_PROTOCOL_NOT_SUPPORTED -41 +#define DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED -42 +#define DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED -43 +#define DRMP3_SOCKET_NOT_SUPPORTED -44 +#define DRMP3_CONNECTION_RESET -45 +#define DRMP3_ALREADY_CONNECTED -46 +#define DRMP3_NOT_CONNECTED -47 +#define DRMP3_CONNECTION_REFUSED -48 +#define DRMP3_NO_HOST -49 +#define DRMP3_IN_PROGRESS -50 +#define DRMP3_CANCELLED -51 +#define DRMP3_MEMORY_ALREADY_MAPPED -52 +#define DRMP3_AT_END -53 + #define DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME 1152 #define DRMP3_MAX_SAMPLES_PER_FRAME (DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME*2) #ifdef _MSC_VER -#define DRMP3_INLINE __forceinline + #define DRMP3_INLINE __forceinline +#elif defined(__GNUC__) + /* + I've had a bug report where GCC is emitting warnings about functions possibly not being inlineable. This warning happens when + the __attribute__((always_inline)) attribute is defined without an "inline" statement. I think therefore there must be some + case where "__inline__" is not always defined, thus the compiler emitting these warnings. When using -std=c89 or -ansi on the + command line, we cannot use the "inline" keyword and instead need to use "__inline__". In an attempt to work around this issue + I am using "__inline__" only when we're compiling in strict ANSI mode. + */ + #if defined(__STRICT_ANSI__) + #define DRMP3_INLINE __inline__ __attribute__((always_inline)) + #else + #define DRMP3_INLINE inline __attribute__((always_inline)) + #endif +#elif defined(__WATCOMC__) + #define DRMP3_INLINE __inline #else -#ifdef __GNUC__ -#define DRMP3_INLINE __inline__ __attribute__((always_inline)) -#else -#define DRMP3_INLINE -#endif + #define DRMP3_INLINE #endif + +DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision); +DRMP3_API const char* drmp3_version_string(void); + + /* Low Level Push API ================== @@ -117,17 +263,17 @@ typedef struct { float mdct_overlap[2][9*32], qmf_state[15*2*32]; int reserv, free_format_bytes; - unsigned char header[4], reserv_buf[511]; + drmp3_uint8 header[4], reserv_buf[511]; } drmp3dec; /* Initializes a low level decoder. */ -void drmp3dec_init(drmp3dec *dec); +DRMP3_API void drmp3dec_init(drmp3dec *dec); /* Reads a frame from a low level decoder. */ -int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); /* Helper for converting between f32 and s16. */ -void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples); @@ -135,58 +281,6 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples); Main API (Pull API) =================== */ -#ifndef DR_MP3_DEFAULT_CHANNELS -#define DR_MP3_DEFAULT_CHANNELS 2 -#endif -#ifndef DR_MP3_DEFAULT_SAMPLE_RATE -#define DR_MP3_DEFAULT_SAMPLE_RATE 44100 -#endif - -typedef struct drmp3_src drmp3_src; -typedef drmp3_uint64 (* drmp3_src_read_proc)(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData); /* Returns the number of frames that were read. */ - -typedef enum -{ - drmp3_src_algorithm_none, - drmp3_src_algorithm_linear -} drmp3_src_algorithm; - -#define DRMP3_SRC_CACHE_SIZE_IN_FRAMES 512 -typedef struct -{ - drmp3_src* pSRC; - float pCachedFrames[2 * DRMP3_SRC_CACHE_SIZE_IN_FRAMES]; - drmp3_uint32 cachedFrameCount; - drmp3_uint32 iNextFrame; -} drmp3_src_cache; - -typedef struct -{ - drmp3_uint32 sampleRateIn; - drmp3_uint32 sampleRateOut; - drmp3_uint32 channels; - drmp3_src_algorithm algorithm; - drmp3_uint32 cacheSizeInFrames; /* The number of frames to read from the client at a time. */ -} drmp3_src_config; - -struct drmp3_src -{ - drmp3_src_config config; - drmp3_src_read_proc onRead; - void* pUserData; - float bin[256]; - drmp3_src_cache cache; /* <-- For simplifying and optimizing client -> memory reading. */ - union - { - struct - { - double alpha; - drmp3_bool32 isPrevFramesLoaded : 1; - drmp3_bool32 isNextFramesLoaded : 1; - } linear; - } algo; -}; - typedef enum { drmp3_seek_origin_start, @@ -231,8 +325,16 @@ typedef drmp3_bool32 (* drmp3_seek_proc)(void* pUserData, int offset, drmp3_seek typedef struct { - drmp3_uint32 outputChannels; - drmp3_uint32 outputSampleRate; + void* pUserData; + void* (* onMalloc)(size_t sz, void* pUserData); + void* (* onRealloc)(void* p, size_t sz, void* pUserData); + void (* onFree)(void* p, void* pUserData); +} drmp3_allocation_callbacks; + +typedef struct +{ + drmp3_uint32 channels; + drmp3_uint32 sampleRate; } drmp3_config; typedef struct @@ -244,6 +346,7 @@ typedef struct drmp3_read_proc onRead; drmp3_seek_proc onSeek; void* pUserData; + drmp3_allocation_callbacks allocationCallbacks; drmp3_uint32 mp3FrameChannels; /* The number of channels in the currently loaded MP3 frame. Internal use only. */ drmp3_uint32 mp3FrameSampleRate; /* The sample rate of the currently loaded MP3 frame. Internal use only. */ drmp3_uint32 pcmFramesConsumedInMP3Frame; @@ -251,11 +354,11 @@ typedef struct drmp3_uint8 pcmFrames[sizeof(float)*DRMP3_MAX_SAMPLES_PER_FRAME]; /* <-- Multipled by sizeof(float) to ensure there's enough room for DR_MP3_FLOAT_OUTPUT. */ drmp3_uint64 currentPCMFrame; /* The current PCM frame, globally, based on the output sample rate. Mainly used for seeking. */ drmp3_uint64 streamCursor; /* The current byte the decoder is sitting on in the raw stream. */ - drmp3_src src; drmp3_seek_point* pSeekPoints; /* NULL by default. Set with drmp3_bind_seek_table(). Memory is owned by the client. dr_mp3 will never attempt to free this pointer. */ drmp3_uint32 seekPointCount; /* The number of items in pSeekPoints. When set to 0 assumes to no seek table. Defaults to zero. */ size_t dataSize; size_t dataCapacity; + size_t dataConsumed; drmp3_uint8* pData; drmp3_bool32 atEnd : 1; struct @@ -279,7 +382,7 @@ Close the loader with drmp3_uninit(). See also: drmp3_init_file(), drmp3_init_memory(), drmp3_uninit() */ -drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig); +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks); /* Initializes an MP3 decoder from a block of memory. @@ -289,7 +392,7 @@ the lifetime of the drmp3 object. The buffer should contain the contents of the entire MP3 file. */ -drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig); +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO /* @@ -299,46 +402,47 @@ This holds the internal FILE object until drmp3_uninit() is called. Keep this in objects because the operating system may restrict the number of file handles an application can have open at any given time. */ -drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig); +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif /* Uninitializes an MP3 decoder. */ -void drmp3_uninit(drmp3* pMP3); +DRMP3_API void drmp3_uninit(drmp3* pMP3); /* Reads PCM frames as interleaved 32-bit IEEE floating point PCM. Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. */ -drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut); /* Reads PCM frames as interleaved signed 16-bit integer PCM. Note that framesToRead specifies the number of PCM frames to read, _not_ the number of MP3 frames. */ -drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut); +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut); /* Seeks to a specific frame. Note that this is _not_ an MP3 frame, but rather a PCM frame. */ -drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); +DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex); /* Calculates the total number of PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. */ -drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); +DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3); /* Calculates the total number of MP3 frames in the MP3 stream. Cannot be used for infinite streams such as internet radio. Runs in linear time. Returns 0 on error. */ -drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); +DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3); /* Calculates the total number of MP3 and PCM frames in the MP3 stream. Cannot be used for infinite streams such as internet @@ -346,7 +450,7 @@ radio. Runs in linear time. Returns 0 on error. This is equivalent to calling drmp3_get_mp3_frame_count() and drmp3_get_pcm_frame_count() except that it's more efficient. */ -drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount); +DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount); /* Calculates the seekpoints based on PCM frames. This is slow. @@ -357,7 +461,7 @@ seekpoints, in which case dr_mp3 will return a corrected count. Note that seektable seeking is not quite sample exact when the MP3 stream contains inconsistent sample rates. */ -drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); +DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints); /* Binds a seek table to the decoder. @@ -367,31 +471,36 @@ remains valid while it is bound to the decoder. Use drmp3_calculate_seek_points() to calculate the seek points. */ -drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); +DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints); /* Opens an decodes an entire MP3 stream as a single operation. -pConfig is both an input and output. On input it contains what you want. On output it contains what you got. +On output pConfig will receive the channel count and sample rate of the stream. Free the returned pointer with drmp3_free(). */ -float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); -drmp3_int16* drmp3_open_and_read_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); -float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); -drmp3_int16* drmp3_open_memory_and_read_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifndef DR_MP3_NO_STDIO -float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); -drmp3_int16* drmp3_open_file_and_read_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount); +DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); +DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks); #endif +/* +Allocates a block of memory on the heap. +*/ +DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks); + /* Frees any memory that was allocated by a public drmp3 API. */ -void drmp3_free(void* p); +DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks); #ifdef __cplusplus } @@ -406,11 +515,34 @@ void drmp3_free(void* p); ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ -#ifdef DR_MP3_IMPLEMENTATION +#if defined(DR_MP3_IMPLEMENTATION) || defined(DRMP3_IMPLEMENTATION) +#ifndef dr_mp3_c +#define dr_mp3_c + #include #include #include /* For INT_MAX */ +DRMP3_API void drmp3_version(drmp3_uint32* pMajor, drmp3_uint32* pMinor, drmp3_uint32* pRevision) +{ + if (pMajor) { + *pMajor = DRMP3_VERSION_MAJOR; + } + + if (pMinor) { + *pMinor = DRMP3_VERSION_MINOR; + } + + if (pRevision) { + *pRevision = DRMP3_VERSION_REVISION; + } +} + +DRMP3_API const char* drmp3_version_string(void) +{ + return DRMP3_VERSION_STRING; +} + /* Disable SIMD when compiling with TCC for now. */ #if defined(__TINYC__) #define DR_MP3_NO_SIMD @@ -458,7 +590,7 @@ void drmp3_free(void* p); #if !defined(DR_MP3_NO_SIMD) -#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__)) +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) /* x64 always have SSE2, arm64 always have neon, no need for generic code */ #define DR_MP3_ONLY_SIMD #endif @@ -508,7 +640,7 @@ static __inline__ __attribute__((always_inline)) void drmp3_cpuid(int CPUInfo[], #endif } #endif -static int drmp3_have_simd() +static int drmp3_have_simd(void) { #ifdef DR_MP3_ONLY_SIMD return 1; @@ -534,8 +666,9 @@ end: return g_have_simd - 1; #endif } -#elif defined(__ARM_NEON) || defined(__aarch64__) +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) #include +#define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 1 #define DRMP3_VSTORE vst1q_f32 #define DRMP3_VLD vld1q_f32 @@ -548,11 +681,12 @@ end: #define DRMP3_VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s)) #define DRMP3_VREV(x) vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x))) typedef float32x4_t drmp3_f4; -static int drmp3_have_simd() +static int drmp3_have_simd(void) { /* TODO: detect neon for !DR_MP3_ONLY_SIMD */ return 1; } #else +#define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 0 #ifdef DR_MP3_ONLY_SIMD #error DR_MP3_ONLY_SIMD used, but SSE/NEON not enabled @@ -565,6 +699,19 @@ static int drmp3_have_simd() #endif +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) +#define DRMP3_HAVE_ARMV6 1 +static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(int32_t a) +{ + drmp3_int32 x = 0; + __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); + return x; +} +#else +#define DRMP3_HAVE_ARMV6 0 +#endif + + typedef struct { const drmp3_uint8 *buf; @@ -738,7 +885,7 @@ static void drmp3_L12_read_scalefactors(drmp3_bs *bs, drmp3_uint8 *pba, drmp3_ui if (mask & m) { int b = drmp3_bs_get_bits(bs, 6); - s = g_deq_L12[ba*3 - 6 + b % 3]*(1 << 21 >> b/3); + s = g_deq_L12[ba*3 - 6 + b % 3]*(int)(1 << 21 >> b/3); } *scf++ = s; } @@ -1054,16 +1201,16 @@ static void drmp3_L3_decode_scalefactors(const drmp3_uint8 *hdr, drmp3_uint8 *is int sh = 3 - scf_shift; for (i = 0; i < gr->n_short_sfb; i += 3) { - iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh; - iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh; - iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh; + iscf[gr->n_long_sfb + i + 0] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 0] + (gr->subblock_gain[0] << sh)); + iscf[gr->n_long_sfb + i + 1] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 1] + (gr->subblock_gain[1] << sh)); + iscf[gr->n_long_sfb + i + 2] = (drmp3_uint8)(iscf[gr->n_long_sfb + i + 2] + (gr->subblock_gain[2] << sh)); } } else if (gr->preflag) { static const drmp3_uint8 g_preamp[10] = { 1,1,1,1,2,2,3,3,3,2 }; for (i = 0; i < 10; i++) { - iscf[11 + i] += g_preamp[i]; + iscf[11 + i] = (drmp3_uint8)(iscf[11 + i] + g_preamp[i]); } } @@ -1715,7 +1862,7 @@ static void drmp3d_DCT_II(float *grbuf, int n) } else #endif #ifdef DR_MP3_ONLY_SIMD - {} + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ #else for (; k < n; k++) { @@ -1784,11 +1931,17 @@ typedef drmp3_int16 drmp3d_sample_t; static drmp3_int16 drmp3d_scale_pcm(float sample) { drmp3_int16 s; +#if DRMP3_HAVE_ARMV6 + drmp3_int32 s32 = (drmp3_int32)(sample + .5f); + s32 -= (s32 < 0); + s = (drmp3_int16)drmp3_clip_int16_arm(s32); +#else if (sample >= 32766.5) return (drmp3_int16) 32767; if (sample <= -32767.5) return (drmp3_int16)-32768; s = (drmp3_int16)(sample + .5f); s -= (s < 0); /* away from zero, to be compliant */ - return (drmp3_int16)s; +#endif + return s; } #else typedef float drmp3d_sample_t; @@ -1942,7 +2095,7 @@ static void drmp3d_synth(float *xl, drmp3d_sample_t *dstl, int nch, float *lins) } else #endif #ifdef DR_MP3_ONLY_SIMD - {} + {} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic "else" branch */ #else for (i = 14; i >= 0; i--) { @@ -2052,15 +2205,15 @@ static int drmp3d_find_frame(const drmp3_uint8 *mp3, int mp3_bytes, int *free_fo } } *ptr_frame_bytes = 0; - return i; + return mp3_bytes; } -void drmp3dec_init(drmp3dec *dec) +DRMP3_API void drmp3dec_init(drmp3dec *dec) { dec->header[0] = 0; } -int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) +DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info) { int i = 0, igr, frame_size = 0, success = 1; const drmp3_uint8 *hdr; @@ -2155,61 +2308,58 @@ int drmp3dec_decode_frame(drmp3dec *dec, const unsigned char *mp3, int mp3_bytes return success*drmp3_hdr_frame_samples(dec->header); } -void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) +DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples) { - if(num_samples > 0) - { - int i = 0; + size_t i = 0; #if DRMP3_HAVE_SIMD - int aligned_count = num_samples & ~7; - for(; i < aligned_count; i+=8) - { - drmp3_f4 scale = DRMP3_VSET(32768.0f); - drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), scale); - drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), scale); + size_t aligned_count = num_samples & ~7; + for(; i < aligned_count; i+=8) + { + drmp3_f4 scale = DRMP3_VSET(32768.0f); + drmp3_f4 a = DRMP3_VMUL(DRMP3_VLD(&in[i ]), scale); + drmp3_f4 b = DRMP3_VMUL(DRMP3_VLD(&in[i+4]), scale); #if DRMP3_HAVE_SSE - drmp3_f4 s16max = DRMP3_VSET( 32767.0f); - drmp3_f4 s16min = DRMP3_VSET(-32768.0f); - __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, s16max), s16min)), - _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, s16max), s16min))); - out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); - out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); - out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); - out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); - out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); - out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); - out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); - out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); + drmp3_f4 s16max = DRMP3_VSET( 32767.0f); + drmp3_f4 s16min = DRMP3_VSET(-32768.0f); + __m128i pcm8 = _mm_packs_epi32(_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, s16max), s16min)), + _mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, s16max), s16min))); + out[i ] = (drmp3_int16)_mm_extract_epi16(pcm8, 0); + out[i+1] = (drmp3_int16)_mm_extract_epi16(pcm8, 1); + out[i+2] = (drmp3_int16)_mm_extract_epi16(pcm8, 2); + out[i+3] = (drmp3_int16)_mm_extract_epi16(pcm8, 3); + out[i+4] = (drmp3_int16)_mm_extract_epi16(pcm8, 4); + out[i+5] = (drmp3_int16)_mm_extract_epi16(pcm8, 5); + out[i+6] = (drmp3_int16)_mm_extract_epi16(pcm8, 6); + out[i+7] = (drmp3_int16)_mm_extract_epi16(pcm8, 7); #else - int16x4_t pcma, pcmb; - a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); - b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); - pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); - pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); - vst1_lane_s16(out+i , pcma, 0); - vst1_lane_s16(out+i+1, pcma, 1); - vst1_lane_s16(out+i+2, pcma, 2); - vst1_lane_s16(out+i+3, pcma, 3); - vst1_lane_s16(out+i+4, pcmb, 0); - vst1_lane_s16(out+i+5, pcmb, 1); - vst1_lane_s16(out+i+6, pcmb, 2); - vst1_lane_s16(out+i+7, pcmb, 3); + int16x4_t pcma, pcmb; + a = DRMP3_VADD(a, DRMP3_VSET(0.5f)); + b = DRMP3_VADD(b, DRMP3_VSET(0.5f)); + pcma = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, DRMP3_VSET(0))))); + pcmb = vqmovn_s32(vqaddq_s32(vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, DRMP3_VSET(0))))); + vst1_lane_s16(out+i , pcma, 0); + vst1_lane_s16(out+i+1, pcma, 1); + vst1_lane_s16(out+i+2, pcma, 2); + vst1_lane_s16(out+i+3, pcma, 3); + vst1_lane_s16(out+i+4, pcmb, 0); + vst1_lane_s16(out+i+5, pcmb, 1); + vst1_lane_s16(out+i+6, pcmb, 2); + vst1_lane_s16(out+i+7, pcmb, 3); #endif - } + } #endif - for(; i < num_samples; i++) + for(; i < num_samples; i++) + { + float sample = in[i] * 32768.0f; + if (sample >= 32766.5) + out[i] = (drmp3_int16) 32767; + else if (sample <= -32767.5) + out[i] = (drmp3_int16)-32768; + else { - float sample = in[i] * 32768.0f; - if (sample >= 32766.5) - out[i] = (drmp3_int16) 32767; - else if (sample <= -32767.5) - out[i] = (drmp3_int16)-32768; - else - { - short s = (drmp3_int16)(sample + .5f); - s -= (s < 0); /* away from zero, to be compliant */ - out[i] = s; - } + short s = (drmp3_int16)(sample + .5f); + s -= (s < 0); /* away from zero, to be compliant */ + out[i] = s; } } } @@ -2221,6 +2371,7 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) Main Public API ************************************************************************************************************************************************************/ +#include /* For sin() and exp(). */ #if defined(SIZE_MAX) #define DRMP3_SIZE_MAX SIZE_MAX @@ -2237,11 +2388,18 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) #define DRMP3_SEEK_LEADING_MP3_FRAMES 2 #endif +#define DRMP3_MIN_DATA_CHUNK_SIZE 16384 + +/* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends at least 16K, but in an attempt to reduce data movement I'm making this slightly larger. */ +#ifndef DRMP3_DATA_CHUNK_SIZE +#define DRMP3_DATA_CHUNK_SIZE DRMP3_MIN_DATA_CHUNK_SIZE*4 +#endif + /* Standard library stuff. */ #ifndef DRMP3_ASSERT #include -#define DRMP3_ASSERT(expression) assert(expression) +#define DRMP3_ASSERT(expression) assert(expression) #endif #ifndef DRMP3_COPY_MEMORY #define DRMP3_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) @@ -2260,285 +2418,161 @@ void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, int num_samples) #define DRMP3_FREE(p) free((p)) #endif -#define drmp3_assert DRMP3_ASSERT -#define drmp3_copy_memory DRMP3_COPY_MEMORY -#define drmp3_zero_memory DRMP3_ZERO_MEMORY -#define drmp3_zero_object DRMP3_ZERO_OBJECT -#define drmp3_malloc DRMP3_MALLOC -#define drmp3_realloc DRMP3_REALLOC +#define DRMP3_COUNTOF(x) (sizeof(x) / sizeof(x[0])) +#define DRMP3_CLAMP(x, lo, hi) (DRMP3_MAX(lo, DRMP3_MIN(x, hi))) -#define drmp3_countof(x) (sizeof(x) / sizeof(x[0])) -#define drmp3_max(x, y) (((x) > (y)) ? (x) : (y)) -#define drmp3_min(x, y) (((x) < (y)) ? (x) : (y)) +#ifndef DRMP3_PI_D +#define DRMP3_PI_D 3.14159265358979323846264 +#endif -#define DRMP3_DATA_CHUNK_SIZE 16384 /* The size in bytes of each chunk of data to read from the MP3 stream. minimp3 recommends 16K. */ +#define DRMP3_DEFAULT_RESAMPLER_LPF_ORDER 2 static DRMP3_INLINE float drmp3_mix_f32(float x, float y, float a) { return x*(1-a) + y*a; } - -static void drmp3_blend_f32(float* pOut, float* pInA, float* pInB, float factor, drmp3_uint32 channels) +static DRMP3_INLINE float drmp3_mix_f32_fast(float x, float y, float a) { - drmp3_uint32 i; - for (i = 0; i < channels; ++i) { - pOut[i] = drmp3_mix_f32(pInA[i], pInB[i], factor); - } + float r0 = (y - x); + float r1 = r0*a; + return x + r1; + /*return x + (y - x)*a;*/ } -void drmp3_src_cache_init(drmp3_src* pSRC, drmp3_src_cache* pCache) + +/* +Greatest common factor using Euclid's algorithm iteratively. +*/ +static DRMP3_INLINE drmp3_uint32 drmp3_gcf_u32(drmp3_uint32 a, drmp3_uint32 b) { - drmp3_assert(pSRC != NULL); - drmp3_assert(pCache != NULL); - - pCache->pSRC = pSRC; - pCache->cachedFrameCount = 0; - pCache->iNextFrame = 0; -} - -drmp3_uint64 drmp3_src_cache_read_frames(drmp3_src_cache* pCache, drmp3_uint64 frameCount, float* pFramesOut) -{ - drmp3_uint32 channels; - drmp3_uint64 totalFramesRead = 0; - - drmp3_assert(pCache != NULL); - drmp3_assert(pCache->pSRC != NULL); - drmp3_assert(pCache->pSRC->onRead != NULL); - drmp3_assert(frameCount > 0); - drmp3_assert(pFramesOut != NULL); - - channels = pCache->pSRC->config.channels; - - while (frameCount > 0) { - /* If there's anything in memory go ahead and copy that over first. */ - drmp3_uint32 framesToReadFromClient; - drmp3_uint64 framesRemainingInMemory = pCache->cachedFrameCount - pCache->iNextFrame; - drmp3_uint64 framesToReadFromMemory = frameCount; - if (framesToReadFromMemory > framesRemainingInMemory) { - framesToReadFromMemory = framesRemainingInMemory; - } - - drmp3_copy_memory(pFramesOut, pCache->pCachedFrames + pCache->iNextFrame*channels, (drmp3_uint32)(framesToReadFromMemory * channels * sizeof(float))); - pCache->iNextFrame += (drmp3_uint32)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. */ - drmp3_assert(frameCount > 0); - pFramesOut += framesToReadFromMemory * channels; - - pCache->iNextFrame = 0; - pCache->cachedFrameCount = 0; - - framesToReadFromClient = drmp3_countof(pCache->pCachedFrames) / pCache->pSRC->config.channels; - if (framesToReadFromClient > pCache->pSRC->config.cacheSizeInFrames) { - framesToReadFromClient = pCache->pSRC->config.cacheSizeInFrames; - } - - pCache->cachedFrameCount = (drmp3_uint32)pCache->pSRC->onRead(pCache->pSRC, framesToReadFromClient, pCache->pCachedFrames, pCache->pSRC->pUserData); - - - /* Get out of this loop if nothing was able to be retrieved. */ - if (pCache->cachedFrameCount == 0) { + for (;;) { + if (b == 0) { break; + } else { + drmp3_uint32 t = a; + a = b; + b = t % a; } } - return totalFramesRead; + return a; } -drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); -drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush); - -drmp3_bool32 drmp3_src_init(const drmp3_src_config* pConfig, drmp3_src_read_proc onRead, void* pUserData, drmp3_src* pSRC) +static DRMP3_INLINE double drmp3_sin(double x) { - if (pSRC == NULL) { - return DRMP3_FALSE; - } - - drmp3_zero_object(pSRC); - - if (pConfig == NULL || onRead == NULL) { - return DRMP3_FALSE; - } - - if (pConfig->channels == 0 || pConfig->channels > 2) { - return DRMP3_FALSE; - } - - pSRC->config = *pConfig; - pSRC->onRead = onRead; - pSRC->pUserData = pUserData; - - if (pSRC->config.cacheSizeInFrames > DRMP3_SRC_CACHE_SIZE_IN_FRAMES || pSRC->config.cacheSizeInFrames == 0) { - pSRC->config.cacheSizeInFrames = DRMP3_SRC_CACHE_SIZE_IN_FRAMES; - } - - drmp3_src_cache_init(pSRC, &pSRC->cache); - return DRMP3_TRUE; + /* TODO: Implement custom sin(x). */ + return sin(x); } -drmp3_bool32 drmp3_src_set_input_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateIn) +static DRMP3_INLINE double drmp3_exp(double x) { - if (pSRC == NULL) { - return DRMP3_FALSE; - } - - /* Must have a sample rate of > 0. */ - if (sampleRateIn == 0) { - return DRMP3_FALSE; - } - - pSRC->config.sampleRateIn = sampleRateIn; - return DRMP3_TRUE; + /* TODO: Implement custom exp(x). */ + return exp(x); } -drmp3_bool32 drmp3_src_set_output_sample_rate(drmp3_src* pSRC, drmp3_uint32 sampleRateOut) +static DRMP3_INLINE double drmp3_cos(double x) { - if (pSRC == NULL) { - return DRMP3_FALSE; - } - - /* Must have a sample rate of > 0. */ - if (sampleRateOut == 0) { - return DRMP3_FALSE; - } - - pSRC->config.sampleRateOut = sampleRateOut; - return DRMP3_TRUE; + return drmp3_sin((DRMP3_PI_D*0.5) - x); } -drmp3_uint64 drmp3_src_read_frames_ex(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) + +static void* drmp3__malloc_default(size_t sz, void* pUserData) { - drmp3_src_algorithm algorithm; - - if (pSRC == NULL || frameCount == 0 || pFramesOut == NULL) { - return 0; - } - - algorithm = pSRC->config.algorithm; - - /* Always use passthrough if the sample rates are the same. */ - if (pSRC->config.sampleRateIn == pSRC->config.sampleRateOut) { - algorithm = drmp3_src_algorithm_none; - } - - /* Could just use a function pointer instead of a switch for this... */ - switch (algorithm) - { - case drmp3_src_algorithm_none: return drmp3_src_read_frames_passthrough(pSRC, frameCount, pFramesOut, flush); - case drmp3_src_algorithm_linear: return drmp3_src_read_frames_linear(pSRC, frameCount, pFramesOut, flush); - default: return 0; - } + (void)pUserData; + return DRMP3_MALLOC(sz); } -drmp3_uint64 drmp3_src_read_frames(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut) +static void* drmp3__realloc_default(void* p, size_t sz, void* pUserData) { - return drmp3_src_read_frames_ex(pSRC, frameCount, pFramesOut, DRMP3_FALSE); + (void)pUserData; + return DRMP3_REALLOC(p, sz); } -drmp3_uint64 drmp3_src_read_frames_passthrough(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) +static void drmp3__free_default(void* p, void* pUserData) { - drmp3_assert(pSRC != NULL); - drmp3_assert(frameCount > 0); - drmp3_assert(pFramesOut != NULL); - - (void)flush; /* Passthrough need not care about flushing. */ - return pSRC->onRead(pSRC, frameCount, pFramesOut, pSRC->pUserData); + (void)pUserData; + DRMP3_FREE(p); } -drmp3_uint64 drmp3_src_read_frames_linear(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, drmp3_bool32 flush) + +static void* drmp3__malloc_from_callbacks(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) { - double factor; - drmp3_uint64 totalFramesRead; - - drmp3_assert(pSRC != NULL); - drmp3_assert(frameCount > 0); - drmp3_assert(pFramesOut != NULL); - - /* For linear SRC, the bin is only 2 frames: 1 prior, 1 future. */ - - /* Load the bin if necessary. */ - if (!pSRC->algo.linear.isPrevFramesLoaded) { - drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin); - if (framesRead == 0) { - return 0; - } - pSRC->algo.linear.isPrevFramesLoaded = DRMP3_TRUE; - } - if (!pSRC->algo.linear.isNextFramesLoaded) { - drmp3_uint64 framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pSRC->bin + pSRC->config.channels); - if (framesRead == 0) { - return 0; - } - pSRC->algo.linear.isNextFramesLoaded = DRMP3_TRUE; + if (pAllocationCallbacks == NULL) { + return NULL; } - factor = (double)pSRC->config.sampleRateIn / pSRC->config.sampleRateOut; + if (pAllocationCallbacks->onMalloc != NULL) { + return pAllocationCallbacks->onMalloc(sz, pAllocationCallbacks->pUserData); + } - totalFramesRead = 0; - while (frameCount > 0) { - drmp3_uint32 i; - drmp3_uint32 framesToReadFromClient; + /* Try using realloc(). */ + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(NULL, sz, pAllocationCallbacks->pUserData); + } - /* The bin is where the previous and next frames are located. */ - float* pPrevFrame = pSRC->bin; - float* pNextFrame = pSRC->bin + pSRC->config.channels; + return NULL; +} - drmp3_blend_f32((float*)pFramesOut, pPrevFrame, pNextFrame, (float)pSRC->algo.linear.alpha, pSRC->config.channels); +static void* drmp3__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks == NULL) { + return NULL; + } - pSRC->algo.linear.alpha += factor; + if (pAllocationCallbacks->onRealloc != NULL) { + return pAllocationCallbacks->onRealloc(p, szNew, pAllocationCallbacks->pUserData); + } - /* The new alpha value is how we determine whether or not we need to read fresh frames. */ - framesToReadFromClient = (drmp3_uint32)pSRC->algo.linear.alpha; - pSRC->algo.linear.alpha = pSRC->algo.linear.alpha - framesToReadFromClient; + /* Try emulating realloc() in terms of malloc()/free(). */ + if (pAllocationCallbacks->onMalloc != NULL && pAllocationCallbacks->onFree != NULL) { + void* p2; - for (i = 0; i < framesToReadFromClient; ++i) { - drmp3_uint64 framesRead; - drmp3_uint32 j; - - for (j = 0; j < pSRC->config.channels; ++j) { - pPrevFrame[j] = pNextFrame[j]; - } - - framesRead = drmp3_src_cache_read_frames(&pSRC->cache, 1, pNextFrame); - if (framesRead == 0) { - drmp3_uint32 k; - for (k = 0; k < pSRC->config.channels; ++k) { - pNextFrame[k] = 0; - } - - if (pSRC->algo.linear.isNextFramesLoaded) { - pSRC->algo.linear.isNextFramesLoaded = DRMP3_FALSE; - } else { - if (flush) { - pSRC->algo.linear.isPrevFramesLoaded = DRMP3_FALSE; - } - } - - break; - } + p2 = pAllocationCallbacks->onMalloc(szNew, pAllocationCallbacks->pUserData); + if (p2 == NULL) { + return NULL; } - pFramesOut = (drmp3_uint8*)pFramesOut + (1 * pSRC->config.channels * sizeof(float)); - frameCount -= 1; - totalFramesRead += 1; - - /* If there's no frames available we need to get out of this loop. */ - if (!pSRC->algo.linear.isNextFramesLoaded && (!flush || !pSRC->algo.linear.isPrevFramesLoaded)) { - break; + if (p != NULL) { + DRMP3_COPY_MEMORY(p2, p, szOld); + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } + + return p2; } - return totalFramesRead; + return NULL; } +static void drmp3__free_from_callbacks(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (p == NULL || pAllocationCallbacks == NULL) { + return; + } + + if (pAllocationCallbacks->onFree != NULL) { + pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); + } +} + + +static drmp3_allocation_callbacks drmp3_copy_allocation_callbacks_or_defaults(const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + /* Copy. */ + return *pAllocationCallbacks; + } else { + /* Defaults. */ + drmp3_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = NULL; + allocationCallbacks.onMalloc = drmp3__malloc_default; + allocationCallbacks.onRealloc = drmp3__realloc_default; + allocationCallbacks.onFree = drmp3__free_default; + return allocationCallbacks; + } +} + + static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) { @@ -2549,7 +2583,7 @@ static size_t drmp3__on_read(drmp3* pMP3, void* pBufferOut, size_t bytesToRead) static drmp3_bool32 drmp3__on_seek(drmp3* pMP3, int offset, drmp3_seek_origin origin) { - drmp3_assert(offset >= 0); + DRMP3_ASSERT(offset >= 0); if (!pMP3->onSeek(pMP3->pUserData, offset, origin)) { return DRMP3_FALSE; @@ -2594,140 +2628,45 @@ static drmp3_bool32 drmp3__on_seek_64(drmp3* pMP3, drmp3_uint64 offset, drmp3_se return DRMP3_TRUE; } -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard); -static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3); -static drmp3_uint64 drmp3_read_src(drmp3_src* pSRC, drmp3_uint64 frameCount, void* pFramesOut, void* pUserData) -{ - drmp3* pMP3 = (drmp3*)pUserData; - float* pFramesOutF = (float*)pFramesOut; - drmp3_uint64 totalFramesRead = 0; - - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->onRead != NULL); - - while (frameCount > 0) { - /* Read from the in-memory buffer first. */ - while (pMP3->pcmFramesRemainingInMP3Frame > 0 && frameCount > 0) { - drmp3d_sample_t* frames = (drmp3d_sample_t*)pMP3->pcmFrames; -#ifndef DR_MP3_FLOAT_OUTPUT - if (pMP3->mp3FrameChannels == 1) { - if (pMP3->channels == 1) { - /* Mono -> Mono. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; - } else { - /* Mono -> Stereo. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; - pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame] / 32768.0f; - } - } else { - if (pMP3->channels == 1) { - /* Stereo -> Mono */ - float sample = 0; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; - pFramesOutF[0] = sample * 0.5f; - } else { - /* Stereo -> Stereo */ - pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0] / 32768.0f; - pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1] / 32768.0f; - } - } -#else - if (pMP3->mp3FrameChannels == 1) { - if (pMP3->channels == 1) { - /* Mono -> Mono. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; - } else { - /* Mono -> Stereo. */ - pFramesOutF[0] = frames[pMP3->pcmFramesConsumedInMP3Frame]; - pFramesOutF[1] = frames[pMP3->pcmFramesConsumedInMP3Frame]; - } - } else { - if (pMP3->channels == 1) { - /* Stereo -> Mono */ - float sample = 0; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; - sample += frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; - pFramesOutF[0] = sample * 0.5f; - } else { - /* Stereo -> Stereo */ - pFramesOutF[0] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+0]; - pFramesOutF[1] = frames[(pMP3->pcmFramesConsumedInMP3Frame*pMP3->mp3FrameChannels)+1]; - } - } -#endif - - pMP3->pcmFramesConsumedInMP3Frame += 1; - pMP3->pcmFramesRemainingInMP3Frame -= 1; - totalFramesRead += 1; - frameCount -= 1; - pFramesOutF += pSRC->config.channels; - } - - if (frameCount == 0) { - break; - } - - drmp3_assert(pMP3->pcmFramesRemainingInMP3Frame == 0); - - /* - At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed - at this point which means we'll also need to update our sample rate conversion pipeline. - */ - if (drmp3_decode_next_frame(pMP3) == 0) { - break; - } - } - - return totalFramesRead; -} - -static drmp3_bool32 drmp3_init_src(drmp3* pMP3) -{ - drmp3_src_config srcConfig; - drmp3_zero_object(&srcConfig); - srcConfig.sampleRateIn = DR_MP3_DEFAULT_SAMPLE_RATE; - srcConfig.sampleRateOut = pMP3->sampleRate; - srcConfig.channels = pMP3->channels; - srcConfig.algorithm = drmp3_src_algorithm_linear; - if (!drmp3_src_init(&srcConfig, drmp3_read_src, pMP3, &pMP3->src)) { - drmp3_uninit(pMP3); - return DRMP3_FALSE; - } - - return DRMP3_TRUE; -} - -static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames, drmp3_bool32 discard) +static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) { drmp3_uint32 pcmFramesRead = 0; - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->onRead != NULL); + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); if (pMP3->atEnd) { return 0; } - do { + for (;;) { drmp3dec_frame_info info; - size_t leftoverDataSize; - /* minimp3 recommends doing data submission in 16K chunks. If we don't have at least 16K bytes available, get more. */ - if (pMP3->dataSize < DRMP3_DATA_CHUNK_SIZE) { + /* minimp3 recommends doing data submission in chunks of at least 16K. If we don't have at least 16K bytes available, get more. */ + if (pMP3->dataSize < DRMP3_MIN_DATA_CHUNK_SIZE) { size_t bytesRead; + /* First we need to move the data down. */ + if (pMP3->pData != NULL) { + memmove(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); + } + + pMP3->dataConsumed = 0; + if (pMP3->dataCapacity < DRMP3_DATA_CHUNK_SIZE) { drmp3_uint8* pNewData; + size_t newDataCap; - pMP3->dataCapacity = DRMP3_DATA_CHUNK_SIZE; - pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + newDataCap = DRMP3_DATA_CHUNK_SIZE; + + pNewData = (drmp3_uint8*)drmp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks); if (pNewData == NULL) { return 0; /* Out of memory. */ } pMP3->pData = pNewData; + pMP3->dataCapacity = newDataCap; } bytesRead = drmp3__on_read(pMP3, pMP3->pData + pMP3->dataSize, (pMP3->dataCapacity - pMP3->dataSize)); @@ -2746,54 +2685,47 @@ static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPC return 0; /* File too big. */ } - pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ - + DRMP3_ASSERT(pMP3->pData != NULL); + DRMP3_ASSERT(pMP3->dataCapacity > 0); + + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); /* <-- Safe size_t -> int conversion thanks to the check above. */ + /* Consume the data. */ - leftoverDataSize = (pMP3->dataSize - (size_t)info.frame_bytes); if (info.frame_bytes > 0) { - memmove(pMP3->pData, pMP3->pData + info.frame_bytes, leftoverDataSize); - pMP3->dataSize = leftoverDataSize; + pMP3->dataConsumed += (size_t)info.frame_bytes; + pMP3->dataSize -= (size_t)info.frame_bytes; } - /* - pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully - decoded the frame. A special case is if we are wanting to discard the frame, in which case we return successfully. - */ - if (pcmFramesRead > 0 || (info.frame_bytes > 0 && discard)) { + /* pcmFramesRead will be equal to 0 if decoding failed. If it is zero and info.frame_bytes > 0 then we have successfully decoded the frame. */ + if (pcmFramesRead > 0) { pcmFramesRead = drmp3_hdr_frame_samples(pMP3->decoder.header); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; pMP3->mp3FrameChannels = info.channels; pMP3->mp3FrameSampleRate = info.hz; - - /* We need to initialize the resampler if we don't yet have the channel count or sample rate. */ - if (pMP3->channels == 0 || pMP3->sampleRate == 0) { - if (pMP3->channels == 0) { - pMP3->channels = info.channels; - } - if (pMP3->sampleRate == 0) { - pMP3->sampleRate = info.hz; - } - drmp3_init_src(pMP3); - } - - drmp3_src_set_input_sample_rate(&pMP3->src, pMP3->mp3FrameSampleRate); break; } else if (info.frame_bytes == 0) { + /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ size_t bytesRead; - /* Need more data. minimp3 recommends doing data submission in 16K chunks. */ - if (pMP3->dataCapacity == pMP3->dataSize) { - drmp3_uint8* pNewData; + /* First we need to move the data down. */ + memmove(pMP3->pData, pMP3->pData + pMP3->dataConsumed, pMP3->dataSize); + pMP3->dataConsumed = 0; + if (pMP3->dataCapacity == pMP3->dataSize) { /* No room. Expand. */ - pMP3->dataCapacity += DRMP3_DATA_CHUNK_SIZE; - pNewData = (drmp3_uint8*)drmp3_realloc(pMP3->pData, pMP3->dataCapacity); + drmp3_uint8* pNewData; + size_t newDataCap; + + newDataCap = pMP3->dataCapacity + DRMP3_DATA_CHUNK_SIZE; + + pNewData = (drmp3_uint8*)drmp3__realloc_from_callbacks(pMP3->pData, newDataCap, pMP3->dataCapacity, &pMP3->allocationCallbacks); if (pNewData == NULL) { return 0; /* Out of memory. */ } pMP3->pData = pNewData; + pMP3->dataCapacity = newDataCap; } /* Fill in a chunk. */ @@ -2805,15 +2737,50 @@ static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPC pMP3->dataSize += bytesRead; } - } while (DRMP3_TRUE); + }; return pcmFramesRead; } +static drmp3_uint32 drmp3_decode_next_frame_ex__memory(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + drmp3_uint32 pcmFramesRead = 0; + drmp3dec_frame_info info; + + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->memory.pData != NULL); + + if (pMP3->atEnd) { + return 0; + } + + pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->memory.pData + pMP3->memory.currentReadPos, (int)(pMP3->memory.dataSize - pMP3->memory.currentReadPos), pPCMFrames, &info); + if (pcmFramesRead > 0) { + pMP3->pcmFramesConsumedInMP3Frame = 0; + pMP3->pcmFramesRemainingInMP3Frame = pcmFramesRead; + pMP3->mp3FrameChannels = info.channels; + pMP3->mp3FrameSampleRate = info.hz; + } + + /* Consume the data. */ + pMP3->memory.currentReadPos += (size_t)info.frame_bytes; + + return pcmFramesRead; +} + +static drmp3_uint32 drmp3_decode_next_frame_ex(drmp3* pMP3, drmp3d_sample_t* pPCMFrames) +{ + if (pMP3->memory.pData != NULL && pMP3->memory.dataSize > 0) { + return drmp3_decode_next_frame_ex__memory(pMP3, pPCMFrames); + } else { + return drmp3_decode_next_frame_ex__callbacks(pMP3, pPCMFrames); + } +} + static drmp3_uint32 drmp3_decode_next_frame(drmp3* pMP3) { - drmp3_assert(pMP3 != NULL); - return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames, DRMP3_FALSE); + DRMP3_ASSERT(pMP3 != NULL); + return drmp3_decode_next_frame_ex(pMP3, (drmp3d_sample_t*)pMP3->pcmFrames); } #if 0 @@ -2821,7 +2788,7 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) { drmp3_uint32 pcmFrameCount; - drmp3_assert(pMP3 != NULL); + DRMP3_ASSERT(pMP3 != NULL); pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, NULL); if (pcmFrameCount == 0) { @@ -2837,61 +2804,43 @@ static drmp3_uint32 drmp3_seek_next_frame(drmp3* pMP3) } #endif -drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) { - drmp3_config config; - - drmp3_assert(pMP3 != NULL); - drmp3_assert(onRead != NULL); + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(onRead != NULL); /* This function assumes the output object has already been reset to 0. Do not do that here, otherwise things will break. */ drmp3dec_init(&pMP3->decoder); - /* The config can be null in which case we use defaults. */ - if (pConfig != NULL) { - config = *pConfig; - } else { - drmp3_zero_object(&config); - } - - pMP3->channels = config.outputChannels; - - /* Cannot have more than 2 channels. */ - if (pMP3->channels > 2) { - pMP3->channels = 2; - } - - pMP3->sampleRate = config.outputSampleRate; - pMP3->onRead = onRead; pMP3->onSeek = onSeek; pMP3->pUserData = pUserData; + pMP3->allocationCallbacks = drmp3_copy_allocation_callbacks_or_defaults(pAllocationCallbacks); - /* - We need a sample rate converter for converting the sample rate from the MP3 frames to the requested output sample rate. Note that if - we don't yet know the channel count or sample rate we defer this until the first frame is read. - */ - if (pMP3->channels != 0 && pMP3->sampleRate != 0) { - drmp3_init_src(pMP3); + if (pMP3->allocationCallbacks.onFree == NULL || (pMP3->allocationCallbacks.onMalloc == NULL && pMP3->allocationCallbacks.onRealloc == NULL)) { + return DRMP3_FALSE; /* Invalid allocation callbacks. */ } - + /* Decode the first frame to confirm that it is indeed a valid MP3 stream. */ if (!drmp3_decode_next_frame(pMP3)) { - drmp3_uninit(pMP3); + drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); /* The call above may have allocated memory. Need to make sure it's freed before aborting. */ return DRMP3_FALSE; /* Not a valid MP3 stream. */ } + pMP3->channels = pMP3->mp3FrameChannels; + pMP3->sampleRate = pMP3->mp3FrameSampleRate; + return DRMP3_TRUE; } -drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_config* pConfig) +DRMP3_API drmp3_bool32 drmp3_init(drmp3* pMP3, drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL || onRead == NULL) { return DRMP3_FALSE; } - drmp3_zero_object(pMP3); - return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pConfig); + DRMP3_ZERO_OBJECT(pMP3); + return drmp3_init_internal(pMP3, onRead, onSeek, pUserData, pAllocationCallbacks); } @@ -2900,8 +2849,8 @@ static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t by drmp3* pMP3 = (drmp3*)pUserData; size_t bytesRemaining; - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->memory.dataSize >= pMP3->memory.currentReadPos); bytesRemaining = pMP3->memory.dataSize - pMP3->memory.currentReadPos; if (bytesToRead > bytesRemaining) { @@ -2909,7 +2858,7 @@ static size_t drmp3__on_read_memory(void* pUserData, void* pBufferOut, size_t by } if (bytesToRead > 0) { - drmp3_copy_memory(pBufferOut, pMP3->memory.pData + pMP3->memory.currentReadPos, bytesToRead); + DRMP3_COPY_MEMORY(pBufferOut, pMP3->memory.pData + pMP3->memory.currentReadPos, bytesToRead); pMP3->memory.currentReadPos += bytesToRead; } @@ -2920,7 +2869,7 @@ static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3 { drmp3* pMP3 = (drmp3*)pUserData; - drmp3_assert(pMP3 != NULL); + DRMP3_ASSERT(pMP3 != NULL); if (origin == drmp3_seek_origin_current) { if (byteOffset > 0) { @@ -2946,13 +2895,13 @@ static drmp3_bool32 drmp3__on_seek_memory(void* pUserData, int byteOffset, drmp3 return DRMP3_TRUE; } -drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_config* pConfig) +DRMP3_API drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, const drmp3_allocation_callbacks* pAllocationCallbacks) { if (pMP3 == NULL) { return DRMP3_FALSE; } - drmp3_zero_object(pMP3); + DRMP3_ZERO_OBJECT(pMP3); if (pData == NULL || dataSize == 0) { return DRMP3_FALSE; @@ -2962,12 +2911,561 @@ drmp3_bool32 drmp3_init_memory(drmp3* pMP3, const void* pData, size_t dataSize, pMP3->memory.dataSize = dataSize; pMP3->memory.currentReadPos = 0; - return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pConfig); + return drmp3_init_internal(pMP3, drmp3__on_read_memory, drmp3__on_seek_memory, pMP3, pAllocationCallbacks); } #ifndef DR_MP3_NO_STDIO #include +#include /* For wcslen(), wcsrtombs() */ + +/* drmp3_result_from_errno() is only used inside DR_MP3_NO_STDIO for now. Move this out if it's ever used elsewhere. */ +#include +static drmp3_result drmp3_result_from_errno(int e) +{ + switch (e) + { + case 0: return DRMP3_SUCCESS; + #ifdef EPERM + case EPERM: return DRMP3_INVALID_OPERATION; + #endif + #ifdef ENOENT + case ENOENT: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef ESRCH + case ESRCH: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef EINTR + case EINTR: return DRMP3_INTERRUPT; + #endif + #ifdef EIO + case EIO: return DRMP3_IO_ERROR; + #endif + #ifdef ENXIO + case ENXIO: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef E2BIG + case E2BIG: return DRMP3_INVALID_ARGS; + #endif + #ifdef ENOEXEC + case ENOEXEC: return DRMP3_INVALID_FILE; + #endif + #ifdef EBADF + case EBADF: return DRMP3_INVALID_FILE; + #endif + #ifdef ECHILD + case ECHILD: return DRMP3_ERROR; + #endif + #ifdef EAGAIN + case EAGAIN: return DRMP3_UNAVAILABLE; + #endif + #ifdef ENOMEM + case ENOMEM: return DRMP3_OUT_OF_MEMORY; + #endif + #ifdef EACCES + case EACCES: return DRMP3_ACCESS_DENIED; + #endif + #ifdef EFAULT + case EFAULT: return DRMP3_BAD_ADDRESS; + #endif + #ifdef ENOTBLK + case ENOTBLK: return DRMP3_ERROR; + #endif + #ifdef EBUSY + case EBUSY: return DRMP3_BUSY; + #endif + #ifdef EEXIST + case EEXIST: return DRMP3_ALREADY_EXISTS; + #endif + #ifdef EXDEV + case EXDEV: return DRMP3_ERROR; + #endif + #ifdef ENODEV + case ENODEV: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef ENOTDIR + case ENOTDIR: return DRMP3_NOT_DIRECTORY; + #endif + #ifdef EISDIR + case EISDIR: return DRMP3_IS_DIRECTORY; + #endif + #ifdef EINVAL + case EINVAL: return DRMP3_INVALID_ARGS; + #endif + #ifdef ENFILE + case ENFILE: return DRMP3_TOO_MANY_OPEN_FILES; + #endif + #ifdef EMFILE + case EMFILE: return DRMP3_TOO_MANY_OPEN_FILES; + #endif + #ifdef ENOTTY + case ENOTTY: return DRMP3_INVALID_OPERATION; + #endif + #ifdef ETXTBSY + case ETXTBSY: return DRMP3_BUSY; + #endif + #ifdef EFBIG + case EFBIG: return DRMP3_TOO_BIG; + #endif + #ifdef ENOSPC + case ENOSPC: return DRMP3_NO_SPACE; + #endif + #ifdef ESPIPE + case ESPIPE: return DRMP3_BAD_SEEK; + #endif + #ifdef EROFS + case EROFS: return DRMP3_ACCESS_DENIED; + #endif + #ifdef EMLINK + case EMLINK: return DRMP3_TOO_MANY_LINKS; + #endif + #ifdef EPIPE + case EPIPE: return DRMP3_BAD_PIPE; + #endif + #ifdef EDOM + case EDOM: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef ERANGE + case ERANGE: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef EDEADLK + case EDEADLK: return DRMP3_DEADLOCK; + #endif + #ifdef ENAMETOOLONG + case ENAMETOOLONG: return DRMP3_PATH_TOO_LONG; + #endif + #ifdef ENOLCK + case ENOLCK: return DRMP3_ERROR; + #endif + #ifdef ENOSYS + case ENOSYS: return DRMP3_NOT_IMPLEMENTED; + #endif + #ifdef ENOTEMPTY + case ENOTEMPTY: return DRMP3_DIRECTORY_NOT_EMPTY; + #endif + #ifdef ELOOP + case ELOOP: return DRMP3_TOO_MANY_LINKS; + #endif + #ifdef ENOMSG + case ENOMSG: return DRMP3_NO_MESSAGE; + #endif + #ifdef EIDRM + case EIDRM: return DRMP3_ERROR; + #endif + #ifdef ECHRNG + case ECHRNG: return DRMP3_ERROR; + #endif + #ifdef EL2NSYNC + case EL2NSYNC: return DRMP3_ERROR; + #endif + #ifdef EL3HLT + case EL3HLT: return DRMP3_ERROR; + #endif + #ifdef EL3RST + case EL3RST: return DRMP3_ERROR; + #endif + #ifdef ELNRNG + case ELNRNG: return DRMP3_OUT_OF_RANGE; + #endif + #ifdef EUNATCH + case EUNATCH: return DRMP3_ERROR; + #endif + #ifdef ENOCSI + case ENOCSI: return DRMP3_ERROR; + #endif + #ifdef EL2HLT + case EL2HLT: return DRMP3_ERROR; + #endif + #ifdef EBADE + case EBADE: return DRMP3_ERROR; + #endif + #ifdef EBADR + case EBADR: return DRMP3_ERROR; + #endif + #ifdef EXFULL + case EXFULL: return DRMP3_ERROR; + #endif + #ifdef ENOANO + case ENOANO: return DRMP3_ERROR; + #endif + #ifdef EBADRQC + case EBADRQC: return DRMP3_ERROR; + #endif + #ifdef EBADSLT + case EBADSLT: return DRMP3_ERROR; + #endif + #ifdef EBFONT + case EBFONT: return DRMP3_INVALID_FILE; + #endif + #ifdef ENOSTR + case ENOSTR: return DRMP3_ERROR; + #endif + #ifdef ENODATA + case ENODATA: return DRMP3_NO_DATA_AVAILABLE; + #endif + #ifdef ETIME + case ETIME: return DRMP3_TIMEOUT; + #endif + #ifdef ENOSR + case ENOSR: return DRMP3_NO_DATA_AVAILABLE; + #endif + #ifdef ENONET + case ENONET: return DRMP3_NO_NETWORK; + #endif + #ifdef ENOPKG + case ENOPKG: return DRMP3_ERROR; + #endif + #ifdef EREMOTE + case EREMOTE: return DRMP3_ERROR; + #endif + #ifdef ENOLINK + case ENOLINK: return DRMP3_ERROR; + #endif + #ifdef EADV + case EADV: return DRMP3_ERROR; + #endif + #ifdef ESRMNT + case ESRMNT: return DRMP3_ERROR; + #endif + #ifdef ECOMM + case ECOMM: return DRMP3_ERROR; + #endif + #ifdef EPROTO + case EPROTO: return DRMP3_ERROR; + #endif + #ifdef EMULTIHOP + case EMULTIHOP: return DRMP3_ERROR; + #endif + #ifdef EDOTDOT + case EDOTDOT: return DRMP3_ERROR; + #endif + #ifdef EBADMSG + case EBADMSG: return DRMP3_BAD_MESSAGE; + #endif + #ifdef EOVERFLOW + case EOVERFLOW: return DRMP3_TOO_BIG; + #endif + #ifdef ENOTUNIQ + case ENOTUNIQ: return DRMP3_NOT_UNIQUE; + #endif + #ifdef EBADFD + case EBADFD: return DRMP3_ERROR; + #endif + #ifdef EREMCHG + case EREMCHG: return DRMP3_ERROR; + #endif + #ifdef ELIBACC + case ELIBACC: return DRMP3_ACCESS_DENIED; + #endif + #ifdef ELIBBAD + case ELIBBAD: return DRMP3_INVALID_FILE; + #endif + #ifdef ELIBSCN + case ELIBSCN: return DRMP3_INVALID_FILE; + #endif + #ifdef ELIBMAX + case ELIBMAX: return DRMP3_ERROR; + #endif + #ifdef ELIBEXEC + case ELIBEXEC: return DRMP3_ERROR; + #endif + #ifdef EILSEQ + case EILSEQ: return DRMP3_INVALID_DATA; + #endif + #ifdef ERESTART + case ERESTART: return DRMP3_ERROR; + #endif + #ifdef ESTRPIPE + case ESTRPIPE: return DRMP3_ERROR; + #endif + #ifdef EUSERS + case EUSERS: return DRMP3_ERROR; + #endif + #ifdef ENOTSOCK + case ENOTSOCK: return DRMP3_NOT_SOCKET; + #endif + #ifdef EDESTADDRREQ + case EDESTADDRREQ: return DRMP3_NO_ADDRESS; + #endif + #ifdef EMSGSIZE + case EMSGSIZE: return DRMP3_TOO_BIG; + #endif + #ifdef EPROTOTYPE + case EPROTOTYPE: return DRMP3_BAD_PROTOCOL; + #endif + #ifdef ENOPROTOOPT + case ENOPROTOOPT: return DRMP3_PROTOCOL_UNAVAILABLE; + #endif + #ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: return DRMP3_PROTOCOL_NOT_SUPPORTED; + #endif + #ifdef ESOCKTNOSUPPORT + case ESOCKTNOSUPPORT: return DRMP3_SOCKET_NOT_SUPPORTED; + #endif + #ifdef EOPNOTSUPP + case EOPNOTSUPP: return DRMP3_INVALID_OPERATION; + #endif + #ifdef EPFNOSUPPORT + case EPFNOSUPPORT: return DRMP3_PROTOCOL_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EAFNOSUPPORT + case EAFNOSUPPORT: return DRMP3_ADDRESS_FAMILY_NOT_SUPPORTED; + #endif + #ifdef EADDRINUSE + case EADDRINUSE: return DRMP3_ALREADY_IN_USE; + #endif + #ifdef EADDRNOTAVAIL + case EADDRNOTAVAIL: return DRMP3_ERROR; + #endif + #ifdef ENETDOWN + case ENETDOWN: return DRMP3_NO_NETWORK; + #endif + #ifdef ENETUNREACH + case ENETUNREACH: return DRMP3_NO_NETWORK; + #endif + #ifdef ENETRESET + case ENETRESET: return DRMP3_NO_NETWORK; + #endif + #ifdef ECONNABORTED + case ECONNABORTED: return DRMP3_NO_NETWORK; + #endif + #ifdef ECONNRESET + case ECONNRESET: return DRMP3_CONNECTION_RESET; + #endif + #ifdef ENOBUFS + case ENOBUFS: return DRMP3_NO_SPACE; + #endif + #ifdef EISCONN + case EISCONN: return DRMP3_ALREADY_CONNECTED; + #endif + #ifdef ENOTCONN + case ENOTCONN: return DRMP3_NOT_CONNECTED; + #endif + #ifdef ESHUTDOWN + case ESHUTDOWN: return DRMP3_ERROR; + #endif + #ifdef ETOOMANYREFS + case ETOOMANYREFS: return DRMP3_ERROR; + #endif + #ifdef ETIMEDOUT + case ETIMEDOUT: return DRMP3_TIMEOUT; + #endif + #ifdef ECONNREFUSED + case ECONNREFUSED: return DRMP3_CONNECTION_REFUSED; + #endif + #ifdef EHOSTDOWN + case EHOSTDOWN: return DRMP3_NO_HOST; + #endif + #ifdef EHOSTUNREACH + case EHOSTUNREACH: return DRMP3_NO_HOST; + #endif + #ifdef EALREADY + case EALREADY: return DRMP3_IN_PROGRESS; + #endif + #ifdef EINPROGRESS + case EINPROGRESS: return DRMP3_IN_PROGRESS; + #endif + #ifdef ESTALE + case ESTALE: return DRMP3_INVALID_FILE; + #endif + #ifdef EUCLEAN + case EUCLEAN: return DRMP3_ERROR; + #endif + #ifdef ENOTNAM + case ENOTNAM: return DRMP3_ERROR; + #endif + #ifdef ENAVAIL + case ENAVAIL: return DRMP3_ERROR; + #endif + #ifdef EISNAM + case EISNAM: return DRMP3_ERROR; + #endif + #ifdef EREMOTEIO + case EREMOTEIO: return DRMP3_IO_ERROR; + #endif + #ifdef EDQUOT + case EDQUOT: return DRMP3_NO_SPACE; + #endif + #ifdef ENOMEDIUM + case ENOMEDIUM: return DRMP3_DOES_NOT_EXIST; + #endif + #ifdef EMEDIUMTYPE + case EMEDIUMTYPE: return DRMP3_ERROR; + #endif + #ifdef ECANCELED + case ECANCELED: return DRMP3_CANCELLED; + #endif + #ifdef ENOKEY + case ENOKEY: return DRMP3_ERROR; + #endif + #ifdef EKEYEXPIRED + case EKEYEXPIRED: return DRMP3_ERROR; + #endif + #ifdef EKEYREVOKED + case EKEYREVOKED: return DRMP3_ERROR; + #endif + #ifdef EKEYREJECTED + case EKEYREJECTED: return DRMP3_ERROR; + #endif + #ifdef EOWNERDEAD + case EOWNERDEAD: return DRMP3_ERROR; + #endif + #ifdef ENOTRECOVERABLE + case ENOTRECOVERABLE: return DRMP3_ERROR; + #endif + #ifdef ERFKILL + case ERFKILL: return DRMP3_ERROR; + #endif + #ifdef EHWPOISON + case EHWPOISON: return DRMP3_ERROR; + #endif + default: return DRMP3_ERROR; + } +} + +static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +{ +#if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err; +#endif + + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRMP3_INVALID_ARGS; + } + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + err = fopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drmp3_result_from_errno(err); + } +#else +#if defined(_WIN32) || defined(__APPLE__) + *ppFile = fopen(pFilePath, pOpenMode); +#else + #if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64 && defined(_LARGEFILE64_SOURCE) + *ppFile = fopen64(pFilePath, pOpenMode); + #else + *ppFile = fopen(pFilePath, pOpenMode); + #endif +#endif + if (*ppFile == NULL) { + drmp3_result result = drmp3_result_from_errno(errno); + if (result == DRMP3_SUCCESS) { + result = DRMP3_ERROR; /* Just a safety check to make sure we never ever return success when pFile == NULL. */ + } + + return result; + } +#endif + + return DRMP3_SUCCESS; +} + +/* +_wfopen() isn't always available in all compilation environments. + + * Windows only. + * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). + * MinGW-64 (both 32- and 64-bit) seems to support it. + * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). + +This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() +fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. +*/ +#if defined(_WIN32) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) + #define DRMP3_HAS_WFOPEN + #endif +#endif + +static drmp3_result drmp3_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (ppFile != NULL) { + *ppFile = NULL; /* Safety. */ + } + + if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { + return DRMP3_INVALID_ARGS; + } + +#if defined(DRMP3_HAS_WFOPEN) + { + /* Use _wfopen() on Windows. */ + #if defined(_MSC_VER) && _MSC_VER >= 1400 + errno_t err = _wfopen_s(ppFile, pFilePath, pOpenMode); + if (err != 0) { + return drmp3_result_from_errno(err); + } + #else + *ppFile = _wfopen(pFilePath, pOpenMode); + if (*ppFile == NULL) { + return drmp3_result_from_errno(errno); + } + #endif + (void)pAllocationCallbacks; + } +#else + /* + Use fopen() on anything other than Windows. Requires a conversion. This is annoying because fopen() is locale specific. The only real way I can + think of to do this is with wcsrtombs(). Note that wcstombs() is apparently not thread-safe because it uses a static global mbstate_t object for + maintaining state. I've checked this with -std=c89 and it works, but if somebody get's a compiler error I'll look into improving compatibility. + */ + { + mbstate_t mbs; + size_t lenMB; + const wchar_t* pFilePathTemp = pFilePath; + char* pFilePathMB = NULL; + char pOpenModeMB[32] = {0}; + + /* Get the length first. */ + DRMP3_ZERO_OBJECT(&mbs); + lenMB = wcsrtombs(NULL, &pFilePathTemp, 0, &mbs); + if (lenMB == (size_t)-1) { + return drmp3_result_from_errno(errno); + } + + pFilePathMB = (char*)drmp3__malloc_from_callbacks(lenMB + 1, pAllocationCallbacks); + if (pFilePathMB == NULL) { + return DRMP3_OUT_OF_MEMORY; + } + + pFilePathTemp = pFilePath; + DRMP3_ZERO_OBJECT(&mbs); + wcsrtombs(pFilePathMB, &pFilePathTemp, lenMB + 1, &mbs); + + /* The open mode should always consist of ASCII characters so we should be able to do a trivial conversion. */ + { + size_t i = 0; + for (;;) { + if (pOpenMode[i] == 0) { + pOpenModeMB[i] = '\0'; + break; + } + + pOpenModeMB[i] = (char)pOpenMode[i]; + i += 1; + } + } + + *ppFile = fopen(pFilePathMB, pOpenModeMB); + + drmp3__free_from_callbacks(pFilePathMB, pAllocationCallbacks); + } + + if (*ppFile == NULL) { + return DRMP3_ERROR; + } +#endif + + return DRMP3_SUCCESS; +} + + static size_t drmp3__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) { @@ -2979,25 +3477,44 @@ static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek return fseek((FILE*)pUserData, offset, (origin == drmp3_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } -drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* filePath, const drmp3_config* pConfig) +DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) { + drmp3_bool32 result; FILE* pFile; -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (fopen_s(&pFile, filePath, "rb") != 0) { - return DRMP3_FALSE; - } -#else - pFile = fopen(filePath, "rb"); - if (pFile == NULL) { - return DRMP3_FALSE; - } -#endif - return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pConfig); + if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { + return DRMP3_FALSE; + } + + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + + return DRMP3_TRUE; +} + +DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + drmp3_bool32 result; + FILE* pFile; + + if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { + return DRMP3_FALSE; + } + + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + + return DRMP3_TRUE; } #endif -void drmp3_uninit(drmp3* pMP3) +DRMP3_API void drmp3_uninit(drmp3* pMP3) { if (pMP3 == NULL) { return; @@ -3005,106 +3522,213 @@ void drmp3_uninit(drmp3* pMP3) #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { - fclose((FILE*)pMP3->pUserData); + FILE* pFile = (FILE*)pMP3->pUserData; + if (pFile != NULL) { + fclose(pFile); + pMP3->pUserData = NULL; /* Make sure the file handle is cleared to NULL to we don't attempt to close it a second time. */ + } } #endif - drmp3_free(pMP3->pData); + drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); } -drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +#if defined(DR_MP3_FLOAT_OUTPUT) +static void drmp3_f32_to_s16(drmp3_int16* dst, const float* src, drmp3_uint64 sampleCount) +{ + drmp3_uint64 i; + drmp3_uint64 i4; + drmp3_uint64 sampleCount4; + + /* Unrolled. */ + i = 0; + sampleCount4 = sampleCount >> 2; + for (i4 = 0; i4 < sampleCount4; i4 += 1) { + float x0 = src[i+0]; + float x1 = src[i+1]; + float x2 = src[i+2]; + float x3 = src[i+3]; + + x0 = ((x0 < -1) ? -1 : ((x0 > 1) ? 1 : x0)); + x1 = ((x1 < -1) ? -1 : ((x1 > 1) ? 1 : x1)); + x2 = ((x2 < -1) ? -1 : ((x2 > 1) ? 1 : x2)); + x3 = ((x3 < -1) ? -1 : ((x3 > 1) ? 1 : x3)); + + x0 = x0 * 32767.0f; + x1 = x1 * 32767.0f; + x2 = x2 * 32767.0f; + x3 = x3 * 32767.0f; + + dst[i+0] = (drmp3_int16)x0; + dst[i+1] = (drmp3_int16)x1; + dst[i+2] = (drmp3_int16)x2; + dst[i+3] = (drmp3_int16)x3; + + i += 4; + } + + /* Leftover. */ + for (; i < sampleCount; i += 1) { + float x = src[i]; + x = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); /* clip */ + x = x * 32767.0f; /* -1..1 to -32767..32767 */ + + dst[i] = (drmp3_int16)x; + } +} +#endif + +#if !defined(DR_MP3_FLOAT_OUTPUT) +static void drmp3_s16_to_f32(float* dst, const drmp3_int16* src, drmp3_uint64 sampleCount) +{ + drmp3_uint64 i; + for (i = 0; i < sampleCount; i += 1) { + float x = (float)src[i]; + x = x * 0.000030517578125f; /* -32768..32767 to -1..0.999969482421875 */ + dst[i] = x; + } +} +#endif + + +static drmp3_uint64 drmp3_read_pcm_frames_raw(drmp3* pMP3, drmp3_uint64 framesToRead, void* pBufferOut) { drmp3_uint64 totalFramesRead = 0; - if (pMP3 == NULL || pMP3->onRead == NULL) { - return 0; - } + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onRead != NULL); - if (pBufferOut == NULL) { - float temp[4096]; - while (framesToRead > 0) { - drmp3_uint64 framesJustRead; - drmp3_uint64 framesToReadRightNow = sizeof(temp)/sizeof(temp[0]) / pMP3->channels; - if (framesToReadRightNow > framesToRead) { - framesToReadRightNow = framesToRead; - } - - framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); - if (framesJustRead == 0) { - break; - } - - framesToRead -= framesJustRead; - totalFramesRead += framesJustRead; + while (framesToRead > 0) { + drmp3_uint32 framesToConsume = (drmp3_uint32)DRMP3_MIN(pMP3->pcmFramesRemainingInMP3Frame, framesToRead); + if (pBufferOut != NULL) { + #if defined(DR_MP3_FLOAT_OUTPUT) + /* f32 */ + float* pFramesOutF32 = (float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalFramesRead * pMP3->channels); + float* pFramesInF32 = (float*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(float) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutF32, pFramesInF32, sizeof(float) * framesToConsume * pMP3->channels); + #else + /* s16 */ + drmp3_int16* pFramesOutS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalFramesRead * pMP3->channels); + drmp3_int16* pFramesInS16 = (drmp3_int16*)DRMP3_OFFSET_PTR(&pMP3->pcmFrames[0], sizeof(drmp3_int16) * pMP3->pcmFramesConsumedInMP3Frame * pMP3->mp3FrameChannels); + DRMP3_COPY_MEMORY(pFramesOutS16, pFramesInS16, sizeof(drmp3_int16) * framesToConsume * pMP3->channels); + #endif + } + + pMP3->currentPCMFrame += framesToConsume; + pMP3->pcmFramesConsumedInMP3Frame += framesToConsume; + pMP3->pcmFramesRemainingInMP3Frame -= framesToConsume; + totalFramesRead += framesToConsume; + framesToRead -= framesToConsume; + + if (framesToRead == 0) { + break; + } + + DRMP3_ASSERT(pMP3->pcmFramesRemainingInMP3Frame == 0); + + /* + At this point we have exhausted our in-memory buffer so we need to re-fill. Note that the sample rate may have changed + at this point which means we'll also need to update our sample rate conversion pipeline. + */ + if (drmp3_decode_next_frame(pMP3) == 0) { + break; } - } else { - totalFramesRead = drmp3_src_read_frames_ex(&pMP3->src, framesToRead, pBufferOut, DRMP3_TRUE); - pMP3->currentPCMFrame += totalFramesRead; } return totalFramesRead; } -drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) -{ - float tempF32[4096]; - drmp3_uint64 pcmFramesJustRead; - drmp3_uint64 totalPCMFramesRead = 0; +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_f32(drmp3* pMP3, drmp3_uint64 framesToRead, float* pBufferOut) +{ if (pMP3 == NULL || pMP3->onRead == NULL) { return 0; } - /* Naive implementation: read into a temp f32 buffer, then convert. */ - for (;;) { - drmp3_uint64 pcmFramesToReadThisIteration = (framesToRead - totalPCMFramesRead); - if (pcmFramesToReadThisIteration > drmp3_countof(tempF32)/pMP3->channels) { - pcmFramesToReadThisIteration = drmp3_countof(tempF32)/pMP3->channels; +#if defined(DR_MP3_FLOAT_OUTPUT) + /* Fast path. No conversion required. */ + return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); +#else + /* Slow path. Convert from s16 to f32. */ + { + drmp3_int16 pTempS16[8192]; + drmp3_uint64 totalPCMFramesRead = 0; + + while (totalPCMFramesRead < framesToRead) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; + drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempS16) / pMP3->channels; + if (framesToReadNow > framesRemaining) { + framesToReadNow = framesRemaining; + } + + framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempS16); + if (framesJustRead == 0) { + break; + } + + drmp3_s16_to_f32((float*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(float) * totalPCMFramesRead * pMP3->channels), pTempS16, framesJustRead * pMP3->channels); + totalPCMFramesRead += framesJustRead; } - pcmFramesJustRead = drmp3_read_pcm_frames_f32(pMP3, pcmFramesToReadThisIteration, tempF32); - if (pcmFramesJustRead == 0) { - break; - } - - drmp3dec_f32_to_s16(tempF32, pBufferOut, (int)(pcmFramesJustRead * pMP3->channels)); /* <-- Safe cast since pcmFramesJustRead will be clamped based on the size of tempF32 which is always small. */ - pBufferOut += pcmFramesJustRead * pMP3->channels; - - totalPCMFramesRead += pcmFramesJustRead; - - if (pcmFramesJustRead < pcmFramesToReadThisIteration) { - break; - } + return totalPCMFramesRead; } - - return totalPCMFramesRead; +#endif } -void drmp3_reset(drmp3* pMP3) +DRMP3_API drmp3_uint64 drmp3_read_pcm_frames_s16(drmp3* pMP3, drmp3_uint64 framesToRead, drmp3_int16* pBufferOut) { - drmp3_assert(pMP3 != NULL); + if (pMP3 == NULL || pMP3->onRead == NULL) { + return 0; + } + +#if !defined(DR_MP3_FLOAT_OUTPUT) + /* Fast path. No conversion required. */ + return drmp3_read_pcm_frames_raw(pMP3, framesToRead, pBufferOut); +#else + /* Slow path. Convert from f32 to s16. */ + { + float pTempF32[4096]; + drmp3_uint64 totalPCMFramesRead = 0; + + while (totalPCMFramesRead < framesToRead) { + drmp3_uint64 framesJustRead; + drmp3_uint64 framesRemaining = framesToRead - totalPCMFramesRead; + drmp3_uint64 framesToReadNow = DRMP3_COUNTOF(pTempF32) / pMP3->channels; + if (framesToReadNow > framesRemaining) { + framesToReadNow = framesRemaining; + } + + framesJustRead = drmp3_read_pcm_frames_raw(pMP3, framesToReadNow, pTempF32); + if (framesJustRead == 0) { + break; + } + + drmp3_f32_to_s16((drmp3_int16*)DRMP3_OFFSET_PTR(pBufferOut, sizeof(drmp3_int16) * totalPCMFramesRead * pMP3->channels), pTempF32, framesJustRead * pMP3->channels); + totalPCMFramesRead += framesJustRead; + } + + return totalPCMFramesRead; + } +#endif +} + +static void drmp3_reset(drmp3* pMP3) +{ + DRMP3_ASSERT(pMP3 != NULL); pMP3->pcmFramesConsumedInMP3Frame = 0; pMP3->pcmFramesRemainingInMP3Frame = 0; pMP3->currentPCMFrame = 0; pMP3->dataSize = 0; pMP3->atEnd = DRMP3_FALSE; - pMP3->src.bin[0] = 0; - pMP3->src.bin[1] = 0; - pMP3->src.bin[2] = 0; - pMP3->src.bin[3] = 0; - pMP3->src.cache.cachedFrameCount = 0; - pMP3->src.cache.iNextFrame = 0; - pMP3->src.algo.linear.alpha = 0; - pMP3->src.algo.linear.isNextFramesLoaded = 0; - pMP3->src.algo.linear.isPrevFramesLoaded = 0; drmp3dec_init(&pMP3->decoder); } -drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) +static drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) { - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->onSeek != NULL); + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->onSeek != NULL); /* Seek to the start of the stream to begin with. */ if (!drmp3__on_seek(pMP3, 0, drmp3_seek_origin_start)) { @@ -3116,80 +3740,31 @@ drmp3_bool32 drmp3_seek_to_start_of_stream(drmp3* pMP3) return DRMP3_TRUE; } -float drmp3_get_cached_pcm_frame_count_from_src(drmp3* pMP3) -{ - return (pMP3->src.cache.cachedFrameCount - pMP3->src.cache.iNextFrame) + (float)pMP3->src.algo.linear.alpha; -} -float drmp3_get_pcm_frames_remaining_in_mp3_frame(drmp3* pMP3) -{ - float factor = (float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn; - float frameCountPreSRC = drmp3_get_cached_pcm_frame_count_from_src(pMP3) + pMP3->pcmFramesRemainingInMP3Frame; - return frameCountPreSRC * factor; -} - -/* -NOTE ON SEEKING -=============== -The seeking code below is a complete mess and is broken for cases when the sample rate changes. The problem -is with the resampling and the crappy resampler used by dr_mp3. What needs to happen is the following: - -1) The resampler needs to be replaced. -2) The resampler has state which needs to be updated whenever an MP3 frame is decoded outside of - drmp3_read_pcm_frames_f32(). The resampler needs an API to "flush" some imaginary input so that it's - state is updated accordingly. -*/ -drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) +static drmp3_bool32 drmp3_seek_forward_by_pcm_frames__brute_force(drmp3* pMP3, drmp3_uint64 frameOffset) { drmp3_uint64 framesRead; -#if 0 /* - MP3 is a bit annoying when it comes to seeking because of the bit reservoir. It basically means that an MP3 frame can possibly - depend on some of the data of prior frames. This means it's not as simple as seeking to the first byte of the MP3 frame that - contains the sample because that MP3 frame will need the data from the previous MP3 frame (which we just seeked past!). To - resolve this we seek past a number of MP3 frames up to a point, and then read-and-discard the remainder. + Just using a dumb read-and-discard for now. What would be nice is to parse only the header of the MP3 frame, and then skip over leading + frames without spending the time doing a full decode. I cannot see an easy way to do this in minimp3, however, so it may involve some + kind of manual processing. */ - drmp3_uint64 maxFramesToReadAndDiscard = (drmp3_uint64)(DRMP3_MAX_PCM_FRAMES_PER_MP3_FRAME * 3 * ((float)pMP3->src.config.sampleRateOut / (float)pMP3->src.config.sampleRateIn)); - - /* Now get rid of leading whole frames. */ - while (frameOffset > maxFramesToReadAndDiscard) { - float pcmFramesRemainingInCurrentMP3FrameF = drmp3_get_pcm_frames_remaining_in_mp3_frame(pMP3); - drmp3_uint32 pcmFramesRemainingInCurrentMP3Frame = (drmp3_uint32)pcmFramesRemainingInCurrentMP3FrameF; - if (frameOffset > pcmFramesRemainingInCurrentMP3Frame) { - frameOffset -= pcmFramesRemainingInCurrentMP3Frame; - pMP3->currentPCMFrame += pcmFramesRemainingInCurrentMP3Frame; - pMP3->pcmFramesConsumedInMP3Frame += pMP3->pcmFramesRemainingInMP3Frame; - pMP3->pcmFramesRemainingInMP3Frame = 0; - } else { - break; - } - - drmp3_uint32 pcmFrameCount = drmp3_decode_next_frame_ex(pMP3, pMP3->pcmFrames, DRMP3_FALSE); - if (pcmFrameCount == 0) { - break; - } - } - - /* The last step is to read-and-discard any remaining PCM frames to make it sample-exact. */ +#if defined(DR_MP3_FLOAT_OUTPUT) framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); - if (framesRead != frameOffset) { - return DRMP3_FALSE; - } #else - /* Just using a dumb read-and-discard for now pending updates to the resampler. */ - framesRead = drmp3_read_pcm_frames_f32(pMP3, frameOffset, NULL); + framesRead = drmp3_read_pcm_frames_s16(pMP3, frameOffset, NULL); +#endif if (framesRead != frameOffset) { return DRMP3_FALSE; } -#endif return DRMP3_TRUE; } -drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) +static drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 frameIndex) { - drmp3_assert(pMP3 != NULL); + DRMP3_ASSERT(pMP3 != NULL); if (frameIndex == pMP3->currentPCMFrame) { return DRMP3_TRUE; @@ -3206,15 +3781,15 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__brute_force(drmp3* pMP3, drmp3_uint64 fram } } - drmp3_assert(frameIndex >= pMP3->currentPCMFrame); + DRMP3_ASSERT(frameIndex >= pMP3->currentPCMFrame); return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, (frameIndex - pMP3->currentPCMFrame)); } -drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) +static drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, drmp3_uint32* pSeekPointIndex) { drmp3_uint32 iSeekPoint; - drmp3_assert(pSeekPointIndex != NULL); + DRMP3_ASSERT(pSeekPointIndex != NULL); *pSeekPointIndex = 0; @@ -3234,16 +3809,16 @@ drmp3_bool32 drmp3_find_closest_seek_point(drmp3* pMP3, drmp3_uint64 frameIndex, return DRMP3_TRUE; } -drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) +static drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frameIndex) { drmp3_seek_point seekPoint; drmp3_uint32 priorSeekPointIndex; drmp3_uint16 iMP3Frame; drmp3_uint64 leftoverFrames; - drmp3_assert(pMP3 != NULL); - drmp3_assert(pMP3->pSeekPoints != NULL); - drmp3_assert(pMP3->seekPointCount > 0); + DRMP3_ASSERT(pMP3 != NULL); + DRMP3_ASSERT(pMP3->pSeekPoints != NULL); + DRMP3_ASSERT(pMP3->seekPointCount > 0); /* If there is no prior seekpoint it means the target PCM frame comes before the first seek point. Just assume a seekpoint at the start of the file in this case. */ if (drmp3_find_closest_seek_point(pMP3, frameIndex, &priorSeekPointIndex)) { @@ -3265,7 +3840,7 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame /* Whole MP3 frames need to be discarded first. */ for (iMP3Frame = 0; iMP3Frame < seekPoint.mp3FramesToDiscard; ++iMP3Frame) { - drmp3_uint32 pcmFramesReadPreSRC; + drmp3_uint32 pcmFramesRead; drmp3d_sample_t* pPCMFrames; /* Pass in non-null for the last frame because we want to ensure the sample rate converter is preloaded correctly. */ @@ -3274,9 +3849,9 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame pPCMFrames = (drmp3d_sample_t*)pMP3->pcmFrames; } - /* We first need to decode the next frame, and then we need to flush the resampler. */ - pcmFramesReadPreSRC = drmp3_decode_next_frame_ex(pMP3, pPCMFrames, DRMP3_TRUE); - if (pcmFramesReadPreSRC == 0) { + /* We first need to decode the next frame. */ + pcmFramesRead = drmp3_decode_next_frame_ex(pMP3, pPCMFrames); + if (pcmFramesRead == 0) { return DRMP3_FALSE; } } @@ -3284,17 +3859,6 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame /* We seeked to an MP3 frame in the raw stream so we need to make sure the current PCM frame is set correctly. */ pMP3->currentPCMFrame = seekPoint.pcmFrameIndex - seekPoint.pcmFramesToDiscard; - /* - Update resampler. This is wrong. Need to instead update it on a per MP3 frame basis. Also broken for cases when - the sample rate is being reduced in my testing. Should work fine when the input and output sample rate is the same - or a clean multiple. - */ - pMP3->src.algo.linear.alpha = (drmp3_int64)pMP3->currentPCMFrame * ((double)pMP3->src.config.sampleRateIn / pMP3->src.config.sampleRateOut); /* <-- Cast to int64 is required for VC6. */ - pMP3->src.algo.linear.alpha = pMP3->src.algo.linear.alpha - (drmp3_uint32)(pMP3->src.algo.linear.alpha); - if (pMP3->src.algo.linear.alpha > 0) { - pMP3->src.algo.linear.isPrevFramesLoaded = 1; - } - /* Now at this point we can follow the same process as the brute force technique where we just skip over unnecessary MP3 frames and then read-and-discard at least 2 whole MP3 frames. @@ -3303,7 +3867,7 @@ drmp3_bool32 drmp3_seek_to_pcm_frame__seek_table(drmp3* pMP3, drmp3_uint64 frame return drmp3_seek_forward_by_pcm_frames__brute_force(pMP3, leftoverFrames); } -drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) +DRMP3_API drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) { if (pMP3 == NULL || pMP3->onSeek == NULL) { return DRMP3_FALSE; @@ -3321,12 +3885,11 @@ drmp3_bool32 drmp3_seek_to_pcm_frame(drmp3* pMP3, drmp3_uint64 frameIndex) } } -drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) +DRMP3_API drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3FrameCount, drmp3_uint64* pPCMFrameCount) { drmp3_uint64 currentPCMFrame; drmp3_uint64 totalPCMFrameCount; drmp3_uint64 totalMP3FrameCount; - float totalPCMFrameCountFractionalPart; if (pMP3 == NULL) { return DRMP3_FALSE; @@ -3352,25 +3915,15 @@ drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3Fr totalPCMFrameCount = 0; totalMP3FrameCount = 0; - totalPCMFrameCountFractionalPart = 0; /* <-- With resampling there will be a fractional part to each MP3 frame that we need to accumulate. */ for (;;) { - drmp3_uint32 pcmFramesInCurrentMP3FrameIn; - float srcRatio; - float pcmFramesInCurrentMP3FrameOutF; - drmp3_uint32 pcmFramesInCurrentMP3FrameOut; + drmp3_uint32 pcmFramesInCurrentMP3Frame; - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); - if (pcmFramesInCurrentMP3FrameIn == 0) { + pcmFramesInCurrentMP3Frame = drmp3_decode_next_frame_ex(pMP3, NULL); + if (pcmFramesInCurrentMP3Frame == 0) { break; } - srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; - drmp3_assert(srcRatio > 0); - - pcmFramesInCurrentMP3FrameOutF = totalPCMFrameCountFractionalPart + (pcmFramesInCurrentMP3FrameIn / srcRatio); - pcmFramesInCurrentMP3FrameOut = (drmp3_uint32)pcmFramesInCurrentMP3FrameOutF; - totalPCMFrameCountFractionalPart = pcmFramesInCurrentMP3FrameOutF - pcmFramesInCurrentMP3FrameOut; - totalPCMFrameCount += pcmFramesInCurrentMP3FrameOut; + totalPCMFrameCount += pcmFramesInCurrentMP3Frame; totalMP3FrameCount += 1; } @@ -3393,7 +3946,7 @@ drmp3_bool32 drmp3_get_mp3_and_pcm_frame_count(drmp3* pMP3, drmp3_uint64* pMP3Fr return DRMP3_TRUE; } -drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) +DRMP3_API drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) { drmp3_uint64 totalPCMFrameCount; if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, NULL, &totalPCMFrameCount)) { @@ -3403,7 +3956,7 @@ drmp3_uint64 drmp3_get_pcm_frame_count(drmp3* pMP3) return totalPCMFrameCount; } -drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) +DRMP3_API drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) { drmp3_uint64 totalMP3FrameCount; if (!drmp3_get_mp3_and_pcm_frame_count(pMP3, &totalMP3FrameCount, NULL)) { @@ -3413,14 +3966,14 @@ drmp3_uint64 drmp3_get_mp3_frame_count(drmp3* pMP3) return totalMP3FrameCount; } -void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) +static void drmp3__accumulate_running_pcm_frame_count(drmp3* pMP3, drmp3_uint32 pcmFrameCountIn, drmp3_uint64* pRunningPCMFrameCount, float* pRunningPCMFrameCountFractionalPart) { float srcRatio; float pcmFrameCountOutF; drmp3_uint32 pcmFrameCountOut; srcRatio = (float)pMP3->mp3FrameSampleRate / (float)pMP3->sampleRate; - drmp3_assert(srcRatio > 0); + DRMP3_ASSERT(srcRatio > 0); pcmFrameCountOutF = *pRunningPCMFrameCountFractionalPart + (pcmFrameCountIn / srcRatio); pcmFrameCountOut = (drmp3_uint32)pcmFrameCountOutF; @@ -3434,7 +3987,7 @@ typedef struct drmp3_uint64 pcmFrameIndex; /* <-- After sample rate conversion. */ } drmp3__seeking_mp3_frame_info; -drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) +DRMP3_API drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCount, drmp3_seek_point* pSeekPoints) { drmp3_uint32 seekPointCount; drmp3_uint64 currentPCMFrame; @@ -3498,12 +4051,12 @@ drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCo drmp3_uint32 pcmFramesInCurrentMP3FrameIn; /* The byte position of the next frame will be the stream's cursor position, minus whatever is sitting in the buffer. */ - drmp3_assert(pMP3->streamCursor >= pMP3->dataSize); + DRMP3_ASSERT(pMP3->streamCursor >= pMP3->dataSize); mp3FrameInfo[iMP3Frame].bytePos = pMP3->streamCursor - pMP3->dataSize; mp3FrameInfo[iMP3Frame].pcmFrameIndex = runningPCMFrameCount; /* We need to get information about this frame so we can know how many samples it contained. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_FALSE); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { return DRMP3_FALSE; /* This should never happen. */ } @@ -3535,19 +4088,19 @@ drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCo The next seek point is not in the current MP3 frame, so continue on to the next one. The first thing to do is cycle the cached MP3 frame info. */ - for (i = 0; i < drmp3_countof(mp3FrameInfo)-1; ++i) { + for (i = 0; i < DRMP3_COUNTOF(mp3FrameInfo)-1; ++i) { mp3FrameInfo[i] = mp3FrameInfo[i+1]; } /* Cache previous MP3 frame info. */ - mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; - mp3FrameInfo[drmp3_countof(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; + mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].bytePos = pMP3->streamCursor - pMP3->dataSize; + mp3FrameInfo[DRMP3_COUNTOF(mp3FrameInfo)-1].pcmFrameIndex = runningPCMFrameCount; /* Go to the next MP3 frame. This shouldn't ever fail, but just in case it does we just set the seek point and break. If it happens, it should only ever do it for the last seek point. */ - pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL, DRMP3_TRUE); + pcmFramesInCurrentMP3FrameIn = drmp3_decode_next_frame_ex(pMP3, NULL); if (pcmFramesInCurrentMP3FrameIn == 0) { pSeekPoints[iSeekPoint].seekPosInBytes = mp3FrameInfo[0].bytePos; pSeekPoints[iSeekPoint].pcmFrameIndex = nextTargetPCMFrame; @@ -3574,7 +4127,7 @@ drmp3_bool32 drmp3_calculate_seek_points(drmp3* pMP3, drmp3_uint32* pSeekPointCo return DRMP3_TRUE; } -drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) +DRMP3_API drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drmp3_seek_point* pSeekPoints) { if (pMP3 == NULL) { return DRMP3_FALSE; @@ -3594,17 +4147,17 @@ drmp3_bool32 drmp3_bind_seek_table(drmp3* pMP3, drmp3_uint32 seekPointCount, drm } -float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +static float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; float* pFrames = NULL; float temp[4096]; - drmp3_assert(pMP3 != NULL); + DRMP3_ASSERT(pMP3 != NULL); for (;;) { - drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_f32(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; @@ -3612,29 +4165,33 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ /* Reallocate the output buffer if there's not enough room. */ if (framesCapacity < totalFramesRead + framesJustRead) { + drmp3_uint64 oldFramesBufferSize; drmp3_uint64 newFramesBufferSize; + drmp3_uint64 newFramesCap; float* pNewFrames; - framesCapacity *= 2; - if (framesCapacity < totalFramesRead + framesJustRead) { - framesCapacity = totalFramesRead + framesJustRead; + newFramesCap = framesCapacity * 2; + if (newFramesCap < totalFramesRead + framesJustRead) { + newFramesCap = totalFramesRead + framesJustRead; } - newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(float); + oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(float); + newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(float); if (newFramesBufferSize > DRMP3_SIZE_MAX) { break; } - pNewFrames = (float*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); + pNewFrames = (float*)drmp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); if (pNewFrames == NULL) { - drmp3_free(pFrames); + drmp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); break; } pFrames = pNewFrames; + framesCapacity = newFramesCap; } - drmp3_copy_memory(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(float))); + DRMP3_COPY_MEMORY(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(float))); totalFramesRead += framesJustRead; /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ @@ -3644,8 +4201,8 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ } if (pConfig != NULL) { - pConfig->outputChannels = pMP3->channels; - pConfig->outputSampleRate = pMP3->sampleRate; + pConfig->channels = pMP3->channels; + pConfig->sampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); @@ -3657,17 +4214,17 @@ float* drmp3__full_read_and_close_f32(drmp3* pMP3, drmp3_config* pConfig, drmp3_ return pFrames; } -drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +static drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) { drmp3_uint64 totalFramesRead = 0; drmp3_uint64 framesCapacity = 0; drmp3_int16* pFrames = NULL; drmp3_int16 temp[4096]; - drmp3_assert(pMP3 != NULL); + DRMP3_ASSERT(pMP3 != NULL); for (;;) { - drmp3_uint64 framesToReadRightNow = drmp3_countof(temp) / pMP3->channels; + drmp3_uint64 framesToReadRightNow = DRMP3_COUNTOF(temp) / pMP3->channels; drmp3_uint64 framesJustRead = drmp3_read_pcm_frames_s16(pMP3, framesToReadRightNow, temp); if (framesJustRead == 0) { break; @@ -3676,28 +4233,32 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, /* Reallocate the output buffer if there's not enough room. */ if (framesCapacity < totalFramesRead + framesJustRead) { drmp3_uint64 newFramesBufferSize; + drmp3_uint64 oldFramesBufferSize; + drmp3_uint64 newFramesCap; drmp3_int16* pNewFrames; - framesCapacity *= 2; - if (framesCapacity < totalFramesRead + framesJustRead) { - framesCapacity = totalFramesRead + framesJustRead; + newFramesCap = framesCapacity * 2; + if (newFramesCap < totalFramesRead + framesJustRead) { + newFramesCap = totalFramesRead + framesJustRead; } - newFramesBufferSize = framesCapacity*pMP3->channels*sizeof(drmp3_int16); + oldFramesBufferSize = framesCapacity * pMP3->channels * sizeof(drmp3_int16); + newFramesBufferSize = newFramesCap * pMP3->channels * sizeof(drmp3_int16); if (newFramesBufferSize > DRMP3_SIZE_MAX) { break; } - pNewFrames = (drmp3_int16*)drmp3_realloc(pFrames, (size_t)newFramesBufferSize); + pNewFrames = (drmp3_int16*)drmp3__realloc_from_callbacks(pFrames, (size_t)newFramesBufferSize, (size_t)oldFramesBufferSize, &pMP3->allocationCallbacks); if (pNewFrames == NULL) { - drmp3_free(pFrames); + drmp3__free_from_callbacks(pFrames, &pMP3->allocationCallbacks); break; } pFrames = pNewFrames; + framesCapacity = newFramesCap; } - drmp3_copy_memory(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(drmp3_int16))); + DRMP3_COPY_MEMORY(pFrames + totalFramesRead*pMP3->channels, temp, (size_t)(framesJustRead*pMP3->channels*sizeof(drmp3_int16))); totalFramesRead += framesJustRead; /* If the number of frames we asked for is less that what we actually read it means we've reached the end. */ @@ -3707,8 +4268,8 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, } if (pConfig != NULL) { - pConfig->outputChannels = pMP3->channels; - pConfig->outputSampleRate = pMP3->sampleRate; + pConfig->channels = pMP3->channels; + pConfig->sampleRate = pMP3->sampleRate; } drmp3_uninit(pMP3); @@ -3721,20 +4282,20 @@ drmp3_int16* drmp3__full_read_and_close_s16(drmp3* pMP3, drmp3_config* pConfig, } -float* drmp3_open_and_read_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +DRMP3_API float* drmp3_open_and_read_pcm_frames_f32(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) { + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -drmp3_int16* drmp3_open_and_read_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +DRMP3_API drmp3_int16* drmp3_open_and_read_pcm_frames_s16(drmp3_read_proc onRead, drmp3_seek_proc onSeek, void* pUserData, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pConfig)) { + if (!drmp3_init(&mp3, onRead, onSeek, pUserData, pAllocationCallbacks)) { return NULL; } @@ -3742,20 +4303,20 @@ drmp3_int16* drmp3_open_and_read_s16(drmp3_read_proc onRead, drmp3_seek_proc onS } -float* drmp3_open_memory_and_read_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +DRMP3_API float* drmp3_open_memory_and_read_pcm_frames_f32(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) { + if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -drmp3_int16* drmp3_open_memory_and_read_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +DRMP3_API drmp3_int16* drmp3_open_memory_and_read_pcm_frames_s16(const void* pData, size_t dataSize, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_memory(&mp3, pData, dataSize, pConfig)) { + if (!drmp3_init_memory(&mp3, pData, dataSize, pAllocationCallbacks)) { return NULL; } @@ -3764,20 +4325,20 @@ drmp3_int16* drmp3_open_memory_and_read_s16(const void* pData, size_t dataSize, #ifndef DR_MP3_NO_STDIO -float* drmp3_open_file_and_read_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +DRMP3_API float* drmp3_open_file_and_read_pcm_frames_f32(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_file(&mp3, filePath, pConfig)) { + if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { return NULL; } return drmp3__full_read_and_close_f32(&mp3, pConfig, pTotalFrameCount); } -drmp3_int16* drmp3_open_file_and_read_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount) +DRMP3_API drmp3_int16* drmp3_open_file_and_read_pcm_frames_s16(const char* filePath, drmp3_config* pConfig, drmp3_uint64* pTotalFrameCount, const drmp3_allocation_callbacks* pAllocationCallbacks) { drmp3 mp3; - if (!drmp3_init_file(&mp3, filePath, pConfig)) { + if (!drmp3_init_file(&mp3, filePath, pAllocationCallbacks)) { return NULL; } @@ -3785,12 +4346,26 @@ drmp3_int16* drmp3_open_file_and_read_s16(const char* filePath, drmp3_config* pC } #endif -void drmp3_free(void* p) +DRMP3_API void* drmp3_malloc(size_t sz, const drmp3_allocation_callbacks* pAllocationCallbacks) { - DRMP3_FREE(p); + if (pAllocationCallbacks != NULL) { + return drmp3__malloc_from_callbacks(sz, pAllocationCallbacks); + } else { + return drmp3__malloc_default(sz, NULL); + } } -#endif /*DR_MP3_IMPLEMENTATION*/ +DRMP3_API void drmp3_free(void* p, const drmp3_allocation_callbacks* pAllocationCallbacks) +{ + if (pAllocationCallbacks != NULL) { + drmp3__free_from_callbacks(p, pAllocationCallbacks); + } else { + drmp3__free_default(p, NULL); + } +} + +#endif /* dr_mp3_c */ +#endif /*DR_MP3_IMPLEMENTATION*/ /* DIFFERENCES BETWEEN minimp3 AND dr_mp3 @@ -3807,9 +4382,207 @@ DIFFERENCES BETWEEN minimp3 AND dr_mp3 using minimp3 in conjunction with stb_vorbis. dr_mp3 addresses this. */ +/* +RELEASE NOTES - v0.5.0 +======================= +Version 0.5.0 has breaking API changes. + +Improved Client-Defined Memory Allocation +----------------------------------------- +The main change with this release is the addition of a more flexible way of implementing custom memory allocation routines. The +existing system of DRMP3_MALLOC, DRMP3_REALLOC and DRMP3_FREE are still in place and will be used by default when no custom +allocation callbacks are specified. + +To use the new system, you pass in a pointer to a drmp3_allocation_callbacks object to drmp3_init() and family, like this: + + void* my_malloc(size_t sz, void* pUserData) + { + return malloc(sz); + } + void* my_realloc(void* p, size_t sz, void* pUserData) + { + return realloc(p, sz); + } + void my_free(void* p, void* pUserData) + { + free(p); + } + + ... + + drmp3_allocation_callbacks allocationCallbacks; + allocationCallbacks.pUserData = &myData; + allocationCallbacks.onMalloc = my_malloc; + allocationCallbacks.onRealloc = my_realloc; + allocationCallbacks.onFree = my_free; + drmp3_init_file(&mp3, "my_file.mp3", NULL, &allocationCallbacks); + +The advantage of this new system is that it allows you to specify user data which will be passed in to the allocation routines. + +Passing in null for the allocation callbacks object will cause dr_mp3 to use defaults which is the same as DRMP3_MALLOC, +DRMP3_REALLOC and DRMP3_FREE and the equivalent of how it worked in previous versions. + +Every API that opens a drmp3 object now takes this extra parameter. These include the following: + + drmp3_init() + drmp3_init_file() + drmp3_init_memory() + drmp3_open_and_read_pcm_frames_f32() + drmp3_open_and_read_pcm_frames_s16() + drmp3_open_memory_and_read_pcm_frames_f32() + drmp3_open_memory_and_read_pcm_frames_s16() + drmp3_open_file_and_read_pcm_frames_f32() + drmp3_open_file_and_read_pcm_frames_s16() + +Renamed APIs +------------ +The following APIs have been renamed for consistency with other dr_* libraries and to make it clear that they return PCM frame +counts rather than sample counts. + + drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() + drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() + drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() + drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() + drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() + drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() +*/ + /* REVISION HISTORY ================ +v0.6.27 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + +v0.6.26 - 2021-01-31 + - Bring up to date with minimp3. + +v0.6.25 - 2020-12-26 + - Remove DRMP3_DEFAULT_CHANNELS and DRMP3_DEFAULT_SAMPLE_RATE which are leftovers from some removed APIs. + +v0.6.24 - 2020-12-07 + - Fix a typo in version date for 0.6.23. + +v0.6.23 - 2020-12-03 + - Fix an error where a file can be closed twice when initialization of the decoder fails. + +v0.6.22 - 2020-12-02 + - Fix an error where it's possible for a file handle to be left open when initialization of the decoder fails. + +v0.6.21 - 2020-11-28 + - Bring up to date with minimp3. + +v0.6.20 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.6.19 - 2020-11-13 + - Minor code clean up. + +v0.6.18 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.6.17 - 2020-09-28 + - Bring up to date with minimp3. + +v0.6.16 - 2020-08-02 + - Simplify sized types. + +v0.6.15 - 2020-07-25 + - Fix a compilation warning. + +v0.6.14 - 2020-07-23 + - Fix undefined behaviour with memmove(). + +v0.6.13 - 2020-07-06 + - Fix a bug when converting from s16 to f32 in drmp3_read_pcm_frames_f32(). + +v0.6.12 - 2020-06-23 + - Add include guard for the implementation section. + +v0.6.11 - 2020-05-26 + - Fix use of uninitialized variable error. + +v0.6.10 - 2020-05-16 + - Add compile-time and run-time version querying. + - DRMP3_VERSION_MINOR + - DRMP3_VERSION_MAJOR + - DRMP3_VERSION_REVISION + - DRMP3_VERSION_STRING + - drmp3_version() + - drmp3_version_string() + +v0.6.9 - 2020-04-30 + - Change the `pcm` parameter of drmp3dec_decode_frame() to a `const drmp3_uint8*` for consistency with internal APIs. + +v0.6.8 - 2020-04-26 + - Optimizations to decoding when initializing from memory. + +v0.6.7 - 2020-04-25 + - Fix a compilation error with DR_MP3_NO_STDIO + - Optimization to decoding by reducing some data movement. + +v0.6.6 - 2020-04-23 + - Fix a minor bug with the running PCM frame counter. + +v0.6.5 - 2020-04-19 + - Fix compilation error on ARM builds. + +v0.6.4 - 2020-04-19 + - Bring up to date with changes to minimp3. + +v0.6.3 - 2020-04-13 + - Fix some pedantic warnings. + +v0.6.2 - 2020-04-10 + - Fix a crash in drmp3_open_*_and_read_pcm_frames_*() if the output config object is NULL. + +v0.6.1 - 2020-04-05 + - Fix warnings. + +v0.6.0 - 2020-04-04 + - API CHANGE: Remove the pConfig parameter from the following APIs: + - drmp3_init() + - drmp3_init_memory() + - drmp3_init_file() + - Add drmp3_init_file_w() for opening a file from a wchar_t encoded path. + +v0.5.6 - 2020-02-12 + - Bring up to date with minimp3. + +v0.5.5 - 2020-01-29 + - Fix a memory allocation bug in high level s16 decoding APIs. + +v0.5.4 - 2019-12-02 + - Fix a possible null pointer dereference when using custom memory allocators for realloc(). + +v0.5.3 - 2019-11-14 + - Fix typos in documentation. + +v0.5.2 - 2019-11-02 + - Bring up to date with minimp3. + +v0.5.1 - 2019-10-08 + - Fix a warning with GCC. + +v0.5.0 - 2019-10-07 + - API CHANGE: Add support for user defined memory allocation routines. This system allows the program to specify their own memory allocation + routines with a user data pointer for client-specific contextual data. This adds an extra parameter to the end of the following APIs: + - drmp3_init() + - drmp3_init_file() + - drmp3_init_memory() + - drmp3_open_and_read_pcm_frames_f32() + - drmp3_open_and_read_pcm_frames_s16() + - drmp3_open_memory_and_read_pcm_frames_f32() + - drmp3_open_memory_and_read_pcm_frames_s16() + - drmp3_open_file_and_read_pcm_frames_f32() + - drmp3_open_file_and_read_pcm_frames_s16() + - API CHANGE: Renamed the following APIs: + - drmp3_open_and_read_f32() -> drmp3_open_and_read_pcm_frames_f32() + - drmp3_open_and_read_s16() -> drmp3_open_and_read_pcm_frames_s16() + - drmp3_open_memory_and_read_f32() -> drmp3_open_memory_and_read_pcm_frames_f32() + - drmp3_open_memory_and_read_s16() -> drmp3_open_memory_and_read_pcm_frames_s16() + - drmp3_open_file_and_read_f32() -> drmp3_open_file_and_read_pcm_frames_f32() + - drmp3_open_file_and_read_s16() -> drmp3_open_file_and_read_pcm_frames_s16() + v0.4.7 - 2019-07-28 - Fix a compiler error. @@ -3823,14 +4596,14 @@ v0.4.4 - 2019-05-06 - Fixes to the VC6 build. v0.4.3 - 2019-05-05 - - Use the channel count and/or sample rate of the first MP3 frame instead of DR_MP3_DEFAULT_CHANNELS and - DR_MP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to - DR_MP3_DEFAULT_CHANNELS or DR_MP3_DEFAULT_SAMPLE_RATE. + - Use the channel count and/or sample rate of the first MP3 frame instead of DRMP3_DEFAULT_CHANNELS and + DRMP3_DEFAULT_SAMPLE_RATE when they are set to 0. To use the old behaviour, just set the relevant property to + DRMP3_DEFAULT_CHANNELS or DRMP3_DEFAULT_SAMPLE_RATE. - Add s16 reading APIs - drmp3_read_pcm_frames_s16 - - drmp3_open_memory_and_read_s16 - - drmp3_open_and_read_s16 - - drmp3_open_file_and_read_s16 + - drmp3_open_memory_and_read_pcm_frames_s16 + - drmp3_open_and_read_pcm_frames_s16 + - drmp3_open_file_and_read_pcm_frames_s16 - Add drmp3_get_mp3_and_pcm_frame_count() to the public header section. - Add support for C89. - Change license to choice of public domain or MIT-0. @@ -3845,9 +4618,9 @@ v0.4.0 - 2018-12-16 - API CHANGE: Rename some APIs: - drmp3_read_f32 -> to drmp3_read_pcm_frames_f32 - drmp3_seek_to_frame -> drmp3_seek_to_pcm_frame - - drmp3_open_and_decode_f32 -> drmp3_open_and_read_f32 - - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_f32 - - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_f32 + - drmp3_open_and_decode_f32 -> drmp3_open_and_read_pcm_frames_f32 + - drmp3_open_and_decode_memory_f32 -> drmp3_open_memory_and_read_pcm_frames_f32 + - drmp3_open_and_decode_file_f32 -> drmp3_open_file_and_read_pcm_frames_f32 - Add drmp3_get_pcm_frame_count(). - Add drmp3_get_mp3_frame_count(). - Improve seeking performance. @@ -3951,7 +4724,7 @@ For more information, please refer to =============================================================================== ALTERNATIVE 2 - MIT No Attribution =============================================================================== -Copyright 2018 David Reid +Copyright 2020 David Reid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/raylib/external/dr_wav.h b/raylib/external/dr_wav.h index 66f7455..be1438b 100644 --- a/raylib/external/dr_wav.h +++ b/raylib/external/dr_wav.h @@ -1,6 +1,6 @@ /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_wav - v0.12.4 - 2020-05-16 +dr_wav - v0.12.19 - 2021-02-21 David Reid - mackron@gmail.com @@ -15,10 +15,10 @@ Version 0.12 includes breaking changes to custom chunk handling. Changes to Chunk Callback ------------------------- -dr_wav supports the ability to fire a callback when a chunk is encounted (except for WAVE and FMT chunks). The callback has been update to include both the +dr_wav supports the ability to fire a callback when a chunk is encounted (except for WAVE and FMT chunks). The callback has been updated to include both the container (RIFF or Wave64) and the FMT chunk which contains information about the format of the data in the wave file. -Previously, there was no direct way to determine the container, and therefore no way discriminate against the different IDs in the chunk header (RIFF and +Previously, there was no direct way to determine the container, and therefore no way to discriminate against the different IDs in the chunk header (RIFF and Wave64 containers encode chunk ID's differently). The `container` parameter can be used to know which ID to use. Sometimes it can be useful to know the data format at the time the chunk callback is fired. A pointer to a `drwav_fmt` object is now passed into the chunk @@ -65,7 +65,7 @@ If you just want to quickly open and read the audio data in a single operation y ... - drwav_free(pSampleData); + drwav_free(pSampleData, NULL); ``` The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in this case), but you can still output the @@ -144,45 +144,44 @@ extern "C" { #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 4 +#define DRWAV_VERSION_REVISION 19 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include /* For size_t. */ -/* Sized types. Prefer built-in types. Fall back to stdint. */ -#ifdef _MSC_VER - #if defined(__clang__) +/* Sized types. */ +typedef signed char drwav_int8; +typedef unsigned char drwav_uint8; +typedef signed short drwav_int16; +typedef unsigned short drwav_uint16; +typedef signed int drwav_int32; +typedef unsigned int drwav_uint32; +#if defined(_MSC_VER) + typedef signed __int64 drwav_int64; + typedef unsigned __int64 drwav_uint64; +#else + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wlanguage-extension-token" - #pragma GCC diagnostic ignored "-Wlong-long" - #pragma GCC diagnostic ignored "-Wc++11-long-long" + #pragma GCC diagnostic ignored "-Wlong-long" + #if defined(__clang__) + #pragma GCC diagnostic ignored "-Wc++11-long-long" + #endif #endif - typedef signed __int8 drwav_int8; - typedef unsigned __int8 drwav_uint8; - typedef signed __int16 drwav_int16; - typedef unsigned __int16 drwav_uint16; - typedef signed __int32 drwav_int32; - typedef unsigned __int32 drwav_uint32; - typedef signed __int64 drwav_int64; - typedef unsigned __int64 drwav_uint64; - #if defined(__clang__) + typedef signed long long drwav_int64; + typedef unsigned long long drwav_uint64; + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif -#else - #include - typedef int8_t drwav_int8; - typedef uint8_t drwav_uint8; - typedef int16_t drwav_int16; - typedef uint16_t drwav_uint16; - typedef int32_t drwav_int32; - typedef uint32_t drwav_uint32; - typedef int64_t drwav_int64; - typedef uint64_t drwav_uint64; #endif -typedef drwav_uint8 drwav_bool8; -typedef drwav_uint32 drwav_bool32; -#define DRWAV_TRUE 1 -#define DRWAV_FALSE 0 +#if defined(__LP64__) || defined(_WIN64) || (defined(__x86_64__) && !defined(__ILP32__)) || defined(_M_X64) || defined(__ia64) || defined (_M_IA64) || defined(__aarch64__) || defined(__powerpc64__) + typedef drwav_uint64 drwav_uintptr; +#else + typedef drwav_uint32 drwav_uintptr; +#endif +typedef drwav_uint8 drwav_bool8; +typedef drwav_uint32 drwav_bool32; +#define DRWAV_TRUE 1 +#define DRWAV_FALSE 0 #if !defined(DRWAV_API) #if defined(DRWAV_DLL) @@ -288,7 +287,7 @@ typedef drwav_int32 drwav_result; #define DRWAV_SEQUENTIAL 0x00000001 DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_uint32* pRevision); -DRWAV_API const char* drwav_version_string(); +DRWAV_API const char* drwav_version_string(void); typedef enum { @@ -299,7 +298,8 @@ typedef enum typedef enum { drwav_container_riff, - drwav_container_w64 + drwav_container_w64, + drwav_container_rf64 } drwav_container; typedef struct @@ -420,8 +420,8 @@ Returns the number of bytes read + seeked. To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must be the total number of bytes you have read _plus_ seeked. -Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` you should use `id.fourcc`, -otherwise you should use `id.guid`. +Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should +use `id.fourcc`, otherwise you should use `id.guid`. The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the `DR_WAVE_FORMAT_*` identifiers. @@ -535,7 +535,7 @@ typedef struct /* The size in bytes of the data chunk. */ drwav_uint64 dataChunkDataSize; - /* The position in the stream of the first byte of the data chunk. This is used for seeking. */ + /* The position in the stream of the first data byte of the data chunk. This is used for seeking. */ drwav_uint64 dataChunkDataPos; /* The number of bytes remaining in the data chunk. */ @@ -674,6 +674,8 @@ bytes of the raw internal sample data. Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for reading sample data in a consistent format. +pBufferOut can be NULL in which case a seek will be performed. + Returns the number of bytes actually read. */ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); @@ -689,6 +691,8 @@ you have requested more PCM frames than can possibly fit in the output buffer. This function will only work when sample data is of a fixed size and uncompressed. If you are using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32(). + +pBufferOut can be NULL in which case a seek will be performed. */ DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); @@ -728,6 +732,8 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 frame /* Reads a chunk of audio data and converts it to signed 16-bit PCM samples. +pBufferOut can be NULL in which case a seek will be performed. + Returns the number of PCM frames actually read. If the return value is less than it means the end of the file has been reached. @@ -761,6 +767,8 @@ DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, siz /* Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. +pBufferOut can be NULL in which case a seek will be performed. + Returns the number of PCM frames actually read. If the return value is less than it means the end of the file has been reached. @@ -794,6 +802,8 @@ DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sa /* Reads a chunk of audio data and converts it to signed 32-bit PCM samples. +pBufferOut can be NULL in which case a seek will be performed. + Returns the number of PCM frames actually read. If the return value is less than it means the end of the file has been reached. @@ -872,8 +882,8 @@ Helper for initializing a writer which outputs data to a memory buffer. dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). -The buffer will remain allocated even after drwav_uninit() is called. Indeed, the buffer should not be -considered valid until after drwav_uninit() has been called anyway. +The buffer will remain allocated even after drwav_uninit() is called. The buffer should not be considered valid +until after drwav_uninit() has been called. */ DRWAV_API drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, const drwav_allocation_callbacks* pAllocationCallbacks); DRWAV_API drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, const drwav_allocation_callbacks* pAllocationCallbacks); @@ -943,6 +953,9 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); ************************************************************************************************************************************************************ ************************************************************************************************************************************************************/ #if defined(DR_WAV_IMPLEMENTATION) || defined(DRWAV_IMPLEMENTATION) +#ifndef dr_wav_c +#define dr_wav_c + #include #include /* For memcpy(), memset() */ #include /* For INT_MAX */ @@ -1008,6 +1021,8 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b); #else #define DRWAV_INLINE inline __attribute__((always_inline)) #endif +#elif defined(__WATCOMC__) + #define DRWAV_INLINE __inline #else #define DRWAV_INLINE #endif @@ -1063,7 +1078,7 @@ DRWAV_API void drwav_version(drwav_uint32* pMajor, drwav_uint32* pMinor, drwav_u } } -DRWAV_API const char* drwav_version_string() +DRWAV_API const char* drwav_version_string(void) { return DRWAV_VERSION_STRING; } @@ -1084,34 +1099,12 @@ you doing?! (Let me know, I'd be curious!) Second, you can adjust these by #defi static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; /* 66666972-912E-11CF-A5D6-28DB04C10000 */ static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 65766177-ACF3-11D3-8CD1-00C04F8EDB8A */ -static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ +/*static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A};*/ /* 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 74636166-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 61746164-ACF3-11D3-8CD1-00C04F8EDB8A */ static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; /* 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A */ -static DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) -{ - int i; - for (i = 0; i < 16; i += 1) { - if (a[i] != b[i]) { - return DRWAV_FALSE; - } - } - - return DRWAV_TRUE; -} - -static DRWAV_INLINE drwav_bool32 drwav__fourcc_equal(const drwav_uint8* a, const char* b) -{ - return - a[0] == b[0] && - a[1] == b[1] && - a[2] == b[2] && - a[3] == b[3]; -} - - static DRWAV_INLINE int drwav__is_little_endian(void) { @@ -1125,37 +1118,6 @@ static DRWAV_INLINE int drwav__is_little_endian(void) #endif } -static DRWAV_INLINE drwav_uint16 drwav__bytes_to_u16(const drwav_uint8* data) -{ - return (data[0] << 0) | (data[1] << 8); -} - -static DRWAV_INLINE drwav_int16 drwav__bytes_to_s16(const drwav_uint8* data) -{ - return (short)drwav__bytes_to_u16(data); -} - -static DRWAV_INLINE drwav_uint32 drwav__bytes_to_u32(const drwav_uint8* data) -{ - return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); -} - -static DRWAV_INLINE drwav_int32 drwav__bytes_to_s32(const drwav_uint8* data) -{ - return (drwav_int32)drwav__bytes_to_u32(data); -} - -static DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const drwav_uint8* data) -{ - return - ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | - ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); -} - -static DRWAV_INLINE drwav_int64 drwav__bytes_to_s64(const drwav_uint8* data) -{ - return (drwav_int64)drwav__bytes_to_u64(data); -} static DRWAV_INLINE void drwav__bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) { @@ -1224,14 +1186,15 @@ static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drwav_uint64)0xFF00000000000000) >> 56) | - ((n & (drwav_uint64)0x00FF000000000000) >> 40) | - ((n & (drwav_uint64)0x0000FF0000000000) >> 24) | - ((n & (drwav_uint64)0x000000FF00000000) >> 8) | - ((n & (drwav_uint64)0x00000000FF000000) << 8) | - ((n & (drwav_uint64)0x0000000000FF0000) << 24) | - ((n & (drwav_uint64)0x000000000000FF00) << 40) | - ((n & (drwav_uint64)0x00000000000000FF) << 56); + /* Weird "<< 32" bitshift is required for C89 because it doesn't support 64-bit constants. Should be optimized out by a good compiler. */ + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); #endif } @@ -1406,26 +1369,26 @@ static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampl } -static void* drwav__malloc_default(size_t sz, void* pUserData) +DRWAV_PRIVATE void* drwav__malloc_default(size_t sz, void* pUserData) { (void)pUserData; return DRWAV_MALLOC(sz); } -static void* drwav__realloc_default(void* p, size_t sz, void* pUserData) +DRWAV_PRIVATE void* drwav__realloc_default(void* p, size_t sz, void* pUserData) { (void)pUserData; return DRWAV_REALLOC(p, sz); } -static void drwav__free_default(void* p, void* pUserData) +DRWAV_PRIVATE void drwav__free_default(void* p, void* pUserData) { (void)pUserData; DRWAV_FREE(p); } -static void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; @@ -1443,7 +1406,7 @@ static void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_call return NULL; } -static void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; @@ -1473,7 +1436,7 @@ static void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, return NULL; } -static void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) { if (p == NULL || pAllocationCallbacks == NULL) { return; @@ -1485,7 +1448,7 @@ static void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks } -static drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { /* Copy. */ @@ -1509,23 +1472,23 @@ static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 fo formatTag == DR_WAVE_FORMAT_DVI_ADPCM; } -static unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) { return (unsigned int)(chunkSize % 2); } -static unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) { return (unsigned int)(chunkSize % 8); } -static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); -static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); -static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); -static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +DRWAV_PRIVATE drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) { - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { drwav_uint8 sizeInBytes[4]; if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { @@ -1536,7 +1499,7 @@ static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUser return DRWAV_INVALID_FILE; } - pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes); + pHeaderOut->sizeInBytes = drwav_bytes_to_u32(sizeInBytes); pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 8; } else { @@ -1550,7 +1513,7 @@ static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUser return DRWAV_INVALID_FILE; } - pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ + pHeaderOut->sizeInBytes = drwav_bytes_to_u64(sizeInBytes) - 24; /* <-- Subtract 24 because w64 includes the size of the header. */ pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 24; } @@ -1558,7 +1521,7 @@ static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUser return DRWAV_SUCCESS; } -static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { drwav_uint64 bytesRemainingToSeek = offset; while (bytesRemainingToSeek > 0) { @@ -1578,7 +1541,7 @@ static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 off return DRWAV_TRUE; } -static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { if (offset <= 0x7FFFFFFF) { return onSeek(pUserData, (int)offset, drwav_seek_origin_start); @@ -1606,7 +1569,7 @@ static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 } -static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) +DRWAV_PRIVATE drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) { drwav_chunk_header header; drwav_uint8 fmt[16]; @@ -1617,7 +1580,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe /* Skip non-fmt chunks. */ - while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav_fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } @@ -1631,12 +1594,12 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe /* Validation. */ - if (container == drwav_container_riff) { - if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { + if (container == drwav_container_riff || container == drwav_container_rf64) { + if (!drwav_fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; } } else { - if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) { + if (!drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT)) { return DRWAV_FALSE; } } @@ -1647,12 +1610,12 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe } *pRunningBytesReadOut += sizeof(fmt); - fmtOut->formatTag = drwav__bytes_to_u16(fmt + 0); - fmtOut->channels = drwav__bytes_to_u16(fmt + 2); - fmtOut->sampleRate = drwav__bytes_to_u32(fmt + 4); - fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8); - fmtOut->blockAlign = drwav__bytes_to_u16(fmt + 12); - fmtOut->bitsPerSample = drwav__bytes_to_u16(fmt + 14); + fmtOut->formatTag = drwav_bytes_to_u16(fmt + 0); + fmtOut->channels = drwav_bytes_to_u16(fmt + 2); + fmtOut->sampleRate = drwav_bytes_to_u32(fmt + 4); + fmtOut->avgBytesPerSec = drwav_bytes_to_u32(fmt + 8); + fmtOut->blockAlign = drwav_bytes_to_u16(fmt + 12); + fmtOut->bitsPerSample = drwav_bytes_to_u16(fmt + 14); fmtOut->extendedSize = 0; fmtOut->validBitsPerSample = 0; @@ -1670,7 +1633,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe bytesReadSoFar = 18; - fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize); + fmtOut->extendedSize = drwav_bytes_to_u16(fmt_cbSize); if (fmtOut->extendedSize > 0) { /* Simple validation. */ if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { @@ -1685,8 +1648,8 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe return DRWAV_FALSE; /* Expecting more data. */ } - fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0); - fmtOut->channelMask = drwav__bytes_to_u32(fmtext + 2); + fmtOut->validBitsPerSample = drwav_bytes_to_u16(fmtext + 0); + fmtOut->channelMask = drwav_bytes_to_u32(fmtext + 2); drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat); } else { if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { @@ -1716,7 +1679,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe } -static size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +DRWAV_PRIVATE size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) { size_t bytesRead; @@ -1729,7 +1692,7 @@ static size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBuf } #if 0 -static drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) { DRWAV_ASSERT(onSeek != NULL); DRWAV_ASSERT(pCursor != NULL); @@ -1750,7 +1713,7 @@ static drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int -static drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) { /* The bytes per frame is a bit ambiguous. It can be either be based on the bits per sample, or the block align. The way I'm doing it here @@ -1773,11 +1736,11 @@ DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { return pFMT->formatTag; } else { - return drwav__bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ + return drwav_bytes_to_u16(pFMT->subFormat); /* Only the first two bytes are required. */ } } -static drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onRead == NULL || onSeek == NULL) { return DRWAV_FALSE; @@ -1796,7 +1759,7 @@ static drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_see return DRWAV_TRUE; } -static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) { /* This function assumes drwav_preinit() has been called beforehand. */ @@ -1805,9 +1768,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, drwav_uint8 riff[4]; drwav_fmt fmt; unsigned short translatedFormatTag; - drwav_uint64 sampleCountFromFactChunk; drwav_bool32 foundDataChunk; - drwav_uint64 dataChunkSize; + drwav_uint64 dataChunkSize = 0; /* <-- Important! Don't explicitly set this to 0 anywhere else. Calculation of the size of the data chunk is performed in different paths depending on the container. */ + drwav_uint64 sampleCountFromFactChunk = 0; /* Same as dataChunkSize - make sure this is the only place this is initialized to 0. */ drwav_uint64 chunkSize; cursor = 0; @@ -1822,9 +1785,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for w64 it will start with "riff". */ - if (drwav__fourcc_equal(riff, "RIFF")) { + if (drwav_fourcc_equal(riff, "RIFF")) { pWav->container = drwav_container_riff; - } else if (drwav__fourcc_equal(riff, "riff")) { + } else if (drwav_fourcc_equal(riff, "riff")) { int i; drwav_uint8 riff2[12]; @@ -1840,12 +1803,14 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + } else if (drwav_fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; } else { return DRWAV_FALSE; /* Unknown or unsupported container. */ } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { drwav_uint8 chunkSizeBytes[4]; drwav_uint8 wave[4]; @@ -1854,15 +1819,21 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } - if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { - return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ + if (pWav->container == drwav_container_riff) { + if (drwav_bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; /* Chunk size should always be at least 36 bytes. */ + } + } else { + if (drwav_bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; /* Chunk size should always be set to -1/0xFFFFFFFF for RF64. The actual size is retrieved later. */ + } } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } - if (!drwav__fourcc_equal(wave, "WAVE")) { + if (!drwav_fourcc_equal(wave, "WAVE")) { return DRWAV_FALSE; /* Expecting "WAVE". */ } } else { @@ -1874,7 +1845,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } - if (drwav__bytes_to_u64(chunkSizeBytes) < 80) { + if (drwav_bytes_to_u64(chunkSizeBytes) < 80) { return DRWAV_FALSE; } @@ -1882,12 +1853,60 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } - if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) { + if (!drwav_guid_equal(wave, drwavGUID_W64_WAVE)) { return DRWAV_FALSE; } } + /* For RF64, the "ds64" chunk must come next, before the "fmt " chunk. */ + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + if (!drwav_fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; /* Expecting "ds64". */ + } + + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + + /* We don't care about the size of the RIFF chunk - skip it. */ + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + + + /* Next 8 bytes is the size of the "data" chunk. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav_bytes_to_u64(sizeBytes); + + + /* Next 8 bytes is the same count which we would usually derived from the FACT chunk if it was available. */ + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav_bytes_to_u64(sizeBytes); + + + /* Skip over everything else. */ + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } + + /* The next bytes should be the "fmt " chunk. */ if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { return DRWAV_FALSE; /* Failed to read the "fmt " chunk. */ @@ -1905,13 +1924,10 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, /* Translate the internal format. */ translatedFormatTag = fmt.formatTag; if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { - translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); + translatedFormatTag = drwav_bytes_to_u16(fmt.subFormat + 0); } - - sampleCountFromFactChunk = 0; - /* We need to enumerate over each chunk for two reasons: 1) The "data" chunk may not be the next one @@ -1920,7 +1936,6 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. */ foundDataChunk = DRWAV_FALSE; - dataChunkSize = 0; /* The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. */ for (;;) @@ -1956,13 +1971,15 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } chunkSize = header.sizeInBytes; - if (pWav->container == drwav_container_riff) { - if (drwav__fourcc_equal(header.id.fourcc, "data")) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + if (drwav_fourcc_equal(header.id.fourcc, "data")) { foundDataChunk = DRWAV_TRUE; - dataChunkSize = chunkSize; + if (pWav->container != drwav_container_rf64) { /* The data chunk size for RF64 will always be set to 0xFFFFFFFF here. It was set to it's true value earlier. */ + dataChunkSize = chunkSize; + } } } else { - if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA)) { foundDataChunk = DRWAV_TRUE; dataChunkSize = chunkSize; } @@ -1978,7 +1995,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, /* Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. */ if (pWav->container == drwav_container_riff) { - if (drwav__fourcc_equal(header.id.fourcc, "fact")) { + if (drwav_fourcc_equal(header.id.fourcc, "fact")) { drwav_uint32 sampleCount; if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { return DRWAV_FALSE; @@ -1999,8 +2016,8 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, sampleCountFromFactChunk = 0; } } - } else { - if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { + } else if (pWav->container == drwav_container_w64) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_FACT)) { if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; } @@ -2010,11 +2027,13 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } } + } else if (pWav->container == drwav_container_rf64) { + /* We retrieved the sample count from the ds64 chunk earlier so no need to do that here. */ } /* "smpl" chunk. */ - if (pWav->container == drwav_container_riff) { - if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + if (drwav_fourcc_equal(header.id.fourcc, "smpl")) { drwav_uint8 smplHeaderData[36]; /* 36 = size of the smpl header section, not including the loop data. */ if (chunkSize >= sizeof(smplHeaderData)) { drwav_uint64 bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplHeaderData, sizeof(smplHeaderData), &cursor); @@ -2023,15 +2042,15 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, if (bytesJustRead == sizeof(smplHeaderData)) { drwav_uint32 iLoop; - pWav->smpl.manufacturer = drwav__bytes_to_u32(smplHeaderData+0); - pWav->smpl.product = drwav__bytes_to_u32(smplHeaderData+4); - pWav->smpl.samplePeriod = drwav__bytes_to_u32(smplHeaderData+8); - pWav->smpl.midiUnityNotes = drwav__bytes_to_u32(smplHeaderData+12); - pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16); - pWav->smpl.smpteFormat = drwav__bytes_to_u32(smplHeaderData+20); - pWav->smpl.smpteOffset = drwav__bytes_to_u32(smplHeaderData+24); - pWav->smpl.numSampleLoops = drwav__bytes_to_u32(smplHeaderData+28); - pWav->smpl.samplerData = drwav__bytes_to_u32(smplHeaderData+32); + pWav->smpl.manufacturer = drwav_bytes_to_u32(smplHeaderData+0); + pWav->smpl.product = drwav_bytes_to_u32(smplHeaderData+4); + pWav->smpl.samplePeriod = drwav_bytes_to_u32(smplHeaderData+8); + pWav->smpl.midiUnityNotes = drwav_bytes_to_u32(smplHeaderData+12); + pWav->smpl.midiPitchFraction = drwav_bytes_to_u32(smplHeaderData+16); + pWav->smpl.smpteFormat = drwav_bytes_to_u32(smplHeaderData+20); + pWav->smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData+24); + pWav->smpl.numSampleLoops = drwav_bytes_to_u32(smplHeaderData+28); + pWav->smpl.samplerData = drwav_bytes_to_u32(smplHeaderData+32); for (iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) { drwav_uint8 smplLoopData[24]; /* 24 = size of a loop section in the smpl chunk. */ @@ -2039,12 +2058,12 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, chunkSize -= bytesJustRead; if (bytesJustRead == sizeof(smplLoopData)) { - pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0); - pWav->smpl.loops[iLoop].type = drwav__bytes_to_u32(smplLoopData+4); - pWav->smpl.loops[iLoop].start = drwav__bytes_to_u32(smplLoopData+8); - pWav->smpl.loops[iLoop].end = drwav__bytes_to_u32(smplLoopData+12); - pWav->smpl.loops[iLoop].fraction = drwav__bytes_to_u32(smplLoopData+16); - pWav->smpl.loops[iLoop].playCount = drwav__bytes_to_u32(smplLoopData+20); + pWav->smpl.loops[iLoop].cuePointId = drwav_bytes_to_u32(smplLoopData+0); + pWav->smpl.loops[iLoop].type = drwav_bytes_to_u32(smplLoopData+4); + pWav->smpl.loops[iLoop].start = drwav_bytes_to_u32(smplLoopData+8); + pWav->smpl.loops[iLoop].end = drwav_bytes_to_u32(smplLoopData+12); + pWav->smpl.loops[iLoop].fraction = drwav_bytes_to_u32(smplLoopData+16); + pWav->smpl.loops[iLoop].playCount = drwav_bytes_to_u32(smplLoopData+20); } else { break; /* Break from the smpl loop for loop. */ } @@ -2055,7 +2074,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } } } else { - if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { /* This path will be hit when a W64 WAV file contains a smpl chunk. I don't have a sample file to test this path, so a contribution is welcome to add support for this. @@ -2179,18 +2198,17 @@ DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_ } -static drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) { - drwav_uint32 dataSubchunkPaddingSize = drwav__chunk_padding_size_riff(dataChunkSize); - - if (dataChunkSize <= (0xFFFFFFFFUL - 36 - dataSubchunkPaddingSize)) { - return 36 + (drwav_uint32)(dataChunkSize + dataSubchunkPaddingSize); - } else { - return 0xFFFFFFFF; + drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 24 = "fmt " chunk. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; } + + return (drwav_uint32)chunkSize; /* Safe cast due to the clamp above. */ } -static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) { if (dataChunkSize <= 0xFFFFFFFFUL) { return (drwav_uint32)dataChunkSize; @@ -2199,20 +2217,81 @@ static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) } } -static drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) { drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; /* +24 because W64 includes the size of the GUID and size fields. */ } -static drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) { return 24 + dataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ } +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); /* 4 = "WAVE". 36 = "ds64" chunk. 24 = "fmt " chunk. */ + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } -static drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) + return chunkSize; +} + +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} + + +DRWAV_PRIVATE size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + /* Generic write. Assumes no byte reordering required. */ + return pWav->onWrite(pWav->pUserData, pData, dataSize); +} + +DRWAV_PRIVATE size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap16(value); + } + + return drwav__write(pWav, &value, 2); +} + +DRWAV_PRIVATE size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap32(value); + } + + return drwav__write(pWav, &value, 4); +} + +DRWAV_PRIVATE size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) +{ + DRWAV_ASSERT(pWav != NULL); + DRWAV_ASSERT(pWav->onWrite != NULL); + + if (!drwav__is_little_endian()) { + value = drwav__bswap64(value); + } + + return drwav__write(pWav, &value, 8); +} + + +DRWAV_PRIVATE drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onWrite == NULL) { return DRWAV_FALSE; @@ -2252,7 +2331,7 @@ static drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pF return DRWAV_TRUE; } -static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) { /* The function assumes drwav_preinit_write() was called beforehand. */ @@ -2284,67 +2363,75 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for /* "RIFF" chunk. */ if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; /* +36 = "RIFF"+[RIFF Chunk Size]+"WAVE" + [sizeof "fmt " chunk] */ - runningPos += pWav->onWrite(pWav->pUserData, "RIFF", 4); - runningPos += pWav->onWrite(pWav->pUserData, &chunkSizeRIFF, 4); - runningPos += pWav->onWrite(pWav->pUserData, "WAVE", 4); - } else { - drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ - runningPos += pWav->onWrite(pWav->pUserData, drwavGUID_W64_RIFF, 16); - runningPos += pWav->onWrite(pWav->pUserData, &chunkSizeRIFF, 8); - runningPos += pWav->onWrite(pWav->pUserData, drwavGUID_W64_WAVE, 16); + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; /* +28 = "WAVE" + [sizeof "fmt " chunk] */ + runningPos += drwav__write(pWav, "RIFF", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, "WAVE", 4); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); + runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always 0xFFFFFFFF for RF64. Set to a proper value in the "ds64" chunk. */ + runningPos += drwav__write(pWav, "WAVE", 4); } + + /* "ds64" chunk (RF64 only). */ + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; /* +8 for the ds64 header. */ + + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); /* Size of ds64. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); /* Size of RIFF. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); /* Size of DATA. Set to true value at the end. */ + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); /* Sample count. */ + runningPos += drwav__write_u32ne_to_le(pWav, 0); /* Table length. Always set to zero in our case since we're not doing any other chunks than "DATA". */ + } + + /* "fmt " chunk. */ - if (pFormat->container == drwav_container_riff) { + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { chunkSizeFMT = 16; - runningPos += pWav->onWrite(pWav->pUserData, "fmt ", 4); - runningPos += pWav->onWrite(pWav->pUserData, &chunkSizeFMT, 4); - } else { + runningPos += drwav__write(pWav, "fmt ", 4); + runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); + } else if (pFormat->container == drwav_container_w64) { chunkSizeFMT = 40; - runningPos += pWav->onWrite(pWav->pUserData, drwavGUID_W64_FMT, 16); - runningPos += pWav->onWrite(pWav->pUserData, &chunkSizeFMT, 8); + runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); } - runningPos += pWav->onWrite(pWav->pUserData, &pWav->fmt.formatTag, 2); - runningPos += pWav->onWrite(pWav->pUserData, &pWav->fmt.channels, 2); - runningPos += pWav->onWrite(pWav->pUserData, &pWav->fmt.sampleRate, 4); - runningPos += pWav->onWrite(pWav->pUserData, &pWav->fmt.avgBytesPerSec, 4); - runningPos += pWav->onWrite(pWav->pUserData, &pWav->fmt.blockAlign, 2); - runningPos += pWav->onWrite(pWav->pUserData, &pWav->fmt.bitsPerSample, 2); - - pWav->dataChunkDataPos = runningPos; + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.formatTag); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.channels); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.sampleRate); + runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); + runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); /* "data" chunk. */ if (pFormat->container == drwav_container_riff) { drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; - runningPos += pWav->onWrite(pWav->pUserData, "data", 4); - runningPos += pWav->onWrite(pWav->pUserData, &chunkSizeDATA, 4); - } else { - drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ - runningPos += pWav->onWrite(pWav->pUserData, drwavGUID_W64_DATA, 16); - runningPos += pWav->onWrite(pWav->pUserData, &chunkSizeDATA, 8); + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_w64) { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; /* +24 because W64 includes the size of the GUID and size fields. */ + runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); + runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); /* Always set to 0xFFFFFFFF for RF64. The true size of the data chunk is specified in the ds64 chunk. */ } - - /* Simple validation. */ - if (pFormat->container == drwav_container_riff) { - if (runningPos != 20 + chunkSizeFMT + 8) { - return DRWAV_FALSE; - } - } else { - if (runningPos != 40 + chunkSizeFMT + 24) { - return DRWAV_FALSE; - } - } - - /* Set some properties for the client's convenience. */ pWav->container = pFormat->container; pWav->channels = (drwav_uint16)pFormat->channels; pWav->sampleRate = pFormat->sampleRate; pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + pWav->dataChunkDataPos = runningPos; return DRWAV_TRUE; } @@ -2382,14 +2469,17 @@ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pF /* Casting totalSampleCount to drwav_int64 for VC6 compatibility. No issues in practice because nobody is going to exhaust the whole 63 bits. */ drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0); drwav_uint64 riffChunkSizeBytes; - drwav_uint64 fileSizeBytes; + drwav_uint64 fileSizeBytes = 0; if (pFormat->container == drwav_container_riff) { riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes); - fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ - } else { + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ + } else if (pFormat->container == drwav_container_w64) { riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes); + fileSizeBytes = (8 + riffChunkSizeBytes); /* +8 because WAV doesn't include the size of the ChunkID and ChunkSize fields. */ } return fileSizeBytes; @@ -2400,7 +2490,7 @@ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pF /* drwav_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */ #include -static drwav_result drwav_result_from_errno(int e) +DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) { switch (e) { @@ -2802,9 +2892,9 @@ static drwav_result drwav_result_from_errno(int e) } } -static drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif @@ -2816,7 +2906,7 @@ static drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char return DRWAV_INVALID_ARGS; } -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drwav_result_from_errno(err); @@ -2851,17 +2941,18 @@ _wfopen() isn't always available in all compilation environments. * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). * MinGW-64 (both 32- and 64-bit) seems to support it. * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRWAV_HAS_WFOPEN #endif #endif -static drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) { if (ppFile != NULL) { *ppFile = NULL; /* Safety. */ @@ -2944,17 +3035,17 @@ static drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const } -static size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +DRWAV_PRIVATE size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) { return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); } -static size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) { return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); } -static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) { return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } @@ -2965,7 +3056,7 @@ DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const } -static drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav_bool32 result; @@ -3012,7 +3103,7 @@ DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename } -static drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav_bool32 result; @@ -3031,7 +3122,7 @@ static drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFil return DRWAV_TRUE; } -static drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { @@ -3042,7 +3133,7 @@ static drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* fil return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); } -static drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { @@ -3093,7 +3184,7 @@ DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav #endif /* DR_WAV_NO_STDIO */ -static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { drwav* pWav = (drwav*)pUserData; size_t bytesRemaining; @@ -3114,7 +3205,7 @@ static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t by return bytesToRead; } -static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; DRWAV_ASSERT(pWav != NULL); @@ -3143,7 +3234,7 @@ static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_see return DRWAV_TRUE; } -static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) { drwav* pWav = (drwav*)pUserData; size_t bytesRemaining; @@ -3183,7 +3274,7 @@ static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_ return bytesToWrite; } -static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; DRWAV_ASSERT(pWav != NULL); @@ -3235,7 +3326,7 @@ DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_ } -static drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { if (ppData == NULL || pDataSize == NULL) { return DRWAV_FALSE; @@ -3295,7 +3386,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint32 paddingSize = 0; /* Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. */ - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); @@ -3303,7 +3394,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) if (paddingSize > 0) { drwav_uint64 paddingData = 0; - pWav->onWrite(pWav->pUserData, &paddingData, paddingSize); + drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ } /* @@ -3315,25 +3406,40 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) /* The "RIFF" chunk size. */ if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize); - pWav->onWrite(pWav->pUserData, &riffChunkSize, 4); - } - - /* the "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) { - drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); - pWav->onWrite(pWav->pUserData, &dataChunkSize, 4); - } - } else { - /* The "RIFF" chunk size. */ - if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { - drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); - pWav->onWrite(pWav->pUserData, &riffChunkSize, 8); + drwav__write_u32ne_to_le(pWav, riffChunkSize); } /* The "data" chunk size. */ - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { + drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); + drwav__write_u32ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_w64) { + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); - pWav->onWrite(pWav->pUserData, &dataChunkSize, 8); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } + } else if (pWav->container == drwav_container_rf64) { + /* We only need to update the ds64 chunk. The "RIFF" and "data" chunks always have their sizes set to 0xFFFFFFFF for RF64. */ + int ds64BodyPos = 12 + 8; + + /* The "RIFF" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + + /* The "data" chunk size. */ + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); } } } @@ -3365,7 +3471,7 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu { size_t bytesRead; - if (pWav == NULL || bytesToRead == 0 || pBufferOut == NULL) { + if (pWav == NULL || bytesToRead == 0) { return 0; } @@ -3373,7 +3479,41 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu bytesToRead = (size_t)pWav->bytesRemaining; } - bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + if (pBufferOut != NULL) { + bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + } else { + /* We need to seek. If we fail, we need to read-and-discard to make sure we get a good byte count. */ + bytesRead = 0; + while (bytesRead < bytesToRead) { + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > 0x7FFFFFFF) { + bytesToSeek = 0x7FFFFFFF; + } + + if (pWav->onSeek(pWav->pUserData, (int)bytesToSeek, drwav_seek_origin_current) == DRWAV_FALSE) { + break; + } + + bytesRead += bytesToSeek; + } + + /* When we get here we may need to read-and-discard some data. */ + while (bytesRead < bytesToRead) { + drwav_uint8 buffer[4096]; + size_t bytesSeeked; + size_t bytesToSeek = (bytesToRead - bytesRead); + if (bytesToSeek > sizeof(buffer)) { + bytesToSeek = sizeof(buffer); + } + + bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); + bytesRead += bytesSeeked; + + if (bytesSeeked < bytesToSeek) { + break; /* Reached the end. */ + } + } + } pWav->bytesRemaining -= bytesRead; return bytesRead; @@ -3384,8 +3524,9 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { drwav_uint32 bytesPerFrame; + drwav_uint64 bytesToRead; /* Intentionally uint64 instead of size_t so we can do a check that we're not reading too much on 32-bit builds. */ - if (pWav == NULL || framesToRead == 0 || pBufferOut == NULL) { + if (pWav == NULL || framesToRead == 0) { return 0; } @@ -3400,17 +3541,29 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 frames } /* Don't try to read more samples than can potentially fit in the output buffer. */ - if (framesToRead * bytesPerFrame > DRWAV_SIZE_MAX) { - framesToRead = DRWAV_SIZE_MAX / bytesPerFrame; + bytesToRead = framesToRead * bytesPerFrame; + if (bytesToRead > DRWAV_SIZE_MAX) { + bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; /* Round the number of bytes to read to a clean frame boundary. */ } - return drwav_read_raw(pWav, (size_t)(framesToRead * bytesPerFrame), pBufferOut) / bytesPerFrame; + /* + Doing an explicit check here just to make it clear that we don't want to be attempt to read anything if there's no bytes to read. There + *could* be a time where it evaluates to 0 due to overflowing. + */ + if (bytesToRead == 0) { + return 0; + } + + return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_le(pWav, framesToRead, pBufferOut); - drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, drwav_get_bytes_per_pcm_frame(pWav)/pWav->channels, pWav->translatedFormatTag); + + if (pBufferOut != NULL) { + drwav__bswap_samples(pBufferOut, framesRead*pWav->channels, drwav_get_bytes_per_pcm_frame(pWav)/pWav->channels, pWav->translatedFormatTag); + } return framesRead; } @@ -3426,7 +3579,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToR -DRWAV_API drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) +DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) { if (pWav->onWrite != NULL) { return DRWAV_FALSE; /* No seeking in write mode. */ @@ -3438,6 +3591,15 @@ DRWAV_API drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { pWav->compressed.iCurrentPCMFrame = 0; + + /* Cached data needs to be cleared for compressed formats. */ + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->msadpcm); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + DRWAV_ZERO_OBJECT(&pWav->ima); + } else { + DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ + } } pWav->bytesRemaining = pWav->dataChunkDataSize; @@ -3667,17 +3829,18 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesTo } -static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead = 0; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); - DRWAV_ASSERT(pBufferOut != NULL); /* TODO: Lots of room for optimization here. */ - while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + while (pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + /* If there are no cached frames we need to load a new block. */ if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { @@ -3689,9 +3852,9 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); pWav->msadpcm.predictor[0] = header[0]; - pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 1); - pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 3); - pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 5); + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 1); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 3); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 5); pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; @@ -3705,12 +3868,12 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 pWav->msadpcm.predictor[0] = header[0]; pWav->msadpcm.predictor[1] = header[1]; - pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 2); - pWav->msadpcm.delta[1] = drwav__bytes_to_s16(header + 4); - pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 6); - pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav__bytes_to_s16(header + 8); - pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 10); - pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav__bytes_to_s16(header + 12); + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav_bytes_to_s16(header + 4); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 6); + pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav_bytes_to_s16(header + 8); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 10); + pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav_bytes_to_s16(header + 12); pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; @@ -3722,12 +3885,15 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 /* Output anything that's cached. */ while (framesToRead > 0 && pWav->msadpcm.cachedFrameCount > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { - drwav_uint32 iSample = 0; - for (iSample = 0; iSample < pWav->channels; iSample += 1) { - pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; + if (pBufferOut != NULL) { + drwav_uint32 iSample = 0; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->msadpcm.cachedFrames[(drwav_countof(pWav->msadpcm.cachedFrames) - (pWav->msadpcm.cachedFrameCount*pWav->channels)) + iSample]; + } + + pBufferOut += pWav->channels; } - pBufferOut += pWav->channels; framesToRead -= 1; totalFramesRead += 1; pWav->compressed.iCurrentPCMFrame += 1; @@ -3735,7 +3901,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 } if (framesToRead == 0) { - return totalFramesRead; + break; } @@ -3845,17 +4011,36 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 } -static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead = 0; + drwav_uint32 iChannel; + + static drwav_int32 indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + static drwav_int32 stepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); - DRWAV_ASSERT(pBufferOut != NULL); /* TODO: Lots of room for optimization here. */ - while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + while (pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); /* This loop iteration will never get hit with framesToRead == 0 because it's asserted at the top, and we check for 0 inside the loop just below. */ + /* If there are no cached samples we need to load a new block. */ if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { @@ -3866,7 +4051,13 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra } pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); - pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + if (header[2] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); pWav->ima.stepIndex[0] = header[2]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; pWav->ima.cachedFrameCount = 1; @@ -3878,9 +4069,15 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra } pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); - pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + if (header[2] >= drwav_countof(stepTable) || header[6] >= drwav_countof(stepTable)) { + pWav->onSeek(pWav->pUserData, pWav->ima.bytesRemainingInBlock, drwav_seek_origin_current); + pWav->ima.bytesRemainingInBlock = 0; + return totalFramesRead; /* Invalid data. */ + } + + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); pWav->ima.stepIndex[0] = header[2]; - pWav->ima.predictor[1] = drwav__bytes_to_s16(header + 4); + pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); pWav->ima.stepIndex[1] = header[6]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; @@ -3891,12 +4088,14 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra /* Output anything that's cached. */ while (framesToRead > 0 && pWav->ima.cachedFrameCount > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { - drwav_uint32 iSample; - for (iSample = 0; iSample < pWav->channels; iSample += 1) { - pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; + if (pBufferOut != NULL) { + drwav_uint32 iSample; + for (iSample = 0; iSample < pWav->channels; iSample += 1) { + pBufferOut[iSample] = (drwav_int16)pWav->ima.cachedFrames[(drwav_countof(pWav->ima.cachedFrames) - (pWav->ima.cachedFrameCount*pWav->channels)) + iSample]; + } + pBufferOut += pWav->channels; } - pBufferOut += pWav->channels; framesToRead -= 1; totalFramesRead += 1; pWav->compressed.iCurrentPCMFrame += 1; @@ -3904,7 +4103,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra } if (framesToRead == 0) { - return totalFramesRead; + break; } /* @@ -3915,25 +4114,6 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra if (pWav->ima.bytesRemainingInBlock == 0) { continue; } else { - static drwav_int32 indexTable[16] = { - -1, -1, -1, -1, 2, 4, 6, 8, - -1, -1, -1, -1, 2, 4, 6, 8 - }; - - static drwav_int32 stepTable[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, - 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, - 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, - 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, - 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, - 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 - }; - - drwav_uint32 iChannel; - /* From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the left channel, 4 bytes for the right channel. @@ -4041,7 +4221,7 @@ static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) -static void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { unsigned int i; @@ -4093,7 +4273,7 @@ static void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t } } -static void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); @@ -4108,14 +4288,14 @@ static void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t } } -static drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { - drwav_uint32 bytesPerFrame; drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; + drwav_uint32 bytesPerFrame; /* Fast path. */ - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) { + if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -4142,12 +4322,17 @@ static drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 fra return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; + drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } @@ -4170,12 +4355,17 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 fr return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; + drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } @@ -4198,12 +4388,17 @@ static drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 fr return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; + drwav_uint32 bytesPerFrame; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } @@ -4228,10 +4423,14 @@ static drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 f DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { - if (pWav == NULL || framesToRead == 0 || pBufferOut == NULL) { + if (pWav == NULL || framesToRead == 0) { return 0; } + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + /* Don't try to read more samples than can potentially fit in the output buffer. */ if (framesToRead * pWav->channels * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16) / pWav->channels; @@ -4267,7 +4466,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 frame DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); - if (!drwav__is_little_endian()) { + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); } @@ -4277,7 +4476,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16le(drwav* pWav, drwav_uint64 fra DRWAV_API drwav_uint64 drwav_read_pcm_frames_s16be(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, framesToRead, pBufferOut); - if (drwav__is_little_endian()) { + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { drwav__bswap_samples_s16(pBufferOut, framesRead*pWav->channels); } @@ -4367,7 +4566,7 @@ DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, siz -static void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) { unsigned int i; @@ -4416,7 +4615,7 @@ static void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sample } } -static void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { unsigned int i; @@ -4435,12 +4634,12 @@ static void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampl } -static drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { return 0; } @@ -4463,7 +4662,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 fra return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't @@ -4471,6 +4670,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { @@ -4487,7 +4687,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't @@ -4495,6 +4695,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 fra */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { @@ -4511,7 +4712,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 fra return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -4545,11 +4746,12 @@ static drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 fr return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { return 0; } @@ -4572,12 +4774,12 @@ static drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 fr return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { return 0; } @@ -4602,10 +4804,14 @@ static drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 f DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { - if (pWav == NULL || framesToRead == 0 || pBufferOut == NULL) { + if (pWav == NULL || framesToRead == 0) { return 0; } + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + /* Don't try to read more samples than can potentially fit in the output buffer. */ if (framesToRead * pWav->channels * sizeof(float) > DRWAV_SIZE_MAX) { framesToRead = DRWAV_SIZE_MAX / sizeof(float) / pWav->channels; @@ -4641,7 +4847,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 frame DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); - if (!drwav__is_little_endian()) { + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); } @@ -4651,7 +4857,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32le(drwav* pWav, drwav_uint64 fra DRWAV_API drwav_uint64 drwav_read_pcm_frames_f32be(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_f32(pWav, framesToRead, pBufferOut); - if (drwav__is_little_endian()) { + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { drwav__bswap_samples_f32(pBufferOut, framesRead*pWav->channels); } @@ -4710,7 +4916,12 @@ DRWAV_API void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t samp } for (i = 0; i < sampleCount; ++i) { - double x = (double)(((drwav_int32)(((drwav_uint32)(pIn[i*3+0]) << 8) | ((drwav_uint32)(pIn[i*3+1]) << 16) | ((drwav_uint32)(pIn[i*3+2])) << 24)) >> 8); + double x; + drwav_uint32 a = ((drwav_uint32)(pIn[i*3+0]) << 8); + drwav_uint32 b = ((drwav_uint32)(pIn[i*3+1]) << 16); + drwav_uint32 c = ((drwav_uint32)(pIn[i*3+2]) << 24); + + x = (double)((drwav_int32)(a | b | c) >> 8); *pOut++ = (float)(x * 0.00000011920928955078125); } } @@ -4768,7 +4979,7 @@ DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sa -static void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { unsigned int i; @@ -4819,7 +5030,7 @@ static void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t } } -static void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); @@ -4835,7 +5046,7 @@ static void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t } -static drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -4869,7 +5080,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 fra return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't @@ -4877,6 +5088,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { @@ -4893,7 +5105,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { /* We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't @@ -4901,6 +5113,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 fra */ drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames_s16(pWav, drwav_min(framesToRead, drwav_countof(samples16)/pWav->channels), samples16); if (framesRead == 0) { @@ -4917,12 +5130,12 @@ static drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 fra return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { return 0; } @@ -4945,12 +5158,12 @@ static drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 fr return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { return 0; } @@ -4973,12 +5186,12 @@ static drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 fr return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; - drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { return 0; } @@ -5003,16 +5216,19 @@ static drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 f DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { - if (pWav == NULL || framesToRead == 0 || pBufferOut == NULL) { + if (pWav == NULL || framesToRead == 0) { return 0; } + if (pBufferOut == NULL) { + return drwav_read_pcm_frames(pWav, framesToRead, NULL); + } + /* Don't try to read more samples than can potentially fit in the output buffer. */ if (framesToRead * pWav->channels * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { framesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32) / pWav->channels; } - if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { return drwav_read_pcm_frames_s32__pcm(pWav, framesToRead, pBufferOut); } @@ -5043,7 +5259,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 frame DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); - if (!drwav__is_little_endian()) { + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_FALSE) { drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); } @@ -5053,7 +5269,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32le(drwav* pWav, drwav_uint64 fra DRWAV_API drwav_uint64 drwav_read_pcm_frames_s32be(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 framesRead = drwav_read_pcm_frames_s32(pWav, framesToRead, pBufferOut); - if (drwav__is_little_endian()) { + if (pBufferOut != NULL && drwav__is_little_endian() == DRWAV_TRUE) { drwav__bswap_samples_s32(pBufferOut, framesRead*pWav->channels); } @@ -5159,7 +5375,7 @@ DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, siz -static drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; drwav_int16* pSampleData; @@ -5201,7 +5417,7 @@ static drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned i return pSampleData; } -static float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; float* pSampleData; @@ -5243,7 +5459,7 @@ static float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* ch return pSampleData; } -static drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; drwav_int32* pSampleData; @@ -5555,45 +5771,59 @@ DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocation DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) { - return drwav__bytes_to_u16(data); + return (data[0] << 0) | (data[1] << 8); } DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) { - return drwav__bytes_to_s16(data); + return (short)drwav_bytes_to_u16(data); } DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) { - return drwav__bytes_to_u32(data); + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) { - return drwav__bytes_to_s32(data); + return (drwav_int32)drwav_bytes_to_u32(data); } DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) { - return drwav__bytes_to_u64(data); + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); } DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) { - return drwav__bytes_to_s64(data); + return (drwav_int64)drwav_bytes_to_u64(data); } DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) { - return drwav__guid_equal(a, b); + int i; + for (i = 0; i < 16; i += 1) { + if (a[i] != b[i]) { + return DRWAV_FALSE; + } + } + + return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) { - return drwav__fourcc_equal(a, b); + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; } +#endif /* dr_wav_c */ #endif /* DR_WAV_IMPLEMENTATION */ /* @@ -5784,6 +6014,58 @@ two different ways to initialize a drwav object. /* REVISION HISTORY ================ +v0.12.19 - 2021-02-21 + - Fix a warning due to referencing _MSC_VER when it is undefined. + - Minor improvements to the management of some internal state concerning the data chunk cursor. + +v0.12.18 - 2021-01-31 + - Clean up some static analysis warnings. + +v0.12.17 - 2021-01-17 + - Minor fix to sample code in documentation. + - Correctly qualify a private API as private rather than public. + - Code cleanup. + +v0.12.16 - 2020-12-02 + - Fix a bug when trying to read more bytes than can fit in a size_t. + +v0.12.15 - 2020-11-21 + - Fix compilation with OpenWatcom. + +v0.12.14 - 2020-11-13 + - Minor code clean up. + +v0.12.13 - 2020-11-01 + - Improve compiler support for older versions of GCC. + +v0.12.12 - 2020-09-28 + - Add support for RF64. + - Fix a bug in writing mode where the size of the RIFF chunk incorrectly includes the header section. + +v0.12.11 - 2020-09-08 + - Fix a compilation error on older compilers. + +v0.12.10 - 2020-08-24 + - Fix a bug when seeking with ADPCM formats. + +v0.12.9 - 2020-08-02 + - Simplify sized types. + +v0.12.8 - 2020-07-25 + - Fix a compilation warning. + +v0.12.7 - 2020-07-15 + - Fix some bugs on big-endian architectures. + - Fix an error in s24 to f32 conversion. + +v0.12.6 - 2020-06-23 + - Change drwav_read_*() to allow NULL to be passed in as the output buffer which is equivalent to a forward seek. + - Fix a buffer overflow when trying to decode invalid IMA-ADPCM files. + - Add include guard for the implementation section. + +v0.12.5 - 2020-05-27 + - Minor documentation fix. + v0.12.4 - 2020-05-16 - Replace assert() with DRWAV_ASSERT(). - Add compile-time and run-time version querying. diff --git a/raylib/external/glfw/include/GLFW/glfw3.h b/raylib/external/glfw/include/GLFW/glfw3.h index 4f5f360..7d9dee0 100644 --- a/raylib/external/glfw/include/GLFW/glfw3.h +++ b/raylib/external/glfw/include/GLFW/glfw3.h @@ -52,7 +52,7 @@ extern "C" { * This is the reference documentation for OpenGL and OpenGL ES context related * functions. For more task-oriented information, see the @ref context_guide. */ -/*! @defgroup vulkan Vulkan reference +/*! @defgroup vulkan Vulkan support reference * @brief Functions and types related to Vulkan. * * This is the reference documentation for Vulkan related functions and types. @@ -193,7 +193,38 @@ extern "C" { #endif /*__APPLE__*/ -#elif !defined(GLFW_INCLUDE_NONE) +#elif defined(GLFW_INCLUDE_GLU) + + #if defined(__APPLE__) + + #if defined(GLFW_INCLUDE_GLU) + #include + #endif + + #else /*__APPLE__*/ + + #if defined(GLFW_INCLUDE_GLU) + #include + #endif + + #endif /*__APPLE__*/ + +#elif !defined(GLFW_INCLUDE_NONE) && \ + !defined(__gl_h_) && \ + !defined(__gles1_gl_h_) && \ + !defined(__gles2_gl2_h_) && \ + !defined(__gles2_gl3_h_) && \ + !defined(__gles2_gl31_h_) && \ + !defined(__gles2_gl32_h_) && \ + !defined(__gl_glcorearb_h_) && \ + !defined(__gl2_h_) /*legacy*/ && \ + !defined(__gl3_h_) /*legacy*/ && \ + !defined(__gl31_h_) /*legacy*/ && \ + !defined(__gl32_h_) /*legacy*/ && \ + !defined(__glcorearb_h_) /*legacy*/ && \ + !defined(__GL_H__) /*non-standard*/ && \ + !defined(__gltypes_h_) /*non-standard*/ && \ + !defined(__glee_h_) /*non-standard*/ #if defined(__APPLE__) @@ -201,9 +232,6 @@ extern "C" { #define GL_GLEXT_LEGACY #endif #include - #if defined(GLFW_INCLUDE_GLU) - #include - #endif #else /*__APPLE__*/ @@ -211,9 +239,6 @@ extern "C" { #if defined(GLFW_INCLUDE_GLEXT) #include #endif - #if defined(GLFW_INCLUDE_GLU) - #include - #endif #endif /*__APPLE__*/ @@ -768,6 +793,33 @@ extern "C" { * [custom cursor](@ref cursor_custom). */ #define GLFW_CURSOR_UNAVAILABLE 0x0001000B +/*! @brief The requested feature is not provided by the platform. + * + * The requested feature is not provided by the platform, so GLFW is unable to + * implement it. The documentation for each function notes if it could emit + * this error. + * + * @analysis Platform or platform version limitation. The error can be ignored + * unless the feature is critical to the application. + * + * @par + * A function call that emits this error has no effect other than the error and + * updating any existing out parameters. + */ +#define GLFW_FEATURE_UNAVAILABLE 0x0001000C +/*! @brief The requested feature is not implemented for the platform. + * + * The requested feature has not yet been implemented in GLFW for this platform. + * + * @analysis An incomplete implementation of GLFW for this platform, hopefully + * fixed in a future release. The error can be ignored unless the feature is + * critical to the application. + * + * @par + * A function call that emits this error has no effect other than the error and + * updating any existing out parameters. + */ +#define GLFW_FEATURE_UNIMPLEMENTED 0x0001000D /*! @} */ /*! @addtogroup window @@ -843,6 +895,13 @@ extern "C" { */ #define GLFW_FOCUS_ON_SHOW 0x0002000C +/*! @brief Mouse input transparency window hint and attribute + * + * Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or + * [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib). + */ +#define GLFW_MOUSE_PASSTHROUGH 0x0002000D + /*! @brief Framebuffer bit depth hint. * * Framebuffer bit depth [hint](@ref GLFW_RED_BITS). @@ -960,12 +1019,17 @@ extern "C" { * and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib). */ #define GLFW_OPENGL_FORWARD_COMPAT 0x00022006 -/*! @brief OpenGL debug context hint and attribute. +/*! @brief Debug mode context hint and attribute. * - * OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and - * [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib). + * Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and + * [attribute](@ref GLFW_CONTEXT_DEBUG_attrib). */ -#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007 +#define GLFW_CONTEXT_DEBUG 0x00022007 +/*! @brief Legacy name for compatibility. + * + * This is an alias for compatibility with earlier versions. + */ +#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG /*! @brief OpenGL profile hint and attribute. * * OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and @@ -1047,6 +1111,14 @@ extern "C" { #define GLFW_EGL_CONTEXT_API 0x00036002 #define GLFW_OSMESA_CONTEXT_API 0x00036003 +#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001 +#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002 +#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003 +#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004 +#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005 +#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007 +#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008 + /*! @defgroup shapes Standard cursor shapes * @brief Standard system cursor shapes. * @@ -1163,6 +1235,11 @@ extern "C" { * Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS). */ #define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001 +/*! @brief ANGLE rendering backend init hint. + * + * ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint). + */ +#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002 /*! @brief macOS specific init hint. * * macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint). @@ -1406,7 +1483,7 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int); * @endcode * * @param[in] window The window that was maximized or restored. - * @param[in] iconified `GLFW_TRUE` if the window was maximized, or + * @param[in] maximized `GLFW_TRUE` if the window was maximized, or * `GLFW_FALSE` if it was restored. * * @sa @ref window_maximize @@ -1831,6 +1908,18 @@ typedef struct GLFWgamepadstate * bundle, if present. This can be disabled with the @ref * GLFW_COCOA_CHDIR_RESOURCES init hint. * + * @remark @macos This function will create the main menu and dock icon for the + * application. If GLFW finds a `MainMenu.nib` it is loaded and assumed to + * contain a menu bar. Otherwise a minimal menu bar is created manually with + * common commands like Hide, Quit and About. The About entry opens a minimal + * about dialog with information from the application's bundle. The menu bar + * and dock icon can be disabled entirely with the @ref GLFW_COCOA_MENUBAR init + * hint. + * + * @remark @x11 This function will set the `LC_CTYPE` category of the + * application locale according to the current environment if that category is + * still "C". This is because the "C" locale breaks Unicode text input. + * * @thread_safety This function must only be called from the main thread. * * @sa @ref intro_init @@ -1854,6 +1943,8 @@ GLFWAPI int glfwInit(void); * call this function, as it is called by @ref glfwInit before it returns * failure. * + * This function has no effect if GLFW is not initialized. + * * @errors Possible errors include @ref GLFW_PLATFORM_ERROR. * * @remark This function may be called before @ref glfwInit. @@ -2670,13 +2761,6 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value); * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * in the Mac Developer Library. * - * @remark @macos The first time a window is created the menu bar is created. - * If GLFW finds a `MainMenu.nib` it is loaded and assumed to contain a menu - * bar. Otherwise a minimal menu bar is created manually with common commands - * like Hide, Quit and About. The About entry opens a minimal about dialog - * with information from the application's bundle. Menu bar creation can be - * disabled entirely with the @ref GLFW_COCOA_MENUBAR init hint. - * * @remark @macos On OS X 10.10 and later the window frame will not be rendered * at full resolution on Retina displays unless the * [GLFW_COCOA_RETINA_FRAMEBUFFER](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint) @@ -2849,21 +2933,21 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title); * @param[in] images The images to create the icon from. This is ignored if * count is zero. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @pointer_lifetime The specified image data is copied before this function * returns. * - * @remark @macos The GLFW window has no icon, as it is not a document - * window, so this function does nothing. The dock icon will be the same as + * @remark @macos Regular windows do not have icons on macOS. This function + * will emit @ref GLFW_FEATURE_UNAVAILABLE. The dock icon will be the same as * the application bundle's icon. For more information on bundles, see the * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * in the Mac Developer Library. * * @remark @wayland There is no existing protocol to change an icon, the * window will thus inherit the one defined in the application's desktop file. - * This function always emits @ref GLFW_PLATFORM_ERROR. + * This function will emit @ref GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * @@ -2889,12 +2973,12 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* i * @param[out] ypos Where to store the y-coordinate of the upper-left corner of * the content area, or `NULL`. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @remark @wayland There is no way for an application to retrieve the global - * position of its windows, this function will always emit @ref - * GLFW_PLATFORM_ERROR. + * position of its windows. This function will emit @ref + * GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * @@ -2923,12 +3007,12 @@ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos); * @param[in] xpos The x-coordinate of the upper-left corner of the content area. * @param[in] ypos The y-coordinate of the upper-left corner of the content area. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * * @remark @wayland There is no way for an application to set the global - * position of its windows, this function will always emit @ref - * GLFW_PLATFORM_ERROR. + * position of its windows. This function will emit @ref + * GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * @@ -3240,8 +3324,11 @@ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* window); * @param[in] window The window to set the opacity for. * @param[in] opacity The desired opacity of the specified window. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). + * + * @remark @wayland There is no way to set an opacity factor for a window. + * This function will emit @ref GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * @@ -3408,11 +3495,11 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window); * * @param[in] window The window to give input focus. * - * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref - * GLFW_PLATFORM_ERROR. + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref + * GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks). * - * @remark @wayland It is not possible for an application to bring its windows - * to front, this function will always emit @ref GLFW_PLATFORM_ERROR. + * @remark @wayland It is not possible for an application to set the input + * focus. This function will emit @ref GLFW_FEATURE_UNAVAILABLE. * * @thread_safety This function must only be called from the main thread. * @@ -3576,6 +3663,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib); * [GLFW_FLOATING](@ref GLFW_FLOATING_attrib), * [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and * [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib). + * [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib) * * Some of these attributes are ignored for full screen windows. The new * value will take effect if the window is later made windowed. @@ -4164,7 +4252,7 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * If the mode is `GLFW_RAW_MOUSE_MOTION`, the value must be either `GLFW_TRUE` * to enable raw (unscaled and unaccelerated) mouse motion when the cursor is * disabled, or `GLFW_FALSE` to disable it. If raw motion is not supported, - * attempting to set this will emit @ref GLFW_PLATFORM_ERROR. Call @ref + * attempting to set this will emit @ref GLFW_FEATURE_UNAVAILABLE. Call @ref * glfwRawMouseMotionSupported to check for support. * * @param[in] window The window whose input mode to set. @@ -4174,7 +4262,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode); * @param[in] value The new value of the specified input mode. * * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref - * GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR. + * GLFW_INVALID_ENUM, @ref GLFW_PLATFORM_ERROR and @ref + * GLFW_FEATURE_UNAVAILABLE (see above). * * @thread_safety This function must only be called from the main thread. * diff --git a/raylib/external/glfw/src/cocoa_init.m b/raylib/external/glfw/src/cocoa_init.m index cbc9462..7cad8b8 100644 --- a/raylib/external/glfw/src/cocoa_init.m +++ b/raylib/external/glfw/src/cocoa_init.m @@ -447,7 +447,6 @@ static GLFWbool initializeTIS(void) - (void)applicationDidFinishLaunching:(NSNotification *)notification { - _glfw.ns.finishedLaunching = GLFW_TRUE; _glfwPlatformPostEmptyEvent(); [NSApp stop:nil]; } @@ -503,9 +502,6 @@ int _glfwPlatformInit(void) toTarget:_glfw.ns.helper withObject:nil]; - if (NSApp) - _glfw.ns.finishedLaunching = GLFW_TRUE; - [NSApplication sharedApplication]; _glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init]; @@ -555,9 +551,12 @@ int _glfwPlatformInit(void) return GLFW_FALSE; _glfwInitTimerNS(); - _glfwInitJoysticksNS(); _glfwPollMonitorsNS(); + + if (![[NSRunningApplication currentApplication] isFinishedLaunching]) + [NSApp run]; + return GLFW_TRUE; } // autoreleasepool @@ -605,7 +604,6 @@ void _glfwPlatformTerminate(void) free(_glfw.ns.clipboardString); _glfwTerminateNSGL(); - _glfwTerminateJoysticksNS(); } // autoreleasepool } diff --git a/raylib/external/glfw/src/cocoa_joystick.h b/raylib/external/glfw/src/cocoa_joystick.h index 9bf5cde..23d2b86 100644 --- a/raylib/external/glfw/src/cocoa_joystick.h +++ b/raylib/external/glfw/src/cocoa_joystick.h @@ -44,7 +44,3 @@ typedef struct _GLFWjoystickNS CFMutableArrayRef hats; } _GLFWjoystickNS; - -void _glfwInitJoysticksNS(void); -void _glfwTerminateJoysticksNS(void); - diff --git a/raylib/external/glfw/src/cocoa_joystick.m b/raylib/external/glfw/src/cocoa_joystick.m index 88636a8..4a64fb0 100644 --- a/raylib/external/glfw/src/cocoa_joystick.m +++ b/raylib/external/glfw/src/cocoa_joystick.m @@ -304,12 +304,10 @@ static void removeCallback(void* context, ////////////////////////////////////////////////////////////////////////// -////// GLFW internal API ////// +////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// -// Initialize joystick interface -// -void _glfwInitJoysticksNS(void) +GLFWbool _glfwPlatformInitJoysticks(void) { CFMutableArrayRef matching; const long usages[] = @@ -328,7 +326,7 @@ void _glfwInitJoysticksNS(void) if (!matching) { _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array"); - return; + return GLFW_FALSE; } for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++) @@ -383,26 +381,24 @@ void _glfwInitJoysticksNS(void) // Execute the run loop once in order to register any initially-attached // joysticks CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + return GLFW_TRUE; } -// Close all opened joystick handles -// -void _glfwTerminateJoysticksNS(void) +void _glfwPlatformTerminateJoysticks(void) { int jid; for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) closeJoystick(_glfw.joysticks + jid); - CFRelease(_glfw.ns.hidManager); - _glfw.ns.hidManager = NULL; + if (_glfw.ns.hidManager) + { + CFRelease(_glfw.ns.hidManager); + _glfw.ns.hidManager = NULL; + } } -////////////////////////////////////////////////////////////////////////// -////// GLFW platform API ////// -////////////////////////////////////////////////////////////////////////// - int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { if (mode & _GLFW_POLL_AXES) diff --git a/raylib/external/glfw/src/cocoa_monitor.m b/raylib/external/glfw/src/cocoa_monitor.m index 42f2dce..2bb8373 100644 --- a/raylib/external/glfw/src/cocoa_monitor.m +++ b/raylib/external/glfw/src/cocoa_monitor.m @@ -277,14 +277,20 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID) CFSTR("IOFBCurrentPixelCount"), kCFAllocatorDefault, kNilOptions); - if (!clockRef || !countRef) - break; uint32_t clock = 0, count = 0; - CFNumberGetValue(clockRef, kCFNumberIntType, &clock); - CFNumberGetValue(countRef, kCFNumberIntType, &count); - CFRelease(clockRef); - CFRelease(countRef); + + if (clockRef) + { + CFNumberGetValue(clockRef, kCFNumberIntType, &clock); + CFRelease(clockRef); + } + + if (countRef) + { + CFNumberGetValue(countRef, kCFNumberIntType, &count); + CFRelease(countRef); + } if (clock > 0 && count > 0) refreshRate = clock / (double) count; diff --git a/raylib/external/glfw/src/cocoa_platform.h b/raylib/external/glfw/src/cocoa_platform.h index 9a979af..01dcd87 100644 --- a/raylib/external/glfw/src/cocoa_platform.h +++ b/raylib/external/glfw/src/cocoa_platform.h @@ -85,16 +85,11 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta #include "posix_thread.h" #include "cocoa_joystick.h" #include "nsgl_context.h" -#include "egl_context.h" -#include "osmesa_context.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) -#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.view) -#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY - #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns #define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns @@ -121,6 +116,7 @@ typedef struct _GLFWwindowNS id layer; GLFWbool maximized; + GLFWbool occluded; GLFWbool retina; // Cached window properties to filter out duplicate events @@ -141,7 +137,6 @@ typedef struct _GLFWlibraryNS { CGEventSourceRef eventSource; id delegate; - GLFWbool finishedLaunching; GLFWbool cursorHidden; TISInputSourceRef inputSource; IOHIDManagerRef hidManager; diff --git a/raylib/external/glfw/src/cocoa_window.m b/raylib/external/glfw/src/cocoa_window.m index e12b5cd..81b22e2 100644 --- a/raylib/external/glfw/src/cocoa_window.m +++ b/raylib/external/glfw/src/cocoa_window.m @@ -322,6 +322,14 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; _glfwInputWindowFocus(window, GLFW_FALSE); } +- (void)windowDidChangeOcclusionState:(NSNotification* )notification +{ + if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible) + window->ns.occluded = GLFW_FALSE; + else + window->ns.occluded = GLFW_TRUE; +} + @end @@ -723,14 +731,24 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; else characters = (NSString*) string; - const NSUInteger length = [characters length]; - for (NSUInteger i = 0; i < length; i++) + NSRange range = NSMakeRange(0, [characters length]); + while (range.length) { - const unichar codepoint = [characters characterAtIndex:i]; - if ((codepoint & 0xff00) == 0xf700) - continue; + uint32_t codepoint = 0; - _glfwInputChar(window, codepoint, mods, plain); + if ([characters getBytes:&codepoint + maxLength:sizeof(codepoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:0 + range:range + remainingRange:&range]) + { + if (codepoint >= 0xf700 && codepoint <= 0xf7ff) + continue; + + _glfwInputChar(window, codepoint, mods, plain); + } } } @@ -884,9 +902,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - if (!createNativeWindow(window, wndconfig, fbconfig)) return GLFW_FALSE; @@ -901,6 +916,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, } else if (ctxconfig->source == GLFW_EGL_CONTEXT_API) { + // EGL implementation on macOS use CALayer* EGLNativeWindowType so we + // need to get the layer for EGL window surface creation. + [window->ns.view setWantsLayer:YES]; + window->ns.layer = [window->ns.view layer]; + if (!_glfwInitEGL()) return GLFW_FALSE; if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig)) @@ -972,7 +992,8 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) { - // Regular windows do not have icons + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Cocoa: Regular windows do not have icons on macOS"); } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) @@ -1350,6 +1371,13 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) } // autoreleasepool } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + @autoreleasepool { + [window->ns.object setIgnoresMouseEvents:enabled]; + } +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { @autoreleasepool { @@ -1366,6 +1394,8 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) { + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, + "Cocoa: Raw mouse motion not yet implemented"); } GLFWbool _glfwPlatformRawMouseMotionSupported(void) @@ -1377,9 +1407,6 @@ void _glfwPlatformPollEvents(void) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - for (;;) { NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny @@ -1399,9 +1426,6 @@ void _glfwPlatformWaitEvents(void) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - // I wanted to pass NO to dequeue:, and rely on PollEvents to // dequeue and send. For reasons not at all clear to me, passing // NO to dequeue: causes this method never to return. @@ -1420,9 +1444,6 @@ void _glfwPlatformWaitEventsTimeout(double timeout) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - NSDate* date = [NSDate dateWithTimeIntervalSinceNow:timeout]; NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:date @@ -1440,9 +1461,6 @@ void _glfwPlatformPostEmptyEvent(void) { @autoreleasepool { - if (!_glfw.ns.finishedLaunching) - [NSApp run]; - NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined location:NSMakePoint(0, 0) modifierFlags:0 @@ -1720,6 +1738,47 @@ const char* _glfwPlatformGetClipboardString(void) } // autoreleasepool } +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) +{ + if (_glfw.egl.ANGLE_platform_angle) + { + int type = 0; + + if (_glfw.egl.ANGLE_platform_angle_opengl) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) + type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; + } + + if (_glfw.egl.ANGLE_platform_angle_metal) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL) + type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE; + } + + if (type) + { + *attribs = calloc(3, sizeof(EGLint)); + (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; + (*attribs)[1] = type; + (*attribs)[2] = EGL_NONE; + return EGL_PLATFORM_ANGLE_ANGLE; + } + } + + return 0; +} + +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) +{ + return EGL_DEFAULT_DISPLAY; +} + +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) +{ + return window->ns.layer; +} + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface) diff --git a/raylib/external/glfw/src/egl_context.c b/raylib/external/glfw/src/egl_context.c index 706a792..533ed8e 100644 --- a/raylib/external/glfw/src/egl_context.c +++ b/raylib/external/glfw/src/egl_context.c @@ -303,6 +303,8 @@ static void destroyContextEGL(_GLFWwindow* window) GLFWbool _glfwInitEGL(void) { int i; + EGLint* attribs = NULL; + const char* extensions; const char* sonames[] = { #if defined(_GLFW_EGL_LIBRARY) @@ -395,7 +397,51 @@ GLFWbool _glfwInitEGL(void) return GLFW_FALSE; } - _glfw.egl.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY); + extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (extensions && eglGetError() == EGL_SUCCESS) + _glfw.egl.EXT_client_extensions = GLFW_TRUE; + + if (_glfw.egl.EXT_client_extensions) + { + _glfw.egl.EXT_platform_base = + _glfwStringInExtensionString("EGL_EXT_platform_base", extensions); + _glfw.egl.EXT_platform_x11 = + _glfwStringInExtensionString("EGL_EXT_platform_x11", extensions); + _glfw.egl.EXT_platform_wayland = + _glfwStringInExtensionString("EGL_EXT_platform_wayland", extensions); + _glfw.egl.ANGLE_platform_angle = + _glfwStringInExtensionString("EGL_ANGLE_platform_angle", extensions); + _glfw.egl.ANGLE_platform_angle_opengl = + _glfwStringInExtensionString("EGL_ANGLE_platform_angle_opengl", extensions); + _glfw.egl.ANGLE_platform_angle_d3d = + _glfwStringInExtensionString("EGL_ANGLE_platform_angle_d3d", extensions); + _glfw.egl.ANGLE_platform_angle_vulkan = + _glfwStringInExtensionString("EGL_ANGLE_platform_angle_vulkan", extensions); + _glfw.egl.ANGLE_platform_angle_metal = + _glfwStringInExtensionString("EGL_ANGLE_platform_angle_metal", extensions); + } + + if (_glfw.egl.EXT_platform_base) + { + _glfw.egl.GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC) + eglGetProcAddress("eglGetPlatformDisplayEXT"); + _glfw.egl.CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) + eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"); + } + + _glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs); + if (_glfw.egl.platform) + { + _glfw.egl.display = + eglGetPlatformDisplayEXT(_glfw.egl.platform, + _glfwPlatformGetEGLNativeDisplay(), + attribs); + } + else + _glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay()); + + free(attribs); + if (_glfw.egl.display == EGL_NO_DISPLAY) { _glfwInputError(GLFW_API_UNAVAILABLE, @@ -463,6 +509,7 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, EGLint attribs[40]; EGLConfig config; EGLContext share = NULL; + EGLNativeWindowType native; int index = 0; if (!_glfw.egl.display) @@ -588,23 +635,30 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window, } // Set up attributes for surface creation + index = 0; + + if (fbconfig->sRGB) { - int index = 0; - - if (fbconfig->sRGB) - { - if (_glfw.egl.KHR_gl_colorspace) - setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR); - } - - setAttrib(EGL_NONE, EGL_NONE); + if (_glfw.egl.KHR_gl_colorspace) + setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR); + } + + setAttrib(EGL_NONE, EGL_NONE); + + native = _glfwPlatformGetEGLNativeWindow(window); + // HACK: ANGLE does not implement eglCreatePlatformWindowSurfaceEXT + // despite reporting EGL_EXT_platform_base + if (_glfw.egl.platform && _glfw.egl.platform != EGL_PLATFORM_ANGLE_ANGLE) + { + window->context.egl.surface = + eglCreatePlatformWindowSurfaceEXT(_glfw.egl.display, config, native, attribs); + } + else + { + window->context.egl.surface = + eglCreateWindowSurface(_glfw.egl.display, config, native, attribs); } - window->context.egl.surface = - eglCreateWindowSurface(_glfw.egl.display, - config, - _GLFW_EGL_NATIVE_WINDOW, - attribs); if (window->context.egl.surface == EGL_NO_SURFACE) { _glfwInputError(GLFW_PLATFORM_ERROR, diff --git a/raylib/external/glfw/src/egl_context.h b/raylib/external/glfw/src/egl_context.h index 8bfb7db..9de424c 100644 --- a/raylib/external/glfw/src/egl_context.h +++ b/raylib/external/glfw/src/egl_context.h @@ -25,26 +25,10 @@ // //======================================================================== -#if defined(_GLFW_USE_EGLPLATFORM_H) - #include -#elif defined(_GLFW_WIN32) +#if defined(_GLFW_WIN32) #define EGLAPIENTRY __stdcall -typedef HDC EGLNativeDisplayType; -typedef HWND EGLNativeWindowType; -#elif defined(_GLFW_COCOA) - #define EGLAPIENTRY -typedef void* EGLNativeDisplayType; -typedef id EGLNativeWindowType; -#elif defined(_GLFW_X11) - #define EGLAPIENTRY -typedef Display* EGLNativeDisplayType; -typedef Window EGLNativeWindowType; -#elif defined(_GLFW_WAYLAND) - #define EGLAPIENTRY -typedef struct wl_display* EGLNativeDisplayType; -typedef struct wl_egl_window* EGLNativeWindowType; #else - #error "No supported EGL platform selected" + #define EGLAPIENTRY #endif #define EGL_SUCCESS 0x3000 @@ -106,6 +90,17 @@ typedef struct wl_egl_window* EGLNativeWindowType; #define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097 #define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0 #define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098 +#define EGL_PLATFORM_X11_EXT 0x31d5 +#define EGL_PLATFORM_WAYLAND_EXT 0x31d8 +#define EGL_PLATFORM_ANGLE_ANGLE 0x3202 +#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203 +#define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320d +#define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320e +#define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207 +#define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208 +#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450 +#define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489 +#define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348f typedef int EGLint; typedef unsigned int EGLBoolean; @@ -115,6 +110,9 @@ typedef void* EGLContext; typedef void* EGLDisplay; typedef void* EGLSurface; +typedef void* EGLNativeDisplayType; +typedef void* EGLNativeWindowType; + // EGL function pointer typedefs typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*); typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*); @@ -149,9 +147,10 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*); #define eglQueryString _glfw.egl.QueryString #define eglGetProcAddress _glfw.egl.GetProcAddress -#define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl -#define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl - +typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*); +typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay,EGLConfig,void*,const EGLint*); +#define eglGetPlatformDisplayEXT _glfw.egl.GetPlatformDisplayEXT +#define eglCreatePlatformWindowSurfaceEXT _glfw.egl.CreatePlatformWindowSurfaceEXT // EGL-specific per-context data // @@ -169,6 +168,7 @@ typedef struct _GLFWcontextEGL // typedef struct _GLFWlibraryEGL { + EGLenum platform; EGLDisplay display; EGLint major, minor; GLFWbool prefix; @@ -178,6 +178,15 @@ typedef struct _GLFWlibraryEGL GLFWbool KHR_gl_colorspace; GLFWbool KHR_get_all_proc_addresses; GLFWbool KHR_context_flush_control; + GLFWbool EXT_client_extensions; + GLFWbool EXT_platform_base; + GLFWbool EXT_platform_x11; + GLFWbool EXT_platform_wayland; + GLFWbool ANGLE_platform_angle; + GLFWbool ANGLE_platform_angle_opengl; + GLFWbool ANGLE_platform_angle_d3d; + GLFWbool ANGLE_platform_angle_vulkan; + GLFWbool ANGLE_platform_angle_metal; void* handle; @@ -198,6 +207,9 @@ typedef struct _GLFWlibraryEGL PFN_eglQueryString QueryString; PFN_eglGetProcAddress GetProcAddress; + PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT; + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT; + } _GLFWlibraryEGL; diff --git a/raylib/external/glfw/src/init.c b/raylib/external/glfw/src/init.c index 13baff7..a7b5af6 100644 --- a/raylib/external/glfw/src/init.c +++ b/raylib/external/glfw/src/init.c @@ -53,6 +53,7 @@ static GLFWerrorfun _glfwErrorCallback; static _GLFWinitconfig _glfwInitHints = { GLFW_TRUE, // hat buttons + GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend { GLFW_TRUE, // macOS menu bar GLFW_TRUE // macOS bundle chdir @@ -90,6 +91,7 @@ static void terminate(void) _glfw.mappingCount = 0; _glfwTerminateVulkan(); + _glfwPlatformTerminateJoysticks(); _glfwPlatformTerminate(); _glfw.initialized = GLFW_FALSE; @@ -191,6 +193,10 @@ void _glfwInputError(int code, const char* format, ...) strcpy(description, "The specified window has no context"); else if (code == GLFW_CURSOR_UNAVAILABLE) strcpy(description, "The specified cursor shape is unavailable"); + else if (code == GLFW_FEATURE_UNAVAILABLE) + strcpy(description, "The requested feature cannot be implemented for this platform"); + else if (code == GLFW_FEATURE_UNIMPLEMENTED) + strcpy(description, "The requested feature has not yet been implemented for this platform"); else strcpy(description, "ERROR: UNKNOWN GLFW ERROR"); } @@ -283,6 +289,9 @@ GLFWAPI void glfwInitHint(int hint, int value) case GLFW_JOYSTICK_HAT_BUTTONS: _glfwInitHints.hatButtons = value; return; + case GLFW_ANGLE_PLATFORM_TYPE: + _glfwInitHints.angleType = value; + return; case GLFW_COCOA_CHDIR_RESOURCES: _glfwInitHints.ns.chdir = value; return; diff --git a/raylib/external/glfw/src/input.c b/raylib/external/glfw/src/input.c index f616309..226f835 100644 --- a/raylib/external/glfw/src/input.c +++ b/raylib/external/glfw/src/input.c @@ -43,6 +43,22 @@ #define _GLFW_JOYSTICK_BUTTON 2 #define _GLFW_JOYSTICK_HATBIT 3 +// Initializes the platform joystick API if it has not been already +// +static GLFWbool initJoysticks(void) +{ + if (!_glfw.joysticksInitialized) + { + if (!_glfwPlatformInitJoysticks()) + { + _glfwPlatformTerminateJoysticks(); + return GLFW_FALSE; + } + } + + return _glfw.joysticksInitialized = GLFW_TRUE; +} + // Finds a mapping based on joystick GUID // static _GLFWmapping* findMapping(const char* guid) @@ -929,6 +945,9 @@ GLFWAPI int glfwJoystickPresent(int jid) return GLFW_FALSE; } + if (!initJoysticks()) + return GLFW_FALSE; + js = _glfw.joysticks + jid; if (!js->present) return GLFW_FALSE; @@ -954,6 +973,9 @@ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count) return NULL; } + if (!initJoysticks()) + return NULL; + js = _glfw.joysticks + jid; if (!js->present) return NULL; @@ -983,6 +1005,9 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count) return NULL; } + if (!initJoysticks()) + return NULL; + js = _glfw.joysticks + jid; if (!js->present) return NULL; @@ -1016,6 +1041,9 @@ GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count) return NULL; } + if (!initJoysticks()) + return NULL; + js = _glfw.joysticks + jid; if (!js->present) return NULL; @@ -1042,6 +1070,9 @@ GLFWAPI const char* glfwGetJoystickName(int jid) return NULL; } + if (!initJoysticks()) + return NULL; + js = _glfw.joysticks + jid; if (!js->present) return NULL; @@ -1067,6 +1098,9 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid) return NULL; } + if (!initJoysticks()) + return NULL; + js = _glfw.joysticks + jid; if (!js->present) return NULL; @@ -1112,6 +1146,10 @@ GLFWAPI void* glfwGetJoystickUserPointer(int jid) GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) { _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + + if (!initJoysticks()) + return NULL; + _GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun); return cbfun; } @@ -1191,6 +1229,9 @@ GLFWAPI int glfwJoystickIsGamepad(int jid) return GLFW_FALSE; } + if (!initJoysticks()) + return GLFW_FALSE; + js = _glfw.joysticks + jid; if (!js->present) return GLFW_FALSE; @@ -1216,6 +1257,9 @@ GLFWAPI const char* glfwGetGamepadName(int jid) return NULL; } + if (!initJoysticks()) + return NULL; + js = _glfw.joysticks + jid; if (!js->present) return NULL; @@ -1248,6 +1292,9 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state) return GLFW_FALSE; } + if (!initJoysticks()) + return GLFW_FALSE; + js = _glfw.joysticks + jid; if (!js->present) return GLFW_FALSE; diff --git a/raylib/external/glfw/src/internal.h b/raylib/external/glfw/src/internal.h index 6d7587c..48053cd 100644 --- a/raylib/external/glfw/src/internal.h +++ b/raylib/external/glfw/src/internal.h @@ -194,6 +194,9 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void); #error "No supported window creation API selected" #endif +#include "egl_context.h" +#include "osmesa_context.h" + // Constructs a version number string from the public header macros #define _GLFW_CONCAT_VERSION(m, n, r) #m "." #n "." #r #define _GLFW_MAKE_VERSION(m, n, r) _GLFW_CONCAT_VERSION(m, n, r) @@ -240,6 +243,7 @@ struct _GLFWerror struct _GLFWinitconfig { GLFWbool hatButtons; + int angleType; struct { GLFWbool menubar; GLFWbool chdir; @@ -266,6 +270,7 @@ struct _GLFWwndconfig GLFWbool maximized; GLFWbool centerCursor; GLFWbool focusOnShow; + GLFWbool mousePassthrough; GLFWbool scaleToMonitor; struct { GLFWbool retina; @@ -359,9 +364,9 @@ struct _GLFWcontext // This is defined in the context API's context.h _GLFW_PLATFORM_CONTEXT_STATE; // This is defined in egl_context.h - _GLFW_EGL_CONTEXT_STATE; + _GLFWcontextEGL egl; // This is defined in osmesa_context.h - _GLFW_OSMESA_CONTEXT_STATE; + _GLFWcontextOSMesa osmesa; }; // Window and context structure @@ -376,6 +381,7 @@ struct _GLFWwindow GLFWbool autoIconify; GLFWbool floating; GLFWbool focusOnShow; + GLFWbool mousePassthrough; GLFWbool shouldClose; void* userPointer; GLFWvidmode videoMode; @@ -533,6 +539,7 @@ struct _GLFWlibrary _GLFWmonitor** monitors; int monitorCount; + GLFWbool joysticksInitialized; _GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1]; _GLFWmapping* mappings; int mappingCount; @@ -581,9 +588,9 @@ struct _GLFWlibrary // This is defined in the platform's joystick.h _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE; // This is defined in egl_context.h - _GLFW_EGL_LIBRARY_CONTEXT_STATE; + _GLFWlibraryEGL egl; // This is defined in osmesa_context.h - _GLFW_OSMESA_LIBRARY_CONTEXT_STATE; + _GLFWlibraryOSMesa osmesa; }; // Global state shared between compilation units of GLFW @@ -626,6 +633,8 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) void _glfwPlatformSetClipboardString(const char* string); const char* _glfwPlatformGetClipboardString(void); +GLFWbool _glfwPlatformInitJoysticks(void); +void _glfwPlatformTerminateJoysticks(void); int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode); void _glfwPlatformUpdateGamepadGUID(char* guid); @@ -674,6 +683,7 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window); void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled); +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity); void _glfwPlatformPollEvents(void); @@ -681,6 +691,10 @@ void _glfwPlatformWaitEvents(void); void _glfwPlatformWaitEventsTimeout(double timeout); void _glfwPlatformPostEmptyEvent(void); +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs); +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void); +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window); + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions); int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, diff --git a/raylib/external/glfw/src/linux_joystick.c b/raylib/external/glfw/src/linux_joystick.c index 1f9b35f..122bc66 100644 --- a/raylib/external/glfw/src/linux_joystick.c +++ b/raylib/external/glfw/src/linux_joystick.c @@ -264,9 +264,50 @@ static int compareJoysticks(const void* fp, const void* sp) ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// -// Initialize joystick interface -// -GLFWbool _glfwInitJoysticksLinux(void) +void _glfwDetectJoystickConnectionLinux(void) +{ + if (_glfw.linjs.inotify <= 0) + return; + + ssize_t offset = 0; + char buffer[16384]; + const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer)); + + while (size > offset) + { + regmatch_t match; + const struct inotify_event* e = (struct inotify_event*) (buffer + offset); + + offset += sizeof(struct inotify_event) + e->len; + + if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0) + continue; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/dev/input/%s", e->name); + + if (e->mask & (IN_CREATE | IN_ATTRIB)) + openJoystickDevice(path); + else if (e->mask & IN_DELETE) + { + for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) + { + if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) + { + closeJoystick(_glfw.joysticks + jid); + break; + } + } + } + } +} + + +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + +GLFWbool _glfwPlatformInitJoysticks(void) { const char* dirname = "/dev/input"; @@ -320,9 +361,7 @@ GLFWbool _glfwInitJoysticksLinux(void) return GLFW_TRUE; } -// Close all opened joystick handles -// -void _glfwTerminateJoysticksLinux(void) +void _glfwPlatformTerminateJoysticks(void) { int jid; @@ -333,60 +372,16 @@ void _glfwTerminateJoysticksLinux(void) closeJoystick(js); } - regfree(&_glfw.linjs.regex); - if (_glfw.linjs.inotify > 0) { if (_glfw.linjs.watch > 0) inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch); close(_glfw.linjs.inotify); + regfree(&_glfw.linjs.regex); } } -void _glfwDetectJoystickConnectionLinux(void) -{ - if (_glfw.linjs.inotify <= 0) - return; - - ssize_t offset = 0; - char buffer[16384]; - const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer)); - - while (size > offset) - { - regmatch_t match; - const struct inotify_event* e = (struct inotify_event*) (buffer + offset); - - offset += sizeof(struct inotify_event) + e->len; - - if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0) - continue; - - char path[PATH_MAX]; - snprintf(path, sizeof(path), "/dev/input/%s", e->name); - - if (e->mask & (IN_CREATE | IN_ATTRIB)) - openJoystickDevice(path); - else if (e->mask & IN_DELETE) - { - for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) - { - if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0) - { - closeJoystick(_glfw.joysticks + jid); - break; - } - } - } - } -} - - -////////////////////////////////////////////////////////////////////////// -////// GLFW platform API ////// -////////////////////////////////////////////////////////////////////////// - int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { // Read all queued events (non-blocking) diff --git a/raylib/external/glfw/src/linux_joystick.h b/raylib/external/glfw/src/linux_joystick.h index 7373f13..05d5488 100644 --- a/raylib/external/glfw/src/linux_joystick.h +++ b/raylib/external/glfw/src/linux_joystick.h @@ -55,8 +55,5 @@ typedef struct _GLFWlibraryLinux GLFWbool dropped; } _GLFWlibraryLinux; - -GLFWbool _glfwInitJoysticksLinux(void); -void _glfwTerminateJoysticksLinux(void); void _glfwDetectJoystickConnectionLinux(void); diff --git a/raylib/external/glfw/src/mappings.h b/raylib/external/glfw/src/mappings.h index eda9a54..4c3f02f 100644 --- a/raylib/external/glfw/src/mappings.h +++ b/raylib/external/glfw/src/mappings.h @@ -61,94 +61,244 @@ const char* _glfwDefaultMappings[] = { "03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,", +"03000000c82d00002038000000000000,8bitdo,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d000011ab000000000000,8BitDo F30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00001038000000000000,8BitDo F30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000090000000000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000650000000000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:a4,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Windows,", +"03000000c82d00005106000000000000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000310000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,", +"03000000c82d00002028000000000000,8BitDo N30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00008010000000000000,8BitDo N30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,", +"03000000c82d00000190000000000000,8BitDo N30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00001590000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00006528000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00015900000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00065280000000000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", "03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", "03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000130000000000000,8BitDo SF30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000060000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000061000000000000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d000021ab000000000000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", "03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00003028000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000030000000000000,8BitDo SN30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000351000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00001290000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d000020ab000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00004028000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00006228000000000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000260000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000261000000000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00000031000000000000,8BitDo Wireless Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00001890000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,", +"03000000c82d00003032000000000000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Windows,", "03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,", -"030000008f0e00001200000000000000,Acme,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,", +"030000008f0e00001200000000000000,Acme GA-02,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,", +"03000000fa190000f0ff000000000000,Acteck AGJ-3200,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"030000006f0e00001413000000000000,Afterglow,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00000263000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00001101000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00001401000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00001402000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00001901000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00001a01000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000d62000001d57000000000000,Airflo PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000869800002400000000007801,Astro C40 TR,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"03000000d6200000e557000000000000,Batarang,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,", -"030000006b1400000055000000000000,bigben ps3padstreetnew,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000006f0e00003201000000000000,Battlefield 4 PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000d62000002a79000000000000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"03000000bc2000006012000000000000,Betop 2126F,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000bc2000000055000000000000,Betop BFM Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000bc2000006312000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000bc2000006321000000000000,BETOP CONTROLLER,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000bc2000006412000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000c01100000555000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000c01100000655000000000000,Betop Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000790000000700000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", +"03000000808300000300000000000000,Betop Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", +"030000006b1400000055000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000006b1400000103000000000000,Bigben PS3 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,", "0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,", "03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", -"030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"03000000457500000401000000000000,Cobra,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"030000005e040000a102000000000000,Controller (Xbox 360 Wireless Receiver for Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"030000005e040000ff02000000000000,Controller (Xbox One For Windows) - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"030000005e040000ea02000000000000,Controller (Xbox One For Windows) - Wireless,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", "03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,", "03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", +"03000000451300000830000000000000,Defender Game Racer X7,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000007d0400000840000000000000,Destroyer Tiltpad,+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b1,b:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,x:b0,y:b3,platform:Windows,", "03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", -"030000004f04000023b3000000000000,Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"03000000bd12000002e0000000000000,Dual USB Vibration Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows,", +"030000008f0e00000910000000000000,DualShock 2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Windows,", +"030000006f0e00003001000000000000,EA SPORTS PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000b80500000410000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,", +"03000000b80500000610000000000000,Elecom Gamepad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,", +"03000000120c0000f61c000000000000,Elite,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000008f0e00000f31000000000000,EXEQ,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,", "03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000006f0e00008401000000000000,Faceoff Deluxe+ Audio Wired Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00008001000000000000,Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000852100000201000000000000,FF-GP1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", -"030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,", "030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,", "030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,", +"03000000790000002201000000000000,Game Controller for PC,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"0300000066f700000100000000000000,Game VIB Joystick,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Windows,", "03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,", +"03000000790000004618000000000000,GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"03000000ac0500003d03000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000ac0500004d04000000000000,GameSir,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", "03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", -"03000000451300000010000000000000,Generic USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"03000000c01100000140000000000000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00000102000000007801,GameStop Xbox 360 Wired Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"030000009b2800003200000000000000,GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,", +"030000009b2800006000000000000000,GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:+a5,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:+a2,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Windows,", +"030000008305000009a0000000000000,Genius,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000008305000031b0000000000000,Genius Maxfire Blaze 3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"03000000451300000010000000000000,Genius Maxfire Grandias 12,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000005c1a00003330000000000000,Genius MaxFire Grandias 12V,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,", +"03000000300f00000b01000000000000,GGE909 Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", +"03000000f0250000c283000000000000,Gioteck,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000f025000021c1000000000000,Gioteck PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000f0250000c383000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000f0250000c483000000000000,Gioteck VX2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"030000007d0400000540000000000000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000001008000001e1000000000000,Havit HV-G60,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b0,platform:Windows,", "03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,", +"03000000632500002605000000000000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"030000000d0f00002d00000000000000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00005400000000000000,Hori Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00000900000000000000,Hori Pad 3 Turbo,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00004d00000000000000,Hori Pad A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00009200000000000000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00001600000000007803,HORI Real Arcade Pro EX-SE (Xbox 360),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,", +"030000000d0f00009c00000000000000,Hori TAC Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f0000c100000000000000,Horipad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00005500000000000000,Horipad 4 FPS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", -"030000000d0f00004d00000000000000,HORIPAD3 A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,", "030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,", "03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,", -"03000000b50700001403000000000000,IMPACT BLACK,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", -"030000006f0e00002401000000000000,INJUSTICE FightStick for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"03000000b50700001403000000000000,Impact Black,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", +"030000006f0e00002401000000000000,INJUSTICE FightStick PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"03000000ac0500002c02000000000000,IPEGA,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", "03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000491900000304000000000000,Ipega PG-9087 - Bluetooth Gamepad,+righty:+a5,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows,", +"030000006e0500000a20000000000000,JC-DUX60 ELECOM MMO Gamepad,a:b2,b:b3,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b14,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b15,righttrigger:b13,rightx:a3,righty:a4,start:b20,x:b0,y:b1,platform:Windows,", +"030000006e0500000520000000000000,JC-P301U,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,", +"030000006e0500000320000000000000,JC-U3613M (DInput),a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Windows,", +"030000006e0500000720000000000000,JC-W01U,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b1,platform:Windows,", +"030000007e0500000620000000000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows,", +"030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Windows,", +"030000007e0500000720000000000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,", +"030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,", +"03000000bd12000003c0000000000000,JY-P70UR,a:b1,b:b0,back:b5,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b11,righttrigger:b9,rightx:a3,righty:a2,start:b4,x:b3,y:b2,platform:Windows,", +"03000000242f00002d00000000000000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000242f00008a00000000000000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,", +"03000000790000000200000000000000,King PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", +"030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006d040000d2ca000000000000,Logitech Cordless Precision,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,", "030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006d0400001ac2000000000000,Logitech Precision Gamepad,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"030000006d0400000ac2000000000000,Logitech WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Windows,", +"03000000380700006652000000000000,Mad Catz C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", -"03000000380700008433000000000000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", -"03000000380700008483000000000000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b6,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"03000000380700008433000000000000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000380700008483000000000000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"03000000380700006252000000000000,Mad Catz Micro C.T.R.L.R,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", -"030000008305000031b0000000000000,MaxfireBlaze3,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000002a0600001024000000000000,Matricom,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,", "03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,", "03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", +"03000000242f00007300000000000000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,", +"0300000079000000d218000000000000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000d620000010a7000000000000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,", "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,", "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000790000002418000000000000,Mega Drive,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b2,start:b9,x:b3,y:b4,platform:Windows,", +"03000000380700006382000000000000,MLG GamePad PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000c62400002a89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000c62400002b89000000000000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000c62400001a89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000c62400001b89000000000000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000efbe0000edfe000000000000,Monect Virtual Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", +"03000000250900006688000000000000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,", +"030000006b140000010c000000000000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,", +"03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", "03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", -"030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,leftx:h0.6,lefty:h0.12,rightshoulder:b5,rightstick:a2,righttrigger:b7,rightx:h0.9,righty:h0.4,start:b9,x:b2,y:b3,platform:Windows,", +"030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,", +"03000000550900001472000000000000,NVIDIA Controller v01.04,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows,", +"030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,rightshoulder:b5,rightstick:a2,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows,", +"03000000782300000a10000000000000,Onlive Wireless Controller,a:b15,b:b14,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b11,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b13,y:b12,platform:Windows,", +"03000000d62000006d57000000000000,OPP PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006b14000001a1000000000000,Orange Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b2,y:b3,platform:Windows,", "03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,", "03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00000901000000000000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", +"030000004c050000da0c000000000000,PlayStation Classic Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Windows,", "03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", -"030000008f0e00007530000000000000,PS (R) Gamepad,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000d62000009557000000000000,Pro Elite PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000d62000009f31000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000d6200000c757000000000000,Pro Ex mini PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000632500002306000000000000,PS Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,", "03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,", -"03000000100800000100000000000000,PS1 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", -"03000000100800000300000000000000,PS2 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", +"03000000100800000100000000000000,PS1 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", +"030000008f0e00007530000000000000,PS1 Controller,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000100800000300000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", +"03000000250900008888000000000000,PS2 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,", +"03000000666600006706000000000000,PS2 Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Windows,", +"030000006b1400000303000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000009d0d00001330000000000000,PS2 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"03000000250900000500000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,", +"030000004c0500006802000000000000,PS3 Controller,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b10,lefttrigger:a3~,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:a4~,rightx:a2,righty:a5,start:b8,x:b3,y:b0,platform:Windows,", +"03000000632500007505000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,", -"030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,", -"03000000250900000500000000000000,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,", +"030000008f0e00001431000000000000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000003807000056a8000000000000,PS3 RF pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000004c050000e60c000000000000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,", "03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,", "03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,", @@ -157,185 +307,375 @@ const char* _glfwDefaultMappings[] = "03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"03000000321500000204000000000000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000321500000104000000000000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"03000000321500000507000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000321500000707000000000000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000321500000011000000000000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"03000000321500000009000000000000,Razer Serval,+lefty:+a2,-lefty:-a1,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,leftx:a0,rightshoulder:b5,rightstick:b9,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", "030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", -"030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", -"030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", -"030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", -"030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,", +"03000000bd12000013d0000000000000,Retrolink USB SEGA Saturn Classic,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows,", "0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,", "0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,", "030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", -"030000006f0e00001e01000000000000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006b140000020d000000000000,Revolution Pro Controller 2(1/2),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000006b140000130d000000000000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00001e01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00002801000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00002f01000000000000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", -"03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,platform:Windows,", +"03000000a30600000701000000000000,Saitek P220,a:b2,b:b3,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b5,x:b0,y:b1,platform:Windows,", +"03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b0,y:b1,platform:Windows,", "03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", "03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,", "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,", -"03000000300f00001101000000000000,saitek rumble pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", +"03000000a30600002106000000000000,Saitek PS1000,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", +"03000000a306000020f6000000000000,Saitek PS2700,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", +"03000000300f00001101000000000000,Saitek Rumble Pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", +"03000000730700000401000000000000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Windows,", "0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,", "030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,", +"030000005e0400008e02000000007801,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", "03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,", -"030000008f0e00000800000000000000,SpeedLink Strike FX Wireless,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000341a00000908000000000000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000008f0e00000800000000000000,SpeedLink Strike FX,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000c01100000591000000000000,Speedlink Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000d11800000094000000000000,Stadia Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:b12,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b11,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:Windows,", +"03000000110100001914000000000000,SteelSeries,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftstick:b13,lefttrigger:b6,leftx:a0,lefty:a1,rightstick:b14,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000381000001214000000000000,SteelSeries Free,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Windows,", +"03000000110100003114000000000000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000381000001814000000000000,SteelSeries Stratus XL,a:b0,b:b1,back:b18,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b19,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b2,y:b3,platform:Windows,", +"03000000790000001c18000000000000,STK-7024X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", "03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,", +"03000000d620000011a7000000000000,Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000457500002211000000000000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000004f04000007d0000000000000,T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000004f0400000ab1000000000000,T.16000M,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b10,x:b2,y:b3,platform:Windows,", "03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,", -"030000004f04000015b3000000000000,Thrustmaster Dual Analog 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,", +"030000004f04000015b3000000000000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,", +"030000004f04000023b3000000000000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000004f0400000ed0000000000000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,", "030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,", "03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,", +"03000000d62000006000000000000000,Tournament PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"030000005f140000c501000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000b80500000210000000000000,Trust Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"030000004f04000087b6000000000000,TWCS Throttle,dpdown:b8,dpleft:b9,dpright:b7,dpup:b6,leftstick:b5,lefttrigger:-a5,leftx:a0,lefty:a1,righttrigger:+a5,platform:Windows,", "03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", -"03000000380700006652000000000000,UnKnown,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,", +"030000006e0500001320000000000000,U4113,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000101c0000171c000000000000,uRage Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000300f00000701000000000000,USB 4-Axis 12-Button Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", +"03000000341a00002308000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"030000005509000000b4000000000000,USB gamepad,a:b10,b:b11,back:b5,dpdown:b1,dpleft:b2,dpright:b3,dpup:b0,guide:b14,leftshoulder:b8,leftstick:b6,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b7,righttrigger:a5,rightx:a2,righty:a3,start:b4,x:b12,y:b13,platform:Windows,", +"030000006b1400000203000000000000,USB gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"03000000790000000a00000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,", +"03000000f0250000c183000000000000,USB gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", +"03000000ff1100004133000000000000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,", "03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", +"03000000790000001a18000000000000,Venom,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,", "03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00000302000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"030000006f0e00000702000000000000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,", +"0300000034120000adbe000000000000,vJoy Device,a:b0,b:b1,back:b15,dpdown:b6,dpleft:b7,dpright:b8,dpup:b5,guide:b16,leftshoulder:b9,leftstick:b13,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b14,righttrigger:b12,rightx:+a3,righty:+a4,start:b4,x:b2,y:b3,platform:Windows,", +"030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:+a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:-a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"030000005e040000ff02000000007801,Xbox One Elite Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"030000005e040000130b000000000000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", +"03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", "03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,", +"03000000ac0500005b05000000000000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,", "03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,", "03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,", -"03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000790000004f18000000000000,ZD-T Android,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,", +"03000000120c0000101e000000000000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,", +"030000008f0e00000300000009010000,2In1 USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,", +"03000000c82d00000090000001000000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00001038000000010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000650000001000000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000c82d00005106000000010000,8BitDo M30 Gamepad,a:b1,b:b0,back:b10,guide:b2,leftshoulder:b6,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00001590000001000000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"030000003512000012ab000001000000,8BitDo NES30 Gamepad,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", "03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000190000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", "03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00001290000001000000,8BitDo SN30 Gamepad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000260000001000000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00000031000001000000,8BitDo Wireless Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00001890000001000000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", +"03000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a31,start:b11,x:b4,y:b3,platform:Mac OS X,", "03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000a00500003232000009010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,", +"03000000c62400001a89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b14,leftshoulder:b6,leftstick:b15,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b16,righttrigger:a4,rightx:a2,righty:a3,start:b13,x:b3,y:b4,platform:Mac OS X,", +"03000000c62400001b89000000010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000d62000002a79000000010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,", "03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,", +"03000000c01100000140000000010000,GameStop PS4 Fun Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000006f0e00000102000000000000,GameStop Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000007d0400000540000001010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000000d0f00002d00000000100000,Hori Fighting Commander 3 Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000000d0f00009200000000010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000000d0f0000ee00000000010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,", "03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,", "03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,", +"030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X,", +"03000000242f00002d00000007010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,", "030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000006d04000018c2000000010000,Logitech RumblePad 2 USB,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000380700008433000000010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000380700008483000000010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000242f00007300000000020000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Mac OS X,", +"0300000079000000d218000026010000,Mayflash Magic NS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,", +"03000000d620000010a7000003010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,", "03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,", "03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000005e0400002700000001010000,Microsoft SideWinder Plug & Play Game Pad,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,leftx:a0,lefty:a1,righttrigger:b5,x:b2,y:b3,platform:Mac OS X,", +"03000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X,", +"03000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000c62400002b89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000632500007505000000020000,NEOGEO mini PAD Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,start:b9,x:b2,y:b3,platform:Mac OS X,", "030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,", +"03000000d620000011a7000000020000,Nintendo Switch Core (Plus) Wired Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,", +"030000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,", +"03000000550900001472000025050000,NVIDIA Controller v01.04,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Mac OS X,", +"030000006f0e00000901000002010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,", -"030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,", +"030000004c050000da0c000000010000,Playstation Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", +"03000000d62000006dca000000010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,", +"030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,", "030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", -"030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"03000000321500000204000000010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000321500000104000000010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000321500000507000001010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", +"03000000321500000011000000010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000321500000009000000020000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,", +"030000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,", "0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000006b140000130d000000010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000c6240000fefa000000000000,Rock Candy Gamepad for PS3,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"03000000730700000401000000010000,Sanwa PlayOnline Mobile,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Mac OS X,", "03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,", "03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,", "030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,", +"0300000000f00000f100000000000000,SNES RetroPort,a:b2,b:b3,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,rightshoulder:b7,start:b6,x:b0,y:b1,platform:Mac OS X,", +"30000004c050000e60c0000000100000,Sony DualSense,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", -"030000005e0400008e02000001000000,Steam Virtual GamePad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"03000000d11800000094000000010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Mac OS X,", +"030000005e0400008e02000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,", "03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,", "03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,", "03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,", "03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,", +"03000000457500002211000000010000,SZMY-POWER PC Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,", +"030000004f0400000ed0000000020000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", "030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,", -"03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", +"03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,", "03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,", +"030000006f0e00000302000025040000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", +"030000006f0e00000702000003060000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000791d00000103000009010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,", "050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,", "050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,", "030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", -"030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000005e0400000a0b000000000000,Xbox Adaptive Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000005e040000050b000003090000,Xbox Elite Wireless Controller Series 2,a:b0,b:b1,back:b31,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b53,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", "030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", +"030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", "030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,", -"030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", -"030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", "030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,", +"030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,", +"030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,", "03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,", "03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", -"05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"03000000120c0000101e000000010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,", +"03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00001038000000010000,8Bitdo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux,", +"03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00000310000011010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux,", +"05000000c82d00008010000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux,", "03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", -"05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00000061000000010000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", +"030000003512000012ab000010010000,8Bitdo SFC30 GamePad,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux,", "05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", -"05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,", +"05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00001290000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00006228000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", +"030000005e0400008e02000020010000,8BitDo Wireless Adapter,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000c82d00000031000011010000,8BitDo Wireless Adapter,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", +"03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,", +"05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,", "05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,", -"030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,", +"030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e00003901000013020000,Afterglow Prismatic Wired Controller 048-007-NA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", +"030000007c1800000006000010010000,Alienware Dual Compatible Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Linux,", +"05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,", +"05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,", +"03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,", +"03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000c21100000791000011010000,Be1 GC101 Controller 1.03 mode,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"03000000c31100000791000011010000,Be1 GC101 GAMEPAD 1.03 mode,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"030000005e0400008e02000003030000,Be1 GC101 Xbox 360 Controller mode,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,", +"03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", "03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux,", "03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,", "03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", "03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,", "03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,", +"030000004f04000004b3000010010000,Dual Power 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,", "030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,", +"03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", "0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", -"030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", -"030000006f0e00001f01000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000007d0400000540000000010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,", "030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,", -"030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", +"03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,", "03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", +"03000000632500002605000010010000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", "030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,", "030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", +"030000000d0f0000c100000011010000,HORI CO. LTD. HORIPAD S,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", +"030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", +"030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,", +"030000000d0f00001600000000010000,Hori Real Arcade Pro.EX-SE (Xbox 360),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,", "030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000000d0f0000ee00000011010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,", +"03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,", "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,", +"03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux,", "03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,", +"0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000632500007505000011010000,Ipega PG-9099 - Bluetooth Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,", "03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,", +"03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", +"030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,", +"050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,", +"030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,", +"050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,", +"03000000242f00002d00000011010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"03000000242f00008a00000011010000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,", "030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", -"030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006d0400001ec2000019200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", -"030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,platform:Linux,", +"030000006d0400000ac2000010010000,Logitech Inc. WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux,", "030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,", "05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", @@ -344,125 +684,307 @@ const char* _glfwDefaultMappings[] = "03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,", "03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", -"03000000380700008433000011010000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", -"03000000380700008483000011010000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"03000000120c00000500000000010000,Manta Dualshock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,", "03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,", +"03000000790000004318000010010000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,", +"03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,", +"0300000079000000d218000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"03000000d620000010a7000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"0300000025090000e803000001010000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,", "03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,", +"030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,", "030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"050000005e040000050b000003090000,Microsoft X-Box One Elite 2 pad,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e040000dd02000003020000,Microsoft X-Box One pad (Firmware 2015),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", "030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", +"030000005e040000000b000008040000,Microsoft Xbox One Elite 2 pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e040000ea02000008040000,Microsoft Xbox One S pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000c62400001a53000000010000,Mini PE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,", +"05000000d6200000e589000001000000,Moga 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,", "05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,", +"05000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,", +"03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"05000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"05000000c62400001a89000000010000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,", +"030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", +"030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,", +"060000007e0500000820000000000000,Nintendo Combined Joy-Cons (joycond),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,", +"030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux,", +"03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b0,y:b3,platform:Linux,", "050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", +"050000007e0500000920000001800000,Nintendo Switch Pro Controller (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,", +"030000007e0500000920000011810000,Nintendo Switch Pro Controller Wired (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,", "050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,", "05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,", "03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", +"03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,", +"05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,", "03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"19000000010000000100000001010000,odroidgo2_joypad,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux,", +"19000000010000000200000011000000,odroidgo2_joypad_v11,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux,", "030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,", "05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,", "05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,", +"03000000830500005020000010010000,Padix Co. Ltd. Rockfire PSX/USB Bridge,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Linux,", +"03000000790000001c18000011010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"030000006f0e0000b802000001010000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e0000b802000013020000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e00008001000011010000,PDP CO. LTD. Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e00008701000011010000,PDP Rock Candy Wired Controller for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", +"030000006f0e0000a802000023020000,PDP Wired Controller for Xbox One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", +"030000006f0e00008501000011010000,PDP Wired Fight Pad Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"05000000491900000204000000000000,PG-9118,x:b76,a:b73,b:b74,y:b77,back:b83,start:b84,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b79,lefttrigger:b81,rightshoulder:b80,righttrigger:b82,leftstick:b86,rightstick:b87,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux,", +"0500000049190000030400001b010000,PG-9099,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,", +"03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"03000000c62400001a58000001010000,PowerA Xbox One Cabled,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,", -"030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", -"050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", -"030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", -"050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", +"030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", -"060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", -"05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", -"050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", +"050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", +"060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,", "030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", -"050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", -"050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", -"030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", -"050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", -"030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", -"030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", "030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"03000000c01100000140000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"050000004c050000c405000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,", +"030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,", +"030000009b2800003200000001010000,Raphnet Technologies GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,", +"030000009b2800006000000001010000,Raphnet Technologies GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,", "030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,", "030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", -"030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000321500000810000011010000,Razer Panthera Evo Arcade Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,", -"0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,", -"0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,", +"0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux,", +"0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,", "030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", -"030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", -"030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", -"03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,", +"03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b0,y:b1,platform:Linux,", "03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,", +"03000000300f00001201000010010000,Saitek P380,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,", "03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,", "03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,", "03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", +"03000000a306000020f6000011010000,Saitek PS2700 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,", +"03000000a30600001005000000010000,Saitek Saitek P150,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b2,righttrigger:b5,x:b3,y:b4,platform:Linux,", +"03000000d81d00000e00000010010000,Savior,a:b0,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b9,x:b4,y:b5,platform:Linux,", "03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,", "03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"03000000bc2000000055000010010000,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,", "030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", -"03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", -"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", +"03000000d11800000094000011010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", "03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", -"05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", -"03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", +"03000000de2800000211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,", "03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", +"03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,", +"03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", +"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", +"05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,", "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000381000003114000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000003b07000004a1000000010000,Suncom SFX Plus for USB,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Linux,", "03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,", +"0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,", +"03000000457500002211000010010000,SZMY-POWER CO. LTD. GAMEPAD,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"030000008f0e00000d31000010010000,SZMY-POWER CO. LTD. GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", "030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,", "030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,", "030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000b50700000399000000010000,Thrustmaster Firestorm Digital 2,a:b2,b:b4,back:b11,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b0,righttrigger:b9,start:b1,x:b3,y:b5,platform:Linux,", +"030000004f04000003b3000010010000,Thrustmaster Firestorm Dual Analog 2,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b9,rightx:a2,righty:a3,x:b1,y:b3,platform:Linux,", "030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,", +"030000004f04000026b3000002040000,Thrustmaster Gamepad GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000c6240000025b000002020000,Thrustmaster GPX Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", "030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000004f04000007d0000000010000,Thrustmaster T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,", +"030000004f04000012b3000010010000,Thrustmaster vibrating gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,", "03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,", "03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,", +"030000005e0400008e02000070050000,Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000c01100000591000011010000,Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", "03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,", +"03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,", "03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,", +"030000006f0e00000302000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", +"030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,", "05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,", -"030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"03000000791d00000103000010010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", +"050000000d0f0000f600000001000000,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", "030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", -"030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,", +"030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"050000005e040000050b000002090000,Xbox One Elite Series 2,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"030000005e040000ea02000000000000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"030000005e040000ea02000001030000,Xbox One Wireless Controller (Model 1708),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e040000120b000001050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", +"030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"050000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,", +"030000005e0400008e02000000010000,xbox360 Wireless EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,", "03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,", +"03000000ac0500005b05000010010000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,", "05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,", "03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,", "03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,", +"05000000c82d000006500000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a4,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000051060000ffff3f00,8BitDo M30 Gamepad,a:b1,b:b0,back:b4,guide:b17,leftshoulder:b9,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000015900000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000065280000ffff3f00,8BitDo N30 Pro 2,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"050000000220000000900000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"050000002038000009000000ffff3f00,8BitDo NES30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000000600000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000000610000ffff3f00,8BitDo SF30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000012900000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000062280000ffff3f00,8BitDo SN30 Gamepad,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000001600000ffff3f00,8BitDo SN30 Pro,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000002600000ffff0f00,8BitDo SN30 Pro+,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b17,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"050000002028000009000000ffff3f00,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,platform:Android,", +"050000003512000020ab000000780f00,8BitDo SNES30 Gamepad,a:b21,b:b20,back:b30,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b26,rightshoulder:b27,start:b31,x:b24,y:b23,platform:Android,", +"05000000c82d000018900000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,", +"05000000c82d000030320000ffff0f00,8BitDo Zero 2,a:b1,b:b0,back:b4,leftshoulder:b9,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b3,y:b2,platform:Android,", +"05000000bc20000000550000ffff3f00,GameSir G3w,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,", +"0500000031366332860c44aadfff0f00,GS Gamepad,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:b15,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b16,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"0500000083050000602000000ffe0000,iBuffalo SNES Controller,a:b1,b:b0,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b15,rightshoulder:b16,start:b10,x:b3,y:b2,platform:Android,", "64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,", -"61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", -"4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"7573622067616d657061642020202020,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Android,", +"050000007e05000009200000ffff0f00,Nintendo Switch Pro Controller,a:b0,b:b1,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b16,x:b17,y:b2,platform:Android,", "37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000005509000003720000cf7f3f00,NVIDIA Controller v01.01,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000005509000010720000ffff3f00,NVIDIA Controller v01.03,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000005509000014720000df7f3f00,NVIDIA Controller v01.04,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,", +"050000004c05000068020000dfff3f00,PS3 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"030000004c050000cc09000000006800,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000004c050000c4050000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,", +"050000004c050000c4050000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000004c050000cc090000fffe3f00,PS4 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,", +"050000004c050000cc090000ffff3f00,PS4 Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", "35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,", +"050000004c050000e60c0000fffe3f00,PS5 Controller,a:b1,b:b17,back:b15,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,", +"62653861643333663663383332396665,Razer Kishi,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000003215000005070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000003215000007070000ffff3f00,Razer Raiju Mobile,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000003215000000090000bf7f3f00,Razer Serval,a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,", +"32633532643734376632656664383733,Sony DualSense,a:b1,b:b19,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android,", +"61303162353165316365336436343139,Sony DualSense,a:b1,b:b19,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a5,start:b18,x:b0,y:b2,platform:Android,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,", +"05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,", +"050000004f0400000ed00000fffe3f00,ThrustMaster eSwap PRO Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", "5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,", +"30306539356238653637313730656134,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b15,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b6,righttrigger:b10,rightx:a2,righty:a3,start:b18,x:b19,y:b2,platform:Android,", +"050000005e040000fd020000ff7f3f00,Xbox One S Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000005e040000e00200000ffe3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b17,y:b2,platform:Android,", +"050000005e040000fd020000ffff3f00,Xbox One Wireless Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000005e040000130b0000ffff3f00,Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"65633038363832353634653836396239,Xbox Series Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,", +"050000005e04000091020000ff073f00,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Android,", "34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,", +"050000001727000044310000ffff3f00,XiaoMi Game Controller,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a6,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Android,", +"05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,", +"05000000ac050000010000004f066d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,", +"05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,platform:iOS,", +"05000000ac05000001000000df076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,", +"05000000ac05000001000000ff076d01,*,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,", +"05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS,", +"05000000ac050000020000004f066d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,platform:iOS,", "4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,", "4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,", +"050000004c050000cc090000df070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,", +"050000004c050000cc090000ff070000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,", +"050000004c050000cc090000ff870001,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,touchpad:b11,x:b2,y:b3,platform:iOS,", +"05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,", +"05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,platform:iOS,", "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,", +"05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,", +"050000005e040000050b0000ff070001,Xbox Elite Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,", +"050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,platform:iOS,", +"050000005e040000e0020000ff070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b10,x:b2,y:b3,platform:iOS,", "78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", "78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,", diff --git a/raylib/external/glfw/src/nsgl_context.m b/raylib/external/glfw/src/nsgl_context.m index e011fa5..3f73f7a 100644 --- a/raylib/external/glfw/src/nsgl_context.m +++ b/raylib/external/glfw/src/nsgl_context.m @@ -51,7 +51,7 @@ static void swapBuffersNSGL(_GLFWwindow* window) // HACK: Simulate vsync with usleep as NSGL swap interval does not apply to // windows with a non-visible occlusion state - if (!([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)) + if (window->ns.occluded) { int interval = 0; [window->context.nsgl.object getValues:&interval diff --git a/raylib/external/glfw/src/null_init.c b/raylib/external/glfw/src/null_init.c index 20c76dc..57aafd5 100644 --- a/raylib/external/glfw/src/null_init.c +++ b/raylib/external/glfw/src/null_init.c @@ -29,6 +29,8 @@ #include "internal.h" +#include + ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// @@ -37,11 +39,14 @@ int _glfwPlatformInit(void) { _glfwInitTimerPOSIX(); + _glfwPollMonitorsNull(); + return GLFW_TRUE; } void _glfwPlatformTerminate(void) { + free(_glfw.null.clipboardString); _glfwTerminateOSMesa(); } diff --git a/raylib/external/glfw/src/null_joystick.c b/raylib/external/glfw/src/null_joystick.c index 36c18aa..27756a6 100644 --- a/raylib/external/glfw/src/null_joystick.c +++ b/raylib/external/glfw/src/null_joystick.c @@ -33,6 +33,15 @@ ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// +GLFWbool _glfwPlatformInitJoysticks(void) +{ + return GLFW_TRUE; +} + +void _glfwPlatformTerminateJoysticks(void) +{ +} + int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { return GLFW_FALSE; diff --git a/raylib/external/glfw/src/null_monitor.c b/raylib/external/glfw/src/null_monitor.c index 0a7fe06..8301eb3 100644 --- a/raylib/external/glfw/src/null_monitor.c +++ b/raylib/external/glfw/src/null_monitor.c @@ -29,6 +29,37 @@ #include "internal.h" +#include +#include +#include + +// The the sole (fake) video mode of our (sole) fake monitor +// +static GLFWvidmode getVideoMode(void) +{ + GLFWvidmode mode; + mode.width = 1920; + mode.height = 1080; + mode.redBits = 8; + mode.greenBits = 8; + mode.blueBits = 8; + mode.refreshRate = 60; + return mode; +} + +////////////////////////////////////////////////////////////////////////// +////// GLFW internal API ////// +////////////////////////////////////////////////////////////////////////// + +void _glfwPollMonitorsNull(void) +{ + const float dpi = 141.f; + const GLFWvidmode mode = getVideoMode(); + _GLFWmonitor* monitor = _glfwAllocMonitor("Null SuperNoop 0", + (int) (mode.width * 25.4f / dpi), + (int) (mode.height * 25.4f / dpi)); + _glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_FIRST); +} ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// @@ -36,10 +67,15 @@ void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor) { + _glfwFreeGammaArrays(&monitor->null.ramp); } void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos) { + if (xpos) + *xpos = 0; + if (ypos) + *ypos = 0; } void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, @@ -55,23 +91,69 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos, int* width, int* height) { + const GLFWvidmode mode = getVideoMode(); + + if (xpos) + *xpos = 0; + if (ypos) + *ypos = 10; + if (width) + *width = mode.width; + if (height) + *height = mode.height - 10; } GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* found) { - return NULL; + GLFWvidmode* mode = calloc(1, sizeof(GLFWvidmode)); + *mode = getVideoMode(); + *found = 1; + return mode; } void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode) { + *mode = getVideoMode(); } GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) { - return GLFW_FALSE; + if (!monitor->null.ramp.size) + { + _glfwAllocGammaArrays(&monitor->null.ramp, 256); + + for (unsigned int i = 0; i < monitor->null.ramp.size; i++) + { + const float gamma = 2.2f; + float value; + value = i / (float) (monitor->null.ramp.size - 1); + value = powf(value, 1.f / gamma) * 65535.f + 0.5f; + value = _glfw_fminf(value, 65535.f); + + monitor->null.ramp.red[i] = (unsigned short) value; + monitor->null.ramp.green[i] = (unsigned short) value; + monitor->null.ramp.blue[i] = (unsigned short) value; + } + } + + _glfwAllocGammaArrays(ramp, monitor->null.ramp.size); + memcpy(ramp->red, monitor->null.ramp.red, sizeof(short) * ramp->size); + memcpy(ramp->green, monitor->null.ramp.green, sizeof(short) * ramp->size); + memcpy(ramp->blue, monitor->null.ramp.blue, sizeof(short) * ramp->size); + return GLFW_TRUE; } void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) { + if (monitor->null.ramp.size != ramp->size) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Null: Gamma ramp size must match current ramp size"); + return; + } + + memcpy(monitor->null.ramp.red, ramp->red, sizeof(short) * ramp->size); + memcpy(monitor->null.ramp.green, ramp->green, sizeof(short) * ramp->size); + memcpy(monitor->null.ramp.blue, ramp->blue, sizeof(short) * ramp->size); } diff --git a/raylib/external/glfw/src/null_platform.h b/raylib/external/glfw/src/null_platform.h index fdea990..49436dc 100644 --- a/raylib/external/glfw/src/null_platform.h +++ b/raylib/external/glfw/src/null_platform.h @@ -27,17 +27,14 @@ #include -#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null +#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null +#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNull null +#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNull null #define _GLFW_PLATFORM_CONTEXT_STATE struct { int dummyContext; } -#define _GLFW_PLATFORM_MONITOR_STATE struct { int dummyMonitor; } #define _GLFW_PLATFORM_CURSOR_STATE struct { int dummyCursor; } -#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE struct { int dummyLibraryWindow; } #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE struct { int dummyLibraryContext; } -#define _GLFW_EGL_CONTEXT_STATE struct { int dummyEGLContext; } -#define _GLFW_EGL_LIBRARY_CONTEXT_STATE struct { int dummyEGLLibraryContext; } -#include "osmesa_context.h" #include "posix_time.h" #include "posix_thread.h" #include "null_joystick.h" @@ -56,7 +53,37 @@ // typedef struct _GLFWwindowNull { - int width; - int height; + int xpos; + int ypos; + int width; + int height; + char* title; + GLFWbool visible; + GLFWbool iconified; + GLFWbool maximized; + GLFWbool resizable; + GLFWbool decorated; + GLFWbool floating; + GLFWbool transparent; + float opacity; } _GLFWwindowNull; +// Null-specific per-monitor data +// +typedef struct _GLFWmonitorNull +{ + GLFWgammaramp ramp; +} _GLFWmonitorNull; + +// Null-specific global data +// +typedef struct _GLFWlibraryNull +{ + int xcursor; + int ycursor; + char* clipboardString; + _GLFWwindow* focusedWindow; +} _GLFWlibraryNull; + +void _glfwPollMonitorsNull(void); + diff --git a/raylib/external/glfw/src/null_window.c b/raylib/external/glfw/src/null_window.c index 936400d..3e44664 100644 --- a/raylib/external/glfw/src/null_window.c +++ b/raylib/external/glfw/src/null_window.c @@ -29,12 +29,71 @@ #include "internal.h" +#include + +static void applySizeLimits(_GLFWwindow* window, int* width, int* height) +{ + if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE) + { + const float ratio = (float) window->numer / (float) window->denom; + *height = (int) (*width / ratio); + } + + if (window->minwidth != GLFW_DONT_CARE && *width < window->minwidth) + *width = window->minwidth; + else if (window->maxwidth != GLFW_DONT_CARE && *width > window->maxwidth) + *width = window->maxwidth; + + if (window->minheight != GLFW_DONT_CARE && *height < window->minheight) + *height = window->minheight; + else if (window->maxheight != GLFW_DONT_CARE && *height > window->maxheight) + *height = window->maxheight; +} + +static void fitToMonitor(_GLFWwindow* window) +{ + GLFWvidmode mode; + _glfwPlatformGetVideoMode(window->monitor, &mode); + _glfwPlatformGetMonitorPos(window->monitor, + &window->null.xpos, + &window->null.ypos); + window->null.width = mode.width; + window->null.height = mode.height; +} + +static void acquireMonitor(_GLFWwindow* window) +{ + _glfwInputMonitorWindow(window->monitor, window); +} + +static void releaseMonitor(_GLFWwindow* window) +{ + if (window->monitor->window != window) + return; + + _glfwInputMonitorWindow(window->monitor, NULL); +} static int createNativeWindow(_GLFWwindow* window, - const _GLFWwndconfig* wndconfig) + const _GLFWwndconfig* wndconfig, + const _GLFWfbconfig* fbconfig) { - window->null.width = wndconfig->width; - window->null.height = wndconfig->height; + if (window->monitor) + fitToMonitor(window); + else + { + window->null.xpos = 17; + window->null.ypos = 17; + window->null.width = wndconfig->width; + window->null.height = wndconfig->height; + } + + window->null.visible = wndconfig->visible; + window->null.decorated = wndconfig->decorated; + window->null.maximized = wndconfig->maximized; + window->null.floating = wndconfig->floating; + window->null.transparent = fbconfig->transparent; + window->null.opacity = 1.f; return GLFW_TRUE; } @@ -49,7 +108,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { - if (!createNativeWindow(window, wndconfig)) + if (!createNativeWindow(window, wndconfig, fbconfig)) return GLFW_FALSE; if (ctxconfig->client != GLFW_NO_API) @@ -69,11 +128,24 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, } } + if (window->monitor) + { + _glfwPlatformShowWindow(window); + _glfwPlatformFocusWindow(window); + acquireMonitor(window); + } + return GLFW_TRUE; } void _glfwPlatformDestroyWindow(_GLFWwindow* window) { + if (window->monitor) + releaseMonitor(window); + + if (_glfw.null.focusedWindow == window) + _glfw.null.focusedWindow = NULL; + if (window->context.destroy) window->context.destroy(window); } @@ -93,14 +165,54 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, int width, int height, int refreshRate) { + if (window->monitor == monitor) + { + if (!monitor) + { + _glfwPlatformSetWindowPos(window, xpos, ypos); + _glfwPlatformSetWindowSize(window, width, height); + } + + return; + } + + if (window->monitor) + releaseMonitor(window); + + _glfwInputWindowMonitor(window, monitor); + + if (window->monitor) + { + window->null.visible = GLFW_TRUE; + acquireMonitor(window); + fitToMonitor(window); + } + else + { + _glfwPlatformSetWindowPos(window, xpos, ypos); + _glfwPlatformSetWindowSize(window, width, height); + } } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) { + if (xpos) + *xpos = window->null.xpos; + if (ypos) + *ypos = window->null.ypos; } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) { + if (window->monitor) + return; + + if (window->null.xpos != xpos || window->null.ypos != ypos) + { + window->null.xpos = xpos; + window->null.ypos = ypos; + _glfwInputWindowPos(window, xpos, ypos); + } } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) @@ -113,18 +225,34 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { - window->null.width = width; - window->null.height = height; + if (window->monitor) + return; + + if (window->null.width != width || window->null.height != height) + { + window->null.width = width; + window->null.height = height; + _glfwInputWindowSize(window, width, height); + _glfwInputFramebufferSize(window, width, height); + } } void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight) { + int width = window->null.width; + int height = window->null.height; + applySizeLimits(window, &width, &height); + _glfwPlatformSetWindowSize(window, width, height); } void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int n, int d) { + int width = window->null.width; + int height = window->null.height; + applySizeLimits(window, &width, &height); + _glfwPlatformSetWindowSize(window, width, height); } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) @@ -139,6 +267,28 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom) { + if (window->null.decorated && !window->monitor) + { + if (left) + *left = 1; + if (top) + *top = 10; + if (right) + *right = 1; + if (bottom) + *bottom = 1; + } + else + { + if (left) + *left = 0; + if (top) + *top = 0; + if (right) + *right = 0; + if (bottom) + *bottom = 0; + } } void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, @@ -152,50 +302,93 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, void _glfwPlatformIconifyWindow(_GLFWwindow* window) { + if (_glfw.null.focusedWindow == window) + { + _glfw.null.focusedWindow = NULL; + _glfwInputWindowFocus(window, GLFW_FALSE); + } + + if (!window->null.iconified) + { + window->null.iconified = GLFW_TRUE; + _glfwInputWindowIconify(window, GLFW_TRUE); + + if (window->monitor) + releaseMonitor(window); + } } void _glfwPlatformRestoreWindow(_GLFWwindow* window) { + if (window->null.iconified) + { + window->null.iconified = GLFW_FALSE; + _glfwInputWindowIconify(window, GLFW_FALSE); + + if (window->monitor) + acquireMonitor(window); + } + else if (window->null.maximized) + { + window->null.maximized = GLFW_FALSE; + _glfwInputWindowMaximize(window, GLFW_FALSE); + } } void _glfwPlatformMaximizeWindow(_GLFWwindow* window) { + if (!window->null.maximized) + { + window->null.maximized = GLFW_TRUE; + _glfwInputWindowMaximize(window, GLFW_TRUE); + } } int _glfwPlatformWindowMaximized(_GLFWwindow* window) { - return GLFW_FALSE; + return window->null.maximized; } int _glfwPlatformWindowHovered(_GLFWwindow* window) { - return GLFW_FALSE; + return _glfw.null.xcursor >= window->null.xpos && + _glfw.null.ycursor >= window->null.ypos && + _glfw.null.xcursor <= window->null.xpos + window->null.width - 1 && + _glfw.null.ycursor <= window->null.ypos + window->null.height - 1; } int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { - return GLFW_FALSE; + return window->null.transparent; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) { + window->null.resizable = enabled; } void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) { + window->null.decorated = enabled; } void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) +{ + window->null.floating = enabled; +} + +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) { } float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { - return 1.f; + return window->null.opacity; } void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { + window->null.opacity = opacity; } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) @@ -204,43 +397,63 @@ void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) GLFWbool _glfwPlatformRawMouseMotionSupported(void) { - return GLFW_FALSE; + return GLFW_TRUE; } void _glfwPlatformShowWindow(_GLFWwindow* window) { + window->null.visible = GLFW_TRUE; } - void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) { } -void _glfwPlatformUnhideWindow(_GLFWwindow* window) -{ -} - void _glfwPlatformHideWindow(_GLFWwindow* window) { + if (_glfw.null.focusedWindow == window) + { + _glfw.null.focusedWindow = NULL; + _glfwInputWindowFocus(window, GLFW_FALSE); + } + + window->null.visible = GLFW_FALSE; } void _glfwPlatformFocusWindow(_GLFWwindow* window) { + if (_glfw.null.focusedWindow == window) + return; + + if (!window->null.visible) + return; + + _GLFWwindow* previous = _glfw.null.focusedWindow; + _glfw.null.focusedWindow = window; + + if (previous) + { + _glfwInputWindowFocus(previous, GLFW_FALSE); + if (previous->monitor && previous->autoIconify) + _glfwPlatformIconifyWindow(previous); + } + + _glfwInputWindowFocus(window, GLFW_TRUE); } int _glfwPlatformWindowFocused(_GLFWwindow* window) { - return GLFW_FALSE; + return _glfw.null.focusedWindow == window; } int _glfwPlatformWindowIconified(_GLFWwindow* window) { - return GLFW_FALSE; + return window->null.iconified; } int _glfwPlatformWindowVisible(_GLFWwindow* window) { - return GLFW_FALSE; + return window->null.visible; } void _glfwPlatformPollEvents(void) @@ -261,10 +474,16 @@ void _glfwPlatformPostEmptyEvent(void) void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos) { + if (xpos) + *xpos = _glfw.null.xcursor - window->null.xpos; + if (ypos) + *ypos = _glfw.null.ycursor - window->null.ypos; } void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y) { + _glfw.null.xcursor = window->null.xpos + (int) x; + _glfw.null.ycursor = window->null.ypos + (int) y; } void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) @@ -293,21 +512,140 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) void _glfwPlatformSetClipboardString(const char* string) { + char* copy = _glfw_strdup(string); + free(_glfw.null.clipboardString); + _glfw.null.clipboardString = copy; } const char* _glfwPlatformGetClipboardString(void) { - return NULL; + return _glfw.null.clipboardString; } const char* _glfwPlatformGetScancodeName(int scancode) { - return ""; + switch (scancode) + { + case GLFW_KEY_APOSTROPHE: + return "'"; + case GLFW_KEY_COMMA: + return ","; + case GLFW_KEY_MINUS: + case GLFW_KEY_KP_SUBTRACT: + return "-"; + case GLFW_KEY_PERIOD: + case GLFW_KEY_KP_DECIMAL: + return "."; + case GLFW_KEY_SLASH: + case GLFW_KEY_KP_DIVIDE: + return "/"; + case GLFW_KEY_SEMICOLON: + return ";"; + case GLFW_KEY_EQUAL: + case GLFW_KEY_KP_EQUAL: + return "="; + case GLFW_KEY_LEFT_BRACKET: + return "["; + case GLFW_KEY_RIGHT_BRACKET: + return "]"; + case GLFW_KEY_KP_MULTIPLY: + return "*"; + case GLFW_KEY_KP_ADD: + return "+"; + case GLFW_KEY_BACKSLASH: + case GLFW_KEY_WORLD_1: + case GLFW_KEY_WORLD_2: + return "\\"; + case GLFW_KEY_0: + case GLFW_KEY_KP_0: + return "0"; + case GLFW_KEY_1: + case GLFW_KEY_KP_1: + return "1"; + case GLFW_KEY_2: + case GLFW_KEY_KP_2: + return "2"; + case GLFW_KEY_3: + case GLFW_KEY_KP_3: + return "3"; + case GLFW_KEY_4: + case GLFW_KEY_KP_4: + return "4"; + case GLFW_KEY_5: + case GLFW_KEY_KP_5: + return "5"; + case GLFW_KEY_6: + case GLFW_KEY_KP_6: + return "6"; + case GLFW_KEY_7: + case GLFW_KEY_KP_7: + return "7"; + case GLFW_KEY_8: + case GLFW_KEY_KP_8: + return "8"; + case GLFW_KEY_9: + case GLFW_KEY_KP_9: + return "9"; + case GLFW_KEY_A: + return "a"; + case GLFW_KEY_B: + return "b"; + case GLFW_KEY_C: + return "c"; + case GLFW_KEY_D: + return "d"; + case GLFW_KEY_E: + return "e"; + case GLFW_KEY_F: + return "f"; + case GLFW_KEY_G: + return "g"; + case GLFW_KEY_H: + return "h"; + case GLFW_KEY_I: + return "i"; + case GLFW_KEY_J: + return "j"; + case GLFW_KEY_K: + return "k"; + case GLFW_KEY_L: + return "l"; + case GLFW_KEY_M: + return "m"; + case GLFW_KEY_N: + return "n"; + case GLFW_KEY_O: + return "o"; + case GLFW_KEY_P: + return "p"; + case GLFW_KEY_Q: + return "q"; + case GLFW_KEY_R: + return "r"; + case GLFW_KEY_S: + return "s"; + case GLFW_KEY_T: + return "t"; + case GLFW_KEY_U: + return "u"; + case GLFW_KEY_V: + return "v"; + case GLFW_KEY_W: + return "w"; + case GLFW_KEY_X: + return "x"; + case GLFW_KEY_Y: + return "y"; + case GLFW_KEY_Z: + return "z"; + } + + return NULL; } int _glfwPlatformGetKeyScancode(int key) { - return -1; + return key; } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) @@ -327,6 +665,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, VkSurfaceKHR* surface) { // This seems like the most appropriate error to return here - return VK_ERROR_INITIALIZATION_FAILED; + return VK_ERROR_EXTENSION_NOT_PRESENT; } diff --git a/raylib/external/glfw/src/osmesa_context.h b/raylib/external/glfw/src/osmesa_context.h index a5de0d9..ce1f1a2 100644 --- a/raylib/external/glfw/src/osmesa_context.h +++ b/raylib/external/glfw/src/osmesa_context.h @@ -54,10 +54,6 @@ typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*); #define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer #define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress -#define _GLFW_OSMESA_CONTEXT_STATE _GLFWcontextOSMesa osmesa -#define _GLFW_OSMESA_LIBRARY_CONTEXT_STATE _GLFWlibraryOSMesa osmesa - - // OSMesa-specific per-context data // typedef struct _GLFWcontextOSMesa diff --git a/raylib/external/glfw/src/wgl_context.h b/raylib/external/glfw/src/wgl_context.h index df983e9..2cf7e4e 100644 --- a/raylib/external/glfw/src/wgl_context.h +++ b/raylib/external/glfw/src/wgl_context.h @@ -104,10 +104,6 @@ typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC); #define wglMakeCurrent _glfw.wgl.MakeCurrent #define wglShareLists _glfw.wgl.ShareLists -#define _GLFW_RECREATION_NOT_NEEDED 0 -#define _GLFW_RECREATION_REQUIRED 1 -#define _GLFW_RECREATION_IMPOSSIBLE 2 - #define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextWGL wgl #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWlibraryWGL wgl diff --git a/raylib/external/glfw/src/win32_init.c b/raylib/external/glfw/src/win32_init.c index 260e888..f6995e5 100644 --- a/raylib/external/glfw/src/win32_init.c +++ b/raylib/external/glfw/src/win32_init.c @@ -143,6 +143,8 @@ static GLFWbool loadLibraries(void) GetProcAddress(_glfw.win32.dwmapi.instance, "DwmFlush"); _glfw.win32.dwmapi.EnableBlurBehindWindow = (PFN_DwmEnableBlurBehindWindow) GetProcAddress(_glfw.win32.dwmapi.instance, "DwmEnableBlurBehindWindow"); + _glfw.win32.dwmapi.GetColorizationColor = (PFN_DwmGetColorizationColor) + GetProcAddress(_glfw.win32.dwmapi.instance, "DwmGetColorizationColor"); } _glfw.win32.shcore.instance = LoadLibraryA("shcore.dll"); @@ -580,7 +582,6 @@ int _glfwPlatformInit(void) return GLFW_FALSE; _glfwInitTimerWin32(); - _glfwInitJoysticksWin32(); _glfwPollMonitorsWin32(); return GLFW_TRUE; @@ -607,8 +608,6 @@ void _glfwPlatformTerminate(void) _glfwTerminateWGL(); _glfwTerminateEGL(); - _glfwTerminateJoysticksWin32(); - freeLibraries(); } diff --git a/raylib/external/glfw/src/win32_joystick.c b/raylib/external/glfw/src/win32_joystick.c index c19f77c..9c71d11 100644 --- a/raylib/external/glfw/src/win32_joystick.c +++ b/raylib/external/glfw/src/win32_joystick.c @@ -356,7 +356,7 @@ static BOOL CALLBACK deviceCallback(const DIDEVICEINSTANCE* di, void* user) for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++) { - _GLFWjoystick* js = _glfw.joysticks + jid; + js = _glfw.joysticks + jid; if (js->present) { if (memcmp(&js->win32.guid, &di->guidInstance, sizeof(GUID)) == 0) @@ -491,39 +491,6 @@ static BOOL CALLBACK deviceCallback(const DIDEVICEINSTANCE* di, void* user) ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// -// Initialize joystick interface -// -void _glfwInitJoysticksWin32(void) -{ - if (_glfw.win32.dinput8.instance) - { - if (FAILED(DirectInput8Create(GetModuleHandle(NULL), - DIRECTINPUT_VERSION, - &IID_IDirectInput8W, - (void**) &_glfw.win32.dinput8.api, - NULL))) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Win32: Failed to create interface"); - } - } - - _glfwDetectJoystickConnectionWin32(); -} - -// Close all opened joystick handles -// -void _glfwTerminateJoysticksWin32(void) -{ - int jid; - - for (jid = GLFW_JOYSTICK_1; jid <= GLFW_JOYSTICK_LAST; jid++) - closeJoystick(_glfw.joysticks + jid); - - if (_glfw.win32.dinput8.api) - IDirectInput8_Release(_glfw.win32.dinput8.api); -} - // Checks for new joysticks after DBT_DEVICEARRIVAL // void _glfwDetectJoystickConnectionWin32(void) @@ -603,6 +570,37 @@ void _glfwDetectJoystickDisconnectionWin32(void) ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// +GLFWbool _glfwPlatformInitJoysticks(void) +{ + if (_glfw.win32.dinput8.instance) + { + if (FAILED(DirectInput8Create(GetModuleHandle(NULL), + DIRECTINPUT_VERSION, + &IID_IDirectInput8W, + (void**) &_glfw.win32.dinput8.api, + NULL))) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Win32: Failed to create interface"); + return GLFW_FALSE; + } + } + + _glfwDetectJoystickConnectionWin32(); + return GLFW_TRUE; +} + +void _glfwPlatformTerminateJoysticks(void) +{ + int jid; + + for (jid = GLFW_JOYSTICK_1; jid <= GLFW_JOYSTICK_LAST; jid++) + closeJoystick(_glfw.joysticks + jid); + + if (_glfw.win32.dinput8.api) + IDirectInput8_Release(_glfw.win32.dinput8.api); +} + int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) { if (js->win32.device) @@ -672,11 +670,11 @@ int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) }; // Screams of horror are appropriate at this point - int state = LOWORD(*(DWORD*) data) / (45 * DI_DEGREES); - if (state < 0 || state > 8) - state = 8; + int stateIndex = LOWORD(*(DWORD*) data) / (45 * DI_DEGREES); + if (stateIndex < 0 || stateIndex > 8) + stateIndex = 8; - _glfwInputJoystickHat(js, pi, states[state]); + _glfwInputJoystickHat(js, pi, states[stateIndex]); pi++; break; } diff --git a/raylib/external/glfw/src/win32_joystick.h b/raylib/external/glfw/src/win32_joystick.h index f593274..b6a7adc 100644 --- a/raylib/external/glfw/src/win32_joystick.h +++ b/raylib/external/glfw/src/win32_joystick.h @@ -48,9 +48,6 @@ typedef struct _GLFWjoystickWin32 GUID guid; } _GLFWjoystickWin32; - -void _glfwInitJoysticksWin32(void); -void _glfwTerminateJoysticksWin32(void); void _glfwDetectJoystickConnectionWin32(void); void _glfwDetectJoystickDisconnectionWin32(void); diff --git a/raylib/external/glfw/src/win32_monitor.c b/raylib/external/glfw/src/win32_monitor.c index 5f91c57..b4c53e4 100644 --- a/raylib/external/glfw/src/win32_monitor.c +++ b/raylib/external/glfw/src/win32_monitor.c @@ -185,6 +185,8 @@ void _glfwPollMonitorsWin32(void) display.DeviceName) == 0) { disconnected[i] = NULL; + // handle may have changed, update + EnumDisplayMonitors(NULL, NULL, monitorCallback, (LPARAM) _glfw.monitors[i]); break; } } diff --git a/raylib/external/glfw/src/win32_platform.h b/raylib/external/glfw/src/win32_platform.h index 2b00b00..4f52051 100644 --- a/raylib/external/glfw/src/win32_platform.h +++ b/raylib/external/glfw/src/win32_platform.h @@ -77,6 +77,9 @@ #ifndef WM_DWMCOMPOSITIONCHANGED #define WM_DWMCOMPOSITIONCHANGED 0x031E #endif +#ifndef WM_DWMCOLORIZATIONCOLORCHANGED + #define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320 +#endif #ifndef WM_COPYGLOBALDATA #define WM_COPYGLOBALDATA 0x0049 #endif @@ -99,7 +102,7 @@ #define DISPLAY_DEVICE_ACTIVE 0x00000001 #endif #ifndef _WIN32_WINNT_WINBLUE - #define _WIN32_WINNT_WINBLUE 0x0602 + #define _WIN32_WINNT_WINBLUE 0x0603 #endif #ifndef _WIN32_WINNT_WIN8 #define _WIN32_WINNT_WIN8 0x0602 @@ -160,9 +163,6 @@ typedef enum #endif /*DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2*/ // HACK: Define versionhelpers.h functions manually as MinGW lacks the header -#define IsWindowsXPOrGreater() \ - _glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINXP), \ - LOBYTE(_WIN32_WINNT_WINXP), 0) #define IsWindowsVistaOrGreater() \ _glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_VISTA), \ LOBYTE(_WIN32_WINNT_VISTA), 0) @@ -247,9 +247,11 @@ typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UIN typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*); typedef HRESULT (WINAPI * PFN_DwmFlush)(VOID); typedef HRESULT(WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*); +typedef HRESULT (WINAPI * PFN_DwmGetColorizationColor)(DWORD*,BOOL*); #define DwmIsCompositionEnabled _glfw.win32.dwmapi.IsCompositionEnabled #define DwmFlush _glfw.win32.dwmapi.Flush #define DwmEnableBlurBehindWindow _glfw.win32.dwmapi.EnableBlurBehindWindow +#define DwmGetColorizationColor _glfw.win32.dwmapi.GetColorizationColor // shcore.dll function pointer typedefs typedef HRESULT (WINAPI * PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS); @@ -277,8 +279,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)( #include "win32_joystick.h" #include "wgl_context.h" -#include "egl_context.h" -#include "osmesa_context.h" #if !defined(_GLFW_WNDCLASSNAME) #define _GLFW_WNDCLASSNAME L"GLFW30" @@ -288,9 +288,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)( #define _glfw_dlclose(handle) FreeLibrary((HMODULE) handle) #define _glfw_dlsym(handle, name) GetProcAddress((HMODULE) handle, name) -#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->win32.handle) -#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY - #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWin32 win32 #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWin32 win32 #define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerWin32 win32 @@ -319,6 +316,8 @@ typedef struct _GLFWwindowWin32 // The last received cursor position, regardless of source int lastCursorPosX, lastCursorPosY; + // The last recevied high surrogate when decoding pairs of UTF-16 messages + WCHAR highSurrogate; } _GLFWwindowWin32; @@ -374,6 +373,7 @@ typedef struct _GLFWlibraryWin32 PFN_DwmIsCompositionEnabled IsCompositionEnabled; PFN_DwmFlush Flush; PFN_DwmEnableBlurBehindWindow EnableBlurBehindWindow; + PFN_DwmGetColorizationColor GetColorizationColor; } dwmapi; struct { diff --git a/raylib/external/glfw/src/win32_window.c b/raylib/external/glfw/src/win32_window.c index 0ae0998..8cb5050 100644 --- a/raylib/external/glfw/src/win32_window.c +++ b/raylib/external/glfw/src/win32_window.c @@ -377,12 +377,17 @@ static void updateWindowStyles(const _GLFWwindow* window) // static void updateFramebufferTransparency(const _GLFWwindow* window) { - BOOL enabled; + BOOL composition, opaque; + DWORD color; if (!IsWindowsVistaOrGreater()) return; - if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled) + if (FAILED(DwmIsCompositionEnabled(&composition)) || !composition) + return; + + if (IsWindows8OrGreater() || + (SUCCEEDED(DwmGetColorizationColor(&color, &opaque)) && !opaque)) { HRGN region = CreateRectRgn(0, 0, -1, -1); DWM_BLURBEHIND bb = {0}; @@ -390,37 +395,18 @@ static void updateFramebufferTransparency(const _GLFWwindow* window) bb.hRgnBlur = region; bb.fEnable = TRUE; - if (SUCCEEDED(DwmEnableBlurBehindWindow(window->win32.handle, &bb))) - { - // Decorated windows don't repaint the transparent background - // leaving a trail behind animations - // HACK: Making the window layered with a transparency color key - // seems to fix this. Normally, when specifying - // a transparency color key to be used when composing the - // layered window, all pixels painted by the window in this - // color will be transparent. That doesn't seem to be the - // case anymore, at least when used with blur behind window - // plus negative region. - LONG exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); - exStyle |= WS_EX_LAYERED; - SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle); - - // Using a color key not equal to black to fix the trailing - // issue. When set to black, something is making the hit test - // not resize with the window frame. - SetLayeredWindowAttributes(window->win32.handle, - RGB(255, 0, 255), 255, LWA_COLORKEY); - } - + DwmEnableBlurBehindWindow(window->win32.handle, &bb); DeleteObject(region); } else { - LONG exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); - exStyle &= ~WS_EX_LAYERED; - SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle); - RedrawWindow(window->win32.handle, NULL, NULL, - RDW_ERASE | RDW_INVALIDATE | RDW_FRAME); + // HACK: Disable framebuffer transparency on Windows 7 when the + // colorization color is opaque, because otherwise the window + // contents is blended additively with the previous frame instead + // of replacing it + DWM_BLURBEHIND bb = {0}; + bb.dwFlags = DWM_BB_ENABLE; + DwmEnableBlurBehindWindow(window->win32.handle, &bb); } } @@ -468,11 +454,8 @@ static void acquireMonitor(_GLFWwindow* window) // HACK: When mouse trails are enabled the cursor becomes invisible when // the OpenGL ICD switches to page flipping - if (IsWindowsXPOrGreater()) - { - SystemParametersInfo(SPI_GETMOUSETRAILS, 0, &_glfw.win32.mouseTrailSize, 0); - SystemParametersInfo(SPI_SETMOUSETRAILS, 0, 0, 0); - } + SystemParametersInfo(SPI_GETMOUSETRAILS, 0, &_glfw.win32.mouseTrailSize, 0); + SystemParametersInfo(SPI_SETMOUSETRAILS, 0, 0, 0); } if (!window->monitor->window) @@ -495,8 +478,7 @@ static void releaseMonitor(_GLFWwindow* window) SetThreadExecutionState(ES_CONTINUOUS); // HACK: Restore mouse trail length saved in acquireMonitor - if (IsWindowsXPOrGreater()) - SystemParametersInfo(SPI_SETMOUSETRAILS, _glfw.win32.mouseTrailSize, 0, 0); + SystemParametersInfo(SPI_SETMOUSETRAILS, _glfw.win32.mouseTrailSize, 0, 0); } _glfwInputMonitorWindow(window->monitor, NULL); @@ -530,6 +512,9 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, case WM_DEVICECHANGE: { + if (!_glfw.joysticksInitialized) + break; + if (wParam == DBT_DEVICEARRIVAL) { DEV_BROADCAST_HDR* dbh = (DEV_BROADCAST_HDR*) lParam; @@ -650,11 +635,38 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, case WM_CHAR: case WM_SYSCHAR: + { + if (wParam >= 0xd800 && wParam <= 0xdbff) + window->win32.highSurrogate = (WCHAR) wParam; + else + { + unsigned int codepoint = 0; + + if (wParam >= 0xdc00 && wParam <= 0xdfff) + { + if (window->win32.highSurrogate) + { + codepoint += (window->win32.highSurrogate - 0xd800) << 10; + codepoint += (WCHAR) wParam - 0xdc00; + codepoint += 0x10000; + } + } + else + codepoint = (WCHAR) wParam; + + window->win32.highSurrogate = 0; + _glfwInputChar(window, codepoint, getKeyMods(), uMsg != WM_SYSCHAR); + } + + if (uMsg == WM_SYSCHAR && window->win32.keymenu) + break; + + return 0; + } + case WM_UNICHAR: { - const GLFWbool plain = (uMsg != WM_SYSCHAR); - - if (uMsg == WM_UNICHAR && wParam == UNICODE_NOCHAR) + if (wParam == UNICODE_NOCHAR) { // WM_UNICHAR is not sent by Windows, but is sent by some // third-party input method engine @@ -662,11 +674,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, return TRUE; } - _glfwInputChar(window, (unsigned int) wParam, getKeyMods(), plain); - - if (uMsg == WM_SYSCHAR && window->win32.keymenu) - break; - + _glfwInputChar(window, (unsigned int) wParam, getKeyMods(), GLFW_TRUE); return 0; } @@ -1082,6 +1090,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, } case WM_DWMCOMPOSITIONCHANGED: + case WM_DWMCOLORIZATIONCOLORCHANGED: { if (window->win32.transparent) updateFramebufferTransparency(window); @@ -1807,7 +1816,8 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) { - BOOL enabled; + BOOL composition, opaque; + DWORD color; if (!window->win32.transparent) return GLFW_FALSE; @@ -1815,7 +1825,20 @@ int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) if (!IsWindowsVistaOrGreater()) return GLFW_FALSE; - return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled; + if (FAILED(DwmIsCompositionEnabled(&composition)) || !composition) + return GLFW_FALSE; + + if (!IsWindows8OrGreater()) + { + // HACK: Disable framebuffer transparency on Windows 7 when the + // colorization color is opaque, because otherwise the window + // contents is blended additively with the previous frame instead + // of replacing it + if (FAILED(DwmGetColorizationColor(&color, &opaque)) || opaque) + return GLFW_FALSE; + } + + return GLFW_TRUE; } void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) @@ -1835,6 +1858,36 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + COLORREF key = 0; + BYTE alpha = 0; + DWORD flags = 0; + DWORD exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); + + if (exStyle & WS_EX_LAYERED) + GetLayeredWindowAttributes(window->win32.handle, &key, &alpha, &flags); + + if (enabled) + exStyle |= (WS_EX_TRANSPARENT | WS_EX_LAYERED); + else + { + exStyle &= ~WS_EX_TRANSPARENT; + // NOTE: Window opacity also needs the layered window style so do not + // remove it if the window is alpha blended + if (exStyle & WS_EX_LAYERED) + { + if (!(flags & LWA_ALPHA)) + exStyle &= ~WS_EX_LAYERED; + } + } + + SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle); + + if (enabled) + SetLayeredWindowAttributes(window->win32.handle, key, alpha, flags); +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { BYTE alpha; @@ -1852,19 +1905,22 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { - if (opacity < 1.f) + LONG exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); + if (opacity < 1.f || (exStyle & WS_EX_TRANSPARENT)) { const BYTE alpha = (BYTE) (255 * opacity); - DWORD style = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); - style |= WS_EX_LAYERED; - SetWindowLongW(window->win32.handle, GWL_EXSTYLE, style); + exStyle |= WS_EX_LAYERED; + SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle); SetLayeredWindowAttributes(window->win32.handle, 0, alpha, LWA_ALPHA); } + else if (exStyle & WS_EX_TRANSPARENT) + { + SetLayeredWindowAttributes(window->win32.handle, 0, 0, 0); + } else { - DWORD style = GetWindowLongW(window->win32.handle, GWL_EXSTYLE); - style &= ~WS_EX_LAYERED; - SetWindowLongW(window->win32.handle, GWL_EXSTYLE, style); + exStyle &= ~WS_EX_LAYERED; + SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle); } } @@ -2190,6 +2246,57 @@ const char* _glfwPlatformGetClipboardString(void) return _glfw.win32.clipboardString; } +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) +{ + if (_glfw.egl.ANGLE_platform_angle) + { + int type = 0; + + if (_glfw.egl.ANGLE_platform_angle_opengl) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) + type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; + else if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGLES) + type = EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE; + } + + if (_glfw.egl.ANGLE_platform_angle_d3d) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_D3D9) + type = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE; + else if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_D3D11) + type = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE; + } + + if (_glfw.egl.ANGLE_platform_angle_vulkan) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN) + type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE; + } + + if (type) + { + *attribs = calloc(3, sizeof(EGLint)); + (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; + (*attribs)[1] = type; + (*attribs)[2] = EGL_NONE; + return EGL_PLATFORM_ANGLE_ANGLE; + } + } + + return 0; +} + +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) +{ + return GetDC(_glfw.win32.helperWindowHandle); +} + +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) +{ + return window->win32.handle; +} + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_win32_surface) diff --git a/raylib/external/glfw/src/window.c b/raylib/external/glfw/src/window.c index bb5ba95..efcb412 100644 --- a/raylib/external/glfw/src/window.c +++ b/raylib/external/glfw/src/window.c @@ -197,13 +197,14 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, window->videoMode.blueBits = fbconfig.blueBits; window->videoMode.refreshRate = _glfw.hints.refreshRate; - window->monitor = (_GLFWmonitor*) monitor; - window->resizable = wndconfig.resizable; - window->decorated = wndconfig.decorated; - window->autoIconify = wndconfig.autoIconify; - window->floating = wndconfig.floating; - window->focusOnShow = wndconfig.focusOnShow; - window->cursorMode = GLFW_CURSOR_NORMAL; + window->monitor = (_GLFWmonitor*) monitor; + window->resizable = wndconfig.resizable; + window->decorated = wndconfig.decorated; + window->autoIconify = wndconfig.autoIconify; + window->floating = wndconfig.floating; + window->focusOnShow = wndconfig.focusOnShow; + window->mousePassthrough = wndconfig.mousePassthrough; + window->cursorMode = GLFW_CURSOR_NORMAL; window->minwidth = GLFW_DONT_CARE; window->minheight = GLFW_DONT_CARE; @@ -228,6 +229,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, } } + if (wndconfig.mousePassthrough) + _glfwPlatformSetWindowMousePassthrough(window, GLFW_TRUE); + if (window->monitor) { if (wndconfig.centerCursor) @@ -378,6 +382,9 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_FOCUS_ON_SHOW: _glfw.hints.window.focusOnShow = value ? GLFW_TRUE : GLFW_FALSE; return; + case GLFW_MOUSE_PASSTHROUGH: + _glfw.hints.window.mousePassthrough = value ? GLFW_TRUE : GLFW_FALSE; + return; case GLFW_CLIENT_API: _glfw.hints.context.client = value; return; @@ -396,7 +403,7 @@ GLFWAPI void glfwWindowHint(int hint, int value) case GLFW_OPENGL_FORWARD_COMPAT: _glfw.hints.context.forward = value ? GLFW_TRUE : GLFW_FALSE; return; - case GLFW_OPENGL_DEBUG_CONTEXT: + case GLFW_CONTEXT_DEBUG: _glfw.hints.context.debug = value ? GLFW_TRUE : GLFW_FALSE; return; case GLFW_CONTEXT_NO_ERROR: @@ -822,6 +829,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) return _glfwPlatformWindowHovered(window); case GLFW_FOCUS_ON_SHOW: return window->focusOnShow; + case GLFW_MOUSE_PASSTHROUGH: + return window->mousePassthrough; case GLFW_TRANSPARENT_FRAMEBUFFER: return _glfwPlatformFramebufferTransparent(window); case GLFW_RESIZABLE: @@ -846,7 +855,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib) return window->context.robustness; case GLFW_OPENGL_FORWARD_COMPAT: return window->context.forward; - case GLFW_OPENGL_DEBUG_CONTEXT: + case GLFW_CONTEXT_DEBUG: return window->context.debug; case GLFW_OPENGL_PROFILE: return window->context.profile; @@ -900,6 +909,14 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value) } else if (attrib == GLFW_FOCUS_ON_SHOW) window->focusOnShow = value; + else if (attrib == GLFW_MOUSE_PASSTHROUGH) + { + if (window->mousePassthrough == value) + return; + + window->mousePassthrough = value; + _glfwPlatformSetWindowMousePassthrough(window, value); + } else _glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib); } @@ -1102,3 +1119,4 @@ GLFWAPI void glfwPostEmptyEvent(void) _GLFW_REQUIRE_INIT(); _glfwPlatformPostEmptyEvent(); } + diff --git a/raylib/external/glfw/src/wl_init.c b/raylib/external/glfw/src/wl_init.c index fff89b3..d3e777d 100644 --- a/raylib/external/glfw/src/wl_init.c +++ b/raylib/external/glfw/src/wl_init.c @@ -26,7 +26,7 @@ // It is fine to use C99 in this file because it will not be built with VS //======================================================================== -//#define _POSIX_C_SOURCE 199309L +#define _POSIX_C_SOURCE 199309L #include "internal.h" @@ -341,9 +341,9 @@ static void pointerHandleAxis(void* data, axis == WL_POINTER_AXIS_VERTICAL_SCROLL); if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) - x = wl_fixed_to_double(value) * scrollFactor; + x = -wl_fixed_to_double(value) * scrollFactor; else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) - y = wl_fixed_to_double(value) * scrollFactor; + y = -wl_fixed_to_double(value) * scrollFactor; _glfwInputScroll(window, x, y); } @@ -1038,6 +1038,8 @@ int _glfwPlatformInit(void) char *cursorSizeEnd; long cursorSizeLong; int cursorSize; + int i; + _GLFWmonitor* monitor; _glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0"); if (!_glfw.wl.cursor.handle) @@ -1146,10 +1148,16 @@ int _glfwPlatformInit(void) // Sync so we got all initial output events wl_display_roundtrip(_glfw.wl.display); -#ifdef __linux__ - if (!_glfwInitJoysticksLinux()) - return GLFW_FALSE; -#endif + for (i = 0; i < _glfw.monitorCount; ++i) + { + monitor = _glfw.monitors[i]; + if (monitor->widthMM <= 0 || monitor->heightMM <= 0) + { + // If Wayland does not provide a physical size, assume the default 96 DPI + monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f); + monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f); + } + } _glfwInitTimerPOSIX(); @@ -1213,9 +1221,6 @@ int _glfwPlatformInit(void) void _glfwPlatformTerminate(void) { -#ifdef __linux__ - _glfwTerminateJoysticksLinux(); -#endif _glfwTerminateEGL(); if (_glfw.wl.egl.handle) { diff --git a/raylib/external/glfw/src/wl_monitor.c b/raylib/external/glfw/src/wl_monitor.c index 55f6201..a6356de 100644 --- a/raylib/external/glfw/src/wl_monitor.c +++ b/raylib/external/glfw/src/wl_monitor.c @@ -199,7 +199,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode) GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) { - _glfwInputError(GLFW_PLATFORM_ERROR, + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: Gamma ramp access is not available"); return GLFW_FALSE; } @@ -207,7 +207,7 @@ GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp) void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) { - _glfwInputError(GLFW_PLATFORM_ERROR, + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, "Wayland: Gamma ramp access is not available"); } diff --git a/raylib/external/glfw/src/wl_platform.h b/raylib/external/glfw/src/wl_platform.h index 542cc78..966155f 100644 --- a/raylib/external/glfw/src/wl_platform.h +++ b/raylib/external/glfw/src/wl_platform.h @@ -53,8 +53,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "null_joystick.h" #endif #include "xkb_unicode.h" -#include "egl_context.h" -#include "osmesa_context.h" #include "wayland-xdg-shell-client-protocol.h" #include "wayland-xdg-decoration-client-protocol.h" @@ -67,9 +65,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) -#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->wl.native) -#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.wl.display) - #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl diff --git a/raylib/external/glfw/src/wl_window.c b/raylib/external/glfw/src/wl_window.c index c8dde30..7b315d7 100644 --- a/raylib/external/glfw/src/wl_window.c +++ b/raylib/external/glfw/src/wl_window.c @@ -142,8 +142,8 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image) if (fd < 0) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Creating a buffer file for %d B failed: %m", - length); + "Wayland: Creating a buffer file for %d B failed: %s", + length, strerror(errno)); return NULL; } @@ -151,7 +151,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image) if (data == MAP_FAILED) { _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: mmap failed: %m"); + "Wayland: mmap failed: %s", strerror(errno)); close(fd); return NULL; } @@ -749,10 +749,17 @@ static void handleEvents(int timeout) if (read_ret != 8) return; - for (i = 0; i < repeats; ++i) - _glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey, - _glfw.wl.keyboardLastScancode, GLFW_REPEAT, - _glfw.wl.xkb.modifiers); + if (_glfw.wl.keyboardFocus) + { + for (i = 0; i < repeats; ++i) + { + _glfwInputKey(_glfw.wl.keyboardFocus, + _glfw.wl.keyboardLastKey, + _glfw.wl.keyboardLastScancode, + GLFW_REPEAT, + _glfw.wl.xkb.modifiers); + } + } } if (fds[2].revents & POLLIN) @@ -883,8 +890,8 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images) { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Setting window icon not supported"); + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: The platform does not support setting the window icon"); } void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) @@ -892,16 +899,16 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos) // A Wayland client is not aware of its position, so just warn and leave it // as (0, 0) - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Window position retrieval not supported"); + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: The platform does not provide the window position"); } void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos) { // A Wayland client can not set its position, so just warn - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Window position setting not supported"); + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: The platform does not support setting the window position"); } void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height) @@ -940,14 +947,18 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, { // TODO: find out how to trigger a resize. // The actual limits are checked in the xdg_toplevel::configure handler. + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, + "Wayland: Window aspect ratio not yet implemented"); } void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height) { _glfwPlatformGetWindowSize(window, width, height); - *width *= window->wl.scale; - *height *= window->wl.scale; + if (width) + *width *= window->wl.scale; + if (height) + *height *= window->wl.scale; } void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, @@ -1030,14 +1041,14 @@ void _glfwPlatformHideWindow(_GLFWwindow* window) void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) { // TODO - _glfwInputError(GLFW_PLATFORM_ERROR, + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Wayland: Window attention request not implemented yet"); } void _glfwPlatformFocusWindow(_GLFWwindow* window) { - _glfwInputError(GLFW_PLATFORM_ERROR, - "Wayland: Focusing a window requires user interaction"); + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: The platform does not support setting the input focus"); } void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, @@ -1096,7 +1107,7 @@ int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled) { // TODO - _glfwInputError(GLFW_PLATFORM_ERROR, + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Wayland: Window attribute setting not implemented yet"); } @@ -1114,10 +1125,23 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled) void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) { // TODO - _glfwInputError(GLFW_PLATFORM_ERROR, + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, "Wayland: Window attribute setting not implemented yet"); } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + if (enabled) + { + struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor); + wl_surface_set_input_region(window->wl.surface, region); + wl_region_destroy(region); + } + else + wl_surface_set_input_region(window->wl.surface, 0); + wl_surface_commit(window->wl.surface); +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { return 1.f; @@ -1125,6 +1149,8 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity) { + _glfwInputError(GLFW_FEATURE_UNAVAILABLE, + "Wayland: The platform does not support setting the window opacity"); } void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, GLFWbool enabled) @@ -1186,6 +1212,8 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode) const char* _glfwPlatformGetScancodeName(int scancode) { // TODO + _glfwInputError(GLFW_FEATURE_UNIMPLEMENTED, + "Wayland: Key names not yet implemented"); return NULL; } @@ -1671,6 +1699,24 @@ const char* _glfwPlatformGetClipboardString(void) return _glfw.wl.clipboardString; } +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) +{ + if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland) + return EGL_PLATFORM_WAYLAND_EXT; + else + return 0; +} + +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) +{ + return _glfw.wl.display; +} + +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) +{ + return window->wl.native; +} + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface) diff --git a/raylib/external/glfw/src/x11_init.c b/raylib/external/glfw/src/x11_init.c index 2b7bc7f..dd24f11 100644 --- a/raylib/external/glfw/src/x11_init.c +++ b/raylib/external/glfw/src/x11_init.c @@ -29,8 +29,6 @@ #include "internal.h" -#include - #include #include #include @@ -39,24 +37,15 @@ #include -// Translate an X11 key code to a GLFW key code. +// Translate the X11 KeySyms for a key to a GLFW key code +// NOTE: This is only used as a fallback, in case the XKB method fails +// It is layout-dependent and will fail partially on most non-US layouts // -static int translateKeyCode(int scancode) +static int translateKeySyms(const KeySym* keysyms, int width) { - int keySym; - - // Valid key code range is [8,255], according to the Xlib manual - if (scancode < 8 || scancode > 255) - return GLFW_KEY_UNKNOWN; - - if (_glfw.x11.xkb.available) + if (width > 1) { - // Try secondary keysym, for numeric keypad keys - // Note: This way we always force "NumLock = ON", which is intentional - // since the returned key code should correspond to a physical - // location. - keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, _glfw.x11.xkb.group, 1); - switch (keySym) + switch (keysyms[1]) { case XK_KP_0: return GLFW_KEY_KP_0; case XK_KP_1: return GLFW_KEY_KP_1; @@ -74,22 +63,9 @@ static int translateKeyCode(int scancode) case XK_KP_Enter: return GLFW_KEY_KP_ENTER; default: break; } - - // Now try primary keysym for function keys (non-printable keys) - // These should not depend on the current keyboard layout - keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, _glfw.x11.xkb.group, 0); - } - else - { - int dummy; - KeySym* keySyms; - - keySyms = XGetKeyboardMapping(_glfw.x11.display, scancode, 1, &dummy); - keySym = keySyms[0]; - XFree(keySyms); } - switch (keySym) + switch (keysyms[0]) { case XK_Escape: return GLFW_KEY_ESCAPE; case XK_Tab: return GLFW_KEY_TAB; @@ -233,7 +209,7 @@ static int translateKeyCode(int scancode) // static void createKeyTables(void) { - int scancode, key; + int scancode, scancodeMin, scancodeMax; memset(_glfw.x11.keycodes, -1, sizeof(_glfw.x11.keycodes)); memset(_glfw.x11.scancodes, -1, sizeof(_glfw.x11.scancodes)); @@ -243,89 +219,217 @@ static void createKeyTables(void) // Use XKB to determine physical key locations independently of the // current keyboard layout - char name[XkbKeyNameLength + 1]; XkbDescPtr desc = XkbGetMap(_glfw.x11.display, 0, XkbUseCoreKbd); - XkbGetNames(_glfw.x11.display, XkbKeyNamesMask, desc); + XkbGetNames(_glfw.x11.display, XkbKeyNamesMask | XkbKeyAliasesMask, desc); + + scancodeMin = desc->min_key_code; + scancodeMax = desc->max_key_code; + + const struct + { + int key; + char* name; + } keymap[] = + { + { GLFW_KEY_GRAVE_ACCENT, "TLDE" }, + { GLFW_KEY_1, "AE01" }, + { GLFW_KEY_2, "AE02" }, + { GLFW_KEY_3, "AE03" }, + { GLFW_KEY_4, "AE04" }, + { GLFW_KEY_5, "AE05" }, + { GLFW_KEY_6, "AE06" }, + { GLFW_KEY_7, "AE07" }, + { GLFW_KEY_8, "AE08" }, + { GLFW_KEY_9, "AE09" }, + { GLFW_KEY_0, "AE10" }, + { GLFW_KEY_MINUS, "AE11" }, + { GLFW_KEY_EQUAL, "AE12" }, + { GLFW_KEY_Q, "AD01" }, + { GLFW_KEY_W, "AD02" }, + { GLFW_KEY_E, "AD03" }, + { GLFW_KEY_R, "AD04" }, + { GLFW_KEY_T, "AD05" }, + { GLFW_KEY_Y, "AD06" }, + { GLFW_KEY_U, "AD07" }, + { GLFW_KEY_I, "AD08" }, + { GLFW_KEY_O, "AD09" }, + { GLFW_KEY_P, "AD10" }, + { GLFW_KEY_LEFT_BRACKET, "AD11" }, + { GLFW_KEY_RIGHT_BRACKET, "AD12" }, + { GLFW_KEY_A, "AC01" }, + { GLFW_KEY_S, "AC02" }, + { GLFW_KEY_D, "AC03" }, + { GLFW_KEY_F, "AC04" }, + { GLFW_KEY_G, "AC05" }, + { GLFW_KEY_H, "AC06" }, + { GLFW_KEY_J, "AC07" }, + { GLFW_KEY_K, "AC08" }, + { GLFW_KEY_L, "AC09" }, + { GLFW_KEY_SEMICOLON, "AC10" }, + { GLFW_KEY_APOSTROPHE, "AC11" }, + { GLFW_KEY_Z, "AB01" }, + { GLFW_KEY_X, "AB02" }, + { GLFW_KEY_C, "AB03" }, + { GLFW_KEY_V, "AB04" }, + { GLFW_KEY_B, "AB05" }, + { GLFW_KEY_N, "AB06" }, + { GLFW_KEY_M, "AB07" }, + { GLFW_KEY_COMMA, "AB08" }, + { GLFW_KEY_PERIOD, "AB09" }, + { GLFW_KEY_SLASH, "AB10" }, + { GLFW_KEY_BACKSLASH, "BKSL" }, + { GLFW_KEY_WORLD_1, "LSGT" }, + { GLFW_KEY_SPACE, "SPCE" }, + { GLFW_KEY_ESCAPE, "ESC" }, + { GLFW_KEY_ENTER, "RTRN" }, + { GLFW_KEY_TAB, "TAB" }, + { GLFW_KEY_BACKSPACE, "BKSP" }, + { GLFW_KEY_INSERT, "INS" }, + { GLFW_KEY_DELETE, "DELE" }, + { GLFW_KEY_RIGHT, "RGHT" }, + { GLFW_KEY_LEFT, "LEFT" }, + { GLFW_KEY_DOWN, "DOWN" }, + { GLFW_KEY_UP, "UP" }, + { GLFW_KEY_PAGE_UP, "PGUP" }, + { GLFW_KEY_PAGE_DOWN, "PGDN" }, + { GLFW_KEY_HOME, "HOME" }, + { GLFW_KEY_END, "END" }, + { GLFW_KEY_CAPS_LOCK, "CAPS" }, + { GLFW_KEY_SCROLL_LOCK, "SCLK" }, + { GLFW_KEY_NUM_LOCK, "NMLK" }, + { GLFW_KEY_PRINT_SCREEN, "PRSC" }, + { GLFW_KEY_PAUSE, "PAUS" }, + { GLFW_KEY_F1, "FK01" }, + { GLFW_KEY_F2, "FK02" }, + { GLFW_KEY_F3, "FK03" }, + { GLFW_KEY_F4, "FK04" }, + { GLFW_KEY_F5, "FK05" }, + { GLFW_KEY_F6, "FK06" }, + { GLFW_KEY_F7, "FK07" }, + { GLFW_KEY_F8, "FK08" }, + { GLFW_KEY_F9, "FK09" }, + { GLFW_KEY_F10, "FK10" }, + { GLFW_KEY_F11, "FK11" }, + { GLFW_KEY_F12, "FK12" }, + { GLFW_KEY_F13, "FK13" }, + { GLFW_KEY_F14, "FK14" }, + { GLFW_KEY_F15, "FK15" }, + { GLFW_KEY_F16, "FK16" }, + { GLFW_KEY_F17, "FK17" }, + { GLFW_KEY_F18, "FK18" }, + { GLFW_KEY_F19, "FK19" }, + { GLFW_KEY_F20, "FK20" }, + { GLFW_KEY_F21, "FK21" }, + { GLFW_KEY_F22, "FK22" }, + { GLFW_KEY_F23, "FK23" }, + { GLFW_KEY_F24, "FK24" }, + { GLFW_KEY_F25, "FK25" }, + { GLFW_KEY_KP_0, "KP0" }, + { GLFW_KEY_KP_1, "KP1" }, + { GLFW_KEY_KP_2, "KP2" }, + { GLFW_KEY_KP_3, "KP3" }, + { GLFW_KEY_KP_4, "KP4" }, + { GLFW_KEY_KP_5, "KP5" }, + { GLFW_KEY_KP_6, "KP6" }, + { GLFW_KEY_KP_7, "KP7" }, + { GLFW_KEY_KP_8, "KP8" }, + { GLFW_KEY_KP_9, "KP9" }, + { GLFW_KEY_KP_DECIMAL, "KPDL" }, + { GLFW_KEY_KP_DIVIDE, "KPDV" }, + { GLFW_KEY_KP_MULTIPLY, "KPMU" }, + { GLFW_KEY_KP_SUBTRACT, "KPSU" }, + { GLFW_KEY_KP_ADD, "KPAD" }, + { GLFW_KEY_KP_ENTER, "KPEN" }, + { GLFW_KEY_KP_EQUAL, "KPEQ" }, + { GLFW_KEY_LEFT_SHIFT, "LFSH" }, + { GLFW_KEY_LEFT_CONTROL, "LCTL" }, + { GLFW_KEY_LEFT_ALT, "LALT" }, + { GLFW_KEY_LEFT_SUPER, "LWIN" }, + { GLFW_KEY_RIGHT_SHIFT, "RTSH" }, + { GLFW_KEY_RIGHT_CONTROL, "RCTL" }, + { GLFW_KEY_RIGHT_ALT, "RALT" }, + { GLFW_KEY_RIGHT_ALT, "LVL3" }, + { GLFW_KEY_RIGHT_ALT, "MDSW" }, + { GLFW_KEY_RIGHT_SUPER, "RWIN" }, + { GLFW_KEY_MENU, "MENU" } + }; // Find the X11 key code -> GLFW key code mapping - for (scancode = desc->min_key_code; scancode <= desc->max_key_code; scancode++) + for (scancode = scancodeMin; scancode <= scancodeMax; scancode++) { - memcpy(name, desc->names->keys[scancode].name, XkbKeyNameLength); - name[XkbKeyNameLength] = '\0'; + int key = GLFW_KEY_UNKNOWN; - // Map the key name to a GLFW key code. Note: We only map printable - // keys here, and we use the US keyboard layout. The rest of the - // keys (function keys) are mapped using traditional KeySym - // translations. - if (strcmp(name, "TLDE") == 0) key = GLFW_KEY_GRAVE_ACCENT; - else if (strcmp(name, "AE01") == 0) key = GLFW_KEY_1; - else if (strcmp(name, "AE02") == 0) key = GLFW_KEY_2; - else if (strcmp(name, "AE03") == 0) key = GLFW_KEY_3; - else if (strcmp(name, "AE04") == 0) key = GLFW_KEY_4; - else if (strcmp(name, "AE05") == 0) key = GLFW_KEY_5; - else if (strcmp(name, "AE06") == 0) key = GLFW_KEY_6; - else if (strcmp(name, "AE07") == 0) key = GLFW_KEY_7; - else if (strcmp(name, "AE08") == 0) key = GLFW_KEY_8; - else if (strcmp(name, "AE09") == 0) key = GLFW_KEY_9; - else if (strcmp(name, "AE10") == 0) key = GLFW_KEY_0; - else if (strcmp(name, "AE11") == 0) key = GLFW_KEY_MINUS; - else if (strcmp(name, "AE12") == 0) key = GLFW_KEY_EQUAL; - else if (strcmp(name, "AD01") == 0) key = GLFW_KEY_Q; - else if (strcmp(name, "AD02") == 0) key = GLFW_KEY_W; - else if (strcmp(name, "AD03") == 0) key = GLFW_KEY_E; - else if (strcmp(name, "AD04") == 0) key = GLFW_KEY_R; - else if (strcmp(name, "AD05") == 0) key = GLFW_KEY_T; - else if (strcmp(name, "AD06") == 0) key = GLFW_KEY_Y; - else if (strcmp(name, "AD07") == 0) key = GLFW_KEY_U; - else if (strcmp(name, "AD08") == 0) key = GLFW_KEY_I; - else if (strcmp(name, "AD09") == 0) key = GLFW_KEY_O; - else if (strcmp(name, "AD10") == 0) key = GLFW_KEY_P; - else if (strcmp(name, "AD11") == 0) key = GLFW_KEY_LEFT_BRACKET; - else if (strcmp(name, "AD12") == 0) key = GLFW_KEY_RIGHT_BRACKET; - else if (strcmp(name, "AC01") == 0) key = GLFW_KEY_A; - else if (strcmp(name, "AC02") == 0) key = GLFW_KEY_S; - else if (strcmp(name, "AC03") == 0) key = GLFW_KEY_D; - else if (strcmp(name, "AC04") == 0) key = GLFW_KEY_F; - else if (strcmp(name, "AC05") == 0) key = GLFW_KEY_G; - else if (strcmp(name, "AC06") == 0) key = GLFW_KEY_H; - else if (strcmp(name, "AC07") == 0) key = GLFW_KEY_J; - else if (strcmp(name, "AC08") == 0) key = GLFW_KEY_K; - else if (strcmp(name, "AC09") == 0) key = GLFW_KEY_L; - else if (strcmp(name, "AC10") == 0) key = GLFW_KEY_SEMICOLON; - else if (strcmp(name, "AC11") == 0) key = GLFW_KEY_APOSTROPHE; - else if (strcmp(name, "AB01") == 0) key = GLFW_KEY_Z; - else if (strcmp(name, "AB02") == 0) key = GLFW_KEY_X; - else if (strcmp(name, "AB03") == 0) key = GLFW_KEY_C; - else if (strcmp(name, "AB04") == 0) key = GLFW_KEY_V; - else if (strcmp(name, "AB05") == 0) key = GLFW_KEY_B; - else if (strcmp(name, "AB06") == 0) key = GLFW_KEY_N; - else if (strcmp(name, "AB07") == 0) key = GLFW_KEY_M; - else if (strcmp(name, "AB08") == 0) key = GLFW_KEY_COMMA; - else if (strcmp(name, "AB09") == 0) key = GLFW_KEY_PERIOD; - else if (strcmp(name, "AB10") == 0) key = GLFW_KEY_SLASH; - else if (strcmp(name, "BKSL") == 0) key = GLFW_KEY_BACKSLASH; - else if (strcmp(name, "LSGT") == 0) key = GLFW_KEY_WORLD_1; - else key = GLFW_KEY_UNKNOWN; + // Map the key name to a GLFW key code. Note: We use the US + // keyboard layout. Because function keys aren't mapped correctly + // when using traditional KeySym translations, they are mapped + // here instead. + for (int i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++) + { + if (strncmp(desc->names->keys[scancode].name, + keymap[i].name, + XkbKeyNameLength) == 0) + { + key = keymap[i].key; + break; + } + } - if ((scancode >= 0) && (scancode < 256)) - _glfw.x11.keycodes[scancode] = key; + // Fall back to key aliases in case the key name did not match + for (int i = 0; i < desc->names->num_key_aliases; i++) + { + if (key != GLFW_KEY_UNKNOWN) + break; + + if (strncmp(desc->names->key_aliases[i].real, + desc->names->keys[scancode].name, + XkbKeyNameLength) != 0) + { + continue; + } + + for (int j = 0; j < sizeof(keymap) / sizeof(keymap[0]); j++) + { + if (strncmp(desc->names->key_aliases[i].alias, + keymap[j].name, + XkbKeyNameLength) == 0) + { + key = keymap[j].key; + break; + } + } + } + + _glfw.x11.keycodes[scancode] = key; } XkbFreeNames(desc, XkbKeyNamesMask, True); XkbFreeKeyboard(desc, 0, True); } + else + XDisplayKeycodes(_glfw.x11.display, &scancodeMin, &scancodeMax); - for (scancode = 0; scancode < 256; scancode++) + int width; + KeySym* keysyms = XGetKeyboardMapping(_glfw.x11.display, + scancodeMin, + scancodeMax - scancodeMin + 1, + &width); + + for (scancode = scancodeMin; scancode <= scancodeMax; scancode++) { // Translate the un-translated key codes using traditional X11 KeySym // lookups if (_glfw.x11.keycodes[scancode] < 0) - _glfw.x11.keycodes[scancode] = translateKeyCode(scancode); + { + const size_t base = (scancode - scancodeMin) * width; + _glfw.x11.keycodes[scancode] = translateKeySyms(&keysyms[base], width); + } // Store the reverse translation for faster key name lookup if (_glfw.x11.keycodes[scancode] > 0) _glfw.x11.scancodes[_glfw.x11.keycodes[scancode]] = scancode; } + + XFree(keysyms); } // Check whether the IM has a usable style @@ -351,15 +455,49 @@ static GLFWbool hasUsableInputMethodStyle(void) return found; } -// Check whether the specified atom is supported +static void inputMethodDestroyCallback(XIM im, XPointer clientData, XPointer callData) +{ + _glfw.x11.im = NULL; +} + +static void inputMethodInstantiateCallback(Display* display, + XPointer clientData, + XPointer callData) +{ + if (_glfw.x11.im) + return; + + _glfw.x11.im = XOpenIM(_glfw.x11.display, 0, NULL, NULL); + if (_glfw.x11.im) + { + if (!hasUsableInputMethodStyle()) + { + XCloseIM(_glfw.x11.im); + _glfw.x11.im = NULL; + } + } + + if (_glfw.x11.im) + { + XIMCallback callback; + callback.callback = (XIMProc) inputMethodDestroyCallback; + callback.client_data = NULL; + XSetIMValues(_glfw.x11.im, XNDestroyCallback, &callback, NULL); + + for (_GLFWwindow* window = _glfw.windowListHead; window; window = window->next) + _glfwCreateInputContextX11(window); + } +} + +// Return the atom ID only if it is listed in the specified array // -static Atom getSupportedAtom(Atom* supportedAtoms, - unsigned long atomCount, - const char* atomName) +static Atom getAtomIfSupported(Atom* supportedAtoms, + unsigned long atomCount, + const char* atomName) { const Atom atom = XInternAtom(_glfw.x11.display, atomName, False); - for (unsigned int i = 0; i < atomCount; i++) + for (unsigned long i = 0; i < atomCount; i++) { if (supportedAtoms[i] == atom) return atom; @@ -427,33 +565,33 @@ static void detectEWMH(void) // See which of the atoms we support that are supported by the WM _glfw.x11.NET_WM_STATE = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE"); _glfw.x11.NET_WM_STATE_ABOVE = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE"); _glfw.x11.NET_WM_STATE_FULLSCREEN = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN"); _glfw.x11.NET_WM_STATE_MAXIMIZED_VERT = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT"); _glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ"); _glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION"); _glfw.x11.NET_WM_FULLSCREEN_MONITORS = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS"); _glfw.x11.NET_WM_WINDOW_TYPE = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE"); _glfw.x11.NET_WM_WINDOW_TYPE_NORMAL = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL"); _glfw.x11.NET_WORKAREA = - getSupportedAtom(supportedAtoms, atomCount, "_NET_WORKAREA"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA"); _glfw.x11.NET_CURRENT_DESKTOP = - getSupportedAtom(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP"); _glfw.x11.NET_ACTIVE_WINDOW = - getSupportedAtom(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW"); _glfw.x11.NET_FRAME_EXTENTS = - getSupportedAtom(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS"); _glfw.x11.NET_REQUEST_FRAME_EXTENTS = - getSupportedAtom(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS"); + getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS"); if (supportedAtoms) XFree(supportedAtoms); @@ -667,13 +805,12 @@ static GLFWbool initExtensions(void) _glfw.x11.xkb.detectable = GLFW_TRUE; } - _glfw.x11.xkb.group = 0; XkbStateRec state; if (XkbGetState(_glfw.x11.display, XkbUseCoreKbd, &state) == Success) - { - XkbSelectEventDetails(_glfw.x11.display, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask); _glfw.x11.xkb.group = (unsigned int)state.group; - } + + XkbSelectEventDetails(_glfw.x11.display, XkbUseCoreKbd, XkbStateNotify, + XkbGroupStateMask, XkbGroupStateMask); } #if defined(__CYGWIN__) @@ -714,6 +851,35 @@ static GLFWbool initExtensions(void) } } +#if defined(__CYGWIN__) + _glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so"); +#else + _glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6"); +#endif + if (_glfw.x11.xshape.handle) + { + _glfw.x11.xshape.QueryExtension = (PFN_XShapeQueryExtension) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeQueryExtension"); + _glfw.x11.xshape.ShapeCombineRegion = (PFN_XShapeCombineRegion) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeCombineRegion"); + _glfw.x11.xshape.QueryVersion = (PFN_XShapeQueryVersion) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeQueryVersion"); + _glfw.x11.xshape.ShapeCombineMask = (PFN_XShapeCombineMask) + _glfw_dlsym(_glfw.x11.xshape.handle, "XShapeCombineMask"); + + if (XShapeQueryExtension(_glfw.x11.display, + &_glfw.x11.xshape.errorBase, + &_glfw.x11.xshape.eventBase)) + { + if (XShapeQueryVersion(_glfw.x11.display, + &_glfw.x11.xshape.major, + &_glfw.x11.xshape.minor)) + { + _glfw.x11.xshape.available = GLFW_TRUE; + } + } + } + // Update the key code LUT // FIXME: We should listen to XkbMapNotify events to track changes to // the keyboard mapping. @@ -858,6 +1024,9 @@ static Window createHelperWindow(void) // static int errorHandler(Display *display, XErrorEvent* event) { + if (_glfw.x11.display != display) + return 0; + _glfw.x11.errorCode = event->error_code; return 0; } @@ -938,15 +1107,231 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot) int _glfwPlatformInit(void) { -#if !defined(X_HAVE_UTF8_STRING) - // HACK: If the current locale is "C" and the Xlib UTF-8 functions are - // unavailable, apply the environment's locale in the hope that it's - // both available and not "C" - // This is done because the "C" locale breaks wide character input, - // which is what we fall back on when UTF-8 support is missing + // HACK: If the application has left the locale as "C" then both wide + // character text input and explicit UTF-8 input via XIM will break + // This sets the CTYPE part of the current locale from the environment + // in the hope that it is set to something more sane than "C" if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0) setlocale(LC_CTYPE, ""); + +#if defined(__CYGWIN__) + _glfw.x11.xlib.handle = _glfw_dlopen("libX11-6.so"); +#else + _glfw.x11.xlib.handle = _glfw_dlopen("libX11.so.6"); #endif + if (!_glfw.x11.xlib.handle) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load Xlib"); + return GLFW_FALSE; + } + + _glfw.x11.xlib.AllocClassHint = (PFN_XAllocClassHint) + _glfw_dlsym(_glfw.x11.xlib.handle, "XAllocClassHint"); + _glfw.x11.xlib.AllocSizeHints = (PFN_XAllocSizeHints) + _glfw_dlsym(_glfw.x11.xlib.handle, "XAllocSizeHints"); + _glfw.x11.xlib.AllocWMHints = (PFN_XAllocWMHints) + _glfw_dlsym(_glfw.x11.xlib.handle, "XAllocWMHints"); + _glfw.x11.xlib.ChangeProperty = (PFN_XChangeProperty) + _glfw_dlsym(_glfw.x11.xlib.handle, "XChangeProperty"); + _glfw.x11.xlib.ChangeWindowAttributes = (PFN_XChangeWindowAttributes) + _glfw_dlsym(_glfw.x11.xlib.handle, "XChangeWindowAttributes"); + _glfw.x11.xlib.CheckIfEvent = (PFN_XCheckIfEvent) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCheckIfEvent"); + _glfw.x11.xlib.CheckTypedWindowEvent = (PFN_XCheckTypedWindowEvent) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCheckTypedWindowEvent"); + _glfw.x11.xlib.CloseDisplay = (PFN_XCloseDisplay) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCloseDisplay"); + _glfw.x11.xlib.CloseIM = (PFN_XCloseIM) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCloseIM"); + _glfw.x11.xlib.ConvertSelection = (PFN_XConvertSelection) + _glfw_dlsym(_glfw.x11.xlib.handle, "XConvertSelection"); + _glfw.x11.xlib.CreateColormap = (PFN_XCreateColormap) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateColormap"); + _glfw.x11.xlib.CreateFontCursor = (PFN_XCreateFontCursor) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateFontCursor"); + _glfw.x11.xlib.CreateIC = (PFN_XCreateIC) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateIC"); + _glfw.x11.xlib.CreateRegion = (PFN_XCreateRegion) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateRegion"); + _glfw.x11.xlib.CreateWindow = (PFN_XCreateWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XCreateWindow"); + _glfw.x11.xlib.DefineCursor = (PFN_XDefineCursor) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDefineCursor"); + _glfw.x11.xlib.DeleteContext = (PFN_XDeleteContext) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDeleteContext"); + _glfw.x11.xlib.DeleteProperty = (PFN_XDeleteProperty) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDeleteProperty"); + _glfw.x11.xlib.DestroyIC = (PFN_XDestroyIC) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDestroyIC"); + _glfw.x11.xlib.DestroyRegion = (PFN_XDestroyRegion) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDestroyRegion"); + _glfw.x11.xlib.DestroyWindow = (PFN_XDestroyWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDestroyWindow"); + _glfw.x11.xlib.DisplayKeycodes = (PFN_XDisplayKeycodes) + _glfw_dlsym(_glfw.x11.xlib.handle, "XDisplayKeycodes"); + _glfw.x11.xlib.EventsQueued = (PFN_XEventsQueued) + _glfw_dlsym(_glfw.x11.xlib.handle, "XEventsQueued"); + _glfw.x11.xlib.FilterEvent = (PFN_XFilterEvent) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFilterEvent"); + _glfw.x11.xlib.FindContext = (PFN_XFindContext) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFindContext"); + _glfw.x11.xlib.Flush = (PFN_XFlush) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFlush"); + _glfw.x11.xlib.Free = (PFN_XFree) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFree"); + _glfw.x11.xlib.FreeColormap = (PFN_XFreeColormap) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFreeColormap"); + _glfw.x11.xlib.FreeCursor = (PFN_XFreeCursor) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFreeCursor"); + _glfw.x11.xlib.FreeEventData = (PFN_XFreeEventData) + _glfw_dlsym(_glfw.x11.xlib.handle, "XFreeEventData"); + _glfw.x11.xlib.GetErrorText = (PFN_XGetErrorText) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetErrorText"); + _glfw.x11.xlib.GetEventData = (PFN_XGetEventData) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetEventData"); + _glfw.x11.xlib.GetICValues = (PFN_XGetICValues) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetICValues"); + _glfw.x11.xlib.GetIMValues = (PFN_XGetIMValues) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetIMValues"); + _glfw.x11.xlib.GetInputFocus = (PFN_XGetInputFocus) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetInputFocus"); + _glfw.x11.xlib.GetKeyboardMapping = (PFN_XGetKeyboardMapping) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetKeyboardMapping"); + _glfw.x11.xlib.GetScreenSaver = (PFN_XGetScreenSaver) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetScreenSaver"); + _glfw.x11.xlib.GetSelectionOwner = (PFN_XGetSelectionOwner) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetSelectionOwner"); + _glfw.x11.xlib.GetVisualInfo = (PFN_XGetVisualInfo) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetVisualInfo"); + _glfw.x11.xlib.GetWMNormalHints = (PFN_XGetWMNormalHints) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetWMNormalHints"); + _glfw.x11.xlib.GetWindowAttributes = (PFN_XGetWindowAttributes) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetWindowAttributes"); + _glfw.x11.xlib.GetWindowProperty = (PFN_XGetWindowProperty) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGetWindowProperty"); + _glfw.x11.xlib.GrabPointer = (PFN_XGrabPointer) + _glfw_dlsym(_glfw.x11.xlib.handle, "XGrabPointer"); + _glfw.x11.xlib.IconifyWindow = (PFN_XIconifyWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XIconifyWindow"); + _glfw.x11.xlib.InitThreads = (PFN_XInitThreads) + _glfw_dlsym(_glfw.x11.xlib.handle, "XInitThreads"); + _glfw.x11.xlib.InternAtom = (PFN_XInternAtom) + _glfw_dlsym(_glfw.x11.xlib.handle, "XInternAtom"); + _glfw.x11.xlib.LookupString = (PFN_XLookupString) + _glfw_dlsym(_glfw.x11.xlib.handle, "XLookupString"); + _glfw.x11.xlib.MapRaised = (PFN_XMapRaised) + _glfw_dlsym(_glfw.x11.xlib.handle, "XMapRaised"); + _glfw.x11.xlib.MapWindow = (PFN_XMapWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XMapWindow"); + _glfw.x11.xlib.MoveResizeWindow = (PFN_XMoveResizeWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XMoveResizeWindow"); + _glfw.x11.xlib.MoveWindow = (PFN_XMoveWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XMoveWindow"); + _glfw.x11.xlib.NextEvent = (PFN_XNextEvent) + _glfw_dlsym(_glfw.x11.xlib.handle, "XNextEvent"); + _glfw.x11.xlib.OpenDisplay = (PFN_XOpenDisplay) + _glfw_dlsym(_glfw.x11.xlib.handle, "XOpenDisplay"); + _glfw.x11.xlib.OpenIM = (PFN_XOpenIM) + _glfw_dlsym(_glfw.x11.xlib.handle, "XOpenIM"); + _glfw.x11.xlib.PeekEvent = (PFN_XPeekEvent) + _glfw_dlsym(_glfw.x11.xlib.handle, "XPeekEvent"); + _glfw.x11.xlib.Pending = (PFN_XPending) + _glfw_dlsym(_glfw.x11.xlib.handle, "XPending"); + _glfw.x11.xlib.QueryExtension = (PFN_XQueryExtension) + _glfw_dlsym(_glfw.x11.xlib.handle, "XQueryExtension"); + _glfw.x11.xlib.QueryPointer = (PFN_XQueryPointer) + _glfw_dlsym(_glfw.x11.xlib.handle, "XQueryPointer"); + _glfw.x11.xlib.RaiseWindow = (PFN_XRaiseWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XRaiseWindow"); + _glfw.x11.xlib.RegisterIMInstantiateCallback = (PFN_XRegisterIMInstantiateCallback) + _glfw_dlsym(_glfw.x11.xlib.handle, "XRegisterIMInstantiateCallback"); + _glfw.x11.xlib.ResizeWindow = (PFN_XResizeWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XResizeWindow"); + _glfw.x11.xlib.ResourceManagerString = (PFN_XResourceManagerString) + _glfw_dlsym(_glfw.x11.xlib.handle, "XResourceManagerString"); + _glfw.x11.xlib.SaveContext = (PFN_XSaveContext) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSaveContext"); + _glfw.x11.xlib.SelectInput = (PFN_XSelectInput) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSelectInput"); + _glfw.x11.xlib.SendEvent = (PFN_XSendEvent) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSendEvent"); + _glfw.x11.xlib.SetClassHint = (PFN_XSetClassHint) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetClassHint"); + _glfw.x11.xlib.SetErrorHandler = (PFN_XSetErrorHandler) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetErrorHandler"); + _glfw.x11.xlib.SetICFocus = (PFN_XSetICFocus) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetICFocus"); + _glfw.x11.xlib.SetIMValues = (PFN_XSetIMValues) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetIMValues"); + _glfw.x11.xlib.SetInputFocus = (PFN_XSetInputFocus) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetInputFocus"); + _glfw.x11.xlib.SetLocaleModifiers = (PFN_XSetLocaleModifiers) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetLocaleModifiers"); + _glfw.x11.xlib.SetScreenSaver = (PFN_XSetScreenSaver) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetScreenSaver"); + _glfw.x11.xlib.SetSelectionOwner = (PFN_XSetSelectionOwner) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetSelectionOwner"); + _glfw.x11.xlib.SetWMHints = (PFN_XSetWMHints) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetWMHints"); + _glfw.x11.xlib.SetWMNormalHints = (PFN_XSetWMNormalHints) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetWMNormalHints"); + _glfw.x11.xlib.SetWMProtocols = (PFN_XSetWMProtocols) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSetWMProtocols"); + _glfw.x11.xlib.SupportsLocale = (PFN_XSupportsLocale) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSupportsLocale"); + _glfw.x11.xlib.Sync = (PFN_XSync) + _glfw_dlsym(_glfw.x11.xlib.handle, "XSync"); + _glfw.x11.xlib.TranslateCoordinates = (PFN_XTranslateCoordinates) + _glfw_dlsym(_glfw.x11.xlib.handle, "XTranslateCoordinates"); + _glfw.x11.xlib.UndefineCursor = (PFN_XUndefineCursor) + _glfw_dlsym(_glfw.x11.xlib.handle, "XUndefineCursor"); + _glfw.x11.xlib.UngrabPointer = (PFN_XUngrabPointer) + _glfw_dlsym(_glfw.x11.xlib.handle, "XUngrabPointer"); + _glfw.x11.xlib.UnmapWindow = (PFN_XUnmapWindow) + _glfw_dlsym(_glfw.x11.xlib.handle, "XUnmapWindow"); + _glfw.x11.xlib.UnsetICFocus = (PFN_XUnsetICFocus) + _glfw_dlsym(_glfw.x11.xlib.handle, "XUnsetICFocus"); + _glfw.x11.xlib.VisualIDFromVisual = (PFN_XVisualIDFromVisual) + _glfw_dlsym(_glfw.x11.xlib.handle, "XVisualIDFromVisual"); + _glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer) + _glfw_dlsym(_glfw.x11.xlib.handle, "XWarpPointer"); + _glfw.x11.xkb.FreeKeyboard = (PFN_XkbFreeKeyboard) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbFreeKeyboard"); + _glfw.x11.xkb.FreeNames = (PFN_XkbFreeNames) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbFreeNames"); + _glfw.x11.xkb.GetMap = (PFN_XkbGetMap) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbGetMap"); + _glfw.x11.xkb.GetNames = (PFN_XkbGetNames) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbGetNames"); + _glfw.x11.xkb.GetState = (PFN_XkbGetState) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbGetState"); + _glfw.x11.xkb.KeycodeToKeysym = (PFN_XkbKeycodeToKeysym) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbKeycodeToKeysym"); + _glfw.x11.xkb.QueryExtension = (PFN_XkbQueryExtension) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbQueryExtension"); + _glfw.x11.xkb.SelectEventDetails = (PFN_XkbSelectEventDetails) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbSelectEventDetails"); + _glfw.x11.xkb.SetDetectableAutoRepeat = (PFN_XkbSetDetectableAutoRepeat) + _glfw_dlsym(_glfw.x11.xlib.handle, "XkbSetDetectableAutoRepeat"); + _glfw.x11.xrm.DestroyDatabase = (PFN_XrmDestroyDatabase) + _glfw_dlsym(_glfw.x11.xlib.handle, "XrmDestroyDatabase"); + _glfw.x11.xrm.GetResource = (PFN_XrmGetResource) + _glfw_dlsym(_glfw.x11.xlib.handle, "XrmGetResource"); + _glfw.x11.xrm.GetStringDatabase = (PFN_XrmGetStringDatabase) + _glfw_dlsym(_glfw.x11.xlib.handle, "XrmGetStringDatabase"); + _glfw.x11.xrm.Initialize = (PFN_XrmInitialize) + _glfw_dlsym(_glfw.x11.xlib.handle, "XrmInitialize"); + _glfw.x11.xrm.UniqueQuark = (PFN_XrmUniqueQuark) + _glfw_dlsym(_glfw.x11.xlib.handle, "XrmUniqueQuark"); + _glfw.x11.xlib.UnregisterIMInstantiateCallback = (PFN_XUnregisterIMInstantiateCallback) + _glfw_dlsym(_glfw.x11.xlib.handle, "XUnregisterIMInstantiateCallback"); + _glfw.x11.xlib.utf8LookupString = (PFN_Xutf8LookupString) + _glfw_dlsym(_glfw.x11.xlib.handle, "Xutf8LookupString"); + _glfw.x11.xlib.utf8SetWMProperties = (PFN_Xutf8SetWMProperties) + _glfw_dlsym(_glfw.x11.xlib.handle, "Xutf8SetWMProperties"); + + if (_glfw.x11.xlib.utf8LookupString && _glfw.x11.xlib.utf8SetWMProperties) + _glfw.x11.xlib.utf8 = GLFW_TRUE; XInitThreads(); XrmInitialize(); @@ -981,26 +1366,17 @@ int _glfwPlatformInit(void) _glfw.x11.helperWindowHandle = createHelperWindow(); _glfw.x11.hiddenCursorHandle = createHiddenCursor(); - if (XSupportsLocale()) + if (XSupportsLocale() && _glfw.x11.xlib.utf8) { XSetLocaleModifiers(""); - _glfw.x11.im = XOpenIM(_glfw.x11.display, 0, NULL, NULL); - if (_glfw.x11.im) - { - if (!hasUsableInputMethodStyle()) - { - XCloseIM(_glfw.x11.im); - _glfw.x11.im = NULL; - } - } + // If an IM is already present our callback will be called right away + XRegisterIMInstantiateCallback(_glfw.x11.display, + NULL, NULL, NULL, + inputMethodInstantiateCallback, + NULL); } -#if defined(__linux__) - if (!_glfwInitJoysticksLinux()) - return GLFW_FALSE; -#endif - _glfwInitTimerPOSIX(); _glfwPollMonitorsX11(); @@ -1030,6 +1406,11 @@ void _glfwPlatformTerminate(void) free(_glfw.x11.primarySelectionString); free(_glfw.x11.clipboardString); + XUnregisterIMInstantiateCallback(_glfw.x11.display, + NULL, NULL, NULL, + inputMethodInstantiateCallback, + NULL); + if (_glfw.x11.im) { XCloseIM(_glfw.x11.im); @@ -1089,9 +1470,11 @@ void _glfwPlatformTerminate(void) _glfwTerminateEGL(); _glfwTerminateGLX(); -#if defined(__linux__) - _glfwTerminateJoysticksLinux(); -#endif + if (_glfw.x11.xlib.handle) + { + _glfw_dlclose(_glfw.x11.xlib.handle); + _glfw.x11.xlib.handle = NULL; + } } const char* _glfwPlatformGetVersionString(void) diff --git a/raylib/external/glfw/src/x11_monitor.c b/raylib/external/glfw/src/x11_monitor.c index 809b93e..8de8659 100644 --- a/raylib/external/glfw/src/x11_monitor.c +++ b/raylib/external/glfw/src/x11_monitor.c @@ -164,7 +164,7 @@ void _glfwPollMonitorsX11(void) if (widthMM <= 0 || heightMM <= 0) { // HACK: If RandR does not provide a physical size, assume the - // X11 default 96 DPI and calcuate from the CRTC viewport + // X11 default 96 DPI and calculate from the CRTC viewport // NOTE: These members are affected by rotation, unlike the mode // info and output info members widthMM = (int) (ci->width * 25.4f / 96.f); diff --git a/raylib/external/glfw/src/x11_platform.h b/raylib/external/glfw/src/x11_platform.h index 04c4664..c5137bc 100644 --- a/raylib/external/glfw/src/x11_platform.h +++ b/raylib/external/glfw/src/x11_platform.h @@ -33,6 +33,7 @@ #include #include #include +#include #include // The XRandR extension provides mode setting and gamma control @@ -47,6 +48,214 @@ // The XInput extension provides raw mouse motion input #include +// The Shape extension provides custom window shapes +#include + +typedef XClassHint* (* PFN_XAllocClassHint)(void); +typedef XSizeHints* (* PFN_XAllocSizeHints)(void); +typedef XWMHints* (* PFN_XAllocWMHints)(void); +typedef int (* PFN_XChangeProperty)(Display*,Window,Atom,Atom,int,int,const unsigned char*,int); +typedef int (* PFN_XChangeWindowAttributes)(Display*,Window,unsigned long,XSetWindowAttributes*); +typedef Bool (* PFN_XCheckIfEvent)(Display*,XEvent*,Bool(*)(Display*,XEvent*,XPointer),XPointer); +typedef Bool (* PFN_XCheckTypedWindowEvent)(Display*,Window,int,XEvent*); +typedef int (* PFN_XCloseDisplay)(Display*); +typedef Status (* PFN_XCloseIM)(XIM); +typedef int (* PFN_XConvertSelection)(Display*,Atom,Atom,Atom,Window,Time); +typedef Colormap (* PFN_XCreateColormap)(Display*,Window,Visual*,int); +typedef Cursor (* PFN_XCreateFontCursor)(Display*,unsigned int); +typedef XIC (* PFN_XCreateIC)(XIM,...); +typedef Region (* PFN_XCreateRegion)(void); +typedef Window (* PFN_XCreateWindow)(Display*,Window,int,int,unsigned int,unsigned int,unsigned int,int,unsigned int,Visual*,unsigned long,XSetWindowAttributes*); +typedef int (* PFN_XDefineCursor)(Display*,Window,Cursor); +typedef int (* PFN_XDeleteContext)(Display*,XID,XContext); +typedef int (* PFN_XDeleteProperty)(Display*,Window,Atom); +typedef void (* PFN_XDestroyIC)(XIC); +typedef int (* PFN_XDestroyRegion)(Region); +typedef int (* PFN_XDestroyWindow)(Display*,Window); +typedef int (* PFN_XDisplayKeycodes)(Display*,int*,int*); +typedef int (* PFN_XEventsQueued)(Display*,int); +typedef Bool (* PFN_XFilterEvent)(XEvent*,Window); +typedef int (* PFN_XFindContext)(Display*,XID,XContext,XPointer*); +typedef int (* PFN_XFlush)(Display*); +typedef int (* PFN_XFree)(void*); +typedef int (* PFN_XFreeColormap)(Display*,Colormap); +typedef int (* PFN_XFreeCursor)(Display*,Cursor); +typedef void (* PFN_XFreeEventData)(Display*,XGenericEventCookie*); +typedef int (* PFN_XGetErrorText)(Display*,int,char*,int); +typedef Bool (* PFN_XGetEventData)(Display*,XGenericEventCookie*); +typedef char* (* PFN_XGetICValues)(XIC,...); +typedef char* (* PFN_XGetIMValues)(XIM,...); +typedef int (* PFN_XGetInputFocus)(Display*,Window*,int*); +typedef KeySym* (* PFN_XGetKeyboardMapping)(Display*,KeyCode,int,int*); +typedef int (* PFN_XGetScreenSaver)(Display*,int*,int*,int*,int*); +typedef Window (* PFN_XGetSelectionOwner)(Display*,Atom); +typedef XVisualInfo* (* PFN_XGetVisualInfo)(Display*,long,XVisualInfo*,int*); +typedef Status (* PFN_XGetWMNormalHints)(Display*,Window,XSizeHints*,long*); +typedef Status (* PFN_XGetWindowAttributes)(Display*,Window,XWindowAttributes*); +typedef int (* PFN_XGetWindowProperty)(Display*,Window,Atom,long,long,Bool,Atom,Atom*,int*,unsigned long*,unsigned long*,unsigned char**); +typedef int (* PFN_XGrabPointer)(Display*,Window,Bool,unsigned int,int,int,Window,Cursor,Time); +typedef Status (* PFN_XIconifyWindow)(Display*,Window,int); +typedef Status (* PFN_XInitThreads)(void); +typedef Atom (* PFN_XInternAtom)(Display*,const char*,Bool); +typedef int (* PFN_XLookupString)(XKeyEvent*,char*,int,KeySym*,XComposeStatus*); +typedef int (* PFN_XMapRaised)(Display*,Window); +typedef int (* PFN_XMapWindow)(Display*,Window); +typedef int (* PFN_XMoveResizeWindow)(Display*,Window,int,int,unsigned int,unsigned int); +typedef int (* PFN_XMoveWindow)(Display*,Window,int,int); +typedef int (* PFN_XNextEvent)(Display*,XEvent*); +typedef Display* (* PFN_XOpenDisplay)(const char*); +typedef XIM (* PFN_XOpenIM)(Display*,XrmDatabase*,char*,char*); +typedef int (* PFN_XPeekEvent)(Display*,XEvent*); +typedef int (* PFN_XPending)(Display*); +typedef Bool (* PFN_XQueryExtension)(Display*,const char*,int*,int*,int*); +typedef Bool (* PFN_XQueryPointer)(Display*,Window,Window*,Window*,int*,int*,int*,int*,unsigned int*); +typedef int (* PFN_XRaiseWindow)(Display*,Window); +typedef Bool (* PFN_XRegisterIMInstantiateCallback)(Display*,void*,char*,char*,XIDProc,XPointer); +typedef int (* PFN_XResizeWindow)(Display*,Window,unsigned int,unsigned int); +typedef char* (* PFN_XResourceManagerString)(Display*); +typedef int (* PFN_XSaveContext)(Display*,XID,XContext,const char*); +typedef int (* PFN_XSelectInput)(Display*,Window,long); +typedef Status (* PFN_XSendEvent)(Display*,Window,Bool,long,XEvent*); +typedef int (* PFN_XSetClassHint)(Display*,Window,XClassHint*); +typedef XErrorHandler (* PFN_XSetErrorHandler)(XErrorHandler); +typedef void (* PFN_XSetICFocus)(XIC); +typedef char* (* PFN_XSetIMValues)(XIM,...); +typedef int (* PFN_XSetInputFocus)(Display*,Window,int,Time); +typedef char* (* PFN_XSetLocaleModifiers)(const char*); +typedef int (* PFN_XSetScreenSaver)(Display*,int,int,int,int); +typedef int (* PFN_XSetSelectionOwner)(Display*,Atom,Window,Time); +typedef int (* PFN_XSetWMHints)(Display*,Window,XWMHints*); +typedef void (* PFN_XSetWMNormalHints)(Display*,Window,XSizeHints*); +typedef Status (* PFN_XSetWMProtocols)(Display*,Window,Atom*,int); +typedef Bool (* PFN_XSupportsLocale)(void); +typedef int (* PFN_XSync)(Display*,Bool); +typedef Bool (* PFN_XTranslateCoordinates)(Display*,Window,Window,int,int,int*,int*,Window*); +typedef int (* PFN_XUndefineCursor)(Display*,Window); +typedef int (* PFN_XUngrabPointer)(Display*,Time); +typedef int (* PFN_XUnmapWindow)(Display*,Window); +typedef void (* PFN_XUnsetICFocus)(XIC); +typedef VisualID (* PFN_XVisualIDFromVisual)(Visual*); +typedef int (* PFN_XWarpPointer)(Display*,Window,Window,int,int,unsigned int,unsigned int,int,int); +typedef void (* PFN_XkbFreeKeyboard)(XkbDescPtr,unsigned int,Bool); +typedef void (* PFN_XkbFreeNames)(XkbDescPtr,unsigned int,Bool); +typedef XkbDescPtr (* PFN_XkbGetMap)(Display*,unsigned int,unsigned int); +typedef Status (* PFN_XkbGetNames)(Display*,unsigned int,XkbDescPtr); +typedef Status (* PFN_XkbGetState)(Display*,unsigned int,XkbStatePtr); +typedef KeySym (* PFN_XkbKeycodeToKeysym)(Display*,KeyCode,int,int); +typedef Bool (* PFN_XkbQueryExtension)(Display*,int*,int*,int*,int*,int*); +typedef Bool (* PFN_XkbSelectEventDetails)(Display*,unsigned int,unsigned int,unsigned long,unsigned long); +typedef Bool (* PFN_XkbSetDetectableAutoRepeat)(Display*,Bool,Bool*); +typedef void (* PFN_XrmDestroyDatabase)(XrmDatabase); +typedef Bool (* PFN_XrmGetResource)(XrmDatabase,const char*,const char*,char**,XrmValue*); +typedef XrmDatabase (* PFN_XrmGetStringDatabase)(const char*); +typedef void (* PFN_XrmInitialize)(void); +typedef XrmQuark (* PFN_XrmUniqueQuark)(void); +typedef Bool (* PFN_XUnregisterIMInstantiateCallback)(Display*,void*,char*,char*,XIDProc,XPointer); +typedef int (* PFN_Xutf8LookupString)(XIC,XKeyPressedEvent*,char*,int,KeySym*,Status*); +typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char*,char**,int,XSizeHints*,XWMHints*,XClassHint*); +#define XAllocClassHint _glfw.x11.xlib.AllocClassHint +#define XAllocSizeHints _glfw.x11.xlib.AllocSizeHints +#define XAllocWMHints _glfw.x11.xlib.AllocWMHints +#define XChangeProperty _glfw.x11.xlib.ChangeProperty +#define XChangeWindowAttributes _glfw.x11.xlib.ChangeWindowAttributes +#define XCheckIfEvent _glfw.x11.xlib.CheckIfEvent +#define XCheckTypedWindowEvent _glfw.x11.xlib.CheckTypedWindowEvent +#define XCloseDisplay _glfw.x11.xlib.CloseDisplay +#define XCloseIM _glfw.x11.xlib.CloseIM +#define XConvertSelection _glfw.x11.xlib.ConvertSelection +#define XCreateColormap _glfw.x11.xlib.CreateColormap +#define XCreateFontCursor _glfw.x11.xlib.CreateFontCursor +#define XCreateIC _glfw.x11.xlib.CreateIC +#define XCreateRegion _glfw.x11.xlib.CreateRegion +#define XCreateWindow _glfw.x11.xlib.CreateWindow +#define XDefineCursor _glfw.x11.xlib.DefineCursor +#define XDeleteContext _glfw.x11.xlib.DeleteContext +#define XDeleteProperty _glfw.x11.xlib.DeleteProperty +#define XDestroyIC _glfw.x11.xlib.DestroyIC +#define XDestroyRegion _glfw.x11.xlib.DestroyRegion +#define XDestroyWindow _glfw.x11.xlib.DestroyWindow +#define XDisplayKeycodes _glfw.x11.xlib.DisplayKeycodes +#define XEventsQueued _glfw.x11.xlib.EventsQueued +#define XFilterEvent _glfw.x11.xlib.FilterEvent +#define XFindContext _glfw.x11.xlib.FindContext +#define XFlush _glfw.x11.xlib.Flush +#define XFree _glfw.x11.xlib.Free +#define XFreeColormap _glfw.x11.xlib.FreeColormap +#define XFreeCursor _glfw.x11.xlib.FreeCursor +#define XFreeEventData _glfw.x11.xlib.FreeEventData +#define XGetErrorText _glfw.x11.xlib.GetErrorText +#define XGetEventData _glfw.x11.xlib.GetEventData +#define XGetICValues _glfw.x11.xlib.GetICValues +#define XGetIMValues _glfw.x11.xlib.GetIMValues +#define XGetInputFocus _glfw.x11.xlib.GetInputFocus +#define XGetKeyboardMapping _glfw.x11.xlib.GetKeyboardMapping +#define XGetScreenSaver _glfw.x11.xlib.GetScreenSaver +#define XGetSelectionOwner _glfw.x11.xlib.GetSelectionOwner +#define XGetVisualInfo _glfw.x11.xlib.GetVisualInfo +#define XGetWMNormalHints _glfw.x11.xlib.GetWMNormalHints +#define XGetWindowAttributes _glfw.x11.xlib.GetWindowAttributes +#define XGetWindowProperty _glfw.x11.xlib.GetWindowProperty +#define XGrabPointer _glfw.x11.xlib.GrabPointer +#define XIconifyWindow _glfw.x11.xlib.IconifyWindow +#define XInitThreads _glfw.x11.xlib.InitThreads +#define XInternAtom _glfw.x11.xlib.InternAtom +#define XLookupString _glfw.x11.xlib.LookupString +#define XMapRaised _glfw.x11.xlib.MapRaised +#define XMapWindow _glfw.x11.xlib.MapWindow +#define XMoveResizeWindow _glfw.x11.xlib.MoveResizeWindow +#define XMoveWindow _glfw.x11.xlib.MoveWindow +#define XNextEvent _glfw.x11.xlib.NextEvent +#define XOpenDisplay _glfw.x11.xlib.OpenDisplay +#define XOpenIM _glfw.x11.xlib.OpenIM +#define XPeekEvent _glfw.x11.xlib.PeekEvent +#define XPending _glfw.x11.xlib.Pending +#define XQueryExtension _glfw.x11.xlib.QueryExtension +#define XQueryPointer _glfw.x11.xlib.QueryPointer +#define XRaiseWindow _glfw.x11.xlib.RaiseWindow +#define XRegisterIMInstantiateCallback _glfw.x11.xlib.RegisterIMInstantiateCallback +#define XResizeWindow _glfw.x11.xlib.ResizeWindow +#define XResourceManagerString _glfw.x11.xlib.ResourceManagerString +#define XSaveContext _glfw.x11.xlib.SaveContext +#define XSelectInput _glfw.x11.xlib.SelectInput +#define XSendEvent _glfw.x11.xlib.SendEvent +#define XSetClassHint _glfw.x11.xlib.SetClassHint +#define XSetErrorHandler _glfw.x11.xlib.SetErrorHandler +#define XSetICFocus _glfw.x11.xlib.SetICFocus +#define XSetIMValues _glfw.x11.xlib.SetIMValues +#define XSetInputFocus _glfw.x11.xlib.SetInputFocus +#define XSetLocaleModifiers _glfw.x11.xlib.SetLocaleModifiers +#define XSetScreenSaver _glfw.x11.xlib.SetScreenSaver +#define XSetSelectionOwner _glfw.x11.xlib.SetSelectionOwner +#define XSetWMHints _glfw.x11.xlib.SetWMHints +#define XSetWMNormalHints _glfw.x11.xlib.SetWMNormalHints +#define XSetWMProtocols _glfw.x11.xlib.SetWMProtocols +#define XSupportsLocale _glfw.x11.xlib.SupportsLocale +#define XSync _glfw.x11.xlib.Sync +#define XTranslateCoordinates _glfw.x11.xlib.TranslateCoordinates +#define XUndefineCursor _glfw.x11.xlib.UndefineCursor +#define XUngrabPointer _glfw.x11.xlib.UngrabPointer +#define XUnmapWindow _glfw.x11.xlib.UnmapWindow +#define XUnsetICFocus _glfw.x11.xlib.UnsetICFocus +#define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual +#define XWarpPointer _glfw.x11.xlib.WarpPointer +#define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard +#define XkbFreeNames _glfw.x11.xkb.FreeNames +#define XkbGetMap _glfw.x11.xkb.GetMap +#define XkbGetNames _glfw.x11.xkb.GetNames +#define XkbGetState _glfw.x11.xkb.GetState +#define XkbKeycodeToKeysym _glfw.x11.xkb.KeycodeToKeysym +#define XkbQueryExtension _glfw.x11.xkb.QueryExtension +#define XkbSelectEventDetails _glfw.x11.xkb.SelectEventDetails +#define XkbSetDetectableAutoRepeat _glfw.x11.xkb.SetDetectableAutoRepeat +#define XrmDestroyDatabase _glfw.x11.xrm.DestroyDatabase +#define XrmGetResource _glfw.x11.xrm.GetResource +#define XrmGetStringDatabase _glfw.x11.xrm.GetStringDatabase +#define XrmInitialize _glfw.x11.xrm.Initialize +#define XrmUniqueQuark _glfw.x11.xrm.UniqueQuark +#define XUnregisterIMInstantiateCallback _glfw.x11.xlib.UnregisterIMInstantiateCallback +#define Xutf8LookupString _glfw.x11.xlib.utf8LookupString +#define Xutf8SetWMProperties _glfw.x11.xlib.utf8SetWMProperties + typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int); typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*); typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*); @@ -129,6 +338,16 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const #define XRenderQueryVersion _glfw.x11.xrender.QueryVersion #define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat +typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*); +typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*); +typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int); +typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int); + +#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension +#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion +#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion +#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask + typedef VkFlags VkXlibSurfaceCreateFlagsKHR; typedef VkFlags VkXcbSurfaceCreateFlagsKHR; @@ -159,8 +378,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk #include "posix_time.h" #include "xkb_unicode.h" #include "glx_context.h" -#include "egl_context.h" -#include "osmesa_context.h" #if defined(__linux__) #include "linux_joystick.h" #else @@ -171,9 +388,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk #define _glfw_dlclose(handle) dlclose(handle) #define _glfw_dlsym(handle, name) dlsym(handle, name) -#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->x11.handle) -#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.x11.display) - #define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11 #define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11 #define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11 @@ -205,8 +419,9 @@ typedef struct _GLFWwindowX11 // The last position the cursor was warped to by GLFW int warpCursorPosX, warpCursorPosY; - // The time of the last KeyPress event - Time lastKeyTime; + // The time of the last KeyPress event per keycode, for discarding + // duplicate key events generated for some keys by ibus + Time keyPressTimes[256]; } _GLFWwindowX11; @@ -301,6 +516,107 @@ typedef struct _GLFWlibraryX11 Atom ATOM_PAIR; Atom GLFW_SELECTION; + struct { + void* handle; + GLFWbool utf8; + PFN_XAllocClassHint AllocClassHint; + PFN_XAllocSizeHints AllocSizeHints; + PFN_XAllocWMHints AllocWMHints; + PFN_XChangeProperty ChangeProperty; + PFN_XChangeWindowAttributes ChangeWindowAttributes; + PFN_XCheckIfEvent CheckIfEvent; + PFN_XCheckTypedWindowEvent CheckTypedWindowEvent; + PFN_XCloseDisplay CloseDisplay; + PFN_XCloseIM CloseIM; + PFN_XConvertSelection ConvertSelection; + PFN_XCreateColormap CreateColormap; + PFN_XCreateFontCursor CreateFontCursor; + PFN_XCreateIC CreateIC; + PFN_XCreateRegion CreateRegion; + PFN_XCreateWindow CreateWindow; + PFN_XDefineCursor DefineCursor; + PFN_XDeleteContext DeleteContext; + PFN_XDeleteProperty DeleteProperty; + PFN_XDestroyIC DestroyIC; + PFN_XDestroyRegion DestroyRegion; + PFN_XDestroyWindow DestroyWindow; + PFN_XDisplayKeycodes DisplayKeycodes; + PFN_XEventsQueued EventsQueued; + PFN_XFilterEvent FilterEvent; + PFN_XFindContext FindContext; + PFN_XFlush Flush; + PFN_XFree Free; + PFN_XFreeColormap FreeColormap; + PFN_XFreeCursor FreeCursor; + PFN_XFreeEventData FreeEventData; + PFN_XGetErrorText GetErrorText; + PFN_XGetEventData GetEventData; + PFN_XGetICValues GetICValues; + PFN_XGetIMValues GetIMValues; + PFN_XGetInputFocus GetInputFocus; + PFN_XGetKeyboardMapping GetKeyboardMapping; + PFN_XGetScreenSaver GetScreenSaver; + PFN_XGetSelectionOwner GetSelectionOwner; + PFN_XGetVisualInfo GetVisualInfo; + PFN_XGetWMNormalHints GetWMNormalHints; + PFN_XGetWindowAttributes GetWindowAttributes; + PFN_XGetWindowProperty GetWindowProperty; + PFN_XGrabPointer GrabPointer; + PFN_XIconifyWindow IconifyWindow; + PFN_XInitThreads InitThreads; + PFN_XInternAtom InternAtom; + PFN_XLookupString LookupString; + PFN_XMapRaised MapRaised; + PFN_XMapWindow MapWindow; + PFN_XMoveResizeWindow MoveResizeWindow; + PFN_XMoveWindow MoveWindow; + PFN_XNextEvent NextEvent; + PFN_XOpenDisplay OpenDisplay; + PFN_XOpenIM OpenIM; + PFN_XPeekEvent PeekEvent; + PFN_XPending Pending; + PFN_XQueryExtension QueryExtension; + PFN_XQueryPointer QueryPointer; + PFN_XRaiseWindow RaiseWindow; + PFN_XRegisterIMInstantiateCallback RegisterIMInstantiateCallback; + PFN_XResizeWindow ResizeWindow; + PFN_XResourceManagerString ResourceManagerString; + PFN_XSaveContext SaveContext; + PFN_XSelectInput SelectInput; + PFN_XSendEvent SendEvent; + PFN_XSetClassHint SetClassHint; + PFN_XSetErrorHandler SetErrorHandler; + PFN_XSetICFocus SetICFocus; + PFN_XSetIMValues SetIMValues; + PFN_XSetInputFocus SetInputFocus; + PFN_XSetLocaleModifiers SetLocaleModifiers; + PFN_XSetScreenSaver SetScreenSaver; + PFN_XSetSelectionOwner SetSelectionOwner; + PFN_XSetWMHints SetWMHints; + PFN_XSetWMNormalHints SetWMNormalHints; + PFN_XSetWMProtocols SetWMProtocols; + PFN_XSupportsLocale SupportsLocale; + PFN_XSync Sync; + PFN_XTranslateCoordinates TranslateCoordinates; + PFN_XUndefineCursor UndefineCursor; + PFN_XUngrabPointer UngrabPointer; + PFN_XUnmapWindow UnmapWindow; + PFN_XUnsetICFocus UnsetICFocus; + PFN_XVisualIDFromVisual VisualIDFromVisual; + PFN_XWarpPointer WarpPointer; + PFN_XUnregisterIMInstantiateCallback UnregisterIMInstantiateCallback; + PFN_Xutf8LookupString utf8LookupString; + PFN_Xutf8SetWMProperties utf8SetWMProperties; + } xlib; + + struct { + PFN_XrmDestroyDatabase DestroyDatabase; + PFN_XrmGetResource GetResource; + PFN_XrmGetStringDatabase GetStringDatabase; + PFN_XrmInitialize Initialize; + PFN_XrmUniqueQuark UniqueQuark; + } xrm; + struct { GLFWbool available; void* handle; @@ -338,6 +654,15 @@ typedef struct _GLFWlibraryX11 int major; int minor; unsigned int group; + PFN_XkbFreeKeyboard FreeKeyboard; + PFN_XkbFreeNames FreeNames; + PFN_XkbGetMap GetMap; + PFN_XkbGetNames GetNames; + PFN_XkbGetState GetState; + PFN_XkbKeycodeToKeysym KeycodeToKeysym; + PFN_XkbQueryExtension QueryExtension; + PFN_XkbSelectEventDetails SelectEventDetails; + PFN_XkbSetDetectableAutoRepeat SetDetectableAutoRepeat; } xkb; struct { @@ -414,6 +739,19 @@ typedef struct _GLFWlibraryX11 PFN_XRenderFindVisualFormat FindVisualFormat; } xrender; + struct { + GLFWbool available; + void* handle; + int major; + int minor; + int eventBase; + int errorBase; + PFN_XShapeQueryExtension QueryExtension; + PFN_XShapeCombineRegion ShapeCombineRegion; + PFN_XShapeQueryVersion QueryVersion; + PFN_XShapeCombineMask ShapeCombineMask; + } xshape; + } _GLFWlibraryX11; // X11-specific per-monitor data @@ -456,4 +794,5 @@ void _glfwReleaseErrorHandlerX11(void); void _glfwInputErrorX11(int error, const char* message); void _glfwPushSelectionToManagerX11(void); +void _glfwCreateInputContextX11(_GLFWwindow* window); diff --git a/raylib/external/glfw/src/x11_window.c b/raylib/external/glfw/src/x11_window.c index 23dc89a..4cd3294 100644 --- a/raylib/external/glfw/src/x11_window.c +++ b/raylib/external/glfw/src/x11_window.c @@ -463,7 +463,6 @@ static size_t encodeUTF8(char* s, unsigned int ch) // Decode a Unicode code point from a UTF-8 stream // Based on cutef8 by Jeff Bezanson (Public Domain) // -#if defined(X_HAVE_UTF8_STRING) static unsigned int decodeUTF8(const char** s) { unsigned int ch = 0, count = 0; @@ -483,7 +482,6 @@ static unsigned int decodeUTF8(const char** s) assert(count <= 6); return ch - offsets[count - 1]; } -#endif /*X_HAVE_UTF8_STRING*/ // Convert the specified Latin-1 string to UTF-8 // @@ -590,6 +588,14 @@ static void enableCursor(_GLFWwindow* window) updateCursorImage(window); } +// Clear its handle when the input context has been destroyed +// +static void inputContextDestroyCallback(XIC ic, XPointer clientData, XPointer callData) +{ + _GLFWwindow* window = (_GLFWwindow*) clientData; + window->x11.ic = NULL; +} + // Create the X11 window (and its colormap) // static GLFWbool createNativeWindow(_GLFWwindow* window, @@ -768,27 +774,10 @@ static GLFWbool createNativeWindow(_GLFWwindow* window, PropModeReplace, (unsigned char*) &version, 1); } - _glfwPlatformSetWindowTitle(window, wndconfig->title); - if (_glfw.x11.im) - { - window->x11.ic = XCreateIC(_glfw.x11.im, - XNInputStyle, - XIMPreeditNothing | XIMStatusNothing, - XNClientWindow, - window->x11.handle, - XNFocusWindow, - window->x11.handle, - NULL); - } - - if (window->x11.ic) - { - unsigned long filter = 0; - if (XGetICValues(window->x11.ic, XNFilterEvents, &filter, NULL) == NULL) - XSelectInput(_glfw.x11.display, window->x11.handle, wa.event_mask | filter); - } + _glfwCreateInputContextX11(window); + _glfwPlatformSetWindowTitle(window, wndconfig->title); _glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos); _glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height); @@ -1173,8 +1162,7 @@ static void processEvent(XEvent *event) if (event->type == KeyPress || event->type == KeyRelease) keycode = event->xkey.keycode; - if (_glfw.x11.im) - filtered = XFilterEvent(event, None); + filtered = XFilterEvent(event, None); if (_glfw.x11.randr.available) { @@ -1195,6 +1183,8 @@ static void processEvent(XEvent *event) { _glfw.x11.xkb.group = ((XkbEvent*) event)->state.group; } + + return; } } @@ -1273,23 +1263,26 @@ static void processEvent(XEvent *event) if (window->x11.ic) { - // HACK: Ignore duplicate key press events generated by ibus - // These have the same timestamp as the original event - // Corresponding release events are filtered out - // implicitly by the GLFW key repeat logic - if (window->x11.lastKeyTime < event->xkey.time) + // HACK: Do not report the key press events duplicated by XIM + // Duplicate key releases are filtered out implicitly by + // the GLFW key repeat logic in _glfwInputKey + // A timestamp per key is used to handle simultaneous keys + // NOTE: Always allow the first event for each key through + // (the server never sends a timestamp of zero) + // NOTE: Timestamp difference is compared to handle wrap-around + Time diff = event->xkey.time - window->x11.keyPressTimes[keycode]; + if (diff == event->xkey.time || (diff > 0 && diff < (1 << 31))) { if (keycode) _glfwInputKey(window, key, keycode, GLFW_PRESS, mods); - window->x11.lastKeyTime = event->xkey.time; + window->x11.keyPressTimes[keycode] = event->xkey.time; } if (!filtered) { int count; Status status; -#if defined(X_HAVE_UTF8_STRING) char buffer[100]; char* chars = buffer; @@ -1314,33 +1307,6 @@ static void processEvent(XEvent *event) while (c - chars < count) _glfwInputChar(window, decodeUTF8(&c), mods, plain); } -#else /*X_HAVE_UTF8_STRING*/ - wchar_t buffer[16]; - wchar_t* chars = buffer; - - count = XwcLookupString(window->x11.ic, - &event->xkey, - buffer, - sizeof(buffer) / sizeof(wchar_t), - NULL, - &status); - - if (status == XBufferOverflow) - { - chars = calloc(count, sizeof(wchar_t)); - count = XwcLookupString(window->x11.ic, - &event->xkey, - chars, count, - NULL, &status); - } - - if (status == XLookupChars || status == XLookupBoth) - { - int i; - for (i = 0; i < count; i++) - _glfwInputChar(window, chars[i], mods, plain); - } -#endif /*X_HAVE_UTF8_STRING*/ if (chars != buffer) free(chars); @@ -1557,6 +1523,8 @@ static void processEvent(XEvent *event) // the position into root (screen) coordinates if (!event->xany.send_event && window->x11.parent != _glfw.x11.root) { + _glfwGrabErrorHandlerX11(); + Window dummy; XTranslateCoordinates(_glfw.x11.display, window->x11.parent, @@ -1564,6 +1532,10 @@ static void processEvent(XEvent *event) xpos, ypos, &xpos, &ypos, &dummy); + + _glfwReleaseErrorHandlerX11(); + if (_glfw.x11.errorCode == BadWindow) + return; } if (xpos != window->x11.xpos || ypos != window->x11.ypos) @@ -1961,6 +1933,38 @@ void _glfwPushSelectionToManagerX11(void) } } +void _glfwCreateInputContextX11(_GLFWwindow* window) +{ + XIMCallback callback; + callback.callback = (XIMProc) inputContextDestroyCallback; + callback.client_data = (XPointer) window; + + window->x11.ic = XCreateIC(_glfw.x11.im, + XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, + window->x11.handle, + XNFocusWindow, + window->x11.handle, + XNDestroyCallback, + &callback, + NULL); + + if (window->x11.ic) + { + XWindowAttributes attribs; + XGetWindowAttributes(_glfw.x11.display, window->x11.handle, &attribs); + + unsigned long filter = 0; + if (XGetICValues(window->x11.ic, XNFilterEvents, &filter, NULL) == NULL) + { + XSelectInput(_glfw.x11.display, + window->x11.handle, + attribs.your_event_mask | filter); + } + } +} + ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// @@ -1971,7 +1975,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { - Visual* visual; + Visual* visual = NULL; int depth; if (ctxconfig->client != GLFW_NO_API) @@ -1997,8 +2001,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window, } } - if (ctxconfig->client == GLFW_NO_API || - ctxconfig->source == GLFW_OSMESA_CONTEXT_API) + if (!visual) { visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen); depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen); @@ -2073,21 +2076,14 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window) void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) { -#if defined(X_HAVE_UTF8_STRING) - Xutf8SetWMProperties(_glfw.x11.display, - window->x11.handle, - title, title, - NULL, 0, - NULL, NULL, NULL); -#else - // This may be a slightly better fallback than using XStoreName and - // XSetIconName, which always store their arguments using STRING - XmbSetWMProperties(_glfw.x11.display, - window->x11.handle, - title, title, - NULL, 0, - NULL, NULL, NULL); -#endif + if (_glfw.x11.xlib.utf8) + { + Xutf8SetWMProperties(_glfw.x11.display, + window->x11.handle, + title, title, + NULL, 0, + NULL, NULL, NULL); + } XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_NAME, _glfw.x11.UTF8_STRING, 8, @@ -2580,13 +2576,19 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window) int rootX, rootY, childX, childY; unsigned int mask; - if (!XQueryPointer(_glfw.x11.display, w, - &root, &w, &rootX, &rootY, &childX, &childY, &mask)) - { - return GLFW_FALSE; - } + _glfwGrabErrorHandlerX11(); - if (w == window->x11.handle) + const Bool result = XQueryPointer(_glfw.x11.display, w, + &root, &w, &rootX, &rootY, + &childX, &childY, &mask); + + _glfwReleaseErrorHandlerX11(); + + if (_glfw.x11.errorCode == BadWindow) + w = _glfw.x11.root; + else if (!result) + return GLFW_FALSE; + else if (w == window->x11.handle) return GLFW_TRUE; } @@ -2700,6 +2702,25 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled) XFlush(_glfw.x11.display); } +void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, GLFWbool enabled) +{ + if (!_glfw.x11.xshape.available) + return; + + if (enabled) + { + Region region = XCreateRegion(); + XShapeCombineRegion(_glfw.x11.display, window->x11.handle, + ShapeInput, 0, 0, region, ShapeSet); + XDestroyRegion(region); + } + else + { + XShapeCombineMask(_glfw.x11.display, window->x11.handle, + ShapeInput, 0, 0, None, ShapeSet); + } +} + float _glfwPlatformGetWindowOpacity(_GLFWwindow* window) { float opacity = 1.f; @@ -2755,11 +2776,12 @@ void _glfwPlatformPollEvents(void) _GLFWwindow* window; #if defined(__linux__) - _glfwDetectJoystickConnectionLinux(); + if (_glfw.joysticksInitialized) + _glfwDetectJoystickConnectionLinux(); #endif XPending(_glfw.x11.display); - while (XQLength(_glfw.x11.display)) + while (QLength(_glfw.x11.display)) { XEvent event; XNextEvent(_glfw.x11.display, &event); @@ -2997,8 +3019,9 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) void _glfwPlatformSetClipboardString(const char* string) { + char* copy = _glfw_strdup(string); free(_glfw.x11.clipboardString); - _glfw.x11.clipboardString = _glfw_strdup(string); + _glfw.x11.clipboardString = copy; XSetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD, @@ -3018,6 +3041,55 @@ const char* _glfwPlatformGetClipboardString(void) return getSelectionString(_glfw.x11.CLIPBOARD); } +EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs) +{ + if (_glfw.egl.ANGLE_platform_angle) + { + int type = 0; + + if (_glfw.egl.ANGLE_platform_angle_opengl) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL) + type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE; + } + + if (_glfw.egl.ANGLE_platform_angle_vulkan) + { + if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN) + type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE; + } + + if (type) + { + *attribs = calloc(5, sizeof(EGLint)); + (*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE; + (*attribs)[1] = type; + (*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE; + (*attribs)[3] = EGL_PLATFORM_X11_EXT; + (*attribs)[4] = EGL_NONE; + return EGL_PLATFORM_ANGLE_ANGLE; + } + } + + if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11) + return EGL_PLATFORM_X11_EXT; + + return 0; +} + +EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void) +{ + return _glfw.x11.display; +} + +EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window) +{ + if (_glfw.egl.platform) + return &window->x11.handle; + else + return (EGLNativeWindowType) window->x11.handle; +} + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface) diff --git a/raylib/external/jar_xm.h b/raylib/external/jar_xm.h index 1839e61..ea7a328 100644 --- a/raylib/external/jar_xm.h +++ b/raylib/external/jar_xm.h @@ -1,9 +1,26 @@ -// jar_xm.h - v0.01 - public domain - Joshua Reisenauer, MAR 2016 +// jar_xm.h +// +// ORIGINAL LICENSE - FOR LIBXM: +// +// Author: Romain "Artefact2" Dalmaso +// Contributor: Dan Spencer +// Repackaged into jar_xm.h By: Joshua Adam Reisenauer +// This program is free software. It comes without any warranty, to the +// extent permitted by applicable law. You can redistribute it and/or +// modify it under the terms of the Do What The Fuck You Want To Public +// License, Version 2, as published by Sam Hocevar. See +// http://sam.zoy.org/wtfpl/COPYING for more details. // // HISTORY: -// -// v0.01 2016-02-22 Setup -// +// v0.1.0 2016-02-22 jar_xm.h - development by Joshua Reisenauer, MAR 2016 +// v0.2.1 2021-03-07 m4ntr0n1c: Fix clipping noise for "bad" xm's (they will always clip), avoid clip noise and just put a ceiling) +// v0.2.2 2021-03-09 m4ntr0n1c: Add complete debug solution (raylib.h must be included) +// v0.2.3 2021-03-11 m4ntr0n1c: Fix tempo, bpm and volume on song stop / start / restart / loop +// v0.2.4 2021-03-17 m4ntr0n1c: Sanitize code for readability +// v0.2.5 2021-03-22 m4ntr0n1c: Minor adjustments +// v0.2.6 2021-04-01 m4ntr0n1c: Minor fixes and optimisation +// v0.3.0 2021-04-03 m4ntr0n1c: Addition of Stereo sample support, Linear Interpolation and Ramping now addressable options in code +// v0.3.1 2021-04-04 m4ntr0n1c: Volume effects column adjustments, sample offset handling adjustments // // USAGE: // @@ -36,27 +53,14 @@ // return 0; // } // -// -// LISCENSE - FOR LIBXM: -// -// Author: Romain "Artefact2" Dalmaso -// Contributor: Dan Spencer -// Repackaged into jar_xm.h By: Joshua Adam Reisenauer -// This program is free software. It comes without any warranty, to the -// extent permitted by applicable law. You can redistribute it and/or -// modify it under the terms of the Do What The Fuck You Want To Public -// License, Version 2, as published by Sam Hocevar. See -// http://sam.zoy.org/wtfpl/COPYING for more details. - #ifndef INCLUDE_JAR_XM_H #define INCLUDE_JAR_XM_H #include #define JAR_XM_DEBUG 0 -#define JAR_XM_LINEAR_INTERPOLATION 1 // speed increase with decrease in quality #define JAR_XM_DEFENSIVE 1 -#define JAR_XM_RAMPING 1 +//#define JAR_XM_RAYLIB 0 // set to 0 to disable the RayLib visualizer extension // Allow custom memory allocators #ifndef JARXM_MALLOC @@ -74,224 +78,145 @@ typedef struct jar_xm_context_s jar_xm_context_t; extern "C" { #endif -/** Create a XM context. - * - * @param moddata the contents of the module - * @param rate play rate in Hz, recommended value of 48000 - * - * @returns 0 on success - * @returns 1 if module data is not sane - * @returns 2 if memory allocation failed - * @returns 3 unable to open input file - * @returns 4 fseek() failed - * @returns 5 fread() failed - * @returns 6 unkown error - * - * @deprecated This function is unsafe! - * @see jar_xm_create_context_safe() - */ +//** Create a XM context. +// * @param moddata the contents of the module +// * @param rate play rate in Hz, recommended value of 48000 +// * @returns 0 on success +// * @returns 1 if module data is not sane +// * @returns 2 if memory allocation failed +// * @returns 3 unable to open input file +// * @returns 4 fseek() failed +// * @returns 5 fread() failed +// * @returns 6 unkown error +// * @deprecated This function is unsafe! +// * @see jar_xm_create_context_safe() int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const char* filename); -/** Create a XM context. - * - * @param moddata the contents of the module - * @param rate play rate in Hz, recommended value of 48000 - * - * @returns 0 on success - * @returns 1 if module data is not sane - * @returns 2 if memory allocation failed - * - * @deprecated This function is unsafe! - * @see jar_xm_create_context_safe() - */ +//** Create a XM context. +// * @param moddata the contents of the module +// * @param rate play rate in Hz, recommended value of 48000 +// * @returns 0 on success +// * @returns 1 if module data is not sane +// * @returns 2 if memory allocation failed +// * @deprecated This function is unsafe! +// * @see jar_xm_create_context_safe() int jar_xm_create_context(jar_xm_context_t** ctx, const char* moddata, uint32_t rate); -/** Create a XM context. - * - * @param moddata the contents of the module - * @param moddata_length the length of the contents of the module, in bytes - * @param rate play rate in Hz, recommended value of 48000 - * - * @returns 0 on success - * @returns 1 if module data is not sane - * @returns 2 if memory allocation failed - */ +//** Create a XM context. +// * @param moddata the contents of the module +// * @param moddata_length the length of the contents of the module, in bytes +// * @param rate play rate in Hz, recommended value of 48000 +// * @returns 0 on success +// * @returns 1 if module data is not sane +// * @returns 2 if memory allocation failed int jar_xm_create_context_safe(jar_xm_context_t** ctx, const char* moddata, size_t moddata_length, uint32_t rate); -/** Free a XM context created by jar_xm_create_context(). */ +//** Free a XM context created by jar_xm_create_context(). */ void jar_xm_free_context(jar_xm_context_t* ctx); -/** Play the module and put the sound samples in an output buffer. - * - * @param output buffer of 2*numsamples elements (A left and right value for each sample) - * @param numsamples number of samples to generate - */ +//** Play the module and put the sound samples in an output buffer. +// * @param output buffer of 2*numsamples elements (A left and right value for each sample) +// * @param numsamples number of samples to generate void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples); -/** Play the module, resample from 32 bit to 16 bit, and put the sound samples in an output buffer. - * - * @param output buffer of 2*numsamples elements (A left and right value for each sample) - * @param numsamples number of samples to generate - */ -void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) -{ +//** Play the module, resample from float to 16 bit, and put the sound samples in an output buffer. +// * @param output buffer of 2*numsamples elements (A left and right value for each sample) +// * @param numsamples number of samples to generate +void jar_xm_generate_samples_16bit(jar_xm_context_t* ctx, short* output, size_t numsamples) { float* musicBuffer = JARXM_MALLOC((2*numsamples)*sizeof(float)); jar_xm_generate_samples(ctx, musicBuffer, numsamples); if(output){ - int x; - for(x=0;x<2*numsamples;x++) - output[x] = musicBuffer[x] * SHRT_MAX; + for(int x=0;x<2*numsamples;x++) output[x] = (musicBuffer[x] * 32767.0f); // scale sample to signed small int } - JARXM_FREE(musicBuffer); } -/** Play the module, resample from 32 bit to 8 bit, and put the sound samples in an output buffer. - * - * @param output buffer of 2*numsamples elements (A left and right value for each sample) - * @param numsamples number of samples to generate - */ -void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) -{ +//** Play the module, resample from float to 8 bit, and put the sound samples in an output buffer. +// * @param output buffer of 2*numsamples elements (A left and right value for each sample) +// * @param numsamples number of samples to generate +void jar_xm_generate_samples_8bit(jar_xm_context_t* ctx, char* output, size_t numsamples) { float* musicBuffer = JARXM_MALLOC((2*numsamples)*sizeof(float)); jar_xm_generate_samples(ctx, musicBuffer, numsamples); if(output){ - int x; - for(x=0;x<2*numsamples;x++) - output[x] = musicBuffer[x] * CHAR_MAX; + for(int x=0;x<2*numsamples;x++) output[x] = (musicBuffer[x] * 127.0f); // scale sample to signed 8 bit } - JARXM_FREE(musicBuffer); } - - -/** Set the maximum number of times a module can loop. After the - * specified number of loops, calls to jar_xm_generate_samples will only - * generate silence. You can control the current number of loops with - * jar_xm_get_loop_count(). - * - * @param loopcnt maximum number of loops. Use 0 to loop - * indefinitely. */ +//** Set the maximum number of times a module can loop. After the specified number of loops, calls to jar_xm_generate_samples will only generate silence. You can control the current number of loops with jar_xm_get_loop_count(). +// * @param loopcnt maximum number of loops. Use 0 to loop indefinitely. void jar_xm_set_max_loop_count(jar_xm_context_t* ctx, uint8_t loopcnt); -/** Get the loop count of the currently playing module. This value is - * 0 when the module is still playing, 1 when the module has looped - * once, etc. */ +//** Get the loop count of the currently playing module. This value is 0 when the module is still playing, 1 when the module has looped once, etc. uint8_t jar_xm_get_loop_count(jar_xm_context_t* ctx); - - -/** Mute or unmute a channel. - * - * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). - * - * @return whether the channel was muted. - */ +//** Mute or unmute a channel. +// * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). +// * @return whether the channel was muted. bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t, bool); -/** Mute or unmute an instrument. - * - * @note Instrument numbers go from 1 to - * jar_xm_get_number_of_instruments(...). - * - * @return whether the instrument was muted. - */ +//** Mute or unmute an instrument. +// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...). +// * @return whether the instrument was muted. bool jar_xm_mute_instrument(jar_xm_context_t* ctx, uint16_t, bool); - - -/** Get the module name as a NUL-terminated string. */ +//** Get the module name as a NUL-terminated string. const char* jar_xm_get_module_name(jar_xm_context_t* ctx); -/** Get the tracker name as a NUL-terminated string. */ +//** Get the tracker name as a NUL-terminated string. const char* jar_xm_get_tracker_name(jar_xm_context_t* ctx); - - -/** Get the number of channels. */ +//** Get the number of channels. uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx); -/** Get the module length (in patterns). */ +//** Get the module length (in patterns). uint16_t jar_xm_get_module_length(jar_xm_context_t*); -/** Get the number of patterns. */ +//** Get the number of patterns. uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx); -/** Get the number of rows of a pattern. - * - * @note Pattern numbers go from 0 to - * jar_xm_get_number_of_patterns(...)-1. - */ +//** Get the number of rows of a pattern. +// * @note Pattern numbers go from 0 to jar_xm_get_number_of_patterns(...)-1. uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, uint16_t); -/** Get the number of instruments. */ +//** Get the number of instruments. uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx); -/** Get the number of samples of an instrument. - * - * @note Instrument numbers go from 1 to - * jar_xm_get_number_of_instruments(...). - */ +//** Get the number of samples of an instrument. +// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...). uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, uint16_t); - - -/** Get the current module speed. - * - * @param bpm will receive the current BPM - * @param tempo will receive the current tempo (ticks per line) - */ +//** Get the current module speed. +// * @param bpm will receive the current BPM +// * @param tempo will receive the current tempo (ticks per line) void jar_xm_get_playing_speed(jar_xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo); -/** Get the current position in the module being played. - * - * @param pattern_index if not NULL, will receive the current pattern - * index in the POT (pattern order table) - * - * @param pattern if not NULL, will receive the current pattern number - * - * @param row if not NULL, will receive the current row - * - * @param samples if not NULL, will receive the total number of - * generated samples (divide by sample rate to get seconds of - * generated audio) - */ +//** Get the current position in the module being played. +// * @param pattern_index if not NULL, will receive the current pattern index in the POT (pattern order table) +// * @param pattern if not NULL, will receive the current pattern number +// * @param row if not NULL, will receive the current row +// * @param samples if not NULL, will receive the total number of +// * generated samples (divide by sample rate to get seconds of generated audio) void jar_xm_get_position(jar_xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples); -/** Get the latest time (in number of generated samples) when a - * particular instrument was triggered in any channel. - * - * @note Instrument numbers go from 1 to - * jar_xm_get_number_of_instruments(...). - */ +//** Get the latest time (in number of generated samples) when a particular instrument was triggered in any channel. +// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...). uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, uint16_t); -/** Get the latest time (in number of generated samples) when a - * particular sample was triggered in any channel. - * - * @note Instrument numbers go from 1 to - * jar_xm_get_number_of_instruments(...). - * - * @note Sample numbers go from 0 to - * jar_xm_get_nubmer_of_samples(...,instr)-1. - */ +//** Get the latest time (in number of generated samples) when a particular sample was triggered in any channel. +// * @note Instrument numbers go from 1 to jar_xm_get_number_of_instruments(...). +// * @note Sample numbers go from 0 to jar_xm_get_nubmer_of_samples(...,instr)-1. uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, uint16_t instr, uint16_t sample); -/** Get the latest time (in number of generated samples) when any - * instrument was triggered in a given channel. - * - * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). - */ +//** Get the latest time (in number of generated samples) when any instrument was triggered in a given channel. +// * @note Channel numbers go from 1 to jar_xm_get_number_of_channels(...). uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, uint16_t); -/** Get the number of remaining samples. Divide by 2 to get the number of individual LR data samples. - * - * @note This is the remaining number of samples before the loop starts module again, or halts if on last pass. - * @note This function is very slow and should only be run once, if at all. - */ +//** Get the number of remaining samples. Divide by 2 to get the number of individual LR data samples. +// * @note This is the remaining number of samples before the loop starts module again, or halts if on last pass. +// * @note This function is very slow and should only be run once, if at all. uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx); #ifdef __cplusplus @@ -299,12 +224,6 @@ uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx); #endif //------------------------------------------------------------------------------- - - - - - -//Function Definitions----------------------------------------------------------- #ifdef JAR_XM_IMPLEMENTATION #include @@ -330,19 +249,16 @@ extern int __fail[-1]; #endif /* ----- XM constants ----- */ - #define SAMPLE_NAME_LENGTH 22 #define INSTRUMENT_NAME_LENGTH 22 #define MODULE_NAME_LENGTH 20 #define TRACKER_NAME_LENGTH 20 #define PATTERN_ORDER_TABLE_LENGTH 256 -#define NUM_NOTES 96 -#define NUM_ENVELOPE_POINTS 12 +#define NUM_NOTES 96 // from 1 to 96, where 1 = C-0 +#define NUM_ENVELOPE_POINTS 12 // to be verified if 12 is the max #define MAX_NUM_ROWS 256 -#if JAR_XM_RAMPING -#define jar_xm_SAMPLE_RAMPING_POINTS 0x20 -#endif +#define jar_xm_SAMPLE_RAMPING_POINTS 8 /* ----- Data types ----- */ @@ -389,7 +305,7 @@ typedef struct jar_xm_envelope_s jar_xm_envelope_t; struct jar_xm_sample_s { char name[SAMPLE_NAME_LENGTH + 1]; int8_t bits; /* Either 8 or 16 */ - + int8_t stereo; uint32_t length; uint32_t loop_start; uint32_t loop_length; @@ -446,12 +362,13 @@ struct jar_xm_sample_s { uint16_t num_channels; uint16_t num_patterns; uint16_t num_instruments; + uint16_t linear_interpolation; + uint16_t ramping; jar_xm_frequency_type_t frequency_type; uint8_t pattern_table[PATTERN_ORDER_TABLE_LENGTH]; jar_xm_pattern_t* patterns; - jar_xm_instrument_t* instruments; /* Instrument 1 has index 0, - * instrument 2 has index 1, etc. */ + jar_xm_instrument_t* instruments; /* Instrument 1 has index 0, instrument 2 has index 1, etc. */ }; typedef struct jar_xm_module_s jar_xm_module_t; @@ -517,15 +434,15 @@ struct jar_xm_sample_s { uint64_t latest_trigger; bool muted; -#if JAR_XM_RAMPING - /* These values are updated at the end of each tick, to save - * a couple of float operations on every generated sample. */ + //* These values are updated at the end of each tick, to save a couple of float operations on every generated sample. float target_panning; float target_volume; unsigned long frame_count; - float end_of_previous_sample[jar_xm_SAMPLE_RAMPING_POINTS]; -#endif + float end_of_previous_sample_left[jar_xm_SAMPLE_RAMPING_POINTS]; + float end_of_previous_sample_right[jar_xm_SAMPLE_RAMPING_POINTS]; + float curr_left; + float curr_right; float actual_panning; float actual_volume; @@ -537,18 +454,16 @@ struct jar_xm_sample_s { jar_xm_module_t module; uint32_t rate; - uint16_t tempo; + uint16_t default_tempo; // Number of ticks per row + uint16_t default_bpm; + float default_global_volume; + + uint16_t tempo; // Number of ticks per row uint16_t bpm; float global_volume; - float amplification; -#if JAR_XM_RAMPING - /* How much is a channel final volume allowed to change per - * sample; this is used to avoid abrubt volume changes which - * manifest as "clicks" in the generated sound. */ - float volume_ramp; + float volume_ramp; /* How much is a channel final volume allowed to change per sample; this is used to avoid abrubt volume changes which manifest as "clicks" in the generated sound. */ float panning_ramp; /* Same for panning. */ -#endif uint8_t current_table_index; uint8_t current_row; @@ -561,9 +476,7 @@ struct jar_xm_sample_s { uint8_t jump_dest; uint8_t jump_row; - /* Extra ticks to be played before going to the next row - - * Used for EEy effect */ - uint16_t extra_ticks; + uint16_t extra_ticks; /* Extra ticks to be played before going to the next row - Used for EEy effect */ uint8_t* row_loop_count; /* Array of size MAX_NUM_ROWS * module_length */ uint8_t loop_count; @@ -572,45 +485,33 @@ struct jar_xm_sample_s { jar_xm_channel_context_t* channels; }; -/* ----- Internal API ----- */ - #if JAR_XM_DEFENSIVE -/** Check the module data for errors/inconsistencies. - * - * @returns 0 if everything looks OK. Module should be safe to load. - */ +//** Check the module data for errors/inconsistencies. +// * @returns 0 if everything looks OK. Module should be safe to load. int jar_xm_check_sanity_preload(const char*, size_t); -/** Check a loaded module for errors/inconsistencies. - * - * @returns 0 if everything looks OK. - */ +//** Check a loaded module for errors/inconsistencies. +// * @returns 0 if everything looks OK. int jar_xm_check_sanity_postload(jar_xm_context_t*); #endif -/** Get the number of bytes needed to store the module data in a - * dynamically allocated blank context. - * - * Things that are dynamically allocated: - * - sample data - * - sample structures in instruments - * - pattern data - * - row loop count arrays - * - pattern structures in module - * - instrument structures in module - * - channel contexts - * - context structure itself - - * @returns 0 if everything looks OK. - */ +//** Get the number of bytes needed to store the module data in a dynamically allocated blank context. +// * Things that are dynamically allocated: +// * - sample data +// * - sample structures in instruments +// * - pattern data +// * - row loop count arrays +// * - pattern structures in module +// * - instrument structures in module +// * - channel contexts +// * - context structure itself +// * @returns 0 if everything looks OK. size_t jar_xm_get_memory_needed_for_context(const char*, size_t); -/** Populate the context from module data. - * - * @returns pointer to the memory pool - */ +//** Populate the context from module data. +// * @returns pointer to the memory pool char* jar_xm_load_module(jar_xm_context_t*, const char*, size_t, char*); int jar_xm_create_context(jar_xm_context_t** ctxp, const char* moddata, uint32_t rate) { @@ -636,8 +537,7 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz bytes_needed = jar_xm_get_memory_needed_for_context(moddata, moddata_length); mempool = JARXM_MALLOC(bytes_needed); - if(mempool == NULL && bytes_needed > 0) { - /* JARXM_MALLOC() failed, trouble ahead */ + if(mempool == NULL && bytes_needed > 0) { /* JARXM_MALLOC() failed, trouble ahead */ DEBUG("call to JARXM_MALLOC() failed, returned %p", (void*)mempool); return 2; } @@ -657,23 +557,19 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz mempool += ctx->module.num_channels * sizeof(jar_xm_channel_context_t); mempool = ALIGN_PTR(mempool, 16); - ctx->global_volume = 1.f; - ctx->amplification = .25f; /* XXX: some bad modules may still clip. Find out something better. */ + ctx->default_global_volume = 1.f; + ctx->global_volume = ctx->default_global_volume; -#if JAR_XM_RAMPING ctx->volume_ramp = (1.f / 128.f); ctx->panning_ramp = (1.f / 128.f); -#endif for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { - jar_xm_channel_context_t* ch = ctx->channels + i; - + jar_xm_channel_context_t *ch = ctx->channels + i; ch->ping = true; ch->vibrato_waveform = jar_xm_SINE_WAVEFORM; ch->vibrato_waveform_retrigger = true; ch->tremolo_waveform = jar_xm_SINE_WAVEFORM; ch->tremolo_waveform_retrigger = true; - ch->volume = ch->volume_envelope_volume = ch->fadeout_volume = 1.0f; ch->panning = ch->panning_envelope_panning = .5f; ch->actual_volume = .0f; @@ -681,12 +577,11 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz } mempool = ALIGN_PTR(mempool, 16); - ctx->row_loop_count = (uint8_t*)mempool; + ctx->row_loop_count = (uint8_t *)mempool; mempool += MAX_NUM_ROWS * sizeof(uint8_t); #if JAR_XM_DEFENSIVE - if((ret = jar_xm_check_sanity_postload(ctx))) { - DEBUG("jar_xm_check_sanity_postload() returned %i, module is not safe to play", ret); + if((ret = jar_xm_check_sanity_postload(ctx))) { DEBUG("jar_xm_check_sanity_postload() returned %i, module is not safe to play", ret); jar_xm_free_context(ctx); return 1; } @@ -695,106 +590,98 @@ int jar_xm_create_context_safe(jar_xm_context_t** ctxp, const char* moddata, siz return 0; } -void jar_xm_free_context(jar_xm_context_t* ctx) { - JARXM_FREE(ctx->allocated_memory); +void jar_xm_free_context(jar_xm_context_t *ctx) { + if (ctx != NULL) { JARXM_FREE(ctx->allocated_memory); } } -void jar_xm_set_max_loop_count(jar_xm_context_t* ctx, uint8_t loopcnt) { +void jar_xm_set_max_loop_count(jar_xm_context_t *ctx, uint8_t loopcnt) { ctx->max_loop_count = loopcnt; } -uint8_t jar_xm_get_loop_count(jar_xm_context_t* ctx) { +uint8_t jar_xm_get_loop_count(jar_xm_context_t *ctx) { return ctx->loop_count; } -bool jar_xm_mute_channel(jar_xm_context_t* ctx, uint16_t channel, bool mute) { +bool jar_xm_mute_channel(jar_xm_context_t *ctx, uint16_t channel, bool mute) { bool old = ctx->channels[channel - 1].muted; ctx->channels[channel - 1].muted = mute; return old; } -bool jar_xm_mute_instrument(jar_xm_context_t* ctx, uint16_t instr, bool mute) { +bool jar_xm_mute_instrument(jar_xm_context_t *ctx, uint16_t instr, bool mute) { bool old = ctx->module.instruments[instr - 1].muted; ctx->module.instruments[instr - 1].muted = mute; return old; } - - -const char* jar_xm_get_module_name(jar_xm_context_t* ctx) { +const char* jar_xm_get_module_name(jar_xm_context_t *ctx) { return ctx->module.name; } -const char* jar_xm_get_tracker_name(jar_xm_context_t* ctx) { +const char* jar_xm_get_tracker_name(jar_xm_context_t *ctx) { return ctx->module.trackername; } - - -uint16_t jar_xm_get_number_of_channels(jar_xm_context_t* ctx) { +uint16_t jar_xm_get_number_of_channels(jar_xm_context_t *ctx) { return ctx->module.num_channels; } -uint16_t jar_xm_get_module_length(jar_xm_context_t* ctx) { +uint16_t jar_xm_get_module_length(jar_xm_context_t *ctx) { return ctx->module.length; } -uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t* ctx) { +uint16_t jar_xm_get_number_of_patterns(jar_xm_context_t *ctx) { return ctx->module.num_patterns; } -uint16_t jar_xm_get_number_of_rows(jar_xm_context_t* ctx, uint16_t pattern) { +uint16_t jar_xm_get_number_of_rows(jar_xm_context_t *ctx, uint16_t pattern) { return ctx->module.patterns[pattern].num_rows; } -uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t* ctx) { +uint16_t jar_xm_get_number_of_instruments(jar_xm_context_t *ctx) { return ctx->module.num_instruments; } -uint16_t jar_xm_get_number_of_samples(jar_xm_context_t* ctx, uint16_t instrument) { +uint16_t jar_xm_get_number_of_samples(jar_xm_context_t *ctx, uint16_t instrument) { return ctx->module.instruments[instrument - 1].num_samples; } - - -void jar_xm_get_playing_speed(jar_xm_context_t* ctx, uint16_t* bpm, uint16_t* tempo) { +void jar_xm_get_playing_speed(jar_xm_context_t *ctx, uint16_t *bpm, uint16_t *tempo) { if(bpm) *bpm = ctx->bpm; if(tempo) *tempo = ctx->tempo; } -void jar_xm_get_position(jar_xm_context_t* ctx, uint8_t* pattern_index, uint8_t* pattern, uint8_t* row, uint64_t* samples) { +void jar_xm_get_position(jar_xm_context_t *ctx, uint8_t *pattern_index, uint8_t *pattern, uint8_t *row, uint64_t *samples) { if(pattern_index) *pattern_index = ctx->current_table_index; if(pattern) *pattern = ctx->module.pattern_table[ctx->current_table_index]; if(row) *row = ctx->current_row; if(samples) *samples = ctx->generated_samples; } -uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t* ctx, uint16_t instr) { +uint64_t jar_xm_get_latest_trigger_of_instrument(jar_xm_context_t *ctx, uint16_t instr) { return ctx->module.instruments[instr - 1].latest_trigger; } -uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t* ctx, uint16_t instr, uint16_t sample) { +uint64_t jar_xm_get_latest_trigger_of_sample(jar_xm_context_t *ctx, uint16_t instr, uint16_t sample) { return ctx->module.instruments[instr - 1].samples[sample].latest_trigger; } -uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t* ctx, uint16_t chn) { +uint64_t jar_xm_get_latest_trigger_of_channel(jar_xm_context_t *ctx, uint16_t chn) { return ctx->channels[chn - 1].latest_trigger; } -/* .xm files are little-endian. (XXX: Are they really?) */ +//* .xm files are little-endian. (XXX: Are they really?) -/* Bounded reader macros. - * If we attempt to read the buffer out-of-bounds, pretend that the buffer is - * infinitely padded with zeroes. - */ +//* Bound reader macros. +//* If we attempt to read the buffer out-of-bounds, pretend that the buffer is infinitely padded with zeroes. #define READ_U8(offset) (((offset) < moddata_length) ? (*(uint8_t*)(moddata + (offset))) : 0) #define READ_U16(offset) ((uint16_t)READ_U8(offset) | ((uint16_t)READ_U8((offset) + 1) << 8)) #define READ_U32(offset) ((uint32_t)READ_U16(offset) | ((uint32_t)READ_U16((offset) + 2) << 16)) #define READ_MEMCPY(ptr, offset, length) memcpy_pad(ptr, length, moddata, moddata_length, offset) -static void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_len, size_t offset) { - uint8_t* dst_c = dst; - const uint8_t* src_c = src; +static void memcpy_pad(void *dst, size_t dst_len, const void *src, size_t src_len, size_t offset) { + uint8_t *dst_c = dst; + const uint8_t *src_c = src; /* how many bytes can be copied without overrunning `src` */ size_t copy_bytes = (src_len >= offset) ? (src_len - offset) : 0; @@ -808,40 +695,22 @@ static void memcpy_pad(void* dst, size_t dst_len, const void* src, size_t src_le #if JAR_XM_DEFENSIVE int jar_xm_check_sanity_preload(const char* module, size_t module_length) { - if(module_length < 60) { - return 4; - } - - if(memcmp("Extended Module: ", module, 17) != 0) { - return 1; - } - - if(module[37] != 0x1A) { - return 2; - } - - if(module[59] != 0x01 || module[58] != 0x04) { - /* Not XM 1.04 */ - return 3; - } - + if(module_length < 60) { return 4; } + if(memcmp("Extended Module: ", module, 17) != 0) { return 1; } + if(module[37] != 0x1A) { return 2; } + if(module[59] != 0x01 || module[58] != 0x04) { return 3; } /* Not XM 1.04 */ return 0; } int jar_xm_check_sanity_postload(jar_xm_context_t* ctx) { - /* @todo: plenty of stuff to do here… */ - /* Check the POT */ for(uint8_t i = 0; i < ctx->module.length; ++i) { if(ctx->module.pattern_table[i] >= ctx->module.num_patterns) { if(i+1 == ctx->module.length && ctx->module.length > 1) { - /* Cheap fix */ - --ctx->module.length; DEBUG("trimming invalid POT at pos %X", i); + --ctx->module.length; } else { - DEBUG("module has invalid POT, pos %X references nonexistent pattern %X", - i, - ctx->module.pattern_table[i]); + DEBUG("module has invalid POT, pos %X references nonexistent pattern %X", i, ctx->module.pattern_table[i]); return 1; } } @@ -854,36 +723,29 @@ int jar_xm_check_sanity_postload(jar_xm_context_t* ctx) { size_t jar_xm_get_memory_needed_for_context(const char* moddata, size_t moddata_length) { size_t memory_needed = 0; - size_t offset = 60; /* Skip the first header */ + size_t offset = 60; /* 60 = Skip the first header */ uint16_t num_channels; uint16_t num_patterns; uint16_t num_instruments; /* Read the module header */ num_channels = READ_U16(offset + 8); - num_patterns = READ_U16(offset + 10); memory_needed += num_patterns * sizeof(jar_xm_pattern_t); memory_needed = ALIGN(memory_needed, 16); - num_instruments = READ_U16(offset + 12); memory_needed += num_instruments * sizeof(jar_xm_instrument_t); memory_needed = ALIGN(memory_needed, 16); - memory_needed += MAX_NUM_ROWS * READ_U16(offset + 4) * sizeof(uint8_t); /* Module length */ - /* Header size */ - offset += READ_U32(offset); + offset += READ_U32(offset); /* Header size */ /* Read pattern headers */ for(uint16_t i = 0; i < num_patterns; ++i) { uint16_t num_rows; - num_rows = READ_U16(offset + 5); memory_needed += num_rows * num_channels * sizeof(jar_xm_pattern_slot_t); - - /* Pattern header length + packed pattern data size */ - offset += READ_U32(offset) + READ_U16(offset + 7); + offset += READ_U32(offset) + READ_U16(offset + 7); /* Pattern header length + packed pattern data size */ } memory_needed = ALIGN(memory_needed, 16); @@ -892,42 +754,30 @@ size_t jar_xm_get_memory_needed_for_context(const char* moddata, size_t moddata_ uint16_t num_samples; uint32_t sample_header_size = 0; uint32_t sample_size_aggregate = 0; - num_samples = READ_U16(offset + 27); memory_needed += num_samples * sizeof(jar_xm_sample_t); + if(num_samples > 0) { sample_header_size = READ_U32(offset + 29); } - if(num_samples > 0) { - sample_header_size = READ_U32(offset + 29); - } - - /* Instrument header size */ - offset += READ_U32(offset); - + offset += READ_U32(offset); /* Instrument header size */ for(uint16_t j = 0; j < num_samples; ++j) { uint32_t sample_size; uint8_t flags; - sample_size = READ_U32(offset); flags = READ_U8(offset + 14); sample_size_aggregate += sample_size; - if(flags & (1 << 4)) { - /* 16 bit sample */ + if(flags & (1 << 4)) { /* 16 bit sample */ memory_needed += sample_size * (sizeof(float) >> 1); - } else { - /* 8 bit sample */ + } else { /* 8 bit sample */ memory_needed += sample_size * sizeof(float); } - offset += sample_header_size; } - offset += sample_size_aggregate; } memory_needed += num_channels * sizeof(jar_xm_channel_context_t); memory_needed += sizeof(jar_xm_context_t); - return memory_needed; } @@ -942,26 +792,25 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd /* Read module header */ uint32_t header_size = READ_U32(offset); - mod->length = READ_U16(offset + 4); mod->restart_position = READ_U16(offset + 6); mod->num_channels = READ_U16(offset + 8); mod->num_patterns = READ_U16(offset + 10); mod->num_instruments = READ_U16(offset + 12); - mod->patterns = (jar_xm_pattern_t*)mempool; + mod->linear_interpolation = 0; // Linear interpolation can be set after loading + mod->ramping = 1; // ramping can be set after loading mempool += mod->num_patterns * sizeof(jar_xm_pattern_t); mempool = ALIGN_PTR(mempool, 16); - mod->instruments = (jar_xm_instrument_t*)mempool; mempool += mod->num_instruments * sizeof(jar_xm_instrument_t); mempool = ALIGN_PTR(mempool, 16); - uint16_t flags = READ_U32(offset + 14); mod->frequency_type = (flags & (1 << 0)) ? jar_xm_LINEAR_FREQUENCIES : jar_xm_AMIGA_FREQUENCIES; - - ctx->tempo = READ_U16(offset + 16); - ctx->bpm = READ_U16(offset + 18); + ctx->default_tempo = READ_U16(offset + 16); + ctx->default_bpm = READ_U16(offset + 18); + ctx->tempo =ctx->default_tempo; + ctx->bpm = ctx->default_bpm; READ_MEMCPY(mod->pattern_table, offset + 20, PATTERN_ORDER_TABLE_LENGTH); offset += header_size; @@ -970,69 +819,52 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd for(uint16_t i = 0; i < mod->num_patterns; ++i) { uint16_t packed_patterndata_size = READ_U16(offset + 7); jar_xm_pattern_t* pat = mod->patterns + i; - pat->num_rows = READ_U16(offset + 5); - pat->slots = (jar_xm_pattern_slot_t*)mempool; mempool += mod->num_channels * pat->num_rows * sizeof(jar_xm_pattern_slot_t); - - /* Pattern header length */ - offset += READ_U32(offset); - - if(packed_patterndata_size == 0) { - /* No pattern data is present */ + offset += READ_U32(offset); /* Pattern header length */ + + if(packed_patterndata_size == 0) { /* No pattern data is present */ memset(pat->slots, 0, sizeof(jar_xm_pattern_slot_t) * pat->num_rows * mod->num_channels); } else { /* This isn't your typical for loop */ for(uint16_t j = 0, k = 0; j < packed_patterndata_size; ++k) { uint8_t note = READ_U8(offset + j); jar_xm_pattern_slot_t* slot = pat->slots + k; - if(note & (1 << 7)) { /* MSB is set, this is a compressed packet */ ++j; - - if(note & (1 << 0)) { - /* Note follows */ + if(note & (1 << 0)) { /* Note follows */ slot->note = READ_U8(offset + j); ++j; } else { slot->note = 0; } - - if(note & (1 << 1)) { - /* Instrument follows */ + if(note & (1 << 1)) { /* Instrument follows */ slot->instrument = READ_U8(offset + j); ++j; } else { slot->instrument = 0; } - - if(note & (1 << 2)) { - /* Volume column follows */ + if(note & (1 << 2)) { /* Volume column follows */ slot->volume_column = READ_U8(offset + j); ++j; } else { slot->volume_column = 0; } - - if(note & (1 << 3)) { - /* Effect follows */ + if(note & (1 << 3)) { /* Effect follows */ slot->effect_type = READ_U8(offset + j); ++j; } else { slot->effect_type = 0; } - - if(note & (1 << 4)) { - /* Effect parameter follows */ + if(note & (1 << 4)) { /* Effect parameter follows */ slot->effect_param = READ_U8(offset + j); ++j; } else { slot->effect_param = 0; } - } else { - /* Uncompressed packet */ + } else { /* Uncompressed packet */ slot->note = note; slot->instrument = READ_U8(offset + j + 1); slot->volume_column = READ_U8(offset + j + 2); @@ -1076,7 +908,6 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd instr->volume_envelope.sustain_point = READ_U8(offset + 227); instr->volume_envelope.loop_start_point = READ_U8(offset + 228); instr->volume_envelope.loop_end_point = READ_U8(offset + 229); - instr->panning_envelope.sustain_point = READ_U8(offset + 230); instr->panning_envelope.loop_start_point = READ_U8(offset + 231); instr->panning_envelope.loop_end_point = READ_U8(offset + 232); @@ -1090,7 +921,6 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd instr->panning_envelope.enabled = flags & (1 << 0); instr->panning_envelope.sustain_enabled = flags & (1 << 1); instr->panning_envelope.loop_enabled = flags & (1 << 2); - instr->vibrato_type = READ_U8(offset + 235); if(instr->vibrato_type == 2) { instr->vibrato_type = 1; @@ -1101,7 +931,6 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd instr->vibrato_depth = READ_U8(offset + 237); instr->vibrato_rate = READ_U8(offset + 238); instr->volume_fadeout = READ_U16(offset + 239); - instr->samples = (jar_xm_sample_t*)mempool; mempool += instr->num_samples * sizeof(jar_xm_sample_t); } else { @@ -1111,7 +940,7 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd /* Instrument header size */ offset += READ_U32(offset); - for(uint16_t j = 0; j < instr->num_samples; ++j) { + for(int j = 0; j < instr->num_samples; ++j) { /* Read sample header */ jar_xm_sample_t* sample = instr->samples + j; @@ -1119,25 +948,28 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd sample->loop_start = READ_U32(offset + 4); sample->loop_length = READ_U32(offset + 8); sample->loop_end = sample->loop_start + sample->loop_length; - sample->volume = (float)READ_U8(offset + 12) / (float)0x40; + sample->volume = (float)(READ_U8(offset + 12) << 2) / 256.f; + if (sample->volume > 1.0f) {sample->volume = 1.f;}; sample->finetune = (int8_t)READ_U8(offset + 13); uint8_t flags = READ_U8(offset + 14); - if((flags & 3) == 0) { - sample->loop_type = jar_xm_NO_LOOP; - } else if((flags & 3) == 1) { - sample->loop_type = jar_xm_FORWARD_LOOP; - } else { + switch (flags & 3) { + case 2: + case 3: sample->loop_type = jar_xm_PING_PONG_LOOP; - } - - sample->bits = (flags & (1 << 4)) ? 16 : 8; - - sample->panning = (float)READ_U8(offset + 15) / (float)0xFF; + case 1: + sample->loop_type = jar_xm_FORWARD_LOOP; + break; + default: + sample->loop_type = jar_xm_NO_LOOP; + break; + }; + sample->bits = (flags & 0x10) ? 16 : 8; + sample->stereo = (flags & 0x20) ? 1 : 0; + sample->panning = (float)READ_U8(offset + 15) / 255.f; sample->relative_note = (int8_t)READ_U8(offset + 16); READ_MEMCPY(sample->name, 18, SAMPLE_NAME_LENGTH); sample->data = (float*)mempool; - if(sample->bits == 16) { /* 16 bit sample */ mempool += sample->length * (sizeof(float) >> 1); @@ -1149,42 +981,72 @@ char* jar_xm_load_module(jar_xm_context_t* ctx, const char* moddata, size_t modd /* 8 bit sample */ mempool += sample->length * sizeof(float); } + // Adjust loop points to reflect half of the reported length (stereo) + if (sample->stereo && sample->loop_type != jar_xm_NO_LOOP) { + div_t lstart = div(READ_U32(offset + 4), 2); + sample->loop_start = lstart.quot; + div_t llength = div(READ_U32(offset + 8), 2); + sample->loop_length = llength.quot; + sample->loop_end = sample->loop_start + sample->loop_length; + }; offset += sample_header_size; } - for(uint16_t j = 0; j < instr->num_samples; ++j) { + // Read all samples and convert them to float values + for(int j = 0; j < instr->num_samples; ++j) { /* Read sample data */ jar_xm_sample_t* sample = instr->samples + j; - uint32_t length = sample->length; - - if(sample->bits == 16) { - int16_t v = 0; - for(uint32_t k = 0; k < length; ++k) { - v = v + (int16_t)READ_U16(offset + (k << 1)); - sample->data[k] = (float)v / (float)(1 << 15); - } - offset += sample->length << 1; + int length = sample->length; + if (sample->stereo) { + // Since it is stereo, we cut the sample in half (treated as single channel) + div_t result = div(sample->length, 2); + if(sample->bits == 16) { + int16_t v = 0; + for(int k = 0; k < length; ++k) { + if (k == result.quot) { v = 0;}; + v = v + (int16_t)READ_U16(offset + (k << 1)); + sample->data[k] = (float) v / 32768.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length << 1; + } else { + int8_t v = 0; + for(int k = 0; k < length; ++k) { + if (k == result.quot) { v = 0;}; + v = v + (int8_t)READ_U8(offset + k); + sample->data[k] = (float)v / 128.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length; + }; + sample->length = result.quot; } else { - int8_t v = 0; - for(uint32_t k = 0; k < length; ++k) { - v = v + (int8_t)READ_U8(offset + k); - sample->data[k] = (float)v / (float)(1 << 7); + if(sample->bits == 16) { + int16_t v = 0; + for(int k = 0; k < length; ++k) { + v = v + (int16_t)READ_U16(offset + (k << 1)); + sample->data[k] = (float) v / 32768.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length << 1; + } else { + int8_t v = 0; + for(int k = 0; k < length; ++k) { + v = v + (int8_t)READ_U8(offset + k); + sample->data[k] = (float)v / 128.f ;//* sign; + if(sample->data[k] < -1.0) {sample->data[k] = -1.0;} else if(sample->data[k] > 1.0) {sample->data[k] = 1.0;}; + } + offset += sample->length; } - offset += sample->length; } - } - } - + }; + }; return mempool; -} +}; //------------------------------------------------------------------------------- //THE FOLLOWING IS FOR PLAYING -//------------------------------------------------------------------------------- - -/* ----- Static functions ----- */ - static float jar_xm_waveform(jar_xm_waveform_type_t, uint8_t); static void jar_xm_autovibrato(jar_xm_context_t*, jar_xm_channel_context_t*); static void jar_xm_vibrato(jar_xm_context_t*, jar_xm_channel_context_t*, uint8_t, uint16_t); @@ -1216,35 +1078,21 @@ static void jar_xm_post_pattern_change(jar_xm_context_t*); static void jar_xm_row(jar_xm_context_t*); static void jar_xm_tick(jar_xm_context_t*); -static float jar_xm_next_of_sample(jar_xm_channel_context_t*); -static void jar_xm_sample(jar_xm_context_t*, float*, float*); - -/* ----- Other oddities ----- */ +static void jar_xm_next_of_sample(jar_xm_context_t*, jar_xm_channel_context_t*, int); +static void jar_xm_mixdown(jar_xm_context_t*, float*, float*); #define jar_xm_TRIGGER_KEEP_VOLUME (1 << 0) #define jar_xm_TRIGGER_KEEP_PERIOD (1 << 1) #define jar_xm_TRIGGER_KEEP_SAMPLE_POSITION (1 << 2) -static const uint16_t amiga_frequencies[] = { - 1712, 1616, 1525, 1440, /* C-2, C#2, D-2, D#2 */ - 1357, 1281, 1209, 1141, /* E-2, F-2, F#2, G-2 */ - 1077, 1017, 961, 907, /* G#2, A-2, A#2, B-2 */ - 856, /* C-3 */ -}; + // C-2, C#2, D-2, D#2, E-2, F-2, F#2, G-2, G#2, A-2, A#2, B-2, C-3 +static const uint16_t amiga_frequencies[] = { 1712, 1616, 1525, 1440, 1357, 1281, 1209, 1141, 1077, 1017, 961, 907, 856 }; -static const float multi_retrig_add[] = { - 0.f, -1.f, -2.f, -4.f, /* 0, 1, 2, 3 */ - -8.f, -16.f, 0.f, 0.f, /* 4, 5, 6, 7 */ - 0.f, 1.f, 2.f, 4.f, /* 8, 9, A, B */ - 8.f, 16.f, 0.f, 0.f /* C, D, E, F */ -}; + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f +static const float multi_retrig_add[] = { 0.f, -1.f, -2.f, -4.f, -8.f, -16.f, 0.f, 0.f, 0.f, 1.f, 2.f, 4.f, 8.f, 16.f, 0.f, 0.f }; -static const float multi_retrig_multiply[] = { - 1.f, 1.f, 1.f, 1.f, /* 0, 1, 2, 3 */ - 1.f, 1.f, .6666667f, .5f, /* 4, 5, 6, 7 */ - 1.f, 1.f, 1.f, 1.f, /* 8, 9, A, B */ - 1.f, 1.f, 1.5f, 2.f /* C, D, E, F */ -}; + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f +static const float multi_retrig_multiply[] = { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, .6666667f, .5f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.f, 1.5f, 2.f }; #define jar_xm_CLAMP_UP1F(vol, limit) do { \ if((vol) > (limit)) (vol) = (limit); \ @@ -1284,43 +1132,26 @@ static const float multi_retrig_multiply[] = { || (s)->effect_param == 6 \ || ((s)->volume_column >> 4) == 0xB) #define NOTE_IS_VALID(n) ((n) > 0 && (n) < 97) - -/* ----- Function definitions ----- */ +#define NOTE_OFF 97 static float jar_xm_waveform(jar_xm_waveform_type_t waveform, uint8_t step) { static unsigned int next_rand = 24492; step %= 0x40; - switch(waveform) { - - case jar_xm_SINE_WAVEFORM: - /* Why not use a table? For saving space, and because there's - * very very little actual performance gain. */ + case jar_xm_SINE_WAVEFORM: /* No SIN() table used, direct calculation. */ return -sinf(2.f * 3.141592f * (float)step / (float)0x40); - - case jar_xm_RAMP_DOWN_WAVEFORM: - /* Ramp down: 1.0f when step = 0; -1.0f when step = 0x40 */ + case jar_xm_RAMP_DOWN_WAVEFORM: /* Ramp down: 1.0f when step = 0; -1.0f when step = 0x40 */ return (float)(0x20 - step) / 0x20; - - case jar_xm_SQUARE_WAVEFORM: - /* Square with a 50% duty */ + case jar_xm_SQUARE_WAVEFORM: /* Square with a 50% duty */ return (step >= 0x20) ? 1.f : -1.f; - - case jar_xm_RANDOM_WAVEFORM: - /* Use the POSIX.1-2001 example, just to be deterministic - * across different machines */ + case jar_xm_RANDOM_WAVEFORM: /* Use the POSIX.1-2001 example, just to be deterministic across different machines */ next_rand = next_rand * 1103515245 + 12345; return (float)((next_rand >> 16) & 0x7FFF) / (float)0x4000 - 1.f; - - case jar_xm_RAMP_UP_WAVEFORM: - /* Ramp up: -1.f when step = 0; 1.f when step = 0x40 */ + case jar_xm_RAMP_UP_WAVEFORM: /* Ramp up: -1.f when step = 0; 1.f when step = 0x40 */ return (float)(step - 0x20) / 0x20; - default: break; - } - return .0f; } @@ -1328,33 +1159,21 @@ static void jar_xm_autovibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* if(ch->instrument == NULL || ch->instrument->vibrato_depth == 0) return; jar_xm_instrument_t* instr = ch->instrument; float sweep = 1.f; - - if(ch->autovibrato_ticks < instr->vibrato_sweep) { - /* No idea if this is correct, but it sounds close enough… */ - sweep = jar_xm_LERP(0.f, 1.f, (float)ch->autovibrato_ticks / (float)instr->vibrato_sweep); - } - + if(ch->autovibrato_ticks < instr->vibrato_sweep) { sweep = jar_xm_LERP(0.f, 1.f, (float)ch->autovibrato_ticks / (float)instr->vibrato_sweep); } unsigned int step = ((ch->autovibrato_ticks++) * instr->vibrato_rate) >> 2; - ch->autovibrato_note_offset = .25f * jar_xm_waveform(instr->vibrato_type, step) - * (float)instr->vibrato_depth / (float)0xF * sweep; + ch->autovibrato_note_offset = .25f * jar_xm_waveform(instr->vibrato_type, step) * (float)instr->vibrato_depth / (float)0xF * sweep; jar_xm_update_frequency(ctx, ch); } static void jar_xm_vibrato(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t pos) { unsigned int step = pos * (param >> 4); - ch->vibrato_note_offset = - 2.f - * jar_xm_waveform(ch->vibrato_waveform, step) - * (float)(param & 0x0F) / (float)0xF; + ch->vibrato_note_offset = 2.f * jar_xm_waveform(ch->vibrato_waveform, step) * (float)(param & 0x0F) / (float)0xF; jar_xm_update_frequency(ctx, ch); } static void jar_xm_tremolo(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t pos) { unsigned int step = pos * (param >> 4); - /* Not so sure about this, it sounds correct by ear compared with - * MilkyTracker, but it could come from other bugs */ - ch->tremolo_volume = -1.f * jar_xm_waveform(ch->tremolo_waveform, step) - * (float)(param & 0x0F) / (float)0xF; + ch->tremolo_volume = -1.f * jar_xm_waveform(ch->tremolo_waveform, step) * (float)(param & 0x0F) / (float)0xF; } static void jar_xm_arpeggio(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, uint8_t param, uint16_t tick) { @@ -1372,80 +1191,36 @@ static void jar_xm_arpeggio(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, ch->arp_note_offset = param & 0x0F; break; } - jar_xm_update_frequency(ctx, ch); } static void jar_xm_tone_portamento(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { - /* 3xx called without a note, wait until we get an actual - * target note. */ - if(ch->tone_portamento_target_period == 0.f) return; - + /* 3xx called without a note, wait until we get an actual target note. */ + if(ch->tone_portamento_target_period == 0.f) return; /* no value, exit */ if(ch->period != ch->tone_portamento_target_period) { - jar_xm_SLIDE_TOWARDS(ch->period, - ch->tone_portamento_target_period, - (ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES ? - 4.f : 1.f) * ch->tone_portamento_param - ); + jar_xm_SLIDE_TOWARDS(ch->period, ch->tone_portamento_target_period, (ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES ? 4.f : 1.f) * ch->tone_portamento_param); jar_xm_update_frequency(ctx, ch); } } static void jar_xm_pitch_slide(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, float period_offset) { - /* Don't ask about the 4.f coefficient. I found mention of it - * nowhere. Found by ear™. */ - if(ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES) { - period_offset *= 4.f; - } - + /* Don't ask about the 4.f coefficient. I found mention of it nowhere. Found by ear™. */ + if(ctx->module.frequency_type == jar_xm_LINEAR_FREQUENCIES) {period_offset *= 4.f; } ch->period += period_offset; jar_xm_CLAMP_DOWN(ch->period); /* XXX: upper bound of period ? */ - jar_xm_update_frequency(ctx, ch); } static void jar_xm_panning_slide(jar_xm_channel_context_t* ch, uint8_t rawval) { - float f; - - if((rawval & 0xF0) && (rawval & 0x0F)) { - /* Illegal state */ - return; - } - - if(rawval & 0xF0) { - /* Slide right */ - f = (float)(rawval >> 4) / (float)0xFF; - ch->panning += f; - jar_xm_CLAMP_UP(ch->panning); - } else { - /* Slide left */ - f = (float)(rawval & 0x0F) / (float)0xFF; - ch->panning -= f; - jar_xm_CLAMP_DOWN(ch->panning); - } -} + if (rawval & 0xF0) {ch->panning += (float)((rawval & 0xF0 )>> 4) / (float)0xFF;}; + if (rawval & 0x0F) {ch->panning -= (float)(rawval & 0x0F) / (float)0xFF;}; +}; static void jar_xm_volume_slide(jar_xm_channel_context_t* ch, uint8_t rawval) { - float f; - - if((rawval & 0xF0) && (rawval & 0x0F)) { - /* Illegal state */ - return; - } - - if(rawval & 0xF0) { - /* Slide up */ - f = (float)(rawval >> 4) / (float)0x40; - ch->volume += f; - jar_xm_CLAMP_UP(ch->volume); - } else { - /* Slide down */ - f = (float)(rawval & 0x0F) / (float)0x40; - ch->volume -= f; - jar_xm_CLAMP_DOWN(ch->volume); - } -} + if (rawval & 0xF0) {ch->volume += (float)((rawval & 0xF0) >> 4) / (float)0x40;}; + if (rawval & 0x0F) {ch->volume -= (float)(rawval & 0x0F) / (float)0x40;}; +}; static float jar_xm_envelope_lerp(jar_xm_envelope_point_t* a, jar_xm_envelope_point_t* b, uint16_t pos) { /* Linear interpolation between two envelope points */ @@ -1461,6 +1236,9 @@ static void jar_xm_post_pattern_change(jar_xm_context_t* ctx) { /* Loop if necessary */ if(ctx->current_table_index >= ctx->module.length) { ctx->current_table_index = ctx->module.restart_position; + ctx->tempo =ctx->default_tempo; // reset to file default value + ctx->bpm = ctx->default_bpm; // reset to file default value + ctx->global_volume = ctx->default_global_volume; // reset to file default value } } @@ -1477,24 +1255,19 @@ static float jar_xm_amiga_period(float note) { uint8_t a = intnote % 12; int8_t octave = note / 12.f - 2; uint16_t p1 = amiga_frequencies[a], p2 = amiga_frequencies[a + 1]; - if(octave > 0) { p1 >>= octave; p2 >>= octave; } else if(octave < 0) { - p1 <<= (-octave); - p2 <<= (-octave); + p1 <<= -octave; + p2 <<= -octave; } - return jar_xm_LERP(p1, p2, note - intnote); } static float jar_xm_amiga_frequency(float period) { if(period == .0f) return .0f; - - /* This is the PAL value. No reason to choose this one over the - * NTSC value. */ - return 7093789.2f / (period * 2.f); + return 7093789.2f / (period * 2.f); /* This is the PAL value. (we could use the NTSC value also) */ } static float jar_xm_period(jar_xm_context_t* ctx, float note) { @@ -1508,29 +1281,20 @@ static float jar_xm_period(jar_xm_context_t* ctx, float note) { } static float jar_xm_frequency(jar_xm_context_t* ctx, float period, float note_offset) { - uint8_t a; - int8_t octave; - float note; - uint16_t p1, p2; - switch(ctx->module.frequency_type) { - case jar_xm_LINEAR_FREQUENCIES: return jar_xm_linear_frequency(period - 64.f * note_offset); - case jar_xm_AMIGA_FREQUENCIES: - if(note_offset == 0) { - /* A chance to escape from insanity */ - return jar_xm_amiga_frequency(period); - } - - /* FIXME: this is very crappy at best */ - a = octave = 0; + if(note_offset == 0) { return jar_xm_amiga_frequency(period); }; + int8_t octave; + float note; + uint16_t p1, p2; + uint8_t a = octave = 0; /* Find the octave of the current period */ if(period > amiga_frequencies[0]) { --octave; - while(period > (amiga_frequencies[0] << (-octave))) --octave; + while(period > (amiga_frequencies[0] << -octave)) --octave; } else if(period < amiga_frequencies[12]) { ++octave; while(period < (amiga_frequencies[12] >> octave)) ++octave; @@ -1539,7 +1303,6 @@ static float jar_xm_frequency(jar_xm_context_t* ctx, float period, float note_of /* Find the smallest note closest to the current period */ for(uint8_t i = 0; i < 12; ++i) { p1 = amiga_frequencies[i], p2 = amiga_frequencies[i + 1]; - if(octave > 0) { p1 >>= octave; p2 >>= octave; @@ -1547,51 +1310,36 @@ static float jar_xm_frequency(jar_xm_context_t* ctx, float period, float note_of p1 <<= (-octave); p2 <<= (-octave); } - if(p2 <= period && period <= p1) { a = i; break; } } - - if(JAR_XM_DEBUG && (p1 < period || p2 > period)) { - DEBUG("%i <= %f <= %i should hold but doesn't, this is a bug", p2, period, p1); - } - + if(JAR_XM_DEBUG && (p1 < period || p2 > period)) { DEBUG("%i <= %f <= %i should hold but doesn't, this is a bug", p2, period, p1); } note = 12.f * (octave + 2) + a + jar_xm_INVERSE_LERP(p1, p2, period); - return jar_xm_amiga_frequency(jar_xm_amiga_period(note + note_offset)); - } return .0f; } static void jar_xm_update_frequency(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch) { - ch->frequency = jar_xm_frequency( - ctx, ch->period, - (ch->arp_note_offset > 0 ? ch->arp_note_offset : ( - ch->vibrato_note_offset + ch->autovibrato_note_offset - )) - ); + ch->frequency = jar_xm_frequency( ctx, ch->period, (ch->arp_note_offset > 0 ? ch->arp_note_offset : ( ch->vibrato_note_offset + ch->autovibrato_note_offset )) ); ch->step = ch->frequency / ctx->rate; } -static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, - jar_xm_pattern_slot_t* s) { +static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, jar_xm_pattern_slot_t* s) { + jar_xm_module_t* mod = &(ctx->module); if(s->instrument > 0) { - if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) { - /* Tone portamento in effect, unclear stuff happens */ + if(HAS_TONE_PORTAMENTO(ch->current) && ch->instrument != NULL && ch->sample != NULL) { /* Tone portamento in effect */ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); - } else if(s->instrument > ctx->module.num_instruments) { - /* Invalid instrument, Cut current note */ + } else if(s->instrument > ctx->module.num_instruments) { /* Invalid instrument, Cut current note */ jar_xm_cut_note(ch); ch->instrument = NULL; ch->sample = NULL; } else { ch->instrument = ctx->module.instruments + (s->instrument - 1); - if(s->note == 0 && ch->sample != NULL) { - /* Ghost instrument, trigger note */ + if(s->note == 0 && ch->sample != NULL) { /* Ghost instrument, trigger note */ /* Sample position is kept, but envelopes are reset */ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_SAMPLE_POSITION); } @@ -1599,270 +1347,174 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan } if(NOTE_IS_VALID(s->note)) { - /* Yes, the real note number is s->note -1. Try finding - * THAT in any of the specs! :-) */ - + // note value is s->note -1 jar_xm_instrument_t* instr = ch->instrument; - if(HAS_TONE_PORTAMENTO(ch->current) && instr != NULL && ch->sample != NULL) { /* Tone portamento in effect */ ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f; ch->tone_portamento_target_period = jar_xm_period(ctx, ch->note); - } else if(instr == NULL || ch->instrument->num_samples == 0) { - /* Bad instrument */ + } else if(instr == NULL || ch->instrument->num_samples == 0) { /* Issue on instrument */ jar_xm_cut_note(ch); } else { if(instr->sample_of_notes[s->note - 1] < instr->num_samples) { -#if JAR_XM_RAMPING - for(unsigned int z = 0; z < jar_xm_SAMPLE_RAMPING_POINTS; ++z) { - ch->end_of_previous_sample[z] = jar_xm_next_of_sample(ch); - } - ch->frame_count = 0; -#endif + if (mod->ramping) { + for(int i = 0; i < jar_xm_SAMPLE_RAMPING_POINTS; ++i) { + jar_xm_next_of_sample(ctx, ch, i); + } + ch->frame_count = 0; + }; ch->sample = instr->samples + instr->sample_of_notes[s->note - 1]; - ch->orig_note = ch->note = s->note + ch->sample->relative_note - + ch->sample->finetune / 128.f - 1.f; + ch->orig_note = ch->note = s->note + ch->sample->relative_note + ch->sample->finetune / 128.f - 1.f; if(s->instrument > 0) { jar_xm_trigger_note(ctx, ch, 0); - } else { - /* Ghost note: keep old volume */ + } else { /* Ghost note: keep old volume */ jar_xm_trigger_note(ctx, ch, jar_xm_TRIGGER_KEEP_VOLUME); } } else { - /* Bad sample */ jar_xm_cut_note(ch); } } - } else if(s->note == 97) { - /* Key Off */ + } else if(s->note == NOTE_OFF) { jar_xm_key_off(ch); } - switch(s->volume_column >> 4) { - - case 0x5: - if(s->volume_column > 0x50) break; - case 0x1: - case 0x2: - case 0x3: - case 0x4: - /* Set volume */ - ch->volume = (float)(s->volume_column - 0x10) / (float)0x40; - break; - - case 0x8: /* Fine volume slide down */ - jar_xm_volume_slide(ch, s->volume_column & 0x0F); - break; - - case 0x9: /* Fine volume slide up */ - jar_xm_volume_slide(ch, s->volume_column << 4); - break; - - case 0xA: /* Set vibrato speed */ - ch->vibrato_param = (ch->vibrato_param & 0x0F) | ((s->volume_column & 0x0F) << 4); - break; - - case 0xC: /* Set panning */ - ch->panning = (float)( - ((s->volume_column & 0x0F) << 4) | (s->volume_column & 0x0F) - ) / (float)0xFF; - break; - - case 0xF: /* Tone portamento */ - if(s->volume_column & 0x0F) { - ch->tone_portamento_param = ((s->volume_column & 0x0F) << 4) - | (s->volume_column & 0x0F); - } - break; - - default: - break; - - } - + // Interpret Effect column switch(s->effect_type) { - case 1: /* 1xx: Portamento up */ - if(s->effect_param > 0) { - ch->portamento_up_param = s->effect_param; - } + if(s->effect_param > 0) { ch->portamento_up_param = s->effect_param; } break; - case 2: /* 2xx: Portamento down */ - if(s->effect_param > 0) { - ch->portamento_down_param = s->effect_param; - } + if(s->effect_param > 0) { ch->portamento_down_param = s->effect_param; } break; - case 3: /* 3xx: Tone portamento */ - if(s->effect_param > 0) { - ch->tone_portamento_param = s->effect_param; - } + if(s->effect_param > 0) { ch->tone_portamento_param = s->effect_param; } break; - case 4: /* 4xy: Vibrato */ - if(s->effect_param & 0x0F) { - /* Set vibrato depth */ - ch->vibrato_param = (ch->vibrato_param & 0xF0) | (s->effect_param & 0x0F); - } - if(s->effect_param >> 4) { - /* Set vibrato speed */ - ch->vibrato_param = (s->effect_param & 0xF0) | (ch->vibrato_param & 0x0F); - } + if(s->effect_param & 0x0F) { ch->vibrato_param = (ch->vibrato_param & 0xF0) | (s->effect_param & 0x0F); } /* Set vibrato depth */ + if(s->effect_param >> 4) { ch->vibrato_param = (s->effect_param & 0xF0) | (ch->vibrato_param & 0x0F); } /* Set vibrato speed */ break; - case 5: /* 5xy: Tone portamento + Volume slide */ - if(s->effect_param > 0) { - ch->volume_slide_param = s->effect_param; - } + if(s->effect_param > 0) { ch->volume_slide_param = s->effect_param; } break; - case 6: /* 6xy: Vibrato + Volume slide */ - if(s->effect_param > 0) { - ch->volume_slide_param = s->effect_param; - } + if(s->effect_param > 0) { ch->volume_slide_param = s->effect_param; } break; - case 7: /* 7xy: Tremolo */ - if(s->effect_param & 0x0F) { - /* Set tremolo depth */ - ch->tremolo_param = (ch->tremolo_param & 0xF0) | (s->effect_param & 0x0F); - } - if(s->effect_param >> 4) { - /* Set tremolo speed */ - ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F); - } + if(s->effect_param & 0x0F) { ch->tremolo_param = (ch->tremolo_param & 0xF0) | (s->effect_param & 0x0F); } /* Set tremolo depth */ + if(s->effect_param >> 4) { ch->tremolo_param = (s->effect_param & 0xF0) | (ch->tremolo_param & 0x0F); } /* Set tremolo speed */ break; - case 8: /* 8xx: Set panning */ - ch->panning = (float)s->effect_param / (float)0xFF; + ch->panning = (float)s->effect_param / 255.f; break; - case 9: /* 9xx: Sample offset */ - if(ch->sample != NULL && NOTE_IS_VALID(s->note)) { + if(ch->sample != 0) { //&& NOTE_IS_VALID(s->note)) { uint32_t final_offset = s->effect_param << (ch->sample->bits == 16 ? 7 : 8); - if(final_offset >= ch->sample->length) { - /* Pretend the sample dosen't loop and is done playing */ - ch->sample_position = -1; + switch (ch->sample->loop_type) { + case jar_xm_NO_LOOP: + if(final_offset >= ch->sample->length) { /* Pretend the sample dosen't loop and is done playing */ + ch->sample_position = -1; + } else { + ch->sample_position = final_offset; + } + break; + case jar_xm_FORWARD_LOOP: + if (final_offset >= ch->sample->loop_end) { + ch->sample_position -= ch->sample->loop_length; + } else if(final_offset >= ch->sample->length) { + ch->sample_position = ch->sample->loop_start; + } else { + ch->sample_position = final_offset; + } + break; + case jar_xm_PING_PONG_LOOP: + if(final_offset >= ch->sample->loop_end) { + ch->ping = false; + ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; + } else if(final_offset >= ch->sample->length) { + ch->ping = false; + ch->sample_position -= ch->sample->length - 1; + } else { + ch->sample_position = final_offset; + }; break; } - ch->sample_position = final_offset; } break; - case 0xA: /* Axy: Volume slide */ - if(s->effect_param > 0) { - ch->volume_slide_param = s->effect_param; - } + if(s->effect_param > 0) { ch->volume_slide_param = s->effect_param; } break; - case 0xB: /* Bxx: Position jump */ if(s->effect_param < ctx->module.length) { ctx->position_jump = true; ctx->jump_dest = s->effect_param; } break; - case 0xC: /* Cxx: Set volume */ - ch->volume = (float)((s->effect_param > 0x40) - ? 0x40 : s->effect_param) / (float)0x40; + ch->volume = (float)((s->effect_param > 0x40) ? 0x40 : s->effect_param) / (float)0x40; break; - case 0xD: /* Dxx: Pattern break */ /* Jump after playing this line */ ctx->pattern_break = true; ctx->jump_row = (s->effect_param >> 4) * 10 + (s->effect_param & 0x0F); break; - case 0xE: /* EXy: Extended command */ switch(s->effect_param >> 4) { - case 1: /* E1y: Fine portamento up */ - if(s->effect_param & 0x0F) { - ch->fine_portamento_up_param = s->effect_param & 0x0F; - } + if(s->effect_param & 0x0F) { ch->fine_portamento_up_param = s->effect_param & 0x0F; } jar_xm_pitch_slide(ctx, ch, -ch->fine_portamento_up_param); break; - case 2: /* E2y: Fine portamento down */ - if(s->effect_param & 0x0F) { - ch->fine_portamento_down_param = s->effect_param & 0x0F; - } + if(s->effect_param & 0x0F) { ch->fine_portamento_down_param = s->effect_param & 0x0F; } jar_xm_pitch_slide(ctx, ch, ch->fine_portamento_down_param); break; - case 4: /* E4y: Set vibrato control */ ch->vibrato_waveform = s->effect_param & 3; ch->vibrato_waveform_retrigger = !((s->effect_param >> 2) & 1); break; - case 5: /* E5y: Set finetune */ if(NOTE_IS_VALID(ch->current->note) && ch->sample != NULL) { - ch->note = ch->current->note + ch->sample->relative_note + - (float)(((s->effect_param & 0x0F) - 8) << 4) / 128.f - 1.f; + ch->note = ch->current->note + ch->sample->relative_note + (float)(((s->effect_param & 0x0F) - 8) << 4) / 128.f - 1.f; ch->period = jar_xm_period(ctx, ch->note); jar_xm_update_frequency(ctx, ch); } break; - case 6: /* E6y: Pattern loop */ if(s->effect_param & 0x0F) { - if((s->effect_param & 0x0F) == ch->pattern_loop_count) { - /* Loop is over */ + if((s->effect_param & 0x0F) == ch->pattern_loop_count) { /* Loop is over */ ch->pattern_loop_count = 0; - break; + ctx->position_jump = false; + } else { /* Jump to the beginning of the loop */ + ch->pattern_loop_count++; + ctx->position_jump = true; + ctx->jump_row = ch->pattern_loop_origin; + ctx->jump_dest = ctx->current_table_index; } - - /* Jump to the beginning of the loop */ - ch->pattern_loop_count++; - ctx->position_jump = true; - ctx->jump_row = ch->pattern_loop_origin; - ctx->jump_dest = ctx->current_table_index; } else { - /* Set loop start point */ - ch->pattern_loop_origin = ctx->current_row; - /* Replicate FT2 E60 bug */ - ctx->jump_row = ch->pattern_loop_origin; + ch->pattern_loop_origin = ctx->current_row; /* Set loop start point */ + ctx->jump_row = ch->pattern_loop_origin; /* Replicate FT2 E60 bug */ } break; - case 7: /* E7y: Set tremolo control */ ch->tremolo_waveform = s->effect_param & 3; ch->tremolo_waveform_retrigger = !((s->effect_param >> 2) & 1); break; - case 0xA: /* EAy: Fine volume slide up */ - if(s->effect_param & 0x0F) { - ch->fine_volume_slide_param = s->effect_param & 0x0F; - } + if(s->effect_param & 0x0F) { ch->fine_volume_slide_param = s->effect_param & 0x0F; } jar_xm_volume_slide(ch, ch->fine_volume_slide_param << 4); break; - case 0xB: /* EBy: Fine volume slide down */ - if(s->effect_param & 0x0F) { - ch->fine_volume_slide_param = s->effect_param & 0x0F; - } + if(s->effect_param & 0x0F) { ch->fine_volume_slide_param = s->effect_param & 0x0F; } jar_xm_volume_slide(ch, ch->fine_volume_slide_param); break; - case 0xD: /* EDy: Note delay */ - /* XXX: figure this out better. EDx triggers - * the note even when there no note and no - * instrument. But ED0 acts like like a ghost - * note, EDx (x ≠ 0) does not. */ + /* XXX: figure this out better. EDx triggers the note even when there no note and no instrument. But ED0 acts like like a ghost note, EDx (x ≠ 0) does not. */ if(s->note == 0 && s->instrument == 0) { unsigned int flags = jar_xm_TRIGGER_KEEP_VOLUME; - if(ch->current->effect_param & 0x0F) { ch->note = ch->orig_note; jar_xm_trigger_note(ctx, ch, flags); } else { - jar_xm_trigger_note( - ctx, ch, - flags - | jar_xm_TRIGGER_KEEP_PERIOD - | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION - ); + jar_xm_trigger_note(ctx, ch, flags | jar_xm_TRIGGER_KEEP_PERIOD | jar_xm_TRIGGER_KEEP_SAMPLE_POSITION ); } } break; @@ -1870,106 +1522,77 @@ static void jar_xm_handle_note_and_instrument(jar_xm_context_t* ctx, jar_xm_chan case 0xE: /* EEy: Pattern delay */ ctx->extra_ticks = (ch->current->effect_param & 0x0F) * ctx->tempo; break; - default: break; - } break; case 0xF: /* Fxx: Set tempo/BPM */ if(s->effect_param > 0) { - if(s->effect_param <= 0x1F) { + if(s->effect_param <= 0x1F) { // First 32 possible values adjust the ticks (goes into tempo) ctx->tempo = s->effect_param; - } else { + } else { //32 and greater values adjust the BPM ctx->bpm = s->effect_param; } } break; case 16: /* Gxx: Set global volume */ - ctx->global_volume = (float)((s->effect_param > 0x40) - ? 0x40 : s->effect_param) / (float)0x40; + ctx->global_volume = (float)((s->effect_param > 0x40) ? 0x40 : s->effect_param) / (float)0x40; break; - case 17: /* Hxy: Global volume slide */ - if(s->effect_param > 0) { - ch->global_volume_slide_param = s->effect_param; - } + if(s->effect_param > 0) { ch->global_volume_slide_param = s->effect_param; } break; - case 21: /* Lxx: Set envelope position */ ch->volume_envelope_frame_count = s->effect_param; ch->panning_envelope_frame_count = s->effect_param; break; - case 25: /* Pxy: Panning slide */ - if(s->effect_param > 0) { - ch->panning_slide_param = s->effect_param; - } + if(s->effect_param > 0) { ch->panning_slide_param = s->effect_param; } break; - case 27: /* Rxy: Multi retrig note */ if(s->effect_param > 0) { - if((s->effect_param >> 4) == 0) { - /* Keep previous x value */ + if((s->effect_param >> 4) == 0) { /* Keep previous x value */ ch->multi_retrig_param = (ch->multi_retrig_param & 0xF0) | (s->effect_param & 0x0F); } else { ch->multi_retrig_param = s->effect_param; } } break; - case 29: /* Txy: Tremor */ - if(s->effect_param > 0) { - /* Tremor x and y params do not appear to be separately - * kept in memory, unlike Rxy */ - ch->tremor_param = s->effect_param; - } + if(s->effect_param > 0) { ch->tremor_param = s->effect_param; } /* Tremor x and y params are not separately kept in memory, unlike Rxy */ break; - case 33: /* Xxy: Extra stuff */ switch(s->effect_param >> 4) { case 1: /* X1y: Extra fine portamento up */ - if(s->effect_param & 0x0F) { - ch->extra_fine_portamento_up_param = s->effect_param & 0x0F; - } + if(s->effect_param & 0x0F) { ch->extra_fine_portamento_up_param = s->effect_param & 0x0F; } jar_xm_pitch_slide(ctx, ch, -1.0f * ch->extra_fine_portamento_up_param); break; - case 2: /* X2y: Extra fine portamento down */ - if(s->effect_param & 0x0F) { - ch->extra_fine_portamento_down_param = s->effect_param & 0x0F; - } + if(s->effect_param & 0x0F) { ch->extra_fine_portamento_down_param = s->effect_param & 0x0F; } jar_xm_pitch_slide(ctx, ch, ch->extra_fine_portamento_down_param); break; - default: break; - } break; - default: break; - } } static void jar_xm_trigger_note(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, unsigned int flags) { - if(!(flags & jar_xm_TRIGGER_KEEP_SAMPLE_POSITION)) { + if (!(flags & jar_xm_TRIGGER_KEEP_SAMPLE_POSITION)) { ch->sample_position = 0.f; ch->ping = true; - } - - if(ch->sample != NULL) { - if(!(flags & jar_xm_TRIGGER_KEEP_VOLUME)) { - ch->volume = ch->sample->volume; - } - - ch->panning = ch->sample->panning; - } - + }; + + if (!(flags & jar_xm_TRIGGER_KEEP_VOLUME)) { + if(ch->sample != NULL) { + ch->volume = ch->sample->volume; + }; + }; + ch->panning = ch->sample->panning; ch->sustained = true; ch->fadeout_volume = ch->volume_envelope_volume = 1.0f; ch->panning_envelope_panning = .5f; @@ -1977,44 +1600,28 @@ static void jar_xm_trigger_note(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch->vibrato_note_offset = 0.f; ch->tremolo_volume = 0.f; ch->tremor_on = false; - ch->autovibrato_ticks = 0; - if(ch->vibrato_waveform_retrigger) { - ch->vibrato_ticks = 0; /* XXX: should the waveform itself also - * be reset to sine? */ - } - if(ch->tremolo_waveform_retrigger) { - ch->tremolo_ticks = 0; - } - + if(ch->vibrato_waveform_retrigger) { ch->vibrato_ticks = 0; } /* XXX: should the waveform itself also be reset to sine? */ + if(ch->tremolo_waveform_retrigger) { ch->tremolo_ticks = 0; } if(!(flags & jar_xm_TRIGGER_KEEP_PERIOD)) { ch->period = jar_xm_period(ctx, ch->note); jar_xm_update_frequency(ctx, ch); } - ch->latest_trigger = ctx->generated_samples; - if(ch->instrument != NULL) { - ch->instrument->latest_trigger = ctx->generated_samples; - } - if(ch->sample != NULL) { - ch->sample->latest_trigger = ctx->generated_samples; - } + if(ch->instrument != NULL) { ch->instrument->latest_trigger = ctx->generated_samples; } + if(ch->sample != NULL) { ch->sample->latest_trigger = ctx->generated_samples; } } static void jar_xm_cut_note(jar_xm_channel_context_t* ch) { - /* NB: this is not the same as Key Off */ - ch->volume = .0f; + ch->volume = .0f; /* NB: this is not the same as Key Off */ +// ch->curr_left = .0f; +// ch->curr_right = .0f; } static void jar_xm_key_off(jar_xm_channel_context_t* ch) { - /* Key Off */ - ch->sustained = false; - - /* If no volume envelope is used, also cut the note */ - if(ch->instrument == NULL || !ch->instrument->volume_envelope.enabled) { - jar_xm_cut_note(ch); - } + ch->sustained = false; /* Key Off */ + if(ch->instrument == NULL || !ch->instrument->volume_envelope.enabled) { jar_xm_cut_note(ch); } /* If no volume envelope is used, also cut the note */ } static void jar_xm_row(jar_xm_context_t* ctx) { @@ -2032,24 +1639,24 @@ static void jar_xm_row(jar_xm_context_t* ctx) { ctx->jump_row = 0; jar_xm_post_pattern_change(ctx); } - jar_xm_pattern_t* cur = ctx->module.patterns + ctx->module.pattern_table[ctx->current_table_index]; bool in_a_loop = false; - /* Read notes… */ + /* Read notes information for all channels into temporary pattern slot */ for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { jar_xm_pattern_slot_t* s = cur->slots + ctx->current_row * ctx->module.num_channels + i; jar_xm_channel_context_t* ch = ctx->channels + i; - ch->current = s; - + // If there is no note delay effect (0xED) then... if(s->effect_type != 0xE || s->effect_param >> 4 != 0xD) { + //********** Process the channel slot information ********** jar_xm_handle_note_and_instrument(ctx, ch, s); } else { + // read the note delay information ch->note_delay_param = s->effect_param & 0x0F; } - if(!in_a_loop && ch->pattern_loop_count > 0) { + // clarify if in a loop or not in_a_loop = true; } } @@ -2058,101 +1665,68 @@ static void jar_xm_row(jar_xm_context_t* ctx) { /* No E6y loop is in effect (or we are in the first pass) */ ctx->loop_count = (ctx->row_loop_count[MAX_NUM_ROWS * ctx->current_table_index + ctx->current_row]++); } - - ctx->current_row++; /* Since this is an uint8, this line can - * increment from 255 to 0, in which case it - * is still necessary to go the next - * pattern. */ - if(!ctx->position_jump && !ctx->pattern_break && - (ctx->current_row >= cur->num_rows || ctx->current_row == 0)) { + + /// Move to next row + ctx->current_row++; /* uint8 warning: can increment from 255 to 0, in which case it is still necessary to go the next pattern. */ + if (!ctx->position_jump && !ctx->pattern_break && (ctx->current_row >= cur->num_rows || ctx->current_row == 0)) { ctx->current_table_index++; - ctx->current_row = ctx->jump_row; /* This will be 0 most of - * the time, except when E60 - * is used */ + ctx->current_row = ctx->jump_row; /* This will be 0 most of the time, except when E60 is used */ ctx->jump_row = 0; jar_xm_post_pattern_change(ctx); } } -static void jar_xm_envelope_tick(jar_xm_channel_context_t* ch, - jar_xm_envelope_t* env, - uint16_t* counter, - float* outval) { +static void jar_xm_envelope_tick(jar_xm_channel_context_t *ch, jar_xm_envelope_t *env, uint16_t *counter, float *outval) { if(env->num_points < 2) { - /* Don't really know what to do… */ if(env->num_points == 1) { - /* XXX I am pulling this out of my ass */ *outval = (float)env->points[0].value / (float)0x40; - if(*outval > 1) { - *outval = 1; - } - } - - return; + if(*outval > 1) { *outval = 1; }; + } else {; + return; + }; } else { - uint8_t j; - if(env->loop_enabled) { uint16_t loop_start = env->points[env->loop_start_point].frame; uint16_t loop_end = env->points[env->loop_end_point].frame; uint16_t loop_length = loop_end - loop_start; - - if(*counter >= loop_end) { - *counter -= loop_length; - } - } - - for(j = 0; j < (env->num_points - 2); ++j) { - if(env->points[j].frame <= *counter && - env->points[j+1].frame >= *counter) { + if(*counter >= loop_end) { *counter -= loop_length; }; + }; + for(uint8_t j = 0; j < (env->num_points - 1); ++j) { + if(env->points[j].frame <= *counter && env->points[j+1].frame >= *counter) { + *outval = jar_xm_envelope_lerp(env->points + j, env->points + j + 1, *counter) / (float)0x40; break; - } - } - - *outval = jar_xm_envelope_lerp(env->points + j, env->points + j + 1, *counter) / (float)0x40; - + }; + }; /* Make sure it is safe to increment frame count */ - if(!ch->sustained || !env->sustain_enabled || - *counter != env->points[env->sustain_point].frame) { - (*counter)++; - } - } -} + if(!ch->sustained || !env->sustain_enabled || *counter != env->points[env->sustain_point].frame) { (*counter)++; }; + }; +}; -static void jar_xm_envelopes(jar_xm_channel_context_t* ch) { +static void jar_xm_envelopes(jar_xm_channel_context_t *ch) { if(ch->instrument != NULL) { if(ch->instrument->volume_envelope.enabled) { if(!ch->sustained) { ch->fadeout_volume -= (float)ch->instrument->volume_fadeout / 65536.f; jar_xm_CLAMP_DOWN(ch->fadeout_volume); - } - - jar_xm_envelope_tick(ch, - &(ch->instrument->volume_envelope), - &(ch->volume_envelope_frame_count), - &(ch->volume_envelope_volume)); - } - + }; + jar_xm_envelope_tick(ch, &(ch->instrument->volume_envelope), &(ch->volume_envelope_frame_count), &(ch->volume_envelope_volume)); + }; if(ch->instrument->panning_envelope.enabled) { - jar_xm_envelope_tick(ch, - &(ch->instrument->panning_envelope), - &(ch->panning_envelope_frame_count), - &(ch->panning_envelope_panning)); - } - } -} + jar_xm_envelope_tick(ch, &(ch->instrument->panning_envelope), &(ch->panning_envelope_frame_count), &(ch->panning_envelope_panning)); + }; + }; +}; static void jar_xm_tick(jar_xm_context_t* ctx) { if(ctx->current_tick == 0) { - jar_xm_row(ctx); + jar_xm_row(ctx); // We have processed all ticks and we run the row } - + + jar_xm_module_t* mod = &(ctx->module); for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { jar_xm_channel_context_t* ch = ctx->channels + i; - jar_xm_envelopes(ch); jar_xm_autovibrato(ctx, ch); - if(ch->arp_in_progress && !HAS_ARPEGGIO(ch->current)) { ch->arp_in_progress = false; ch->arp_note_offset = 0; @@ -2164,46 +1738,59 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { jar_xm_update_frequency(ctx, ch); } - switch(ch->current->volume_column >> 4) { - - case 0x6: /* Volume slide down */ - if(ctx->current_tick == 0) break; + // Effects in volumne column mostly handled on a per tick basis + switch(ch->current->volume_column & 0xF0) { + case 0x50: // Checks for volume = 64 + if(ch->current->volume_column != 0x50) break; + case 0x10: // Set volume 0-15 + case 0x20: // Set volume 16-32 + case 0x30: // Set volume 32-48 + case 0x40: // Set volume 48-64 + ch->volume = (float)(ch->current->volume_column - 16) / 64.0f; + break; + case 0x60: // Volume slide down jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F); break; - - case 0x7: /* Volume slide up */ - if(ctx->current_tick == 0) break; + case 0x70: // Volume slide up jar_xm_volume_slide(ch, ch->current->volume_column << 4); break; - - case 0xB: /* Vibrato */ - if(ctx->current_tick == 0) break; + case 0x80: // Fine volume slide down + jar_xm_volume_slide(ch, ch->current->volume_column & 0x0F); + break; + case 0x90: // Fine volume slide up + jar_xm_volume_slide(ch, ch->current->volume_column << 4); + break; + case 0xA0: // Set vibrato speed + ch->vibrato_param = (ch->vibrato_param & 0x0F) | ((ch->current->volume_column & 0x0F) << 4); + break; + case 0xB0: // Vibrato ch->vibrato_in_progress = false; jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); break; - - case 0xD: /* Panning slide left */ - if(ctx->current_tick == 0) break; + case 0xC0: // Set panning + if(!ctx->current_tick ) { + ch->panning = (float)(ch->current->volume_column & 0x0F) / 15.0f; + } + break; + case 0xD0: // Panning slide left jar_xm_panning_slide(ch, ch->current->volume_column & 0x0F); break; - - case 0xE: /* Panning slide right */ - if(ctx->current_tick == 0) break; + case 0xE0: // Panning slide right jar_xm_panning_slide(ch, ch->current->volume_column << 4); break; - - case 0xF: /* Tone portamento */ - if(ctx->current_tick == 0) break; + case 0xF0: // Tone portamento + if(!ctx->current_tick ) { + if(ch->current->volume_column & 0x0F) { ch->tone_portamento_param = ((ch->current->volume_column & 0x0F) << 4) | (ch->current->volume_column & 0x0F); } + }; jar_xm_tone_portamento(ctx, ch); break; - default: break; - } + // Only some standard effects handled on a per tick basis + // see jar_xm_handle_note_and_instrument for all effects handling on a per row basis switch(ch->current->effect_type) { - case 0: /* 0xy: Arpeggio */ if(ch->current->effect_param > 0) { char arp_offset = ctx->tempo % 3; @@ -2236,49 +1823,44 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { if(ctx->current_tick == 0) break; jar_xm_pitch_slide(ctx, ch, -ch->portamento_up_param); break; - case 2: /* 2xx: Portamento down */ if(ctx->current_tick == 0) break; jar_xm_pitch_slide(ctx, ch, ch->portamento_down_param); break; - case 3: /* 3xx: Tone portamento */ if(ctx->current_tick == 0) break; jar_xm_tone_portamento(ctx, ch); break; - case 4: /* 4xy: Vibrato */ if(ctx->current_tick == 0) break; ch->vibrato_in_progress = true; jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); break; - case 5: /* 5xy: Tone portamento + Volume slide */ if(ctx->current_tick == 0) break; jar_xm_tone_portamento(ctx, ch); jar_xm_volume_slide(ch, ch->volume_slide_param); break; - case 6: /* 6xy: Vibrato + Volume slide */ if(ctx->current_tick == 0) break; ch->vibrato_in_progress = true; jar_xm_vibrato(ctx, ch, ch->vibrato_param, ch->vibrato_ticks++); jar_xm_volume_slide(ch, ch->volume_slide_param); break; - case 7: /* 7xy: Tremolo */ if(ctx->current_tick == 0) break; jar_xm_tremolo(ctx, ch, ch->tremolo_param, ch->tremolo_ticks++); break; - + case 8: /* 8xy: Set panning */ + break; + case 9: /* 9xy: Sample offset */ + break; case 0xA: /* Axy: Volume slide */ if(ctx->current_tick == 0) break; jar_xm_volume_slide(ch, ch->volume_slide_param); break; - case 0xE: /* EXy: Extended command */ switch(ch->current->effect_param >> 4) { - case 0x9: /* E9y: Retrigger note */ if(ctx->current_tick != 0 && ch->current->effect_param & 0x0F) { if(!(ctx->current_tick % (ch->current->effect_param & 0x0F))) { @@ -2287,59 +1869,46 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { } } break; - case 0xC: /* ECy: Note cut */ if((ch->current->effect_param & 0x0F) == ctx->current_tick) { jar_xm_cut_note(ch); } break; - case 0xD: /* EDy: Note delay */ if(ch->note_delay_param == ctx->current_tick) { jar_xm_handle_note_and_instrument(ctx, ch, ch->current); jar_xm_envelopes(ch); } break; - default: break; - } break; - + case 16: /* Fxy: Set tempo/BPM */ + break; case 17: /* Hxy: Global volume slide */ if(ctx->current_tick == 0) break; - if((ch->global_volume_slide_param & 0xF0) && - (ch->global_volume_slide_param & 0x0F)) { - /* Illegal state */ - break; - } - if(ch->global_volume_slide_param & 0xF0) { - /* Global slide up */ + if((ch->global_volume_slide_param & 0xF0) && (ch->global_volume_slide_param & 0x0F)) { break; }; /* Invalid state */ + if(ch->global_volume_slide_param & 0xF0) { /* Global slide up */ float f = (float)(ch->global_volume_slide_param >> 4) / (float)0x40; ctx->global_volume += f; jar_xm_CLAMP_UP(ctx->global_volume); - } else { - /* Global slide down */ + } else { /* Global slide down */ float f = (float)(ch->global_volume_slide_param & 0x0F) / (float)0x40; ctx->global_volume -= f; jar_xm_CLAMP_DOWN(ctx->global_volume); - } + }; break; case 20: /* Kxx: Key off */ - /* Most documentations will tell you the parameter has no - * use. Don't be fooled. */ - if(ctx->current_tick == ch->current->effect_param) { - jar_xm_key_off(ch); - } + if(ctx->current_tick == ch->current->effect_param) { jar_xm_key_off(ch); }; break; - + case 21: /* Lxx: Set envelope position */ + break; case 25: /* Pxy: Panning slide */ if(ctx->current_tick == 0) break; jar_xm_panning_slide(ch, ch->panning_slide_param); break; - case 27: /* Rxy: Multi retrig note */ if(ctx->current_tick == 0) break; if(((ch->multi_retrig_param) & 0x0F) == 0) break; @@ -2349,228 +1918,251 @@ static void jar_xm_tick(jar_xm_context_t* ctx) { jar_xm_CLAMP(v); jar_xm_trigger_note(ctx, ch, 0); ch->volume = v; - } + }; break; case 29: /* Txy: Tremor */ if(ctx->current_tick == 0) break; - ch->tremor_on = ( - (ctx->current_tick - 1) % ((ch->tremor_param >> 4) + (ch->tremor_param & 0x0F) + 2) - > - (ch->tremor_param >> 4) - ); + ch->tremor_on = ( (ctx->current_tick - 1) % ((ch->tremor_param >> 4) + (ch->tremor_param & 0x0F) + 2) > (ch->tremor_param >> 4) ); break; - default: break; - - } + }; float panning, volume; - - panning = ch->panning + - (ch->panning_envelope_panning - .5f) * (.5f - fabs(ch->panning - .5f)) * 2.0f; - + panning = ch->panning + (ch->panning_envelope_panning - .5f) * (.5f - fabs(ch->panning - .5f)) * 2.0f; if(ch->tremor_on) { - volume = .0f; + volume = .0f; } else { volume = ch->volume + ch->tremolo_volume; jar_xm_CLAMP(volume); volume *= ch->fadeout_volume * ch->volume_envelope_volume; - } + }; -#if JAR_XM_RAMPING - ch->target_panning = panning; - ch->target_volume = volume; -#else - ch->actual_panning = panning; - ch->actual_volume = volume; -#endif - } + if (mod->ramping) { + ch->target_panning = panning; + ch->target_volume = volume; + } else { + ch->actual_panning = panning; + ch->actual_volume = volume; + }; + }; - ctx->current_tick++; + ctx->current_tick++; // ok so we understand that ticks increment within the row if(ctx->current_tick >= ctx->tempo + ctx->extra_ticks) { + // This means it reached the end of the row and we reset ctx->current_tick = 0; ctx->extra_ticks = 0; - } + }; - /* FT2 manual says number of ticks / second = BPM * 0.4 */ + // Number of ticks / second = BPM * 0.4 ctx->remaining_samples_in_tick += (float)ctx->rate / ((float)ctx->bpm * 0.4f); -} +}; -static float jar_xm_next_of_sample(jar_xm_channel_context_t* ch) { +static void jar_xm_next_of_sample(jar_xm_context_t* ctx, jar_xm_channel_context_t* ch, int previous) { + jar_xm_module_t* mod = &(ctx->module); + +// ch->curr_left = 0.f; +// ch->curr_right = 0.f; if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) { -#if JAR_XM_RAMPING - if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { - return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], .0f, - (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); - } -#endif - return .0f; - } + ch->curr_left = 0.f; + ch->curr_right = 0.f; + if (mod->ramping) { + if (ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + if (previous > -1) { + ch->end_of_previous_sample_left[previous] = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], ch->curr_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->end_of_previous_sample_right[previous] = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], ch->curr_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } else { + ch->curr_left = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], ch->curr_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->curr_right = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], ch->curr_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + }; + }; + }; + return; + }; if(ch->sample->length == 0) { - return .0f; - } + return; + }; - float u, v, t; - uint32_t a, b; - a = (uint32_t)ch->sample_position; /* This cast is fine, - * sample_position will not - * go above integer - * ranges */ - if(JAR_XM_LINEAR_INTERPOLATION) { - b = a + 1; - t = ch->sample_position - a; /* Cheaper than fmodf(., 1.f) */ - } - u = ch->sample->data[a]; + float t = 0.f; + uint32_t b = 0; + if(mod->linear_interpolation) { + b = ch->sample_position + 1; + t = ch->sample_position - (uint32_t)ch->sample_position; /* Cheaper than fmodf(., 1.f) */ + }; + float u_left, u_right; + u_left = ch->sample->data[(uint32_t)ch->sample_position]; + if (ch->sample->stereo) { + u_right = ch->sample->data[(uint32_t)ch->sample_position + ch->sample->length]; + } else { + u_right = u_left; + }; + float v_left = 0.f, v_right = 0.f; switch(ch->sample->loop_type) { - case jar_xm_NO_LOOP: - if(JAR_XM_LINEAR_INTERPOLATION) { - v = (b < ch->sample->length) ? ch->sample->data[b] : .0f; - } + if(mod->linear_interpolation) { + v_left = (b < ch->sample->length) ? ch->sample->data[b] : .0f; + if (ch->sample->stereo) { + v_right = (b < ch->sample->length) ? ch->sample->data[b + ch->sample->length] : .0f; + } else { + v_right = v_left; + }; + }; ch->sample_position += ch->step; - if(ch->sample_position >= ch->sample->length) { - ch->sample_position = -1; - } + if(ch->sample_position >= ch->sample->length) { ch->sample_position = -1; } // stop playing this sample break; - case jar_xm_FORWARD_LOOP: - if(JAR_XM_LINEAR_INTERPOLATION) { - v = ch->sample->data[ - (b == ch->sample->loop_end) ? ch->sample->loop_start : b - ]; - } + if(mod->linear_interpolation) { + v_left = ch->sample->data[ (b == ch->sample->loop_end) ? ch->sample->loop_start : b ]; + if (ch->sample->stereo) { + v_right = ch->sample->data[ (b == ch->sample->loop_end) ? ch->sample->loop_start + ch->sample->length : b + ch->sample->length]; + } else { + v_right = v_left; + }; + }; ch->sample_position += ch->step; - while(ch->sample_position >= ch->sample->loop_end) { + if (ch->sample_position >= ch->sample->loop_end) { ch->sample_position -= ch->sample->loop_length; - } + }; + if(ch->sample_position >= ch->sample->length) { + ch->sample_position = ch->sample->loop_start; + }; break; - case jar_xm_PING_PONG_LOOP: if(ch->ping) { + if(mod->linear_interpolation) { + v_left = (b >= ch->sample->loop_end) ? ch->sample->data[(uint32_t)ch->sample_position] : ch->sample->data[b]; + if (ch->sample->stereo) { + v_right = (b >= ch->sample->loop_end) ? ch->sample->data[(uint32_t)ch->sample_position + ch->sample->length] : ch->sample->data[b + ch->sample->length]; + } else { + v_right = v_left; + }; + }; ch->sample_position += ch->step; - } else { - ch->sample_position -= ch->step; - } - /* XXX: this may not work for very tight ping-pong loops - * (ie switches direction more than once per sample */ - if(ch->ping) { - if(JAR_XM_LINEAR_INTERPOLATION) { - v = (b >= ch->sample->loop_end) ? ch->sample->data[a] : ch->sample->data[b]; - } if(ch->sample_position >= ch->sample->loop_end) { ch->ping = false; ch->sample_position = (ch->sample->loop_end << 1) - ch->sample_position; - } - /* sanity checking */ + }; if(ch->sample_position >= ch->sample->length) { ch->ping = false; ch->sample_position -= ch->sample->length - 1; - } + }; } else { - if(JAR_XM_LINEAR_INTERPOLATION) { - v = u; - u = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[a] : ch->sample->data[b - 2]; - } + if(mod->linear_interpolation) { + v_left = u_left; + v_right = u_right; + u_left = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[(uint32_t)ch->sample_position] : ch->sample->data[b - 2]; + if (ch->sample->stereo) { + u_right = (b == 1 || b - 2 <= ch->sample->loop_start) ? ch->sample->data[(uint32_t)ch->sample_position + ch->sample->length] : ch->sample->data[b + ch->sample->length - 2]; + } else { + u_right = u_left; + }; + }; + ch->sample_position -= ch->step; if(ch->sample_position <= ch->sample->loop_start) { ch->ping = true; ch->sample_position = (ch->sample->loop_start << 1) - ch->sample_position; - } - /* sanity checking */ - if(ch->sample_position <= .0f) { + }; + if (ch->sample_position <= .0f) { ch->ping = true; ch->sample_position = .0f; - } - } + }; + }; break; default: - v = .0f; + v_left = .0f; + v_right = .0f; break; - } + }; - float endval = JAR_XM_LINEAR_INTERPOLATION ? jar_xm_LERP(u, v, t) : u; + float endval_left = mod->linear_interpolation ? jar_xm_LERP(u_left, v_left, t) : u_left; + float endval_right = mod->linear_interpolation ? jar_xm_LERP(u_right, v_right, t) : u_right; -#if JAR_XM_RAMPING - if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { - /* Smoothly transition between old and new sample. */ - return jar_xm_LERP(ch->end_of_previous_sample[ch->frame_count], endval, - (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); - } -#endif + if (mod->ramping) { + if(ch->frame_count < jar_xm_SAMPLE_RAMPING_POINTS) { + /* Smoothly transition between old and new sample. */ + if (previous > -1) { + ch->end_of_previous_sample_left[previous] = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], endval_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->end_of_previous_sample_right[previous] = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], endval_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + } else { + ch->curr_left = jar_xm_LERP(ch->end_of_previous_sample_left[ch->frame_count], endval_left, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + ch->curr_right = jar_xm_LERP(ch->end_of_previous_sample_right[ch->frame_count], endval_right, (float)ch->frame_count / (float)jar_xm_SAMPLE_RAMPING_POINTS); + }; + }; + }; - return endval; -} + if (previous > -1) { + ch->end_of_previous_sample_left[previous] = endval_left; + ch->end_of_previous_sample_right[previous] = endval_right; + } else { + ch->curr_left = endval_left; + ch->curr_right = endval_right; + }; +}; -static void jar_xm_sample(jar_xm_context_t* ctx, float* left, float* right) { +// gather all channel audio into stereo float +static void jar_xm_mixdown(jar_xm_context_t* ctx, float* left, float* right) { + jar_xm_module_t* mod = &(ctx->module); + if(ctx->remaining_samples_in_tick <= 0) { jar_xm_tick(ctx); - } + }; ctx->remaining_samples_in_tick--; - *left = 0.f; *right = 0.f; - - if(ctx->max_loop_count > 0 && ctx->loop_count >= ctx->max_loop_count) { - return; - } + if(ctx->max_loop_count > 0 && ctx->loop_count > ctx->max_loop_count) { return; } for(uint8_t i = 0; i < ctx->module.num_channels; ++i) { jar_xm_channel_context_t* ch = ctx->channels + i; + if(ch->instrument != NULL && ch->sample != NULL && ch->sample_position >= 0) { + jar_xm_next_of_sample(ctx, ch, -1); + if(!ch->muted && !ch->instrument->muted) { + *left += ch->curr_left * ch->actual_volume * (1.f - ch->actual_panning); + *right += ch->curr_right * ch->actual_volume * ch->actual_panning; + }; - if(ch->instrument == NULL || ch->sample == NULL || ch->sample_position < 0) { - continue; - } + if (mod->ramping) { + ch->frame_count++; + jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp); + jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp); + }; + }; + }; + if (ctx->global_volume != 1.0f) { + *left *= ctx->global_volume; + *right *= ctx->global_volume; + }; - const float fval = jar_xm_next_of_sample(ch); + // experimental +// float counter = (float)ctx->generated_samples * 0.0001f +// *left = tan(&left + sin(counter)); +// *right = tan(&right + cos(counter)); - if(!ch->muted && !ch->instrument->muted) { - *left += fval * ch->actual_volume * (1.f - ch->actual_panning); - *right += fval * ch->actual_volume * ch->actual_panning; - } - -#if JAR_XM_RAMPING - ch->frame_count++; - jar_xm_SLIDE_TOWARDS(ch->actual_volume, ch->target_volume, ctx->volume_ramp); - jar_xm_SLIDE_TOWARDS(ch->actual_panning, ch->target_panning, ctx->panning_ramp); -#endif - } - - const float fgvol = ctx->global_volume * ctx->amplification; - *left *= fgvol; - *right *= fgvol; - -#if JAR_XM_DEBUG - if(fabs(*left) > 1 || fabs(*right) > 1) { - DEBUG("clipping frame: %f %f, this is a bad module or a libxm bug", *left, *right); - } -#endif -} + // apply brick wall limiter when audio goes beyond bounderies + if(*left < -1.0) {*left = -1.0;} else if(*left > 1.0) {*left = 1.0;}; + if(*right < -1.0) {*right = -1.0;} else if(*right > 1.0) {*right = 1.0;}; +}; void jar_xm_generate_samples(jar_xm_context_t* ctx, float* output, size_t numsamples) { if(ctx && output) { ctx->generated_samples += numsamples; for(size_t i = 0; i < numsamples; i++) { - jar_xm_sample(ctx, output + (2 * i), output + (2 * i + 1)); - } - } -} + jar_xm_mixdown(ctx, output + (2 * i), output + (2 * i + 1)); + }; + }; +}; -uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx) -{ +uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx) { uint64_t total = 0; uint8_t currentLoopCount = jar_xm_get_loop_count(ctx); jar_xm_set_max_loop_count(ctx, 0); - - while(jar_xm_get_loop_count(ctx) == currentLoopCount) - { + while(jar_xm_get_loop_count(ctx) == currentLoopCount) { total += ctx->remaining_samples_in_tick; ctx->remaining_samples_in_tick = 0; jar_xm_tick(ctx); } - ctx->loop_count = currentLoopCount; return total; } @@ -2578,7 +2170,6 @@ uint64_t jar_xm_get_remaining_samples(jar_xm_context_t* ctx) //-------------------------------------------- //FILE LOADER - TODO - NEEDS TO BE CLEANED UP //-------------------------------------------- - #undef DEBUG #define DEBUG(...) do { \ fprintf(stderr, __VA_ARGS__); \ @@ -2642,44 +2233,237 @@ int jar_xm_create_context_from_file(jar_xm_context_t** ctx, uint32_t rate, const switch(ret) { case 0: break; - - case 1: - DEBUG("could not create context: module is not sane\n"); + case 1: DEBUG("could not create context: module is not sane\n"); *ctx = NULL; return 1; break; - - case 2: - FATAL("could not create context: malloc failed\n"); + case 2: FATAL("could not create context: malloc failed\n"); return 2; break; - - default: - FATAL("could not create context: unknown error\n"); + default: FATAL("could not create context: unknown error\n"); return 6; break; - } - + return 0; } // not part of the original library -void jar_xm_reset(jar_xm_context_t* ctx) -{ - // I don't know what I am doing - // this is probably very broken - // but it kinda works - for (uint16_t i = 0; i < jar_xm_get_number_of_channels(ctx); i++) - { +void jar_xm_reset(jar_xm_context_t* ctx) { + for (uint16_t i = 0; i < jar_xm_get_number_of_channels(ctx); i++) { jar_xm_cut_note(&ctx->channels[i]); } ctx->current_row = 0; ctx->current_table_index = 0; ctx->current_tick = 0; + ctx->tempo =ctx->default_tempo; // reset to file default value + ctx->bpm = ctx->default_bpm; // reset to file default value + ctx->global_volume = ctx->default_global_volume; // reset to file default value } +void jar_xm_flip_linear_interpolation(jar_xm_context_t* ctx) { + if (ctx->module.linear_interpolation) { + ctx->module.linear_interpolation = 0; + } else { + ctx->module.linear_interpolation = 1; + } +} + +void jar_xm_table_jump(jar_xm_context_t* ctx, int table_ptr) { + for (uint16_t i = 0; i < jar_xm_get_number_of_channels(ctx); i++) { + jar_xm_cut_note(&ctx->channels[i]); + } + ctx->current_row = 0; + ctx->current_tick = 0; + if(table_ptr > 0 && table_ptr < ctx->module.length) { + ctx->current_table_index = table_ptr; + ctx->module.restart_position = table_ptr; // The reason to jump is to start a new loop or track + } else { + ctx->current_table_index = 0; + ctx->module.restart_position = 0; // The reason to jump is to start a new loop or track + ctx->tempo =ctx->default_tempo; // reset to file default value + ctx->bpm = ctx->default_bpm; // reset to file default value + ctx->global_volume = ctx->default_global_volume; // reset to file default value + }; +} + + +// TRANSLATE NOTE NUMBER INTO USER VALUE (ie. 1 = C-1, 2 = C#1, 3 = D-1 ... ) +const char* xm_note_chr(int number) { + if (number == NOTE_OFF) { + return "=="; + }; + number = number % 12; + switch(number) { + case 1: return "C-"; + case 2: return "C#"; + case 3: return "D-"; + case 4: return "D#"; + case 5: return "E-"; + case 6: return "F-"; + case 7: return "F#"; + case 8: return "G-"; + case 9: return "G#"; + case 10: return "A-"; + case 11: return "A#"; + case 12: return "B-"; + }; + return "??"; +}; + +const char* xm_octave_chr(int number) { + if (number == NOTE_OFF) { + return "="; + }; + + int number2 = number - number % 12; + int result = floor(number2 / 12) + 1; + switch(result) { + case 1: return "1"; + case 2: return "2"; + case 3: return "3"; + case 4: return "4"; + case 5: return "5"; + case 6: return "6"; + case 7: return "7"; + case 8: return "8"; + default: return "?"; /* UNKNOWN */ + }; + +}; + +// TRANSLATE NOTE EFFECT CODE INTO USER VALUE +const char* xm_effect_chr(int fx) { + switch(fx) { + case 0: return "0"; /* ZERO = NO EFFECT */ + case 1: return "1"; /* 1xx: Portamento up */ + case 2: return "2"; /* 2xx: Portamento down */ + case 3: return "3"; /* 3xx: Tone portamento */ + case 4: return "4"; /* 4xy: Vibrato */ + case 5: return "5"; /* 5xy: Tone portamento + Volume slide */ + case 6: return "6"; /* 6xy: Vibrato + Volume slide */ + case 7: return "7"; /* 7xy: Tremolo */ + case 8: return "8"; /* 8xx: Set panning */ + case 9: return "9"; /* 9xx: Sample offset */ + case 0xA: return "A";/* Axy: Volume slide */ + case 0xB: return "B";/* Bxx: Position jump */ + case 0xC: return "C";/* Cxx: Set volume */ + case 0xD: return "D";/* Dxx: Pattern break */ + case 0xE: return "E";/* EXy: Extended command */ + case 0xF: return "F";/* Fxx: Set tempo/BPM */ + case 16: return "G"; /* Gxx: Set global volume */ + case 17: return "H"; /* Hxy: Global volume slide */ + case 21: return "L"; /* Lxx: Set envelope position */ + case 25: return "P"; /* Pxy: Panning slide */ + case 27: return "R"; /* Rxy: Multi retrig note */ + case 29: return "T"; /* Txy: Tremor */ + case 33: return "X"; /* Xxy: Extra stuff */ + default: return "?"; /* UNKNOWN */ + }; +} + +#ifdef JAR_XM_RAYLIB + +#include "raylib.h" // Need RayLib API calls for the DEBUG display + +void jar_xm_debug(jar_xm_context_t *ctx) { + int size=40; + int x = 0, y = 0; + + // DEBUG VARIABLES + y += size; DrawText(TextFormat("CUR TBL = %i", ctx->current_table_index), x, y, size, WHITE); + y += size; DrawText(TextFormat("CUR PAT = %i", ctx->module.pattern_table[ctx->current_table_index]), x, y, size, WHITE); + y += size; DrawText(TextFormat("POS JMP = %d", ctx->position_jump), x, y, size, WHITE); + y += size; DrawText(TextFormat("JMP DST = %i", ctx->jump_dest), x, y, size, WHITE); + y += size; DrawText(TextFormat("PTN BRK = %d", ctx->pattern_break), x, y, size, WHITE); + y += size; DrawText(TextFormat("CUR ROW = %i", ctx->current_row), x, y, size, WHITE); + y += size; DrawText(TextFormat("JMP ROW = %i", ctx->jump_row), x, y, size, WHITE); + y += size; DrawText(TextFormat("ROW LCT = %i", ctx->row_loop_count), x, y, size, WHITE); + y += size; DrawText(TextFormat("LCT = %i", ctx->loop_count), x, y, size, WHITE); + y += size; DrawText(TextFormat("MAX LCT = %i", ctx->max_loop_count), x, y, size, WHITE); + x = size * 12; y = 0; + + y += size; DrawText(TextFormat("CUR TCK = %i", ctx->current_tick), x, y, size, WHITE); + y += size; DrawText(TextFormat("XTR TCK = %i", ctx->extra_ticks), x, y, size, WHITE); + y += size; DrawText(TextFormat("TCK/ROW = %i", ctx->tempo), x, y, size, ORANGE); + y += size; DrawText(TextFormat("SPL TCK = %f", ctx->remaining_samples_in_tick), x, y, size, WHITE); + y += size; DrawText(TextFormat("GEN SPL = %i", ctx->generated_samples), x, y, size, WHITE); + y += size * 7; + + x = 0; + size=16; + // TIMELINE OF MODULE + for (int i=0; i < ctx->module.length; i++) { + if (i == ctx->jump_dest) { + if (ctx->position_jump) { + DrawRectangle(i * size * 2, y - size, size * 2, size, GOLD); + } else { + DrawRectangle(i * size * 2, y - size, size * 2, size, BROWN); + }; + }; + if (i == ctx->current_table_index) { +// DrawText(TextFormat("%02X", ctx->current_tick), i * size * 2, y - size, size, WHITE); + DrawRectangle(i * size * 2, y, size * 2, size, RED); + DrawText(TextFormat("%02X", ctx->current_row), i * size * 2, y - size, size, YELLOW); + } else { + DrawRectangle(i * size * 2, y, size * 2, size, ORANGE); + }; + DrawText(TextFormat("%02X", ctx->module.pattern_table[i]), i * size * 2, y, size, WHITE); + }; + y += size; + + jar_xm_pattern_t* cur = ctx->module.patterns + ctx->module.pattern_table[ctx->current_table_index]; + + /* DISPLAY CURRENTLY PLAYING PATTERN */ + + x += 2 * size; + for(uint8_t i = 0; i < ctx->module.num_channels; i++) { + DrawRectangle(x, y, 8 * size, size, PURPLE); + DrawText("N", x, y, size, YELLOW); + DrawText("I", x + size * 2, y, size, YELLOW); + DrawText("V", x + size * 4, y, size, YELLOW); + DrawText("FX", x + size * 6, y, size, YELLOW); + x += 9 * size; + }; + x += size; + for (int j=(ctx->current_row - 14); j<(ctx->current_row + 15); j++) { + y += size; + x = 0; + if (j >=0 && j < (cur->num_rows)) { + DrawRectangle(x, y, size * 2, size, BROWN); + DrawText(TextFormat("%02X",j), x, y, size, WHITE); + x += 2 * size; + for(uint8_t i = 0; i < ctx->module.num_channels; i++) { + if (j==(ctx->current_row)) { + DrawRectangle(x, y, 8 * size, size, DARKGREEN); + } else { + DrawRectangle(x, y, 8 * size, size, DARKGRAY); + }; + jar_xm_pattern_slot_t *s = cur->slots + j * ctx->module.num_channels + i; + // jar_xm_channel_context_t *ch = ctx->channels + i; + if (s->note > 0) {DrawText(TextFormat("%s%s", xm_note_chr(s->note), xm_octave_chr(s->note) ), x, y, size, WHITE);} else {DrawText("...", x, y, size, GRAY);}; + if (s->instrument > 0) { + DrawText(TextFormat("%02X", s->instrument), x + size * 2, y, size, WHITE); + if (s->volume_column == 0) { + DrawText(TextFormat("%02X", 64), x + size * 4, y, size, YELLOW); + }; + } else { + DrawText("..", x + size * 2, y, size, GRAY); + if (s->volume_column == 0) { + DrawText("..", x + size * 4, y, size, GRAY); + }; + }; + if (s->volume_column > 0) {DrawText(TextFormat("%02X", (s->volume_column - 16)), x + size * 4, y, size, WHITE);}; + if (s->effect_type > 0 || s->effect_param > 0) {DrawText(TextFormat("%s%02X", xm_effect_chr(s->effect_type), s->effect_param), x + size * 6, y, size, WHITE);}; + x += 9 * size; + }; + }; + }; + +} +#endif // RayLib extension + #endif//end of JAR_XM_IMPLEMENTATION //------------------------------------------------------------------------------- diff --git a/raylib/external/miniaudio.h b/raylib/external/miniaudio.h index 1545c23..60522da 100644 --- a/raylib/external/miniaudio.h +++ b/raylib/external/miniaudio.h @@ -1,6 +1,6 @@ /* Audio playback and capture library. Choice of public domain or MIT-0. See license statements at the end of this file. -miniaudio - v0.10.18 - 2020-08-30 +miniaudio - v0.10.33 - 2021-04-04 David Reid - mackron@gmail.com @@ -114,8 +114,8 @@ event indicating that the device needs to stop and handle it in a different thre ``` You must never try uninitializing and reinitializing a device inside the callback. You must also never try to stop and start it from inside the callback. There -are a few other things you shouldn't do in the callback depending on your requirements, however this isn't so much a thread-safety thing, but rather a real- -time processing thing which is beyond the scope of this introduction. +are a few other things you shouldn't do in the callback depending on your requirements, however this isn't so much a thread-safety thing, but rather a +real-time processing thing which is beyond the scope of this introduction. The example above demonstrates the initialization of a playback device, but it works exactly the same for capture. All you need to do is change the device type from `ma_device_type_playback` to `ma_device_type_capture` when setting up the config, like so: @@ -230,8 +230,30 @@ The Windows build should compile cleanly on all popular compilers without the ne 2.2. macOS and iOS ------------------ The macOS build should compile cleanly without the need to download any dependencies nor link to any libraries or frameworks. The iOS build needs to be -compiled as Objective-C (sorry) and will need to link the relevant frameworks but should Just Work with Xcode. Compiling through the command line requires -linking to `-lpthread` and `-lm`. +compiled as Objective-C and will need to link the relevant frameworks but should compile cleanly out of the box with Xcode. Compiling through the command line +requires linking to `-lpthread` and `-lm`. + +Due to the way miniaudio links to frameworks at runtime, your application may not pass Apple's notarization process. To fix this there are two options. The +first is to use the `MA_NO_RUNTIME_LINKING` option, like so: + + ```c + #ifdef __APPLE__ + #define MA_NO_RUNTIME_LINKING + #endif + #define MINIAUDIO_IMPLEMENTATION + #include "miniaudio.h" + ``` + +This will require linking with `-framework CoreFoundation -framework CoreAudio -framework AudioUnit`. Alternatively, if you would rather keep using runtime +linking you can add the following to your entitlements.xcent file: + + ``` + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.allow-unsigned-executable-memory + + ``` + 2.3. Linux ---------- @@ -246,96 +268,159 @@ The BSD build only requires linking to `-lpthread` and `-lm`. NetBSD uses audio( AAudio is the highest priority backend on Android. This should work out of the box without needing any kind of compiler configuration. Support for AAudio starts with Android 8 which means older versions will fall back to OpenSL|ES which requires API level 16+. +There have been reports that the OpenSL|ES backend fails to initialize on some Android based devices due to `dlopen()` failing to open "libOpenSLES.so". If +this happens on your platform you'll need to disable run-time linking with `MA_NO_RUNTIME_LINKING` and link with -lOpenSLES. + 2.6. Emscripten --------------- -The Emscripten build emits Web Audio JavaScript directly and should Just Work without any configuration. You cannot use -std=c* compiler flags, nor -ansi. +The Emscripten build emits Web Audio JavaScript directly and should compile cleanly out of the box. You cannot use -std=c* compiler flags, nor -ansi. 2.7. Build Options ------------------ `#define` these options before including miniaudio.h. - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | Option | Description | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WASAPI | Disables the WASAPI backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_DSOUND | Disables the DirectSound backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WINMM | Disables the WinMM backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_ALSA | Disables the ALSA backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_PULSEAUDIO | Disables the PulseAudio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_JACK | Disables the JACK backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_COREAUDIO | Disables the Core Audio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_SNDIO | Disables the sndio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AUDIO4 | Disables the audio(4) backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_OSS | Disables the OSS backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AAUDIO | Disables the AAudio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_OPENSL | Disables the OpenSL|ES backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WEBAUDIO | Disables the Web Audio backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_NULL | Disables the null backend. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_DECODING | Disables decoding APIs. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_ENCODING | Disables encoding APIs. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_WAV | Disables the built-in WAV decoder and encoder. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_FLAC | Disables the built-in FLAC decoder. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_MP3 | Disables the built-in MP3 decoder. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and ma_device APIs. This is useful if you only want to use | - | | miniaudio's data conversion and/or decoding APIs. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. This option is useful if you only need to use miniaudio for | - | | data conversion, decoding and/or encoding. Some families of APIs require threading which means the following options must also | - | | be set: | - | | | - | | ``` | - | | MA_NO_DEVICE_IO | - | | ``` | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_SSE2 | Disables SSE2 optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AVX2 | Disables AVX2 optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_AVX512 | Disables AVX-512 optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_NO_NEON | Disables NEON optimizations. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_LOG_LEVEL [level] | Sets the logging level. Set level to one of the following: | - | | | - | | ``` | - | | MA_LOG_LEVEL_VERBOSE | - | | MA_LOG_LEVEL_INFO | - | | MA_LOG_LEVEL_WARNING | - | | MA_LOG_LEVEL_ERROR | - | | ``` | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_DEBUG_OUTPUT | Enable printf() debug output. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_API | Controls how public APIs should be decorated. Defaults to `extern`. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ - | MA_DLL | If set, configures MA_API to either import or export APIs depending on whether or not the implementation is being defined. If | - | | defining the implementation, MA_API will be configured to export. Otherwise it will be configured to import. This has no effect | - | | if MA_API is defined externally. | - +----------------------+----------------------------------------------------------------------------------------------------------------------------------+ + +----------------------------------+--------------------------------------------------------------------+ + | Option | Description | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_WASAPI | Disables the WASAPI backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_DSOUND | Disables the DirectSound backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_WINMM | Disables the WinMM backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_ALSA | Disables the ALSA backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_PULSEAUDIO | Disables the PulseAudio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_JACK | Disables the JACK backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_COREAUDIO | Disables the Core Audio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_SNDIO | Disables the sndio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_AUDIO4 | Disables the audio(4) backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_OSS | Disables the OSS backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_AAUDIO | Disables the AAudio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_OPENSL | Disables the OpenSL|ES backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_WEBAUDIO | Disables the Web Audio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_NULL | Disables the null backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_ONLY_SPECIFIC_BACKENDS | Disables all backends by default and requires `MA_ENABLE_*` to | + | | enable specific backends. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_WASAPI | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the WASAPI backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_DSOUND | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the DirectSound backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_WINMM | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the WinMM backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_ALSA | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the ALSA backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_PULSEAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the PulseAudio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_JACK | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the JACK backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_COREAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the Core Audio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_SNDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the sndio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_AUDIO4 | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the audio(4) backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_OSS | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the OSS backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_AAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the AAudio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_OPENSL | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the OpenSL|ES backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_WEBAUDIO | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the Web Audio backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_ENABLE_NULL | Used in conjunction with MA_ENABLE_ONLY_SPECIFIC_BACKENDS to | + | | enable the null backend. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_DECODING | Disables decoding APIs. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_ENCODING | Disables encoding APIs. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_WAV | Disables the built-in WAV decoder and encoder. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_FLAC | Disables the built-in FLAC decoder. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_MP3 | Disables the built-in MP3 decoder. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_DEVICE_IO | Disables playback and recording. This will disable ma_context and | + | | ma_device APIs. This is useful if you only want to use miniaudio's | + | | data conversion and/or decoding APIs. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_THREADING | Disables the ma_thread, ma_mutex, ma_semaphore and ma_event APIs. | + | | This option is useful if you only need to use miniaudio for data | + | | conversion, decoding and/or encoding. Some families of APIs | + | | require threading which means the following options must also be | + | | set: | + | | | + | | ``` | + | | MA_NO_DEVICE_IO | + | | ``` | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_GENERATION | Disables generation APIs such a ma_waveform and ma_noise. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_SSE2 | Disables SSE2 optimizations. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_AVX2 | Disables AVX2 optimizations. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_AVX512 | Disables AVX-512 optimizations. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_NEON | Disables NEON optimizations. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_NO_RUNTIME_LINKING | Disables runtime linking. This is useful for passing Apple's | + | | notarization process. When enabling this, you may need to avoid | + | | using `-std=c89` or `-std=c99` on Linux builds or else you may end | + | | up with compilation errors due to conflicts with `timespec` and | + | | `timeval` data types. | + | | | + | | You may need to enable this if your target platform does not allow | + | | runtime linking via `dlopen()`. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_LOG_LEVEL [level] | Sets the logging level. Set `level` to one of the following: | + | | | + | | ``` | + | | MA_LOG_LEVEL_VERBOSE | + | | MA_LOG_LEVEL_INFO | + | | MA_LOG_LEVEL_WARNING | + | | MA_LOG_LEVEL_ERROR | + | | ``` | + +----------------------------------+--------------------------------------------------------------------+ + | MA_DEBUG_OUTPUT | Enable `printf()` debug output. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_COINIT_VALUE | Windows only. The value to pass to internal calls to | + | | `CoInitializeEx()`. Defaults to `COINIT_MULTITHREADED`. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_API | Controls how public APIs should be decorated. Default is `extern`. | + +----------------------------------+--------------------------------------------------------------------+ + | MA_DLL | If set, configures MA_API to either import or export APIs | + | | depending on whether or not the implementation is being defined. | + | | If defining the implementation, MA_API will be configured to | + | | export. Otherwise it will be configured to import. This has no | + | | effect if MA_API is defined externally. | + +----------------------------------+--------------------------------------------------------------------+ 3. Definitions @@ -384,7 +469,8 @@ All formats are native-endian. 4. Decoding =========== -The `ma_decoder` API is used for reading audio files. The following formats are supported: +The `ma_decoder` API is used for reading audio files. Decoders are completely decoupled from devices and can be used independently. The following formats are +supported: +---------+------------------+----------+ | Format | Decoding Backend | Built-In | @@ -437,14 +523,14 @@ with `ma_decoder_init()`. Here is an example for loading a decoder from a file: ma_decoder_uninit(&decoder); ``` -When initializing a decoder, you can optionally pass in a pointer to a ma_decoder_config object (the NULL argument in the example above) which allows you to -configure the output format, channel count, sample rate and channel map: +When initializing a decoder, you can optionally pass in a pointer to a `ma_decoder_config` object (the `NULL` argument in the example above) which allows you +to configure the output format, channel count, sample rate and channel map: ```c ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 2, 48000); ``` -When passing in NULL for decoder config in `ma_decoder_init*()`, the output format will be the same as that defined by the decoding backend. +When passing in `NULL` for decoder config in `ma_decoder_init*()`, the output format will be the same as that defined by the decoding backend. Data is read from the decoder as PCM frames. This will return the number of PCM frames actually read. If the return value is less than the requested number of PCM frames it means you've reached the end: @@ -645,10 +731,10 @@ be one of the following: | ma_standard_channel_map_vorbis | Vorbis channel map. | | ma_standard_channel_map_sound4 | FreeBSD's sound(4). | | ma_standard_channel_map_sndio | sndio channel map. http://www.sndio.org/tips.html. | - | ma_standard_channel_map_webaudio | https://webaudio.github.io/web-audio-api/#ChannelOrdering | + | ma_standard_channel_map_webaudio | https://webaudio.github.io/web-audio-api/#ChannelOrdering | +-----------------------------------+-----------------------------------------------------------+ -Below are the channel maps used by default in miniaudio (ma_standard_channel_map_default): +Below are the channel maps used by default in miniaudio (`ma_standard_channel_map_default`): +---------------+---------------------------------+ | Channel Count | Mapping | @@ -815,7 +901,7 @@ The API for the linear resampler is the same as the main resampler API, only it' ------------------------- The Speex resampler is made up of third party code which is released under the BSD license. Because it is licensed differently to miniaudio, which is public domain, it is strictly opt-in and all of it's code is stored in separate files. If you opt-in to the Speex resampler you must consider the license text in it's -source files. To opt-in, you must first #include the following file before the implementation of miniaudio.h: +source files. To opt-in, you must first `#include` the following file before the implementation of miniaudio.h: ```c #include "extras/speex_resampler/ma_speex_resampler.h" @@ -898,8 +984,8 @@ The data converter supports multiple channels and is always interleaved (both in Sample rates can be anything other than zero, and are always specified in hertz. They should be set to something like 44100, etc. The sample rate is the only configuration property that can be changed after initialization, but only if the `resampling.allowDynamicSampleRate` member of `ma_data_converter_config` is -set to MA_TRUE. To change the sample rate, use `ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. The resampling -algorithm cannot be changed after initialization. +set to `MA_TRUE`. To change the sample rate, use `ma_data_converter_set_rate()` or `ma_data_converter_set_rate_ratio()`. The ratio must be in/out. The +resampling algorithm cannot be changed after initialization. Processing always happens on a per PCM frame basis and always assumes interleaved input and output. De-interleaved processing is not supported. To process frames, use `ma_data_converter_process_pcm_frames()`. On input, this function takes the number of output frames you can fit in the output buffer and the number @@ -990,7 +1076,7 @@ Filtering can be applied in-place by passing in the same pointer for both the in ma_lpf_process_pcm_frames(&lpf, pMyData, pMyData, frameCount); ``` -The maximum filter order is limited to MA_MAX_FILTER_ORDER which is set to 8. If you need more, you can chain first and second order filters together. +The maximum filter order is limited to `MA_MAX_FILTER_ORDER` which is set to 8. If you need more, you can chain first and second order filters together. ```c for (iFilter = 0; iFilter < filterCount; iFilter += 1) { @@ -1119,8 +1205,8 @@ miniaudio supports generation of sine, square, triangle and sawtooth waveforms. ma_waveform_read_pcm_frames(&waveform, pOutput, frameCount); ``` -The amplitude, frequency and sample rate can be changed dynamically with `ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()` and -`ma_waveform_set_sample_rate()` respectively. +The amplitude, frequency, type, and sample rate can be changed dynamically with `ma_waveform_set_amplitude()`, `ma_waveform_set_frequency()`, +`ma_waveform_set_type()`, and `ma_waveform_set_sample_rate()` respectively. You can invert the waveform by setting the amplitude to a negative value. You can use this to control whether or not a sawtooth has a positive or negative ramp, for example. @@ -1162,7 +1248,9 @@ miniaudio supports generation of white, pink and Brownian noise via the `ma_nois ``` The noise API uses simple LCG random number generation. It supports a custom seed which is useful for things like automated testing requiring reproducibility. -Setting the seed to zero will default to MA_DEFAULT_LCG_SEED. +Setting the seed to zero will default to `MA_DEFAULT_LCG_SEED`. + +The amplitude, seed, and type can be changed dynamically with `ma_noise_set_amplitude()`, `ma_noise_set_seed()`, and `ma_noise_set_type()` respectively. By default, the noise API will use different values for different channels. So, for example, the left side in a stereo stream will be different to the right side. To instead have each channel use the same random value, set the `duplicateChannels` member of the noise config to true, like so: @@ -1209,10 +1297,10 @@ Audio buffers are initialised using the standard configuration system used every ma_audio_buffer_uninit(&buffer); ``` -In the example above, the memory pointed to by `pExistingData` will _not_ be copied and is how an application can do self-managed memory allocation. If you +In the example above, the memory pointed to by `pExistingData` will *not* be copied and is how an application can do self-managed memory allocation. If you would rather make a copy of the data, use `ma_audio_buffer_init_copy()`. To uninitialize the buffer, use `ma_audio_buffer_uninit()`. -Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure _and_ the raw audio data in a contiguous block of memory. That is, +Sometimes it can be convenient to allocate the memory for the `ma_audio_buffer` structure and the raw audio data in a contiguous block of memory. That is, the raw audio data will be located immediately after the `ma_audio_buffer` structure. To do this, use `ma_audio_buffer_alloc_and_init()`: ```c @@ -1230,7 +1318,7 @@ the raw audio data will be located immediately after the `ma_audio_buffer` struc } ... - + ma_audio_buffer_uninit_and_free(&buffer); ``` @@ -1300,14 +1388,14 @@ routines. Passing in `NULL` for this results in `MA_MALLOC()` and `MA_FREE()` be Use `ma_pcm_rb_init_ex()` if you need a deinterleaved buffer. The data for each sub-buffer is offset from each other based on the stride. To manage your sub-buffers you can use `ma_pcm_rb_get_subbuffer_stride()`, `ma_pcm_rb_get_subbuffer_offset()` and `ma_pcm_rb_get_subbuffer_ptr()`. -Use 'ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section of the ring buffer. You specify the number of frames you +Use `ma_pcm_rb_acquire_read()` and `ma_pcm_rb_acquire_write()` to retrieve a pointer to a section of the ring buffer. You specify the number of frames you need, and on output it will set to what was actually acquired. If the read or write pointer is positioned such that the number of frames requested will require a loop, it will be clamped to the end of the buffer. Therefore, the number of frames you're given may be less than the number you requested. After calling `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()`, you do your work on the buffer and then "commit" it with `ma_pcm_rb_commit_read()` or `ma_pcm_rb_commit_write()`. This is where the read/write pointers are updated. When you commit you need to pass in the buffer that was returned by the earlier call to `ma_pcm_rb_acquire_read()` or `ma_pcm_rb_acquire_write()` and is only used for validation. The number of frames passed to `ma_pcm_rb_commit_read()` and -`ma_pcm_rb_commit_write()` is what's used to increment the pointers. +`ma_pcm_rb_commit_write()` is what's used to increment the pointers, and can be less that what was originally requested. If you want to correct for drift between the write pointer and the read pointer you can use a combination of `ma_pcm_rb_pointer_distance()`, `ma_pcm_rb_seek_read()` and `ma_pcm_rb_seek_write()`. Note that you can only move the pointers forward, and you should only move the read pointer forward via @@ -1344,6 +1432,7 @@ The following backends are supported by miniaudio. | AAudio | ma_backend_aaudio | Android 8+ | | OpenSL ES | ma_backend_opensl | Android (API level 16+) | | Web Audio | ma_backend_webaudio | Web (via Emscripten) | + | Custom | ma_backend_custom | Cross Platform | | Null | ma_backend_null | Cross Platform (not used on Web) | +-------------+-----------------------+--------------------------------------------------------+ @@ -1399,7 +1488,7 @@ Some backends have some nuance details you may want to be aware of. ======================= - Automatic stream routing is enabled on a per-backend basis. Support is explicitly enabled for WASAPI and Core Audio, however other backends such as PulseAudio may naturally support it, though not all have been tested. -- The contents of the output buffer passed into the data callback will always be pre-initialized to zero unless the `noPreZeroedOutputBuffer` config variable +- The contents of the output buffer passed into the data callback will always be pre-initialized to silence unless the `noPreZeroedOutputBuffer` config variable in `ma_device_config` is set to true, in which case it'll be undefined which will require you to write something to the entire buffer. - By default miniaudio will automatically clip samples. This only applies when the playback sample format is configured as `ma_format_f32`. If you are doing clipping yourself, you can disable this overhead by setting `noClip` to true in the device config. @@ -1421,7 +1510,7 @@ extern "C" { #define MA_VERSION_MAJOR 0 #define MA_VERSION_MINOR 10 -#define MA_VERSION_REVISION 18 +#define MA_VERSION_REVISION 33 #define MA_VERSION_STRING MA_XSTRINGIFY(MA_VERSION_MAJOR) "." MA_XSTRINGIFY(MA_VERSION_MINOR) "." MA_XSTRINGIFY(MA_VERSION_REVISION) #if defined(_MSC_VER) && !defined(__clang__) @@ -1429,7 +1518,7 @@ extern "C" { #pragma warning(disable:4201) /* nonstandard extension used: nameless struct/union */ #pragma warning(disable:4214) /* nonstandard extension used: bit field types other than int */ #pragma warning(disable:4324) /* structure was padded due to alignment specifier */ -#else +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ #if defined(__clang__) @@ -1482,7 +1571,7 @@ typedef unsigned int ma_uint32; typedef signed __int64 ma_int64; typedef unsigned __int64 ma_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -1491,7 +1580,7 @@ typedef unsigned int ma_uint32; #endif typedef signed long long ma_int64; typedef unsigned long long ma_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -1541,6 +1630,8 @@ typedef ma_uint16 wchar_t; #else #define MA_INLINE inline __attribute__((always_inline)) #endif +#elif defined(__WATCOMC__) + #define MA_INLINE __inline #else #define MA_INLINE #endif @@ -1612,6 +1703,12 @@ MA_LOG_LEVEL_ERROR #define MA_LOG_LEVEL MA_LOG_LEVEL_ERROR #endif +/* +An annotation for variables which must be used atomically. This doesn't actually do anything - it's +just used as a way for humans to identify variables that should be used atomically. +*/ +#define MA_ATOMIC + typedef struct ma_context ma_context; typedef struct ma_device ma_device; @@ -1737,6 +1834,7 @@ typedef int ma_result; #define MA_NO_DEVICE -104 #define MA_API_NOT_FOUND -105 #define MA_INVALID_DEVICE_CONFIG -106 +#define MA_LOOP -107 /* State errors. */ #define MA_DEVICE_NOT_INITIALIZED -200 @@ -1751,28 +1849,11 @@ typedef int ma_result; #define MA_FAILED_TO_STOP_BACKEND_DEVICE -303 -/* Standard sample rates. */ -#define MA_SAMPLE_RATE_8000 8000 -#define MA_SAMPLE_RATE_11025 11025 -#define MA_SAMPLE_RATE_16000 16000 -#define MA_SAMPLE_RATE_22050 22050 -#define MA_SAMPLE_RATE_24000 24000 -#define MA_SAMPLE_RATE_32000 32000 -#define MA_SAMPLE_RATE_44100 44100 -#define MA_SAMPLE_RATE_48000 48000 -#define MA_SAMPLE_RATE_88200 88200 -#define MA_SAMPLE_RATE_96000 96000 -#define MA_SAMPLE_RATE_176400 176400 -#define MA_SAMPLE_RATE_192000 192000 -#define MA_SAMPLE_RATE_352800 352800 -#define MA_SAMPLE_RATE_384000 384000 - #define MA_MIN_CHANNELS 1 #ifndef MA_MAX_CHANNELS #define MA_MAX_CHANNELS 32 #endif -#define MA_MIN_SAMPLE_RATE MA_SAMPLE_RATE_8000 -#define MA_MAX_SAMPLE_RATE MA_SAMPLE_RATE_384000 + #ifndef MA_MAX_FILTER_ORDER #define MA_MAX_FILTER_ORDER 8 @@ -1811,6 +1892,38 @@ typedef enum ma_format_count } ma_format; +typedef enum +{ + /* Standard rates need to be in priority order. */ + ma_standard_sample_rate_48000 = 48000, /* Most common */ + ma_standard_sample_rate_44100 = 44100, + + ma_standard_sample_rate_32000 = 32000, /* Lows */ + ma_standard_sample_rate_24000 = 24000, + ma_standard_sample_rate_22050 = 22050, + + ma_standard_sample_rate_88200 = 88200, /* Highs */ + ma_standard_sample_rate_96000 = 96000, + ma_standard_sample_rate_176400 = 176400, + ma_standard_sample_rate_192000 = 192000, + + ma_standard_sample_rate_16000 = 16000, /* Extreme lows */ + ma_standard_sample_rate_11025 = 11250, + ma_standard_sample_rate_8000 = 8000, + + ma_standard_sample_rate_352800 = 352800, /* Extreme highs */ + ma_standard_sample_rate_384000 = 384000, + + ma_standard_sample_rate_min = ma_standard_sample_rate_8000, + ma_standard_sample_rate_max = ma_standard_sample_rate_384000, + ma_standard_sample_rate_count = 14 /* Need to maintain the count manually. Make sure this is updated if items are added to enum. */ +} ma_standard_sample_rate; + +/* These are deprecated. Use ma_standard_sample_rate_min and ma_standard_sample_rate_max. */ +#define MA_MIN_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_min +#define MA_MAX_SAMPLE_RATE (ma_uint32)ma_standard_sample_rate_max + + typedef enum { ma_channel_mix_mode_rectangular = 0, /* Simple averaging based on the plane(s) the channel is sitting on. */ @@ -1855,7 +1968,7 @@ typedef struct #ifndef MA_NO_THREADING -/* Thread priorties should be ordered such that the default priority of the worker thread is 0. */ +/* Thread priorities should be ordered such that the default priority of the worker thread is 0. */ typedef enum { ma_thread_priority_idle = -5, @@ -1868,7 +1981,8 @@ typedef enum ma_thread_priority_default = 0 } ma_thread_priority; -typedef unsigned char ma_spinlock; +/* Spinlocks are 32-bit for compatibility reasons. */ +typedef ma_uint32 ma_spinlock; #if defined(MA_WIN32) typedef ma_handle ma_thread; @@ -1967,7 +2081,7 @@ typedef struct MA_API ma_result ma_biquad_init(const ma_biquad_config* pConfig, ma_biquad* pBQ); MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pBQ); MA_API ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_biquad_get_latency(ma_biquad* pBQ); +MA_API ma_uint32 ma_biquad_get_latency(const ma_biquad* pBQ); /************************************************************************************************************************************************************** @@ -1998,7 +2112,7 @@ typedef struct MA_API ma_result ma_lpf1_init(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF); MA_API ma_result ma_lpf1_process_pcm_frames(ma_lpf1* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_lpf1_get_latency(ma_lpf1* pLPF); +MA_API ma_uint32 ma_lpf1_get_latency(const ma_lpf1* pLPF); typedef struct { @@ -2008,7 +2122,7 @@ typedef struct MA_API ma_result ma_lpf2_init(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); MA_API ma_result ma_lpf2_reinit(const ma_lpf2_config* pConfig, ma_lpf2* pLPF); MA_API ma_result ma_lpf2_process_pcm_frames(ma_lpf2* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_lpf2_get_latency(ma_lpf2* pLPF); +MA_API ma_uint32 ma_lpf2_get_latency(const ma_lpf2* pLPF); typedef struct @@ -2026,6 +2140,7 @@ typedef struct { ma_format format; ma_uint32 channels; + ma_uint32 sampleRate; ma_uint32 lpf1Count; ma_uint32 lpf2Count; ma_lpf1 lpf1[1]; @@ -2035,7 +2150,7 @@ typedef struct MA_API ma_result ma_lpf_init(const ma_lpf_config* pConfig, ma_lpf* pLPF); MA_API ma_result ma_lpf_reinit(const ma_lpf_config* pConfig, ma_lpf* pLPF); MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_lpf_get_latency(ma_lpf* pLPF); +MA_API ma_uint32 ma_lpf_get_latency(const ma_lpf* pLPF); /************************************************************************************************************************************************************** @@ -2066,7 +2181,7 @@ typedef struct MA_API ma_result ma_hpf1_init(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF); MA_API ma_result ma_hpf1_process_pcm_frames(ma_hpf1* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_hpf1_get_latency(ma_hpf1* pHPF); +MA_API ma_uint32 ma_hpf1_get_latency(const ma_hpf1* pHPF); typedef struct { @@ -2076,7 +2191,7 @@ typedef struct MA_API ma_result ma_hpf2_init(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); MA_API ma_result ma_hpf2_reinit(const ma_hpf2_config* pConfig, ma_hpf2* pHPF); MA_API ma_result ma_hpf2_process_pcm_frames(ma_hpf2* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_hpf2_get_latency(ma_hpf2* pHPF); +MA_API ma_uint32 ma_hpf2_get_latency(const ma_hpf2* pHPF); typedef struct @@ -2094,6 +2209,7 @@ typedef struct { ma_format format; ma_uint32 channels; + ma_uint32 sampleRate; ma_uint32 hpf1Count; ma_uint32 hpf2Count; ma_hpf1 hpf1[1]; @@ -2103,7 +2219,7 @@ typedef struct MA_API ma_result ma_hpf_init(const ma_hpf_config* pConfig, ma_hpf* pHPF); MA_API ma_result ma_hpf_reinit(const ma_hpf_config* pConfig, ma_hpf* pHPF); MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_hpf_get_latency(ma_hpf* pHPF); +MA_API ma_uint32 ma_hpf_get_latency(const ma_hpf* pHPF); /************************************************************************************************************************************************************** @@ -2130,7 +2246,7 @@ typedef struct MA_API ma_result ma_bpf2_init(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); MA_API ma_result ma_bpf2_reinit(const ma_bpf2_config* pConfig, ma_bpf2* pBPF); MA_API ma_result ma_bpf2_process_pcm_frames(ma_bpf2* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_bpf2_get_latency(ma_bpf2* pBPF); +MA_API ma_uint32 ma_bpf2_get_latency(const ma_bpf2* pBPF); typedef struct @@ -2155,7 +2271,7 @@ typedef struct MA_API ma_result ma_bpf_init(const ma_bpf_config* pConfig, ma_bpf* pBPF); MA_API ma_result ma_bpf_reinit(const ma_bpf_config* pConfig, ma_bpf* pBPF); MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_bpf_get_latency(ma_bpf* pBPF); +MA_API ma_uint32 ma_bpf_get_latency(const ma_bpf* pBPF); /************************************************************************************************************************************************************** @@ -2182,7 +2298,7 @@ typedef struct MA_API ma_result ma_notch2_init(const ma_notch2_config* pConfig, ma_notch2* pFilter); MA_API ma_result ma_notch2_reinit(const ma_notch2_config* pConfig, ma_notch2* pFilter); MA_API ma_result ma_notch2_process_pcm_frames(ma_notch2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_notch2_get_latency(ma_notch2* pFilter); +MA_API ma_uint32 ma_notch2_get_latency(const ma_notch2* pFilter); /************************************************************************************************************************************************************** @@ -2210,7 +2326,7 @@ typedef struct MA_API ma_result ma_peak2_init(const ma_peak2_config* pConfig, ma_peak2* pFilter); MA_API ma_result ma_peak2_reinit(const ma_peak2_config* pConfig, ma_peak2* pFilter); MA_API ma_result ma_peak2_process_pcm_frames(ma_peak2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_peak2_get_latency(ma_peak2* pFilter); +MA_API ma_uint32 ma_peak2_get_latency(const ma_peak2* pFilter); /************************************************************************************************************************************************************** @@ -2238,7 +2354,7 @@ typedef struct MA_API ma_result ma_loshelf2_init(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); MA_API ma_result ma_loshelf2_reinit(const ma_loshelf2_config* pConfig, ma_loshelf2* pFilter); MA_API ma_result ma_loshelf2_process_pcm_frames(ma_loshelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_loshelf2_get_latency(ma_loshelf2* pFilter); +MA_API ma_uint32 ma_loshelf2_get_latency(const ma_loshelf2* pFilter); /************************************************************************************************************************************************************** @@ -2266,7 +2382,7 @@ typedef struct MA_API ma_result ma_hishelf2_init(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); MA_API ma_result ma_hishelf2_reinit(const ma_hishelf2_config* pConfig, ma_hishelf2* pFilter); MA_API ma_result ma_hishelf2_process_pcm_frames(ma_hishelf2* pFilter, void* pFramesOut, const void* pFramesIn, ma_uint64 frameCount); -MA_API ma_uint32 ma_hishelf2_get_latency(ma_hishelf2* pFilter); +MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter); @@ -2323,10 +2439,10 @@ MA_API void ma_linear_resampler_uninit(ma_linear_resampler* pResampler); MA_API ma_result ma_linear_resampler_process_pcm_frames(ma_linear_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_linear_resampler_set_rate(ma_linear_resampler* pResampler, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResampler, float ratioInOut); -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(ma_linear_resampler* pResampler, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(ma_linear_resampler* pResampler, ma_uint64 inputFrameCount); -MA_API ma_uint64 ma_linear_resampler_get_input_latency(ma_linear_resampler* pResampler); -MA_API ma_uint64 ma_linear_resampler_get_output_latency(ma_linear_resampler* pResampler); +MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount); +MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount); +MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler); +MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler); typedef enum { @@ -2422,24 +2538,24 @@ number of output frames. The returned value does not include cached input frames. It only returns the number of extra frames that would need to be read from the input buffer in order to output the specified number of output frames. */ -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler, ma_uint64 outputFrameCount); +MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount); /* Calculates the number of whole output frames that would be output after fully reading and consuming the specified number of input frames. */ -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler, ma_uint64 inputFrameCount); +MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount); /* Retrieves the latency introduced by the resampler in input frames. */ -MA_API ma_uint64 ma_resampler_get_input_latency(ma_resampler* pResampler); +MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler); /* Retrieves the latency introduced by the resampler in output frames. */ -MA_API ma_uint64 ma_resampler_get_output_latency(ma_resampler* pResampler); +MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler); @@ -2474,11 +2590,11 @@ typedef struct float f32[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; ma_int32 s16[MA_MAX_CHANNELS][MA_MAX_CHANNELS]; } weights; - ma_bool32 isPassthrough : 1; - ma_bool32 isSimpleShuffle : 1; - ma_bool32 isSimpleMonoExpansion : 1; - ma_bool32 isStereoToMono : 1; - ma_uint8 shuffleTable[MA_MAX_CHANNELS]; + ma_bool8 isPassthrough; + ma_bool8 isSimpleShuffle; + ma_bool8 isSimpleMonoExpansion; + ma_bool8 isStereoToMono; + ma_uint8 shuffleTable[MA_MAX_CHANNELS]; } ma_channel_converter; MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pConfig, ma_channel_converter* pConverter); @@ -2528,11 +2644,11 @@ typedef struct ma_data_converter_config config; ma_channel_converter channelConverter; ma_resampler resampler; - ma_bool32 hasPreFormatConversion : 1; - ma_bool32 hasPostFormatConversion : 1; - ma_bool32 hasChannelConverter : 1; - ma_bool32 hasResampler : 1; - ma_bool32 isPassthrough : 1; + ma_bool8 hasPreFormatConversion; + ma_bool8 hasPostFormatConversion; + ma_bool8 hasChannelConverter; + ma_bool8 hasResampler; + ma_bool8 isPassthrough; } ma_data_converter; MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_data_converter* pConverter); @@ -2540,10 +2656,10 @@ MA_API void ma_data_converter_uninit(ma_data_converter* pConverter); MA_API ma_result ma_data_converter_process_pcm_frames(ma_data_converter* pConverter, const void* pFramesIn, ma_uint64* pFrameCountIn, void* pFramesOut, ma_uint64* pFrameCountOut); MA_API ma_result ma_data_converter_set_rate(ma_data_converter* pConverter, ma_uint32 sampleRateIn, ma_uint32 sampleRateOut); MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, float ratioInOut); -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(ma_data_converter* pConverter, ma_uint64 outputFrameCount); -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(ma_data_converter* pConverter, ma_uint64 inputFrameCount); -MA_API ma_uint64 ma_data_converter_get_input_latency(ma_data_converter* pConverter); -MA_API ma_uint64 ma_data_converter_get_output_latency(ma_data_converter* pConverter); +MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount); +MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount); +MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter); +MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* pConverter); /************************************************************************************************************************************************************ @@ -2584,12 +2700,20 @@ Interleaves a group of deinterleaved buffers. */ MA_API void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_uint64 frameCount, const void** ppDeinterleavedPCMFrames, void* pInterleavedPCMFrames); + /************************************************************************************************************************************************************ Channel Maps ************************************************************************************************************************************************************/ +/* +Initializes a blank channel map. + +When a blank channel map is specified anywhere it indicates that the native channel map should be used. +*/ +MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap); + /* Helper for retrieving a standard channel map. @@ -2680,10 +2804,10 @@ typedef struct ma_uint32 subbufferSizeInBytes; ma_uint32 subbufferCount; ma_uint32 subbufferStrideInBytes; - volatile ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. */ - volatile ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. */ - ma_bool32 ownsBuffer : 1; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ - ma_bool32 clearOnWriteAcquire : 1; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ + MA_ATOMIC ma_uint32 encodedReadOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + MA_ATOMIC ma_uint32 encodedWriteOffset; /* Most significant bit is the loop flag. Lower 31 bits contains the actual offset in bytes. Must be used atomically. */ + ma_bool8 ownsBuffer; /* Used to know whether or not miniaudio is responsible for free()-ing the buffer. */ + ma_bool8 clearOnWriteAcquire; /* When set, clears the acquired write buffer before returning from ma_rb_acquire_write(). */ ma_allocation_callbacks allocationCallbacks; } ma_rb; @@ -2732,6 +2856,25 @@ MA_API ma_uint32 ma_pcm_rb_get_subbuffer_offset(ma_pcm_rb* pRB, ma_uint32 subbuf MA_API void* ma_pcm_rb_get_subbuffer_ptr(ma_pcm_rb* pRB, ma_uint32 subbufferIndex, void* pBuffer); +/* +The idea of the duplex ring buffer is to act as the intermediary buffer when running two asynchronous devices in a duplex set up. The +capture device writes to it, and then a playback device reads from it. + +At the moment this is just a simple naive implementation, but in the future I want to implement some dynamic resampling to seamlessly +handle desyncs. Note that the API is work in progress and may change at any time in any version. + +The size of the buffer is based on the capture side since that's what'll be written to the buffer. It is based on the capture period size +in frames. The internal sample rate of the capture device is also needed in order to calculate the size. +*/ +typedef struct +{ + ma_pcm_rb rb; +} ma_duplex_rb; + +MA_API ma_result ma_duplex_rb_init(ma_format captureFormat, ma_uint32 captureChannels, ma_uint32 sampleRate, ma_uint32 captureInternalSampleRate, ma_uint32 captureInternalPeriodSizeInFrames, const ma_allocation_callbacks* pAllocationCallbacks, ma_duplex_rb* pRB); +MA_API ma_result ma_duplex_rb_uninit(ma_duplex_rb* pRB); + + /************************************************************************************************************************************************************ Miscellaneous Helpers @@ -2846,54 +2989,66 @@ This section contains the APIs for device playback and capture. Here is where yo #define MA_SUPPORT_WEBAUDIO #endif +/* All platforms should support custom backends. */ +#define MA_SUPPORT_CUSTOM + /* Explicitly disable the Null backend for Emscripten because it uses a background thread which is not properly supported right now. */ #if !defined(MA_EMSCRIPTEN) #define MA_SUPPORT_NULL #endif -#if !defined(MA_NO_WASAPI) && defined(MA_SUPPORT_WASAPI) - #define MA_ENABLE_WASAPI +#if defined(MA_SUPPORT_WASAPI) && !defined(MA_NO_WASAPI) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WASAPI)) + #define MA_HAS_WASAPI #endif -#if !defined(MA_NO_DSOUND) && defined(MA_SUPPORT_DSOUND) - #define MA_ENABLE_DSOUND +#if defined(MA_SUPPORT_DSOUND) && !defined(MA_NO_DSOUND) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_DSOUND)) + #define MA_HAS_DSOUND #endif -#if !defined(MA_NO_WINMM) && defined(MA_SUPPORT_WINMM) - #define MA_ENABLE_WINMM +#if defined(MA_SUPPORT_WINMM) && !defined(MA_NO_WINMM) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WINMM)) + #define MA_HAS_WINMM #endif -#if !defined(MA_NO_ALSA) && defined(MA_SUPPORT_ALSA) - #define MA_ENABLE_ALSA +#if defined(MA_SUPPORT_ALSA) && !defined(MA_NO_ALSA) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_ALSA)) + #define MA_HAS_ALSA #endif -#if !defined(MA_NO_PULSEAUDIO) && defined(MA_SUPPORT_PULSEAUDIO) - #define MA_ENABLE_PULSEAUDIO +#if defined(MA_SUPPORT_PULSEAUDIO) && !defined(MA_NO_PULSEAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_PULSEAUDIO)) + #define MA_HAS_PULSEAUDIO #endif -#if !defined(MA_NO_JACK) && defined(MA_SUPPORT_JACK) - #define MA_ENABLE_JACK +#if defined(MA_SUPPORT_JACK) && !defined(MA_NO_JACK) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_JACK)) + #define MA_HAS_JACK #endif -#if !defined(MA_NO_COREAUDIO) && defined(MA_SUPPORT_COREAUDIO) - #define MA_ENABLE_COREAUDIO +#if defined(MA_SUPPORT_COREAUDIO) && !defined(MA_NO_COREAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_COREAUDIO)) + #define MA_HAS_COREAUDIO #endif -#if !defined(MA_NO_SNDIO) && defined(MA_SUPPORT_SNDIO) - #define MA_ENABLE_SNDIO +#if defined(MA_SUPPORT_SNDIO) && !defined(MA_NO_SNDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SNDIO)) + #define MA_HAS_SNDIO #endif -#if !defined(MA_NO_AUDIO4) && defined(MA_SUPPORT_AUDIO4) - #define MA_ENABLE_AUDIO4 +#if defined(MA_SUPPORT_AUDIO4) && !defined(MA_NO_AUDIO4) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AUDIO4)) + #define MA_HAS_AUDIO4 #endif -#if !defined(MA_NO_OSS) && defined(MA_SUPPORT_OSS) - #define MA_ENABLE_OSS +#if defined(MA_SUPPORT_OSS) && !defined(MA_NO_OSS) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_OSS)) + #define MA_HAS_OSS #endif -#if !defined(MA_NO_AAUDIO) && defined(MA_SUPPORT_AAUDIO) - #define MA_ENABLE_AAUDIO +#if defined(MA_SUPPORT_AAUDIO) && !defined(MA_NO_AAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_AAUDIO)) + #define MA_HAS_AAUDIO #endif -#if !defined(MA_NO_OPENSL) && defined(MA_SUPPORT_OPENSL) - #define MA_ENABLE_OPENSL +#if defined(MA_SUPPORT_OPENSL) && !defined(MA_NO_OPENSL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_OPENSL)) + #define MA_HAS_OPENSL #endif -#if !defined(MA_NO_WEBAUDIO) && defined(MA_SUPPORT_WEBAUDIO) - #define MA_ENABLE_WEBAUDIO +#if defined(MA_SUPPORT_WEBAUDIO) && !defined(MA_NO_WEBAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WEBAUDIO)) + #define MA_HAS_WEBAUDIO #endif -#if !defined(MA_NO_NULL) && defined(MA_SUPPORT_NULL) - #define MA_ENABLE_NULL +#if defined(MA_SUPPORT_CUSTOM) && !defined(MA_NO_CUSTOM) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_CUSTOM)) + #define MA_HAS_CUSTOM #endif +#if defined(MA_SUPPORT_NULL) && !defined(MA_NO_NULL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NULL)) + #define MA_HAS_NULL +#endif + +#define MA_STATE_UNINITIALIZED 0 +#define MA_STATE_STOPPED 1 /* The device's default state after initialization. */ +#define MA_STATE_STARTED 2 /* The device is started and is requesting and/or delivering audio data. */ +#define MA_STATE_STARTING 3 /* Transitioning from a stopped state to started. */ +#define MA_STATE_STOPPING 4 /* Transitioning from a started state to stopped. */ #ifdef MA_SUPPORT_WASAPI /* We need a IMMNotificationClient object for WASAPI. */ @@ -2921,9 +3076,12 @@ typedef enum ma_backend_aaudio, ma_backend_opensl, ma_backend_webaudio, - ma_backend_null /* <-- Must always be the last item. Lowest priority, and used as the terminator for backend enumeration. */ + ma_backend_custom, /* <-- Custom backend, with callbacks defined by the context config. */ + ma_backend_null /* <-- Must always be the last item. Lowest priority, and used as the terminator for backend enumeration. */ } ma_backend; +#define MA_BACKEND_COUNT (ma_backend_null+1) + /* The callback for processing audio data from the device. @@ -3001,14 +3159,14 @@ pDevice (in) logLevel (in) The log level. This can be one of the following: - |----------------------| + +----------------------+ | Log Level | - |----------------------| + +----------------------+ | MA_LOG_LEVEL_VERBOSE | | MA_LOG_LEVEL_INFO | | MA_LOG_LEVEL_WARNING | | MA_LOG_LEVEL_ERROR | - |----------------------| + +----------------------+ message (in) The log message. @@ -3059,6 +3217,74 @@ typedef enum ma_ios_session_category_option_allow_air_play = 0x40, /* AVAudioSessionCategoryOptionAllowAirPlay */ } ma_ios_session_category_option; +/* OpenSL stream types. */ +typedef enum +{ + ma_opensl_stream_type_default = 0, /* Leaves the stream type unset. */ + ma_opensl_stream_type_voice, /* SL_ANDROID_STREAM_VOICE */ + ma_opensl_stream_type_system, /* SL_ANDROID_STREAM_SYSTEM */ + ma_opensl_stream_type_ring, /* SL_ANDROID_STREAM_RING */ + ma_opensl_stream_type_media, /* SL_ANDROID_STREAM_MEDIA */ + ma_opensl_stream_type_alarm, /* SL_ANDROID_STREAM_ALARM */ + ma_opensl_stream_type_notification /* SL_ANDROID_STREAM_NOTIFICATION */ +} ma_opensl_stream_type; + +/* OpenSL recording presets. */ +typedef enum +{ + ma_opensl_recording_preset_default = 0, /* Leaves the input preset unset. */ + ma_opensl_recording_preset_generic, /* SL_ANDROID_RECORDING_PRESET_GENERIC */ + ma_opensl_recording_preset_camcorder, /* SL_ANDROID_RECORDING_PRESET_CAMCORDER */ + ma_opensl_recording_preset_voice_recognition, /* SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION */ + ma_opensl_recording_preset_voice_communication, /* SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION */ + ma_opensl_recording_preset_voice_unprocessed /* SL_ANDROID_RECORDING_PRESET_UNPROCESSED */ +} ma_opensl_recording_preset; + +/* AAudio usage types. */ +typedef enum +{ + ma_aaudio_usage_default = 0, /* Leaves the usage type unset. */ + ma_aaudio_usage_announcement, /* AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT */ + ma_aaudio_usage_emergency, /* AAUDIO_SYSTEM_USAGE_EMERGENCY */ + ma_aaudio_usage_safety, /* AAUDIO_SYSTEM_USAGE_SAFETY */ + ma_aaudio_usage_vehicle_status, /* AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS */ + ma_aaudio_usage_alarm, /* AAUDIO_USAGE_ALARM */ + ma_aaudio_usage_assistance_accessibility, /* AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY */ + ma_aaudio_usage_assistance_navigation_guidance, /* AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE */ + ma_aaudio_usage_assistance_sonification, /* AAUDIO_USAGE_ASSISTANCE_SONIFICATION */ + ma_aaudio_usage_assitant, /* AAUDIO_USAGE_ASSISTANT */ + ma_aaudio_usage_game, /* AAUDIO_USAGE_GAME */ + ma_aaudio_usage_media, /* AAUDIO_USAGE_MEDIA */ + ma_aaudio_usage_notification, /* AAUDIO_USAGE_NOTIFICATION */ + ma_aaudio_usage_notification_event, /* AAUDIO_USAGE_NOTIFICATION_EVENT */ + ma_aaudio_usage_notification_ringtone, /* AAUDIO_USAGE_NOTIFICATION_RINGTONE */ + ma_aaudio_usage_voice_communication, /* AAUDIO_USAGE_VOICE_COMMUNICATION */ + ma_aaudio_usage_voice_communication_signalling /* AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING */ +} ma_aaudio_usage; + +/* AAudio content types. */ +typedef enum +{ + ma_aaudio_content_type_default = 0, /* Leaves the content type unset. */ + ma_aaudio_content_type_movie, /* AAUDIO_CONTENT_TYPE_MOVIE */ + ma_aaudio_content_type_music, /* AAUDIO_CONTENT_TYPE_MUSIC */ + ma_aaudio_content_type_sonification, /* AAUDIO_CONTENT_TYPE_SONIFICATION */ + ma_aaudio_content_type_speech /* AAUDIO_CONTENT_TYPE_SPEECH */ +} ma_aaudio_content_type; + +/* AAudio input presets. */ +typedef enum +{ + ma_aaudio_input_preset_default = 0, /* Leaves the input preset unset. */ + ma_aaudio_input_preset_generic, /* AAUDIO_INPUT_PRESET_GENERIC */ + ma_aaudio_input_preset_camcorder, /* AAUDIO_INPUT_PRESET_CAMCORDER */ + ma_aaudio_input_preset_unprocessed, /* AAUDIO_INPUT_PRESET_UNPROCESSED */ + ma_aaudio_input_preset_voice_recognition, /* AAUDIO_INPUT_PRESET_VOICE_RECOGNITION */ + ma_aaudio_input_preset_voice_communication, /* AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION */ + ma_aaudio_input_preset_voice_performance /* AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE */ +} ma_aaudio_input_preset; + + typedef union { ma_int64 counter; @@ -3080,21 +3306,35 @@ typedef union ma_int32 aaudio; /* AAudio uses a 32-bit integer for identification. */ ma_uint32 opensl; /* OpenSL|ES uses a 32-bit unsigned integer for identification. */ char webaudio[32]; /* Web Audio always uses default devices for now, but if this changes it'll be a GUID. */ + union + { + int i; + char s[256]; + void* p; + } custom; /* The custom backend could be anything. Give them a few options. */ int nullbackend; /* The null backend uses an integer for device IDs. */ } ma_device_id; + +typedef struct ma_context_config ma_context_config; +typedef struct ma_device_config ma_device_config; +typedef struct ma_backend_callbacks ma_backend_callbacks; + +#define MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE (1U << 1) /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ + typedef struct { /* Basic info. This is the only information guaranteed to be filled in during device enumeration. */ ma_device_id id; char name[256]; + ma_bool32 isDefault; /* Detailed info. As much of this is filled as possible with ma_context_get_device_info(). Note that you are allowed to initialize a device with settings outside of this range, but it just means the data will be converted using miniaudio's data conversion pipeline before sending the data to/from the device. Most programs will need to not worry about these values, but it's provided here mainly for informational purposes or in the rare case that someone might find it useful. - + These will be set to 0 when returned by ma_context_enumerate_devices() or ma_context_get_devices(). */ ma_uint32 formatCount; @@ -3104,13 +3344,19 @@ typedef struct ma_uint32 minSampleRate; ma_uint32 maxSampleRate; + + /* Experimental. Don't use these right now. */ + ma_uint32 nativeDataFormatCount; struct { - ma_bool32 isDefault; - } _private; + ma_format format; /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ + ma_uint32 channels; /* If set to 0, all channels are supported. */ + ma_uint32 sampleRate; /* If set to 0, all sample rates are supported. */ + ma_uint32 flags; /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ + } nativeDataFormats[/*ma_format_count * ma_standard_sample_rate_count * MA_MAX_CHANNELS*/ 64]; /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ } ma_device_info; -typedef struct +struct ma_device_config { ma_device_type deviceType; ma_uint32 sampleRate; @@ -3118,8 +3364,8 @@ typedef struct ma_uint32 periodSizeInMilliseconds; ma_uint32 periods; ma_performance_profile performanceProfile; - ma_bool32 noPreZeroedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ - ma_bool32 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ + ma_bool8 noPreZeroedOutputBuffer; /* When set to true, the contents of the output buffer passed into the data callback will be left undefined rather than initialized to zero. */ + ma_bool8 noClip; /* When set to true, the contents of the output buffer passed into the data callback will be clipped after returning. Only applies when the playback sample format is f32. */ ma_device_callback_proc dataCallback; ma_stop_proc stopCallback; void* pUserData; @@ -3141,6 +3387,7 @@ typedef struct ma_format format; ma_uint32 channels; ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } playback; struct @@ -3149,15 +3396,16 @@ typedef struct ma_format format; ma_uint32 channels; ma_channel channelMap[MA_MAX_CHANNELS]; + ma_channel_mix_mode channelMixMode; ma_share_mode shareMode; } capture; struct { - ma_bool32 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ - ma_bool32 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ - ma_bool32 noAutoStreamRouting; /* Disables automatic stream routing. */ - ma_bool32 noHardwareOffloading; /* Disables WASAPI's hardware offloading feature. */ + ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ + ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ + ma_bool8 noAutoStreamRouting; /* Disables automatic stream routing. */ + ma_bool8 noHardwareOffloading; /* Disables WASAPI's hardware offloading feature. */ } wasapi; struct { @@ -3171,9 +3419,145 @@ typedef struct const char* pStreamNamePlayback; const char* pStreamNameCapture; } pulse; -} ma_device_config; + struct + { + ma_bool32 allowNominalSampleRateChange; /* Desktop only. When enabled, allows changing of the sample rate at the operating system level. */ + } coreaudio; + struct + { + ma_opensl_stream_type streamType; + ma_opensl_recording_preset recordingPreset; + } opensl; + struct + { + ma_aaudio_usage usage; + ma_aaudio_content_type contentType; + ma_aaudio_input_preset inputPreset; + } aaudio; +}; + +/* +The callback for handling device enumeration. This is fired from `ma_context_enumerated_devices()`. + + +Parameters +---------- +pContext (in) + A pointer to the context performing the enumeration. + +deviceType (in) + The type of the device being enumerated. This will always be either `ma_device_type_playback` or `ma_device_type_capture`. + +pInfo (in) + A pointer to a `ma_device_info` containing the ID and name of the enumerated device. Note that this will not include detailed information about the device, + only basic information (ID and name). The reason for this is that it would otherwise require opening the backend device to probe for the information which + is too inefficient. + +pUserData (in) + The user data pointer passed into `ma_context_enumerate_devices()`. +*/ +typedef ma_bool32 (* ma_enum_devices_callback_proc)(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData); + + +/* +Describes some basic details about a playback or capture device. +*/ typedef struct +{ + const ma_device_id* pDeviceID; + ma_share_mode shareMode; + ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_channel channelMap[MA_MAX_CHANNELS]; + ma_uint32 periodSizeInFrames; + ma_uint32 periodSizeInMilliseconds; + ma_uint32 periodCount; +} ma_device_descriptor; + +/* +These are the callbacks required to be implemented for a backend. These callbacks are grouped into two parts: context and device. There is one context +to many devices. A device is created from a context. + +The general flow goes like this: + + 1) A context is created with `onContextInit()` + 1a) Available devices can be enumerated with `onContextEnumerateDevices()` if required. + 1b) Detailed information about a device can be queried with `onContextGetDeviceInfo()` if required. + 2) A device is created from the context that was created in the first step using `onDeviceInit()`, and optionally a device ID that was + selected from device enumeration via `onContextEnumerateDevices()`. + 3) A device is started or stopped with `onDeviceStart()` / `onDeviceStop()` + 4) Data is delivered to and from the device by the backend. This is always done based on the native format returned by the prior call + to `onDeviceInit()`. Conversion between the device's native format and the format requested by the application will be handled by + miniaudio internally. + +Initialization of the context is quite simple. You need to do any necessary initialization of internal objects and then output the +callbacks defined in this structure. + +Once the context has been initialized you can initialize a device. Before doing so, however, the application may want to know which +physical devices are available. This is where `onContextEnumerateDevices()` comes in. This is fairly simple. For each device, fire the +given callback with, at a minimum, the basic information filled out in `ma_device_info`. When the callback returns `MA_FALSE`, enumeration +needs to stop and the `onContextEnumerateDevices()` function return with a success code. + +Detailed device information can be retrieved from a device ID using `onContextGetDeviceInfo()`. This takes as input the device type and ID, +and on output returns detailed information about the device in `ma_device_info`. The `onContextGetDeviceInfo()` callback must handle the +case when the device ID is NULL, in which case information about the default device needs to be retrieved. + +Once the context has been created and the device ID retrieved (if using anything other than the default device), the device can be created. +This is a little bit more complicated than initialization of the context due to it's more complicated configuration. When initializing a +device, a duplex device may be requested. This means a separate data format needs to be specified for both playback and capture. On input, +the data format is set to what the application wants. On output it's set to the native format which should match as closely as possible to +the requested format. The conversion between the format requested by the application and the device's native format will be handled +internally by miniaudio. + +On input, if the sample format is set to `ma_format_unknown`, the backend is free to use whatever sample format it desires, so long as it's +supported by miniaudio. When the channel count is set to 0, the backend should use the device's native channel count. The same applies for +sample rate. For the channel map, the default should be used when `ma_channel_map_blank()` returns true (all channels set to +`MA_CHANNEL_NONE`). On input, the `periodSizeInFrames` or `periodSizeInMilliseconds` option should always be set. The backend should +inspect both of these variables. If `periodSizeInFrames` is set, it should take priority, otherwise it needs to be derived from the period +size in milliseconds (`periodSizeInMilliseconds`) and the sample rate, keeping in mind that the sample rate may be 0, in which case the +sample rate will need to be determined before calculating the period size in frames. On output, all members of the `ma_device_data_format` +object should be set to a valid value, except for `periodSizeInMilliseconds` which is optional (`periodSizeInFrames` *must* be set). + +Starting and stopping of the device is done with `onDeviceStart()` and `onDeviceStop()` and should be self-explanatory. If the backend uses +asynchronous reading and writing, `onDeviceStart()` and `onDeviceStop()` should always be implemented. + +The handling of data delivery between the application and the device is the most complicated part of the process. To make this a bit +easier, some helper callbacks are available. If the backend uses a blocking read/write style of API, the `onDeviceRead()` and +`onDeviceWrite()` callbacks can optionally be implemented. These are blocking and work just like reading and writing from a file. If the +backend uses a callback for data delivery, that callback must call `ma_device_handle_backend_data_callback()` from within it's callback. +This allows miniaudio to then process any necessary data conversion and then pass it to the miniaudio data callback. + +If the backend requires absolute flexibility with it's data delivery, it can optionally implement the `onDeviceWorkerThread()` callback +which will allow it to implement the logic that will run on the audio thread. This is much more advanced and is completely optional. + +The audio thread should run data delivery logic in a loop while `ma_device_get_state() == MA_STATE_STARTED` and no errors have been +encounted. Do not start or stop the device here. That will be handled from outside the `onDeviceDataLoop()` callback. + +The invocation of the `onDeviceDataLoop()` callback will be handled by miniaudio. When you start the device, miniaudio will fire this +callback. When the device is stopped, the `ma_device_get_state() == MA_STATE_STARTED` condition will fail and the loop will be terminated +which will then fall through to the part that stops the device. For an example on how to implement the `onDeviceDataLoop()` callback, +look at `ma_device_audio_thread__default_read_write()`. Implement the `onDeviceDataLoopWakeup()` callback if you need a mechanism to +wake up the audio thread. +*/ +struct ma_backend_callbacks +{ + ma_result (* onContextInit)(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks); + ma_result (* onContextUninit)(ma_context* pContext); + ma_result (* onContextEnumerateDevices)(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData); + ma_result (* onContextGetDeviceInfo)(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo); + ma_result (* onDeviceInit)(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture); + ma_result (* onDeviceUninit)(ma_device* pDevice); + ma_result (* onDeviceStart)(ma_device* pDevice); + ma_result (* onDeviceStop)(ma_device* pDevice); + ma_result (* onDeviceRead)(ma_device* pDevice, void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesRead); + ma_result (* onDeviceWrite)(ma_device* pDevice, const void* pFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten); + ma_result (* onDeviceDataLoop)(ma_device* pDevice); + ma_result (* onDeviceDataLoopWakeup)(ma_device* pDevice); +}; + +struct ma_context_config { ma_log_proc logCallback; ma_thread_priority threadPriority; @@ -3202,62 +3586,62 @@ typedef struct const char* pClientName; ma_bool32 tryStartServer; } jack; -} ma_context_config; + ma_backend_callbacks custom; +}; -/* -The callback for handling device enumeration. This is fired from `ma_context_enumerated_devices()`. - - -Parameters ----------- -pContext (in) - A pointer to the context performing the enumeration. - -deviceType (in) - The type of the device being enumerated. This will always be either `ma_device_type_playback` or `ma_device_type_capture`. - -pInfo (in) - A pointer to a `ma_device_info` containing the ID and name of the enumerated device. Note that this will not include detailed information about the device, - only basic information (ID and name). The reason for this is that it would otherwise require opening the backend device to probe for the information which - is too inefficient. - -pUserData (in) - The user data pointer passed into `ma_context_enumerate_devices()`. -*/ -typedef ma_bool32 (* ma_enum_devices_callback_proc)(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pInfo, void* pUserData); +/* WASAPI specific structure for some commands which must run on a common thread due to bugs in WASAPI. */ +typedef struct +{ + int code; + ma_event* pEvent; /* This will be signalled when the event is complete. */ + union + { + struct + { + int _unused; + } quit; + struct + { + ma_device_type deviceType; + void* pAudioClient; + void** ppAudioClientService; + ma_result result; /* The result from creating the audio client service. */ + } createAudioClient; + struct + { + ma_device* pDevice; + ma_device_type deviceType; + } releaseAudioClient; + } data; +} ma_context_command__wasapi; struct ma_context { - ma_backend backend; /* DirectSound, ALSA, etc. */ + ma_backend_callbacks callbacks; + ma_backend backend; /* DirectSound, ALSA, etc. */ ma_log_proc logCallback; ma_thread_priority threadPriority; size_t threadStackSize; void* pUserData; ma_allocation_callbacks allocationCallbacks; - ma_mutex deviceEnumLock; /* Used to make ma_context_get_devices() thread safe. */ - ma_mutex deviceInfoLock; /* Used to make ma_context_get_device_info() thread safe. */ - ma_uint32 deviceInfoCapacity; /* Total capacity of pDeviceInfos. */ + ma_mutex deviceEnumLock; /* Used to make ma_context_get_devices() thread safe. */ + ma_mutex deviceInfoLock; /* Used to make ma_context_get_device_info() thread safe. */ + ma_uint32 deviceInfoCapacity; /* Total capacity of pDeviceInfos. */ ma_uint32 playbackDeviceInfoCount; ma_uint32 captureDeviceInfoCount; - ma_device_info* pDeviceInfos; /* Playback devices first, then capture. */ - ma_bool32 isBackendAsynchronous : 1; /* Set when the context is initialized. Set to 1 for asynchronous backends such as Core Audio and JACK. Do not modify. */ - - ma_result (* onUninit )(ma_context* pContext); - ma_bool32 (* onDeviceIDEqual )(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1); - ma_result (* onEnumDevices )(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData); /* Return false from the callback to stop enumeration. */ - ma_result (* onGetDeviceInfo )(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo); - ma_result (* onDeviceInit )(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice); - void (* onDeviceUninit )(ma_device* pDevice); - ma_result (* onDeviceStart )(ma_device* pDevice); - ma_result (* onDeviceStop )(ma_device* pDevice); - ma_result (* onDeviceMainLoop)(ma_device* pDevice); + ma_device_info* pDeviceInfos; /* Playback devices first, then capture. */ union { #ifdef MA_SUPPORT_WASAPI struct { - int _unused; + ma_thread commandThread; + ma_mutex commandLock; + ma_semaphore commandSem; + ma_uint32 commandIndex; + ma_uint32 commandCount; + ma_context_command__wasapi commands[4]; } wasapi; #endif #ifdef MA_SUPPORT_DSOUND @@ -3304,8 +3688,11 @@ struct ma_context ma_proc snd_pcm_hw_params_set_format; ma_proc snd_pcm_hw_params_set_format_first; ma_proc snd_pcm_hw_params_get_format_mask; + ma_proc snd_pcm_hw_params_set_channels; ma_proc snd_pcm_hw_params_set_channels_near; + ma_proc snd_pcm_hw_params_set_channels_minmax; ma_proc snd_pcm_hw_params_set_rate_resample; + ma_proc snd_pcm_hw_params_set_rate; ma_proc snd_pcm_hw_params_set_rate_near; ma_proc snd_pcm_hw_params_set_buffer_size_near; ma_proc snd_pcm_hw_params_set_periods_near; @@ -3320,6 +3707,9 @@ struct ma_context ma_proc snd_pcm_hw_params_get_buffer_size; ma_proc snd_pcm_hw_params_get_periods; ma_proc snd_pcm_hw_params_get_access; + ma_proc snd_pcm_hw_params_test_format; + ma_proc snd_pcm_hw_params_test_channels; + ma_proc snd_pcm_hw_params_test_rate; ma_proc snd_pcm_hw_params; ma_proc snd_pcm_sw_params_sizeof; ma_proc snd_pcm_sw_params_current; @@ -3363,9 +3753,23 @@ struct ma_context ma_handle pulseSO; ma_proc pa_mainloop_new; ma_proc pa_mainloop_free; + ma_proc pa_mainloop_quit; ma_proc pa_mainloop_get_api; ma_proc pa_mainloop_iterate; ma_proc pa_mainloop_wakeup; + ma_proc pa_threaded_mainloop_new; + ma_proc pa_threaded_mainloop_free; + ma_proc pa_threaded_mainloop_start; + ma_proc pa_threaded_mainloop_stop; + ma_proc pa_threaded_mainloop_lock; + ma_proc pa_threaded_mainloop_unlock; + ma_proc pa_threaded_mainloop_wait; + ma_proc pa_threaded_mainloop_signal; + ma_proc pa_threaded_mainloop_accept; + ma_proc pa_threaded_mainloop_get_retval; + ma_proc pa_threaded_mainloop_get_api; + ma_proc pa_threaded_mainloop_in_thread; + ma_proc pa_threaded_mainloop_set_name; ma_proc pa_context_new; ma_proc pa_context_unref; ma_proc pa_context_connect; @@ -3406,9 +3810,8 @@ struct ma_context ma_proc pa_stream_writable_size; ma_proc pa_stream_readable_size; - char* pApplicationName; - char* pServerName; - ma_bool32 tryAutoSpawn; + /*pa_mainloop**/ ma_ptr pMainLoop; + /*pa_context**/ ma_ptr pPulseContext; } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -3442,14 +3845,14 @@ struct ma_context ma_handle hCoreFoundation; ma_proc CFStringGetCString; ma_proc CFRelease; - + ma_handle hCoreAudio; ma_proc AudioObjectGetPropertyData; ma_proc AudioObjectGetPropertyDataSize; ma_proc AudioObjectSetPropertyData; ma_proc AudioObjectAddPropertyListener; ma_proc AudioObjectRemovePropertyListener; - + ma_handle hAudioUnit; /* Could possibly be set to AudioToolbox on later versions of macOS. */ ma_proc AudioComponentFindNext; ma_proc AudioComponentInstanceDispose; @@ -3462,9 +3865,8 @@ struct ma_context ma_proc AudioUnitSetProperty; ma_proc AudioUnitInitialize; ma_proc AudioUnitRender; - + /*AudioComponent*/ ma_ptr component; - ma_bool32 noAudioSessionDeactivate; /* For tracking whether or not the iOS audio session should be explicitly deactivated. Set from the config in ma_context_init__coreaudio(). */ } coreaudio; #endif @@ -3521,6 +3923,9 @@ struct ma_context ma_proc AAudioStreamBuilder_setDataCallback; ma_proc AAudioStreamBuilder_setErrorCallback; ma_proc AAudioStreamBuilder_setPerformanceMode; + ma_proc AAudioStreamBuilder_setUsage; + ma_proc AAudioStreamBuilder_setContentType; + ma_proc AAudioStreamBuilder_setInputPreset; ma_proc AAudioStreamBuilder_openStream; ma_proc AAudioStream_close; ma_proc AAudioStream_getState; @@ -3545,6 +3950,7 @@ struct ma_context ma_handle SL_IID_RECORD; ma_handle SL_IID_PLAY; ma_handle SL_IID_OUTPUTMIX; + ma_handle SL_IID_ANDROIDCONFIGURATION; ma_proc slCreateEngine; } opensl; #endif @@ -3615,23 +4021,21 @@ struct ma_device ma_context* pContext; ma_device_type type; ma_uint32 sampleRate; - volatile ma_uint32 state; /* The state of the device is variable and can change at any time on any thread, so tell the compiler as such with `volatile`. */ + MA_ATOMIC ma_uint32 state; /* The state of the device is variable and can change at any time on any thread. Must be used atomically. */ ma_device_callback_proc onData; /* Set once at initialization time and should not be changed after. */ ma_stop_proc onStop; /* Set once at initialization time and should not be changed after. */ void* pUserData; /* Application defined data. */ - ma_mutex lock; + ma_mutex startStopLock; ma_event wakeupEvent; ma_event startEvent; ma_event stopEvent; ma_thread thread; ma_result workResult; /* This is set by the worker thread after it's finished doing a job. */ - ma_bool32 usingDefaultSampleRate : 1; - ma_bool32 usingDefaultBufferSize : 1; - ma_bool32 usingDefaultPeriods : 1; - ma_bool32 isOwnerOfContext : 1; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ - ma_bool32 noPreZeroedOutputBuffer : 1; - ma_bool32 noClip : 1; - volatile float masterVolumeFactor; /* Volatile so we can use some thread safety when applying volume to periods. */ + ma_bool8 isOwnerOfContext; /* When set to true, uninitializing the device will also uninitialize the context. Set to true when NULL is passed into ma_device_init(). */ + ma_bool8 noPreZeroedOutputBuffer; + ma_bool8 noClip; + MA_ATOMIC float masterVolumeFactor; /* Linear 0..1. Can be read and written simultaneously by different threads. Must be used atomically. */ + ma_duplex_rb duplexRB; /* Intermediary buffer for duplex device on asynchronous backends. */ struct { ma_resample_algorithm algorithm; @@ -3649,9 +4053,6 @@ struct ma_device ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ - ma_bool32 usingDefaultFormat : 1; - ma_bool32 usingDefaultChannels : 1; - ma_bool32 usingDefaultChannelMap : 1; ma_format format; ma_uint32 channels; ma_channel channelMap[MA_MAX_CHANNELS]; @@ -3661,6 +4062,7 @@ struct ma_device ma_channel internalChannelMap[MA_MAX_CHANNELS]; ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; + ma_channel_mix_mode channelMixMode; ma_data_converter converter; } playback; struct @@ -3668,9 +4070,6 @@ struct ma_device ma_device_id id; /* If using an explicit device, will be set to a copy of the ID used for initialization. Otherwise cleared to 0. */ char name[256]; /* Maybe temporary. Likely to be replaced with a query API. */ ma_share_mode shareMode; /* Set to whatever was passed in when the device was initialized. */ - ma_bool32 usingDefaultFormat : 1; - ma_bool32 usingDefaultChannels : 1; - ma_bool32 usingDefaultChannelMap : 1; ma_format format; ma_uint32 channels; ma_channel channelMap[MA_MAX_CHANNELS]; @@ -3680,6 +4079,7 @@ struct ma_device ma_channel internalChannelMap[MA_MAX_CHANNELS]; ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; + ma_channel_mix_mode channelMixMode; ma_data_converter converter; } capture; @@ -3692,26 +4092,27 @@ struct ma_device /*IAudioClient**/ ma_ptr pAudioClientCapture; /*IAudioRenderClient**/ ma_ptr pRenderClient; /*IAudioCaptureClient**/ ma_ptr pCaptureClient; - /*IMMDeviceEnumerator**/ ma_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */ + /*IMMDeviceEnumerator**/ ma_ptr pDeviceEnumerator; /* Used for IMMNotificationClient notifications. Required for detecting default device changes. */ ma_IMMNotificationClient notificationClient; - /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ - /*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */ - ma_uint32 actualPeriodSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ + /*HANDLE*/ ma_handle hEventPlayback; /* Auto reset. Initialized to signaled. */ + /*HANDLE*/ ma_handle hEventCapture; /* Auto reset. Initialized to unsignaled. */ + ma_uint32 actualPeriodSizeInFramesPlayback; /* Value from GetBufferSize(). internalPeriodSizeInFrames is not set to the _actual_ buffer size when low-latency shared mode is being used due to the way the IAudioClient3 API works. */ ma_uint32 actualPeriodSizeInFramesCapture; ma_uint32 originalPeriodSizeInFrames; ma_uint32 originalPeriodSizeInMilliseconds; ma_uint32 originalPeriods; - ma_bool32 hasDefaultPlaybackDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ - ma_bool32 hasDefaultCaptureDeviceChanged; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ + ma_performance_profile originalPerformanceProfile; ma_uint32 periodSizeInFramesPlayback; ma_uint32 periodSizeInFramesCapture; - ma_bool32 isStartedCapture; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ - ma_bool32 isStartedPlayback; /* <-- Make sure this is always a whole 32-bits because we use atomic assignments. */ - ma_bool32 noAutoConvertSRC : 1; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ - ma_bool32 noDefaultQualitySRC : 1; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ - ma_bool32 noHardwareOffloading : 1; - ma_bool32 allowCaptureAutoStreamRouting : 1; - ma_bool32 allowPlaybackAutoStreamRouting : 1; + MA_ATOMIC ma_bool32 isStartedCapture; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + MA_ATOMIC ma_bool32 isStartedPlayback; /* Can be read and written simultaneously across different threads. Must be used atomically, and must be 32-bit. */ + ma_bool8 noAutoConvertSRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM. */ + ma_bool8 noDefaultQualitySRC; /* When set to true, disables the use of AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY. */ + ma_bool8 noHardwareOffloading; + ma_bool8 allowCaptureAutoStreamRouting; + ma_bool8 allowPlaybackAutoStreamRouting; + ma_bool8 isDetachedPlayback; + ma_bool8 isDetachedCapture; } wasapi; #endif #ifdef MA_SUPPORT_DSOUND @@ -3748,26 +4149,15 @@ struct ma_device { /*snd_pcm_t**/ ma_ptr pPCMPlayback; /*snd_pcm_t**/ ma_ptr pPCMCapture; - ma_bool32 isUsingMMapPlayback : 1; - ma_bool32 isUsingMMapCapture : 1; + ma_bool8 isUsingMMapPlayback; + ma_bool8 isUsingMMapCapture; } alsa; #endif #ifdef MA_SUPPORT_PULSEAUDIO struct { - /*pa_mainloop**/ ma_ptr pMainLoop; - /*pa_mainloop_api**/ ma_ptr pAPI; - /*pa_context**/ ma_ptr pPulseContext; /*pa_stream**/ ma_ptr pStreamPlayback; /*pa_stream**/ ma_ptr pStreamCapture; - /*pa_context_state*/ ma_uint32 pulseContextState; - void* pMappedBufferPlayback; - const void* pMappedBufferCapture; - ma_uint32 mappedBufferFramesRemainingPlayback; - ma_uint32 mappedBufferFramesRemainingCapture; - ma_uint32 mappedBufferFramesCapacityPlayback; - ma_uint32 mappedBufferFramesCapacityCapture; - ma_bool32 breakFromMainLoop : 1; } pulse; #endif #ifdef MA_SUPPORT_JACK @@ -3778,7 +4168,6 @@ struct ma_device /*jack_port_t**/ ma_ptr pPortsCapture[MA_MAX_CHANNELS]; float* pIntermediaryBufferPlayback; /* Typed as a float because JACK is always floating point. */ float* pIntermediaryBufferCapture; - ma_pcm_rb duplexRB; } jack; #endif #ifdef MA_SUPPORT_COREAUDIO @@ -3788,16 +4177,17 @@ struct ma_device ma_uint32 deviceObjectIDCapture; /*AudioUnit*/ ma_ptr audioUnitPlayback; /*AudioUnit*/ ma_ptr audioUnitCapture; - /*AudioBufferList**/ ma_ptr pAudioBufferList; /* Only used for input devices. */ + /*AudioBufferList**/ ma_ptr pAudioBufferList; /* Only used for input devices. */ + ma_uint32 audioBufferCapInFrames; /* Only used for input devices. The capacity in frames of each buffer in pAudioBufferList. */ ma_event stopEvent; ma_uint32 originalPeriodSizeInFrames; ma_uint32 originalPeriodSizeInMilliseconds; ma_uint32 originalPeriods; + ma_performance_profile originalPerformanceProfile; ma_bool32 isDefaultPlaybackDevice; ma_bool32 isDefaultCaptureDevice; ma_bool32 isSwitchingPlaybackDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ ma_bool32 isSwitchingCaptureDevice; /* <-- Set to true when the default device has changed and miniaudio is in the process of switching. */ - ma_pcm_rb duplexRB; void* pRouteChangeHandler; /* Only used on mobile platforms. Obj-C object for handling route changes. */ } coreaudio; #endif @@ -3829,7 +4219,6 @@ struct ma_device { /*AAudioStream**/ ma_ptr pStreamPlayback; /*AAudioStream**/ ma_ptr pStreamCapture; - ma_pcm_rb duplexRB; } aaudio; #endif #ifdef MA_SUPPORT_OPENSL @@ -3849,7 +4238,6 @@ struct ma_device ma_uint32 currentBufferIndexCapture; ma_uint8* pBufferPlayback; /* This is malloc()'d and is used for storing audio data. Typed as ma_uint8 for easy offsetting. */ ma_uint8* pBufferCapture; - ma_pcm_rb duplexRB; } opensl; #endif #ifdef MA_SUPPORT_WEBAUDIO @@ -3857,7 +4245,6 @@ struct ma_device { int indexPlayback; /* We use a factory on the JavaScript side to manage devices and use an index for JS/C interop. */ int indexCapture; - ma_pcm_rb duplexRB; /* In external capture format. */ } webaudio; #endif #ifdef MA_SUPPORT_NULL @@ -3866,6 +4253,7 @@ struct ma_device ma_thread deviceThread; ma_event operationEvent; ma_event operationCompletionEvent; + ma_semaphore operationSemaphore; ma_uint32 operation; ma_result operationResult; ma_timer timer; @@ -3873,15 +4261,15 @@ struct ma_device ma_uint32 currentPeriodFramesRemainingPlayback; ma_uint32 currentPeriodFramesRemainingCapture; ma_uint64 lastProcessedFramePlayback; - ma_uint32 lastProcessedFrameCapture; - ma_bool32 isStarted; + ma_uint64 lastProcessedFrameCapture; + MA_ATOMIC ma_bool32 isStarted; /* Read and written by multiple threads. Must be used atomically, and must be 32-bit for compiler compatibility. */ } null_device; #endif }; }; #if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) -#else +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) #pragma GCC diagnostic pop /* For ISO C99 doesn't support unnamed structs/unions [-Wpedantic] */ #endif @@ -3967,7 +4355,7 @@ The context can be configured via the `pConfig` argument. The config object is i can then be set directly on the structure. Below are the members of the `ma_context_config` object. logCallback - Callback for handling log messages from miniaudio. + Callback for handling log messages from miniaudio. threadPriority The desired priority to use for the audio thread. Allowable values include the following: @@ -4541,7 +4929,7 @@ then be set directly on the structure. Below are the members of the `ma_device_c ma_share_mode_shared and reinitializing. wasapi.noAutoConvertSRC - WASAPI only. When set to true, disables WASAPI's automatic resampling and forces the use of miniaudio's resampler. Defaults to false. + WASAPI only. When set to true, disables WASAPI's automatic resampling and forces the use of miniaudio's resampler. Defaults to false. wasapi.noDefaultQualitySRC WASAPI only. Only used when `wasapi.noAutoConvertSRC` is set to false. When set to true, disables the use of `AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY`. @@ -4571,6 +4959,13 @@ then be set directly on the structure. Below are the members of the `ma_device_c pulse.pStreamNameCapture PulseAudio only. Sets the stream name for capture. + coreaudio.allowNominalSampleRateChange + Core Audio only. Desktop only. When enabled, allows the sample rate of the device to be changed at the operating system level. This + is disabled by default in order to prevent intrusive changes to the user's system. This is useful if you want to use a sample rate + that is known to be natively supported by the hardware thereby avoiding the cost of resampling. When set to true, miniaudio will + find the closest match between the sample rate requested in the device config and the sample rates natively supported by the + hardware. When set to false, the sample rate currently set by the operating system will always be used. + Once initialized, the device's config is immutable. If you need to change the config you will need to initialize a new device. @@ -4742,7 +5137,7 @@ pDevice (in) Return Value ------------ -MA_SUCCESS if successful; any other error code otherwise. +Nothing Thread Safety @@ -4884,7 +5279,63 @@ See Also ma_device_start() ma_device_stop() */ -MA_API ma_bool32 ma_device_is_started(ma_device* pDevice); +MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice); + + +/* +Retrieves the state of the device. + + +Parameters +---------- +pDevice (in) + A pointer to the device whose state is being retrieved. + + +Return Value +------------ +The current state of the device. The return value will be one of the following: + + +------------------------+------------------------------------------------------------------------------+ + | MA_STATE_UNINITIALIZED | Will only be returned if the device is in the middle of initialization. | + +------------------------+------------------------------------------------------------------------------+ + | MA_STATE_STOPPED | The device is stopped. The initial state of the device after initialization. | + +------------------------+------------------------------------------------------------------------------+ + | MA_STATE_STARTED | The device started and requesting and/or delivering audio data. | + +------------------------+------------------------------------------------------------------------------+ + | MA_STATE_STARTING | The device is in the process of starting. | + +------------------------+------------------------------------------------------------------------------+ + | MA_STATE_STOPPING | The device is in the process of stopping. | + +------------------------+------------------------------------------------------------------------------+ + + +Thread Safety +------------- +Safe. This is implemented as a simple accessor. Note that if the device is started or stopped at the same time as this function is called, +there's a possibility the return value could be out of sync. See remarks. + + +Callback Safety +--------------- +Safe. This is implemented as a simple accessor. + + +Remarks +------- +The general flow of a devices state goes like this: + + ``` + ma_device_init() -> MA_STATE_UNINITIALIZED -> MA_STATE_STOPPED + ma_device_start() -> MA_STATE_STARTING -> MA_STATE_STARTED + ma_device_stop() -> MA_STATE_STOPPING -> MA_STATE_STOPPED + ``` + +When the state of the device is changed with `ma_device_start()` or `ma_device_stop()` at this same time as this function is called, the +value returned by this function could potentially be out of sync. If this is significant to your program you need to implement your own +synchronization. +*/ +MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice); + /* Sets the master volume factor for the device. @@ -5068,11 +5519,191 @@ ma_device_get_master_volume() MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB); +/* +Called from the data callback of asynchronous backends to allow miniaudio to process the data and fire the miniaudio data callback. + + +Parameters +---------- +pDevice (in) + A pointer to device whose processing the data callback. + +pOutput (out) + A pointer to the buffer that will receive the output PCM frame data. On a playback device this must not be NULL. On a duplex device + this can be NULL, in which case pInput must not be NULL. + +pInput (in) + A pointer to the buffer containing input PCM frame data. On a capture device this must not be NULL. On a duplex device this can be + NULL, in which case `pOutput` must not be NULL. + +frameCount (in) + The number of frames being processed. + + +Return Value +------------ +MA_SUCCESS if successful; any other result code otherwise. + + +Thread Safety +------------- +This function should only ever be called from the internal data callback of the backend. It is safe to call this simultaneously between a +playback and capture device in duplex setups. + + +Callback Safety +--------------- +Do not call this from the miniaudio data callback. It should only ever be called from the internal data callback of the backend. + + +Remarks +------- +If both `pOutput` and `pInput` are NULL, and error will be returned. In duplex scenarios, both `pOutput` and `pInput` can be non-NULL, in +which case `pInput` will be processed first, followed by `pOutput`. + +If you are implementing a custom backend, and that backend uses a callback for data delivery, you'll need to call this from inside that +callback. +*/ +MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount); + + +/* +Calculates an appropriate buffer size from a descriptor, native sample rate and performance profile. + +This function is used by backends for helping determine an appropriately sized buffer to use with +the device depending on the values of `periodSizeInFrames` and `periodSizeInMilliseconds` in the +`pDescriptor` object. Since buffer size calculations based on time depends on the sample rate, a +best guess at the device's native sample rate is also required which is where `nativeSampleRate` +comes in. In addition, the performance profile is also needed for cases where both the period size +in frames and milliseconds are both zero. + + +Parameters +---------- +pDescriptor (in) + A pointer to device descriptor whose `periodSizeInFrames` and `periodSizeInMilliseconds` members + will be used for the calculation of the buffer size. + +nativeSampleRate (in) + The device's native sample rate. This is only ever used when the `periodSizeInFrames` member of + `pDescriptor` is zero. In this case, `periodSizeInMilliseconds` will be used instead, in which + case a sample rate is required to convert to a size in frames. + +performanceProfile (in) + When both the `periodSizeInFrames` and `periodSizeInMilliseconds` members of `pDescriptor` are + zero, miniaudio will fall back to a buffer size based on the performance profile. The profile + to use for this calculation is determine by this parameter. + + +Return Value +------------ +The calculated buffer size in frames. + + +Thread Safety +------------- +This is safe so long as nothing modifies `pDescriptor` at the same time. However, this function +should only ever be called from within the backend's device initialization routine and therefore +shouldn't have any multithreading concerns. + + +Callback Safety +--------------- +This is safe to call within the data callback, but there is no reason to ever do this. + + +Remarks +------- +If `nativeSampleRate` is zero, this function will fall back to `pDescriptor->sampleRate`. If that +is also zero, `MA_DEFAULT_SAMPLE_RATE` will be used instead. +*/ +MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile); + + + /* Retrieves a friendly name for a backend. */ MA_API const char* ma_get_backend_name(ma_backend backend); +/* +Determines whether or not the given backend is available by the compilation environment. +*/ +MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend); + +/* +Retrieves compile-time enabled backends. + + +Parameters +---------- +pBackends (out, optional) + A pointer to the buffer that will receive the enabled backends. Set to NULL to retrieve the backend count. Setting + the capacity of the buffer to `MA_BUFFER_COUNT` will guarantee it's large enough for all backends. + +backendCap (in) + The capacity of the `pBackends` buffer. + +pBackendCount (out) + A pointer to the variable that will receive the enabled backend count. + + +Return Value +------------ +MA_SUCCESS if successful. +MA_INVALID_ARGS if `pBackendCount` is NULL. +MA_NO_SPACE if the capacity of `pBackends` is not large enough. + +If `MA_NO_SPACE` is returned, the `pBackends` buffer will be filled with `*pBackendCount` values. + + +Thread Safety +------------- +Safe. + + +Callback Safety +--------------- +Safe. + + +Remarks +------- +If you want to retrieve the number of backends so you can determine the capacity of `pBackends` buffer, you can call +this function with `pBackends` set to NULL. + +This will also enumerate the null backend. If you don't want to include this you need to check for `ma_backend_null` +when you enumerate over the returned backends and handle it appropriately. Alternatively, you can disable it at +compile time with `MA_NO_NULL`. + +The returned backends are determined based on compile time settings, not the platform it's currently running on. For +example, PulseAudio will be returned if it was enabled at compile time, even when the user doesn't actually have +PulseAudio installed. + + +Example 1 +--------- +The example below retrieves the enabled backend count using a fixed sized buffer allocated on the stack. The buffer is +given a capacity of `MA_BACKEND_COUNT` which will guarantee it'll be large enough to store all available backends. +Since `MA_BACKEND_COUNT` is always a relatively small value, this should be suitable for most scenarios. + +``` +ma_backend enabledBackends[MA_BACKEND_COUNT]; +size_t enabledBackendCount; + +result = ma_get_enabled_backends(enabledBackends, MA_BACKEND_COUNT, &enabledBackendCount); +if (result != MA_SUCCESS) { + // Failed to retrieve enabled backends. Should never happen in this example since all inputs are valid. +} +``` + + +See Also +-------- +ma_is_backend_enabled() +*/ +MA_API ma_result ma_get_enabled_backends(ma_backend* pBackends, size_t backendCap, size_t* pBackendCount); + /* Determines whether or not loopback mode is support by a backend. */ @@ -5086,17 +5717,17 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend); /* Locks a spinlock. */ -MA_API ma_result ma_spinlock_lock(ma_spinlock* pSpinlock); +MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock); /* Locks a spinlock, but does not yield() when looping. */ -MA_API ma_result ma_spinlock_lock_noyield(ma_spinlock* pSpinlock); +MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock); /* Unlocks a spinlock. */ -MA_API ma_result ma_spinlock_unlock(ma_spinlock* pSpinlock); +MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock); /* @@ -5189,6 +5820,8 @@ Offsets a pointer by the specified number of PCM frames. */ MA_API void* ma_offset_pcm_frames_ptr(void* p, ma_uint64 offsetInFrames, ma_format format, ma_uint32 channels); MA_API const void* ma_offset_pcm_frames_const_ptr(const void* p, ma_uint64 offsetInFrames, ma_format format, ma_uint32 channels); +static MA_INLINE float* ma_offset_pcm_frames_ptr_f32(float* p, ma_uint64 offsetInFrames, ma_uint32 channels) { return (float*)ma_offset_pcm_frames_ptr((void*)p, offsetInFrames, ma_format_f32, channels); } +static MA_INLINE const float* ma_offset_pcm_frames_const_ptr_f32(const float* p, ma_uint64 offsetInFrames, ma_uint32 channels) { return (const float*)ma_offset_pcm_frames_const_ptr((const void*)p, offsetInFrames, ma_format_f32, channels); } /* @@ -5263,6 +5896,29 @@ MA_API ma_result ma_data_source_get_cursor_in_pcm_frames(ma_data_source* pDataSo MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSource, ma_uint64* pLength); /* Returns MA_NOT_IMPLEMENTED if the length is unknown or cannot be determined. Decoders can return this. */ + + +typedef struct +{ + ma_data_source_callbacks ds; + ma_format format; + ma_uint32 channels; + ma_uint64 cursor; + ma_uint64 sizeInFrames; + const void* pData; +} ma_audio_buffer_ref; + +MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef); +MA_API ma_result ma_audio_buffer_ref_set_data(ma_audio_buffer_ref* pAudioBufferRef, const void* pData, ma_uint64 sizeInFrames); +MA_API ma_uint64 ma_audio_buffer_ref_read_pcm_frames(ma_audio_buffer_ref* pAudioBufferRef, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop); +MA_API ma_result ma_audio_buffer_ref_seek_to_pcm_frame(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameIndex); +MA_API ma_result ma_audio_buffer_ref_map(ma_audio_buffer_ref* pAudioBufferRef, void** ppFramesOut, ma_uint64* pFrameCount); +MA_API ma_result ma_audio_buffer_ref_unmap(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameCount); /* Returns MA_AT_END if the end has been reached. This should be considered successful. */ +MA_API ma_result ma_audio_buffer_ref_at_end(ma_audio_buffer_ref* pAudioBufferRef); +MA_API ma_result ma_audio_buffer_ref_get_available_frames(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pAvailableFrames); + + + typedef struct { ma_format format; @@ -5276,12 +5932,7 @@ MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_u typedef struct { - ma_data_source_callbacks ds; - ma_format format; - ma_uint32 channels; - ma_uint64 cursor; - ma_uint64 sizeInFrames; - const void* pData; + ma_audio_buffer_ref ref; ma_allocation_callbacks allocationCallbacks; ma_bool32 ownsData; /* Used to control whether or not miniaudio owns the data buffer. If set to true, pData will be freed in ma_audio_buffer_uninit(). */ ma_uint8 _pExtraData[1]; /* For allocating a buffer with the memory located directly after the other memory of the structure. */ @@ -5637,10 +6288,9 @@ MA_API ma_uint64 ma_waveform_read_pcm_frames(ma_waveform* pWaveform, void* pFram MA_API ma_result ma_waveform_seek_to_pcm_frame(ma_waveform* pWaveform, ma_uint64 frameIndex); MA_API ma_result ma_waveform_set_amplitude(ma_waveform* pWaveform, double amplitude); MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double frequency); +MA_API ma_result ma_waveform_set_type(ma_waveform* pWaveform, ma_waveform_type type); MA_API ma_result ma_waveform_set_sample_rate(ma_waveform* pWaveform, ma_uint32 sampleRate); - - typedef enum { ma_noise_type_white, @@ -5682,6 +6332,9 @@ typedef struct MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise); MA_API ma_uint64 ma_noise_read_pcm_frames(ma_noise* pNoise, void* pFramesOut, ma_uint64 frameCount); +MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude); +MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed); +MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type); #endif /* MA_NO_GENERATION */ @@ -5879,7 +6532,7 @@ IMPLEMENTATION It looks like the -fPIC option uses the ebx register which GCC complains about. We can work around this by just using a different register, the specific register of which I'm letting the compiler decide on. The "k" prefix is used to specify a 32-bit register. The {...} syntax is for supporting different assembly dialects. - + What's basically happening is that we're saving and restoring the ebx register manually. */ #if defined(DRFLAC_X86) && defined(__PIC__) @@ -6081,22 +6734,45 @@ static MA_INLINE ma_bool32 ma_has_neon(void) #endif #endif +#if defined(__has_builtin) + #define MA_COMPILER_HAS_BUILTIN(x) __has_builtin(x) +#else + #define MA_COMPILER_HAS_BUILTIN(x) 0 +#endif + +#ifndef MA_ASSUME + #if MA_COMPILER_HAS_BUILTIN(__builtin_assume) + #define MA_ASSUME(x) __builtin_assume(x) + #elif MA_COMPILER_HAS_BUILTIN(__builtin_unreachable) + #define MA_ASSUME(x) do { if (!(x)) __builtin_unreachable(); } while (0) + #elif defined(_MSC_VER) + #define MA_ASSUME(x) __assume(x) + #else + #define MA_ASSUME(x) while(0) + #endif +#endif + +#ifndef MA_RESTRICT + #if defined(__clang__) || defined(__GNUC__) || defined(_MSC_VER) + #define MA_RESTRICT __restrict + #else + #define MA_RESTRICT + #endif +#endif #if defined(_MSC_VER) && _MSC_VER >= 1400 #define MA_HAS_BYTESWAP16_INTRINSIC #define MA_HAS_BYTESWAP32_INTRINSIC #define MA_HAS_BYTESWAP64_INTRINSIC #elif defined(__clang__) - #if defined(__has_builtin) - #if __has_builtin(__builtin_bswap16) - #define MA_HAS_BYTESWAP16_INTRINSIC - #endif - #if __has_builtin(__builtin_bswap32) - #define MA_HAS_BYTESWAP32_INTRINSIC - #endif - #if __has_builtin(__builtin_bswap64) - #define MA_HAS_BYTESWAP64_INTRINSIC - #endif + #if MA_COMPILER_HAS_BUILTIN(__builtin_bswap16) + #define MA_HAS_BYTESWAP16_INTRINSIC + #endif + #if MA_COMPILER_HAS_BUILTIN(__builtin_bswap32) + #define MA_HAS_BYTESWAP32_INTRINSIC + #endif + #if MA_COMPILER_HAS_BUILTIN(__builtin_bswap64) + #define MA_HAS_BYTESWAP64_INTRINSIC #endif #elif defined(__GNUC__) #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) @@ -6201,11 +6877,16 @@ static MA_INLINE void ma_yield() { #if defined(__i386) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64) /* x86/x64 */ - #if defined(_MSC_VER) && !defined(__clang__) + #if (defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__)) && !defined(__clang__) #if _MSC_VER >= 1400 _mm_pause(); #else - __asm pause; + #if defined(__DMC__) + /* Digital Mars does not recognize the PAUSE opcode. Fall back to NOP. */ + __asm nop; + #else + __asm pause; + #endif #endif #else __asm__ __volatile__ ("pause"); @@ -6230,6 +6911,14 @@ static MA_INLINE void ma_yield() #endif +#ifndef MA_FLT_MAX + #ifdef FLT_MAX + #define MA_FLT_MAX FLT_MAX + #else + #define MA_FLT_MAX 3.402823466e+38F + #endif +#endif + #ifndef MA_PI #define MA_PI 3.14159265358979323846264f @@ -6285,44 +6974,60 @@ static MA_INLINE void ma_yield() #endif -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" #endif + /* Standard sample rates, in order of priority. */ static ma_uint32 g_maStandardSampleRatePriorities[] = { - MA_SAMPLE_RATE_48000, /* Most common */ - MA_SAMPLE_RATE_44100, + (ma_uint32)ma_standard_sample_rate_48000, + (ma_uint32)ma_standard_sample_rate_44100, - MA_SAMPLE_RATE_32000, /* Lows */ - MA_SAMPLE_RATE_24000, - MA_SAMPLE_RATE_22050, + (ma_uint32)ma_standard_sample_rate_32000, + (ma_uint32)ma_standard_sample_rate_24000, + (ma_uint32)ma_standard_sample_rate_22050, - MA_SAMPLE_RATE_88200, /* Highs */ - MA_SAMPLE_RATE_96000, - MA_SAMPLE_RATE_176400, - MA_SAMPLE_RATE_192000, + (ma_uint32)ma_standard_sample_rate_88200, + (ma_uint32)ma_standard_sample_rate_96000, + (ma_uint32)ma_standard_sample_rate_176400, + (ma_uint32)ma_standard_sample_rate_192000, - MA_SAMPLE_RATE_16000, /* Extreme lows */ - MA_SAMPLE_RATE_11025, - MA_SAMPLE_RATE_8000, + (ma_uint32)ma_standard_sample_rate_16000, + (ma_uint32)ma_standard_sample_rate_11025, + (ma_uint32)ma_standard_sample_rate_8000, - MA_SAMPLE_RATE_352800, /* Extreme highs */ - MA_SAMPLE_RATE_384000 + (ma_uint32)ma_standard_sample_rate_352800, + (ma_uint32)ma_standard_sample_rate_384000 }; +static MA_INLINE ma_bool32 ma_is_standard_sample_rate(ma_uint32 sampleRate) +{ + ma_uint32 iSampleRate; + + for (iSampleRate = 0; iSampleRate < sizeof(g_maStandardSampleRatePriorities) / sizeof(g_maStandardSampleRatePriorities[0]); iSampleRate += 1) { + if (g_maStandardSampleRatePriorities[iSampleRate] == sampleRate) { + return MA_TRUE; + } + } + + /* Getting here means the sample rate is not supported. */ + return MA_FALSE; +} + + static ma_format g_maFormatPriorities[] = { ma_format_s16, /* Most common */ ma_format_f32, - + /*ma_format_s24_32,*/ /* Clean alignment */ ma_format_s32, - + ma_format_s24, /* Unclean alignment */ - + ma_format_u8 /* Low quality */ }; -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif @@ -6464,6 +7169,27 @@ static MA_INLINE float ma_log10f(float x) } +static MA_INLINE double ma_degrees_to_radians(double degrees) +{ + return degrees * 0.01745329252; +} + +static MA_INLINE double ma_radians_to_degrees(double radians) +{ + return radians * 57.295779512896; +} + +static MA_INLINE float ma_degrees_to_radians_f(float degrees) +{ + return degrees * 0.01745329252f; +} + +static MA_INLINE float ma_radians_to_degrees_f(float radians) +{ + return radians * 57.295779512896f; +} + + /* Return Values: 0: Success @@ -6500,6 +7226,35 @@ MA_API int ma_strcpy_s(char* dst, size_t dstSizeInBytes, const char* src) return 34; } +MA_API int ma_wcscpy_s(wchar_t* dst, size_t dstCap, const wchar_t* src) +{ + size_t i; + + if (dst == 0) { + return 22; + } + if (dstCap == 0) { + return 34; + } + if (src == 0) { + dst[0] = '\0'; + return 22; + } + + for (i = 0; i < dstCap && src[i] != '\0'; ++i) { + dst[i] = src[i]; + } + + if (i < dstCap) { + dst[i] = '\0'; + return 0; + } + + dst[0] = '\0'; + return 34; +} + + MA_API int ma_strncpy_s(char* dst, size_t dstSizeInBytes, const char* src, size_t count) { size_t maxcount; @@ -6744,6 +7499,19 @@ MA_API char* ma_copy_string(const char* src, const ma_allocation_callbacks* pAll return dst; } +MA_API wchar_t* ma_copy_string_w(const wchar_t* src, const ma_allocation_callbacks* pAllocationCallbacks) +{ + size_t sz = wcslen(src)+1; + wchar_t* dst = (wchar_t*)ma_malloc(sz * sizeof(*dst), pAllocationCallbacks); + if (dst == NULL) { + return NULL; + } + + ma_wcscpy_s(dst, sz, src); + + return dst; +} + #include static ma_result ma_result_from_errno(int e) @@ -7150,7 +7918,7 @@ static ma_result ma_result_from_errno(int e) MA_API ma_result ma_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif @@ -7162,7 +7930,7 @@ MA_API ma_result ma_fopen(FILE** ppFile, const char* pFilePath, const char* pOpe return MA_INVALID_ARGS; } -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return ma_result_from_errno(err); @@ -7199,12 +7967,13 @@ _wfopen() isn't always available in all compilation environments. * MSVC seems to support it universally as far back as VC6 from what I can tell (haven't checked further back). * MinGW-64 (both 32- and 64-bit) seems to support it. * MinGW wraps it in !defined(__STRICT_ANSI__). + * OpenWatcom wraps it in !defined(_NO_EXT_KEYS). This can be reviewed as compatibility issues arise. The preference is to use _wfopen_s() and _wfopen() as opposed to the wcsrtombs() fallback, so if you notice your compiler not detecting this properly I'm happy to look at adding support. */ #if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define MA_HAS_WFOPEN #endif #endif @@ -7370,10 +8139,10 @@ static MA_INLINE unsigned int ma_count_set_bits(unsigned int x) if (x & 1) { count += 1; } - + x = x >> 1; } - + return count; } @@ -7623,7 +8392,7 @@ typedef unsigned int c89atomic_uint32; typedef signed __int64 c89atomic_int64; typedef unsigned __int64 c89atomic_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -7632,13 +8401,12 @@ typedef unsigned int c89atomic_uint32; #endif typedef signed long long c89atomic_int64; typedef unsigned long long c89atomic_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif typedef int c89atomic_memory_order; typedef unsigned char c89atomic_bool; -typedef unsigned char c89atomic_flag; #if !defined(C89ATOMIC_64BIT) && !defined(C89ATOMIC_32BIT) #ifdef _WIN32 #ifdef _WIN64 @@ -7672,7 +8440,7 @@ typedef unsigned char c89atomic_flag; #elif defined(__arm__) || defined(_M_ARM) #define C89ATOMIC_ARM #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) #define C89ATOMIC_INLINE __forceinline #elif defined(__GNUC__) #if defined(__STRICT_ANSI__) @@ -7680,137 +8448,80 @@ typedef unsigned char c89atomic_flag; #else #define C89ATOMIC_INLINE inline __attribute__((always_inline)) #endif +#elif defined(__WATCOMC__) || defined(__DMC__) + #define C89ATOMIC_INLINE __inline #else #define C89ATOMIC_INLINE #endif -#if defined(_MSC_VER) +#define C89ATOMIC_HAS_8 +#define C89ATOMIC_HAS_16 +#define C89ATOMIC_HAS_32 +#define C89ATOMIC_HAS_64 +#if (defined(_MSC_VER) ) || defined(__WATCOMC__) || defined(__DMC__) #define c89atomic_memory_order_relaxed 0 #define c89atomic_memory_order_consume 1 #define c89atomic_memory_order_acquire 2 #define c89atomic_memory_order_release 3 #define c89atomic_memory_order_acq_rel 4 #define c89atomic_memory_order_seq_cst 5 - #if _MSC_VER >= 1400 + #if _MSC_VER < 1600 && defined(C89ATOMIC_32BIT) + #define C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY + #endif + #if _MSC_VER < 1600 + #undef C89ATOMIC_HAS_8 + #undef C89ATOMIC_HAS_16 + #endif + #if !defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) #include - #define c89atomic_exchange_explicit_8( dst, src, order) (c89atomic_uint8 )_InterlockedExchange8 ((volatile char* )dst, (char )src) - #define c89atomic_exchange_explicit_16(dst, src, order) (c89atomic_uint16)_InterlockedExchange16((volatile short*)dst, (short)src) - #define c89atomic_exchange_explicit_32(dst, src, order) (c89atomic_uint32)_InterlockedExchange ((volatile long* )dst, (long )src) - #if defined(C89ATOMIC_64BIT) - #define c89atomic_exchange_explicit_64(dst, src, order) (c89atomic_uint64)_InterlockedExchange64((volatile long long*)dst, (long long)src) #endif - #define c89atomic_fetch_add_explicit_8( dst, src, order) (c89atomic_uint8 )_InterlockedExchangeAdd8 ((volatile char* )dst, (char )src) - #define c89atomic_fetch_add_explicit_16(dst, src, order) (c89atomic_uint16)_InterlockedExchangeAdd16((volatile short*)dst, (short)src) - #define c89atomic_fetch_add_explicit_32(dst, src, order) (c89atomic_uint32)_InterlockedExchangeAdd ((volatile long* )dst, (long )src) - #if defined(C89ATOMIC_64BIT) - #define c89atomic_fetch_add_explicit_64(dst, src, order) (c89atomic_uint64)_InterlockedExchangeAdd64((volatile long long*)dst, (long long)src) - #endif - #define c89atomic_compare_and_swap_8( dst, expected, desired) (c89atomic_uint8 )_InterlockedCompareExchange8 ((volatile char* )dst, (char )desired, (char )expected) - #define c89atomic_compare_and_swap_16(dst, expected, desired) (c89atomic_uint16)_InterlockedCompareExchange16((volatile short* )dst, (short )desired, (short )expected) - #define c89atomic_compare_and_swap_32(dst, expected, desired) (c89atomic_uint32)_InterlockedCompareExchange ((volatile long* )dst, (long )desired, (long )expected) - #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile long long*)dst, (long long)desired, (long long)expected) - #if defined(C89ATOMIC_X64) - #define c89atomic_thread_fence(order) __faststorefence() - #else - static C89ATOMIC_INLINE void c89atomic_thread_fence(c89atomic_memory_order order) - { - volatile c89atomic_uint32 barrier = 0; - (void)order; - c89atomic_fetch_add_explicit_32(&barrier, 0, order); - } - #endif - #else - #if defined(__i386) || defined(_M_IX86) - static C89ATOMIC_INLINE void __stdcall c89atomic_thread_fence(int order) - { - volatile c89atomic_uint32 barrier; - (void)order; - __asm { - xchg barrier, eax - } - } - static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, int order) - { - (void)order; - __asm { - mov ecx, dst - mov al, src - lock xchg [ecx], al - } - } - static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, int order) - { - (void)order; - __asm { - mov ecx, dst - mov ax, src - lock xchg [ecx], ax - } - } - static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, int order) - { - (void)order; - __asm { - mov ecx, dst - mov eax, src - lock xchg [ecx], eax - } - } - static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, int order) - { - (void)order; - __asm { - mov ecx, dst - mov al, src - lock xadd [ecx], al - } - } - static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, int order) - { - (void)order; - __asm { - mov ecx, dst - mov ax, src - lock xadd [ecx], ax - } - } - static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, int order) - { - (void)order; - __asm { - mov ecx, dst - mov eax, src - lock xadd [ecx], eax - } - } + #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) + #if defined(C89ATOMIC_HAS_8) static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_compare_and_swap_8(volatile c89atomic_uint8* dst, c89atomic_uint8 expected, c89atomic_uint8 desired) { + c89atomic_uint8 result = 0; __asm { mov ecx, dst mov al, expected mov dl, desired lock cmpxchg [ecx], dl + mov result, al } + return result; } + #endif + #if defined(C89ATOMIC_HAS_16) static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_compare_and_swap_16(volatile c89atomic_uint16* dst, c89atomic_uint16 expected, c89atomic_uint16 desired) { + c89atomic_uint16 result = 0; __asm { mov ecx, dst mov ax, expected mov dx, desired lock cmpxchg [ecx], dx + mov result, ax } + return result; } + #endif + #if defined(C89ATOMIC_HAS_32) static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_compare_and_swap_32(volatile c89atomic_uint32* dst, c89atomic_uint32 expected, c89atomic_uint32 desired) { + c89atomic_uint32 result = 0; __asm { mov ecx, dst mov eax, expected mov edx, desired lock cmpxchg [ecx], edx + mov result, eax } + return result; } + #endif + #if defined(C89ATOMIC_HAS_64) static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_compare_and_swap_64(volatile c89atomic_uint64* dst, c89atomic_uint64 expected, c89atomic_uint64 desired) { + c89atomic_uint32 resultEAX = 0; + c89atomic_uint32 resultEDX = 0; __asm { mov esi, dst mov eax, dword ptr expected @@ -7818,248 +8529,518 @@ typedef unsigned char c89atomic_flag; mov ebx, dword ptr desired mov ecx, dword ptr desired + 4 lock cmpxchg8b qword ptr [esi] + mov resultEAX, eax + mov resultEDX, edx } + return ((c89atomic_uint64)resultEDX << 32) | resultEAX; + } + #endif + #else + #if defined(C89ATOMIC_HAS_8) + #define c89atomic_compare_and_swap_8( dst, expected, desired) (c89atomic_uint8 )_InterlockedCompareExchange8((volatile char*)dst, (char)desired, (char)expected) + #endif + #if defined(C89ATOMIC_HAS_16) + #define c89atomic_compare_and_swap_16(dst, expected, desired) (c89atomic_uint16)_InterlockedCompareExchange16((volatile short*)dst, (short)desired, (short)expected) + #endif + #if defined(C89ATOMIC_HAS_32) + #define c89atomic_compare_and_swap_32(dst, expected, desired) (c89atomic_uint32)_InterlockedCompareExchange((volatile long*)dst, (long)desired, (long)expected) + #endif + #if defined(C89ATOMIC_HAS_64) + #define c89atomic_compare_and_swap_64(dst, expected, desired) (c89atomic_uint64)_InterlockedCompareExchange64((volatile long long*)dst, (long long)desired, (long long)expected) + #endif + #endif + #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 result = 0; + (void)order; + __asm { + mov ecx, dst + mov al, src + lock xchg [ecx], al + mov result, al + } + return result; + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 result = 0; + (void)order; + __asm { + mov ecx, dst + mov ax, src + lock xchg [ecx], ax + mov result, ax + } + return result; + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 result = 0; + (void)order; + __asm { + mov ecx, dst + mov eax, src + lock xchg [ecx], eax + mov result, eax + } + return result; + } + #endif + #else + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint8)_InterlockedExchange8((volatile char*)dst, (char)src); + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint16)_InterlockedExchange16((volatile short*)dst, (short)src); + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint32)_InterlockedExchange((volatile long*)dst, (long)src); + } + #endif + #if defined(C89ATOMIC_HAS_64) && defined(C89ATOMIC_64BIT) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint64)_InterlockedExchange64((volatile long long*)dst, (long long)src); } #else - error "Unsupported architecture." + #endif + #endif + #if defined(C89ATOMIC_HAS_64) && !defined(C89ATOMIC_64BIT) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + do { + oldValue = *dst; + } while (c89atomic_compare_and_swap_64(dst, oldValue, src) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 result = 0; + (void)order; + __asm { + mov ecx, dst + mov al, src + lock xadd [ecx], al + mov result, al + } + return result; + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 result = 0; + (void)order; + __asm { + mov ecx, dst + mov ax, src + lock xadd [ecx], ax + mov result, ax + } + return result; + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 result = 0; + (void)order; + __asm { + mov ecx, dst + mov eax, src + lock xadd [ecx], eax + mov result, eax + } + return result; + } + #endif + #else + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint8)_InterlockedExchangeAdd8((volatile char*)dst, (char)src); + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint16)_InterlockedExchangeAdd16((volatile short*)dst, (short)src); + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint32)_InterlockedExchangeAdd((volatile long*)dst, (long)src); + } + #endif + #if defined(C89ATOMIC_HAS_64) && defined(C89ATOMIC_64BIT) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return (c89atomic_uint64)_InterlockedExchangeAdd64((volatile long long*)dst, (long long)src); + } + #else + #endif + #endif + #if defined(C89ATOMIC_HAS_64) && !defined(C89ATOMIC_64BIT) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue + src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_MSVC_USE_INLINED_ASSEMBLY) + static C89ATOMIC_INLINE void __stdcall c89atomic_thread_fence(c89atomic_memory_order order) + { + (void)order; + __asm { + lock add [esp], 0 + } + } + #else + #if defined(C89ATOMIC_X64) + #define c89atomic_thread_fence(order) __faststorefence(), (void)order + #else + static C89ATOMIC_INLINE void c89atomic_thread_fence(c89atomic_memory_order order) + { + volatile c89atomic_uint32 barrier = 0; + c89atomic_fetch_add_explicit_32(&barrier, 0, order); + } #endif #endif #define c89atomic_compiler_fence() c89atomic_thread_fence(c89atomic_memory_order_seq_cst) #define c89atomic_signal_fence(order) c89atomic_thread_fence(order) - #define c89atomic_load_explicit_8( ptr, order) c89atomic_compare_and_swap_8 (ptr, 0, 0) - #define c89atomic_load_explicit_16(ptr, order) c89atomic_compare_and_swap_16(ptr, 0, 0) - #define c89atomic_load_explicit_32(ptr, order) c89atomic_compare_and_swap_32(ptr, 0, 0) - #define c89atomic_load_explicit_64(ptr, order) c89atomic_compare_and_swap_64(ptr, 0, 0) - #define c89atomic_store_explicit_8( dst, src, order) (void)c89atomic_exchange_explicit_8 (dst, src, order) - #define c89atomic_store_explicit_16(dst, src, order) (void)c89atomic_exchange_explicit_16(dst, src, order) - #define c89atomic_store_explicit_32(dst, src, order) (void)c89atomic_exchange_explicit_32(dst, src, order) - #define c89atomic_store_explicit_64(dst, src, order) (void)c89atomic_exchange_explicit_64(dst, src, order) -#if defined(C89ATOMIC_32BIT) - static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, int order) - { - volatile c89atomic_uint64 oldValue; - do { - oldValue = *dst; - } while (c89atomic_compare_and_swap_64(dst, oldValue, src) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, int order) - { - volatile c89atomic_uint64 oldValue; - volatile c89atomic_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue + src; - } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } -#endif - static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_sub_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, int order) - { - volatile c89atomic_uint8 oldValue; - volatile c89atomic_uint8 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint8)(oldValue - src); - } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_sub_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, int order) - { - volatile c89atomic_uint16 oldValue; - volatile c89atomic_uint16 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint16)(oldValue - src); - } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_sub_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, int order) - { - volatile c89atomic_uint32 oldValue; - volatile c89atomic_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue - src; - } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_sub_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, int order) - { - volatile c89atomic_uint64 oldValue; - volatile c89atomic_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue - src; - } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_and_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, int order) - { - volatile c89atomic_uint8 oldValue; - volatile c89atomic_uint8 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint8)(oldValue & src); - } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_and_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, int order) - { - volatile c89atomic_uint16 oldValue; - volatile c89atomic_uint16 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint16)(oldValue & src); - } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_and_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, int order) - { - volatile c89atomic_uint32 oldValue; - volatile c89atomic_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue & src; - } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_and_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, int order) - { - volatile c89atomic_uint64 oldValue; - volatile c89atomic_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue & src; - } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_xor_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, int order) - { - volatile c89atomic_uint8 oldValue; - volatile c89atomic_uint8 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint8)(oldValue ^ src); - } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_xor_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, int order) - { - volatile c89atomic_uint16 oldValue; - volatile c89atomic_uint16 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint16)(oldValue ^ src); - } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_xor_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, int order) - { - volatile c89atomic_uint32 oldValue; - volatile c89atomic_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue ^ src; - } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_xor_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, int order) - { - volatile c89atomic_uint64 oldValue; - volatile c89atomic_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue ^ src; - } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_or_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, int order) - { - volatile c89atomic_uint8 oldValue; - volatile c89atomic_uint8 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint8)(oldValue | src); - } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_or_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, int order) - { - volatile c89atomic_uint16 oldValue; - volatile c89atomic_uint16 newValue; - do { - oldValue = *dst; - newValue = (c89atomic_uint16)(oldValue | src); - } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_or_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, int order) - { - volatile c89atomic_uint32 oldValue; - volatile c89atomic_uint32 newValue; - do { - oldValue = *dst; - newValue = oldValue | src; - } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_or_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, int order) - { - volatile c89atomic_uint64 oldValue; - volatile c89atomic_uint64 newValue; - do { - oldValue = *dst; - newValue = oldValue | src; - } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); - (void)order; - return oldValue; - } - #define c89atomic_test_and_set_explicit_8( dst, order) c89atomic_exchange_explicit_8 (dst, 1, order) - #define c89atomic_test_and_set_explicit_16(dst, order) c89atomic_exchange_explicit_16(dst, 1, order) - #define c89atomic_test_and_set_explicit_32(dst, order) c89atomic_exchange_explicit_32(dst, 1, order) - #define c89atomic_test_and_set_explicit_64(dst, order) c89atomic_exchange_explicit_64(dst, 1, order) - #define c89atomic_clear_explicit_8( dst, order) c89atomic_store_explicit_8 (dst, 0, order) - #define c89atomic_clear_explicit_16(dst, order) c89atomic_store_explicit_16(dst, 0, order) - #define c89atomic_clear_explicit_32(dst, order) c89atomic_store_explicit_32(dst, 0, order) - #define c89atomic_clear_explicit_64(dst, order) c89atomic_store_explicit_64(dst, 0, order) - #define c89atomic_flag_test_and_set_explicit(ptr, order) (c89atomic_flag)c89atomic_test_and_set_explicit_8(ptr, order) - #define c89atomic_flag_clear_explicit(ptr, order) c89atomic_clear_explicit_8(ptr, order) -#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC__ >= 7))) + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_load_explicit_8(volatile const c89atomic_uint8* ptr, c89atomic_memory_order order) + { + (void)order; + return c89atomic_compare_and_swap_8((c89atomic_uint8*)ptr, 0, 0); + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_load_explicit_16(volatile const c89atomic_uint16* ptr, c89atomic_memory_order order) + { + (void)order; + return c89atomic_compare_and_swap_16((c89atomic_uint16*)ptr, 0, 0); + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_load_explicit_32(volatile const c89atomic_uint32* ptr, c89atomic_memory_order order) + { + (void)order; + return c89atomic_compare_and_swap_32((c89atomic_uint32*)ptr, 0, 0); + } + #endif + #if defined(C89ATOMIC_HAS_64) + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_load_explicit_64(volatile const c89atomic_uint64* ptr, c89atomic_memory_order order) + { + (void)order; + return c89atomic_compare_and_swap_64((c89atomic_uint64*)ptr, 0, 0); + } + #endif + #if defined(C89ATOMIC_HAS_8) + #define c89atomic_store_explicit_8( dst, src, order) (void)c89atomic_exchange_explicit_8 (dst, src, order) + #endif + #if defined(C89ATOMIC_HAS_16) + #define c89atomic_store_explicit_16(dst, src, order) (void)c89atomic_exchange_explicit_16(dst, src, order) + #endif + #if defined(C89ATOMIC_HAS_32) + #define c89atomic_store_explicit_32(dst, src, order) (void)c89atomic_exchange_explicit_32(dst, src, order) + #endif + #if defined(C89ATOMIC_HAS_64) + #define c89atomic_store_explicit_64(dst, src, order) (void)c89atomic_exchange_explicit_64(dst, src, order) + #endif + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_sub_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue - src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_sub_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue - src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_sub_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue - src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_64) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_sub_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue - src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_and_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue & src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_and_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue & src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_and_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue & src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_64) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_and_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue & src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_xor_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue ^ src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_xor_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue ^ src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_xor_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue ^ src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_64) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_xor_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue ^ src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_8) + static C89ATOMIC_INLINE c89atomic_uint8 __stdcall c89atomic_fetch_or_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue | src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_16) + static C89ATOMIC_INLINE c89atomic_uint16 __stdcall c89atomic_fetch_or_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue | src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_32) + static C89ATOMIC_INLINE c89atomic_uint32 __stdcall c89atomic_fetch_or_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue | src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_64) + static C89ATOMIC_INLINE c89atomic_uint64 __stdcall c89atomic_fetch_or_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue | src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #if defined(C89ATOMIC_HAS_8) + #define c89atomic_test_and_set_explicit_8( dst, order) c89atomic_exchange_explicit_8 (dst, 1, order) + #endif + #if defined(C89ATOMIC_HAS_16) + #define c89atomic_test_and_set_explicit_16(dst, order) c89atomic_exchange_explicit_16(dst, 1, order) + #endif + #if defined(C89ATOMIC_HAS_32) + #define c89atomic_test_and_set_explicit_32(dst, order) c89atomic_exchange_explicit_32(dst, 1, order) + #endif + #if defined(C89ATOMIC_HAS_64) + #define c89atomic_test_and_set_explicit_64(dst, order) c89atomic_exchange_explicit_64(dst, 1, order) + #endif + #if defined(C89ATOMIC_HAS_8) + #define c89atomic_clear_explicit_8( dst, order) c89atomic_store_explicit_8 (dst, 0, order) + #endif + #if defined(C89ATOMIC_HAS_16) + #define c89atomic_clear_explicit_16(dst, order) c89atomic_store_explicit_16(dst, 0, order) + #endif + #if defined(C89ATOMIC_HAS_32) + #define c89atomic_clear_explicit_32(dst, order) c89atomic_store_explicit_32(dst, 0, order) + #endif + #if defined(C89ATOMIC_HAS_64) + #define c89atomic_clear_explicit_64(dst, order) c89atomic_store_explicit_64(dst, 0, order) + #endif + #if defined(C89ATOMIC_HAS_8) + typedef c89atomic_uint8 c89atomic_flag; + #define c89atomic_flag_test_and_set_explicit(ptr, order) (c89atomic_bool)c89atomic_test_and_set_explicit_8(ptr, order) + #define c89atomic_flag_clear_explicit(ptr, order) c89atomic_clear_explicit_8(ptr, order) + #define c89atoimc_flag_load_explicit(ptr, order) c89atomic_load_explicit_8(ptr, order) + #else + typedef c89atomic_uint32 c89atomic_flag; + #define c89atomic_flag_test_and_set_explicit(ptr, order) (c89atomic_bool)c89atomic_test_and_set_explicit_32(ptr, order) + #define c89atomic_flag_clear_explicit(ptr, order) c89atomic_clear_explicit_32(ptr, order) + #define c89atoimc_flag_load_explicit(ptr, order) c89atomic_load_explicit_32(ptr, order) + #endif +#elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7))) #define C89ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE #define C89ATOMIC_HAS_NATIVE_IS_LOCK_FREE - #define c89atomic_memory_order_relaxed __ATOMIC_RELAXED - #define c89atomic_memory_order_consume __ATOMIC_CONSUME - #define c89atomic_memory_order_acquire __ATOMIC_ACQUIRE - #define c89atomic_memory_order_release __ATOMIC_RELEASE - #define c89atomic_memory_order_acq_rel __ATOMIC_ACQ_REL - #define c89atomic_memory_order_seq_cst __ATOMIC_SEQ_CST - #define c89atomic_compiler_fence() __asm__ __volatile__("":::"memory") - #define c89atomic_thread_fence(order) __atomic_thread_fence(order) - #define c89atomic_signal_fence(order) __atomic_signal_fence(order) - #define c89atomic_is_lock_free_8(ptr) __atomic_is_lock_free(1, ptr) - #define c89atomic_is_lock_free_16(ptr) __atomic_is_lock_free(2, ptr) - #define c89atomic_is_lock_free_32(ptr) __atomic_is_lock_free(4, ptr) - #define c89atomic_is_lock_free_64(ptr) __atomic_is_lock_free(8, ptr) - #define c89atomic_flag_test_and_set_explicit(dst, order) (c89atomic_flag)__atomic_test_and_set(dst, order) - #define c89atomic_flag_clear_explicit(dst, order) __atomic_clear(dst, order) + #define c89atomic_memory_order_relaxed __ATOMIC_RELAXED + #define c89atomic_memory_order_consume __ATOMIC_CONSUME + #define c89atomic_memory_order_acquire __ATOMIC_ACQUIRE + #define c89atomic_memory_order_release __ATOMIC_RELEASE + #define c89atomic_memory_order_acq_rel __ATOMIC_ACQ_REL + #define c89atomic_memory_order_seq_cst __ATOMIC_SEQ_CST + #define c89atomic_compiler_fence() __asm__ __volatile__("":::"memory") + #define c89atomic_thread_fence(order) __atomic_thread_fence(order) + #define c89atomic_signal_fence(order) __atomic_signal_fence(order) + #define c89atomic_is_lock_free_8(ptr) __atomic_is_lock_free(1, ptr) + #define c89atomic_is_lock_free_16(ptr) __atomic_is_lock_free(2, ptr) + #define c89atomic_is_lock_free_32(ptr) __atomic_is_lock_free(4, ptr) + #define c89atomic_is_lock_free_64(ptr) __atomic_is_lock_free(8, ptr) #define c89atomic_test_and_set_explicit_8( dst, order) __atomic_exchange_n(dst, 1, order) #define c89atomic_test_and_set_explicit_16(dst, order) __atomic_exchange_n(dst, 1, order) #define c89atomic_test_and_set_explicit_32(dst, order) __atomic_exchange_n(dst, 1, order) @@ -8112,6 +9093,10 @@ typedef unsigned char c89atomic_flag; #define c89atomic_compare_and_swap_16(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) #define c89atomic_compare_and_swap_32(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) #define c89atomic_compare_and_swap_64(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) + typedef c89atomic_uint8 c89atomic_flag; + #define c89atomic_flag_test_and_set_explicit(dst, order) (c89atomic_bool)__atomic_test_and_set(dst, order) + #define c89atomic_flag_clear_explicit(dst, order) __atomic_clear(dst, order) + #define c89atoimc_flag_load_explicit(ptr, order) c89atomic_load_explicit_8(ptr, order) #else #define c89atomic_memory_order_relaxed 1 #define c89atomic_memory_order_consume 2 @@ -8119,71 +9104,497 @@ typedef unsigned char c89atomic_flag; #define c89atomic_memory_order_release 4 #define c89atomic_memory_order_acq_rel 5 #define c89atomic_memory_order_seq_cst 6 - #define c89atomic_compiler_fence() __asm__ __volatile__("":::"memory") - #define c89atomic_thread_fence(order) __sync_synchronize() - #define c89atomic_signal_fence(order) c89atomic_thread_fence(order) - static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) - { - if (order > c89atomic_memory_order_acquire) { - __sync_synchronize(); + #define c89atomic_compiler_fence() __asm__ __volatile__("":::"memory") + #if defined(__GNUC__) + #define c89atomic_thread_fence(order) __sync_synchronize(), (void)order + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + if (order > c89atomic_memory_order_acquire) { + __sync_synchronize(); + } + return __sync_lock_test_and_set(dst, src); } - return __sync_lock_test_and_set(dst, src); - } - static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + do { + oldValue = *dst; + } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + do { + oldValue = *dst; + } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + do { + oldValue = *dst; + } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_add(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_sub_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_sub_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_sub_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_sub_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_sub(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_or_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_or_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_or_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_or_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_or(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_xor_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_xor_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_xor_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_xor_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_xor(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_and_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_and_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_and_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_and_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + (void)order; + return __sync_fetch_and_and(dst, src); + } + #define c89atomic_compare_and_swap_8( dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) + #define c89atomic_compare_and_swap_16(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) + #define c89atomic_compare_and_swap_32(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) + #define c89atomic_compare_and_swap_64(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) + #else + #if defined(C89ATOMIC_X86) + #define c89atomic_thread_fence(order) __asm__ __volatile__("lock; addl $0, (%%esp)" ::: "memory", "cc") + #elif defined(C89ATOMIC_X64) + #define c89atomic_thread_fence(order) __asm__ __volatile__("lock; addq $0, (%%rsp)" ::: "memory", "cc") + #else + #error Unsupported architecture. Please submit a feature request. + #endif + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_compare_and_swap_8(volatile c89atomic_uint8* dst, c89atomic_uint8 expected, c89atomic_uint8 desired) + { + c89atomic_uint8 result; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_compare_and_swap_16(volatile c89atomic_uint16* dst, c89atomic_uint16 expected, c89atomic_uint16 desired) + { + c89atomic_uint16 result; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_compare_and_swap_32(volatile c89atomic_uint32* dst, c89atomic_uint32 expected, c89atomic_uint32 desired) + { + c89atomic_uint32 result; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_compare_and_swap_64(volatile c89atomic_uint64* dst, c89atomic_uint64 expected, c89atomic_uint64 desired) + { + volatile c89atomic_uint64 result; + #if defined(C89ATOMIC_X86) + c89atomic_uint32 resultEAX; + c89atomic_uint32 resultEDX; + __asm__ __volatile__("push %%ebx; xchg %5, %%ebx; lock; cmpxchg8b %0; pop %%ebx" : "+m"(*dst), "=a"(resultEAX), "=d"(resultEDX) : "a"(expected & 0xFFFFFFFF), "d"(expected >> 32), "r"(desired & 0xFFFFFFFF), "c"(desired >> 32) : "cc"); + result = ((c89atomic_uint64)resultEDX << 32) | resultEAX; + #elif defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; cmpxchg %3, %0" : "+m"(*dst), "=a"(result) : "a"(expected), "d"(desired) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_exchange_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 result = 0; + (void)order; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_exchange_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 result = 0; + (void)order; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 result; + (void)order; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 result; + (void)order; + #if defined(C89ATOMIC_X86) + do { + result = *dst; + } while (c89atomic_compare_and_swap_64(dst, result, src) != result); + #elif defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xchg %1, %0" : "+m"(*dst), "=a"(result) : "a"(src)); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_add_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 result; + (void)order; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_add_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 result; + (void)order; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_add_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 result; + (void)order; + #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) + __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); + #else + #error Unsupported architecture. Please submit a feature request. + #endif + return result; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_add_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + #if defined(C89ATOMIC_X86) + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + (void)order; + do { + oldValue = *dst; + newValue = oldValue + src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + return oldValue; + #elif defined(C89ATOMIC_X64) + c89atomic_uint64 result; + (void)order; + __asm__ __volatile__("lock; xadd %1, %0" : "+m"(*dst), "=a"(result) : "a"(src) : "cc"); + return result; + #endif + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_sub_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue - src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_sub_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue - src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_sub_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue - src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_sub_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue - src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_and_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue & src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_and_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue & src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_and_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue & src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_and_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue & src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_xor_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue ^ src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_xor_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue ^ src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_xor_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue ^ src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_xor_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue ^ src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_fetch_or_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8 src, c89atomic_memory_order order) + { + c89atomic_uint8 oldValue; + c89atomic_uint8 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint8)(oldValue | src); + } while (c89atomic_compare_and_swap_8(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_fetch_or_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16 src, c89atomic_memory_order order) + { + c89atomic_uint16 oldValue; + c89atomic_uint16 newValue; + do { + oldValue = *dst; + newValue = (c89atomic_uint16)(oldValue | src); + } while (c89atomic_compare_and_swap_16(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_fetch_or_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + { + c89atomic_uint32 oldValue; + c89atomic_uint32 newValue; + do { + oldValue = *dst; + newValue = oldValue | src; + } while (c89atomic_compare_and_swap_32(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_fetch_or_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + { + c89atomic_uint64 oldValue; + c89atomic_uint64 newValue; + do { + oldValue = *dst; + newValue = oldValue | src; + } while (c89atomic_compare_and_swap_64(dst, oldValue, newValue) != oldValue); + (void)order; + return oldValue; + } + #endif + #define c89atomic_signal_fence(order) c89atomic_thread_fence(order) + static C89ATOMIC_INLINE c89atomic_uint8 c89atomic_load_explicit_8(volatile const c89atomic_uint8* ptr, c89atomic_memory_order order) { - volatile c89atomic_uint16 oldValue; - do { - oldValue = *dst; - } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); (void)order; - return oldValue; + return c89atomic_compare_and_swap_8((c89atomic_uint8*)ptr, 0, 0); } - static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_exchange_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32 src, c89atomic_memory_order order) + static C89ATOMIC_INLINE c89atomic_uint16 c89atomic_load_explicit_16(volatile const c89atomic_uint16* ptr, c89atomic_memory_order order) { - volatile c89atomic_uint32 oldValue; - do { - oldValue = *dst; - } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); (void)order; - return oldValue; + return c89atomic_compare_and_swap_16((c89atomic_uint16*)ptr, 0, 0); } - static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_exchange_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64 src, c89atomic_memory_order order) + static C89ATOMIC_INLINE c89atomic_uint32 c89atomic_load_explicit_32(volatile const c89atomic_uint32* ptr, c89atomic_memory_order order) { - volatile c89atomic_uint64 oldValue; - do { - oldValue = *dst; - } while (__sync_val_compare_and_swap(dst, oldValue, src) != oldValue); (void)order; - return oldValue; + return c89atomic_compare_and_swap_32((c89atomic_uint32*)ptr, 0, 0); + } + static C89ATOMIC_INLINE c89atomic_uint64 c89atomic_load_explicit_64(volatile const c89atomic_uint64* ptr, c89atomic_memory_order order) + { + (void)order; + return c89atomic_compare_and_swap_64((c89atomic_uint64*)ptr, 0, 0); } - #define c89atomic_fetch_add_explicit_8( dst, src, order) __sync_fetch_and_add(dst, src) - #define c89atomic_fetch_add_explicit_16(dst, src, order) __sync_fetch_and_add(dst, src) - #define c89atomic_fetch_add_explicit_32(dst, src, order) __sync_fetch_and_add(dst, src) - #define c89atomic_fetch_add_explicit_64(dst, src, order) __sync_fetch_and_add(dst, src) - #define c89atomic_fetch_sub_explicit_8( dst, src, order) __sync_fetch_and_sub(dst, src) - #define c89atomic_fetch_sub_explicit_16(dst, src, order) __sync_fetch_and_sub(dst, src) - #define c89atomic_fetch_sub_explicit_32(dst, src, order) __sync_fetch_and_sub(dst, src) - #define c89atomic_fetch_sub_explicit_64(dst, src, order) __sync_fetch_and_sub(dst, src) - #define c89atomic_fetch_or_explicit_8( dst, src, order) __sync_fetch_and_or(dst, src) - #define c89atomic_fetch_or_explicit_16(dst, src, order) __sync_fetch_and_or(dst, src) - #define c89atomic_fetch_or_explicit_32(dst, src, order) __sync_fetch_and_or(dst, src) - #define c89atomic_fetch_or_explicit_64(dst, src, order) __sync_fetch_and_or(dst, src) - #define c89atomic_fetch_xor_explicit_8( dst, src, order) __sync_fetch_and_xor(dst, src) - #define c89atomic_fetch_xor_explicit_16(dst, src, order) __sync_fetch_and_xor(dst, src) - #define c89atomic_fetch_xor_explicit_32(dst, src, order) __sync_fetch_and_xor(dst, src) - #define c89atomic_fetch_xor_explicit_64(dst, src, order) __sync_fetch_and_xor(dst, src) - #define c89atomic_fetch_and_explicit_8( dst, src, order) __sync_fetch_and_and(dst, src) - #define c89atomic_fetch_and_explicit_16(dst, src, order) __sync_fetch_and_and(dst, src) - #define c89atomic_fetch_and_explicit_32(dst, src, order) __sync_fetch_and_and(dst, src) - #define c89atomic_fetch_and_explicit_64(dst, src, order) __sync_fetch_and_and(dst, src) - #define c89atomic_compare_and_swap_8( dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define c89atomic_compare_and_swap_16(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define c89atomic_compare_and_swap_32(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define c89atomic_compare_and_swap_64(dst, expected, desired) __sync_val_compare_and_swap(dst, expected, desired) - #define c89atomic_load_explicit_8( ptr, order) c89atomic_compare_and_swap_8 (ptr, 0, 0) - #define c89atomic_load_explicit_16(ptr, order) c89atomic_compare_and_swap_16(ptr, 0, 0) - #define c89atomic_load_explicit_32(ptr, order) c89atomic_compare_and_swap_32(ptr, 0, 0) - #define c89atomic_load_explicit_64(ptr, order) c89atomic_compare_and_swap_64(ptr, 0, 0) #define c89atomic_store_explicit_8( dst, src, order) (void)c89atomic_exchange_explicit_8 (dst, src, order) #define c89atomic_store_explicit_16(dst, src, order) (void)c89atomic_exchange_explicit_16(dst, src, order) #define c89atomic_store_explicit_32(dst, src, order) (void)c89atomic_exchange_explicit_32(dst, src, order) @@ -8196,156 +9607,224 @@ typedef unsigned char c89atomic_flag; #define c89atomic_clear_explicit_16(dst, order) c89atomic_store_explicit_16(dst, 0, order) #define c89atomic_clear_explicit_32(dst, order) c89atomic_store_explicit_32(dst, 0, order) #define c89atomic_clear_explicit_64(dst, order) c89atomic_store_explicit_64(dst, 0, order) - #define c89atomic_flag_test_and_set_explicit(ptr, order) (c89atomic_flag)c89atomic_test_and_set_explicit_8(ptr, order) + typedef c89atomic_uint8 c89atomic_flag; + #define c89atomic_flag_test_and_set_explicit(ptr, order) (c89atomic_bool)c89atomic_test_and_set_explicit_8(ptr, order) #define c89atomic_flag_clear_explicit(ptr, order) c89atomic_clear_explicit_8(ptr, order) + #define c89atoimc_flag_load_explicit(ptr, order) c89atomic_load_explicit_8(ptr, order) #endif #if !defined(C89ATOMIC_HAS_NATIVE_COMPARE_EXCHANGE) -c89atomic_bool c89atomic_compare_exchange_strong_explicit_8(volatile c89atomic_uint8* dst, volatile c89atomic_uint8* expected, c89atomic_uint8 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) -{ - c89atomic_uint8 expectedValue; - c89atomic_uint8 result; - (void)successOrder; - (void)failureOrder; - expectedValue = c89atomic_load_explicit_8(expected, c89atomic_memory_order_seq_cst); - result = c89atomic_compare_and_swap_8(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - c89atomic_store_explicit_8(expected, result, failureOrder); - return 0; - } -} -c89atomic_bool c89atomic_compare_exchange_strong_explicit_16(volatile c89atomic_uint16* dst, volatile c89atomic_uint16* expected, c89atomic_uint16 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) -{ - c89atomic_uint16 expectedValue; - c89atomic_uint16 result; - (void)successOrder; - (void)failureOrder; - expectedValue = c89atomic_load_explicit_16(expected, c89atomic_memory_order_seq_cst); - result = c89atomic_compare_and_swap_16(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - c89atomic_store_explicit_16(expected, result, failureOrder); - return 0; - } -} -c89atomic_bool c89atomic_compare_exchange_strong_explicit_32(volatile c89atomic_uint32* dst, volatile c89atomic_uint32* expected, c89atomic_uint32 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) -{ - c89atomic_uint32 expectedValue; - c89atomic_uint32 result; - (void)successOrder; - (void)failureOrder; - expectedValue = c89atomic_load_explicit_32(expected, c89atomic_memory_order_seq_cst); - result = c89atomic_compare_and_swap_32(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - c89atomic_store_explicit_32(expected, result, failureOrder); - return 0; - } -} -c89atomic_bool c89atomic_compare_exchange_strong_explicit_64(volatile c89atomic_uint64* dst, volatile c89atomic_uint64* expected, c89atomic_uint64 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) -{ - c89atomic_uint64 expectedValue; - c89atomic_uint64 result; - (void)successOrder; - (void)failureOrder; - expectedValue = c89atomic_load_explicit_64(expected, c89atomic_memory_order_seq_cst); - result = c89atomic_compare_and_swap_64(dst, expectedValue, desired); - if (result == expectedValue) { - return 1; - } else { - c89atomic_store_explicit_64(expected, result, failureOrder); - return 0; - } -} -#define c89atomic_compare_exchange_weak_explicit_8( dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_8 (dst, expected, desired, successOrder, failureOrder) -#define c89atomic_compare_exchange_weak_explicit_16(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_16(dst, expected, desired, successOrder, failureOrder) -#define c89atomic_compare_exchange_weak_explicit_32(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_32(dst, expected, desired, successOrder, failureOrder) -#define c89atomic_compare_exchange_weak_explicit_64(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_64(dst, expected, desired, successOrder, failureOrder) + #if defined(C89ATOMIC_HAS_8) + c89atomic_bool c89atomic_compare_exchange_strong_explicit_8(volatile c89atomic_uint8* dst, c89atomic_uint8* expected, c89atomic_uint8 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + c89atomic_uint8 expectedValue; + c89atomic_uint8 result; + (void)successOrder; + (void)failureOrder; + expectedValue = c89atomic_load_explicit_8(expected, c89atomic_memory_order_seq_cst); + result = c89atomic_compare_and_swap_8(dst, expectedValue, desired); + if (result == expectedValue) { + return 1; + } else { + c89atomic_store_explicit_8(expected, result, failureOrder); + return 0; + } + } + #endif + #if defined(C89ATOMIC_HAS_16) + c89atomic_bool c89atomic_compare_exchange_strong_explicit_16(volatile c89atomic_uint16* dst, c89atomic_uint16* expected, c89atomic_uint16 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + c89atomic_uint16 expectedValue; + c89atomic_uint16 result; + (void)successOrder; + (void)failureOrder; + expectedValue = c89atomic_load_explicit_16(expected, c89atomic_memory_order_seq_cst); + result = c89atomic_compare_and_swap_16(dst, expectedValue, desired); + if (result == expectedValue) { + return 1; + } else { + c89atomic_store_explicit_16(expected, result, failureOrder); + return 0; + } + } + #endif + #if defined(C89ATOMIC_HAS_32) + c89atomic_bool c89atomic_compare_exchange_strong_explicit_32(volatile c89atomic_uint32* dst, c89atomic_uint32* expected, c89atomic_uint32 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + c89atomic_uint32 expectedValue; + c89atomic_uint32 result; + (void)successOrder; + (void)failureOrder; + expectedValue = c89atomic_load_explicit_32(expected, c89atomic_memory_order_seq_cst); + result = c89atomic_compare_and_swap_32(dst, expectedValue, desired); + if (result == expectedValue) { + return 1; + } else { + c89atomic_store_explicit_32(expected, result, failureOrder); + return 0; + } + } + #endif + #if defined(C89ATOMIC_HAS_64) + c89atomic_bool c89atomic_compare_exchange_strong_explicit_64(volatile c89atomic_uint64* dst, c89atomic_uint64* expected, c89atomic_uint64 desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + c89atomic_uint64 expectedValue; + c89atomic_uint64 result; + (void)successOrder; + (void)failureOrder; + expectedValue = c89atomic_load_explicit_64(expected, c89atomic_memory_order_seq_cst); + result = c89atomic_compare_and_swap_64(dst, expectedValue, desired); + if (result == expectedValue) { + return 1; + } else { + c89atomic_store_explicit_64(expected, result, failureOrder); + return 0; + } + } + #endif + #define c89atomic_compare_exchange_weak_explicit_8( dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_8 (dst, expected, desired, successOrder, failureOrder) + #define c89atomic_compare_exchange_weak_explicit_16(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_16(dst, expected, desired, successOrder, failureOrder) + #define c89atomic_compare_exchange_weak_explicit_32(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_32(dst, expected, desired, successOrder, failureOrder) + #define c89atomic_compare_exchange_weak_explicit_64(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_64(dst, expected, desired, successOrder, failureOrder) #endif #if !defined(C89ATOMIC_HAS_NATIVE_IS_LOCK_FREE) - #define c89atomic_is_lock_free_8( ptr) 1 - #define c89atomic_is_lock_free_16(ptr) 1 - #define c89atomic_is_lock_free_32(ptr) 1 + static C89ATOMIC_INLINE c89atomic_bool c89atomic_is_lock_free_8(volatile void* ptr) + { + (void)ptr; + return 1; + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_is_lock_free_16(volatile void* ptr) + { + (void)ptr; + return 1; + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_is_lock_free_32(volatile void* ptr) + { + (void)ptr; + return 1; + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_is_lock_free_64(volatile void* ptr) + { + (void)ptr; #if defined(C89ATOMIC_64BIT) - #define c89atomic_is_lock_free_64(ptr) 1 + return 1; #else #if defined(C89ATOMIC_X86) || defined(C89ATOMIC_X64) - #define c89atomic_is_lock_free_64(ptr) 1 + return 1; #else - #define c89atomic_is_lock_free_64(ptr) 0 + return 0; #endif #endif + } #endif #if defined(C89ATOMIC_64BIT) - #define c89atomic_is_lock_free_ptr(ptr) c89atomic_is_lock_free_64((volatile c89atomic_uint64*)ptr) - #define c89atomic_load_explicit_ptr(ptr, order) (void*)c89atomic_load_explicit_64((volatile c89atomic_uint64*)ptr, order) - #define c89atomic_store_explicit_ptr(dst, src, order) (void*)c89atomic_store_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order) - #define c89atomic_exchange_explicit_ptr(dst, src, order) (void*)c89atomic_exchange_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order) - #define c89atomic_compare_exchange_strong_explicit_ptr(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_64((volatile c89atomic_uint64*)dst, (volatile c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder) - #define c89atomic_compare_exchange_weak_explicit_ptr(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_weak_explicit_64((volatile c89atomic_uint64*)dst, (volatile c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder) - #define c89atomic_compare_and_swap_ptr(dst, expected, desired) (void*)c89atomic_compare_and_swap_64 ((volatile c89atomic_uint64*)dst, (c89atomic_uint64)expected, (c89atomic_uint64)desired) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_is_lock_free_ptr(volatile void** ptr) + { + return c89atomic_is_lock_free_64((volatile c89atomic_uint64*)ptr); + } + static C89ATOMIC_INLINE void* c89atomic_load_explicit_ptr(volatile void** ptr, c89atomic_memory_order order) + { + return (void*)c89atomic_load_explicit_64((volatile c89atomic_uint64*)ptr, order); + } + static C89ATOMIC_INLINE void c89atomic_store_explicit_ptr(volatile void** dst, void* src, c89atomic_memory_order order) + { + c89atomic_store_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order); + } + static C89ATOMIC_INLINE void* c89atomic_exchange_explicit_ptr(volatile void** dst, void* src, c89atomic_memory_order order) + { + return (void*)c89atomic_exchange_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)src, order); + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + return c89atomic_compare_exchange_strong_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + return c89atomic_compare_exchange_weak_explicit_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder); + } + static C89ATOMIC_INLINE void* c89atomic_compare_and_swap_ptr(volatile void** dst, void* expected, void* desired) + { + return (void*)c89atomic_compare_and_swap_64((volatile c89atomic_uint64*)dst, (c89atomic_uint64)expected, (c89atomic_uint64)desired); + } #elif defined(C89ATOMIC_32BIT) - #define c89atomic_is_lock_free_ptr(ptr) c89atomic_is_lock_free_32((volatile c89atomic_uint32*)ptr) - #define c89atomic_load_explicit_ptr(ptr, order) (void*)c89atomic_load_explicit_32((volatile c89atomic_uint32*)ptr, order) - #define c89atomic_store_explicit_ptr(dst, src, order) (void*)c89atomic_store_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32)src, order) - #define c89atomic_exchange_explicit_ptr(dst, src, order) (void*)c89atomic_exchange_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32)src, order) - #define c89atomic_compare_exchange_strong_explicit_ptr(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_32((volatile c89atomic_uint32*)dst, (volatile c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder) - #define c89atomic_compare_exchange_weak_explicit_ptr(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_weak_explicit_32((volatile c89atomic_uint32*)dst, (volatile c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder) - #define c89atomic_compare_and_swap_ptr(dst, expected, desired) (void*)c89atomic_compare_and_swap_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32)expected, (c89atomic_uint32)desired) + static C89ATOMIC_INLINE c89atomic_bool c89atomic_is_lock_free_ptr(volatile void** ptr) + { + return c89atomic_is_lock_free_32((volatile c89atomic_uint32*)ptr); + } + static C89ATOMIC_INLINE void* c89atomic_load_explicit_ptr(volatile void** ptr, c89atomic_memory_order order) + { + return (void*)c89atomic_load_explicit_32((volatile c89atomic_uint32*)ptr, order); + } + static C89ATOMIC_INLINE void c89atomic_store_explicit_ptr(volatile void** dst, void* src, c89atomic_memory_order order) + { + c89atomic_store_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32)src, order); + } + static C89ATOMIC_INLINE void* c89atomic_exchange_explicit_ptr(volatile void** dst, void* src, c89atomic_memory_order order) + { + return (void*)c89atomic_exchange_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32)src, order); + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_strong_explicit_ptr(volatile void** dst, void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + return c89atomic_compare_exchange_strong_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); + } + static C89ATOMIC_INLINE c89atomic_bool c89atomic_compare_exchange_weak_explicit_ptr(volatile void** dst, volatile void** expected, void* desired, c89atomic_memory_order successOrder, c89atomic_memory_order failureOrder) + { + return c89atomic_compare_exchange_weak_explicit_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder); + } + static C89ATOMIC_INLINE void* c89atomic_compare_and_swap_ptr(volatile void** dst, void* expected, void* desired) + { + return (void*)c89atomic_compare_and_swap_32((volatile c89atomic_uint32*)dst, (c89atomic_uint32)expected, (c89atomic_uint32)desired); + } #else - error "Unsupported architecture." + #error Unsupported architecture. #endif #define c89atomic_flag_test_and_set(ptr) c89atomic_flag_test_and_set_explicit(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_flag_clear(ptr) c89atomic_flag_clear_explicit(ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_test_and_set_8( ptr) c89atomic_test_and_set_explicit_8 (ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_store_ptr(dst, src) c89atomic_store_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) +#define c89atomic_load_ptr(ptr) c89atomic_load_explicit_ptr((volatile void**)ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_ptr(dst, src) c89atomic_exchange_explicit_ptr((volatile void**)dst, (void*)src, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr((volatile void**)dst, (void*)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr((volatile void**)dst, (void*)expected, (void*)desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_test_and_set_8( ptr) c89atomic_test_and_set_explicit_8( ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_16(ptr) c89atomic_test_and_set_explicit_16(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_32(ptr) c89atomic_test_and_set_explicit_32(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_test_and_set_64(ptr) c89atomic_test_and_set_explicit_64(ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_clear_8( ptr) c89atomic_clear_explicit_8 (ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_clear_8( ptr) c89atomic_clear_explicit_8( ptr, c89atomic_memory_order_seq_cst) #define c89atomic_clear_16(ptr) c89atomic_clear_explicit_16(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_clear_32(ptr) c89atomic_clear_explicit_32(ptr, c89atomic_memory_order_seq_cst) #define c89atomic_clear_64(ptr) c89atomic_clear_explicit_64(ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_store_8( dst, src) c89atomic_store_explicit_8 ( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_store_16( dst, src) c89atomic_store_explicit_16( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_store_32( dst, src) c89atomic_store_explicit_32( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_store_64( dst, src) c89atomic_store_explicit_64( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_store_ptr(dst, src) c89atomic_store_explicit_ptr(dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_load_8( ptr) c89atomic_load_explicit_8 ( ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_load_16( ptr) c89atomic_load_explicit_16( ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_load_32( ptr) c89atomic_load_explicit_32( ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_load_64( ptr) c89atomic_load_explicit_64( ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_load_ptr(ptr) c89atomic_load_explicit_ptr(ptr, c89atomic_memory_order_seq_cst) -#define c89atomic_exchange_8( dst, src) c89atomic_exchange_explicit_8 ( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_exchange_16( dst, src) c89atomic_exchange_explicit_16( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_exchange_32( dst, src) c89atomic_exchange_explicit_32( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_exchange_64( dst, src) c89atomic_exchange_explicit_64( dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_exchange_ptr(dst, src) c89atomic_exchange_explicit_ptr(dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_8( dst, expected, desired) c89atomic_compare_exchange_strong_explicit_8 ( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_16( dst, expected, desired) c89atomic_compare_exchange_strong_explicit_16( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_32( dst, expected, desired) c89atomic_compare_exchange_strong_explicit_32( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_64( dst, expected, desired) c89atomic_compare_exchange_strong_explicit_64( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_strong_ptr(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_ptr(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_8( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_8 ( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_16( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_16( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_32( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_32( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_64( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_64( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_compare_exchange_weak_ptr(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_ptr(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) -#define c89atomic_fetch_add_8( dst, src) c89atomic_fetch_add_explicit_8 (dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_8( dst, src) c89atomic_store_explicit_8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_16(dst, src) c89atomic_store_explicit_16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_32(dst, src) c89atomic_store_explicit_32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_64(dst, src) c89atomic_store_explicit_64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_load_8( ptr) c89atomic_load_explicit_8( ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_16(ptr) c89atomic_load_explicit_16(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_32(ptr) c89atomic_load_explicit_32(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_64(ptr) c89atomic_load_explicit_64(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_8( dst, src) c89atomic_exchange_explicit_8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_16(dst, src) c89atomic_exchange_explicit_16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_32(dst, src) c89atomic_exchange_explicit_32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_64(dst, src) c89atomic_exchange_explicit_64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_8( dst, expected, desired) c89atomic_compare_exchange_strong_explicit_8( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_16(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_16(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_32(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_32(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_64(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_64(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_8( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_8( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_16( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_16(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_32( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_32(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_64( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_64(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_add_8( dst, src) c89atomic_fetch_add_explicit_8( dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_add_16(dst, src) c89atomic_fetch_add_explicit_16(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_add_32(dst, src) c89atomic_fetch_add_explicit_32(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_add_64(dst, src) c89atomic_fetch_add_explicit_64(dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_fetch_sub_8( dst, src) c89atomic_fetch_sub_explicit_8 (dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_sub_8( dst, src) c89atomic_fetch_sub_explicit_8( dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_sub_16(dst, src) c89atomic_fetch_sub_explicit_16(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_sub_32(dst, src) c89atomic_fetch_sub_explicit_32(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_sub_64(dst, src) c89atomic_fetch_sub_explicit_64(dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_fetch_or_8( dst, src) c89atomic_fetch_or_explicit_8 (dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_or_8( dst, src) c89atomic_fetch_or_explicit_8( dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_or_16(dst, src) c89atomic_fetch_or_explicit_16(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_or_32(dst, src) c89atomic_fetch_or_explicit_32(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_or_64(dst, src) c89atomic_fetch_or_explicit_64(dst, src, c89atomic_memory_order_seq_cst) -#define c89atomic_fetch_xor_8( dst, src) c89atomic_fetch_xor_explicit_8 (dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_xor_8( dst, src) c89atomic_fetch_xor_explicit_8( dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_xor_16(dst, src) c89atomic_fetch_xor_explicit_16(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_xor_32(dst, src) c89atomic_fetch_xor_explicit_32(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_xor_64(dst, src) c89atomic_fetch_xor_explicit_64(dst, src, c89atomic_memory_order_seq_cst) @@ -8353,6 +9832,177 @@ c89atomic_bool c89atomic_compare_exchange_strong_explicit_64(volatile c89atomic_ #define c89atomic_fetch_and_16(dst, src) c89atomic_fetch_and_explicit_16(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_and_32(dst, src) c89atomic_fetch_and_explicit_32(dst, src, c89atomic_memory_order_seq_cst) #define c89atomic_fetch_and_64(dst, src) c89atomic_fetch_and_explicit_64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_test_and_set_explicit_i8( ptr, order) (c89atomic_int8 )c89atomic_test_and_set_explicit_8( (c89atomic_uint8* )ptr, order) +#define c89atomic_test_and_set_explicit_i16(ptr, order) (c89atomic_int16)c89atomic_test_and_set_explicit_16((c89atomic_uint16*)ptr, order) +#define c89atomic_test_and_set_explicit_i32(ptr, order) (c89atomic_int32)c89atomic_test_and_set_explicit_32((c89atomic_uint32*)ptr, order) +#define c89atomic_test_and_set_explicit_i64(ptr, order) (c89atomic_int64)c89atomic_test_and_set_explicit_64((c89atomic_uint64*)ptr, order) +#define c89atomic_clear_explicit_i8( ptr, order) c89atomic_clear_explicit_8( (c89atomic_uint8* )ptr, order) +#define c89atomic_clear_explicit_i16(ptr, order) c89atomic_clear_explicit_16((c89atomic_uint16*)ptr, order) +#define c89atomic_clear_explicit_i32(ptr, order) c89atomic_clear_explicit_32((c89atomic_uint32*)ptr, order) +#define c89atomic_clear_explicit_i64(ptr, order) c89atomic_clear_explicit_64((c89atomic_uint64*)ptr, order) +#define c89atomic_store_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_store_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_store_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_store_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_store_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_store_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_store_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_store_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_load_explicit_i8( ptr, order) (c89atomic_int8 )c89atomic_load_explicit_8( (c89atomic_uint8* )ptr, order) +#define c89atomic_load_explicit_i16(ptr, order) (c89atomic_int16)c89atomic_load_explicit_16((c89atomic_uint16*)ptr, order) +#define c89atomic_load_explicit_i32(ptr, order) (c89atomic_int32)c89atomic_load_explicit_32((c89atomic_uint32*)ptr, order) +#define c89atomic_load_explicit_i64(ptr, order) (c89atomic_int64)c89atomic_load_explicit_64((c89atomic_uint64*)ptr, order) +#define c89atomic_exchange_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_exchange_explicit_8 ((c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_exchange_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_exchange_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_exchange_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_exchange_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_exchange_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_exchange_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_compare_exchange_strong_explicit_i8( dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8* )expected, (c89atomic_uint8 )desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_strong_explicit_i16(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16*)expected, (c89atomic_uint16)desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_strong_explicit_i32(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_strong_explicit_i64(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_strong_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_weak_explicit_i8( dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_weak_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8* )expected, (c89atomic_uint8 )desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_weak_explicit_i16(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_weak_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16*)expected, (c89atomic_uint16)desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_weak_explicit_i32(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_weak_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32*)expected, (c89atomic_uint32)desired, successOrder, failureOrder) +#define c89atomic_compare_exchange_weak_explicit_i64(dst, expected, desired, successOrder, failureOrder) c89atomic_compare_exchange_weak_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64*)expected, (c89atomic_uint64)desired, successOrder, failureOrder) +#define c89atomic_fetch_add_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_fetch_add_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_fetch_add_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_fetch_add_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_fetch_add_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_fetch_add_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_fetch_add_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_fetch_add_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_fetch_sub_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_fetch_sub_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_fetch_sub_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_fetch_sub_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_fetch_sub_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_fetch_sub_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_fetch_sub_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_fetch_sub_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_fetch_or_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_fetch_or_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_fetch_or_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_fetch_or_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_fetch_or_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_fetch_or_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_fetch_or_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_fetch_or_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_fetch_xor_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_fetch_xor_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_fetch_xor_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_fetch_xor_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_fetch_xor_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_fetch_xor_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_fetch_xor_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_fetch_xor_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_fetch_and_explicit_i8( dst, src, order) (c89atomic_int8 )c89atomic_fetch_and_explicit_8( (c89atomic_uint8* )dst, (c89atomic_uint8 )src, order) +#define c89atomic_fetch_and_explicit_i16(dst, src, order) (c89atomic_int16)c89atomic_fetch_and_explicit_16((c89atomic_uint16*)dst, (c89atomic_uint16)src, order) +#define c89atomic_fetch_and_explicit_i32(dst, src, order) (c89atomic_int32)c89atomic_fetch_and_explicit_32((c89atomic_uint32*)dst, (c89atomic_uint32)src, order) +#define c89atomic_fetch_and_explicit_i64(dst, src, order) (c89atomic_int64)c89atomic_fetch_and_explicit_64((c89atomic_uint64*)dst, (c89atomic_uint64)src, order) +#define c89atomic_test_and_set_i8( ptr) c89atomic_test_and_set_explicit_i8( ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_test_and_set_i16(ptr) c89atomic_test_and_set_explicit_i16(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_test_and_set_i32(ptr) c89atomic_test_and_set_explicit_i32(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_test_and_set_i64(ptr) c89atomic_test_and_set_explicit_i64(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_clear_i8( ptr) c89atomic_clear_explicit_i8( ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_clear_i16(ptr) c89atomic_clear_explicit_i16(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_clear_i32(ptr) c89atomic_clear_explicit_i32(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_clear_i64(ptr) c89atomic_clear_explicit_i64(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_store_i8( dst, src) c89atomic_store_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_i16(dst, src) c89atomic_store_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_i32(dst, src) c89atomic_store_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_i64(dst, src) c89atomic_store_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_load_i8( ptr) c89atomic_load_explicit_i8( ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_i16(ptr) c89atomic_load_explicit_i16(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_i32(ptr) c89atomic_load_explicit_i32(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_i64(ptr) c89atomic_load_explicit_i64(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_i8( dst, src) c89atomic_exchange_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_i16(dst, src) c89atomic_exchange_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_i32(dst, src) c89atomic_exchange_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_i64(dst, src) c89atomic_exchange_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_i8( dst, expected, desired) c89atomic_compare_exchange_strong_explicit_i8( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_i16(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_i16(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_i32(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_i32(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_strong_i64(dst, expected, desired) c89atomic_compare_exchange_strong_explicit_i64(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_i8( dst, expected, desired) c89atomic_compare_exchange_weak_explicit_i8( dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_i16(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_i16(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_i32(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_i32(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_compare_exchange_weak_i64(dst, expected, desired) c89atomic_compare_exchange_weak_explicit_i64(dst, expected, desired, c89atomic_memory_order_seq_cst, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_add_i8( dst, src) c89atomic_fetch_add_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_add_i16(dst, src) c89atomic_fetch_add_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_add_i32(dst, src) c89atomic_fetch_add_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_add_i64(dst, src) c89atomic_fetch_add_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_sub_i8( dst, src) c89atomic_fetch_sub_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_sub_i16(dst, src) c89atomic_fetch_sub_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_sub_i32(dst, src) c89atomic_fetch_sub_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_sub_i64(dst, src) c89atomic_fetch_sub_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_or_i8( dst, src) c89atomic_fetch_or_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_or_i16(dst, src) c89atomic_fetch_or_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_or_i32(dst, src) c89atomic_fetch_or_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_or_i64(dst, src) c89atomic_fetch_or_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_xor_i8( dst, src) c89atomic_fetch_xor_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_xor_i16(dst, src) c89atomic_fetch_xor_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_xor_i32(dst, src) c89atomic_fetch_xor_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_xor_i64(dst, src) c89atomic_fetch_xor_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_and_i8( dst, src) c89atomic_fetch_and_explicit_i8( dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_and_i16(dst, src) c89atomic_fetch_and_explicit_i16(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_and_i32(dst, src) c89atomic_fetch_and_explicit_i32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_fetch_and_i64(dst, src) c89atomic_fetch_and_explicit_i64(dst, src, c89atomic_memory_order_seq_cst) +typedef union +{ + c89atomic_uint32 i; + float f; +} c89atomic_if32; +typedef union +{ + c89atomic_uint64 i; + double f; +} c89atomic_if64; +#define c89atomic_clear_explicit_f32(ptr, order) c89atomic_clear_explicit_32((c89atomic_uint32*)ptr, order) +#define c89atomic_clear_explicit_f64(ptr, order) c89atomic_clear_explicit_64((c89atomic_uint64*)ptr, order) +static C89ATOMIC_INLINE void c89atomic_store_explicit_f32(volatile float* dst, float src, c89atomic_memory_order order) +{ + c89atomic_if32 x; + x.f = src; + c89atomic_store_explicit_32((volatile c89atomic_uint32*)dst, x.i, order); +} +static C89ATOMIC_INLINE void c89atomic_store_explicit_f64(volatile float* dst, float src, c89atomic_memory_order order) +{ + c89atomic_if64 x; + x.f = src; + c89atomic_store_explicit_64((volatile c89atomic_uint64*)dst, x.i, order); +} +static C89ATOMIC_INLINE float c89atomic_load_explicit_f32(volatile float* ptr, c89atomic_memory_order order) +{ + c89atomic_if32 r; + r.i = c89atomic_load_explicit_32((volatile c89atomic_uint32*)ptr, order); + return r.f; +} +static C89ATOMIC_INLINE double c89atomic_load_explicit_f64(volatile double* ptr, c89atomic_memory_order order) +{ + c89atomic_if64 r; + r.i = c89atomic_load_explicit_64((volatile c89atomic_uint64*)ptr, order); + return r.f; +} +static C89ATOMIC_INLINE float c89atomic_exchange_explicit_f32(volatile float* dst, float src, c89atomic_memory_order order) +{ + c89atomic_if32 r; + c89atomic_if32 x; + x.f = src; + r.i = c89atomic_exchange_explicit_32((volatile c89atomic_uint32*)dst, x.i, order); + return r.f; +} +static C89ATOMIC_INLINE double c89atomic_exchange_explicit_f64(volatile double* dst, double src, c89atomic_memory_order order) +{ + c89atomic_if64 r; + c89atomic_if64 x; + x.f = src; + r.i = c89atomic_exchange_explicit_64((volatile c89atomic_uint64*)dst, x.i, order); + return r.f; +} +#define c89atomic_clear_f32(ptr) (float )c89atomic_clear_explicit_f32(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_clear_f64(ptr) (double)c89atomic_clear_explicit_f64(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_store_f32(dst, src) c89atomic_store_explicit_f32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_store_f64(dst, src) c89atomic_store_explicit_f64(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_load_f32(ptr) (float )c89atomic_load_explicit_f32(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_load_f64(ptr) (double)c89atomic_load_explicit_f64(ptr, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_f32(dst, src) (float )c89atomic_exchange_explicit_f32(dst, src, c89atomic_memory_order_seq_cst) +#define c89atomic_exchange_f64(dst, src) (double)c89atomic_exchange_explicit_f64(dst, src, c89atomic_memory_order_seq_cst) +typedef c89atomic_flag c89atomic_spinlock; +static C89ATOMIC_INLINE void c89atomic_spinlock_lock(volatile c89atomic_spinlock* pSpinlock) +{ + for (;;) { + if (c89atomic_flag_test_and_set_explicit(pSpinlock, c89atomic_memory_order_acquire) == 0) { + break; + } + while (c89atoimc_flag_load_explicit(pSpinlock, c89atomic_memory_order_relaxed) == 1) { + } + } +} +static C89ATOMIC_INLINE void c89atomic_spinlock_unlock(volatile c89atomic_spinlock* pSpinlock) +{ + c89atomic_flag_clear_explicit(pSpinlock, c89atomic_memory_order_release); +} #if defined(__cplusplus) } #endif @@ -8553,18 +10203,18 @@ Threading #endif typedef ma_thread_result (MA_THREADCALL * ma_thread_entry_proc)(void* pData); -static MA_INLINE ma_result ma_spinlock_lock_ex(ma_spinlock* pSpinlock, ma_bool32 yield) +static MA_INLINE ma_result ma_spinlock_lock_ex(volatile ma_spinlock* pSpinlock, ma_bool32 yield) { if (pSpinlock == NULL) { return MA_INVALID_ARGS; } for (;;) { - if (c89atomic_flag_test_and_set_explicit(pSpinlock, c89atomic_memory_order_acquire) == 0) { + if (c89atomic_exchange_explicit_32(pSpinlock, 1, c89atomic_memory_order_acquire) == 0) { break; } - while (c89atomic_load_explicit_8(pSpinlock, c89atomic_memory_order_relaxed) == 1) { + while (c89atomic_load_explicit_32(pSpinlock, c89atomic_memory_order_relaxed) == 1) { if (yield) { ma_yield(); } @@ -8574,23 +10224,23 @@ static MA_INLINE ma_result ma_spinlock_lock_ex(ma_spinlock* pSpinlock, ma_bool32 return MA_SUCCESS; } -MA_API ma_result ma_spinlock_lock(ma_spinlock* pSpinlock) +MA_API ma_result ma_spinlock_lock(volatile ma_spinlock* pSpinlock) { return ma_spinlock_lock_ex(pSpinlock, MA_TRUE); } -MA_API ma_result ma_spinlock_lock_noyield(ma_spinlock* pSpinlock) +MA_API ma_result ma_spinlock_lock_noyield(volatile ma_spinlock* pSpinlock) { return ma_spinlock_lock_ex(pSpinlock, MA_FALSE); } -MA_API ma_result ma_spinlock_unlock(ma_spinlock* pSpinlock) +MA_API ma_result ma_spinlock_unlock(volatile ma_spinlock* pSpinlock) { if (pSpinlock == NULL) { return MA_INVALID_ARGS; } - c89atomic_flag_clear_explicit(pSpinlock, c89atomic_memory_order_release); + c89atomic_store_explicit_32(pSpinlock, 0, c89atomic_memory_order_release); return MA_SUCCESS; } @@ -8624,6 +10274,7 @@ static ma_result ma_thread_create__win32(ma_thread* pThread, ma_thread_priority static void ma_thread_wait__win32(ma_thread* pThread) { WaitForSingleObject((HANDLE)*pThread, INFINITE); + CloseHandle((HANDLE)*pThread); } @@ -8793,8 +10444,6 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority } } } - - pthread_attr_destroy(&attr); } #else /* It's the emscripten build. We'll have a few unused parameters. */ @@ -8803,6 +10452,12 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority #endif result = pthread_create(pThread, pAttr, entryProc, pData); + + /* The thread attributes object is no longer required. */ + if (pAttr != NULL) { + pthread_attr_destroy(pAttr); + } + if (result != 0) { return ma_result_from_errno(result); } @@ -8813,6 +10468,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority static void ma_thread_wait__posix(ma_thread* pThread) { pthread_join(*pThread, NULL); + pthread_detach(*pThread); } @@ -9221,6 +10877,7 @@ MA_API ma_result ma_semaphore_release(ma_semaphore* pSemaphore) #endif /* MA_NO_THREADING */ + /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* @@ -9257,78 +10914,27 @@ not officially supporting this, but I'm leaving it here in case it's useful for /* Disable run-time linking on certain backends. */ #ifndef MA_NO_RUNTIME_LINKING - #if defined(MA_ANDROID) || defined(MA_EMSCRIPTEN) + #if defined(MA_EMSCRIPTEN) #define MA_NO_RUNTIME_LINKING #endif #endif -/* -Check if we have the necessary development packages for each backend at the top so we can use this to determine whether or not -certain unused functions and variables can be excluded from the build to avoid warnings. -*/ -#ifdef MA_ENABLE_WASAPI - #define MA_HAS_WASAPI /* Every compiler should support WASAPI */ -#endif -#ifdef MA_ENABLE_DSOUND - #define MA_HAS_DSOUND /* Every compiler should support DirectSound. */ -#endif -#ifdef MA_ENABLE_WINMM - #define MA_HAS_WINMM /* Every compiler I'm aware of supports WinMM. */ -#endif -#ifdef MA_ENABLE_ALSA - #define MA_HAS_ALSA - #ifdef MA_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MA_HAS_ALSA - #endif - #endif - #endif -#endif -#ifdef MA_ENABLE_PULSEAUDIO - #define MA_HAS_PULSEAUDIO - #ifdef MA_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MA_HAS_PULSEAUDIO - #endif - #endif - #endif -#endif -#ifdef MA_ENABLE_JACK - #define MA_HAS_JACK - #ifdef MA_NO_RUNTIME_LINKING - #ifdef __has_include - #if !__has_include() - #undef MA_HAS_JACK - #endif - #endif - #endif -#endif -#ifdef MA_ENABLE_COREAUDIO - #define MA_HAS_COREAUDIO -#endif -#ifdef MA_ENABLE_SNDIO - #define MA_HAS_SNDIO -#endif -#ifdef MA_ENABLE_AUDIO4 - #define MA_HAS_AUDIO4 -#endif -#ifdef MA_ENABLE_OSS - #define MA_HAS_OSS -#endif -#ifdef MA_ENABLE_AAUDIO - #define MA_HAS_AAUDIO -#endif -#ifdef MA_ENABLE_OPENSL - #define MA_HAS_OPENSL -#endif -#ifdef MA_ENABLE_WEBAUDIO - #define MA_HAS_WEBAUDIO -#endif -#ifdef MA_ENABLE_NULL - #define MA_HAS_NULL /* Everything supports the null backend. */ -#endif + +MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags) +{ + if (pDeviceInfo == NULL) { + return; + } + + if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats)) { + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; + pDeviceInfo->nativeDataFormatCount += 1; + } +} + MA_API const char* ma_get_backend_name(ma_backend backend) { @@ -9347,11 +10953,149 @@ MA_API const char* ma_get_backend_name(ma_backend backend) case ma_backend_aaudio: return "AAudio"; case ma_backend_opensl: return "OpenSL|ES"; case ma_backend_webaudio: return "Web Audio"; + case ma_backend_custom: return "Custom"; case ma_backend_null: return "Null"; default: return "Unknown"; } } +MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend) +{ + /* + This looks a little bit gross, but we want all backends to be included in the switch to avoid warnings on some compilers + about some enums not being handled by the switch statement. + */ + switch (backend) + { + case ma_backend_wasapi: + #if defined(MA_HAS_WASAPI) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_dsound: + #if defined(MA_HAS_DSOUND) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_winmm: + #if defined(MA_HAS_WINMM) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_coreaudio: + #if defined(MA_HAS_COREAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_sndio: + #if defined(MA_HAS_SNDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_audio4: + #if defined(MA_HAS_AUDIO4) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_oss: + #if defined(MA_HAS_OSS) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_pulseaudio: + #if defined(MA_HAS_PULSEAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_alsa: + #if defined(MA_HAS_ALSA) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_jack: + #if defined(MA_HAS_JACK) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_aaudio: + #if defined(MA_HAS_AAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_opensl: + #if defined(MA_HAS_OPENSL) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_webaudio: + #if defined(MA_HAS_WEBAUDIO) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_custom: + #if defined(MA_HAS_CUSTOM) + return MA_TRUE; + #else + return MA_FALSE; + #endif + case ma_backend_null: + #if defined(MA_HAS_NULL) + return MA_TRUE; + #else + return MA_FALSE; + #endif + + default: return MA_FALSE; + } +} + +MA_API ma_result ma_get_enabled_backends(ma_backend* pBackends, size_t backendCap, size_t* pBackendCount) +{ + size_t backendCount; + size_t iBackend; + ma_result result = MA_SUCCESS; + + if (pBackendCount == NULL) { + return MA_INVALID_ARGS; + } + + backendCount = 0; + + for (iBackend = 0; iBackend <= ma_backend_null; iBackend += 1) { + ma_backend backend = (ma_backend)iBackend; + + if (ma_is_backend_enabled(backend)) { + /* The backend is enabled. Try adding it to the list. If there's no room, MA_NO_SPACE needs to be returned. */ + if (backendCount == backendCap) { + result = MA_NO_SPACE; + break; + } else { + pBackends[backendCount] = backend; + backendCount += 1; + } + } + } + + if (pBackendCount != NULL) { + *pBackendCount = backendCount; + } + + return result; +} + MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend) { switch (backend) @@ -9369,6 +11113,7 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend) case ma_backend_aaudio: return MA_FALSE; case ma_backend_opensl: return MA_FALSE; case ma_backend_webaudio: return MA_FALSE; + case ma_backend_custom: return MA_FALSE; /* <-- Will depend on the implementation of the backend. */ case ma_backend_null: return MA_FALSE; default: return MA_FALSE; } @@ -9553,12 +11298,6 @@ typedef LONG (WINAPI * MA_PFN_RegQueryValueExA)(HKEY hKey, LPCSTR lpValueName, L #endif -#define MA_STATE_UNINITIALIZED 0 -#define MA_STATE_STOPPED 1 /* The device's default state after initialization. */ -#define MA_STATE_STARTED 2 /* The worker thread is in it's main loop waiting for the driver to request or deliver audio data. */ -#define MA_STATE_STARTING 3 /* Transitioning from a stopped state to started. */ -#define MA_STATE_STOPPING 4 /* Transitioning from a started state to stopped. */ - #define MA_DEFAULT_PLAYBACK_DEVICE_NAME "Default Playback Device" #define MA_DEFAULT_CAPTURE_DEVICE_NAME "Default Capture Device" @@ -9592,7 +11331,7 @@ static void ma_post_log_message(ma_context* pContext, ma_device* pDevice, ma_uin if (pContext == NULL) { return; } - + #if defined(MA_LOG_LEVEL) if (logLevel <= MA_LOG_LEVEL) { ma_log_proc onLog; @@ -9624,7 +11363,7 @@ int ma_vscprintf(const char* format, va_list args) return -1; } - for (;;) { + for (;;) { char* pNewTempBuffer = (char*)ma_realloc(pTempBuffer, tempBufferCap, NULL); /* TODO: Add support for custom memory allocators? */ if (pNewTempBuffer == NULL) { ma_free(pTempBuffer, NULL); @@ -9636,14 +11375,14 @@ int ma_vscprintf(const char* format, va_list args) result = _vsnprintf(pTempBuffer, tempBufferCap, format, args); ma_free(pTempBuffer, NULL); - + if (result != -1) { break; /* Got it. */ } /* Buffer wasn't big enough. Ideally it'd be nice to use an error code to know the reason for sure, but this is reliable enough. */ tempBufferCap *= 2; - } + } return result; #endif @@ -9653,7 +11392,7 @@ int ma_vscprintf(const char* format, va_list args) /* Posts a formatted log message. */ static void ma_post_log_messagev(ma_context* pContext, ma_device* pDevice, ma_uint32 logLevel, const char* pFormat, va_list args) { -#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && !defined(__STRICT_ANSI__) +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || ((!defined(_MSC_VER) || _MSC_VER >= 1900) && !defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) { char pFormattedMessage[1024]; vsnprintf(pFormattedMessage, sizeof(pFormattedMessage), pFormat, args); @@ -9701,7 +11440,7 @@ static void ma_post_log_messagev(ma_context* pContext, ma_device* pDevice, ma_ui #else vsprintf(pFormattedMessage, pFormat, args); #endif - + ma_post_log_message(pContext, pDevice, logLevel, pFormattedMessage); ma_free(pFormattedMessage, pAllocationCallbacks); } @@ -9747,7 +11486,7 @@ Timing *******************************************************************************/ #ifdef MA_WIN32 - static LARGE_INTEGER g_ma_TimerFrequency = {{0}}; + static LARGE_INTEGER g_ma_TimerFrequency; /* <-- Initialized to zero since it's static. */ static void ma_timer_init(ma_timer* pTimer) { LARGE_INTEGER counter; @@ -9927,12 +11666,12 @@ MA_API ma_proc ma_dlsym(ma_context* pContext, ma_handle handle, const char* symb #ifdef _WIN32 proc = (ma_proc)GetProcAddress((HMODULE)handle, symbol); #else -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" #endif proc = (ma_proc)dlsym((void*)handle, symbol); -#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) #pragma GCC diagnostic pop #endif #endif @@ -9985,8 +11724,8 @@ static ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) static void ma_device__on_data(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount) { float masterVolumeFactor; - - masterVolumeFactor = pDevice->masterVolumeFactor; + + ma_device_get_master_volume(pDevice, &masterVolumeFactor); /* Use ma_device_get_master_volume() to ensure the volume is loaded atomically. */ if (pDevice->onData) { if (!pDevice->noPreZeroedOutputBuffer && pFramesOut != NULL) { @@ -10142,13 +11881,6 @@ static void ma_device__send_frames_to_client(ma_device* pDevice, ma_uint32 frame } } - -/* We only want to expose ma_device__handle_duplex_callback_capture() and ma_device__handle_duplex_callback_playback() if we have an asynchronous backend enabled. */ -#if defined(MA_HAS_JACK) || \ - defined(MA_HAS_COREAUDIO) || \ - defined(MA_HAS_AAUDIO) || \ - defined(MA_HAS_OPENSL) || \ - defined(MA_HAS_WEBAUDIO) static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, ma_uint32 frameCountInDeviceFormat, const void* pFramesInDeviceFormat, ma_pcm_rb* pRB) { ma_result result; @@ -10159,7 +11891,7 @@ static ma_result ma_device__handle_duplex_callback_capture(ma_device* pDevice, m MA_ASSERT(frameCountInDeviceFormat > 0); MA_ASSERT(pFramesInDeviceFormat != NULL); MA_ASSERT(pRB != NULL); - + /* Write to the ring buffer. The ring buffer is in the client format which means we need to convert. */ for (;;) { ma_uint32 framesToProcessInDeviceFormat = (frameCountInDeviceFormat - totalDeviceFramesProcessed); @@ -10219,7 +11951,7 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, MA_ASSERT(frameCount > 0); MA_ASSERT(pFramesInInternalFormat != NULL); MA_ASSERT(pRB != NULL); - + /* Sitting in the ring buffer should be captured data from the capture callback in external format. If there's not enough data in there for the whole frameCount frames we just use silence instead for the input data. @@ -10253,7 +11985,7 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, break; /* Underrun. */ } } - + /* We're done with the captured samples. */ result = ma_pcm_rb_commit_read(pRB, inputFrameCount, pInputFrames); if (result != MA_SUCCESS) { @@ -10283,7 +12015,6 @@ static ma_result ma_device__handle_duplex_callback_playback(ma_device* pDevice, return MA_SUCCESS; } -#endif /* Asynchronous backends. */ /* A helper for changing the state of the device. */ static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_uint32 newState) @@ -10291,12 +12022,6 @@ static MA_INLINE void ma_device__set_state(ma_device* pDevice, ma_uint32 newStat c89atomic_exchange_32(&pDevice->state, newState); } -/* A helper for getting the state of the device. */ -static MA_INLINE ma_uint32 ma_device__get_state(ma_device* pDevice) -{ - return pDevice->state; -} - #ifdef MA_WIN32 GUID MA_GUID_KSDATAFORMAT_SUBTYPE_PCM = {0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; @@ -10306,64 +12031,6 @@ static MA_INLINE ma_uint32 ma_device__get_state(ma_device* pDevice) #endif -typedef struct -{ - ma_device_type deviceType; - const ma_device_id* pDeviceID; - char* pName; - size_t nameBufferSize; - ma_bool32 foundDevice; -} ma_context__try_get_device_name_by_id__enum_callback_data; - -static ma_bool32 ma_context__try_get_device_name_by_id__enum_callback(ma_context* pContext, ma_device_type deviceType, const ma_device_info* pDeviceInfo, void* pUserData) -{ - ma_context__try_get_device_name_by_id__enum_callback_data* pData = (ma_context__try_get_device_name_by_id__enum_callback_data*)pUserData; - MA_ASSERT(pData != NULL); - - if (pData->deviceType == deviceType) { - if (pContext->onDeviceIDEqual(pContext, pData->pDeviceID, &pDeviceInfo->id)) { - ma_strncpy_s(pData->pName, pData->nameBufferSize, pDeviceInfo->name, (size_t)-1); - pData->foundDevice = MA_TRUE; - } - } - - return !pData->foundDevice; -} - -/* -Generic function for retrieving the name of a device by it's ID. - -This function simply enumerates every device and then retrieves the name of the first device that has the same ID. -*/ -static ma_result ma_context__try_get_device_name_by_id(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, char* pName, size_t nameBufferSize) -{ - ma_result result; - ma_context__try_get_device_name_by_id__enum_callback_data data; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(pName != NULL); - - if (pDeviceID == NULL) { - return MA_NO_DEVICE; - } - - data.deviceType = deviceType; - data.pDeviceID = pDeviceID; - data.pName = pName; - data.nameBufferSize = nameBufferSize; - data.foundDevice = MA_FALSE; - result = ma_context_enumerate_devices(pContext, ma_context__try_get_device_name_by_id__enum_callback, &data); - if (result != MA_SUCCESS) { - return result; - } - - if (!data.foundDevice) { - return MA_NO_DEVICE; - } else { - return MA_SUCCESS; - } -} - MA_API ma_uint32 ma_get_format_priority_index(ma_format format) /* Lower = better. */ { @@ -10381,6 +12048,205 @@ MA_API ma_uint32 ma_get_format_priority_index(ma_format format) /* Lower = bette static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type deviceType); +static ma_bool32 ma_device_descriptor_is_valid(const ma_device_descriptor* pDeviceDescriptor) +{ + if (pDeviceDescriptor == NULL) { + return MA_FALSE; + } + + if (pDeviceDescriptor->format == ma_format_unknown) { + return MA_FALSE; + } + + if (pDeviceDescriptor->channels < MA_MIN_CHANNELS || pDeviceDescriptor->channels > MA_MAX_CHANNELS) { + return MA_FALSE; + } + + if (pDeviceDescriptor->sampleRate == 0) { + return MA_FALSE; + } + + return MA_TRUE; +} + + +static ma_result ma_device_audio_thread__default_read_write(ma_device* pDevice) +{ + ma_result result = MA_SUCCESS; + ma_bool32 exitLoop = MA_FALSE; + ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 capturedDeviceDataCapInFrames = 0; + ma_uint32 playbackDeviceDataCapInFrames = 0; + + MA_ASSERT(pDevice != NULL); + + /* Just some quick validation on the device type and the available callbacks. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + if (pDevice->pContext->callbacks.onDeviceRead == NULL) { + return MA_NOT_IMPLEMENTED; + } + + capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + if (pDevice->pContext->callbacks.onDeviceWrite == NULL) { + return MA_NOT_IMPLEMENTED; + } + + playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + } + + /* NOTE: The device was started outside of this function, in the worker thread. */ + + while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + switch (pDevice->type) { + case ma_device_type_duplex: + { + /* The process is: onDeviceRead() -> convert -> callback -> convert -> onDeviceWrite() */ + ma_uint32 totalCapturedDeviceFramesProcessed = 0; + ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); + + while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { + ma_uint32 capturedDeviceFramesRemaining; + ma_uint32 capturedDeviceFramesProcessed; + ma_uint32 capturedDeviceFramesToProcess; + ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; + if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { + capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; + } + + result = pDevice->pContext->callbacks.onDeviceRead(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; + capturedDeviceFramesProcessed = 0; + + /* At this point we have our captured data in device format and we now need to convert it to client format. */ + for (;;) { + ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); + ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; + ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + + /* Convert capture data from device format to client format. */ + result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); + if (result != MA_SUCCESS) { + break; + } + + /* + If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small + which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. + */ + if (capturedClientFramesToProcessThisIteration == 0) { + break; + } + + ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ + + capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ + capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ + + /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ + for (;;) { + ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; + ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); + if (result != MA_SUCCESS) { + break; + } + + result = pDevice->pContext->callbacks.onDeviceWrite(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ + if (capturedClientFramesToProcessThisIteration == 0) { + break; + } + } + + /* In case an error happened from ma_device_write__null()... */ + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + } + + totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; + } + } break; + + case ma_device_type_capture: + case ma_device_type_loopback: + { + ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; + ma_uint32 framesReadThisPeriod = 0; + while (framesReadThisPeriod < periodSizeInFrames) { + ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; + ma_uint32 framesProcessed; + ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; + if (framesToReadThisIteration > capturedDeviceDataCapInFrames) { + framesToReadThisIteration = capturedDeviceDataCapInFrames; + } + + result = pDevice->pContext->callbacks.onDeviceRead(pDevice, capturedDeviceData, framesToReadThisIteration, &framesProcessed); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + ma_device__send_frames_to_client(pDevice, framesProcessed, capturedDeviceData); + + framesReadThisPeriod += framesProcessed; + } + } break; + + case ma_device_type_playback: + { + /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ + ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; + ma_uint32 framesWrittenThisPeriod = 0; + while (framesWrittenThisPeriod < periodSizeInFrames) { + ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; + ma_uint32 framesProcessed; + ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; + if (framesToWriteThisIteration > playbackDeviceDataCapInFrames) { + framesToWriteThisIteration = playbackDeviceDataCapInFrames; + } + + ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, playbackDeviceData); + + result = pDevice->pContext->callbacks.onDeviceWrite(pDevice, playbackDeviceData, framesToWriteThisIteration, &framesProcessed); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + framesWrittenThisPeriod += framesProcessed; + } + } break; + + /* Should never get here. */ + default: break; + } + } + + return result; +} + + + /******************************************************************************* Null Backend @@ -10399,56 +12265,53 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData) MA_ASSERT(pDevice != NULL); for (;;) { /* Keep the thread alive until the device is uninitialized. */ + ma_uint32 operation; + /* Wait for an operation to be requested. */ ma_event_wait(&pDevice->null_device.operationEvent); /* At this point an event should have been triggered. */ + operation = pDevice->null_device.operation; /* Starting the device needs to put the thread into a loop. */ - if (pDevice->null_device.operation == MA_DEVICE_OP_START__NULL) { - c89atomic_exchange_32(&pDevice->null_device.operation, MA_DEVICE_OP_NONE__NULL); - + if (operation == MA_DEVICE_OP_START__NULL) { /* Reset the timer just in case. */ ma_timer_init(&pDevice->null_device.timer); - /* Keep looping until an operation has been requested. */ - while (pDevice->null_device.operation != MA_DEVICE_OP_NONE__NULL && pDevice->null_device.operation != MA_DEVICE_OP_START__NULL) { - ma_sleep(10); /* Don't hog the CPU. */ - } - /* Getting here means a suspend or kill operation has been requested. */ - c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, MA_SUCCESS); + pDevice->null_device.operationResult = MA_SUCCESS; ma_event_signal(&pDevice->null_device.operationCompletionEvent); + ma_semaphore_release(&pDevice->null_device.operationSemaphore); continue; } /* Suspending the device means we need to stop the timer and just continue the loop. */ - if (pDevice->null_device.operation == MA_DEVICE_OP_SUSPEND__NULL) { - c89atomic_exchange_32(&pDevice->null_device.operation, MA_DEVICE_OP_NONE__NULL); - + if (operation == MA_DEVICE_OP_SUSPEND__NULL) { /* We need to add the current run time to the prior run time, then reset the timer. */ pDevice->null_device.priorRunTime += ma_timer_get_time_in_seconds(&pDevice->null_device.timer); ma_timer_init(&pDevice->null_device.timer); /* We're done. */ - c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, MA_SUCCESS); + pDevice->null_device.operationResult = MA_SUCCESS; ma_event_signal(&pDevice->null_device.operationCompletionEvent); + ma_semaphore_release(&pDevice->null_device.operationSemaphore); continue; } /* Killing the device means we need to get out of this loop so that this thread can terminate. */ - if (pDevice->null_device.operation == MA_DEVICE_OP_KILL__NULL) { - c89atomic_exchange_32(&pDevice->null_device.operation, MA_DEVICE_OP_NONE__NULL); - c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, MA_SUCCESS); + if (operation == MA_DEVICE_OP_KILL__NULL) { + pDevice->null_device.operationResult = MA_SUCCESS; ma_event_signal(&pDevice->null_device.operationCompletionEvent); + ma_semaphore_release(&pDevice->null_device.operationSemaphore); break; } /* Getting a signal on a "none" operation probably means an error. Return invalid operation. */ - if (pDevice->null_device.operation == MA_DEVICE_OP_NONE__NULL) { + if (operation == MA_DEVICE_OP_NONE__NULL) { MA_ASSERT(MA_FALSE); /* <-- Trigger this in debug mode to ensure developers are aware they're doing something wrong (or there's a bug in a miniaudio). */ - c89atomic_exchange_32((c89atomic_uint32*)&pDevice->null_device.operationResult, (c89atomic_uint32)MA_INVALID_OPERATION); + pDevice->null_device.operationResult = MA_INVALID_OPERATION; ma_event_signal(&pDevice->null_device.operationCompletionEvent); + ma_semaphore_release(&pDevice->null_device.operationSemaphore); continue; /* Continue the loop. Don't terminate. */ } } @@ -10458,11 +12321,35 @@ static ma_thread_result MA_THREADCALL ma_device_thread__null(void* pData) static ma_result ma_device_do_operation__null(ma_device* pDevice, ma_uint32 operation) { - c89atomic_exchange_32(&pDevice->null_device.operation, operation); + ma_result result; + + /* + TODO: Need to review this and consider just using mutual exclusion. I think the original motivation + for this was to just post the event to a queue and return immediately, but that has since changed + and now this function is synchronous. I think this can be simplified to just use a mutex. + */ + + /* + The first thing to do is wait for an operation slot to become available. We only have a single slot for this, but we could extend this later + to support queing of operations. + */ + result = ma_semaphore_wait(&pDevice->null_device.operationSemaphore); + if (result != MA_SUCCESS) { + return result; /* Failed to wait for the event. */ + } + + /* + When we get here it means the background thread is not referencing the operation code and it can be changed. After changing this we need to + signal an event to the worker thread to let it know that it can start work. + */ + pDevice->null_device.operation = operation; + + /* Once the operation code has been set, the worker thread can start work. */ if (ma_event_signal(&pDevice->null_device.operationEvent) != MA_SUCCESS) { return MA_ERROR; } + /* We want everything to be synchronous so we're going to wait for the worker thread to complete it's operation. */ if (ma_event_wait(&pDevice->null_device.operationCompletionEvent) != MA_SUCCESS) { return MA_ERROR; } @@ -10479,20 +12366,9 @@ static ma_uint64 ma_device_get_total_run_time_in_frames__null(ma_device* pDevice internalSampleRate = pDevice->playback.internalSampleRate; } - return (ma_uint64)((pDevice->null_device.priorRunTime + ma_timer_get_time_in_seconds(&pDevice->null_device.timer)) * internalSampleRate); } -static ma_bool32 ma_context_is_device_id_equal__null(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return pID0->nullbackend == pID1->nullbackend; -} - static ma_result ma_context_enumerate_devices__null(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { ma_bool32 cbResult = MA_TRUE; @@ -10516,13 +12392,13 @@ static ma_result ma_context_enumerate_devices__null(ma_context* pContext, ma_enu cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } + (void)cbResult; /* Silence a static analysis warning. */ + return MA_SUCCESS; } -static ma_result ma_context_get_device_info__null(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__null(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { - ma_uint32 iFormat; - MA_ASSERT(pContext != NULL); if (pDeviceID != NULL && pDeviceID->nullbackend != 0) { @@ -10537,38 +12413,38 @@ static ma_result ma_context_get_device_info__null(ma_context* pContext, ma_devic } /* Support everything on the null backend. */ - pDeviceInfo->formatCount = ma_format_count - 1; /* Minus one because we don't want to include ma_format_unknown. */ - for (iFormat = 0; iFormat < pDeviceInfo->formatCount; ++iFormat) { - pDeviceInfo->formats[iFormat] = (ma_format)(iFormat + 1); /* +1 to skip over ma_format_unknown. */ - } - - pDeviceInfo->minChannels = 1; - pDeviceInfo->maxChannels = MA_MAX_CHANNELS; - pDeviceInfo->minSampleRate = MA_SAMPLE_RATE_8000; - pDeviceInfo->maxSampleRate = MA_SAMPLE_RATE_384000; + pDeviceInfo->nativeDataFormats[0].format = ma_format_unknown; + pDeviceInfo->nativeDataFormats[0].channels = 0; + pDeviceInfo->nativeDataFormats[0].sampleRate = 0; + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormatCount = 1; (void)pContext; - (void)shareMode; return MA_SUCCESS; } -static void ma_device_uninit__null(ma_device* pDevice) +static ma_result ma_device_uninit__null(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); /* Keep it clean and wait for the device thread to finish before returning. */ ma_device_do_operation__null(pDevice, MA_DEVICE_OP_KILL__NULL); + /* Wait for the thread to finish before continuing. */ + ma_thread_wait(&pDevice->null_device.deviceThread); + /* At this point the loop in the device thread is as good as terminated so we can uninitialize our events. */ + ma_semaphore_uninit(&pDevice->null_device.operationSemaphore); ma_event_uninit(&pDevice->null_device.operationCompletionEvent); ma_event_uninit(&pDevice->null_device.operationEvent); + + return MA_SUCCESS; } -static ma_result ma_device_init__null(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__null(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; - ma_uint32 periodSizeInFrames; MA_ASSERT(pDevice != NULL); @@ -10578,26 +12454,29 @@ static ma_result ma_device_init__null(ma_context* pContext, const ma_device_conf return MA_DEVICE_TYPE_NOT_SUPPORTED; } - periodSizeInFrames = pConfig->periodSizeInFrames; - if (periodSizeInFrames == 0) { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, pConfig->sampleRate); + /* The null backend supports everything exactly as we specify it. */ + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + pDescriptorCapture->format = (pDescriptorCapture->format != ma_format_unknown) ? pDescriptorCapture->format : MA_DEFAULT_FORMAT; + pDescriptorCapture->channels = (pDescriptorCapture->channels != 0) ? pDescriptorCapture->channels : MA_DEFAULT_CHANNELS; + pDescriptorCapture->sampleRate = (pDescriptorCapture->sampleRate != 0) ? pDescriptorCapture->sampleRate : MA_DEFAULT_SAMPLE_RATE; + + if (pDescriptorCapture->channelMap[0] == MA_CHANNEL_NONE) { + ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + } + + pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); } - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "NULL Capture Device", (size_t)-1); - pDevice->capture.internalFormat = pConfig->capture.format; - pDevice->capture.internalChannels = pConfig->capture.channels; - ma_channel_map_copy(pDevice->capture.internalChannelMap, pConfig->capture.channelMap, ma_min(pConfig->capture.channels, MA_MAX_CHANNELS)); - pDevice->capture.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->capture.internalPeriods = pConfig->periods; - } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "NULL Playback Device", (size_t)-1); - pDevice->playback.internalFormat = pConfig->playback.format; - pDevice->playback.internalChannels = pConfig->playback.channels; - ma_channel_map_copy(pDevice->playback.internalChannelMap, pConfig->playback.channelMap, ma_min(pConfig->playback.channels, MA_MAX_CHANNELS)); - pDevice->playback.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->playback.internalPeriods = pConfig->periods; + pDescriptorPlayback->format = (pDescriptorPlayback->format != ma_format_unknown) ? pDescriptorPlayback->format : MA_DEFAULT_FORMAT; + pDescriptorPlayback->channels = (pDescriptorPlayback->channels != 0) ? pDescriptorPlayback->channels : MA_DEFAULT_CHANNELS; + pDescriptorPlayback->sampleRate = (pDescriptorPlayback->sampleRate != 0) ? pDescriptorPlayback->sampleRate : MA_DEFAULT_SAMPLE_RATE; + + if (pDescriptorPlayback->channelMap[0] == MA_CHANNEL_NONE) { + ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + } + + pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); } /* @@ -10614,7 +12493,12 @@ static ma_result ma_device_init__null(ma_context* pContext, const ma_device_conf return result; } - result = ma_thread_create(&pDevice->thread, pContext->threadPriority, 0, ma_device_thread__null, pDevice); + result = ma_semaphore_init(1, &pDevice->null_device.operationSemaphore); /* <-- It's important that the initial value is set to 1. */ + if (result != MA_SUCCESS) { + return result; + } + + result = ma_thread_create(&pDevice->null_device.deviceThread, pDevice->pContext->threadPriority, 0, ma_device_thread__null, pDevice); if (result != MA_SUCCESS) { return result; } @@ -10652,7 +12536,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame *pFramesWritten = 0; } - wasStartedOnEntry = pDevice->null_device.isStarted; + wasStartedOnEntry = c89atomic_load_32(&pDevice->null_device.isStarted); /* Keep going until everything has been read. */ totalPCMFramesProcessed = 0; @@ -10678,7 +12562,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame if (pDevice->null_device.currentPeriodFramesRemainingPlayback == 0) { pDevice->null_device.currentPeriodFramesRemainingPlayback = 0; - if (!pDevice->null_device.isStarted && !wasStartedOnEntry) { + if (!c89atomic_load_32(&pDevice->null_device.isStarted) && !wasStartedOnEntry) { result = ma_device_start__null(pDevice); if (result != MA_SUCCESS) { break; @@ -10698,7 +12582,7 @@ static ma_result ma_device_write__null(ma_device* pDevice, const void* pPCMFrame ma_uint64 currentFrame; /* Stop waiting if the device has been stopped. */ - if (!pDevice->null_device.isStarted) { + if (!c89atomic_load_32(&pDevice->null_device.isStarted)) { break; } @@ -10769,7 +12653,7 @@ static ma_result ma_device_read__null(ma_device* pDevice, void* pPCMFrames, ma_u ma_uint64 currentFrame; /* Stop waiting if the device has been stopped. */ - if (!pDevice->null_device.isStarted) { + if (!c89atomic_load_32(&pDevice->null_device.isStarted)) { break; } @@ -10793,180 +12677,6 @@ static ma_result ma_device_read__null(ma_device* pDevice, void* pPCMFrames, ma_u return result; } -static ma_result ma_device_main_loop__null(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - MA_ASSERT(pDevice != NULL); - - /* The capture device needs to be started immediately. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - result = ma_device_start__null(pDevice); - if (result != MA_SUCCESS) { - return result; - } - } - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__null(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - /* At this point we have our captured data in device format and we now need to convert it to client format. */ - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__null(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__null()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__null(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__null(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - - /* Here is where the device is started. */ - ma_device_stop__null(pDevice); - - return result; -} - static ma_result ma_context_uninit__null(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -10976,21 +12686,24 @@ static ma_result ma_context_uninit__null(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__null(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__null(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { MA_ASSERT(pContext != NULL); (void)pConfig; + (void)pContext; - pContext->onUninit = ma_context_uninit__null; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__null; - pContext->onEnumDevices = ma_context_enumerate_devices__null; - pContext->onGetDeviceInfo = ma_context_get_device_info__null; - pContext->onDeviceInit = ma_device_init__null; - pContext->onDeviceUninit = ma_device_uninit__null; - pContext->onDeviceStart = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceStop = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceMainLoop = ma_device_main_loop__null; + pCallbacks->onContextInit = ma_context_init__null; + pCallbacks->onContextUninit = ma_context_uninit__null; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__null; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__null; + pCallbacks->onDeviceInit = ma_device_init__null; + pCallbacks->onDeviceUninit = ma_device_uninit__null; + pCallbacks->onDeviceStart = ma_device_start__null; + pCallbacks->onDeviceStop = ma_device_stop__null; + pCallbacks->onDeviceRead = ma_device_read__null; + pCallbacks->onDeviceWrite = ma_device_write__null; + pCallbacks->onDeviceDataLoop = NULL; /* Our backend is asynchronous with a blocking read-write API which means we can get miniaudio to deal with the audio thread. */ /* The null backend always works. */ return MA_SUCCESS; @@ -11019,7 +12732,7 @@ WIN32 COMMON #define ma_PropVariantClear(pContext, pvar) PropVariantClear(pvar) #endif -#if !defined(MAXULONG_PTR) +#if !defined(MAXULONG_PTR) && !defined(__WATCOMC__) typedef size_t DWORD_PTR; #endif @@ -11280,6 +12993,8 @@ WASAPI Backend #endif #endif /* 0 */ +static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type deviceType); + /* Some compilers don't define VerifyVersionInfoW. Need to write this ourselves. */ #define MA_WIN32_WINNT_VISTA 0x0600 #define MA_VER_MINORVERSION 0x01 @@ -11307,12 +13022,14 @@ typedef ULONGLONG (WINAPI * ma_PFNVerSetConditionMask)(ULONGLONG dwlConditionMas #ifndef PROPERTYKEY_DEFINED #define PROPERTYKEY_DEFINED +#ifndef __WATCOMC__ typedef struct { GUID fmtid; DWORD pid; } PROPERTYKEY; #endif +#endif /* Some compilers don't define PropVariantInit(). We just do this ourselves since it's just a memset(). */ static MA_INLINE void ma_PropVariantInit(PROPVARIANT* pProp) @@ -11325,7 +13042,9 @@ static const PROPERTYKEY MA_PKEY_Device_FriendlyName = {{0xA45C254E, static const PROPERTYKEY MA_PKEY_AudioEngine_DeviceFormat = {{0xF19F064D, 0x82C, 0x4E27, {0xBC, 0x73, 0x68, 0x82, 0xA1, 0xBB, 0x8E, 0x4C}}, 0}; static const IID MA_IID_IUnknown = {0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; /* 00000000-0000-0000-C000-000000000046 */ +#ifndef MA_WIN32_DESKTOP static const IID MA_IID_IAgileObject = {0x94EA2B94, 0xE9CC, 0x49E0, {0xC0, 0xFF, 0xEE, 0x64, 0xCA, 0x8F, 0x5B, 0x90}}; /* 94EA2B94-E9CC-49E0-C0FF-EE64CA8F5B90 */ +#endif static const IID MA_IID_IAudioClient = {0x1CB9AD4C, 0xDBFA, 0x4C32, {0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2}}; /* 1CB9AD4C-DBFA-4C32-B178-C2F568A703B2 = __uuidof(IAudioClient) */ static const IID MA_IID_IAudioClient2 = {0x726778CD, 0xF60A, 0x4EDA, {0x82, 0xDE, 0xE4, 0x76, 0x10, 0xCD, 0x78, 0xAA}}; /* 726778CD-F60A-4EDA-82DE-E47610CD78AA = __uuidof(IAudioClient2) */ @@ -11415,7 +13134,7 @@ typedef enum typedef struct { - UINT32 cbSize; + ma_uint32 cbSize; BOOL bIsOffload; MA_AUDIO_STREAM_CATEGORY eCategory; } ma_AudioClientProperties; @@ -11702,9 +13421,9 @@ typedef struct HRESULT (STDMETHODCALLTYPE * GetBufferSizeLimits)(ma_IAudioClient3* pThis, const WAVEFORMATEX* pFormat, BOOL eventDriven, MA_REFERENCE_TIME* pMinBufferDuration, MA_REFERENCE_TIME* pMaxBufferDuration); /* IAudioClient3 */ - HRESULT (STDMETHODCALLTYPE * GetSharedModeEnginePeriod) (ma_IAudioClient3* pThis, const WAVEFORMATEX* pFormat, UINT32* pDefaultPeriodInFrames, UINT32* pFundamentalPeriodInFrames, UINT32* pMinPeriodInFrames, UINT32* pMaxPeriodInFrames); - HRESULT (STDMETHODCALLTYPE * GetCurrentSharedModeEnginePeriod)(ma_IAudioClient3* pThis, WAVEFORMATEX** ppFormat, UINT32* pCurrentPeriodInFrames); - HRESULT (STDMETHODCALLTYPE * InitializeSharedAudioStream) (ma_IAudioClient3* pThis, DWORD streamFlags, UINT32 periodInFrames, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid); + HRESULT (STDMETHODCALLTYPE * GetSharedModeEnginePeriod) (ma_IAudioClient3* pThis, const WAVEFORMATEX* pFormat, ma_uint32* pDefaultPeriodInFrames, ma_uint32* pFundamentalPeriodInFrames, ma_uint32* pMinPeriodInFrames, ma_uint32* pMaxPeriodInFrames); + HRESULT (STDMETHODCALLTYPE * GetCurrentSharedModeEnginePeriod)(ma_IAudioClient3* pThis, WAVEFORMATEX** ppFormat, ma_uint32* pCurrentPeriodInFrames); + HRESULT (STDMETHODCALLTYPE * InitializeSharedAudioStream) (ma_IAudioClient3* pThis, DWORD streamFlags, ma_uint32 periodInFrames, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGuid); } ma_IAudioClient3Vtbl; struct ma_IAudioClient3 { @@ -11728,9 +13447,9 @@ static MA_INLINE HRESULT ma_IAudioClient3_GetService(ma_IAudioClient3* pThis, co static MA_INLINE HRESULT ma_IAudioClient3_IsOffloadCapable(ma_IAudioClient3* pThis, MA_AUDIO_STREAM_CATEGORY category, BOOL* pOffloadCapable) { return pThis->lpVtbl->IsOffloadCapable(pThis, category, pOffloadCapable); } static MA_INLINE HRESULT ma_IAudioClient3_SetClientProperties(ma_IAudioClient3* pThis, const ma_AudioClientProperties* pProperties) { return pThis->lpVtbl->SetClientProperties(pThis, pProperties); } static MA_INLINE HRESULT ma_IAudioClient3_GetBufferSizeLimits(ma_IAudioClient3* pThis, const WAVEFORMATEX* pFormat, BOOL eventDriven, MA_REFERENCE_TIME* pMinBufferDuration, MA_REFERENCE_TIME* pMaxBufferDuration) { return pThis->lpVtbl->GetBufferSizeLimits(pThis, pFormat, eventDriven, pMinBufferDuration, pMaxBufferDuration); } -static MA_INLINE HRESULT ma_IAudioClient3_GetSharedModeEnginePeriod(ma_IAudioClient3* pThis, const WAVEFORMATEX* pFormat, UINT32* pDefaultPeriodInFrames, UINT32* pFundamentalPeriodInFrames, UINT32* pMinPeriodInFrames, UINT32* pMaxPeriodInFrames) { return pThis->lpVtbl->GetSharedModeEnginePeriod(pThis, pFormat, pDefaultPeriodInFrames, pFundamentalPeriodInFrames, pMinPeriodInFrames, pMaxPeriodInFrames); } -static MA_INLINE HRESULT ma_IAudioClient3_GetCurrentSharedModeEnginePeriod(ma_IAudioClient3* pThis, WAVEFORMATEX** ppFormat, UINT32* pCurrentPeriodInFrames) { return pThis->lpVtbl->GetCurrentSharedModeEnginePeriod(pThis, ppFormat, pCurrentPeriodInFrames); } -static MA_INLINE HRESULT ma_IAudioClient3_InitializeSharedAudioStream(ma_IAudioClient3* pThis, DWORD streamFlags, UINT32 periodInFrames, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGUID) { return pThis->lpVtbl->InitializeSharedAudioStream(pThis, streamFlags, periodInFrames, pFormat, pAudioSessionGUID); } +static MA_INLINE HRESULT ma_IAudioClient3_GetSharedModeEnginePeriod(ma_IAudioClient3* pThis, const WAVEFORMATEX* pFormat, ma_uint32* pDefaultPeriodInFrames, ma_uint32* pFundamentalPeriodInFrames, ma_uint32* pMinPeriodInFrames, ma_uint32* pMaxPeriodInFrames) { return pThis->lpVtbl->GetSharedModeEnginePeriod(pThis, pFormat, pDefaultPeriodInFrames, pFundamentalPeriodInFrames, pMinPeriodInFrames, pMaxPeriodInFrames); } +static MA_INLINE HRESULT ma_IAudioClient3_GetCurrentSharedModeEnginePeriod(ma_IAudioClient3* pThis, WAVEFORMATEX** ppFormat, ma_uint32* pCurrentPeriodInFrames) { return pThis->lpVtbl->GetCurrentSharedModeEnginePeriod(pThis, ppFormat, pCurrentPeriodInFrames); } +static MA_INLINE HRESULT ma_IAudioClient3_InitializeSharedAudioStream(ma_IAudioClient3* pThis, DWORD streamFlags, ma_uint32 periodInFrames, const WAVEFORMATEX* pFormat, const GUID* pAudioSessionGUID) { return pThis->lpVtbl->InitializeSharedAudioStream(pThis, streamFlags, periodInFrames, pFormat, pAudioSessionGUID); } /* IAudioRenderClient */ @@ -11797,7 +13516,7 @@ typedef struct struct ma_completion_handler_uwp { ma_completion_handler_uwp_vtbl* lpVtbl; - ma_uint32 counter; + MA_ATOMIC ma_uint32 counter; HANDLE hEvent; }; @@ -11913,35 +13632,85 @@ static ULONG STDMETHODCALLTYPE ma_IMMNotificationClient_Release(ma_IMMNotificati static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceStateChanged(ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID, DWORD dwNewState) { ma_bool32 isThisDevice = MA_FALSE; + ma_bool32 isCapture = MA_FALSE; + ma_bool32 isPlayback = MA_FALSE; + #ifdef MA_DEBUG_OUTPUT - /*printf("IMMNotificationClient_OnDeviceStateChanged(pDeviceID=%S, dwNewState=%u)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)", (unsigned int)dwNewState);*/ + printf("IMMNotificationClient_OnDeviceStateChanged(pDeviceID=%S, dwNewState=%u)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)", (unsigned int)dwNewState); #endif - if ((dwNewState & MA_MM_DEVICE_STATE_ACTIVE) != 0) { - return S_OK; - } - /* There have been reports of a hang when a playback device is disconnected. The idea with this code is to explicitly stop the device if we detect that the device is disabled or has been unplugged. */ if (pThis->pDevice->wasapi.allowCaptureAutoStreamRouting && (pThis->pDevice->type == ma_device_type_capture || pThis->pDevice->type == ma_device_type_duplex || pThis->pDevice->type == ma_device_type_loopback)) { + isCapture = MA_TRUE; if (wcscmp(pThis->pDevice->capture.id.wasapi, pDeviceID) == 0) { isThisDevice = MA_TRUE; } } if (pThis->pDevice->wasapi.allowPlaybackAutoStreamRouting && (pThis->pDevice->type == ma_device_type_playback || pThis->pDevice->type == ma_device_type_duplex)) { + isPlayback = MA_TRUE; if (wcscmp(pThis->pDevice->playback.id.wasapi, pDeviceID) == 0) { isThisDevice = MA_TRUE; } } + + /* + If the device ID matches our device we need to mark our device as detached and stop it. When a + device is added in OnDeviceAdded(), we'll restart it. We only mark it as detached if the device + was started at the time of being removed. + */ if (isThisDevice) { - ma_device_stop(pThis->pDevice); + if ((dwNewState & MA_MM_DEVICE_STATE_ACTIVE) == 0) { + /* + Unplugged or otherwise unavailable. Mark as detached if we were in a playing state. We'll + use this to determine whether or not we need to automatically start the device when it's + plugged back in again. + */ + if (ma_device_get_state(pThis->pDevice) == MA_STATE_STARTED) { + if (isPlayback) { + pThis->pDevice->wasapi.isDetachedPlayback = MA_TRUE; + } + if (isCapture) { + pThis->pDevice->wasapi.isDetachedCapture = MA_TRUE; + } + + ma_device_stop(pThis->pDevice); + } + } + + if ((dwNewState & MA_MM_DEVICE_STATE_ACTIVE) != 0) { + /* The device was activated. If we were detached, we need to start it again. */ + ma_bool8 tryRestartingDevice = MA_FALSE; + + if (isPlayback) { + if (pThis->pDevice->wasapi.isDetachedPlayback) { + pThis->pDevice->wasapi.isDetachedPlayback = MA_FALSE; + ma_device_reroute__wasapi(pThis->pDevice, ma_device_type_playback); + tryRestartingDevice = MA_TRUE; + } + } + + if (isCapture) { + if (pThis->pDevice->wasapi.isDetachedCapture) { + pThis->pDevice->wasapi.isDetachedCapture = MA_FALSE; + ma_device_reroute__wasapi(pThis->pDevice, (pThis->pDevice->type == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture); + tryRestartingDevice = MA_TRUE; + } + } + + if (tryRestartingDevice) { + if (pThis->pDevice->wasapi.isDetachedPlayback == MA_FALSE && pThis->pDevice->wasapi.isDetachedCapture == MA_FALSE) { + ma_device_start(pThis->pDevice); + } + } + } } - + return S_OK; } @@ -11960,7 +13729,7 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceAdded(ma_IMMNo static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceRemoved(ma_IMMNotificationClient* pThis, LPCWSTR pDeviceID) { #ifdef MA_DEBUG_OUTPUT - /*printf("IMMNotificationClient_OnDeviceRemoved(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)");*/ + printf("IMMNotificationClient_OnDeviceRemoved(pDeviceID=%S)\n", (pDeviceID != NULL) ? pDeviceID : L"(NULL)"); #endif /* We don't need to worry about this event for our purposes. */ @@ -11972,23 +13741,32 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDeviceRemoved(ma_IMM static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged(ma_IMMNotificationClient* pThis, ma_EDataFlow dataFlow, ma_ERole role, LPCWSTR pDefaultDeviceID) { #ifdef MA_DEBUG_OUTPUT - /*printf("IMMNotificationClient_OnDefaultDeviceChanged(dataFlow=%d, role=%d, pDefaultDeviceID=%S)\n", dataFlow, role, (pDefaultDeviceID != NULL) ? pDefaultDeviceID : L"(NULL)");*/ + printf("IMMNotificationClient_OnDefaultDeviceChanged(dataFlow=%d, role=%d, pDefaultDeviceID=%S)\n", dataFlow, role, (pDefaultDeviceID != NULL) ? pDefaultDeviceID : L"(NULL)"); #endif /* We only ever use the eConsole role in miniaudio. */ if (role != ma_eConsole) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Stream rerouting: role != eConsole\n"); + #endif return S_OK; } /* We only care about devices with the same data flow and role as the current device. */ if ((pThis->pDevice->type == ma_device_type_playback && dataFlow != ma_eRender) || (pThis->pDevice->type == ma_device_type_capture && dataFlow != ma_eCapture)) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Stream rerouting abandoned because dataFlow does match device type.\n"); + #endif return S_OK; } /* Don't do automatic stream routing if we're not allowed. */ if ((dataFlow == ma_eRender && pThis->pDevice->wasapi.allowPlaybackAutoStreamRouting == MA_FALSE) || (dataFlow == ma_eCapture && pThis->pDevice->wasapi.allowCaptureAutoStreamRouting == MA_FALSE)) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Stream rerouting abandoned because automatic stream routing has been disabled by the device config.\n"); + #endif return S_OK; } @@ -11999,22 +13777,62 @@ static HRESULT STDMETHODCALLTYPE ma_IMMNotificationClient_OnDefaultDeviceChanged */ if ((dataFlow == ma_eRender && pThis->pDevice->playback.shareMode == ma_share_mode_exclusive) || (dataFlow == ma_eCapture && pThis->pDevice->capture.shareMode == ma_share_mode_exclusive)) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Stream rerouting abandoned because the device shared mode is exclusive.\n"); + #endif return S_OK; } + + + /* - We don't change the device here - we change it in the worker thread to keep synchronization simple. To do this I'm just setting a flag to - indicate that the default device has changed. Loopback devices are treated as capture devices so we need to do a bit of a dance to handle - that properly. + Second attempt at device rerouting. We're going to retrieve the device's state at the time of + the route change. We're then going to stop the device, reinitialize the device, and then start + it again if the state before stopping was MA_STATE_STARTED. */ - if (dataFlow == ma_eRender && pThis->pDevice->type != ma_device_type_loopback) { - c89atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_TRUE); - } - if (dataFlow == ma_eCapture || pThis->pDevice->type == ma_device_type_loopback) { - c89atomic_exchange_32(&pThis->pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_TRUE); + { + ma_uint32 previousState = ma_device_get_state(pThis->pDevice); + ma_bool8 restartDevice = MA_FALSE; + + if (previousState == MA_STATE_STARTED) { + ma_device_stop(pThis->pDevice); + restartDevice = MA_TRUE; + } + + if (pDefaultDeviceID != NULL) { /* <-- The input device ID will be null if there's no other device available. */ + if (dataFlow == ma_eRender) { + ma_device_reroute__wasapi(pThis->pDevice, ma_device_type_playback); + + if (pThis->pDevice->wasapi.isDetachedPlayback) { + pThis->pDevice->wasapi.isDetachedPlayback = MA_FALSE; + + if (pThis->pDevice->type == ma_device_type_duplex && pThis->pDevice->wasapi.isDetachedCapture) { + restartDevice = MA_FALSE; /* It's a duplex device and the capture side is detached. We cannot be restarting the device just yet. */ + } else { + restartDevice = MA_TRUE; /* It's not a duplex device, or the capture side is also attached so we can go ahead and restart the device. */ + } + } + } else { + ma_device_reroute__wasapi(pThis->pDevice, (pThis->pDevice->type == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture); + + if (pThis->pDevice->wasapi.isDetachedCapture) { + pThis->pDevice->wasapi.isDetachedCapture = MA_FALSE; + + if (pThis->pDevice->type == ma_device_type_duplex && pThis->pDevice->wasapi.isDetachedPlayback) { + restartDevice = MA_FALSE; /* It's a duplex device and the playback side is detached. We cannot be restarting the device just yet. */ + } else { + restartDevice = MA_TRUE; /* It's not a duplex device, or the playback side is also attached so we can go ahead and restart the device. */ + } + } + } + + if (restartDevice) { + ma_device_start(pThis->pDevice); + } + } } - (void)pDefaultDeviceID; return S_OK; } @@ -12049,85 +13867,257 @@ typedef ma_IUnknown ma_WASAPIDeviceInterface; #endif +#define MA_CONTEXT_COMMAND_QUIT__WASAPI 1 +#define MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI 2 +#define MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI 3 -static ma_bool32 ma_context_is_device_id_equal__wasapi(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) +static ma_context_command__wasapi ma_context_init_command__wasapi(int code) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; + ma_context_command__wasapi cmd; + + MA_ZERO_OBJECT(&cmd); + cmd.code = code; - return memcmp(pID0->wasapi, pID1->wasapi, sizeof(pID0->wasapi)) == 0; + return cmd; } -static void ma_set_device_info_from_WAVEFORMATEX(const WAVEFORMATEX* pWF, ma_device_info* pInfo) +static ma_result ma_context_post_command__wasapi(ma_context* pContext, const ma_context_command__wasapi* pCmd) +{ + /* For now we are doing everything synchronously, but I might relax this later if the need arises. */ + ma_result result; + ma_bool32 isUsingLocalEvent = MA_FALSE; + ma_event localEvent; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pCmd != NULL); + + if (pCmd->pEvent == NULL) { + isUsingLocalEvent = MA_TRUE; + + result = ma_event_init(&localEvent); + if (result != MA_SUCCESS) { + return result; /* Failed to create the event for this command. */ + } + } + + /* Here is where we add the command to the list. If there's not enough room we'll spin until there is. */ + ma_mutex_lock(&pContext->wasapi.commandLock); + { + ma_uint32 index; + + /* Spin until we've got some space available. */ + while (pContext->wasapi.commandCount == ma_countof(pContext->wasapi.commands)) { + ma_yield(); + } + + /* Space is now available. Can safely add to the list. */ + index = (pContext->wasapi.commandIndex + pContext->wasapi.commandCount) % ma_countof(pContext->wasapi.commands); + pContext->wasapi.commands[index] = *pCmd; + pContext->wasapi.commands[index].pEvent = &localEvent; + pContext->wasapi.commandCount += 1; + + /* Now that the command has been added, release the semaphore so ma_context_next_command__wasapi() can return. */ + ma_semaphore_release(&pContext->wasapi.commandSem); + } + ma_mutex_unlock(&pContext->wasapi.commandLock); + + if (isUsingLocalEvent) { + ma_event_wait(&localEvent); + ma_event_uninit(&localEvent); + } + + return MA_SUCCESS; +} + +static ma_result ma_context_next_command__wasapi(ma_context* pContext, ma_context_command__wasapi* pCmd) +{ + ma_result result = MA_SUCCESS; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pCmd != NULL); + + result = ma_semaphore_wait(&pContext->wasapi.commandSem); + if (result == MA_SUCCESS) { + ma_mutex_lock(&pContext->wasapi.commandLock); + { + *pCmd = pContext->wasapi.commands[pContext->wasapi.commandIndex]; + pContext->wasapi.commandIndex = (pContext->wasapi.commandIndex + 1) % ma_countof(pContext->wasapi.commands); + pContext->wasapi.commandCount -= 1; + } + ma_mutex_unlock(&pContext->wasapi.commandLock); + } + + return result; +} + +static ma_thread_result MA_THREADCALL ma_context_command_thread__wasapi(void* pUserData) +{ + ma_result result; + ma_context* pContext = (ma_context*)pUserData; + MA_ASSERT(pContext != NULL); + + for (;;) { + ma_context_command__wasapi cmd; + result = ma_context_next_command__wasapi(pContext, &cmd); + if (result != MA_SUCCESS) { + break; + } + + switch (cmd.code) + { + case MA_CONTEXT_COMMAND_QUIT__WASAPI: + { + /* Do nothing. Handled after the switch. */ + } break; + + case MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI: + { + if (cmd.data.createAudioClient.deviceType == ma_device_type_playback) { + result = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioRenderClient, cmd.data.createAudioClient.ppAudioClientService)); + } else { + result = ma_result_from_HRESULT(ma_IAudioClient_GetService((ma_IAudioClient*)cmd.data.createAudioClient.pAudioClient, &MA_IID_IAudioCaptureClient, cmd.data.createAudioClient.ppAudioClientService)); + } + } break; + + case MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI: + { + if (cmd.data.releaseAudioClient.deviceType == ma_device_type_playback) { + if (cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback != NULL) { + ma_IAudioClient_Release((ma_IAudioClient*)cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback); + cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientPlayback = NULL; + } + } + + if (cmd.data.releaseAudioClient.deviceType == ma_device_type_capture) { + if (cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture != NULL) { + ma_IAudioClient_Release((ma_IAudioClient*)cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture); + cmd.data.releaseAudioClient.pDevice->wasapi.pAudioClientCapture = NULL; + } + } + } break; + + default: + { + /* Unknown command. Ignore it, but trigger an assert in debug mode so we're aware of it. */ + MA_ASSERT(MA_FALSE); + } break; + } + + if (cmd.pEvent != NULL) { + ma_event_signal(cmd.pEvent); + } + + if (cmd.code == MA_CONTEXT_COMMAND_QUIT__WASAPI) { + break; /* Received a quit message. Get out of here. */ + } + } + + return (ma_thread_result)0; +} + +static ma_result ma_device_create_IAudioClient_service__wasapi(ma_context* pContext, ma_device_type deviceType, ma_IAudioClient* pAudioClient, void** ppAudioClientService) +{ + ma_result result; + ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_CREATE_IAUDIOCLIENT__WASAPI); + cmd.data.createAudioClient.deviceType = deviceType; + cmd.data.createAudioClient.pAudioClient = (void*)pAudioClient; + cmd.data.createAudioClient.ppAudioClientService = ppAudioClientService; + cmd.data.createAudioClient.result = MA_SUCCESS; + + result = ma_context_post_command__wasapi(pContext, &cmd); /* This will not return until the command has actually been run. */ + if (result != MA_SUCCESS) { + return result; + } + + return cmd.data.createAudioClient.result; +} + +#if 0 /* Not used at the moment, but leaving here for future use. */ +static ma_result ma_device_release_IAudioClient_service__wasapi(ma_device* pDevice, ma_device_type deviceType) +{ + ma_result result; + ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_RELEASE_IAUDIOCLIENT__WASAPI); + cmd.data.releaseAudioClient.pDevice = pDevice; + cmd.data.releaseAudioClient.deviceType = deviceType; + + result = ma_context_post_command__wasapi(pDevice->pContext, &cmd); /* This will not return until the command has actually been run. */ + if (result != MA_SUCCESS) { + return result; + } + + return MA_SUCCESS; +} +#endif + + +static void ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(const WAVEFORMATEX* pWF, ma_share_mode shareMode, ma_device_info* pInfo) { MA_ASSERT(pWF != NULL); MA_ASSERT(pInfo != NULL); - pInfo->formatCount = 1; - pInfo->formats[0] = ma_format_from_WAVEFORMATEX(pWF); - pInfo->minChannels = pWF->nChannels; - pInfo->maxChannels = pWF->nChannels; - pInfo->minSampleRate = pWF->nSamplesPerSec; - pInfo->maxSampleRate = pWF->nSamplesPerSec; + if (pInfo->nativeDataFormatCount >= ma_countof(pInfo->nativeDataFormats)) { + return; /* Too many data formats. Need to ignore this one. Don't think this should ever happen with WASAPI. */ + } + + pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].format = ma_format_from_WAVEFORMATEX(pWF); + pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].channels = pWF->nChannels; + pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].sampleRate = pWF->nSamplesPerSec; + pInfo->nativeDataFormats[pInfo->nativeDataFormatCount].flags = (shareMode == ma_share_mode_exclusive) ? MA_DATA_FORMAT_FLAG_EXCLUSIVE_MODE : 0; + pInfo->nativeDataFormatCount += 1; } -static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context* pContext, /*ma_IMMDevice**/void* pMMDevice, ma_IAudioClient* pAudioClient, ma_share_mode shareMode, ma_device_info* pInfo) +static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context* pContext, /*ma_IMMDevice**/void* pMMDevice, ma_IAudioClient* pAudioClient, ma_device_info* pInfo) { + HRESULT hr; + WAVEFORMATEX* pWF = NULL; + MA_ASSERT(pAudioClient != NULL); MA_ASSERT(pInfo != NULL); - /* We use a different technique to retrieve the device information depending on whether or not we are using shared or exclusive mode. */ - if (shareMode == ma_share_mode_shared) { - /* Shared Mode. We use GetMixFormat() here. */ - WAVEFORMATEX* pWF = NULL; - HRESULT hr = ma_IAudioClient_GetMixFormat((ma_IAudioClient*)pAudioClient, (WAVEFORMATEX**)&pWF); - if (SUCCEEDED(hr)) { - ma_set_device_info_from_WAVEFORMATEX(pWF, pInfo); - return MA_SUCCESS; - } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval.", ma_result_from_HRESULT(hr)); - } + /* Shared Mode. We use GetMixFormat() here. */ + hr = ma_IAudioClient_GetMixFormat((ma_IAudioClient*)pAudioClient, (WAVEFORMATEX**)&pWF); + if (SUCCEEDED(hr)) { + ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_shared, pInfo); } else { - /* Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently support on UWP. */ -#ifdef MA_WIN32_DESKTOP + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve mix format for device info retrieval.", ma_result_from_HRESULT(hr)); + } + + /* + Exlcusive Mode. We repeatedly call IsFormatSupported() here. This is not currently supported on + UWP. Failure to retrieve the exclusive mode format is not considered an error, so from here on + out, MA_SUCCESS is guaranteed to be returned. + */ + #ifdef MA_WIN32_DESKTOP + { + ma_IPropertyStore *pProperties; + /* The first thing to do is get the format from PKEY_AudioEngine_DeviceFormat. This should give us a channel count we assume is correct which will simplify our searching. */ - ma_IPropertyStore *pProperties; - HRESULT hr = ma_IMMDevice_OpenPropertyStore((ma_IMMDevice*)pMMDevice, STGM_READ, &pProperties); + hr = ma_IMMDevice_OpenPropertyStore((ma_IMMDevice*)pMMDevice, STGM_READ, &pProperties); if (SUCCEEDED(hr)) { PROPVARIANT var; ma_PropVariantInit(&var); hr = ma_IPropertyStore_GetValue(pProperties, &MA_PKEY_AudioEngine_DeviceFormat, &var); if (SUCCEEDED(hr)) { - WAVEFORMATEX* pWF = (WAVEFORMATEX*)var.blob.pBlobData; - ma_set_device_info_from_WAVEFORMATEX(pWF, pInfo); + pWF = (WAVEFORMATEX*)var.blob.pBlobData; /* In my testing, the format returned by PKEY_AudioEngine_DeviceFormat is suitable for exclusive mode so we check this format first. If this fails, fall back to a search. */ hr = ma_IAudioClient_IsFormatSupported((ma_IAudioClient*)pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, pWF, NULL); - ma_PropVariantClear(pContext, &var); - - if (FAILED(hr)) { + if (SUCCEEDED(hr)) { + /* The format returned by PKEY_AudioEngine_DeviceFormat is supported. */ + ma_add_native_data_format_to_device_info_from_WAVEFORMATEX(pWF, ma_share_mode_exclusive, pInfo); + } else { /* The format returned by PKEY_AudioEngine_DeviceFormat is not supported, so fall back to a search. We assume the channel count returned by MA_PKEY_AudioEngine_DeviceFormat is valid and correct. For simplicity we're only returning one format. */ ma_uint32 channels = pInfo->minChannels; - ma_format formatsToSearch[] = { - ma_format_s16, - ma_format_s24, - /*ma_format_s24_32,*/ - ma_format_f32, - ma_format_s32, - ma_format_u8 - }; ma_channel defaultChannelMap[MA_MAX_CHANNELS]; WAVEFORMATEXTENSIBLE wf; ma_bool32 found; @@ -12147,8 +14137,8 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context wf.dwChannelMask = ma_channel_map_to_channel_mask__win32(defaultChannelMap, channels); found = MA_FALSE; - for (iFormat = 0; iFormat < ma_countof(formatsToSearch); ++iFormat) { - ma_format format = formatsToSearch[iFormat]; + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); ++iFormat) { + ma_format format = g_maFormatPriorities[iFormat]; ma_uint32 iSampleRate; wf.Format.wBitsPerSample = (WORD)(ma_get_bytes_per_sample(format)*8); @@ -12166,7 +14156,7 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context hr = ma_IAudioClient_IsFormatSupported((ma_IAudioClient*)pAudioClient, MA_AUDCLNT_SHAREMODE_EXCLUSIVE, (WAVEFORMATEX*)&wf, NULL); if (SUCCEEDED(hr)) { - ma_set_device_info_from_WAVEFORMATEX((WAVEFORMATEX*)&wf, pInfo); + ma_add_native_data_format_to_device_info_from_WAVEFORMATEX((WAVEFORMATEX*)&wf, ma_share_mode_exclusive, pInfo); found = MA_TRUE; break; } @@ -12177,27 +14167,24 @@ static ma_result ma_context_get_device_info_from_IAudioClient__wasapi(ma_context } } + ma_PropVariantClear(pContext, &var); + if (!found) { - ma_IPropertyStore_Release(pProperties); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to find suitable device format for device info retrieval.", MA_FORMAT_NOT_SUPPORTED); + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to find suitable device format for device info retrieval.", MA_FORMAT_NOT_SUPPORTED); } } } else { - ma_IPropertyStore_Release(pProperties); - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve device format for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to retrieve device format for device info retrieval.", ma_result_from_HRESULT(hr)); } ma_IPropertyStore_Release(pProperties); } else { - return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to open property store for device info retrieval.", ma_result_from_HRESULT(hr)); + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "[WASAPI] Failed to open property store for device info retrieval.", ma_result_from_HRESULT(hr)); } - - return MA_SUCCESS; -#else - /* Exclusive mode not fully supported in UWP right now. */ - return MA_ERROR; -#endif } + #endif + + return MA_SUCCESS; } #ifdef MA_WIN32_DESKTOP @@ -12244,6 +14231,8 @@ static LPWSTR ma_context_get_default_device_id_from_IMMDeviceEnumerator__wasapi( MA_ASSERT(pContext != NULL); MA_ASSERT(pDeviceEnumerator != NULL); + (void)pContext; + /* Grab the EDataFlow type from the device type. */ dataFlow = ma_device_type_to_EDataFlow(deviceType); @@ -12281,7 +14270,7 @@ static LPWSTR ma_context_get_default_device_id__wasapi(ma_context* pContext, ma_ } pDefaultDeviceID = ma_context_get_default_device_id_from_IMMDeviceEnumerator__wasapi(pContext, pDeviceEnumerator, deviceType); - + ma_IMMDeviceEnumerator_Release(pDeviceEnumerator); return pDefaultDeviceID; } @@ -12313,9 +14302,36 @@ static ma_result ma_context_get_MMDevice__wasapi(ma_context* pContext, ma_device return MA_SUCCESS; } -static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pContext, ma_IMMDevice* pMMDevice, ma_share_mode shareMode, LPWSTR pDefaultDeviceID, ma_bool32 onlySimpleInfo, ma_device_info* pInfo) +static ma_result ma_context_get_device_id_from_MMDevice__wasapi(ma_context* pContext, ma_IMMDevice* pMMDevice, ma_device_id* pDeviceID) { - LPWSTR pDeviceID; + LPWSTR pDeviceIDString; + HRESULT hr; + + MA_ASSERT(pDeviceID != NULL); + + hr = ma_IMMDevice_GetId(pMMDevice, &pDeviceIDString); + if (SUCCEEDED(hr)) { + size_t idlen = wcslen(pDeviceIDString); + if (idlen+1 > ma_countof(pDeviceID->wasapi)) { + ma_CoTaskMemFree(pContext, pDeviceIDString); + MA_ASSERT(MA_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. */ + return MA_ERROR; + } + + MA_COPY_MEMORY(pDeviceID->wasapi, pDeviceIDString, idlen * sizeof(wchar_t)); + pDeviceID->wasapi[idlen] = '\0'; + + ma_CoTaskMemFree(pContext, pDeviceIDString); + + return MA_SUCCESS; + } + + return MA_ERROR; +} + +static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pContext, ma_IMMDevice* pMMDevice, LPWSTR pDefaultDeviceID, ma_bool32 onlySimpleInfo, ma_device_info* pInfo) +{ + ma_result result; HRESULT hr; MA_ASSERT(pContext != NULL); @@ -12323,35 +14339,22 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC MA_ASSERT(pInfo != NULL); /* ID. */ - hr = ma_IMMDevice_GetId(pMMDevice, &pDeviceID); - if (SUCCEEDED(hr)) { - size_t idlen = wcslen(pDeviceID); - if (idlen+1 > ma_countof(pInfo->id.wasapi)) { - ma_CoTaskMemFree(pContext, pDeviceID); - MA_ASSERT(MA_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. */ - return MA_ERROR; - } - - MA_COPY_MEMORY(pInfo->id.wasapi, pDeviceID, idlen * sizeof(wchar_t)); - pInfo->id.wasapi[idlen] = '\0'; - + result = ma_context_get_device_id_from_MMDevice__wasapi(pContext, pMMDevice, &pInfo->id); + if (result == MA_SUCCESS) { if (pDefaultDeviceID != NULL) { - if (wcscmp(pDeviceID, pDefaultDeviceID) == 0) { - /* It's a default device. */ - pInfo->_private.isDefault = MA_TRUE; + if (wcscmp(pInfo->id.wasapi, pDefaultDeviceID) == 0) { + pInfo->isDefault = MA_TRUE; } } - - ma_CoTaskMemFree(pContext, pDeviceID); } + /* Description / Friendly Name */ { ma_IPropertyStore *pProperties; hr = ma_IMMDevice_OpenPropertyStore(pMMDevice, STGM_READ, &pProperties); if (SUCCEEDED(hr)) { PROPVARIANT var; - /* Description / Friendly Name */ ma_PropVariantInit(&var); hr = ma_IPropertyStore_GetValue(pProperties, &MA_PKEY_Device_FriendlyName, &var); if (SUCCEEDED(hr)) { @@ -12368,8 +14371,8 @@ static ma_result ma_context_get_device_info_from_MMDevice__wasapi(ma_context* pC ma_IAudioClient* pAudioClient; hr = ma_IMMDevice_Activate(pMMDevice, &MA_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient); if (SUCCEEDED(hr)) { - ma_result result = ma_context_get_device_info_from_IAudioClient__wasapi(pContext, pMMDevice, pAudioClient, shareMode, pInfo); - + result = ma_context_get_device_info_from_IAudioClient__wasapi(pContext, pMMDevice, pAudioClient, pInfo); + ma_IAudioClient_Release(pAudioClient); return result; } else { @@ -12388,7 +14391,7 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte ma_uint32 iDevice; LPWSTR pDefaultDeviceID = NULL; ma_IMMDeviceCollection* pDeviceCollection = NULL; - + MA_ASSERT(pContext != NULL); MA_ASSERT(callback != NULL); @@ -12407,12 +14410,12 @@ static ma_result ma_context_enumerate_devices_by_type__wasapi(ma_context* pConte for (iDevice = 0; iDevice < deviceCount; ++iDevice) { ma_device_info deviceInfo; ma_IMMDevice* pMMDevice; - + MA_ZERO_OBJECT(&deviceInfo); hr = ma_IMMDeviceCollection_Item(pDeviceCollection, iDevice, &pMMDevice); if (SUCCEEDED(hr)) { - result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, ma_share_mode_shared, pDefaultDeviceID, MA_TRUE, &deviceInfo); /* MA_TRUE = onlySimpleInfo. */ + result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, pDefaultDeviceID, MA_TRUE, &deviceInfo); /* MA_TRUE = onlySimpleInfo. */ ma_IMMDevice_Release(pMMDevice); if (result == MA_SUCCESS) { @@ -12570,10 +14573,10 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e #else /* UWP - + 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. - + Hint: DeviceInformation::FindAllAsync() with DeviceClass.AudioCapture/AudioRender. https://blogs.windows.com/buildingapps/2014/05/15/real-time-audio-in-windows-store-and-windows-phone-apps/ */ if (callback) { @@ -12584,7 +14587,7 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); - deviceInfo._private.isDefault = MA_TRUE; + deviceInfo.isDefault = MA_TRUE; cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -12593,7 +14596,7 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); - deviceInfo._private.isDefault = MA_TRUE; + deviceInfo.isDefault = MA_TRUE; cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } } @@ -12602,13 +14605,13 @@ static ma_result ma_context_enumerate_devices__wasapi(ma_context* pContext, ma_e return MA_SUCCESS; } -static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { #ifdef MA_WIN32_DESKTOP ma_result result; ma_IMMDevice* pMMDevice = NULL; LPWSTR pDefaultDeviceID = NULL; - + result = ma_context_get_MMDevice__wasapi(pContext, deviceType, pDeviceID, &pMMDevice); if (result != MA_SUCCESS) { return result; @@ -12617,7 +14620,7 @@ static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_dev /* We need the default device ID so we can set the isDefault flag in the device info. */ pDefaultDeviceID = ma_context_get_default_device_id__wasapi(pContext, deviceType); - result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, shareMode, pDefaultDeviceID, MA_FALSE, pDeviceInfo); /* MA_FALSE = !onlySimpleInfo. */ + result = ma_context_get_device_info_from_MMDevice__wasapi(pContext, pMMDevice, pDefaultDeviceID, MA_FALSE, pDeviceInfo); /* MA_FALSE = !onlySimpleInfo. */ if (pDefaultDeviceID != NULL) { ma_CoTaskMemFree(pContext, pDefaultDeviceID); @@ -12638,26 +14641,21 @@ static ma_result ma_context_get_device_info__wasapi(ma_context* pContext, ma_dev ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } - /* Not currently supporting exclusive mode on UWP. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_ERROR; - } - result = ma_context_get_IAudioClient_UWP__wasapi(pContext, deviceType, pDeviceID, &pAudioClient, NULL); if (result != MA_SUCCESS) { return result; } - result = ma_context_get_device_info_from_IAudioClient__wasapi(pContext, NULL, pAudioClient, shareMode, pDeviceInfo); + result = ma_context_get_device_info_from_IAudioClient__wasapi(pContext, NULL, pAudioClient, pDeviceInfo); - pDeviceInfo->_private.isDefault = MA_TRUE; /* UWP only supports default devices. */ + pDeviceInfo->isDefault = MA_TRUE; /* UWP only supports default devices. */ ma_IAudioClient_Release(pAudioClient); return result; #endif } -static void ma_device_uninit__wasapi(ma_device* pDevice) +static ma_result ma_device_uninit__wasapi(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -12688,6 +14686,8 @@ static void ma_device_uninit__wasapi(ma_device* pDevice) if (pDevice->wasapi.hEventCapture) { CloseHandle(pDevice->wasapi.hEventCapture); } + + return MA_SUCCESS; } @@ -12701,11 +14701,8 @@ typedef struct ma_uint32 periodSizeInFramesIn; ma_uint32 periodSizeInMillisecondsIn; ma_uint32 periodsIn; - ma_bool32 usingDefaultFormat; - ma_bool32 usingDefaultChannels; - ma_bool32 usingDefaultSampleRate; - ma_bool32 usingDefaultChannelMap; ma_share_mode shareMode; + ma_performance_profile performanceProfile; ma_bool32 noAutoConvertSRC; ma_bool32 noDefaultQualitySRC; ma_bool32 noHardwareOffloading; @@ -12722,6 +14719,7 @@ typedef struct ma_uint32 periodsOut; ma_bool32 usingAudioClient3; char deviceName[256]; + ma_device_id id; } ma_device_init_internal_data__wasapi; static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_init_internal_data__wasapi* pData) @@ -12751,10 +14749,10 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device pData->pCaptureClient = NULL; streamFlags = MA_AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - if (!pData->noAutoConvertSRC && !pData->usingDefaultSampleRate && pData->shareMode != ma_share_mode_exclusive) { /* <-- Exclusive streams must use the native sample rate. */ + if (!pData->noAutoConvertSRC && pData->sampleRateIn != 0 && pData->shareMode != ma_share_mode_exclusive) { /* <-- Exclusive streams must use the native sample rate. */ streamFlags |= MA_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM; } - if (!pData->noDefaultQualitySRC && !pData->usingDefaultSampleRate && (streamFlags & MA_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM) != 0) { + if (!pData->noDefaultQualitySRC && pData->sampleRateIn != 0 && (streamFlags & MA_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM) != 0) { streamFlags |= MA_AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; } if (deviceType == ma_device_type_loopback) { @@ -12815,7 +14813,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device I do not know how to query the device's native format on UWP so for now I'm just disabling support for exclusive mode. The alternative is to enumerate over different formats and check IsFormatSupported() until you find one that works. - + TODO: Add support for exclusive mode to UWP. */ hr = S_FALSE; @@ -12855,11 +14853,27 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device */ nativeSampleRate = wf.Format.nSamplesPerSec; if (streamFlags & MA_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM) { - wf.Format.nSamplesPerSec = pData->sampleRateIn; + wf.Format.nSamplesPerSec = (pData->sampleRateIn != 0) ? pData->sampleRateIn : MA_DEFAULT_SAMPLE_RATE; wf.Format.nAvgBytesPerSec = wf.Format.nSamplesPerSec * wf.Format.nBlockAlign; } pData->formatOut = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)&wf); + if (pData->formatOut == ma_format_unknown) { + /* + The format isn't supported. This is almost certainly because the exclusive mode format isn't supported by miniaudio. We need to return MA_SHARE_MODE_NOT_SUPPORTED + in this case so that the caller can detect it and fall back to shared mode if desired. We should never get here if shared mode was requested, but just for + completeness we'll check for it and return MA_FORMAT_NOT_SUPPORTED. + */ + if (shareMode == MA_AUDCLNT_SHAREMODE_EXCLUSIVE) { + result = MA_SHARE_MODE_NOT_SUPPORTED; + } else { + result = MA_FORMAT_NOT_SUPPORTED; + } + + errorMsg = "[WASAPI] Native format not supported."; + goto done; + } + pData->channelsOut = wf.Format.nChannels; pData->sampleRateOut = wf.Format.nSamplesPerSec; @@ -12867,10 +14881,18 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pData->channelsOut, pData->channelMapOut); /* Period size. */ - pData->periodsOut = pData->periodsIn; + pData->periodsOut = (pData->periodsIn != 0) ? pData->periodsIn : MA_DEFAULT_PERIODS; pData->periodSizeInFramesOut = pData->periodSizeInFramesIn; if (pData->periodSizeInFramesOut == 0) { - pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, wf.Format.nSamplesPerSec); + if (pData->periodSizeInMillisecondsIn == 0) { + if (pData->performanceProfile == ma_performance_profile_low_latency) { + pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY, wf.Format.nSamplesPerSec); + } else { + pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE, wf.Format.nSamplesPerSec); + } + } else { + pData->periodSizeInFramesOut = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, wf.Format.nSamplesPerSec); + } } periodDurationInMicroseconds = ((ma_uint64)pData->periodSizeInFramesOut * 1000 * 1000) / wf.Format.nSamplesPerSec; @@ -12902,7 +14924,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device break; } } - + if (hr == MA_AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { ma_uint32 bufferSizeInFrames; hr = ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pData->pAudioClient, &bufferSizeInFrames); @@ -12953,14 +14975,14 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device ma_IAudioClient3* pAudioClient3 = NULL; hr = ma_IAudioClient_QueryInterface(pData->pAudioClient, &MA_IID_IAudioClient3, (void**)&pAudioClient3); if (SUCCEEDED(hr)) { - UINT32 defaultPeriodInFrames; - UINT32 fundamentalPeriodInFrames; - UINT32 minPeriodInFrames; - UINT32 maxPeriodInFrames; + ma_uint32 defaultPeriodInFrames; + ma_uint32 fundamentalPeriodInFrames; + ma_uint32 minPeriodInFrames; + ma_uint32 maxPeriodInFrames; hr = ma_IAudioClient3_GetSharedModeEnginePeriod(pAudioClient3, (WAVEFORMATEX*)&wf, &defaultPeriodInFrames, &fundamentalPeriodInFrames, &minPeriodInFrames, &maxPeriodInFrames); if (SUCCEEDED(hr)) { - UINT32 desiredPeriodInFrames = pData->periodSizeInFramesOut; - UINT32 actualPeriodInFrames = desiredPeriodInFrames; + ma_uint32 desiredPeriodInFrames = pData->periodSizeInFramesOut; + ma_uint32 actualPeriodInFrames = desiredPeriodInFrames; /* Make sure the period size is a multiple of fundamentalPeriodInFrames. */ actualPeriodInFrames = actualPeriodInFrames / fundamentalPeriodInFrames; @@ -12994,7 +15016,7 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device } else { #if defined(MA_DEBUG_OUTPUT) printf("[WASAPI] IAudioClient3_InitializeSharedAudioStream failed. Falling back to IAudioClient.\n"); - #endif + #endif } } else { #if defined(MA_DEBUG_OUTPUT) @@ -13048,20 +15070,22 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device pData->usingAudioClient3 = wasInitializedUsingIAudioClient3; + if (deviceType == ma_device_type_playback) { - hr = ma_IAudioClient_GetService((ma_IAudioClient*)pData->pAudioClient, &MA_IID_IAudioRenderClient, (void**)&pData->pRenderClient); + result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pRenderClient); } else { - hr = ma_IAudioClient_GetService((ma_IAudioClient*)pData->pAudioClient, &MA_IID_IAudioCaptureClient, (void**)&pData->pCaptureClient); + result = ma_device_create_IAudioClient_service__wasapi(pContext, deviceType, (ma_IAudioClient*)pData->pAudioClient, (void**)&pData->pCaptureClient); } - if (FAILED(hr)) { - errorMsg = "[WASAPI] Failed to get audio client service.", result = ma_result_from_HRESULT(hr); + /*if (FAILED(hr)) {*/ + if (result != MA_SUCCESS) { + errorMsg = "[WASAPI] Failed to get audio client service."; goto done; } /* Grab the name of the device. */ -#ifdef MA_WIN32_DESKTOP + #ifdef MA_WIN32_DESKTOP { ma_IPropertyStore *pProperties; hr = ma_IMMDevice_OpenPropertyStore(pDeviceInterface, STGM_READ, &pProperties); @@ -13077,7 +15101,24 @@ static ma_result ma_device_init_internal__wasapi(ma_context* pContext, ma_device ma_IPropertyStore_Release(pProperties); } } -#endif + #endif + + /* + For the WASAPI backend we need to know the actual IDs of the device in order to do automatic + stream routing so that IDs can be compared and we can determine which device has been detached + and whether or not it matches with our ma_device. + */ + #ifdef MA_WIN32_DESKTOP + { + /* Desktop */ + ma_context_get_device_id_from_MMDevice__wasapi(pContext, pDeviceInterface, &pData->id); + } + #else + { + /* UWP */ + /* TODO: Implement me. Need to figure out how to get the ID of the default device. */ + } + #endif done: /* Clean up. */ @@ -13127,29 +15168,59 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev return MA_INVALID_ARGS; } + + /* + Before reinitializing the device we need to free the previous audio clients. + + There's a known memory leak here. We will be calling this from the routing change callback that + is fired by WASAPI. If we attempt to release the IAudioClient we will deadlock. In my opinion + this is a bug. I'm not sure what I need to do to handle this cleanly, but I think we'll probably + need some system where we post an event, but delay the execution of it until the callback has + returned. I'm not sure how to do this reliably, however. I have set up some infrastructure for + a command thread which might be useful for this. + */ + if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { + if (pDevice->wasapi.pCaptureClient) { + ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); + pDevice->wasapi.pCaptureClient = NULL; + } + + if (pDevice->wasapi.pAudioClientCapture) { + /*ma_device_release_IAudioClient_service__wasapi(pDevice, ma_device_type_capture);*/ + pDevice->wasapi.pAudioClientCapture = NULL; + } + } + + if (deviceType == ma_device_type_playback) { + if (pDevice->wasapi.pRenderClient) { + ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); + pDevice->wasapi.pRenderClient = NULL; + } + + if (pDevice->wasapi.pAudioClientPlayback) { + /*ma_device_release_IAudioClient_service__wasapi(pDevice, ma_device_type_playback);*/ + pDevice->wasapi.pAudioClientPlayback = NULL; + } + } + + if (deviceType == ma_device_type_playback) { data.formatIn = pDevice->playback.format; data.channelsIn = pDevice->playback.channels; MA_COPY_MEMORY(data.channelMapIn, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap)); data.shareMode = pDevice->playback.shareMode; - data.usingDefaultFormat = pDevice->playback.usingDefaultFormat; - data.usingDefaultChannels = pDevice->playback.usingDefaultChannels; - data.usingDefaultChannelMap = pDevice->playback.usingDefaultChannelMap; } else { data.formatIn = pDevice->capture.format; data.channelsIn = pDevice->capture.channels; MA_COPY_MEMORY(data.channelMapIn, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap)); data.shareMode = pDevice->capture.shareMode; - data.usingDefaultFormat = pDevice->capture.usingDefaultFormat; - data.usingDefaultChannels = pDevice->capture.usingDefaultChannels; - data.usingDefaultChannelMap = pDevice->capture.usingDefaultChannelMap; } - + data.sampleRateIn = pDevice->sampleRate; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; data.periodSizeInFramesIn = pDevice->wasapi.originalPeriodSizeInFrames; data.periodSizeInMillisecondsIn = pDevice->wasapi.originalPeriodSizeInMilliseconds; data.periodsIn = pDevice->wasapi.originalPeriods; + data.performanceProfile = pDevice->wasapi.originalPerformanceProfile; data.noAutoConvertSRC = pDevice->wasapi.noAutoConvertSRC; data.noDefaultQualitySRC = pDevice->wasapi.noDefaultQualitySRC; data.noHardwareOffloading = pDevice->wasapi.noHardwareOffloading; @@ -13160,16 +15231,6 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev /* At this point we have some new objects ready to go. We need to uninitialize the previous ones and then set the new ones. */ if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { - if (pDevice->wasapi.pCaptureClient) { - ma_IAudioCaptureClient_Release((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient); - pDevice->wasapi.pCaptureClient = NULL; - } - - if (pDevice->wasapi.pAudioClientCapture) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - pDevice->wasapi.pAudioClientCapture = NULL; - } - pDevice->wasapi.pAudioClientCapture = data.pAudioClient; pDevice->wasapi.pCaptureClient = data.pCaptureClient; @@ -13186,26 +15247,11 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture); - /* The device may be in a started state. If so we need to immediately restart it. */ - if (pDevice->wasapi.isStartedCapture) { - HRESULT hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device after reinitialization.", ma_result_from_HRESULT(hr)); - } - } + /* We must always have a valid ID. */ + ma_wcscpy_s(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); } if (deviceType == ma_device_type_playback) { - if (pDevice->wasapi.pRenderClient) { - ma_IAudioRenderClient_Release((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient); - pDevice->wasapi.pRenderClient = NULL; - } - - if (pDevice->wasapi.pAudioClientPlayback) { - ma_IAudioClient_Release((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - pDevice->wasapi.pAudioClientPlayback = NULL; - } - pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; pDevice->wasapi.pRenderClient = data.pRenderClient; @@ -13222,36 +15268,28 @@ static ma_result ma_device_reinit__wasapi(ma_device* pDevice, ma_device_type dev pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback); - /* The device may be in a started state. If so we need to immediately restart it. */ - if (pDevice->wasapi.isStartedPlayback) { - HRESULT hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device after reinitialization.", ma_result_from_HRESULT(hr)); - } - } + /* We must always have a valid ID. */ + ma_wcscpy_s(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); } return MA_SUCCESS; } -static ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__wasapi(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result = MA_SUCCESS; + +#ifdef MA_WIN32_DESKTOP HRESULT hr; ma_IMMDeviceEnumerator* pDeviceEnumerator; +#endif - (void)pContext; - - MA_ASSERT(pContext != NULL); MA_ASSERT(pDevice != NULL); MA_ZERO_OBJECT(&pDevice->wasapi); - pDevice->wasapi.originalPeriodSizeInFrames = pConfig->periodSizeInFrames; - pDevice->wasapi.originalPeriodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; - pDevice->wasapi.originalPeriods = pConfig->periods; - pDevice->wasapi.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; - pDevice->wasapi.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; - pDevice->wasapi.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; + pDevice->wasapi.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; + pDevice->wasapi.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; + pDevice->wasapi.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; /* Exclusive mode is not allowed with loopback. */ if (pConfig->deviceType == ma_device_type_loopback && pConfig->playback.shareMode == ma_share_mode_exclusive) { @@ -13260,37 +15298,30 @@ static ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_co if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { ma_device_init_internal_data__wasapi data; - data.formatIn = pConfig->capture.format; - data.channelsIn = pConfig->capture.channels; - data.sampleRateIn = pConfig->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pConfig->capture.channelMap, sizeof(pConfig->capture.channelMap)); - data.usingDefaultFormat = pDevice->capture.usingDefaultFormat; - data.usingDefaultChannels = pDevice->capture.usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->capture.usingDefaultChannelMap; - data.shareMode = pConfig->capture.shareMode; - data.periodSizeInFramesIn = pConfig->periodSizeInFrames; - data.periodSizeInMillisecondsIn = pConfig->periodSizeInMilliseconds; - data.periodsIn = pConfig->periods; + data.formatIn = pDescriptorCapture->format; + data.channelsIn = pDescriptorCapture->channels; + data.sampleRateIn = pDescriptorCapture->sampleRate; + MA_COPY_MEMORY(data.channelMapIn, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); + data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; + data.periodSizeInMillisecondsIn = pDescriptorCapture->periodSizeInMilliseconds; + data.periodsIn = pDescriptorCapture->periodCount; + data.shareMode = pDescriptorCapture->shareMode; + data.performanceProfile = pConfig->performanceProfile; data.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; data.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; data.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; - result = ma_device_init_internal__wasapi(pDevice->pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture, pConfig->capture.pDeviceID, &data); + result = ma_device_init_internal__wasapi(pDevice->pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture, pDescriptorCapture->pDeviceID, &data); if (result != MA_SUCCESS) { return result; } - pDevice->wasapi.pAudioClientCapture = data.pAudioClient; - pDevice->wasapi.pCaptureClient = data.pCaptureClient; - - pDevice->capture.internalFormat = data.formatOut; - pDevice->capture.internalChannels = data.channelsOut; - pDevice->capture.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->capture.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->capture.internalPeriods = data.periodsOut; - ma_strcpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), data.deviceName); + pDevice->wasapi.pAudioClientCapture = data.pAudioClient; + pDevice->wasapi.pCaptureClient = data.pCaptureClient; + pDevice->wasapi.originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; + pDevice->wasapi.originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDevice->wasapi.originalPeriods = pDescriptorCapture->periodCount; + pDevice->wasapi.originalPerformanceProfile = pConfig->performanceProfile; /* The event for capture needs to be manual reset for the same reason as playback. We keep the initial state set to unsignaled, @@ -13315,27 +15346,35 @@ static ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_co pDevice->wasapi.periodSizeInFramesCapture = data.periodSizeInFramesOut; ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &pDevice->wasapi.actualPeriodSizeInFramesCapture); + + /* We must always have a valid ID. */ + ma_wcscpy_s(pDevice->capture.id.wasapi, sizeof(pDevice->capture.id.wasapi), data.id.wasapi); + + /* The descriptor needs to be updated with actual values. */ + pDescriptorCapture->format = data.formatOut; + pDescriptorCapture->channels = data.channelsOut; + pDescriptorCapture->sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(pDescriptorCapture->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDescriptorCapture->periodSizeInFrames = data.periodSizeInFramesOut; + pDescriptorCapture->periodCount = data.periodsOut; } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ma_device_init_internal_data__wasapi data; - data.formatIn = pConfig->playback.format; - data.channelsIn = pConfig->playback.channels; - data.sampleRateIn = pConfig->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pConfig->playback.channelMap, sizeof(pConfig->playback.channelMap)); - data.usingDefaultFormat = pDevice->playback.usingDefaultFormat; - data.usingDefaultChannels = pDevice->playback.usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->playback.usingDefaultChannelMap; - data.shareMode = pConfig->playback.shareMode; - data.periodSizeInFramesIn = pConfig->periodSizeInFrames; - data.periodSizeInMillisecondsIn = pConfig->periodSizeInMilliseconds; - data.periodsIn = pConfig->periods; + data.formatIn = pDescriptorPlayback->format; + data.channelsIn = pDescriptorPlayback->channels; + data.sampleRateIn = pDescriptorPlayback->sampleRate; + MA_COPY_MEMORY(data.channelMapIn, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); + data.periodSizeInFramesIn = pDescriptorPlayback->periodSizeInFrames; + data.periodSizeInMillisecondsIn = pDescriptorPlayback->periodSizeInMilliseconds; + data.periodsIn = pDescriptorPlayback->periodCount; + data.shareMode = pDescriptorPlayback->shareMode; + data.performanceProfile = pConfig->performanceProfile; data.noAutoConvertSRC = pConfig->wasapi.noAutoConvertSRC; data.noDefaultQualitySRC = pConfig->wasapi.noDefaultQualitySRC; data.noHardwareOffloading = pConfig->wasapi.noHardwareOffloading; - result = ma_device_init_internal__wasapi(pDevice->pContext, ma_device_type_playback, pConfig->playback.pDeviceID, &data); + result = ma_device_init_internal__wasapi(pDevice->pContext, ma_device_type_playback, pDescriptorPlayback->pDeviceID, &data); if (result != MA_SUCCESS) { if (pConfig->deviceType == ma_device_type_duplex) { if (pDevice->wasapi.pCaptureClient != NULL) { @@ -13353,16 +15392,12 @@ static ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_co return result; } - pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; - pDevice->wasapi.pRenderClient = data.pRenderClient; - - pDevice->playback.internalFormat = data.formatOut; - pDevice->playback.internalChannels = data.channelsOut; - pDevice->playback.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->playback.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->playback.internalPeriods = data.periodsOut; - ma_strcpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), data.deviceName); + pDevice->wasapi.pAudioClientPlayback = data.pAudioClient; + pDevice->wasapi.pRenderClient = data.pRenderClient; + pDevice->wasapi.originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; + pDevice->wasapi.originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDevice->wasapi.originalPeriods = pDescriptorPlayback->periodCount; + pDevice->wasapi.originalPerformanceProfile = pConfig->performanceProfile; /* The event for playback is needs to be manual reset because we want to explicitly control the fact that it becomes signalled @@ -13404,6 +15439,17 @@ static ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_co pDevice->wasapi.periodSizeInFramesPlayback = data.periodSizeInFramesOut; ma_IAudioClient_GetBufferSize((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &pDevice->wasapi.actualPeriodSizeInFramesPlayback); + + /* We must always have a valid ID. */ + ma_wcscpy_s(pDevice->playback.id.wasapi, sizeof(pDevice->playback.id.wasapi), data.id.wasapi); + + /* The descriptor needs to be updated with actual values. */ + pDescriptorPlayback->format = data.formatOut; + pDescriptorPlayback->channels = data.channelsOut; + pDescriptorPlayback->sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(pDescriptorPlayback->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDescriptorPlayback->periodSizeInFrames = data.periodSizeInFramesOut; + pDescriptorPlayback->periodCount = data.periodsOut; } /* @@ -13421,7 +15467,7 @@ static ma_result ma_device_init__wasapi(ma_context* pContext, const ma_device_co } } - hr = ma_CoCreateInstance(pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); + hr = ma_CoCreateInstance(pDevice->pContext, MA_CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, MA_IID_IMMDeviceEnumerator, (void**)&pDeviceEnumerator); if (FAILED(hr)) { ma_device_uninit__wasapi(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to create device enumerator.", ma_result_from_HRESULT(hr)); @@ -13454,47 +15500,53 @@ static ma_result ma_device__get_available_frames__wasapi(ma_device* pDevice, ma_ MA_ASSERT(pDevice != NULL); MA_ASSERT(pFrameCount != NULL); - + *pFrameCount = 0; if ((ma_ptr)pAudioClient != pDevice->wasapi.pAudioClientPlayback && (ma_ptr)pAudioClient != pDevice->wasapi.pAudioClientCapture) { return MA_INVALID_OPERATION; } - hr = ma_IAudioClient_GetCurrentPadding(pAudioClient, &paddingFramesCount); - if (FAILED(hr)) { - return ma_result_from_HRESULT(hr); - } + /* + I've had a report that GetCurrentPadding() is returning a frame count of 0 which is preventing + higher level function calls from doing anything because it thinks nothing is available. I have + taken a look at the documentation and it looks like this is unnecessary in exclusive mode. - /* Slightly different rules for exclusive and shared modes. */ + From Microsoft's documentation: + + For an exclusive-mode rendering or capture stream that was initialized with the + AUDCLNT_STREAMFLAGS_EVENTCALLBACK flag, the client typically has no use for the padding + value reported by GetCurrentPadding. Instead, the client accesses an entire buffer during + each processing pass. + + Considering this, I'm going to skip GetCurrentPadding() for exclusive mode and just report the + entire buffer. This depends on the caller making sure they wait on the event handler. + */ shareMode = ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) ? pDevice->playback.shareMode : pDevice->capture.shareMode; - if (shareMode == ma_share_mode_exclusive) { - *pFrameCount = paddingFramesCount; - } else { + if (shareMode == ma_share_mode_shared) { + /* Shared mode. */ + hr = ma_IAudioClient_GetCurrentPadding(pAudioClient, &paddingFramesCount); + if (FAILED(hr)) { + return ma_result_from_HRESULT(hr); + } + if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesPlayback - paddingFramesCount; } else { *pFrameCount = paddingFramesCount; } + } else { + /* Exclusive mode. */ + if ((ma_ptr)pAudioClient == pDevice->wasapi.pAudioClientPlayback) { + *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesPlayback; + } else { + *pFrameCount = pDevice->wasapi.actualPeriodSizeInFramesCapture; + } } return MA_SUCCESS; } -static ma_bool32 ma_device_is_reroute_required__wasapi(ma_device* pDevice, ma_device_type deviceType) -{ - MA_ASSERT(pDevice != NULL); - - if (deviceType == ma_device_type_playback) { - return pDevice->wasapi.hasDefaultPlaybackDeviceChanged; - } - - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { - return pDevice->wasapi.hasDefaultCaptureDeviceChanged; - } - - return MA_FALSE; -} static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type deviceType) { @@ -13504,20 +15556,15 @@ static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type de return MA_INVALID_ARGS; } - if (deviceType == ma_device_type_playback) { - c89atomic_exchange_32(&pDevice->wasapi.hasDefaultPlaybackDeviceChanged, MA_FALSE); - } - if (deviceType == ma_device_type_capture || deviceType == ma_device_type_loopback) { - c89atomic_exchange_32(&pDevice->wasapi.hasDefaultCaptureDeviceChanged, MA_FALSE); - } - - - #ifdef MA_DEBUG_OUTPUT - printf("=== CHANGING DEVICE ===\n"); - #endif +#ifdef MA_DEBUG_OUTPUT + printf("=== CHANGING DEVICE ===\n"); +#endif result = ma_device_reinit__wasapi(pDevice, deviceType); if (result != MA_SUCCESS) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Reinitializing device after route change failed.\n"); + #endif return result; } @@ -13526,565 +15573,36 @@ static ma_result ma_device_reroute__wasapi(ma_device* pDevice, ma_device_type de return MA_SUCCESS; } - -static ma_result ma_device_stop__wasapi(ma_device* pDevice) +static ma_result ma_device_start__wasapi(ma_device* pDevice) { + HRESULT hr; + MA_ASSERT(pDevice != NULL); - /* - It's possible for the main loop to get stuck if the device is disconnected. - - In loopback mode it's possible for WaitForSingleObject() to get stuck in a deadlock when nothing is being played. When nothing - is being played, the event is never signalled internally by WASAPI which means we will deadlock when stopping the device. - */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_duplex) { - SetEvent((HANDLE)pDevice->wasapi.hEventCapture); + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + if (FAILED(hr)) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr)); + } + + c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - SetEvent((HANDLE)pDevice->wasapi.hEventPlayback); + /* No need to do anything for playback as that'll be started automatically in the data loop. */ } return MA_SUCCESS; } - -static ma_result ma_device_main_loop__wasapi(ma_device* pDevice) +static ma_result ma_device_stop__wasapi(ma_device* pDevice) { ma_result result; HRESULT hr; - ma_bool32 exitLoop = MA_FALSE; - ma_uint32 framesWrittenToPlaybackDevice = 0; - ma_uint32 mappedDeviceBufferSizeInFramesCapture = 0; - ma_uint32 mappedDeviceBufferSizeInFramesPlayback = 0; - ma_uint32 mappedDeviceBufferFramesRemainingCapture = 0; - ma_uint32 mappedDeviceBufferFramesRemainingPlayback = 0; - BYTE* pMappedDeviceBufferCapture = NULL; - BYTE* pMappedDeviceBufferPlayback = NULL; - ma_uint32 bpfCaptureDevice = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 bpfPlaybackDevice = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 bpfCaptureClient = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 bpfPlaybackClient = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint8 inputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 inputDataInClientFormatCap = sizeof(inputDataInClientFormat) / bpfCaptureClient; - ma_uint8 outputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 outputDataInClientFormatCap = sizeof(outputDataInClientFormat) / bpfPlaybackClient; - ma_uint32 outputDataInClientFormatCount = 0; - ma_uint32 outputDataInClientFormatConsumed = 0; - ma_uint32 periodSizeInFramesCapture = 0; MA_ASSERT(pDevice != NULL); - /* The capture device needs to be started immediately. */ if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - periodSizeInFramesCapture = pDevice->capture.internalPeriodSizeInFrames; - - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - if (FAILED(hr)) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal capture device.", ma_result_from_HRESULT(hr)); - } - c89atomic_exchange_32(&pDevice->wasapi.isStartedCapture, MA_TRUE); - } - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - /* We may need to reroute the device. */ - if (ma_device_is_reroute_required__wasapi(pDevice, ma_device_type_playback)) { - result = ma_device_reroute__wasapi(pDevice, ma_device_type_playback); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - if (ma_device_is_reroute_required__wasapi(pDevice, ma_device_type_capture)) { - result = ma_device_reroute__wasapi(pDevice, (pDevice->type == ma_device_type_loopback) ? ma_device_type_loopback : ma_device_type_capture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - switch (pDevice->type) - { - case ma_device_type_duplex: - { - ma_uint32 framesAvailableCapture; - ma_uint32 framesAvailablePlayback; - DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ - - /* The process is to map the playback buffer and fill it as quickly as possible from input data. */ - if (pMappedDeviceBufferPlayback == NULL) { - /* WASAPI is weird with exclusive mode. You need to wait on the event _before_ querying the available frames. */ - if (pDevice->playback.shareMode == ma_share_mode_exclusive) { - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - return result; - } - - /*printf("TRACE 1: framesAvailablePlayback=%d\n", framesAvailablePlayback);*/ - - - /* In exclusive mode, the frame count needs to exactly match the value returned by GetCurrentPadding(). */ - if (pDevice->playback.shareMode != ma_share_mode_exclusive) { - if (framesAvailablePlayback > pDevice->wasapi.periodSizeInFramesPlayback) { - framesAvailablePlayback = pDevice->wasapi.periodSizeInFramesPlayback; - } - } - - /* If there's no frames available in the playback device we need to wait for more. */ - if (framesAvailablePlayback == 0) { - /* In exclusive mode we waited at the top. */ - if (pDevice->playback.shareMode != ma_share_mode_exclusive) { - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - continue; - } - - /* We're ready to map the playback device's buffer. We don't release this until it's been entirely filled. */ - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - mappedDeviceBufferSizeInFramesPlayback = framesAvailablePlayback; - mappedDeviceBufferFramesRemainingPlayback = framesAvailablePlayback; - } - - /* At this point we should have a buffer available for output. We need to keep writing input samples to it. */ - for (;;) { - /* Try grabbing some captured data if we haven't already got a mapped buffer. */ - if (pMappedDeviceBufferCapture == NULL) { - if (pDevice->capture.shareMode == ma_share_mode_shared) { - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - /*printf("TRACE 2: framesAvailableCapture=%d\n", framesAvailableCapture);*/ - - /* Wait for more if nothing is available. */ - if (framesAvailableCapture == 0) { - /* In exclusive mode we waited at the top. */ - if (pDevice->capture.shareMode != ma_share_mode_shared) { - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE) != WAIT_OBJECT_0) { - return MA_ERROR; /* Wait failed. */ - } - } - - continue; - } - - /* Getting here means there's data available for writing to the output device. */ - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - - /* Overrun detection. */ - if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - /* Glitched. Probably due to an overrun. */ - #ifdef MA_DEBUG_OUTPUT - printf("[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - #endif - - /* - Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment - by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the - last period. - */ - if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { - #ifdef MA_DEBUG_OUTPUT - printf("[WASAPI] Synchronizing capture stream. "); - #endif - do - { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - break; - } - - framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; - - if (framesAvailableCapture > 0) { - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } else { - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferSizeInFramesCapture = 0; - } - } while (framesAvailableCapture > periodSizeInFramesCapture); - #ifdef MA_DEBUG_OUTPUT - printf("framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - #endif - } - } else { - #ifdef MA_DEBUG_OUTPUT - if (flagsCapture != 0) { - printf("[WASAPI] Capture Flags: %ld\n", flagsCapture); - } - #endif - } - - mappedDeviceBufferFramesRemainingCapture = mappedDeviceBufferSizeInFramesCapture; - } - - - /* At this point we should have both input and output data available. We now need to convert the data and post it to the client. */ - for (;;) { - BYTE* pRunningDeviceBufferCapture; - BYTE* pRunningDeviceBufferPlayback; - ma_uint32 framesToProcess; - ma_uint32 framesProcessed; - - pRunningDeviceBufferCapture = pMappedDeviceBufferCapture + ((mappedDeviceBufferSizeInFramesCapture - mappedDeviceBufferFramesRemainingCapture ) * bpfCaptureDevice); - pRunningDeviceBufferPlayback = pMappedDeviceBufferPlayback + ((mappedDeviceBufferSizeInFramesPlayback - mappedDeviceBufferFramesRemainingPlayback) * bpfPlaybackDevice); - - /* There may be some data sitting in the converter that needs to be processed first. Once this is exhaused, run the data callback again. */ - if (!pDevice->playback.converter.isPassthrough && outputDataInClientFormatConsumed < outputDataInClientFormatCount) { - ma_uint64 convertedFrameCountClient = (outputDataInClientFormatCount - outputDataInClientFormatConsumed); - ma_uint64 convertedFrameCountDevice = mappedDeviceBufferFramesRemainingPlayback; - void* pConvertedFramesClient = outputDataInClientFormat + (outputDataInClientFormatConsumed * bpfPlaybackClient); - void* pConvertedFramesDevice = pRunningDeviceBufferPlayback; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pConvertedFramesClient, &convertedFrameCountClient, pConvertedFramesDevice, &convertedFrameCountDevice); - if (result != MA_SUCCESS) { - break; - } - - outputDataInClientFormatConsumed += (ma_uint32)convertedFrameCountClient; /* Safe cast. */ - mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)convertedFrameCountDevice; /* Safe cast. */ - - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; - } - } - - /* - Getting here means we need to fire the callback. If format conversion is unnecessary, we can optimize this by passing the pointers to the internal - buffers directly to the callback. - */ - if (pDevice->capture.converter.isPassthrough && pDevice->playback.converter.isPassthrough) { - /* Optimal path. We can pass mapped pointers directly to the callback. */ - framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, mappedDeviceBufferFramesRemainingPlayback); - framesProcessed = framesToProcess; - - ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, pRunningDeviceBufferCapture, framesToProcess); - - mappedDeviceBufferFramesRemainingCapture -= framesProcessed; - mappedDeviceBufferFramesRemainingPlayback -= framesProcessed; - - if (mappedDeviceBufferFramesRemainingCapture == 0) { - break; /* Exhausted input data. */ - } - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; /* Exhausted output data. */ - } - } else if (pDevice->capture.converter.isPassthrough) { - /* The input buffer is a passthrough, but the playback buffer requires a conversion. */ - framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, outputDataInClientFormatCap); - framesProcessed = framesToProcess; - - ma_device__on_data(pDevice, outputDataInClientFormat, pRunningDeviceBufferCapture, framesToProcess); - outputDataInClientFormatCount = framesProcessed; - outputDataInClientFormatConsumed = 0; - - mappedDeviceBufferFramesRemainingCapture -= framesProcessed; - if (mappedDeviceBufferFramesRemainingCapture == 0) { - break; /* Exhausted input data. */ - } - } else if (pDevice->playback.converter.isPassthrough) { - /* The input buffer requires conversion, the playback buffer is passthrough. */ - ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; - ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, mappedDeviceBufferFramesRemainingPlayback); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); - if (result != MA_SUCCESS) { - break; - } - - if (capturedClientFramesToProcess == 0) { - break; - } - - ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); /* Safe cast. */ - - mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; - mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)capturedClientFramesToProcess; - } else { - ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; - ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, outputDataInClientFormatCap); - - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); - if (result != MA_SUCCESS) { - break; - } - - if (capturedClientFramesToProcess == 0) { - break; - } - - ma_device__on_data(pDevice, outputDataInClientFormat, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); - - mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; - outputDataInClientFormatCount = (ma_uint32)capturedClientFramesToProcess; - outputDataInClientFormatConsumed = 0; - } - } - - - /* If at this point we've run out of capture data we need to release the buffer. */ - if (mappedDeviceBufferFramesRemainingCapture == 0 && pMappedDeviceBufferCapture != NULL) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - /*printf("TRACE: Released capture buffer\n");*/ - - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferFramesRemainingCapture = 0; - mappedDeviceBufferSizeInFramesCapture = 0; - } - - /* Get out of this loop if we're run out of room in the playback buffer. */ - if (mappedDeviceBufferFramesRemainingPlayback == 0) { - break; - } - } - - - /* If at this point we've run out of data we need to release the buffer. */ - if (mappedDeviceBufferFramesRemainingPlayback == 0 && pMappedDeviceBufferPlayback != NULL) { - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - /*printf("TRACE: Released playback buffer\n");*/ - framesWrittenToPlaybackDevice += mappedDeviceBufferSizeInFramesPlayback; - - pMappedDeviceBufferPlayback = NULL; - mappedDeviceBufferFramesRemainingPlayback = 0; - mappedDeviceBufferSizeInFramesPlayback = 0; - } - - if (!pDevice->wasapi.isStartedPlayback) { - ma_uint32 startThreshold = pDevice->playback.internalPeriodSizeInFrames * 1; - - /* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */ - if (startThreshold > pDevice->wasapi.actualPeriodSizeInFramesPlayback) { - startThreshold = pDevice->wasapi.actualPeriodSizeInFramesPlayback; - } - - if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= startThreshold) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); - } - c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); - } - } - } break; - - - - case ma_device_type_capture: - case ma_device_type_loopback: - { - ma_uint32 framesAvailableCapture; - DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ - - /* Wait for data to become available first. */ - if (WaitForSingleObject(pDevice->wasapi.hEventCapture, INFINITE) != WAIT_OBJECT_0) { - exitLoop = MA_TRUE; - break; /* Wait failed. */ - } - - /* See how many frames are available. Since we waited at the top, I don't think this should ever return 0. I'm checking for this anyway. */ - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - if (framesAvailableCapture < pDevice->wasapi.periodSizeInFramesCapture) { - continue; /* Nothing available. Keep waiting. */ - } - - /* Map the data buffer in preparation for sending to the client. */ - mappedDeviceBufferSizeInFramesCapture = framesAvailableCapture; - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - /* Overrun detection. */ - if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { - /* Glitched. Probably due to an overrun. */ - #ifdef MA_DEBUG_OUTPUT - printf("[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - #endif - - /* - Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment - by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the - last period. - */ - if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { - #ifdef MA_DEBUG_OUTPUT - printf("[WASAPI] Synchronizing capture stream. "); - #endif - do - { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - if (FAILED(hr)) { - break; - } - - framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; - - if (framesAvailableCapture > 0) { - mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); - hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } else { - pMappedDeviceBufferCapture = NULL; - mappedDeviceBufferSizeInFramesCapture = 0; - } - } while (framesAvailableCapture > periodSizeInFramesCapture); - #ifdef MA_DEBUG_OUTPUT - printf("framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); - #endif - } - } else { - #ifdef MA_DEBUG_OUTPUT - if (flagsCapture != 0) { - printf("[WASAPI] Capture Flags: %ld\n", flagsCapture); - } - #endif - } - - /* We should have a buffer at this point, but let's just do a sanity check anyway. */ - if (mappedDeviceBufferSizeInFramesCapture > 0 && pMappedDeviceBufferCapture != NULL) { - ma_device__send_frames_to_client(pDevice, mappedDeviceBufferSizeInFramesCapture, pMappedDeviceBufferCapture); - - /* At this point we're done with the buffer. */ - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - pMappedDeviceBufferCapture = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ - mappedDeviceBufferSizeInFramesCapture = 0; - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - } - } break; - - - - case ma_device_type_playback: - { - ma_uint32 framesAvailablePlayback; - - /* Wait for space to become available first. */ - if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, INFINITE) != WAIT_OBJECT_0) { - exitLoop = MA_TRUE; - break; /* Wait failed. */ - } - - /* Check how much space is available. If this returns 0 we just keep waiting. */ - result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - if (framesAvailablePlayback < pDevice->wasapi.periodSizeInFramesPlayback) { - continue; /* No space available. */ - } - - /* Map a the data buffer in preparation for the callback. */ - hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - /* We should have a buffer at this point. */ - ma_device__read_frames_from_client(pDevice, framesAvailablePlayback, pMappedDeviceBufferPlayback); - - /* At this point we're done writing to the device and we just need to release the buffer. */ - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, 0); - pMappedDeviceBufferPlayback = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ - mappedDeviceBufferSizeInFramesPlayback = 0; - - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - - framesWrittenToPlaybackDevice += framesAvailablePlayback; - if (!pDevice->wasapi.isStartedPlayback) { - if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= pDevice->playback.internalPeriodSizeInFrames*1) { - hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); - if (FAILED(hr)) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); - exitLoop = MA_TRUE; - break; - } - c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); - } - } - } break; - - default: return MA_INVALID_ARGS; - } - } - - /* Here is where the device needs to be stopped. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - /* Any mapped buffers need to be released. */ - if (pMappedDeviceBufferCapture != NULL) { - hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); - } - hr = ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); if (FAILED(hr)) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to stop internal capture device.", ma_result_from_HRESULT(hr)); @@ -14100,16 +15618,11 @@ static ma_result ma_device_main_loop__wasapi(ma_device* pDevice) } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - /* Any mapped buffers need to be released. */ - if (pMappedDeviceBufferPlayback != NULL) { - hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); - } - /* The buffer needs to be drained before stopping the device. Not doing this will result in the last few frames not getting output to the speakers. This is a problem for very short sounds because it'll result in a significant portion of it not getting played. */ - if (pDevice->wasapi.isStartedPlayback) { + if (c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { /* We need to make sure we put a timeout here or else we'll risk getting stuck in a deadlock in some cases. */ DWORD waitTime = pDevice->wasapi.actualPeriodSizeInFramesPlayback / pDevice->playback.internalSampleRate; @@ -14160,16 +15673,562 @@ static ma_result ma_device_main_loop__wasapi(ma_device* pDevice) return MA_SUCCESS; } -static ma_result ma_context_uninit__wasapi(ma_context* pContext) + +#ifndef MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS +#define MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS 5000 +#endif + +static ma_result ma_device_data_loop__wasapi(ma_device* pDevice) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pContext->backend == ma_backend_wasapi); - (void)pContext; + ma_result result; + HRESULT hr; + ma_bool32 exitLoop = MA_FALSE; + ma_uint32 framesWrittenToPlaybackDevice = 0; + ma_uint32 mappedDeviceBufferSizeInFramesCapture = 0; + ma_uint32 mappedDeviceBufferSizeInFramesPlayback = 0; + ma_uint32 mappedDeviceBufferFramesRemainingCapture = 0; + ma_uint32 mappedDeviceBufferFramesRemainingPlayback = 0; + BYTE* pMappedDeviceBufferCapture = NULL; + BYTE* pMappedDeviceBufferPlayback = NULL; + ma_uint32 bpfCaptureDevice = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + ma_uint32 bpfPlaybackDevice = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_uint32 bpfCaptureClient = ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); + ma_uint32 bpfPlaybackClient = ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); + ma_uint8 inputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 inputDataInClientFormatCap = 0; + ma_uint8 outputDataInClientFormat[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; + ma_uint32 outputDataInClientFormatCap = 0; + ma_uint32 outputDataInClientFormatCount = 0; + ma_uint32 outputDataInClientFormatConsumed = 0; + ma_uint32 periodSizeInFramesCapture = 0; + + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + periodSizeInFramesCapture = pDevice->capture.internalPeriodSizeInFrames; + inputDataInClientFormatCap = sizeof(inputDataInClientFormat) / bpfCaptureClient; + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + outputDataInClientFormatCap = sizeof(outputDataInClientFormat) / bpfPlaybackClient; + } + + while (ma_device_get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { + switch (pDevice->type) + { + case ma_device_type_duplex: + { + ma_uint32 framesAvailableCapture; + ma_uint32 framesAvailablePlayback; + DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ + + /* The process is to map the playback buffer and fill it as quickly as possible from input data. */ + if (pMappedDeviceBufferPlayback == NULL) { + result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); + if (result != MA_SUCCESS) { + return result; + } + + /*printf("TRACE 1: framesAvailablePlayback=%d\n", framesAvailablePlayback);*/ + + + /* In exclusive mode, the frame count needs to exactly match the value returned by GetCurrentPadding(). */ + if (pDevice->playback.shareMode != ma_share_mode_exclusive) { + if (framesAvailablePlayback > pDevice->wasapi.periodSizeInFramesPlayback) { + framesAvailablePlayback = pDevice->wasapi.periodSizeInFramesPlayback; + } + } + + /* We're ready to map the playback device's buffer. We don't release this until it's been entirely filled. */ + hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + mappedDeviceBufferSizeInFramesPlayback = framesAvailablePlayback; + mappedDeviceBufferFramesRemainingPlayback = framesAvailablePlayback; + } + + if (mappedDeviceBufferFramesRemainingPlayback > 0) { + /* At this point we should have a buffer available for output. We need to keep writing input samples to it. */ + for (;;) { + /* Try grabbing some captured data if we haven't already got a mapped buffer. */ + if (pMappedDeviceBufferCapture == NULL) { + if (pDevice->capture.shareMode == ma_share_mode_shared) { + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + return MA_ERROR; /* Wait failed. */ + } + } + + result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + /*printf("TRACE 2: framesAvailableCapture=%d\n", framesAvailableCapture);*/ + + /* Wait for more if nothing is available. */ + if (framesAvailableCapture == 0) { + /* In exclusive mode we waited at the top. */ + if (pDevice->capture.shareMode != ma_share_mode_shared) { + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + return MA_ERROR; /* Wait failed. */ + } + } + + continue; + } + + /* Getting here means there's data available for writing to the output device. */ + mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + + /* Overrun detection. */ + if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { + /* Glitched. Probably due to an overrun. */ + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + #endif + + /* + Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment + by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the + last period. + */ + if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Synchronizing capture stream. "); + #endif + do + { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + if (FAILED(hr)) { + break; + } + + framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; + + if (framesAvailableCapture > 0) { + mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + } else { + pMappedDeviceBufferCapture = NULL; + mappedDeviceBufferSizeInFramesCapture = 0; + } + } while (framesAvailableCapture > periodSizeInFramesCapture); + #ifdef MA_DEBUG_OUTPUT + printf("framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + #endif + } + } else { + #ifdef MA_DEBUG_OUTPUT + if (flagsCapture != 0) { + printf("[WASAPI] Capture Flags: %ld\n", flagsCapture); + } + #endif + } + + mappedDeviceBufferFramesRemainingCapture = mappedDeviceBufferSizeInFramesCapture; + } + + + /* At this point we should have both input and output data available. We now need to convert the data and post it to the client. */ + for (;;) { + BYTE* pRunningDeviceBufferCapture; + BYTE* pRunningDeviceBufferPlayback; + ma_uint32 framesToProcess; + ma_uint32 framesProcessed; + + pRunningDeviceBufferCapture = pMappedDeviceBufferCapture + ((mappedDeviceBufferSizeInFramesCapture - mappedDeviceBufferFramesRemainingCapture ) * bpfCaptureDevice); + pRunningDeviceBufferPlayback = pMappedDeviceBufferPlayback + ((mappedDeviceBufferSizeInFramesPlayback - mappedDeviceBufferFramesRemainingPlayback) * bpfPlaybackDevice); + + /* There may be some data sitting in the converter that needs to be processed first. Once this is exhaused, run the data callback again. */ + if (!pDevice->playback.converter.isPassthrough && outputDataInClientFormatConsumed < outputDataInClientFormatCount) { + ma_uint64 convertedFrameCountClient = (outputDataInClientFormatCount - outputDataInClientFormatConsumed); + ma_uint64 convertedFrameCountDevice = mappedDeviceBufferFramesRemainingPlayback; + void* pConvertedFramesClient = outputDataInClientFormat + (outputDataInClientFormatConsumed * bpfPlaybackClient); + void* pConvertedFramesDevice = pRunningDeviceBufferPlayback; + result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, pConvertedFramesClient, &convertedFrameCountClient, pConvertedFramesDevice, &convertedFrameCountDevice); + if (result != MA_SUCCESS) { + break; + } + + outputDataInClientFormatConsumed += (ma_uint32)convertedFrameCountClient; /* Safe cast. */ + mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)convertedFrameCountDevice; /* Safe cast. */ + + if (mappedDeviceBufferFramesRemainingPlayback == 0) { + break; + } + } + + /* + Getting here means we need to fire the callback. If format conversion is unnecessary, we can optimize this by passing the pointers to the internal + buffers directly to the callback. + */ + if (pDevice->capture.converter.isPassthrough && pDevice->playback.converter.isPassthrough) { + /* Optimal path. We can pass mapped pointers directly to the callback. */ + framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, mappedDeviceBufferFramesRemainingPlayback); + framesProcessed = framesToProcess; + + ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, pRunningDeviceBufferCapture, framesToProcess); + + mappedDeviceBufferFramesRemainingCapture -= framesProcessed; + mappedDeviceBufferFramesRemainingPlayback -= framesProcessed; + + if (mappedDeviceBufferFramesRemainingCapture == 0) { + break; /* Exhausted input data. */ + } + if (mappedDeviceBufferFramesRemainingPlayback == 0) { + break; /* Exhausted output data. */ + } + } else if (pDevice->capture.converter.isPassthrough) { + /* The input buffer is a passthrough, but the playback buffer requires a conversion. */ + framesToProcess = ma_min(mappedDeviceBufferFramesRemainingCapture, outputDataInClientFormatCap); + framesProcessed = framesToProcess; + + ma_device__on_data(pDevice, outputDataInClientFormat, pRunningDeviceBufferCapture, framesToProcess); + outputDataInClientFormatCount = framesProcessed; + outputDataInClientFormatConsumed = 0; + + mappedDeviceBufferFramesRemainingCapture -= framesProcessed; + if (mappedDeviceBufferFramesRemainingCapture == 0) { + break; /* Exhausted input data. */ + } + } else if (pDevice->playback.converter.isPassthrough) { + /* The input buffer requires conversion, the playback buffer is passthrough. */ + ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; + ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, mappedDeviceBufferFramesRemainingPlayback); + + result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); + if (result != MA_SUCCESS) { + break; + } + + if (capturedClientFramesToProcess == 0) { + break; + } + + ma_device__on_data(pDevice, pRunningDeviceBufferPlayback, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); /* Safe cast. */ + + mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; + mappedDeviceBufferFramesRemainingPlayback -= (ma_uint32)capturedClientFramesToProcess; + } else { + ma_uint64 capturedDeviceFramesToProcess = mappedDeviceBufferFramesRemainingCapture; + ma_uint64 capturedClientFramesToProcess = ma_min(inputDataInClientFormatCap, outputDataInClientFormatCap); + + result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningDeviceBufferCapture, &capturedDeviceFramesToProcess, inputDataInClientFormat, &capturedClientFramesToProcess); + if (result != MA_SUCCESS) { + break; + } + + if (capturedClientFramesToProcess == 0) { + break; + } + + ma_device__on_data(pDevice, outputDataInClientFormat, inputDataInClientFormat, (ma_uint32)capturedClientFramesToProcess); + + mappedDeviceBufferFramesRemainingCapture -= (ma_uint32)capturedDeviceFramesToProcess; + outputDataInClientFormatCount = (ma_uint32)capturedClientFramesToProcess; + outputDataInClientFormatConsumed = 0; + } + } + + + /* If at this point we've run out of capture data we need to release the buffer. */ + if (mappedDeviceBufferFramesRemainingCapture == 0 && pMappedDeviceBufferCapture != NULL) { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + /*printf("TRACE: Released capture buffer\n");*/ + + pMappedDeviceBufferCapture = NULL; + mappedDeviceBufferFramesRemainingCapture = 0; + mappedDeviceBufferSizeInFramesCapture = 0; + } + + /* Get out of this loop if we're run out of room in the playback buffer. */ + if (mappedDeviceBufferFramesRemainingPlayback == 0) { + break; + } + } + } + + + /* If at this point we've run out of data we need to release the buffer. */ + if (mappedDeviceBufferFramesRemainingPlayback == 0 && pMappedDeviceBufferPlayback != NULL) { + hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + /*printf("TRACE: Released playback buffer\n");*/ + framesWrittenToPlaybackDevice += mappedDeviceBufferSizeInFramesPlayback; + + pMappedDeviceBufferPlayback = NULL; + mappedDeviceBufferFramesRemainingPlayback = 0; + mappedDeviceBufferSizeInFramesPlayback = 0; + } + + if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { + ma_uint32 startThreshold = pDevice->playback.internalPeriodSizeInFrames * 1; + + /* Prevent a deadlock. If we don't clamp against the actual buffer size we'll never end up starting the playback device which will result in a deadlock. */ + if (startThreshold > pDevice->wasapi.actualPeriodSizeInFramesPlayback) { + startThreshold = pDevice->wasapi.actualPeriodSizeInFramesPlayback; + } + + if (pDevice->playback.shareMode == ma_share_mode_exclusive || framesWrittenToPlaybackDevice >= startThreshold) { + hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + ma_IAudioClient_Stop((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + ma_IAudioClient_Reset((ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); + } + + c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); + } + } + + /* Make sure the device has started before waiting. */ + if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + return MA_ERROR; /* Wait failed. */ + } + } break; + + + + case ma_device_type_capture: + case ma_device_type_loopback: + { + ma_uint32 framesAvailableCapture; + DWORD flagsCapture; /* Passed to IAudioCaptureClient_GetBuffer(). */ + + /* Wait for data to become available first. */ + if (WaitForSingleObject(pDevice->wasapi.hEventCapture, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + exitLoop = MA_TRUE; + break; /* Wait failed. */ + } + + /* See how many frames are available. Since we waited at the top, I don't think this should ever return 0. I'm checking for this anyway. */ + result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientCapture, &framesAvailableCapture); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + if (framesAvailableCapture < pDevice->wasapi.periodSizeInFramesCapture) { + continue; /* Nothing available. Keep waiting. */ + } + + /* Map the data buffer in preparation for sending to the client. */ + mappedDeviceBufferSizeInFramesCapture = framesAvailableCapture; + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + /* Overrun detection. */ + if ((flagsCapture & MA_AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) { + /* Glitched. Probably due to an overrun. */ + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Data discontinuity (possible overrun). framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + #endif + + /* + Exeriment: If we get an overrun it probably means we're straddling the end of the buffer. In order to prevent a never-ending sequence of glitches let's experiment + by dropping every frame until we're left with only a single period. To do this we just keep retrieving and immediately releasing buffers until we're down to the + last period. + */ + if (framesAvailableCapture >= pDevice->wasapi.actualPeriodSizeInFramesCapture) { + #ifdef MA_DEBUG_OUTPUT + printf("[WASAPI] Synchronizing capture stream. "); + #endif + do + { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + if (FAILED(hr)) { + break; + } + + framesAvailableCapture -= mappedDeviceBufferSizeInFramesCapture; + + if (framesAvailableCapture > 0) { + mappedDeviceBufferSizeInFramesCapture = ma_min(framesAvailableCapture, periodSizeInFramesCapture); + hr = ma_IAudioCaptureClient_GetBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, (BYTE**)&pMappedDeviceBufferCapture, &mappedDeviceBufferSizeInFramesCapture, &flagsCapture, NULL, NULL); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from capture device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + } else { + pMappedDeviceBufferCapture = NULL; + mappedDeviceBufferSizeInFramesCapture = 0; + } + } while (framesAvailableCapture > periodSizeInFramesCapture); + #ifdef MA_DEBUG_OUTPUT + printf("framesAvailableCapture=%d, mappedBufferSizeInFramesCapture=%d\n", framesAvailableCapture, mappedDeviceBufferSizeInFramesCapture); + #endif + } + } else { + #ifdef MA_DEBUG_OUTPUT + if (flagsCapture != 0) { + printf("[WASAPI] Capture Flags: %ld\n", flagsCapture); + } + #endif + } + + /* We should have a buffer at this point, but let's just do a sanity check anyway. */ + if (mappedDeviceBufferSizeInFramesCapture > 0 && pMappedDeviceBufferCapture != NULL) { + ma_device__send_frames_to_client(pDevice, mappedDeviceBufferSizeInFramesCapture, pMappedDeviceBufferCapture); + + /* At this point we're done with the buffer. */ + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + pMappedDeviceBufferCapture = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ + mappedDeviceBufferSizeInFramesCapture = 0; + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from capture device after reading from the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + } + } break; + + + + case ma_device_type_playback: + { + ma_uint32 framesAvailablePlayback; + + /* Check how much space is available. If this returns 0 we just keep waiting. */ + result = ma_device__get_available_frames__wasapi(pDevice, (ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback, &framesAvailablePlayback); + if (result != MA_SUCCESS) { + exitLoop = MA_TRUE; + break; + } + + if (framesAvailablePlayback >= pDevice->wasapi.periodSizeInFramesPlayback) { + /* Map a the data buffer in preparation for the callback. */ + hr = ma_IAudioRenderClient_GetBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, &pMappedDeviceBufferPlayback); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to retrieve internal buffer from playback device in preparation for writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + /* We should have a buffer at this point. */ + ma_device__read_frames_from_client(pDevice, framesAvailablePlayback, pMappedDeviceBufferPlayback); + + /* At this point we're done writing to the device and we just need to release the buffer. */ + hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, framesAvailablePlayback, 0); + pMappedDeviceBufferPlayback = NULL; /* <-- Important. Not doing this can result in an error once we leave this loop because it will use this to know whether or not a final ReleaseBuffer() needs to be called. */ + mappedDeviceBufferSizeInFramesPlayback = 0; + + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to release internal buffer from playback device after writing to the device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + framesWrittenToPlaybackDevice += framesAvailablePlayback; + } + + if (!c89atomic_load_32(&pDevice->wasapi.isStartedPlayback)) { + hr = ma_IAudioClient_Start((ma_IAudioClient*)pDevice->wasapi.pAudioClientPlayback); + if (FAILED(hr)) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WASAPI] Failed to start internal playback device.", ma_result_from_HRESULT(hr)); + exitLoop = MA_TRUE; + break; + } + + c89atomic_exchange_32(&pDevice->wasapi.isStartedPlayback, MA_TRUE); + } + + /* Make sure we don't wait on the event before we've started the device or we may end up deadlocking. */ + if (WaitForSingleObject(pDevice->wasapi.hEventPlayback, MA_WASAPI_WAIT_TIMEOUT_MILLISECONDS) != WAIT_OBJECT_0) { + exitLoop = MA_TRUE; + break; /* Wait failed. Probably timed out. */ + } + } break; + + default: return MA_INVALID_ARGS; + } + } + + /* Here is where the device needs to be stopped. */ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + /* Any mapped buffers need to be released. */ + if (pMappedDeviceBufferCapture != NULL) { + hr = ma_IAudioCaptureClient_ReleaseBuffer((ma_IAudioCaptureClient*)pDevice->wasapi.pCaptureClient, mappedDeviceBufferSizeInFramesCapture); + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* Any mapped buffers need to be released. */ + if (pMappedDeviceBufferPlayback != NULL) { + hr = ma_IAudioRenderClient_ReleaseBuffer((ma_IAudioRenderClient*)pDevice->wasapi.pRenderClient, mappedDeviceBufferSizeInFramesPlayback, 0); + } + } return MA_SUCCESS; } -static ma_result ma_context_init__wasapi(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_device_data_loop_wakeup__wasapi(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { + SetEvent((HANDLE)pDevice->wasapi.hEventCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + SetEvent((HANDLE)pDevice->wasapi.hEventPlayback); + } + + return MA_SUCCESS; +} + + +static ma_result ma_context_uninit__wasapi(ma_context* pContext) +{ + MA_ASSERT(pContext != NULL); + MA_ASSERT(pContext->backend == ma_backend_wasapi); + + if (pContext->wasapi.commandThread != NULL) { + ma_context_command__wasapi cmd = ma_context_init_command__wasapi(MA_CONTEXT_COMMAND_QUIT__WASAPI); + ma_context_post_command__wasapi(pContext, &cmd); + ma_thread_wait(&pContext->wasapi.commandThread); + + /* Only after the thread has been terminated can we uninitialize the sync objects for the command thread. */ + ma_semaphore_uninit(&pContext->wasapi.commandSem); + ma_mutex_uninit(&pContext->wasapi.commandLock); + } + + return MA_SUCCESS; +} + +static ma_result ma_context_init__wasapi(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { ma_result result = MA_SUCCESS; @@ -14182,7 +16241,7 @@ static ma_result ma_context_init__wasapi(const ma_context_config* pConfig, ma_co 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. - Unfortunately older compilers don't define these functions so we need to dynamically load them in order to avoid a lin error. + Unfortunately older compilers don't define these functions so we need to dynamically load them in order to avoid a link error. */ { ma_OSVERSIONINFOEXW osvi; @@ -14195,7 +16254,7 @@ static ma_result ma_context_init__wasapi(const ma_context_config* pConfig, ma_co return MA_NO_BACKEND; } - _VerifyVersionInfoW = (ma_PFNVerifyVersionInfoW)ma_dlsym(pContext, kernel32DLL, "VerifyVersionInfoW"); + _VerifyVersionInfoW = (ma_PFNVerifyVersionInfoW )ma_dlsym(pContext, kernel32DLL, "VerifyVersionInfoW"); _VerSetConditionMask = (ma_PFNVerSetConditionMask)ma_dlsym(pContext, kernel32DLL, "VerSetConditionMask"); if (_VerifyVersionInfoW == NULL || _VerSetConditionMask == NULL) { ma_dlclose(pContext, kernel32DLL); @@ -14221,17 +16280,68 @@ static ma_result ma_context_init__wasapi(const ma_context_config* pConfig, ma_co return result; } - pContext->onUninit = ma_context_uninit__wasapi; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__wasapi; - pContext->onEnumDevices = ma_context_enumerate_devices__wasapi; - pContext->onGetDeviceInfo = ma_context_get_device_info__wasapi; - pContext->onDeviceInit = ma_device_init__wasapi; - pContext->onDeviceUninit = ma_device_uninit__wasapi; - pContext->onDeviceStart = NULL; /* Not used. Started in onDeviceMainLoop. */ - pContext->onDeviceStop = ma_device_stop__wasapi; /* Required to ensure the capture event is signalled when stopping a loopback device while nothing is playing. */ - pContext->onDeviceMainLoop = ma_device_main_loop__wasapi; + MA_ZERO_OBJECT(&pContext->wasapi); - return result; + /* + Annoyingly, WASAPI does not allow you to release an IAudioClient object from a different thread + than the one that retrieved it with GetService(). This can result in a deadlock in two + situations: + + 1) When calling ma_device_uninit() from a different thread to ma_device_init(); and + 2) When uninitializing and reinitializing the internal IAudioClient object in response to + automatic stream routing. + + We could define ma_device_uninit() such that it must be called on the same thread as + ma_device_init(). We could also just not release the IAudioClient when performing automatic + stream routing to avoid the deadlock. Neither of these are acceptable solutions in my view so + we're going to have to work around this with a worker thread. This is not ideal, but I can't + think of a better way to do this. + + More information about this can be found here: + + https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nn-audioclient-iaudiorenderclient + + Note this section: + + When releasing an IAudioRenderClient interface instance, the client must call the interface's + Release method from the same thread as the call to IAudioClient::GetService that created the + object. + */ + { + result = ma_mutex_init(&pContext->wasapi.commandLock); + if (result != MA_SUCCESS) { + return result; + } + + result = ma_semaphore_init(0, &pContext->wasapi.commandSem); + if (result != MA_SUCCESS) { + ma_mutex_uninit(&pContext->wasapi.commandLock); + return result; + } + + result = ma_thread_create(&pContext->wasapi.commandThread, ma_thread_priority_normal, 0, ma_context_command_thread__wasapi, pContext); + if (result != MA_SUCCESS) { + ma_semaphore_uninit(&pContext->wasapi.commandSem); + ma_mutex_uninit(&pContext->wasapi.commandLock); + return result; + } + } + + + pCallbacks->onContextInit = ma_context_init__wasapi; + pCallbacks->onContextUninit = ma_context_uninit__wasapi; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__wasapi; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__wasapi; + pCallbacks->onDeviceInit = ma_device_init__wasapi; + pCallbacks->onDeviceUninit = ma_device_uninit__wasapi; + pCallbacks->onDeviceStart = ma_device_start__wasapi; + pCallbacks->onDeviceStop = ma_device_stop__wasapi; + pCallbacks->onDeviceRead = NULL; /* Not used. Reading is done manually in the audio thread. */ + pCallbacks->onDeviceWrite = NULL; /* Not used. Writing is done manually in the audio thread. */ + pCallbacks->onDeviceDataLoop = ma_device_data_loop__wasapi; + pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__wasapi; + + return MA_SUCCESS; } #endif @@ -14243,7 +16353,7 @@ DirectSound Backend #ifdef MA_HAS_DSOUND /*#include */ -static const GUID MA_GUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}}; +/*static const GUID MA_GUID_IID_DirectSoundNotify = {0xb0210783, 0x89cd, 0x11d0, {0xaf, 0x08, 0x00, 0xa0, 0xc9, 0x25, 0xcd, 0x16}};*/ /* miniaudio only uses priority or exclusive modes. */ #define MA_DSSCL_NORMAL 1 @@ -14565,11 +16675,11 @@ typedef HRESULT (WINAPI * ma_DirectSoundCaptureEnumerateAProc)(ma_DSEnumCallba static ma_uint32 ma_get_best_sample_rate_within_range(ma_uint32 sampleRateMin, ma_uint32 sampleRateMax) { /* Normalize the range in case we were given something stupid. */ - if (sampleRateMin < MA_MIN_SAMPLE_RATE) { - sampleRateMin = MA_MIN_SAMPLE_RATE; + if (sampleRateMin < (ma_uint32)ma_standard_sample_rate_min) { + sampleRateMin = (ma_uint32)ma_standard_sample_rate_min; } - if (sampleRateMax > MA_MAX_SAMPLE_RATE) { - sampleRateMax = MA_MAX_SAMPLE_RATE; + if (sampleRateMax > (ma_uint32)ma_standard_sample_rate_max) { + sampleRateMax = (ma_uint32)ma_standard_sample_rate_max; } if (sampleRateMin > sampleRateMax) { sampleRateMin = sampleRateMax; @@ -14795,16 +16905,6 @@ static ma_result ma_context_get_format_info_for_IDirectSoundCapture__dsound(ma_c return MA_SUCCESS; } -static ma_bool32 ma_context_is_device_id_equal__dsound(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return memcmp(pID0->dsound, pID1->dsound, sizeof(pID0->dsound)) == 0; -} - typedef struct { @@ -14818,7 +16918,9 @@ typedef struct static BOOL CALLBACK ma_context_enumerate_devices_callback__dsound(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext) { ma_context_enumerate_devices_callback_data__dsound* pData = (ma_context_enumerate_devices_callback_data__dsound*)lpContext; - ma_device_info deviceInfo; + ma_device_info deviceInfo; + + (void)lpcstrModule; MA_ZERO_OBJECT(&deviceInfo); @@ -14827,6 +16929,7 @@ static BOOL CALLBACK ma_context_enumerate_devices_callback__dsound(LPGUID lpGuid MA_COPY_MEMORY(deviceInfo.id.dsound, lpGuid, 16); } else { MA_ZERO_MEMORY(deviceInfo.id.dsound, 16); + deviceInfo.isDefault = MA_TRUE; } /* Name / Description */ @@ -14841,8 +16944,6 @@ static BOOL CALLBACK ma_context_enumerate_devices_callback__dsound(LPGUID lpGuid } else { return TRUE; /* Continue enumeration. */ } - - (void)lpcstrModule; } static ma_result ma_context_enumerate_devices__dsound(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) @@ -14888,6 +16989,7 @@ static BOOL CALLBACK ma_context_get_device_info_callback__dsound(LPGUID lpGuid, if ((pData->pDeviceID == NULL || ma_is_guid_null(pData->pDeviceID->dsound)) && (lpGuid == NULL || ma_is_guid_null(lpGuid))) { /* Default device. */ ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), lpcstrDescription, (size_t)-1); + pData->pDeviceInfo->isDefault = MA_TRUE; pData->found = MA_TRUE; return FALSE; /* Stop enumeration. */ } else { @@ -14905,16 +17007,11 @@ static BOOL CALLBACK ma_context_get_device_info_callback__dsound(LPGUID lpGuid, return TRUE; } -static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_result result; HRESULT hr; - /* Exclusive mode and capture not supported with DirectSound. */ - if (deviceType == ma_device_type_capture && shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - if (pDeviceID != NULL) { ma_context_get_device_info_callback_data__dsound data; @@ -14946,6 +17043,8 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev } else { ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + + pDeviceInfo->isDefault = MA_TRUE; } /* Retrieving detailed information is slightly different depending on the device type. */ @@ -14953,9 +17052,9 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev /* Playback. */ ma_IDirectSound* pDirectSound; MA_DSCAPS caps; - ma_uint32 iFormat; + WORD channels; - result = ma_context_create_IDirectSound__dsound(pContext, shareMode, pDeviceID, &pDirectSound); + result = ma_context_create_IDirectSound__dsound(pContext, ma_share_mode_shared, pDeviceID, &pDirectSound); if (result != MA_SUCCESS) { return result; } @@ -14967,50 +17066,50 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); } + + /* Channels. Only a single channel count is reported for DirectSound. */ if ((caps.dwFlags & MA_DSCAPS_PRIMARYSTEREO) != 0) { /* It supports at least stereo, but could support more. */ - WORD channels = 2; + DWORD speakerConfig; + + channels = 2; /* Look at the speaker configuration to get a better idea on the channel count. */ - DWORD speakerConfig; hr = ma_IDirectSound_GetSpeakerConfig(pDirectSound, &speakerConfig); if (SUCCEEDED(hr)) { ma_get_channels_from_speaker_config__dsound(speakerConfig, &channels, NULL); } - - pDeviceInfo->minChannels = channels; - pDeviceInfo->maxChannels = channels; } else { /* It does not support stereo, which means we are stuck with mono. */ - pDeviceInfo->minChannels = 1; - pDeviceInfo->maxChannels = 1; + channels = 1; } - /* Sample rate. */ - if ((caps.dwFlags & MA_DSCAPS_CONTINUOUSRATE) != 0) { - pDeviceInfo->minSampleRate = caps.dwMinSecondarySampleRate; - pDeviceInfo->maxSampleRate = caps.dwMaxSecondarySampleRate; - /* - On my machine the min and max sample rates can return 100 and 200000 respectively. I'd rather these be within - the range of our standard sample rates so I'm clamping. - */ - if (caps.dwMinSecondarySampleRate < MA_MIN_SAMPLE_RATE && caps.dwMaxSecondarySampleRate >= MA_MIN_SAMPLE_RATE) { - pDeviceInfo->minSampleRate = MA_MIN_SAMPLE_RATE; - } - if (caps.dwMaxSecondarySampleRate > MA_MAX_SAMPLE_RATE && caps.dwMinSecondarySampleRate <= MA_MAX_SAMPLE_RATE) { - pDeviceInfo->maxSampleRate = MA_MAX_SAMPLE_RATE; + /* + In DirectSound, our native formats are centered around sample rates. All formats are supported, and we're only reporting a single channel + count. However, DirectSound can report a range of supported sample rates. We're only going to include standard rates known by miniaudio + in order to keep the size of this within reason. + */ + if ((caps.dwFlags & MA_DSCAPS_CONTINUOUSRATE) != 0) { + /* Multiple sample rates are supported. We'll report in order of our preferred sample rates. */ + size_t iStandardSampleRate; + for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) { + ma_uint32 sampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate]; + if (sampleRate >= caps.dwMinSecondarySampleRate && sampleRate <= caps.dwMaxSecondarySampleRate) { + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = ma_format_unknown; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; + pDeviceInfo->nativeDataFormatCount += 1; + } } } else { - /* Only supports a single sample rate. Set both min an max to the same thing. Do not clamp within the standard rates. */ - pDeviceInfo->minSampleRate = caps.dwMaxSecondarySampleRate; - pDeviceInfo->maxSampleRate = caps.dwMaxSecondarySampleRate; - } - - /* DirectSound can support all formats. */ - pDeviceInfo->formatCount = ma_format_count - 1; /* Minus one because we don't want to include ma_format_unknown. */ - for (iFormat = 0; iFormat < pDeviceInfo->formatCount; ++iFormat) { - pDeviceInfo->formats[iFormat] = (ma_format)(iFormat + 1); /* +1 to skip over ma_format_unknown. */ + /* Only a single sample rate is supported. */ + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = ma_format_unknown; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = caps.dwMaxSecondarySampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; + pDeviceInfo->nativeDataFormatCount += 1; } ma_IDirectSound_Release(pDirectSound); @@ -15025,7 +17124,7 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev WORD bitsPerSample; DWORD sampleRate; - result = ma_context_create_IDirectSoundCapture__dsound(pContext, shareMode, pDeviceID, &pDirectSoundCapture); + result = ma_context_create_IDirectSoundCapture__dsound(pContext, ma_share_mode_shared, pDeviceID, &pDirectSoundCapture); if (result != MA_SUCCESS) { return result; } @@ -15036,11 +17135,9 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev return result; } - pDeviceInfo->minChannels = channels; - pDeviceInfo->maxChannels = channels; - pDeviceInfo->minSampleRate = sampleRate; - pDeviceInfo->maxSampleRate = sampleRate; - pDeviceInfo->formatCount = 1; + ma_IDirectSoundCapture_Release(pDirectSoundCapture); + + /* The format is always an integer format and is based on the bits per sample. */ if (bitsPerSample == 8) { pDeviceInfo->formats[0] = ma_format_u8; } else if (bitsPerSample == 16) { @@ -15050,11 +17147,13 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev } else if (bitsPerSample == 32) { pDeviceInfo->formats[0] = ma_format_s32; } else { - ma_IDirectSoundCapture_Release(pDirectSoundCapture); return MA_FORMAT_NOT_SUPPORTED; } - ma_IDirectSoundCapture_Release(pDirectSoundCapture); + pDeviceInfo->nativeDataFormats[0].channels = channels; + pDeviceInfo->nativeDataFormats[0].sampleRate = sampleRate; + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormatCount = 1; } return MA_SUCCESS; @@ -15062,7 +17161,7 @@ static ma_result ma_context_get_device_info__dsound(ma_context* pContext, ma_dev -static void ma_device_uninit__dsound(ma_device* pDevice) +static ma_result ma_device_uninit__dsound(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -15082,12 +17181,26 @@ static void ma_device_uninit__dsound(ma_device* pDevice) if (pDevice->dsound.pPlayback != NULL) { ma_IDirectSound_Release((ma_IDirectSound*)pDevice->dsound.pPlayback); } + + return MA_SUCCESS; } static ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* pChannelMap, WAVEFORMATEXTENSIBLE* pWF) { GUID subformat; + if (format == ma_format_unknown) { + format = MA_DEFAULT_FORMAT; + } + + if (channels == 0) { + channels = MA_DEFAULT_CHANNELS; + } + + if (sampleRate == 0) { + sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + switch (format) { case ma_format_u8: @@ -15123,38 +17236,33 @@ static ma_result ma_config_to_WAVEFORMATEXTENSIBLE(ma_format format, ma_uint32 c return MA_SUCCESS; } -static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__dsound(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +{ + /* DirectSound has a minimum period size of 20ms. */ + ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(20, nativeSampleRate); + ma_uint32 periodSizeInFrames; + + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate, performanceProfile); + if (periodSizeInFrames < minPeriodSizeInFrames) { + periodSizeInFrames = minPeriodSizeInFrames; + } + + return periodSizeInFrames; +} + +static ma_result ma_device_init__dsound(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; HRESULT hr; - ma_uint32 periodSizeInMilliseconds; MA_ASSERT(pDevice != NULL); + MA_ZERO_OBJECT(&pDevice->dsound); if (pConfig->deviceType == ma_device_type_loopback) { return MA_DEVICE_TYPE_NOT_SUPPORTED; } - periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; - if (periodSizeInMilliseconds == 0) { - periodSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->periodSizeInFrames, pConfig->sampleRate); - } - - /* DirectSound should use a latency of about 20ms per period for low latency mode. */ - if (pDevice->usingDefaultBufferSize) { - if (pConfig->performanceProfile == ma_performance_profile_low_latency) { - periodSizeInMilliseconds = 20; - } else { - periodSizeInMilliseconds = 200; - } - } - - /* DirectSound breaks down with tiny buffer sizes (bad glitching and silent output). I am therefore restricting the size of the buffer to a minimum of 20 milliseconds. */ - if (periodSizeInMilliseconds < 20) { - periodSizeInMilliseconds = 20; - } - /* Unfortunately DirectSound uses different APIs and data structures for playback and catpure devices. We need to initialize the capture device first because we'll want to match it's buffer size and period count on the playback side if we're using @@ -15164,21 +17272,22 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co WAVEFORMATEXTENSIBLE wf; MA_DSCBUFFERDESC descDS; ma_uint32 periodSizeInFrames; + ma_uint32 periodCount; char rawdata[1024]; /* <-- Ugly hack to avoid a malloc() due to a crappy DirectSound API. */ WAVEFORMATEXTENSIBLE* pActualFormat; - result = ma_config_to_WAVEFORMATEXTENSIBLE(pConfig->capture.format, pConfig->capture.channels, pConfig->sampleRate, pConfig->capture.channelMap, &wf); + result = ma_config_to_WAVEFORMATEXTENSIBLE(pDescriptorCapture->format, pDescriptorCapture->channels, pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, &wf); if (result != MA_SUCCESS) { return result; } - result = ma_context_create_IDirectSoundCapture__dsound(pContext, pConfig->capture.shareMode, pConfig->capture.pDeviceID, (ma_IDirectSoundCapture**)&pDevice->dsound.pCapture); + result = ma_context_create_IDirectSoundCapture__dsound(pDevice->pContext, pDescriptorCapture->shareMode, pDescriptorCapture->pDeviceID, (ma_IDirectSoundCapture**)&pDevice->dsound.pCapture); if (result != MA_SUCCESS) { ma_device_uninit__dsound(pDevice); return result; } - result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pContext, (ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &wf.Format.nChannels, &wf.Format.wBitsPerSample, &wf.Format.nSamplesPerSec); + result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pDevice->pContext, (ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &wf.Format.nChannels, &wf.Format.wBitsPerSample, &wf.Format.nSamplesPerSec); if (result != MA_SUCCESS) { ma_device_uninit__dsound(pDevice); return result; @@ -15190,13 +17299,14 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co wf.SubFormat = MA_GUID_KSDATAFORMAT_SUBTYPE_PCM; /* The size of the buffer must be a clean multiple of the period count. */ - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, wf.Format.nSamplesPerSec); + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__dsound(pDescriptorCapture, wf.Format.nSamplesPerSec, pConfig->performanceProfile); + periodCount = (pDescriptorCapture->periodCount > 0) ? pDescriptorCapture->periodCount : MA_DEFAULT_PERIODS; MA_ZERO_OBJECT(&descDS); - descDS.dwSize = sizeof(descDS); - descDS.dwFlags = 0; - descDS.dwBufferBytes = periodSizeInFrames * pConfig->periods * ma_get_bytes_per_frame(pDevice->capture.internalFormat, wf.Format.nChannels); - descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; + descDS.dwSize = sizeof(descDS); + descDS.dwFlags = 0; + descDS.dwBufferBytes = periodSizeInFrames * periodCount * wf.Format.nBlockAlign; + descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); if (FAILED(hr)) { ma_device_uninit__dsound(pDevice); @@ -15211,23 +17321,24 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the capture device's buffer.", ma_result_from_HRESULT(hr)); } - pDevice->capture.internalFormat = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); - pDevice->capture.internalChannels = pActualFormat->Format.nChannels; - pDevice->capture.internalSampleRate = pActualFormat->Format.nSamplesPerSec; + /* We can now start setting the output data formats. */ + pDescriptorCapture->format = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); + pDescriptorCapture->channels = pActualFormat->Format.nChannels; + pDescriptorCapture->sampleRate = pActualFormat->Format.nSamplesPerSec; - /* Get the internal channel map based on the channel mask. */ + /* Get the native channel map based on the channel mask. */ if (pActualFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - ma_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); + ma_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDescriptorCapture->channels, pDescriptorCapture->channelMap); } else { - ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); + ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDescriptorCapture->channels, pDescriptorCapture->channelMap); } /* After getting the actual format the size of the buffer in frames may have actually changed. However, we want this to be as close to what the user has asked for as possible, so let's go ahead and release the old capture buffer and create a new one in this case. */ - if (periodSizeInFrames != (descDS.dwBufferBytes / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels) / pConfig->periods)) { - descDS.dwBufferBytes = periodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, wf.Format.nChannels) * pConfig->periods; + if (periodSizeInFrames != (descDS.dwBufferBytes / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / periodCount)) { + descDS.dwBufferBytes = periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * periodCount; ma_IDirectSoundCaptureBuffer_Release((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer); hr = ma_IDirectSoundCapture_CreateCaptureBuffer((ma_IDirectSoundCapture*)pDevice->dsound.pCapture, &descDS, (ma_IDirectSoundCaptureBuffer**)&pDevice->dsound.pCaptureBuffer, NULL); @@ -15238,8 +17349,8 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co } /* DirectSound should give us a buffer exactly the size we asked for. */ - pDevice->capture.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->capture.internalPeriods = pConfig->periods; + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; + pDescriptorCapture->periodCount = periodCount; } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { @@ -15249,14 +17360,15 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co char rawdata[1024]; /* <-- Ugly hack to avoid a malloc() due to a crappy DirectSound API. */ WAVEFORMATEXTENSIBLE* pActualFormat; ma_uint32 periodSizeInFrames; + ma_uint32 periodCount; MA_DSBUFFERDESC descDS; - result = ma_config_to_WAVEFORMATEXTENSIBLE(pConfig->playback.format, pConfig->playback.channels, pConfig->sampleRate, pConfig->playback.channelMap, &wf); + result = ma_config_to_WAVEFORMATEXTENSIBLE(pDescriptorPlayback->format, pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, &wf); if (result != MA_SUCCESS) { return result; } - result = ma_context_create_IDirectSound__dsound(pContext, pConfig->playback.shareMode, pConfig->playback.pDeviceID, (ma_IDirectSound**)&pDevice->dsound.pPlayback); + result = ma_context_create_IDirectSound__dsound(pDevice->pContext, pDescriptorPlayback->shareMode, pDescriptorPlayback->pDeviceID, (ma_IDirectSound**)&pDevice->dsound.pPlayback); if (result != MA_SUCCESS) { ma_device_uninit__dsound(pDevice); return result; @@ -15281,7 +17393,7 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.", ma_result_from_HRESULT(hr)); } - if (pDevice->playback.usingDefaultChannels) { + if (pDescriptorPlayback->channels == 0) { if ((caps.dwFlags & MA_DSCAPS_PRIMARYSTEREO) != 0) { DWORD speakerConfig; @@ -15298,7 +17410,7 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co } } - if (pDevice->usingDefaultSampleRate) { + if (pDescriptorPlayback->sampleRate == 0) { /* We base the sample rate on the values returned by GetCaps(). */ if ((caps.dwFlags & MA_DSCAPS_CONTINUOUSRATE) != 0) { wf.Format.nSamplesPerSec = ma_get_best_sample_rate_within_range(caps.dwMinSecondarySampleRate, caps.dwMaxSecondarySampleRate); @@ -15312,7 +17424,7 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co /* 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. @@ -15331,30 +17443,32 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] Failed to retrieve the actual format of the playback device's primary buffer.", ma_result_from_HRESULT(hr)); } - pDevice->playback.internalFormat = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); - pDevice->playback.internalChannels = pActualFormat->Format.nChannels; - pDevice->playback.internalSampleRate = pActualFormat->Format.nSamplesPerSec; + /* We now have enough information to start setting some output properties. */ + pDescriptorPlayback->format = ma_format_from_WAVEFORMATEX((WAVEFORMATEX*)pActualFormat); + pDescriptorPlayback->channels = pActualFormat->Format.nChannels; + pDescriptorPlayback->sampleRate = pActualFormat->Format.nSamplesPerSec; /* Get the internal channel map based on the channel mask. */ if (pActualFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - ma_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + ma_channel_mask_to_channel_map__win32(pActualFormat->dwChannelMask, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); } else { - ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + ma_channel_mask_to_channel_map__win32(wf.dwChannelMask, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); } /* The size of the buffer must be a clean multiple of the period count. */ - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, pDevice->playback.internalSampleRate); + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__dsound(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); + periodCount = (pDescriptorPlayback->periodCount > 0) ? pDescriptorPlayback->periodCount : MA_DEFAULT_PERIODS; /* 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 @@ -15363,7 +17477,7 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co MA_ZERO_OBJECT(&descDS); descDS.dwSize = sizeof(descDS); descDS.dwFlags = MA_DSBCAPS_CTRLPOSITIONNOTIFY | MA_DSBCAPS_GLOBALFOCUS | MA_DSBCAPS_GETCURRENTPOSITION2; - descDS.dwBufferBytes = periodSizeInFrames * pConfig->periods * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + descDS.dwBufferBytes = periodSizeInFrames * periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels); descDS.lpwfxFormat = (WAVEFORMATEX*)&wf; hr = ma_IDirectSound_CreateSoundBuffer((ma_IDirectSound*)pDevice->dsound.pPlayback, &descDS, (ma_IDirectSoundBuffer**)&pDevice->dsound.pPlaybackBuffer, NULL); if (FAILED(hr)) { @@ -15372,16 +17486,15 @@ static ma_result ma_device_init__dsound(ma_context* pContext, const ma_device_co } /* DirectSound should give us a buffer exactly the size we asked for. */ - pDevice->playback.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->playback.internalPeriods = pConfig->periods; + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodCount = periodCount; } - (void)pContext; return MA_SUCCESS; } -static ma_result ma_device_main_loop__dsound(ma_device* pDevice) +static ma_result ma_device_data_loop__dsound(ma_device* pDevice) { ma_result result = MA_SUCCESS; ma_uint32 bpfDeviceCapture = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); @@ -15413,8 +17526,8 @@ static ma_result ma_device_main_loop__dsound(ma_device* pDevice) return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSoundCaptureBuffer_Start() failed.", MA_FAILED_TO_START_BACKEND_DEVICE); } } - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED) { + + while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { switch (pDevice->type) { case ma_device_type_duplex: @@ -15608,7 +17721,7 @@ static ma_result ma_device_main_loop__dsound(ma_device* pDevice) outputFramesInClientFormatConsumed += (ma_uint32)convertedFrameCountOut; framesWrittenThisIteration = (ma_uint32)convertedFrameCountOut; } - + hr = ma_IDirectSoundBuffer_Unlock((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer, pMappedDeviceBufferPlayback, framesWrittenThisIteration*bpfDevicePlayback, NULL, 0); if (FAILED(hr)) { @@ -15621,7 +17734,7 @@ static ma_result ma_device_main_loop__dsound(ma_device* pDevice) virtualWriteCursorInBytesPlayback = 0; virtualWriteCursorLoopFlagPlayback = !virtualWriteCursorLoopFlagPlayback; } - + /* We may need to start the device. We want two full periods to be written before starting the playback device. Having an extra period adds a bit of a buffer to prevent the playback buffer from getting starved. @@ -15819,7 +17932,7 @@ static ma_result ma_device_main_loop__dsound(ma_device* pDevice) virtualWriteCursorInBytesPlayback = 0; virtualWriteCursorLoopFlagPlayback = !virtualWriteCursorLoopFlagPlayback; } - + /* We may need to start the device. We want two full periods to be written before starting the playback device. Having an extra period adds a bit of a buffer to prevent the playback buffer from getting starved. @@ -15914,7 +18027,7 @@ static ma_result ma_context_uninit__dsound(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__dsound(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__dsound(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { MA_ASSERT(pContext != NULL); @@ -15930,15 +18043,17 @@ static ma_result ma_context_init__dsound(const ma_context_config* pConfig, ma_co pContext->dsound.DirectSoundCaptureCreate = ma_dlsym(pContext, pContext->dsound.hDSoundDLL, "DirectSoundCaptureCreate"); pContext->dsound.DirectSoundCaptureEnumerateA = ma_dlsym(pContext, pContext->dsound.hDSoundDLL, "DirectSoundCaptureEnumerateA"); - pContext->onUninit = ma_context_uninit__dsound; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__dsound; - pContext->onEnumDevices = ma_context_enumerate_devices__dsound; - pContext->onGetDeviceInfo = ma_context_get_device_info__dsound; - pContext->onDeviceInit = ma_device_init__dsound; - pContext->onDeviceUninit = ma_device_uninit__dsound; - pContext->onDeviceStart = NULL; /* Not used. Started in onDeviceMainLoop. */ - pContext->onDeviceStop = NULL; /* Not used. Stopped in onDeviceMainLoop. */ - pContext->onDeviceMainLoop = ma_device_main_loop__dsound; + pCallbacks->onContextInit = ma_context_init__dsound; + pCallbacks->onContextUninit = ma_context_uninit__dsound; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__dsound; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__dsound; + pCallbacks->onDeviceInit = ma_device_init__dsound; + pCallbacks->onDeviceUninit = ma_device_uninit__dsound; + pCallbacks->onDeviceStart = NULL; /* Not used. Started in onDeviceDataLoop. */ + pCallbacks->onDeviceStop = NULL; /* Not used. Stopped in onDeviceDataLoop. */ + pCallbacks->onDeviceRead = NULL; /* Not used. Data is read directly in onDeviceDataLoop. */ + pCallbacks->onDeviceWrite = NULL; /* Not used. Data is written directly in onDeviceDataLoop. */ + pCallbacks->onDeviceDataLoop = ma_device_data_loop__dsound; return MA_SUCCESS; } @@ -16139,6 +18254,8 @@ static ma_result ma_get_best_info_from_formats_flags__winmm(DWORD dwFormats, WOR static ma_result ma_formats_flags_to_WAVEFORMATEX__winmm(DWORD dwFormats, WORD channels, WAVEFORMATEX* pWF) { + ma_result result; + MA_ASSERT(pWF != NULL); MA_ZERO_OBJECT(pWF); @@ -16149,62 +18266,9 @@ static ma_result ma_formats_flags_to_WAVEFORMATEX__winmm(DWORD dwFormats, WORD c pWF->nChannels = 2; } - if (channels == 1) { - pWF->wBitsPerSample = 16; - if ((dwFormats & WAVE_FORMAT_48M16) != 0) { - pWF->nSamplesPerSec = 48000; - } else if ((dwFormats & WAVE_FORMAT_44M16) != 0) { - pWF->nSamplesPerSec = 44100; - } else if ((dwFormats & WAVE_FORMAT_2M16) != 0) { - pWF->nSamplesPerSec = 22050; - } else if ((dwFormats & WAVE_FORMAT_1M16) != 0) { - pWF->nSamplesPerSec = 11025; - } else if ((dwFormats & WAVE_FORMAT_96M16) != 0) { - pWF->nSamplesPerSec = 96000; - } else { - pWF->wBitsPerSample = 8; - if ((dwFormats & WAVE_FORMAT_48M08) != 0) { - pWF->nSamplesPerSec = 48000; - } else if ((dwFormats & WAVE_FORMAT_44M08) != 0) { - pWF->nSamplesPerSec = 44100; - } else if ((dwFormats & WAVE_FORMAT_2M08) != 0) { - pWF->nSamplesPerSec = 22050; - } else if ((dwFormats & WAVE_FORMAT_1M08) != 0) { - pWF->nSamplesPerSec = 11025; - } else if ((dwFormats & WAVE_FORMAT_96M08) != 0) { - pWF->nSamplesPerSec = 96000; - } else { - return MA_FORMAT_NOT_SUPPORTED; - } - } - } else { - pWF->wBitsPerSample = 16; - if ((dwFormats & WAVE_FORMAT_48S16) != 0) { - pWF->nSamplesPerSec = 48000; - } else if ((dwFormats & WAVE_FORMAT_44S16) != 0) { - pWF->nSamplesPerSec = 44100; - } else if ((dwFormats & WAVE_FORMAT_2S16) != 0) { - pWF->nSamplesPerSec = 22050; - } else if ((dwFormats & WAVE_FORMAT_1S16) != 0) { - pWF->nSamplesPerSec = 11025; - } else if ((dwFormats & WAVE_FORMAT_96S16) != 0) { - pWF->nSamplesPerSec = 96000; - } else { - pWF->wBitsPerSample = 8; - if ((dwFormats & WAVE_FORMAT_48S08) != 0) { - pWF->nSamplesPerSec = 48000; - } else if ((dwFormats & WAVE_FORMAT_44S08) != 0) { - pWF->nSamplesPerSec = 44100; - } else if ((dwFormats & WAVE_FORMAT_2S08) != 0) { - pWF->nSamplesPerSec = 22050; - } else if ((dwFormats & WAVE_FORMAT_1S08) != 0) { - pWF->nSamplesPerSec = 11025; - } else if ((dwFormats & WAVE_FORMAT_96S08) != 0) { - pWF->nSamplesPerSec = 96000; - } else { - return MA_FORMAT_NOT_SUPPORTED; - } - } + result = ma_get_best_info_from_formats_flags__winmm(dwFormats, channels, &pWF->wBitsPerSample, &pWF->nSamplesPerSec); + if (result != MA_SUCCESS) { + return result; } pWF->nBlockAlign = (WORD)(pWF->nChannels * pWF->wBitsPerSample / 8); @@ -16225,7 +18289,7 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, /* Name / Description - + Unfortunately the name specified in WAVE(OUT/IN)CAPS2 is limited to 31 characters. This results in an unprofessional looking situation where the names of the devices are truncated. To help work around this, we need to look at the name GUID and try looking in the registry for the full name. If we can't find it there, we need to just fall back to the default name. @@ -16290,22 +18354,21 @@ static ma_result ma_context_get_device_info_from_WAVECAPS(ma_context* pContext, return result; } - pDeviceInfo->minChannels = pCaps->wChannels; - pDeviceInfo->maxChannels = pCaps->wChannels; - pDeviceInfo->minSampleRate = sampleRate; - pDeviceInfo->maxSampleRate = sampleRate; - pDeviceInfo->formatCount = 1; if (bitsPerSample == 8) { - pDeviceInfo->formats[0] = ma_format_u8; + pDeviceInfo->nativeDataFormats[0].format = ma_format_u8; } else if (bitsPerSample == 16) { - pDeviceInfo->formats[0] = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; } else if (bitsPerSample == 24) { - pDeviceInfo->formats[0] = ma_format_s24; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s24; } else if (bitsPerSample == 32) { - pDeviceInfo->formats[0] = ma_format_s32; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s32; } else { return MA_FORMAT_NOT_SUPPORTED; } + pDeviceInfo->nativeDataFormats[0].channels = pCaps->wChannels; + pDeviceInfo->nativeDataFormats[0].sampleRate = sampleRate; + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormatCount = 1; return MA_SUCCESS; } @@ -16321,7 +18384,7 @@ static ma_result ma_context_get_device_info_from_WAVEOUTCAPS2(ma_context* pConte MA_COPY_MEMORY(caps.szPname, pCaps->szPname, sizeof(caps.szPname)); caps.dwFormats = pCaps->dwFormats; caps.wChannels = pCaps->wChannels; - caps.NameGuid = pCaps->NameGuid; + caps.NameGuid = pCaps->NameGuid; return ma_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); } @@ -16336,21 +18399,11 @@ static ma_result ma_context_get_device_info_from_WAVEINCAPS2(ma_context* pContex MA_COPY_MEMORY(caps.szPname, pCaps->szPname, sizeof(caps.szPname)); caps.dwFormats = pCaps->dwFormats; caps.wChannels = pCaps->wChannels; - caps.NameGuid = pCaps->NameGuid; + caps.NameGuid = pCaps->NameGuid; return ma_context_get_device_info_from_WAVECAPS(pContext, &caps, pDeviceInfo); } -static ma_bool32 ma_context_is_device_id_equal__winmm(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return pID0->winmm == pID1->winmm; -} - static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { UINT playbackDeviceCount; @@ -16376,6 +18429,11 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en MA_ZERO_OBJECT(&deviceInfo); deviceInfo.id.winmm = iPlaybackDevice; + /* The first enumerated device is the default device. */ + if (iPlaybackDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + if (ma_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, &deviceInfo) == MA_SUCCESS) { ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); if (cbResult == MA_FALSE) { @@ -16400,6 +18458,11 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en MA_ZERO_OBJECT(&deviceInfo); deviceInfo.id.winmm = iCaptureDevice; + /* The first enumerated device is the default device. */ + if (iCaptureDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + if (ma_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, &deviceInfo) == MA_SUCCESS) { ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); if (cbResult == MA_FALSE) { @@ -16412,16 +18475,12 @@ static ma_result ma_context_enumerate_devices__winmm(ma_context* pContext, ma_en return MA_SUCCESS; } -static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { UINT winMMDeviceID; MA_ASSERT(pContext != NULL); - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - winMMDeviceID = 0; if (pDeviceID != NULL) { winMMDeviceID = (UINT)pDeviceID->winmm; @@ -16429,12 +18488,17 @@ static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_devi pDeviceInfo->id.winmm = winMMDeviceID; + /* The first ID is the default device. */ + if (winMMDeviceID == 0) { + pDeviceInfo->isDefault = MA_TRUE; + } + if (deviceType == ma_device_type_playback) { MMRESULT result; MA_WAVEOUTCAPS2A caps; MA_ZERO_OBJECT(&caps); - + result = ((MA_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(winMMDeviceID, (WAVEOUTCAPSA*)&caps, sizeof(caps)); if (result == MMSYSERR_NOERROR) { return ma_context_get_device_info_from_WAVEOUTCAPS2(pContext, &caps, pDeviceInfo); @@ -16444,7 +18508,7 @@ static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_devi MA_WAVEINCAPS2A caps; MA_ZERO_OBJECT(&caps); - + result = ((MA_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(winMMDeviceID, (WAVEINCAPSA*)&caps, sizeof(caps)); if (result == MMSYSERR_NOERROR) { return ma_context_get_device_info_from_WAVEINCAPS2(pContext, &caps, pDeviceInfo); @@ -16455,7 +18519,7 @@ static ma_result ma_context_get_device_info__winmm(ma_context* pContext, ma_devi } -static void ma_device_uninit__winmm(ma_device* pDevice) +static ma_result ma_device_uninit__winmm(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -16473,9 +18537,25 @@ static void ma_device_uninit__winmm(ma_device* pDevice) ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); MA_ZERO_OBJECT(&pDevice->winmm); /* Safety. */ + + return MA_SUCCESS; } -static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__winmm(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +{ + /* WinMM has a minimum period size of 40ms. */ + ma_uint32 minPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(40, nativeSampleRate); + ma_uint32 periodSizeInFrames; + + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, nativeSampleRate, performanceProfile); + if (periodSizeInFrames < minPeriodSizeInFrames) { + periodSizeInFrames = minPeriodSizeInFrames; + } + + return periodSizeInFrames; +} + +static ma_result ma_device_init__winmm(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { const char* errorMsg = ""; ma_result errorCode = MA_ERROR; @@ -16483,9 +18563,9 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con ma_uint32 heapSize; UINT winMMDeviceIDPlayback = 0; UINT winMMDeviceIDCapture = 0; - ma_uint32 periodSizeInMilliseconds; MA_ASSERT(pDevice != NULL); + MA_ZERO_OBJECT(&pDevice->winmm); if (pConfig->deviceType == ma_device_type_loopback) { @@ -16493,31 +18573,16 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con } /* No exlusive mode with WinMM. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } - periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; - if (periodSizeInMilliseconds == 0) { - periodSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->periodSizeInFrames, pConfig->sampleRate); + if (pDescriptorPlayback->pDeviceID != NULL) { + winMMDeviceIDPlayback = (UINT)pDescriptorPlayback->pDeviceID->winmm; } - - /* WinMM has horrible latency. */ - if (pDevice->usingDefaultBufferSize) { - if (pConfig->performanceProfile == ma_performance_profile_low_latency) { - periodSizeInMilliseconds = 40; - } else { - periodSizeInMilliseconds = 400; - } - } - - - if (pConfig->playback.pDeviceID != NULL) { - winMMDeviceIDPlayback = (UINT)pConfig->playback.pDeviceID->winmm; - } - if (pConfig->capture.pDeviceID != NULL) { - winMMDeviceIDCapture = (UINT)pConfig->capture.pDeviceID->winmm; + if (pDescriptorCapture->pDeviceID != NULL) { + winMMDeviceIDCapture = (UINT)pDescriptorCapture->pDeviceID->winmm; } /* The capture device needs to be initialized first. */ @@ -16534,7 +18599,7 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con } /* The format should be based on the device's actual format. */ - if (((MA_PFN_waveInGetDevCapsA)pContext->winmm.waveInGetDevCapsA)(winMMDeviceIDCapture, &caps, sizeof(caps)) != MMSYSERR_NOERROR) { + if (((MA_PFN_waveInGetDevCapsA)pDevice->pContext->winmm.waveInGetDevCapsA)(winMMDeviceIDCapture, &caps, sizeof(caps)) != MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MA_FORMAT_NOT_SUPPORTED; goto on_error; } @@ -16551,12 +18616,12 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con goto on_error; } - pDevice->capture.internalFormat = ma_format_from_WAVEFORMATEX(&wf); - pDevice->capture.internalChannels = wf.nChannels; - pDevice->capture.internalSampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); - pDevice->capture.internalPeriods = pConfig->periods; - pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, pDevice->capture.internalSampleRate); + pDescriptorCapture->format = ma_format_from_WAVEFORMATEX(&wf); + pDescriptorCapture->channels = wf.nChannels; + pDescriptorCapture->sampleRate = wf.nSamplesPerSec; + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorCapture->channels, pDescriptorCapture->channelMap); + pDescriptorCapture->periodCount = pDescriptorCapture->periodCount; + pDescriptorCapture->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { @@ -16572,7 +18637,7 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con } /* The format should be based on the device's actual format. */ - if (((MA_PFN_waveOutGetDevCapsA)pContext->winmm.waveOutGetDevCapsA)(winMMDeviceIDPlayback, &caps, sizeof(caps)) != MMSYSERR_NOERROR) { + if (((MA_PFN_waveOutGetDevCapsA)pDevice->pContext->winmm.waveOutGetDevCapsA)(winMMDeviceIDPlayback, &caps, sizeof(caps)) != MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to retrieve internal device caps.", errorCode = MA_FORMAT_NOT_SUPPORTED; goto on_error; } @@ -16583,34 +18648,34 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con goto on_error; } - resultMM = ((MA_PFN_waveOutOpen)pContext->winmm.waveOutOpen)((LPHWAVEOUT)&pDevice->winmm.hDevicePlayback, winMMDeviceIDPlayback, &wf, (DWORD_PTR)pDevice->winmm.hEventPlayback, (DWORD_PTR)pDevice, CALLBACK_EVENT | WAVE_ALLOWSYNC); + resultMM = ((MA_PFN_waveOutOpen)pDevice->pContext->winmm.waveOutOpen)((LPHWAVEOUT)&pDevice->winmm.hDevicePlayback, winMMDeviceIDPlayback, &wf, (DWORD_PTR)pDevice->winmm.hEventPlayback, (DWORD_PTR)pDevice, CALLBACK_EVENT | WAVE_ALLOWSYNC); if (resultMM != MMSYSERR_NOERROR) { errorMsg = "[WinMM] Failed to open playback device.", errorCode = MA_FAILED_TO_OPEN_BACKEND_DEVICE; goto on_error; } - pDevice->playback.internalFormat = ma_format_from_WAVEFORMATEX(&wf); - pDevice->playback.internalChannels = wf.nChannels; - pDevice->playback.internalSampleRate = wf.nSamplesPerSec; - ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); - pDevice->playback.internalPeriods = pConfig->periods; - pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, pDevice->playback.internalSampleRate); + pDescriptorPlayback->format = ma_format_from_WAVEFORMATEX(&wf); + pDescriptorPlayback->channels = wf.nChannels; + pDescriptorPlayback->sampleRate = wf.nSamplesPerSec; + ma_get_standard_channel_map(ma_standard_channel_map_microsoft, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + pDescriptorPlayback->periodCount = pDescriptorPlayback->periodCount; + pDescriptorPlayback->periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__winmm(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); } /* The heap allocated data is allocated like so: - + [Capture WAVEHDRs][Playback WAVEHDRs][Capture Intermediary Buffer][Playback Intermediary Buffer] */ heapSize = 0; if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - heapSize += sizeof(WAVEHDR)*pDevice->capture.internalPeriods + (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + heapSize += sizeof(WAVEHDR)*pDescriptorCapture->periodCount + (pDescriptorCapture->periodSizeInFrames * pDescriptorCapture->periodCount * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels)); } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - heapSize += sizeof(WAVEHDR)*pDevice->playback.internalPeriods + (pDevice->playback.internalPeriodSizeInFrames*pDevice->playback.internalPeriods*ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); + heapSize += sizeof(WAVEHDR)*pDescriptorPlayback->periodCount + (pDescriptorPlayback->periodSizeInFrames * pDescriptorPlayback->periodCount * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels)); } - pDevice->winmm._pHeapData = (ma_uint8*)ma__calloc_from_callbacks(heapSize, &pContext->allocationCallbacks); + pDevice->winmm._pHeapData = (ma_uint8*)ma__calloc_from_callbacks(heapSize, &pDevice->pContext->allocationCallbacks); if (pDevice->winmm._pHeapData == NULL) { errorMsg = "[WinMM] Failed to allocate memory for the intermediary buffer.", errorCode = MA_OUT_OF_MEMORY; goto on_error; @@ -16623,21 +18688,21 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con if (pConfig->deviceType == ma_device_type_capture) { pDevice->winmm.pWAVEHDRCapture = pDevice->winmm._pHeapData; - pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDevice->capture.internalPeriods)); + pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount)); } else { pDevice->winmm.pWAVEHDRCapture = pDevice->winmm._pHeapData; - pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDevice->capture.internalPeriods + pDevice->playback.internalPeriods)); + pDevice->winmm.pIntermediaryBufferCapture = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)); } /* Prepare headers. */ - for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { - ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalFormat, pDevice->capture.internalChannels); + for (iPeriod = 0; iPeriod < pDescriptorCapture->periodCount; ++iPeriod) { + ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->format, pDescriptorCapture->channels); ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBufferCapture + (periodSizeInBytes*iPeriod)); ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwBufferLength = periodSizeInBytes; ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwFlags = 0L; ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwLoops = 0L; - ((MA_PFN_waveInPrepareHeader)pContext->winmm.waveInPrepareHeader)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); + ((MA_PFN_waveInPrepareHeader)pDevice->pContext->winmm.waveInPrepareHeader)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); /* The user data of the WAVEHDR structure is a single flag the controls whether or not it is ready for writing. Consider it to be named "isLocked". A value of 0 means @@ -16646,26 +18711,27 @@ static ma_result ma_device_init__winmm(ma_context* pContext, const ma_device_con ((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod].dwUser = 0; } } + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ma_uint32 iPeriod; if (pConfig->deviceType == ma_device_type_playback) { pDevice->winmm.pWAVEHDRPlayback = pDevice->winmm._pHeapData; - pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*pDevice->playback.internalPeriods); + pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*pDescriptorPlayback->periodCount); } else { - pDevice->winmm.pWAVEHDRPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDevice->capture.internalPeriods)); - pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDevice->capture.internalPeriods + pDevice->playback.internalPeriods)) + (pDevice->capture.internalPeriodSizeInFrames*pDevice->capture.internalPeriods*ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); + pDevice->winmm.pWAVEHDRPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount)); + pDevice->winmm.pIntermediaryBufferPlayback = pDevice->winmm._pHeapData + (sizeof(WAVEHDR)*(pDescriptorCapture->periodCount + pDescriptorPlayback->periodCount)) + (pDescriptorCapture->periodSizeInFrames*pDescriptorCapture->periodCount*ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels)); } /* Prepare headers. */ - for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; ++iPeriod) { - ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDevice->playback.internalPeriodSizeInFrames, pDevice->playback.internalFormat, pDevice->playback.internalChannels); + for (iPeriod = 0; iPeriod < pDescriptorPlayback->periodCount; ++iPeriod) { + ma_uint32 periodSizeInBytes = ma_get_period_size_in_bytes(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->format, pDescriptorPlayback->channels); ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].lpData = (LPSTR)(pDevice->winmm.pIntermediaryBufferPlayback + (periodSizeInBytes*iPeriod)); ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwBufferLength = periodSizeInBytes; ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwFlags = 0L; ((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod].dwLoops = 0L; - ((MA_PFN_waveOutPrepareHeader)pContext->winmm.waveOutPrepareHeader)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(WAVEHDR)); + ((MA_PFN_waveOutPrepareHeader)pDevice->pContext->winmm.waveOutPrepareHeader)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(WAVEHDR)); /* The user data of the WAVEHDR structure is a single flag the controls whether or not it is ready for writing. Consider it to be named "isLocked". A value of 0 means @@ -16681,29 +18747,68 @@ on_error: if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { if (pDevice->winmm.pWAVEHDRCapture != NULL) { ma_uint32 iPeriod; - for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { - ((MA_PFN_waveInUnprepareHeader)pContext->winmm.waveInUnprepareHeader)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); + for (iPeriod = 0; iPeriod < pDescriptorCapture->periodCount; ++iPeriod) { + ((MA_PFN_waveInUnprepareHeader)pDevice->pContext->winmm.waveInUnprepareHeader)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((WAVEHDR*)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); } } - ((MA_PFN_waveInClose)pContext->winmm.waveInClose)((HWAVEIN)pDevice->winmm.hDeviceCapture); + ((MA_PFN_waveInClose)pDevice->pContext->winmm.waveInClose)((HWAVEIN)pDevice->winmm.hDeviceCapture); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { if (pDevice->winmm.pWAVEHDRCapture != NULL) { ma_uint32 iPeriod; - for (iPeriod = 0; iPeriod < pDevice->playback.internalPeriods; ++iPeriod) { - ((MA_PFN_waveOutUnprepareHeader)pContext->winmm.waveOutUnprepareHeader)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(WAVEHDR)); + for (iPeriod = 0; iPeriod < pDescriptorPlayback->periodCount; ++iPeriod) { + ((MA_PFN_waveOutUnprepareHeader)pDevice->pContext->winmm.waveOutUnprepareHeader)((HWAVEOUT)pDevice->winmm.hDevicePlayback, &((WAVEHDR*)pDevice->winmm.pWAVEHDRPlayback)[iPeriod], sizeof(WAVEHDR)); } } - ((MA_PFN_waveOutClose)pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); + ((MA_PFN_waveOutClose)pDevice->pContext->winmm.waveOutClose)((HWAVEOUT)pDevice->winmm.hDevicePlayback); } - ma__free_from_callbacks(pDevice->winmm._pHeapData, &pContext->allocationCallbacks); + ma__free_from_callbacks(pDevice->winmm._pHeapData, &pDevice->pContext->allocationCallbacks); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, errorMsg, errorCode); } +static ma_result ma_device_start__winmm(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + MMRESULT resultMM; + WAVEHDR* pWAVEHDR; + ma_uint32 iPeriod; + + pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRCapture; + + /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ + ResetEvent((HANDLE)pDevice->winmm.hEventCapture); + + /* To start the device we attach all of the buffers and then start it. As the buffers are filled with data we will get notifications. */ + for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { + resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); + if (resultMM != MMSYSERR_NOERROR) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture.", ma_result_from_MMRESULT(resultMM)); + } + + /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */ + pWAVEHDR[iPeriod].dwUser = 1; /* 1 = locked. */ + } + + /* Capture devices need to be explicitly started, unlike playback devices. */ + resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDeviceCapture); + if (resultMM != MMSYSERR_NOERROR) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device.", ma_result_from_MMRESULT(resultMM)); + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* Don't need to do anything for playback. It'll be started automatically in ma_device_start__winmm(). */ + } + + return MA_SUCCESS; +} + static ma_result ma_device_stop__winmm(ma_device* pDevice) { MMRESULT resultMM; @@ -16830,7 +18935,7 @@ static ma_result ma_device_write__winmm(ma_device* pDevice, const void* pPCMFram } /* If the device has been stopped we need to break. */ - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { break; } } @@ -16919,7 +19024,7 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ } /* If the device has been stopped we need to break. */ - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { break; } } @@ -16931,201 +19036,6 @@ static ma_result ma_device_read__winmm(ma_device* pDevice, void* pPCMFrames, ma_ return result; } -static ma_result ma_device_main_loop__winmm(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - MA_ASSERT(pDevice != NULL); - - /* The capture device needs to be started immediately. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - MMRESULT resultMM; - WAVEHDR* pWAVEHDR; - ma_uint32 iPeriod; - - pWAVEHDR = (WAVEHDR*)pDevice->winmm.pWAVEHDRCapture; - - /* Make sure the event is reset to a non-signaled state to ensure we don't prematurely return from WaitForSingleObject(). */ - ResetEvent((HANDLE)pDevice->winmm.hEventCapture); - - /* To start the device we attach all of the buffers and then start it. As the buffers are filled with data we will get notifications. */ - for (iPeriod = 0; iPeriod < pDevice->capture.internalPeriods; ++iPeriod) { - resultMM = ((MA_PFN_waveInAddBuffer)pDevice->pContext->winmm.waveInAddBuffer)((HWAVEIN)pDevice->winmm.hDeviceCapture, &((LPWAVEHDR)pDevice->winmm.pWAVEHDRCapture)[iPeriod], sizeof(WAVEHDR)); - if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to attach input buffers to capture device in preparation for capture.", ma_result_from_MMRESULT(resultMM)); - } - - /* Make sure all of the buffers start out locked. We don't want to access them until the backend tells us we can. */ - pWAVEHDR[iPeriod].dwUser = 1; /* 1 = locked. */ - } - - /* Capture devices need to be explicitly started, unlike playback devices. */ - resultMM = ((MA_PFN_waveInStart)pDevice->pContext->winmm.waveInStart)((HWAVEIN)pDevice->winmm.hDeviceCapture); - if (resultMM != MMSYSERR_NOERROR) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[WinMM] Failed to start backend device.", ma_result_from_MMRESULT(resultMM)); - } - } - - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__winmm(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__winmm(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__winmm()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__winmm(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__winmm(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - - /* Here is where the device is started. */ - ma_device_stop__winmm(pDevice); - - return result; -} - static ma_result ma_context_uninit__winmm(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -17135,7 +19045,7 @@ static ma_result ma_context_uninit__winmm(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__winmm(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__winmm(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { MA_ASSERT(pContext != NULL); @@ -17164,15 +19074,17 @@ static ma_result ma_context_init__winmm(const ma_context_config* pConfig, ma_con pContext->winmm.waveInStart = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInStart"); pContext->winmm.waveInReset = ma_dlsym(pContext, pContext->winmm.hWinMM, "waveInReset"); - pContext->onUninit = ma_context_uninit__winmm; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__winmm; - pContext->onEnumDevices = ma_context_enumerate_devices__winmm; - pContext->onGetDeviceInfo = ma_context_get_device_info__winmm; - pContext->onDeviceInit = ma_device_init__winmm; - pContext->onDeviceUninit = ma_device_uninit__winmm; - pContext->onDeviceStart = NULL; /* Not used with synchronous backends. */ - pContext->onDeviceStop = NULL; /* Not used with synchronous backends. */ - pContext->onDeviceMainLoop = ma_device_main_loop__winmm; + pCallbacks->onContextInit = ma_context_init__winmm; + pCallbacks->onContextUninit = ma_context_uninit__winmm; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__winmm; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__winmm; + pCallbacks->onDeviceInit = ma_device_init__winmm; + pCallbacks->onDeviceUninit = ma_device_uninit__winmm; + pCallbacks->onDeviceStart = ma_device_start__winmm; + pCallbacks->onDeviceStop = ma_device_stop__winmm; + pCallbacks->onDeviceRead = ma_device_read__winmm; + pCallbacks->onDeviceWrite = ma_device_write__winmm; + pCallbacks->onDeviceDataLoop = NULL; /* This is a blocking read-write API, so this can be NULL since miniaudio will manage the audio thread for us. */ return MA_SUCCESS; } @@ -17408,8 +19320,11 @@ typedef int (* ma_snd_pcm_hw_params_any_proc) ( typedef int (* ma_snd_pcm_hw_params_set_format_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_t val); typedef int (* ma_snd_pcm_hw_params_set_format_first_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_t *format); typedef void (* ma_snd_pcm_hw_params_get_format_mask_proc) (ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_mask_t *mask); +typedef int (* ma_snd_pcm_hw_params_set_channels_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val); typedef int (* ma_snd_pcm_hw_params_set_channels_near_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *val); +typedef int (* ma_snd_pcm_hw_params_set_channels_minmax_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *minimum, unsigned int *maximum); typedef int (* ma_snd_pcm_hw_params_set_rate_resample_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val); +typedef int (* ma_snd_pcm_hw_params_set_rate_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val, int dir); typedef int (* ma_snd_pcm_hw_params_set_rate_near_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); typedef int (* ma_snd_pcm_hw_params_set_buffer_size_near_proc)(ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_uframes_t *val); typedef int (* ma_snd_pcm_hw_params_set_periods_near_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); @@ -17424,6 +19339,9 @@ typedef int (* ma_snd_pcm_hw_params_get_rate_max_proc) ( typedef int (* ma_snd_pcm_hw_params_get_buffer_size_proc) (const ma_snd_pcm_hw_params_t *params, ma_snd_pcm_uframes_t *val); typedef int (* ma_snd_pcm_hw_params_get_periods_proc) (const ma_snd_pcm_hw_params_t *params, unsigned int *val, int *dir); typedef int (* ma_snd_pcm_hw_params_get_access_proc) (const ma_snd_pcm_hw_params_t *params, ma_snd_pcm_access_t *_access); +typedef int (* ma_snd_pcm_hw_params_test_format_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, ma_snd_pcm_format_t val); +typedef int (* ma_snd_pcm_hw_params_test_channels_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val); +typedef int (* ma_snd_pcm_hw_params_test_rate_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params, unsigned int val, int dir); typedef int (* ma_snd_pcm_hw_params_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_hw_params_t *params); typedef size_t (* ma_snd_pcm_sw_params_sizeof_proc) (void); typedef int (* ma_snd_pcm_sw_params_current_proc) (ma_snd_pcm_t *pcm, ma_snd_pcm_sw_params_t *params); @@ -17476,36 +19394,6 @@ static const char* g_maBlacklistedCaptureDeviceNamesALSA[] = { }; -/* -This array allows miniaudio to control device-specific default buffer sizes. This uses a scaling factor. Order is important. If -any part of the string is present in the device's name, the associated scale will be used. -*/ -static struct -{ - const char* name; - float scale; -} g_maDefaultBufferSizeScalesALSA[] = { - {"bcm2835 IEC958/HDMI", 2.0f}, - {"bcm2835 ALSA", 2.0f} -}; - -static float ma_find_default_buffer_size_scale__alsa(const char* deviceName) -{ - size_t i; - - if (deviceName == NULL) { - return 1; - } - - for (i = 0; i < ma_countof(g_maDefaultBufferSizeScalesALSA); ++i) { - if (strstr(g_maDefaultBufferSizeScalesALSA[i].name, deviceName) != NULL) { - return g_maDefaultBufferSizeScalesALSA[i].scale; - } - } - - return 1; -} - static ma_snd_pcm_format_t ma_convert_ma_format_to_alsa_format(ma_format format) { ma_snd_pcm_format_t ALSAFormats[] = { @@ -17851,7 +19739,7 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } else { /* We're trying to open a specific device. There's a few things to consider here: - + miniaudio 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 miniaudio 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"). @@ -17903,16 +19791,6 @@ static ma_result ma_context_open_pcm__alsa(ma_context* pContext, ma_share_mode s } -static ma_bool32 ma_context_is_device_id_equal__alsa(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return ma_strcmp(pID0->alsa, pID1->alsa) == 0; -} - static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { int resultALSA; @@ -17994,12 +19872,21 @@ static ma_result ma_context_enumerate_devices__alsa(ma_context* pContext, ma_enu MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.id.alsa, sizeof(deviceInfo.id.alsa), hwid, (size_t)-1); + /* + There's no good way to determine whether or not a device is the default on Linux. We're just going to do something simple and + just use the name of "default" as the indicator. + */ + if (ma_strcmp(deviceInfo.id.alsa, "default") == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + /* 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 @@ -18088,11 +19975,13 @@ static ma_bool32 ma_context_get_device_info_enum_callback__alsa(ma_context* pCon ma_context_get_device_info_enum_callback_data__alsa* pData = (ma_context_get_device_info_enum_callback_data__alsa*)pUserData; MA_ASSERT(pData != NULL); + (void)pContext; + if (pData->pDeviceID == NULL && ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) { ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); pData->foundDevice = MA_TRUE; } else { - if (pData->deviceType == deviceType && ma_context_is_device_id_equal__alsa(pContext, pData->pDeviceID, &pDeviceInfo->id)) { + if (pData->deviceType == deviceType && (pData->pDeviceID != NULL && ma_strcmp(pData->pDeviceID->alsa, pDeviceInfo->id.alsa) == 0)) { ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pDeviceInfo->name, (size_t)-1); pData->foundDevice = MA_TRUE; } @@ -18102,22 +19991,69 @@ static ma_bool32 ma_context_get_device_info_enum_callback__alsa(ma_context* pCon return !pData->foundDevice; } -static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static void ma_context_test_rate_and_add_native_data_format__alsa(ma_context* pContext, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags, ma_device_info* pDeviceInfo) +{ + MA_ASSERT(pPCM != NULL); + MA_ASSERT(pHWParams != NULL); + MA_ASSERT(pDeviceInfo != NULL); + + if (pDeviceInfo->nativeDataFormatCount < ma_countof(pDeviceInfo->nativeDataFormats) && ((ma_snd_pcm_hw_params_test_rate_proc)pContext->alsa.snd_pcm_hw_params_test_rate)(pPCM, pHWParams, sampleRate, 0) == 0) { + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; + pDeviceInfo->nativeDataFormatCount += 1; + } +} + +static void ma_context_iterate_rates_and_add_native_data_format__alsa(ma_context* pContext, ma_snd_pcm_t* pPCM, ma_snd_pcm_hw_params_t* pHWParams, ma_format format, ma_uint32 channels, ma_uint32 flags, ma_device_info* pDeviceInfo) +{ + ma_uint32 iSampleRate; + unsigned int minSampleRate; + unsigned int maxSampleRate; + int sampleRateDir; /* Not used. Just passed into snd_pcm_hw_params_get_rate_min/max(). */ + + /* There could be a range. */ + ((ma_snd_pcm_hw_params_get_rate_min_proc)pContext->alsa.snd_pcm_hw_params_get_rate_min)(pHWParams, &minSampleRate, &sampleRateDir); + ((ma_snd_pcm_hw_params_get_rate_max_proc)pContext->alsa.snd_pcm_hw_params_get_rate_max)(pHWParams, &maxSampleRate, &sampleRateDir); + + /* Make sure our sample rates are clamped to sane values. Stupid devices like "pulse" will reports rates like "1" which is ridiculus. */ + minSampleRate = ma_clamp(minSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); + maxSampleRate = ma_clamp(maxSampleRate, (unsigned int)ma_standard_sample_rate_min, (unsigned int)ma_standard_sample_rate_max); + + for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); iSampleRate += 1) { + ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iSampleRate]; + + if (standardSampleRate >= minSampleRate && standardSampleRate <= maxSampleRate) { + ma_context_test_rate_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, standardSampleRate, flags, pDeviceInfo); + } + } + + /* Now make sure our min and max rates are included just in case they aren't in the range of our standard rates. */ + if (!ma_is_standard_sample_rate(minSampleRate)) { + ma_context_test_rate_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, minSampleRate, flags, pDeviceInfo); + } + + if (!ma_is_standard_sample_rate(maxSampleRate) && maxSampleRate != minSampleRate) { + ma_context_test_rate_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, maxSampleRate, flags, pDeviceInfo); + } +} + +static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_context_get_device_info_enum_callback_data__alsa data; ma_result result; int resultALSA; ma_snd_pcm_t* pPCM; ma_snd_pcm_hw_params_t* pHWParams; - ma_snd_pcm_format_mask_t* pFormatMask; - int sampleRateDir = 0; + ma_uint32 iFormat; + ma_uint32 iChannel; MA_ASSERT(pContext != NULL); /* We just enumerate to find basic information about the device. */ - data.deviceType = deviceType; - data.pDeviceID = pDeviceID; - data.shareMode = shareMode; + data.deviceType = deviceType; + data.pDeviceID = pDeviceID; data.pDeviceInfo = pDeviceInfo; data.foundDevice = MA_FALSE; result = ma_context_enumerate_devices__alsa(pContext, ma_context_get_device_info_enum_callback__alsa, &data); @@ -18129,8 +20065,12 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic return MA_NO_DEVICE; } + if (ma_strcmp(pDeviceInfo->id.alsa, "default") == 0) { + pDeviceInfo->isDefault = MA_TRUE; + } + /* For detailed info we need to open the device. */ - result = ma_context_open_pcm__alsa(pContext, shareMode, deviceType, pDeviceID, 0, &pPCM); + result = ma_context_open_pcm__alsa(pContext, ma_share_mode_shared, deviceType, pDeviceID, 0, &pPCM); if (result != MA_SUCCESS) { return result; } @@ -18138,322 +20078,105 @@ static ma_result ma_context_get_device_info__alsa(ma_context* pContext, ma_devic /* We need to initialize a HW parameters object in order to know what formats are supported. */ pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); if (pHWParams == NULL) { + ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; } resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); } - ((ma_snd_pcm_hw_params_get_channels_min_proc)pContext->alsa.snd_pcm_hw_params_get_channels_min)(pHWParams, &pDeviceInfo->minChannels); - ((ma_snd_pcm_hw_params_get_channels_max_proc)pContext->alsa.snd_pcm_hw_params_get_channels_max)(pHWParams, &pDeviceInfo->maxChannels); - ((ma_snd_pcm_hw_params_get_rate_min_proc)pContext->alsa.snd_pcm_hw_params_get_rate_min)(pHWParams, &pDeviceInfo->minSampleRate, &sampleRateDir); - ((ma_snd_pcm_hw_params_get_rate_max_proc)pContext->alsa.snd_pcm_hw_params_get_rate_max)(pHWParams, &pDeviceInfo->maxSampleRate, &sampleRateDir); + /* + Some ALSA devices can support many permutations of formats, channels and rates. We only support + a fixed number of permutations which means we need to employ some strategies to ensure the best + combinations are returned. An example is the "pulse" device which can do it's own data conversion + in software and as a result can support any combination of format, channels and rate. - /* Formats. */ - pFormatMask = (ma_snd_pcm_format_mask_t*)ma__calloc_from_callbacks(((ma_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)(), &pContext->allocationCallbacks); - if (pFormatMask == NULL) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); - return MA_OUT_OF_MEMORY; + We want to ensure the the first data formats are the best. We have a list of favored sample + formats and sample rates, so these will be the basis of our iteration. + */ + + /* Formats. We just iterate over our standard formats and test them, making sure we reset the configuration space each iteration. */ + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { + ma_format format = g_maFormatPriorities[iFormat]; + + /* + For each format we need to make sure we reset the configuration space so we don't return + channel counts and rates that aren't compatible with a format. + */ + ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); + + /* Test the format first. If this fails it means the format is not supported and we can skip it. */ + if (((ma_snd_pcm_hw_params_test_format_proc)pContext->alsa.snd_pcm_hw_params_test_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)) == 0) { + /* The format is supported. */ + unsigned int minChannels; + unsigned int maxChannels; + + /* + The configuration space needs to be restricted to this format so we can get an accurate + picture of which sample rates and channel counts are support with this format. + */ + ((ma_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)); + + /* Now we need to check for supported channels. */ + ((ma_snd_pcm_hw_params_get_channels_min_proc)pContext->alsa.snd_pcm_hw_params_get_channels_min)(pHWParams, &minChannels); + ((ma_snd_pcm_hw_params_get_channels_max_proc)pContext->alsa.snd_pcm_hw_params_get_channels_max)(pHWParams, &maxChannels); + + if (minChannels > MA_MAX_CHANNELS) { + continue; /* Too many channels. */ + } + if (maxChannels < MA_MIN_CHANNELS) { + continue; /* Not enough channels. */ + } + + /* + Make sure the channel count is clamped. This is mainly intended for the max channels + because some devices can report an unbound maximum. + */ + minChannels = ma_clamp(minChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + maxChannels = ma_clamp(maxChannels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + + if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { + /* The device supports all channels. Don't iterate over every single one. Instead just set the channels to 0 which means all channels are supported. */ + ma_context_iterate_rates_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, 0, 0, pDeviceInfo); /* Intentionally setting the channel count to 0 as that means all channels are supported. */ + } else { + /* The device only supports a specific set of channels. We need to iterate over all of them. */ + for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { + /* Test the channel before applying it to the configuration space. */ + unsigned int channels = iChannel; + + /* Make sure our channel range is reset before testing again or else we'll always fail the test. */ + ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); + ((ma_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(format)); + + if (((ma_snd_pcm_hw_params_test_channels_proc)pContext->alsa.snd_pcm_hw_params_test_channels)(pPCM, pHWParams, channels) == 0) { + /* The channel count is supported. */ + + /* The configuration space now needs to be restricted to the channel count before extracting the sample rate. */ + ((ma_snd_pcm_hw_params_set_channels_proc)pContext->alsa.snd_pcm_hw_params_set_channels)(pPCM, pHWParams, channels); + + /* Only after the configuration space has been restricted to the specific channel count should we iterate over our sample rates. */ + ma_context_iterate_rates_and_add_native_data_format__alsa(pContext, pPCM, pHWParams, format, channels, 0, pDeviceInfo); + } else { + /* The channel count is not supported. Skip. */ + } + } + } + } else { + /* The format is not supported. Skip. */ + } } - ((ma_snd_pcm_hw_params_get_format_mask_proc)pContext->alsa.snd_pcm_hw_params_get_format_mask)(pHWParams, pFormatMask); - - pDeviceInfo->formatCount = 0; - if (((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, MA_SND_PCM_FORMAT_U8)) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_u8; - } - if (((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, MA_SND_PCM_FORMAT_S16_LE)) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_s16; - } - if (((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, MA_SND_PCM_FORMAT_S24_3LE)) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_s24; - } - if (((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, MA_SND_PCM_FORMAT_S32_LE)) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_s32; - } - if (((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, MA_SND_PCM_FORMAT_FLOAT_LE)) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_f32; - } - - ma__free_from_callbacks(pFormatMask, &pContext->allocationCallbacks); ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pContext->alsa.snd_pcm_close)(pPCM); return MA_SUCCESS; } - -#if 0 -/* -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 ma_device__break_main_loop(). -*/ -static ma_uint32 ma_device__wait_for_frames__alsa(ma_device* pDevice, ma_bool32* pRequiresRestart) -{ - MA_ASSERT(pDevice != NULL); - - if (pRequiresRestart) *pRequiresRestart = MA_FALSE; - - /* I want it so that this function returns the period size in frames. We just wait until that number of frames are available and then return. */ - ma_uint32 periodSizeInFrames = pDevice->bufferSizeInFrames / pDevice->periods; - while (!pDevice->alsa.breakFromMainLoop) { - ma_snd_pcm_sframes_t framesAvailable = ((ma_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((ma_snd_pcm_t*)pDevice->alsa.pPCM); - if (framesAvailable < 0) { - if (framesAvailable == -EPIPE) { - if (((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCM, framesAvailable, MA_TRUE) < 0) { - return 0; - } - - /* A device recovery means a restart for mmap mode. */ - if (pRequiresRestart) { - *pRequiresRestart = MA_TRUE; - } - - /* Try again, but if it fails this time just return an error. */ - framesAvailable = ((ma_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((ma_snd_pcm_t*)pDevice->alsa.pPCM); - if (framesAvailable < 0) { - return 0; - } - } - } - - if (framesAvailable >= periodSizeInFrames) { - return periodSizeInFrames; - } - - if (framesAvailable < periodSizeInFrames) { - /* Less than a whole period is available so keep waiting. */ - int waitResult = ((ma_snd_pcm_wait_proc)pDevice->pContext->alsa.snd_pcm_wait)((ma_snd_pcm_t*)pDevice->alsa.pPCM, -1); - if (waitResult < 0) { - if (waitResult == -EPIPE) { - if (((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCM, waitResult, MA_TRUE) < 0) { - return 0; - } - - /* A device recovery means a restart for mmap mode. */ - if (pRequiresRestart) { - *pRequiresRestart = MA_TRUE; - } - } - } - } - } - - /* We'll get here if the loop was terminated. Just return whatever's available. */ - ma_snd_pcm_sframes_t framesAvailable = ((ma_snd_pcm_avail_update_proc)pDevice->pContext->alsa.snd_pcm_avail_update)((ma_snd_pcm_t*)pDevice->alsa.pPCM); - if (framesAvailable < 0) { - return 0; - } - - return framesAvailable; -} - -static ma_bool32 ma_device_read_from_client_and_write__alsa(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); - if (!ma_device_is_started(pDevice) && ma_device__get_state(pDevice) != MA_STATE_STARTING) { - return MA_FALSE; - } - if (pDevice->alsa.breakFromMainLoop) { - return MA_FALSE; - } - - if (pDevice->alsa.isUsingMMap) { - /* mmap. */ - ma_bool32 requiresRestart; - ma_uint32 framesAvailable = ma_device__wait_for_frames__alsa(pDevice, &requiresRestart); - if (framesAvailable == 0) { - return MA_FALSE; - } - - /* Don't bother asking the client for more audio data if we're just stopping the device anyway. */ - if (pDevice->alsa.breakFromMainLoop) { - return MA_FALSE; - } - - const ma_snd_pcm_channel_area_t* pAreas; - ma_snd_pcm_uframes_t mappedOffset; - ma_snd_pcm_uframes_t mappedFrames = framesAvailable; - while (framesAvailable > 0) { - int result = ((ma_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((ma_snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); - if (result < 0) { - return MA_FALSE; - } - - if (mappedFrames > 0) { - void* pBuffer = (ma_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); - ma_device__read_frames_from_client(pDevice, mappedFrames, pBuffer); - } - - result = ((ma_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((ma_snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); - if (result < 0 || (ma_snd_pcm_uframes_t)result != mappedFrames) { - ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCM, result, MA_TRUE); - return MA_FALSE; - } - - if (requiresRestart) { - if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { - return MA_FALSE; - } - } - - if (framesAvailable >= mappedFrames) { - framesAvailable -= mappedFrames; - } else { - framesAvailable = 0; - } - } - } else { - /* readi/writei. */ - while (!pDevice->alsa.breakFromMainLoop) { - ma_uint32 framesAvailable = ma_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 MA_FALSE; - } - - ma_device__read_frames_from_client(pDevice, framesAvailable, pDevice->alsa.pIntermediaryBuffer); - - ma_snd_pcm_sframes_t framesWritten = ((ma_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((ma_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 (((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCM, framesWritten, MA_TRUE) < 0) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after underrun.", MA_FAILED_TO_START_BACKEND_DEVICE); - return MA_FALSE; - } - - framesWritten = ((ma_snd_pcm_writei_proc)pDevice->pContext->alsa.snd_pcm_writei)((ma_snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); - if (framesWritten < 0) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to write data to the internal device.", ma_result_from_errno((int)-framesWritten)); - return MA_FALSE; - } - - break; /* Success. */ - } else { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_writei() failed when writing initial data.", ma_result_from_errno((int)-framesWritten)); - return MA_FALSE; - } - } else { - break; /* Success. */ - } - } - } - - return MA_TRUE; -} - -static ma_bool32 ma_device_read_and_send_to_client__alsa(ma_device* pDevice) -{ - MA_ASSERT(pDevice != NULL); - if (!ma_device_is_started(pDevice)) { - return MA_FALSE; - } - if (pDevice->alsa.breakFromMainLoop) { - return MA_FALSE; - } - - ma_uint32 framesToSend = 0; - void* pBuffer = NULL; - if (pDevice->alsa.pIntermediaryBuffer == NULL) { - /* mmap. */ - ma_bool32 requiresRestart; - ma_uint32 framesAvailable = ma_device__wait_for_frames__alsa(pDevice, &requiresRestart); - if (framesAvailable == 0) { - return MA_FALSE; - } - - const ma_snd_pcm_channel_area_t* pAreas; - ma_snd_pcm_uframes_t mappedOffset; - ma_snd_pcm_uframes_t mappedFrames = framesAvailable; - while (framesAvailable > 0) { - int result = ((ma_snd_pcm_mmap_begin_proc)pDevice->pContext->alsa.snd_pcm_mmap_begin)((ma_snd_pcm_t*)pDevice->alsa.pPCM, &pAreas, &mappedOffset, &mappedFrames); - if (result < 0) { - return MA_FALSE; - } - - if (mappedFrames > 0) { - void* pBuffer = (ma_uint8*)pAreas[0].addr + ((pAreas[0].first + (mappedOffset * pAreas[0].step)) / 8); - ma_device__send_frames_to_client(pDevice, mappedFrames, pBuffer); - } - - result = ((ma_snd_pcm_mmap_commit_proc)pDevice->pContext->alsa.snd_pcm_mmap_commit)((ma_snd_pcm_t*)pDevice->alsa.pPCM, mappedOffset, mappedFrames); - if (result < 0 || (ma_snd_pcm_uframes_t)result != mappedFrames) { - ((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCM, result, MA_TRUE); - return MA_FALSE; - } - - if (requiresRestart) { - if (((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCM) < 0) { - return MA_FALSE; - } - } - - if (framesAvailable >= mappedFrames) { - framesAvailable -= mappedFrames; - } else { - framesAvailable = 0; - } - } - } else { - /* readi/writei. */ - ma_snd_pcm_sframes_t framesRead = 0; - while (!pDevice->alsa.breakFromMainLoop) { - ma_uint32 framesAvailable = ma_device__wait_for_frames__alsa(pDevice, NULL); - if (framesAvailable == 0) { - continue; - } - - framesRead = ((ma_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((ma_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 (((ma_snd_pcm_recover_proc)pDevice->pContext->alsa.snd_pcm_recover)((ma_snd_pcm_t*)pDevice->alsa.pPCM, framesRead, MA_TRUE) < 0) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to recover device after overrun.", MA_FAILED_TO_START_BACKEND_DEVICE); - return MA_FALSE; - } - - framesRead = ((ma_snd_pcm_readi_proc)pDevice->pContext->alsa.snd_pcm_readi)((ma_snd_pcm_t*)pDevice->alsa.pPCM, pDevice->alsa.pIntermediaryBuffer, framesAvailable); - if (framesRead < 0) { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to read data from the internal device.", ma_result_from_errno((int)-framesRead)); - return MA_FALSE; - } - - break; /* Success. */ - } else { - return MA_FALSE; - } - } else { - break; /* Success. */ - } - } - - framesToSend = framesRead; - pBuffer = pDevice->alsa.pIntermediaryBuffer; - } - - if (framesToSend > 0) { - ma_device__send_frames_to_client(pDevice, framesToSend, pBuffer); - } - - return MA_TRUE; -} -#endif /* 0 */ - -static void ma_device_uninit__alsa(ma_device* pDevice) +static ma_result ma_device_uninit__alsa(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -18464,17 +20187,17 @@ static void ma_device_uninit__alsa(ma_device* pDevice) if ((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); } + + return MA_SUCCESS; } -static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +static ma_result ma_device_init_by_type__alsa(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { ma_result result; int resultALSA; ma_snd_pcm_t* pPCM; ma_bool32 isUsingMMap; ma_snd_pcm_format_t formatALSA; - ma_share_mode shareMode; - const ma_device_id* pDeviceID; ma_format internalFormat; ma_uint32 internalChannels; ma_uint32 internalSampleRate; @@ -18485,16 +20208,12 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev ma_snd_pcm_hw_params_t* pHWParams; ma_snd_pcm_sw_params_t* pSWParams; ma_snd_pcm_uframes_t bufferBoundary; - float bufferSizeScaleFactor; - MA_ASSERT(pContext != NULL); MA_ASSERT(pConfig != NULL); MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should only be called for playback _or_ capture, never duplex. */ MA_ASSERT(pDevice != NULL); - formatALSA = ma_convert_ma_format_to_alsa_format((deviceType == ma_device_type_capture) ? pConfig->capture.format : pConfig->playback.format); - shareMode = (deviceType == ma_device_type_capture) ? pConfig->capture.shareMode : pConfig->playback.shareMode; - pDeviceID = (deviceType == ma_device_type_capture) ? pConfig->capture.pDeviceID : pConfig->playback.pDeviceID; + formatALSA = ma_convert_ma_format_to_alsa_format(pDescriptor->format); openMode = 0; if (pConfig->alsa.noAutoResample) { @@ -18507,78 +20226,21 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev openMode |= MA_SND_PCM_NO_AUTO_FORMAT; } - result = ma_context_open_pcm__alsa(pContext, shareMode, deviceType, pDeviceID, openMode, &pPCM); + result = ma_context_open_pcm__alsa(pDevice->pContext, pDescriptor->shareMode, deviceType, pDescriptor->pDeviceID, openMode, &pPCM); if (result != MA_SUCCESS) { return result; } - /* If using the default buffer size we may want to apply some device-specific scaling for known devices that have peculiar latency characteristics */ - bufferSizeScaleFactor = 1; - if (pDevice->usingDefaultBufferSize) { - ma_snd_pcm_info_t* pInfo = (ma_snd_pcm_info_t*)ma__calloc_from_callbacks(((ma_snd_pcm_info_sizeof_proc)pContext->alsa.snd_pcm_info_sizeof)(), &pContext->allocationCallbacks); - if (pInfo == NULL) { - return MA_OUT_OF_MEMORY; - } - - /* We may need to scale the size of the buffer depending on the device. */ - if (((ma_snd_pcm_info_proc)pContext->alsa.snd_pcm_info)(pPCM, pInfo) == 0) { - const char* deviceName = ((ma_snd_pcm_info_get_name_proc)pContext->alsa.snd_pcm_info_get_name)(pInfo); - if (deviceName != NULL) { - if (ma_strcmp(deviceName, "default") == 0) { - char** ppDeviceHints; - char** ppNextDeviceHint; - - /* It's the default device. We need to use DESC from snd_device_name_hint(). */ - if (((ma_snd_device_name_hint_proc)pContext->alsa.snd_device_name_hint)(-1, "pcm", (void***)&ppDeviceHints) < 0) { - ma__free_from_callbacks(pInfo, &pContext->allocationCallbacks); - return MA_NO_BACKEND; - } - - ppNextDeviceHint = ppDeviceHints; - while (*ppNextDeviceHint != NULL) { - char* NAME = ((ma_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "NAME"); - char* DESC = ((ma_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "DESC"); - char* IOID = ((ma_snd_device_name_get_hint_proc)pContext->alsa.snd_device_name_get_hint)(*ppNextDeviceHint, "IOID"); - - ma_bool32 foundDevice = MA_FALSE; - if ((deviceType == ma_device_type_playback && (IOID == NULL || ma_strcmp(IOID, "Output") == 0)) || - (deviceType == ma_device_type_capture && (IOID != NULL && ma_strcmp(IOID, "Input" ) == 0))) { - if (ma_strcmp(NAME, deviceName) == 0) { - bufferSizeScaleFactor = ma_find_default_buffer_size_scale__alsa(DESC); - foundDevice = MA_TRUE; - } - } - - free(NAME); - free(DESC); - free(IOID); - ppNextDeviceHint += 1; - - if (foundDevice) { - break; - } - } - - ((ma_snd_device_name_free_hint_proc)pContext->alsa.snd_device_name_free_hint)((void**)ppDeviceHints); - } else { - bufferSizeScaleFactor = ma_find_default_buffer_size_scale__alsa(deviceName); - } - } - } - - ma__free_from_callbacks(pInfo, &pContext->allocationCallbacks); - } - /* Hardware parameters. */ - pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pContext->alsa.snd_pcm_hw_params_sizeof)(), &pContext->allocationCallbacks); + pHWParams = (ma_snd_pcm_hw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_hw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_hw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pHWParams == NULL) { return MA_OUT_OF_MEMORY; } - resultALSA = ((ma_snd_pcm_hw_params_any_proc)pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); + resultALSA = ((ma_snd_pcm_hw_params_any_proc)pDevice->pContext->alsa.snd_pcm_hw_params_any)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize hardware parameters. snd_pcm_hw_params_any() failed.", ma_result_from_errno(-resultALSA)); } @@ -18588,7 +20250,7 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev #if 0 /* NOTE: MMAP mode temporarily disabled. */ if (deviceType != ma_device_type_capture) { /* <-- Disabling MMAP mode for capture devices because I apparently do not have a device that supports it which means I can't test it... Contributions welcome. */ if (!pConfig->alsa.noMMap && ma_device__is_async(pDevice)) { - if (((ma_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { + if (((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0) { pDevice->alsa.isUsingMMap = MA_TRUE; } } @@ -18596,9 +20258,9 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev #endif if (!isUsingMMap) { - resultALSA = ((ma_snd_pcm_hw_params_set_access_proc)pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); + resultALSA = ((ma_snd_pcm_hw_params_set_access_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_access)(pPCM, pHWParams, MA_SND_PCM_ACCESS_RW_INTERLEAVED); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[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.", ma_result_from_errno(-resultALSA)); } @@ -18611,70 +20273,39 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev /* Format. */ { - ma_snd_pcm_format_mask_t* pFormatMask; - - /* Try getting every supported format first. */ - pFormatMask = (ma_snd_pcm_format_mask_t*)ma__calloc_from_callbacks(((ma_snd_pcm_format_mask_sizeof_proc)pContext->alsa.snd_pcm_format_mask_sizeof)(), &pContext->allocationCallbacks); - if (pFormatMask == NULL) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); - ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); - return MA_OUT_OF_MEMORY; - } - - ((ma_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 (!((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, formatALSA)) { - size_t i; - - /* The requested format is not supported so now try running through the list of formats and return the best one. */ - ma_snd_pcm_format_t preferredFormatsALSA[] = { - MA_SND_PCM_FORMAT_S16_LE, /* ma_format_s16 */ - MA_SND_PCM_FORMAT_FLOAT_LE, /* ma_format_f32 */ - MA_SND_PCM_FORMAT_S32_LE, /* ma_format_s32 */ - MA_SND_PCM_FORMAT_S24_3LE, /* ma_format_s24 */ - MA_SND_PCM_FORMAT_U8 /* ma_format_u8 */ - }; - - if (ma_is_big_endian()) { - preferredFormatsALSA[0] = MA_SND_PCM_FORMAT_S16_BE; - preferredFormatsALSA[1] = MA_SND_PCM_FORMAT_FLOAT_BE; - preferredFormatsALSA[2] = MA_SND_PCM_FORMAT_S32_BE; - preferredFormatsALSA[3] = MA_SND_PCM_FORMAT_S24_3BE; - preferredFormatsALSA[4] = MA_SND_PCM_FORMAT_U8; - } + if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN || ((ma_snd_pcm_hw_params_test_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_test_format)(pPCM, pHWParams, formatALSA) != 0) { + /* We're either requesting the native format or the specified format is not supported. */ + size_t iFormat; formatALSA = MA_SND_PCM_FORMAT_UNKNOWN; - for (i = 0; i < (sizeof(preferredFormatsALSA) / sizeof(preferredFormatsALSA[0])); ++i) { - if (((ma_snd_pcm_format_mask_test_proc)pContext->alsa.snd_pcm_format_mask_test)(pFormatMask, preferredFormatsALSA[i])) { - formatALSA = preferredFormatsALSA[i]; + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); ++iFormat) { + if (((ma_snd_pcm_hw_params_test_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_test_format)(pPCM, pHWParams, ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat])) == 0) { + formatALSA = ma_convert_ma_format_to_alsa_format(g_maFormatPriorities[iFormat]); break; } } if (formatALSA == MA_SND_PCM_FORMAT_UNKNOWN) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. The device does not support any miniaudio formats.", MA_FORMAT_NOT_SUPPORTED); } } - ma__free_from_callbacks(pFormatMask, &pContext->allocationCallbacks); - pFormatMask = NULL; - - resultALSA = ((ma_snd_pcm_hw_params_set_format_proc)pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA); + resultALSA = ((ma_snd_pcm_hw_params_set_format_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_format)(pPCM, pHWParams, formatALSA); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Format not supported. snd_pcm_hw_params_set_format() failed.", ma_result_from_errno(-resultALSA)); } - + internalFormat = ma_format_from_alsa(formatALSA); if (internalFormat == ma_format_unknown) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] The chosen format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); } @@ -18682,13 +20313,18 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev /* Channels. */ { - unsigned int channels = (deviceType == ma_device_type_capture) ? pConfig->capture.channels : pConfig->playback.channels; - resultALSA = ((ma_snd_pcm_hw_params_set_channels_near_proc)pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels); + unsigned int channels = pDescriptor->channels; + if (channels == 0) { + channels = MA_DEFAULT_CHANNELS; + } + + resultALSA = ((ma_snd_pcm_hw_params_set_channels_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_channels_near)(pPCM, pHWParams, &channels); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set channel count. snd_pcm_hw_params_set_channels_near() failed.", ma_result_from_errno(-resultALSA)); } + internalChannels = (ma_uint32)channels; } @@ -18700,93 +20336,98 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev 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. - + miniaudio 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 miniaudio'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. */ - ((ma_snd_pcm_hw_params_set_rate_resample_proc)pContext->alsa.snd_pcm_hw_params_set_rate_resample)(pPCM, pHWParams, 0); + ((ma_snd_pcm_hw_params_set_rate_resample_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_resample)(pPCM, pHWParams, 0); - sampleRate = pConfig->sampleRate; - resultALSA = ((ma_snd_pcm_hw_params_set_rate_near_proc)pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0); + sampleRate = pDescriptor->sampleRate; + if (sampleRate == 0) { + sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + resultALSA = ((ma_snd_pcm_hw_params_set_rate_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_rate_near)(pPCM, pHWParams, &sampleRate, 0); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Sample rate not supported. snd_pcm_hw_params_set_rate_near() failed.", ma_result_from_errno(-resultALSA)); } + internalSampleRate = (ma_uint32)sampleRate; } /* Periods. */ { - ma_uint32 periods = pConfig->periods; - resultALSA = ((ma_snd_pcm_hw_params_set_periods_near_proc)pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL); + ma_uint32 periods = pDescriptor->periodCount; + + resultALSA = ((ma_snd_pcm_hw_params_set_periods_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_periods_near)(pPCM, pHWParams, &periods, NULL); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set period count. snd_pcm_hw_params_set_periods_near() failed.", ma_result_from_errno(-resultALSA)); } + internalPeriods = periods; } /* Buffer Size */ { - ma_snd_pcm_uframes_t actualBufferSizeInFrames = pConfig->periodSizeInFrames * internalPeriods; - if (actualBufferSizeInFrames == 0) { - actualBufferSizeInFrames = ma_scale_buffer_size(ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, internalSampleRate), bufferSizeScaleFactor) * internalPeriods; - } + ma_snd_pcm_uframes_t actualBufferSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate, pConfig->performanceProfile) * internalPeriods; - resultALSA = ((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames); + resultALSA = ((ma_snd_pcm_hw_params_set_buffer_size_near_proc)pDevice->pContext->alsa.snd_pcm_hw_params_set_buffer_size_near)(pPCM, pHWParams, &actualBufferSizeInFrames); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set buffer size for device. snd_pcm_hw_params_set_buffer_size() failed.", ma_result_from_errno(-resultALSA)); } + internalPeriodSizeInFrames = actualBufferSizeInFrames / internalPeriods; } /* Apply hardware parameters. */ - resultALSA = ((ma_snd_pcm_hw_params_proc)pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams); + resultALSA = ((ma_snd_pcm_hw_params_proc)pDevice->pContext->alsa.snd_pcm_hw_params)(pPCM, pHWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set hardware parameters. snd_pcm_hw_params() failed.", ma_result_from_errno(-resultALSA)); } - ma__free_from_callbacks(pHWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pHWParams, &pDevice->pContext->allocationCallbacks); pHWParams = NULL; /* Software parameters. */ - pSWParams = (ma_snd_pcm_sw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_sw_params_sizeof_proc)pContext->alsa.snd_pcm_sw_params_sizeof)(), &pContext->allocationCallbacks); + pSWParams = (ma_snd_pcm_sw_params_t*)ma__calloc_from_callbacks(((ma_snd_pcm_sw_params_sizeof_proc)pDevice->pContext->alsa.snd_pcm_sw_params_sizeof)(), &pDevice->pContext->allocationCallbacks); if (pSWParams == NULL) { ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return MA_OUT_OF_MEMORY; } - resultALSA = ((ma_snd_pcm_sw_params_current_proc)pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams); + resultALSA = ((ma_snd_pcm_sw_params_current_proc)pDevice->pContext->alsa.snd_pcm_sw_params_current)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to initialize software parameters. snd_pcm_sw_params_current() failed.", ma_result_from_errno(-resultALSA)); } - resultALSA = ((ma_snd_pcm_sw_params_set_avail_min_proc)pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); + resultALSA = ((ma_snd_pcm_sw_params_set_avail_min_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_avail_min)(pPCM, pSWParams, ma_prev_power_of_2(internalPeriodSizeInFrames)); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] snd_pcm_sw_params_set_avail_min() failed.", ma_result_from_errno(-resultALSA)); } - resultALSA = ((ma_snd_pcm_sw_params_get_boundary_proc)pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary); + resultALSA = ((ma_snd_pcm_sw_params_get_boundary_proc)pDevice->pContext->alsa.snd_pcm_sw_params_get_boundary)(pSWParams, &bufferBoundary); if (resultALSA < 0) { bufferBoundary = internalPeriodSizeInFrames * internalPeriods; } @@ -18798,35 +20439,35 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev Subtle detail here with the start threshold. When in playback-only mode (no full-duplex) we can set the start threshold to the size of a period. But for full-duplex we need to set it such that it is at least two periods. */ - resultALSA = ((ma_snd_pcm_sw_params_set_start_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalPeriodSizeInFrames*2); + resultALSA = ((ma_snd_pcm_sw_params_set_start_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_start_threshold)(pPCM, pSWParams, internalPeriodSizeInFrames*2); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set start threshold for playback device. snd_pcm_sw_params_set_start_threshold() failed.", ma_result_from_errno(-resultALSA)); } - resultALSA = ((ma_snd_pcm_sw_params_set_stop_threshold_proc)pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary); + resultALSA = ((ma_snd_pcm_sw_params_set_stop_threshold_proc)pDevice->pContext->alsa.snd_pcm_sw_params_set_stop_threshold)(pPCM, pSWParams, bufferBoundary); if (resultALSA < 0) { /* Set to boundary to loop instead of stop in the event of an xrun. */ - ma__free_from_callbacks(pSWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set stop threshold for playback device. snd_pcm_sw_params_set_stop_threshold() failed.", ma_result_from_errno(-resultALSA)); } } - resultALSA = ((ma_snd_pcm_sw_params_proc)pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams); + resultALSA = ((ma_snd_pcm_sw_params_proc)pDevice->pContext->alsa.snd_pcm_sw_params)(pPCM, pSWParams); if (resultALSA < 0) { - ma__free_from_callbacks(pSWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); ((ma_snd_pcm_close_proc)pDevice->pContext->alsa.snd_pcm_close)(pPCM); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to set software parameters. snd_pcm_sw_params() failed.", ma_result_from_errno(-resultALSA)); } - ma__free_from_callbacks(pSWParams, &pContext->allocationCallbacks); + ma__free_from_callbacks(pSWParams, &pDevice->pContext->allocationCallbacks); pSWParams = NULL; /* 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. */ { - ma_snd_pcm_chmap_t* pChmap = ((ma_snd_pcm_get_chmap_proc)pContext->alsa.snd_pcm_get_chmap)(pPCM); + ma_snd_pcm_chmap_t* pChmap = ((ma_snd_pcm_get_chmap_proc)pDevice->pContext->alsa.snd_pcm_get_chmap)(pPCM); if (pChmap != NULL) { ma_uint32 iChannel; @@ -18888,29 +20529,26 @@ static ma_result ma_device_init_by_type__alsa(ma_context* pContext, const ma_dev if (deviceType == ma_device_type_capture) { - pDevice->alsa.pPCMCapture = (ma_ptr)pPCM; - pDevice->alsa.isUsingMMapCapture = isUsingMMap; - pDevice->capture.internalFormat = internalFormat; - pDevice->capture.internalChannels = internalChannels; - pDevice->capture.internalSampleRate = internalSampleRate; - ma_channel_map_copy(pDevice->capture.internalChannelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); - pDevice->capture.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->capture.internalPeriods = internalPeriods; + pDevice->alsa.pPCMCapture = (ma_ptr)pPCM; + pDevice->alsa.isUsingMMapCapture = isUsingMMap; } else { - pDevice->alsa.pPCMPlayback = (ma_ptr)pPCM; - pDevice->alsa.isUsingMMapPlayback = isUsingMMap; - pDevice->playback.internalFormat = internalFormat; - pDevice->playback.internalChannels = internalChannels; - pDevice->playback.internalSampleRate = internalSampleRate; - ma_channel_map_copy(pDevice->playback.internalChannelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); - pDevice->playback.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->playback.internalPeriods = internalPeriods; + pDevice->alsa.pPCMPlayback = (ma_ptr)pPCM; + pDevice->alsa.isUsingMMapPlayback = isUsingMMap; } + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_channel_map_copy(pDescriptor->channelMap, internalChannelMap, ma_min(internalChannels, MA_MAX_CHANNELS)); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; + + /*printf("format=%d; channels=%d; sampleRate=%d; periodSizeInFrames=%d; periodCount=%d\n", internalFormat, internalChannels, internalSampleRate, internalPeriodSizeInFrames, internalPeriods);*/ + return MA_SUCCESS; } -static ma_result ma_device_init__alsa(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__alsa(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { MA_ASSERT(pDevice != NULL); @@ -18921,14 +20559,14 @@ static ma_result ma_device_init__alsa(ma_context* pContext, const ma_device_conf } if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_by_type__alsa(pContext, pConfig, ma_device_type_capture, pDevice); + ma_result result = ma_device_init_by_type__alsa(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_by_type__alsa(pContext, pConfig, ma_device_type_playback, pDevice); + ma_result result = ma_device_init_by_type__alsa(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { return result; } @@ -18937,6 +20575,51 @@ static ma_result ma_device_init__alsa(ma_context* pContext, const ma_device_conf return MA_SUCCESS; } +static ma_result ma_device_start__alsa(ma_device* pDevice) +{ + int resultALSA; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); + if (resultALSA < 0) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start capture device.", ma_result_from_errno(-resultALSA)); + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* Don't need to do anything for playback because it'll be started automatically when enough data has been written. */ + } + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__alsa(ma_device* pDevice) +{ + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((ma_snd_pcm_drain_proc)pDevice->pContext->alsa.snd_pcm_drain)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); + + /* We need to prepare the device again, otherwise we won't be able to restart the device. */ + if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { + #ifdef MA_DEBUG_OUTPUT + printf("[ALSA] Failed to prepare capture device after stopping.\n"); + #endif + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((ma_snd_pcm_drain_proc)pDevice->pContext->alsa.snd_pcm_drain)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); + + /* We need to prepare the device again, otherwise we won't be able to restart the device. */ + if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) < 0) { + #ifdef MA_DEBUG_OUTPUT + printf("[ALSA] Failed to prepare playback device after stopping.\n"); + #endif + } + } + + return MA_SUCCESS; +} + static ma_result ma_device_read__alsa(ma_device* pDevice, void* pFramesOut, ma_uint32 frameCount, ma_uint32* pFramesRead) { ma_snd_pcm_sframes_t resultALSA; @@ -19044,220 +20727,6 @@ static ma_result ma_device_write__alsa(ma_device* pDevice, const void* pFrames, return MA_SUCCESS; } -static ma_result ma_device_main_loop__alsa(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - int resultALSA; - ma_bool32 exitLoop = MA_FALSE; - - MA_ASSERT(pDevice != NULL); - - /* Capture devices need to be started immediately. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - resultALSA = ((ma_snd_pcm_start_proc)pDevice->pContext->alsa.snd_pcm_start)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - if (resultALSA < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[ALSA] Failed to start device in preparation for reading.", ma_result_from_errno(-resultALSA)); - } - } - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - if (pDevice->alsa.isUsingMMapCapture || pDevice->alsa.isUsingMMapPlayback) { - /* MMAP */ - return MA_INVALID_OPERATION; /* Not yet implemented. */ - } else { - /* readi() and writei() */ - - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__alsa(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__alsa(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__alsa()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } - } break; - - case ma_device_type_capture: - { - if (pDevice->alsa.isUsingMMapCapture) { - /* MMAP */ - return MA_INVALID_OPERATION; /* Not yet implemented. */ - } else { - /* readi() */ - - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__alsa(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } - } break; - - case ma_device_type_playback: - { - if (pDevice->alsa.isUsingMMapPlayback) { - /* MMAP */ - return MA_INVALID_OPERATION; /* Not yet implemented. */ - } else { - /* writei() */ - - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__alsa(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - /* Here is where the device needs to be stopped. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((ma_snd_pcm_drain_proc)pDevice->pContext->alsa.snd_pcm_drain)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture); - - /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMCapture) < 0) { - #ifdef MA_DEBUG_OUTPUT - printf("[ALSA] Failed to prepare capture device after stopping.\n"); - #endif - } - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((ma_snd_pcm_drain_proc)pDevice->pContext->alsa.snd_pcm_drain)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback); - - /* We need to prepare the device again, otherwise we won't be able to restart the device. */ - if (((ma_snd_pcm_prepare_proc)pDevice->pContext->alsa.snd_pcm_prepare)((ma_snd_pcm_t*)pDevice->alsa.pPCMPlayback) < 0) { - #ifdef MA_DEBUG_OUTPUT - printf("[ALSA] Failed to prepare playback device after stopping.\n"); - #endif - } - } - - return result; -} - static ma_result ma_context_uninit__alsa(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -19275,7 +20744,7 @@ static ma_result ma_context_uninit__alsa(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__alsa(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { #ifndef MA_NO_RUNTIME_LINKING const char* libasoundNames[] = { @@ -19305,8 +20774,11 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont pContext->alsa.snd_pcm_hw_params_set_format = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format"); pContext->alsa.snd_pcm_hw_params_set_format_first = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_format_first"); pContext->alsa.snd_pcm_hw_params_get_format_mask = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_format_mask"); + pContext->alsa.snd_pcm_hw_params_set_channels = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels"); pContext->alsa.snd_pcm_hw_params_set_channels_near = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_near"); + pContext->alsa.snd_pcm_hw_params_set_channels_minmax = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_channels_minmax"); pContext->alsa.snd_pcm_hw_params_set_rate_resample = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_resample"); + pContext->alsa.snd_pcm_hw_params_set_rate = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate"); pContext->alsa.snd_pcm_hw_params_set_rate_near = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_rate_near"); pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_buffer_size_near"); pContext->alsa.snd_pcm_hw_params_set_periods_near = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_set_periods_near"); @@ -19321,6 +20793,9 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont pContext->alsa.snd_pcm_hw_params_get_buffer_size = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_buffer_size"); pContext->alsa.snd_pcm_hw_params_get_periods = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_periods"); pContext->alsa.snd_pcm_hw_params_get_access = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_get_access"); + pContext->alsa.snd_pcm_hw_params_test_format = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_test_format"); + pContext->alsa.snd_pcm_hw_params_test_channels = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_test_channels"); + pContext->alsa.snd_pcm_hw_params_test_rate = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params_test_rate"); pContext->alsa.snd_pcm_hw_params = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_hw_params"); pContext->alsa.snd_pcm_sw_params_sizeof = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params_sizeof"); pContext->alsa.snd_pcm_sw_params_current = (ma_proc)ma_dlsym(pContext, pContext->alsa.asoundSO, "snd_pcm_sw_params_current"); @@ -19362,9 +20837,12 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont ma_snd_pcm_hw_params_set_format_proc _snd_pcm_hw_params_set_format = snd_pcm_hw_params_set_format; ma_snd_pcm_hw_params_set_format_first_proc _snd_pcm_hw_params_set_format_first = snd_pcm_hw_params_set_format_first; ma_snd_pcm_hw_params_get_format_mask_proc _snd_pcm_hw_params_get_format_mask = snd_pcm_hw_params_get_format_mask; + ma_snd_pcm_hw_params_set_channels_proc _snd_pcm_hw_params_set_channels = snd_pcm_hw_params_set_channels; ma_snd_pcm_hw_params_set_channels_near_proc _snd_pcm_hw_params_set_channels_near = snd_pcm_hw_params_set_channels_near; ma_snd_pcm_hw_params_set_rate_resample_proc _snd_pcm_hw_params_set_rate_resample = snd_pcm_hw_params_set_rate_resample; + ma_snd_pcm_hw_params_set_rate_near _snd_pcm_hw_params_set_rate = snd_pcm_hw_params_set_rate; ma_snd_pcm_hw_params_set_rate_near_proc _snd_pcm_hw_params_set_rate_near = snd_pcm_hw_params_set_rate_near; + ma_snd_pcm_hw_params_set_rate_minmax_proc _snd_pcm_hw_params_set_rate_minmax = snd_pcm_hw_params_set_rate_minmax; ma_snd_pcm_hw_params_set_buffer_size_near_proc _snd_pcm_hw_params_set_buffer_size_near = snd_pcm_hw_params_set_buffer_size_near; ma_snd_pcm_hw_params_set_periods_near_proc _snd_pcm_hw_params_set_periods_near = snd_pcm_hw_params_set_periods_near; ma_snd_pcm_hw_params_set_access_proc _snd_pcm_hw_params_set_access = snd_pcm_hw_params_set_access; @@ -19378,6 +20856,9 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont ma_snd_pcm_hw_params_get_buffer_size_proc _snd_pcm_hw_params_get_buffer_size = snd_pcm_hw_params_get_buffer_size; ma_snd_pcm_hw_params_get_periods_proc _snd_pcm_hw_params_get_periods = snd_pcm_hw_params_get_periods; ma_snd_pcm_hw_params_get_access_proc _snd_pcm_hw_params_get_access = snd_pcm_hw_params_get_access; + ma_snd_pcm_hw_params_test_format_proc _snd_pcm_hw_params_test_format = snd_pcm_hw_params_test_format; + ma_snd_pcm_hw_params_test_channels_proc _snd_pcm_hw_params_test_channels = snd_pcm_hw_params_test_channels; + ma_snd_pcm_hw_params_test_rate_proc _snd_pcm_hw_params_test_rate = snd_pcm_hw_params_test_rate; ma_snd_pcm_hw_params_proc _snd_pcm_hw_params = snd_pcm_hw_params; ma_snd_pcm_sw_params_sizeof_proc _snd_pcm_sw_params_sizeof = snd_pcm_sw_params_sizeof; ma_snd_pcm_sw_params_current_proc _snd_pcm_sw_params_current = snd_pcm_sw_params_current; @@ -19418,8 +20899,11 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont pContext->alsa.snd_pcm_hw_params_set_format = (ma_proc)_snd_pcm_hw_params_set_format; pContext->alsa.snd_pcm_hw_params_set_format_first = (ma_proc)_snd_pcm_hw_params_set_format_first; pContext->alsa.snd_pcm_hw_params_get_format_mask = (ma_proc)_snd_pcm_hw_params_get_format_mask; + pContext->alsa.snd_pcm_hw_params_set_channels = (ma_proc)_snd_pcm_hw_params_set_channels; pContext->alsa.snd_pcm_hw_params_set_channels_near = (ma_proc)_snd_pcm_hw_params_set_channels_near; + pContext->alsa.snd_pcm_hw_params_set_channels_minmax = (ma_proc)_snd_pcm_hw_params_set_channels_minmax; pContext->alsa.snd_pcm_hw_params_set_rate_resample = (ma_proc)_snd_pcm_hw_params_set_rate_resample; + pContext->alsa.snd_pcm_hw_params_set_rate = (ma_proc)_snd_pcm_hw_params_set_rate; pContext->alsa.snd_pcm_hw_params_set_rate_near = (ma_proc)_snd_pcm_hw_params_set_rate_near; pContext->alsa.snd_pcm_hw_params_set_buffer_size_near = (ma_proc)_snd_pcm_hw_params_set_buffer_size_near; pContext->alsa.snd_pcm_hw_params_set_periods_near = (ma_proc)_snd_pcm_hw_params_set_periods_near; @@ -19434,6 +20918,9 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont pContext->alsa.snd_pcm_hw_params_get_buffer_size = (ma_proc)_snd_pcm_hw_params_get_buffer_size; pContext->alsa.snd_pcm_hw_params_get_periods = (ma_proc)_snd_pcm_hw_params_get_periods; pContext->alsa.snd_pcm_hw_params_get_access = (ma_proc)_snd_pcm_hw_params_get_access; + pContext->alsa.snd_pcm_hw_params_test_format = (ma_proc)_snd_pcm_hw_params_test_format; + pContext->alsa.snd_pcm_hw_params_test_channels = (ma_proc)_snd_pcm_hw_params_test_channels; + pContext->alsa.snd_pcm_hw_params_test_rate = (ma_proc)_snd_pcm_hw_params_test_rate; pContext->alsa.snd_pcm_hw_params = (ma_proc)_snd_pcm_hw_params; pContext->alsa.snd_pcm_sw_params_sizeof = (ma_proc)_snd_pcm_sw_params_sizeof; pContext->alsa.snd_pcm_sw_params_current = (ma_proc)_snd_pcm_sw_params_current; @@ -19474,15 +20961,17 @@ static ma_result ma_context_init__alsa(const ma_context_config* pConfig, ma_cont ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[ALSA] WARNING: Failed to initialize mutex for internal device enumeration.", MA_ERROR); } - pContext->onUninit = ma_context_uninit__alsa; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__alsa; - pContext->onEnumDevices = ma_context_enumerate_devices__alsa; - pContext->onGetDeviceInfo = ma_context_get_device_info__alsa; - pContext->onDeviceInit = ma_device_init__alsa; - pContext->onDeviceUninit = ma_device_uninit__alsa; - pContext->onDeviceStart = NULL; /* Not used. Started in the main loop. */ - pContext->onDeviceStop = NULL; /* Not used. Started in the main loop. */ - pContext->onDeviceMainLoop = ma_device_main_loop__alsa; + pCallbacks->onContextInit = ma_context_init__alsa; + pCallbacks->onContextUninit = ma_context_uninit__alsa; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__alsa; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__alsa; + pCallbacks->onDeviceInit = ma_device_init__alsa; + pCallbacks->onDeviceUninit = ma_device_uninit__alsa; + pCallbacks->onDeviceStart = ma_device_start__alsa; + pCallbacks->onDeviceStop = ma_device_stop__alsa; + pCallbacks->onDeviceRead = ma_device_read__alsa; + pCallbacks->onDeviceWrite = ma_device_write__alsa; + pCallbacks->onDeviceDataLoop = NULL; return MA_SUCCESS; } @@ -19497,11 +20986,123 @@ PulseAudio Backend ******************************************************************************/ #ifdef MA_HAS_PULSEAUDIO /* -It is assumed pulseaudio.h is available when compile-time linking is being used. We use this for type safety when using -compile time linking (we don't have this luxury when using runtime linking without headers). +The PulseAudio API, along with Apple's Core Audio, is the worst of the maintream audio APIs. This is a brief description of what's going on +in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion. -When using compile time linking, each of our ma_* equivalents should use the sames types as defined by the header. The -reason for this is that it allow us to take advantage of proper type safety. +PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it +allows you to enumerate over devices, nor does it seem to support the ability to stop and start streams. Looking at the documentation, it +appears as though the stream is constantly running and you prevent sound from being emitted or captured by simply not calling the read or +write functions. This is not a professional solution as it would be much better to *actually* stop the underlying stream. Perhaps the +simple API has some smarts to do this automatically, but I'm not sure. Another limitation with the simple API is that it seems inefficient +when you want to have multiple streams to a single context. For these reasons, miniaudio is not using the simple API. + +Since we're not using the simple API, we're left with the asynchronous API as our only other option. And boy, is this where it starts to +get fun, and I don't mean that in a good way... + +The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands +don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is +enabled through the use of a "main loop". In the asychronous API you cannot get away from the main loop, and the main loop is where almost +all of PulseAudio's problems stem from. + +When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own +vtable, but it's much easier to just use one of the built-in main loop implementations. There's two generic implementations called +pa_mainloop and pa_threaded_mainloop, and another implementation specific to GLib called pa_glib_mainloop. We're using pa_threaded_mainloop +because it simplifies management of the worker thread. The idea of the main loop object is pretty self explanatory - you're supposed to use +it to implement a worker thread which runs in a loop. The main loop is where operations are actually executed. + +To initialize the main loop, you just use `pa_threaded_mainloop_new()`. This is the first function you'll call. You can then get a pointer +to the vtable with `pa_threaded_mainloop_get_api()` (the main loop vtable is called `pa_mainloop_api`). Again, you can bypass the threaded +main loop object entirely and just implement `pa_mainloop_api` directly, but there's no need for it unless you're doing something extremely +specialized such as if you want to integrate it into your application's existing main loop infrastructure. + +(EDIT 2021-01-26: miniaudio is no longer using `pa_threaded_mainloop` due to this issue: https://github.com/mackron/miniaudio/issues/262. +It is now using `pa_mainloop` which turns out to be a simpler solution anyway. The rest of this rant still applies, however.) + +Once you have your main loop vtable (the `pa_mainloop_api` object) you can create the PulseAudio context. This is very similar to +miniaudio's context and they map to each other quite well. You have one context to many streams, which is basically the same as miniaudio's +one `ma_context` to many `ma_device`s. Here's where it starts to get annoying, however. When you first create the PulseAudio context, which +is done with `pa_context_new()`, it's not actually connected to anything. When you connect, you call `pa_context_connect()`. However, if +you remember, PulseAudio is an asynchronous API. That means you cannot just assume the context is connected after `pa_context_context()` +has returned. You instead need to wait for it to connect. To do this, you need to either wait for a callback to get fired, which you can +set with `pa_context_set_state_callback()`, or you can continuously poll the context's state. Either way, you need to run this in a loop. +All objects from here out are created from the context, and, I believe, you can't be creating these objects until the context is connected. +This waiting loop is therefore unavoidable. In order for the waiting to ever complete, however, the main loop needs to be running. Before +attempting to connect the context, the main loop needs to be started with `pa_threaded_mainloop_start()`. + +The reason for this asynchronous design is to support cases where you're connecting to a remote server, say through a local network or an +internet connection. However, the *VAST* majority of cases don't involve this at all - they just connect to a local "server" running on the +host machine. The fact that this would be the default rather than making `pa_context_connect()` synchronous tends to boggle the mind. + +Once the context has been created and connected you can start creating a stream. A PulseAudio stream is analogous to miniaudio's device. +The initialization of a stream is fairly standard - you configure some attributes (analogous to miniaudio's device config) and then call +`pa_stream_new()` to actually create it. Here is where we start to get into "operations". When configuring the stream, you can get +information about the source (such as sample format, sample rate, etc.), however it's not synchronous. Instead, a `pa_operation` object +is returned from `pa_context_get_source_info_by_name()` (capture) or `pa_context_get_sink_info_by_name()` (playback). Then, you need to +run a loop (again!) to wait for the operation to complete which you can determine via a callback or polling, just like we did with the +context. Then, as an added bonus, you need to decrement the reference counter of the `pa_operation` object to ensure memory is cleaned up. +All of that just to retrieve basic information about a device! + +Once the basic information about the device has been retrieved, miniaudio can now create the stream with `ma_stream_new()`. Like the +context, this needs to be connected. But we need to be careful here, because we're now about to introduce one of the most horrific design +choices in PulseAudio. + +PulseAudio allows you to specify a callback that is fired when data can be written to or read from a stream. The language is important here +because PulseAudio takes it literally, specifically the "can be". You would think these callbacks would be appropriate as the place for +writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can +set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices +straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified, +PulseAudio will immediately fire it's write or read callback. This is *technically* correct (based on the wording in the documentation) +because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback +would be where a program will want to write or read data to or from the stream, but when it's called before the application has even +requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at +that point (it may still need to load files or whatnot). Instead, this callback should only be fired when the application requests the +stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data +callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio +doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been +started. The device state is used for this - if the state is anything other than `MA_STATE_STARTING` or `MA_STATE_STARTED`, the main data +callback is not fired. + +This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will +continuously fire the callback at regular intervals based on the size of the internal buffer. This will only ever be fired when the device +is running, and will be fired regardless of whether or not the user actually wrote anything to the device/stream. This not the case in +PulseAudio. In PulseAudio, the data callback will *only* be called if you wrote something to it previously. That means, if you don't call +`pa_stream_write()`, the callback will not get fired. On the surface you wouldn't think this would matter because you should be always +writing data, and if you don't have anything to write, just write silence. That's fine until you want to drain the stream. You see, if +you're continuously writing data to the stream, the stream will never get drained! That means in order to drain the stream, you need to +*not* write data to it! But remember, when you don't write data to the stream, the callback won't get fired again! Why is draining +important? Because that's how we've defined stopping to work in miniaudio. In miniaudio, stopping the device requires it to be drained +before returning from ma_device_stop(). So we've stopped the device, which requires us to drain, but draining requires us to *not* write +data to the stream (or else it won't ever complete draining), but not writing to the stream means the callback won't get fired again! + +This becomes a problem when stopping and then restarting the device. When the device is stopped, it's drained, which requires us to *not* +write anything to the stream. But then, since we didn't write anything to it, the write callback will *never* get called again if we just +resume the stream naively. This means that starting the stream requires us to write data to the stream from outside the callback. This +disconnect is something PulseAudio has got seriously wrong - there should only ever be a single source of data delivery, that being the +callback. (I have tried using `pa_stream_flush()` to trigger the write callback to fire, but this just doesn't work for some reason.) + +Once you've created the stream, you need to connect it which involves the whole waiting procedure. This is the same process as the context, +only this time you'll poll for the state with `pa_stream_get_status()`. The starting and stopping of a streaming is referred to as +"corking" in PulseAudio. The analogy is corking a barrel. To start the stream, you uncork it, to stop it you cork it. Personally I think +it's silly - why would you not just call it "starting" and "stopping" like any other normal audio API? Anyway, the act of corking is, you +guessed it, asynchronous. This means you'll need our waiting loop as usual. Again, why this asynchronous design is the default is +absolutely beyond me. Would it really be that hard to just make it run synchronously? + +Teardown is pretty simple (what?!). It's just a matter of calling the relevant `_unref()` function on each object in reverse order that +they were initialized in. + +That's about it from the PulseAudio side. A bit ranty, I know, but they really need to fix that main loop and callback system. They're +embarrassingly unpractical. The main loop thing is an easy fix - have synchronous versions of all APIs. If an application wants these to +run asynchronously, they can execute them in a separate thread themselves. The desire to run these asynchronously is such a niche +requirement - it makes no sense to make it the default. The stream write callback needs to be change, or an alternative provided, that is +constantly fired, regardless of whether or not `pa_stream_write()` has been called, and it needs to take a pointer to a buffer as a +parameter which the program just writes to directly rather than having to call `pa_stream_writable_size()` and `pa_stream_write()`. These +changes alone will change PulseAudio from one of the worst audio APIs to one of the best. +*/ + + +/* +It is assumed pulseaudio.h is available when linking at compile time. When linking at compile time, we use the declarations in the header +to check for type safety. We cannot do this when linking at run time because the header might not be available. */ #ifdef MA_NO_RUNTIME_LINKING @@ -19700,25 +21301,26 @@ typedef pa_sample_format_t ma_pa_sample_format_t; #define MA_PA_SAMPLE_S24_32LE PA_SAMPLE_S24_32LE #define MA_PA_SAMPLE_S24_32BE PA_SAMPLE_S24_32BE -typedef pa_mainloop ma_pa_mainloop; -typedef pa_mainloop_api ma_pa_mainloop_api; -typedef pa_context ma_pa_context; -typedef pa_operation ma_pa_operation; -typedef pa_stream ma_pa_stream; -typedef pa_spawn_api ma_pa_spawn_api; -typedef pa_buffer_attr ma_pa_buffer_attr; -typedef pa_channel_map ma_pa_channel_map; -typedef pa_cvolume ma_pa_cvolume; -typedef pa_sample_spec ma_pa_sample_spec; -typedef pa_sink_info ma_pa_sink_info; -typedef pa_source_info ma_pa_source_info; +typedef pa_mainloop ma_pa_mainloop; +typedef pa_threaded_mainloop ma_pa_threaded_mainloop; +typedef pa_mainloop_api ma_pa_mainloop_api; +typedef pa_context ma_pa_context; +typedef pa_operation ma_pa_operation; +typedef pa_stream ma_pa_stream; +typedef pa_spawn_api ma_pa_spawn_api; +typedef pa_buffer_attr ma_pa_buffer_attr; +typedef pa_channel_map ma_pa_channel_map; +typedef pa_cvolume ma_pa_cvolume; +typedef pa_sample_spec ma_pa_sample_spec; +typedef pa_sink_info ma_pa_sink_info; +typedef pa_source_info ma_pa_source_info; -typedef pa_context_notify_cb_t ma_pa_context_notify_cb_t; -typedef pa_sink_info_cb_t ma_pa_sink_info_cb_t; -typedef pa_source_info_cb_t ma_pa_source_info_cb_t; -typedef pa_stream_success_cb_t ma_pa_stream_success_cb_t; -typedef pa_stream_request_cb_t ma_pa_stream_request_cb_t; -typedef pa_free_cb_t ma_pa_free_cb_t; +typedef pa_context_notify_cb_t ma_pa_context_notify_cb_t; +typedef pa_sink_info_cb_t ma_pa_sink_info_cb_t; +typedef pa_source_info_cb_t ma_pa_source_info_cb_t; +typedef pa_stream_success_cb_t ma_pa_stream_success_cb_t; +typedef pa_stream_request_cb_t ma_pa_stream_request_cb_t; +typedef pa_free_cb_t ma_pa_free_cb_t; #else #define MA_PA_OK 0 #define MA_PA_ERR_ACCESS 1 @@ -19900,12 +21502,13 @@ typedef int ma_pa_sample_format_t; #define MA_PA_SAMPLE_S24_32LE 11 #define MA_PA_SAMPLE_S24_32BE 12 -typedef struct ma_pa_mainloop ma_pa_mainloop; -typedef struct ma_pa_mainloop_api ma_pa_mainloop_api; -typedef struct ma_pa_context ma_pa_context; -typedef struct ma_pa_operation ma_pa_operation; -typedef struct ma_pa_stream ma_pa_stream; -typedef struct ma_pa_spawn_api ma_pa_spawn_api; +typedef struct ma_pa_mainloop ma_pa_mainloop; +typedef struct ma_pa_threaded_mainloop ma_pa_threaded_mainloop; +typedef struct ma_pa_mainloop_api ma_pa_mainloop_api; +typedef struct ma_pa_context ma_pa_context; +typedef struct ma_pa_operation ma_pa_operation; +typedef struct ma_pa_stream ma_pa_stream; +typedef struct ma_pa_spawn_api ma_pa_spawn_api; typedef struct { @@ -20002,9 +21605,23 @@ typedef void (* ma_pa_free_cb_t) (void* p); typedef ma_pa_mainloop* (* ma_pa_mainloop_new_proc) (void); typedef void (* ma_pa_mainloop_free_proc) (ma_pa_mainloop* m); +typedef void (* ma_pa_mainloop_quit_proc) (ma_pa_mainloop* m, int retval); typedef ma_pa_mainloop_api* (* ma_pa_mainloop_get_api_proc) (ma_pa_mainloop* m); typedef int (* ma_pa_mainloop_iterate_proc) (ma_pa_mainloop* m, int block, int* retval); typedef void (* ma_pa_mainloop_wakeup_proc) (ma_pa_mainloop* m); +typedef ma_pa_threaded_mainloop* (* ma_pa_threaded_mainloop_new_proc) (void); +typedef void (* ma_pa_threaded_mainloop_free_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_start_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_stop_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_lock_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_unlock_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_wait_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_signal_proc) (ma_pa_threaded_mainloop* m, int wait_for_accept); +typedef void (* ma_pa_threaded_mainloop_accept_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_get_retval_proc) (ma_pa_threaded_mainloop* m); +typedef ma_pa_mainloop_api* (* ma_pa_threaded_mainloop_get_api_proc) (ma_pa_threaded_mainloop* m); +typedef int (* ma_pa_threaded_mainloop_in_thread_proc) (ma_pa_threaded_mainloop* m); +typedef void (* ma_pa_threaded_mainloop_set_name_proc) (ma_pa_threaded_mainloop* m, const char* name); typedef ma_pa_context* (* ma_pa_context_new_proc) (ma_pa_mainloop_api* mainloop, const char* name); typedef void (* ma_pa_context_unref_proc) (ma_pa_context* c); typedef int (* ma_pa_context_connect_proc) (ma_pa_context* c, const char* server, ma_pa_context_flags_t flags, const ma_pa_spawn_api* api); @@ -20054,12 +21671,16 @@ typedef struct static ma_result ma_result_from_pulse(int result) { + if (result < 0) { + return MA_ERROR; + } + switch (result) { case MA_PA_OK: return MA_SUCCESS; case MA_PA_ERR_ACCESS: return MA_ACCESS_DENIED; case MA_PA_ERR_INVALID: return MA_INVALID_ARGS; case MA_PA_ERR_NOENTITY: return MA_NO_DEVICE; - default: return MA_ERROR; + default: return MA_ERROR; } } @@ -20222,388 +21843,93 @@ static ma_pa_channel_position_t ma_channel_position_to_pulse(ma_channel position } #endif -static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_mainloop* pMainLoop, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation__pulse(ma_context* pContext, ma_pa_operation* pOP) { + int resultPA; + ma_pa_operation_state_t state; + MA_ASSERT(pContext != NULL); - MA_ASSERT(pMainLoop != NULL); MA_ASSERT(pOP != NULL); - while (((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP) == MA_PA_OPERATION_RUNNING) { - int error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); - if (error < 0) { - return ma_result_from_pulse(error); + for (;;) { + state = ((ma_pa_operation_get_state_proc)pContext->pulse.pa_operation_get_state)(pOP); + if (state != MA_PA_OPERATION_RUNNING) { + break; /* Done. */ + } + + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + if (resultPA < 0) { + return ma_result_from_pulse(resultPA); } } return MA_SUCCESS; } -static ma_result ma_device__wait_for_operation__pulse(ma_device* pDevice, ma_pa_operation* pOP) +static ma_result ma_wait_for_operation_and_unref__pulse(ma_context* pContext, ma_pa_operation* pOP) { - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pOP != NULL); + ma_result result; - return ma_wait_for_operation__pulse(pDevice->pContext, (ma_pa_mainloop*)pDevice->pulse.pMainLoop, pOP); -} - - -static ma_bool32 ma_context_is_device_id_equal__pulse(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return ma_strcmp(pID0->pulse, pID1->pulse) == 0; -} - - -typedef struct -{ - ma_context* pContext; - ma_enum_devices_callback_proc callback; - void* pUserData; - ma_bool32 isTerminated; -} ma_context_enumerate_devices_callback_data__pulse; - -static void ma_context_enumerate_devices_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) -{ - ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; - ma_device_info deviceInfo; - - MA_ASSERT(pData != NULL); - - if (endOfList || pData->isTerminated) { - return; + if (pOP == NULL) { + return MA_INVALID_ARGS; } - MA_ZERO_OBJECT(&deviceInfo); + result = ma_wait_for_operation__pulse(pContext, pOP); + ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - /* The name from PulseAudio is the ID for miniaudio. */ - if (pSinkInfo->name != NULL) { - ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } - - /* The description from PulseAudio is the name for miniaudio. */ - if (pSinkInfo->description != NULL) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); - } - - pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_playback, &deviceInfo, pData->pUserData); - - (void)pPulseContext; /* Unused. */ -} - -static void ma_context_enumerate_devices_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pSinkInfo, int endOfList, void* pUserData) -{ - ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; - ma_device_info deviceInfo; - - MA_ASSERT(pData != NULL); - - if (endOfList || pData->isTerminated) { - return; - } - - MA_ZERO_OBJECT(&deviceInfo); - - /* The name from PulseAudio is the ID for miniaudio. */ - if (pSinkInfo->name != NULL) { - ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); - } - - /* The description from PulseAudio is the name for miniaudio. */ - if (pSinkInfo->description != NULL) { - ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); - } - - pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_capture, &deviceInfo, pData->pUserData); - - (void)pPulseContext; /* Unused. */ -} - -static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) -{ - ma_result result = MA_SUCCESS; - ma_context_enumerate_devices_callback_data__pulse callbackData; - ma_pa_operation* pOP = NULL; - ma_pa_mainloop* pMainLoop; - ma_pa_mainloop_api* pAPI; - ma_pa_context* pPulseContext; - int error; - - MA_ASSERT(pContext != NULL); - MA_ASSERT(callback != NULL); - - callbackData.pContext = pContext; - callbackData.callback = callback; - callbackData.pUserData = pUserData; - callbackData.isTerminated = MA_FALSE; - - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->pulse.pApplicationName); - if (pPulseContext == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->pulse.pServerName, (pContext->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL); - if (error != MA_PA_OK) { - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return ma_result_from_pulse(error); - } - - for (;;) { - ma_pa_context_state_t state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); - if (state == MA_PA_CONTEXT_READY) { - break; /* Success. */ - } - if (state == MA_PA_CONTEXT_CONNECTING || state == MA_PA_CONTEXT_AUTHORIZING || state == MA_PA_CONTEXT_SETTING_NAME) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); - if (error < 0) { - result = ma_result_from_pulse(error); - goto done; - } - -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Waiting.\n", state); -#endif - continue; /* Keep trying. */ - } - if (state == MA_PA_CONTEXT_UNCONNECTED || state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Failed.\n", state); -#endif - goto done; /* Failed. */ - } - } - - - /* Playback. */ - if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)(pPulseContext, ma_context_enumerate_devices_sink_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MA_ERROR; - goto done; - } - - result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - if (result != MA_SUCCESS) { - goto done; - } - } - - - /* Capture. */ - if (!callbackData.isTerminated) { - pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)(pPulseContext, ma_context_enumerate_devices_source_callback__pulse, &callbackData); - if (pOP == NULL) { - result = MA_ERROR; - goto done; - } - - result = ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - if (result != MA_SUCCESS) { - goto done; - } - } - -done: - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); return result; } - -typedef struct +static ma_result ma_context_wait_for_pa_context_to_connect__pulse(ma_context* pContext) { - ma_device_info* pDeviceInfo; - ma_bool32 foundDevice; -} ma_context_get_device_info_callback_data__pulse; - -static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) -{ - ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; - - if (endOfList > 0) { - return; - } - - MA_ASSERT(pData != NULL); - pData->foundDevice = MA_TRUE; - - if (pInfo->name != NULL) { - ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); - } - - if (pInfo->description != NULL) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); - } - - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); - - (void)pPulseContext; /* Unused. */ -} - -static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) -{ - ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; - - if (endOfList > 0) { - return; - } - - MA_ASSERT(pData != NULL); - pData->foundDevice = MA_TRUE; - - if (pInfo->name != NULL) { - ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); - } - - if (pInfo->description != NULL) { - ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); - } - - pData->pDeviceInfo->minChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->maxChannels = pInfo->sample_spec.channels; - pData->pDeviceInfo->minSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->maxSampleRate = pInfo->sample_spec.rate; - pData->pDeviceInfo->formatCount = 1; - pData->pDeviceInfo->formats[0] = ma_format_from_pulse(pInfo->sample_spec.format); - - (void)pPulseContext; /* Unused. */ -} - -static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) -{ - ma_result result = MA_SUCCESS; - ma_context_get_device_info_callback_data__pulse callbackData; - ma_pa_operation* pOP = NULL; - ma_pa_mainloop* pMainLoop; - ma_pa_mainloop_api* pAPI; - ma_pa_context* pPulseContext; - int error; - - MA_ASSERT(pContext != NULL); - - /* No exclusive mode with the PulseAudio backend. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - - callbackData.pDeviceInfo = pDeviceInfo; - callbackData.foundDevice = MA_FALSE; - - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - return MA_FAILED_TO_INIT_BACKEND; - } - - pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->pulse.pApplicationName); - if (pPulseContext == NULL) { - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return MA_FAILED_TO_INIT_BACKEND; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->pulse.pServerName, 0, NULL); - if (error != MA_PA_OK) { - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return ma_result_from_pulse(error); - } + int resultPA; + ma_pa_context_state_t state; for (;;) { - ma_pa_context_state_t state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); + state = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)((ma_pa_context*)pContext->pulse.pPulseContext); if (state == MA_PA_CONTEXT_READY) { - break; /* Success. */ + break; /* Done. */ } - if (state == MA_PA_CONTEXT_CONNECTING || state == MA_PA_CONTEXT_AUTHORIZING || state == MA_PA_CONTEXT_SETTING_NAME) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)(pMainLoop, 1, NULL); - if (error < 0) { - result = ma_result_from_pulse(error); - goto done; - } -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Waiting.\n", state); -#endif - continue; /* Keep trying. */ + if (state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); } - if (state == MA_PA_CONTEXT_UNCONNECTED || state == MA_PA_CONTEXT_FAILED || state == MA_PA_CONTEXT_TERMINATED) { -#ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] pa_context_get_state() returned %d. Failed.\n", state); -#endif - goto done; /* Failed. */ + + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + if (resultPA < 0) { + return ma_result_from_pulse(resultPA); } } - if (deviceType == ma_device_type_playback) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)(pPulseContext, pDeviceID->pulse, ma_context_get_device_info_sink_callback__pulse, &callbackData); - } else { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)(pPulseContext, pDeviceID->pulse, ma_context_get_device_info_source_callback__pulse, &callbackData); - } - - if (pOP != NULL) { - ma_wait_for_operation__pulse(pContext, pMainLoop, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } else { - result = MA_ERROR; - goto done; - } - - if (!callbackData.foundDevice) { - result = MA_NO_DEVICE; - goto done; - } - - -done: - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - return result; + /* Should never get here. */ + return MA_SUCCESS; } - -static void ma_pulse_device_state_callback(ma_pa_context* pPulseContext, void* pUserData) +static ma_result ma_context_wait_for_pa_stream_to_connect__pulse(ma_context* pContext, ma_pa_stream* pStream) { - ma_device* pDevice; - ma_context* pContext; + int resultPA; + ma_pa_stream_state_t state; - pDevice = (ma_device*)pUserData; - MA_ASSERT(pDevice != NULL); + for (;;) { + state = ((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)(pStream); + if (state == MA_PA_STREAM_READY) { + break; /* Done. */ + } - pContext = pDevice->pContext; - MA_ASSERT(pContext != NULL); + if (state == MA_PA_STREAM_FAILED || state == MA_PA_STREAM_TERMINATED) { + return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio stream.", MA_ERROR); + } - pDevice->pulse.pulseContextState = ((ma_pa_context_get_state_proc)pContext->pulse.pa_context_get_state)(pPulseContext); + resultPA = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pContext->pulse.pMainLoop, 1, NULL); + if (resultPA < 0) { + return ma_result_from_pulse(resultPA); + } + } + + return MA_SUCCESS; } + static void ma_device_sink_info_callback(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) { ma_pa_sink_info* pInfoOut; @@ -20668,7 +21994,319 @@ static void ma_device_source_name_callback(ma_pa_context* pPulseContext, const m (void)pPulseContext; /* Unused. */ } -static void ma_device_uninit__pulse(ma_device* pDevice) + +static ma_result ma_context_get_sink_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_sink_info* pSinkInfo) +{ + ma_pa_operation* pOP; + + pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, pDeviceName, ma_device_sink_info_callback, pSinkInfo); + if (pOP == NULL) { + return MA_ERROR; + } + + return ma_wait_for_operation_and_unref__pulse(pContext, pOP); +} + +static ma_result ma_context_get_source_info__pulse(ma_context* pContext, const char* pDeviceName, ma_pa_source_info* pSourceInfo) +{ + ma_pa_operation* pOP; + + pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pContext->pulse.pPulseContext, pDeviceName, ma_device_source_info_callback, pSourceInfo); + if (pOP == NULL) { + return MA_ERROR; + } + + return ma_wait_for_operation_and_unref__pulse(pContext, pOP);; +} + +static ma_result ma_context_get_default_device_index__pulse(ma_context* pContext, ma_device_type deviceType, ma_uint32* pIndex) +{ + ma_result result; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pIndex != NULL); + + if (pIndex != NULL) { + *pIndex = (ma_uint32)-1; + } + + if (deviceType == ma_device_type_playback) { + ma_pa_sink_info sinkInfo; + result = ma_context_get_sink_info__pulse(pContext, NULL, &sinkInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pIndex != NULL) { + *pIndex = sinkInfo.index; + } + } + + if (deviceType == ma_device_type_capture) { + ma_pa_source_info sourceInfo; + result = ma_context_get_source_info__pulse(pContext, NULL, &sourceInfo); + if (result != MA_SUCCESS) { + return result; + } + + if (pIndex != NULL) { + *pIndex = sourceInfo.index; + } + } + + return MA_SUCCESS; +} + + +typedef struct +{ + ma_context* pContext; + ma_enum_devices_callback_proc callback; + void* pUserData; + ma_bool32 isTerminated; + ma_uint32 defaultDeviceIndexPlayback; + ma_uint32 defaultDeviceIndexCapture; +} ma_context_enumerate_devices_callback_data__pulse; + +static void ma_context_enumerate_devices_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pSinkInfo, int endOfList, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; + ma_device_info deviceInfo; + + MA_ASSERT(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* The name from PulseAudio is the ID for miniaudio. */ + if (pSinkInfo->name != NULL) { + ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSinkInfo->name, (size_t)-1); + } + + /* The description from PulseAudio is the name for miniaudio. */ + if (pSinkInfo->description != NULL) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSinkInfo->description, (size_t)-1); + } + + if (pSinkInfo->index == pData->defaultDeviceIndexPlayback) { + deviceInfo.isDefault = MA_TRUE; + } + + pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_playback, &deviceInfo, pData->pUserData); + + (void)pPulseContext; /* Unused. */ +} + +static void ma_context_enumerate_devices_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pSourceInfo, int endOfList, void* pUserData) +{ + ma_context_enumerate_devices_callback_data__pulse* pData = (ma_context_enumerate_devices_callback_data__pulse*)pUserData; + ma_device_info deviceInfo; + + MA_ASSERT(pData != NULL); + + if (endOfList || pData->isTerminated) { + return; + } + + MA_ZERO_OBJECT(&deviceInfo); + + /* The name from PulseAudio is the ID for miniaudio. */ + if (pSourceInfo->name != NULL) { + ma_strncpy_s(deviceInfo.id.pulse, sizeof(deviceInfo.id.pulse), pSourceInfo->name, (size_t)-1); + } + + /* The description from PulseAudio is the name for miniaudio. */ + if (pSourceInfo->description != NULL) { + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), pSourceInfo->description, (size_t)-1); + } + + if (pSourceInfo->index == pData->defaultDeviceIndexCapture) { + deviceInfo.isDefault = MA_TRUE; + } + + pData->isTerminated = !pData->callback(pData->pContext, ma_device_type_capture, &deviceInfo, pData->pUserData); + + (void)pPulseContext; /* Unused. */ +} + +static ma_result ma_context_enumerate_devices__pulse(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) +{ + ma_result result = MA_SUCCESS; + ma_context_enumerate_devices_callback_data__pulse callbackData; + ma_pa_operation* pOP = NULL; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(callback != NULL); + + callbackData.pContext = pContext; + callbackData.callback = callback; + callbackData.pUserData = pUserData; + callbackData.isTerminated = MA_FALSE; + callbackData.defaultDeviceIndexPlayback = (ma_uint32)-1; + callbackData.defaultDeviceIndexCapture = (ma_uint32)-1; + + /* We need to get the index of the default devices. */ + ma_context_get_default_device_index__pulse(pContext, ma_device_type_playback, &callbackData.defaultDeviceIndexPlayback); + ma_context_get_default_device_index__pulse(pContext, ma_device_type_capture, &callbackData.defaultDeviceIndexCapture); + + /* Playback. */ + if (!callbackData.isTerminated) { + pOP = ((ma_pa_context_get_sink_info_list_proc)pContext->pulse.pa_context_get_sink_info_list)((ma_pa_context*)(pContext->pulse.pPulseContext), ma_context_enumerate_devices_sink_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MA_ERROR; + goto done; + } + + result = ma_wait_for_operation__pulse(pContext, pOP); + ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + + if (result != MA_SUCCESS) { + goto done; + } + } + + + /* Capture. */ + if (!callbackData.isTerminated) { + pOP = ((ma_pa_context_get_source_info_list_proc)pContext->pulse.pa_context_get_source_info_list)((ma_pa_context*)(pContext->pulse.pPulseContext), ma_context_enumerate_devices_source_callback__pulse, &callbackData); + if (pOP == NULL) { + result = MA_ERROR; + goto done; + } + + result = ma_wait_for_operation__pulse(pContext, pOP); + ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); + + if (result != MA_SUCCESS) { + goto done; + } + } + +done: + return result; +} + + +typedef struct +{ + ma_device_info* pDeviceInfo; + ma_uint32 defaultDeviceIndex; + ma_bool32 foundDevice; +} ma_context_get_device_info_callback_data__pulse; + +static void ma_context_get_device_info_sink_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_sink_info* pInfo, int endOfList, void* pUserData) +{ + ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; + + if (endOfList > 0) { + return; + } + + MA_ASSERT(pData != NULL); + pData->foundDevice = MA_TRUE; + + if (pInfo->name != NULL) { + ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + } + + /* + We're just reporting a single data format here. I think technically PulseAudio might support + all formats, but I don't trust that PulseAudio will do *anything* right, so I'm just going to + report the "native" device format. + */ + pData->pDeviceInfo->nativeDataFormats[0].format = ma_format_from_pulse(pInfo->sample_spec.format); + pData->pDeviceInfo->nativeDataFormats[0].channels = pInfo->sample_spec.channels; + pData->pDeviceInfo->nativeDataFormats[0].sampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->nativeDataFormats[0].flags = 0; + pData->pDeviceInfo->nativeDataFormatCount = 1; + + if (pData->defaultDeviceIndex == pInfo->index) { + pData->pDeviceInfo->isDefault = MA_TRUE; + } + + (void)pPulseContext; /* Unused. */ +} + +static void ma_context_get_device_info_source_callback__pulse(ma_pa_context* pPulseContext, const ma_pa_source_info* pInfo, int endOfList, void* pUserData) +{ + ma_context_get_device_info_callback_data__pulse* pData = (ma_context_get_device_info_callback_data__pulse*)pUserData; + + if (endOfList > 0) { + return; + } + + MA_ASSERT(pData != NULL); + pData->foundDevice = MA_TRUE; + + if (pInfo->name != NULL) { + ma_strncpy_s(pData->pDeviceInfo->id.pulse, sizeof(pData->pDeviceInfo->id.pulse), pInfo->name, (size_t)-1); + } + + if (pInfo->description != NULL) { + ma_strncpy_s(pData->pDeviceInfo->name, sizeof(pData->pDeviceInfo->name), pInfo->description, (size_t)-1); + } + + /* + We're just reporting a single data format here. I think technically PulseAudio might support + all formats, but I don't trust that PulseAudio will do *anything* right, so I'm just going to + report the "native" device format. + */ + pData->pDeviceInfo->nativeDataFormats[0].format = ma_format_from_pulse(pInfo->sample_spec.format); + pData->pDeviceInfo->nativeDataFormats[0].channels = pInfo->sample_spec.channels; + pData->pDeviceInfo->nativeDataFormats[0].sampleRate = pInfo->sample_spec.rate; + pData->pDeviceInfo->nativeDataFormats[0].flags = 0; + pData->pDeviceInfo->nativeDataFormatCount = 1; + + if (pData->defaultDeviceIndex == pInfo->index) { + pData->pDeviceInfo->isDefault = MA_TRUE; + } + + (void)pPulseContext; /* Unused. */ +} + +static ma_result ma_context_get_device_info__pulse(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +{ + ma_result result = MA_SUCCESS; + ma_context_get_device_info_callback_data__pulse callbackData; + ma_pa_operation* pOP = NULL; + + MA_ASSERT(pContext != NULL); + + callbackData.pDeviceInfo = pDeviceInfo; + callbackData.foundDevice = MA_FALSE; + + result = ma_context_get_default_device_index__pulse(pContext, deviceType, &callbackData.defaultDeviceIndex); + + if (deviceType == ma_device_type_playback) { + pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)(pContext->pulse.pPulseContext), pDeviceID->pulse, ma_context_get_device_info_sink_callback__pulse, &callbackData); + } else { + pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)(pContext->pulse.pPulseContext), pDeviceID->pulse, ma_context_get_device_info_source_callback__pulse, &callbackData); + } + + if (pOP != NULL) { + ma_wait_for_operation_and_unref__pulse(pContext, pOP); + } else { + result = MA_ERROR; + goto done; + } + + if (!callbackData.foundDevice) { + result = MA_NO_DEVICE; + goto done; + } + +done: + return result; +} + +static ma_result ma_device_uninit__pulse(ma_device* pDevice) { ma_context* pContext; @@ -20681,14 +22319,17 @@ static void ma_device_uninit__pulse(ma_device* pDevice) ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); } + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); } - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); + if (pDevice->type == ma_device_type_duplex) { + ma_duplex_rb_uninit(&pDevice->duplexRB); + } + + return MA_SUCCESS; } static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFrames, ma_uint32 periods, const ma_pa_sample_spec* ss) @@ -20703,7 +22344,7 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra return attr; } -static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) +static ma_pa_stream* ma_context__pa_stream_new__pulse(ma_context* pContext, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; char actualStreamName[256]; @@ -20716,19 +22357,190 @@ static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const c } g_StreamCounter += 1; - return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); + return ((ma_pa_stream_new_proc)pContext->pulse.pa_stream_new)((ma_pa_context*)pContext->pulse.pPulseContext, actualStreamName, ss, cmap); } -static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) + +static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, void* pUserData) { + ma_device* pDevice = (ma_device*)pUserData; + ma_uint32 bpf; + ma_uint64 frameCount; + ma_uint64 framesProcessed; + + MA_ASSERT(pDevice != NULL); + + bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); + MA_ASSERT(bpf > 0); + + frameCount = byteCount / bpf; + framesProcessed = 0; + + while (ma_device_get_state(pDevice) == MA_STATE_STARTED && framesProcessed < frameCount) { + const void* pMappedPCMFrames; + size_t bytesMapped; + ma_uint64 framesMapped; + + int pulseResult = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)(pStream, &pMappedPCMFrames, &bytesMapped); + if (pulseResult < 0) { + break; /* Failed to map. Abort. */ + } + + framesMapped = bytesMapped / bpf; + if (framesMapped > 0) { + if (pMappedPCMFrames != NULL) { + ma_device_handle_backend_data_callback(pDevice, NULL, pMappedPCMFrames, framesMapped); + } else { + /* It's a hole. */ + #if defined(MA_DEBUG_OUTPUT) + printf("[PulseAudio] ma_device_on_read__pulse: Hole.\n"); + #endif + } + + pulseResult = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)(pStream); + if (pulseResult < 0) { + break; /* Failed to drop the buffer. */ + } + + framesProcessed += framesMapped; + + } else { + /* Nothing was mapped. Just abort. */ + break; + } + } +} + +static ma_result ma_device_write_to_stream__pulse(ma_device* pDevice, ma_pa_stream* pStream, ma_uint64* pFramesProcessed) +{ + ma_result result = MA_SUCCESS; + ma_uint64 framesProcessed = 0; + size_t bytesMapped; + ma_uint32 bpf; + ma_uint32 deviceState; + + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pStream != NULL); + + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + MA_ASSERT(bpf > 0); + + deviceState = ma_device_get_state(pDevice); + + bytesMapped = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)(pStream); + if (bytesMapped != (size_t)-1) { + if (bytesMapped > 0) { + ma_uint64 framesMapped; + void* pMappedPCMFrames; + int pulseResult = ((ma_pa_stream_begin_write_proc)pDevice->pContext->pulse.pa_stream_begin_write)(pStream, &pMappedPCMFrames, &bytesMapped); + if (pulseResult < 0) { + result = ma_result_from_pulse(pulseResult); + goto done; + } + + framesMapped = bytesMapped / bpf; + + if (deviceState == MA_STATE_STARTED) { + ma_device_handle_backend_data_callback(pDevice, pMappedPCMFrames, NULL, framesMapped); + } else { + /* Device is not started. Don't write anything to it. */ + } + + pulseResult = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)(pStream, pMappedPCMFrames, bytesMapped, NULL, 0, MA_PA_SEEK_RELATIVE); + if (pulseResult < 0) { + result = ma_result_from_pulse(pulseResult); + goto done; /* Failed to write data to stream. */ + } + + framesProcessed += framesMapped; + } else { + result = MA_ERROR; /* No data available. Abort. */ + goto done; + } + } else { + result = MA_ERROR; /* Failed to retrieve the writable size. Abort. */ + goto done; + } + +done: + if (pFramesProcessed != NULL) { + *pFramesProcessed = framesProcessed; + } + + return result; +} + +static void ma_device_on_write__pulse(ma_pa_stream* pStream, size_t byteCount, void* pUserData) +{ + ma_device* pDevice = (ma_device*)pUserData; + ma_uint32 bpf; + ma_uint64 frameCount; + ma_uint64 framesProcessed; + ma_uint32 deviceState; + ma_result result; + + MA_ASSERT(pDevice != NULL); + + /* + Don't do anything if the device isn't initialized yet. Yes, this can happen because PulseAudio + can fire this callback before the stream has even started. Ridiculous. + */ + deviceState = ma_device_get_state(pDevice); + if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + return; + } + + bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + MA_ASSERT(bpf > 0); + + frameCount = byteCount / bpf; + framesProcessed = 0; + + while (framesProcessed < frameCount) { + ma_uint64 framesProcessedThisIteration; + + /* Don't keep trying to process frames if the device isn't started. */ + deviceState = ma_device_get_state(pDevice); + if (deviceState != MA_STATE_STARTING && deviceState != MA_STATE_STARTED) { + break; + } + + result = ma_device_write_to_stream__pulse(pDevice, pStream, &framesProcessedThisIteration); + if (result != MA_SUCCESS) { + break; + } + + framesProcessed += framesProcessedThisIteration; + } +} + +static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +{ + /* + Notes for PulseAudio: + + - We're always using native format/channels/rate regardless of whether or not PulseAudio + supports the format directly through their own data conversion system. I'm doing this to + reduce as much variability from the PulseAudio side as possible because it's seems to be + extremely unreliable at everything it does. + + - When both the period size in frames and milliseconds are 0, we default to miniaudio's + default buffer sizes rather than leaving it up to PulseAudio because I don't trust + PulseAudio to give us any kind of reasonable latency by default. + + - Do not ever, *ever* forget to use MA_PA_STREAM_ADJUST_LATENCY. If you don't specify this + flag, capture mode will just not work properly until you open another PulseAudio app. + */ + ma_result result = MA_SUCCESS; int error = 0; const char* devPlayback = NULL; const char* devCapture = NULL; - ma_uint32 periodSizeInMilliseconds; + ma_format format = ma_format_unknown; + ma_uint32 channels = 0; + ma_uint32 sampleRate = 0; ma_pa_sink_info sinkInfo; ma_pa_source_info sourceInfo; - ma_pa_operation* pOP = NULL; ma_pa_sample_spec ss; ma_pa_channel_map cmap; ma_pa_buffer_attr attr; @@ -20751,283 +22563,240 @@ static ma_result ma_device_init__pulse(ma_context* pContext, const ma_device_con return MA_SHARE_MODE_NOT_SUPPORTED; } - if ((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.pDeviceID != NULL) { - devPlayback = pConfig->playback.pDeviceID->pulse; - } - if ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.pDeviceID != NULL) { - devCapture = pConfig->capture.pDeviceID->pulse; - } - - periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; - if (periodSizeInMilliseconds == 0) { - periodSizeInMilliseconds = ma_calculate_buffer_size_in_milliseconds_from_frames(pConfig->periodSizeInFrames, pConfig->sampleRate); - } - - pDevice->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pDevice->pulse.pMainLoop == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create main loop for device.", MA_FAILED_TO_INIT_BACKEND); - goto on_error0; - } - - pDevice->pulse.pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); - if (pDevice->pulse.pAPI == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve PulseAudio main loop.", MA_FAILED_TO_INIT_BACKEND); - goto on_error1; - } - - pDevice->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)((ma_pa_mainloop_api*)pDevice->pulse.pAPI, pContext->pulse.pApplicationName); - if (pDevice->pulse.pPulseContext == NULL) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context for device.", MA_FAILED_TO_INIT_BACKEND); - goto on_error1; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pDevice->pulse.pPulseContext, pContext->pulse.pServerName, (pContext->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL); - if (error != MA_PA_OK) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", ma_result_from_pulse(error)); - goto on_error2; - } - - - pDevice->pulse.pulseContextState = MA_PA_CONTEXT_UNCONNECTED; - ((ma_pa_context_set_state_callback_proc)pContext->pulse.pa_context_set_state_callback)((ma_pa_context*)pDevice->pulse.pPulseContext, ma_pulse_device_state_callback, pDevice); - - /* Wait for PulseAudio to get itself ready before returning. */ - for (;;) { - if (pDevice->pulse.pulseContextState == MA_PA_CONTEXT_READY) { - break; + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (pDescriptorPlayback->pDeviceID != NULL) { + devPlayback = pDescriptorPlayback->pDeviceID->pulse; } - /* An error may have occurred. */ - if (pDevice->pulse.pulseContextState == MA_PA_CONTEXT_FAILED || pDevice->pulse.pulseContextState == MA_PA_CONTEXT_TERMINATED) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while connecting the PulseAudio context.", MA_ERROR); - goto on_error3; - } + format = pDescriptorPlayback->format; + channels = pDescriptorPlayback->channels; + sampleRate = pDescriptorPlayback->sampleRate; + } - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio context.", ma_result_from_pulse(error)); - goto on_error3; + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + if (pDescriptorCapture->pDeviceID != NULL) { + devCapture = pDescriptorCapture->pDeviceID->pulse; } + + format = pDescriptorCapture->format; + channels = pDescriptorCapture->channels; + sampleRate = pDescriptorCapture->sampleRate; } if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devCapture, ma_device_source_info_callback, &sourceInfo); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } else { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", ma_result_from_pulse(error)); - goto on_error3; + result = ma_context_get_source_info__pulse(pDevice->pContext, devCapture, &sourceInfo); + if (result != MA_SUCCESS) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve source info for capture device.", result); + goto on_error0; } - ss = sourceInfo.sample_spec; + ss = sourceInfo.sample_spec; cmap = sourceInfo.channel_map; - pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, ss.rate); - pDevice->capture.internalPeriods = pConfig->periods; + /* We now have enough information to calculate our actual period size in frames. */ + pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, ss.rate, pConfig->performanceProfile); - attr = ma_device__pa_buffer_attr_new(pDevice->capture.internalPeriodSizeInFrames, pConfig->periods, &ss); + attr = ma_device__pa_buffer_attr_new(pDescriptorCapture->periodSizeInFrames, pDescriptorCapture->periodCount, &ss); #ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalPeriodSizeInFrames); + printf("[PulseAudio] Capture attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); #endif - pDevice->pulse.pStreamCapture = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNameCapture, &ss, &cmap); + pDevice->pulse.pStreamCapture = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNameCapture, &ss, &cmap); if (pDevice->pulse.pStreamCapture == NULL) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio capture stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); - goto on_error3; + goto on_error0; } - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + + /* The callback needs to be set before connecting the stream. */ + ((ma_pa_stream_set_read_callback_proc)pDevice->pContext->pulse.pa_stream_set_read_callback)((ma_pa_stream*)pDevice->pulse.pStreamCapture, ma_device_on_read__pulse, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; if (devCapture != NULL) { streamFlags |= MA_PA_STREAM_DONT_MOVE; } - error = ((ma_pa_stream_connect_record_proc)pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); + error = ((ma_pa_stream_connect_record_proc)pDevice->pContext->pulse.pa_stream_connect_record)((ma_pa_stream*)pDevice->pulse.pStreamCapture, devCapture, &attr, streamFlags); if (error != MA_PA_OK) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error4; + goto on_error1; } - while (((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamCapture) != MA_PA_STREAM_READY) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio capture stream.", ma_result_from_pulse(error)); - goto on_error5; - } + result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (result != MA_SUCCESS) { + goto on_error2; } /* Internal format. */ - pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualSS != NULL) { - /* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */ - if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { - attr = ma_device__pa_buffer_attr_new(pDevice->capture.internalPeriodSizeInFrames, pConfig->periods, pActualSS); - - pOP = ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &attr, NULL, NULL); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } - } - ss = *pActualSS; } - pDevice->capture.internalFormat = ma_format_from_pulse(ss.format); - pDevice->capture.internalChannels = ss.channels; - pDevice->capture.internalSampleRate = ss.rate; + pDescriptorCapture->format = ma_format_from_pulse(ss.format); + pDescriptorCapture->channels = ss.channels; + pDescriptorCapture->sampleRate = ss.rate; /* Internal channel map. */ - pActualCMap = ((ma_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualCMap != NULL) { cmap = *pActualCMap; } + for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { - pDevice->capture.internalChannelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + pDescriptorCapture->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); } + /* Buffer. */ - pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pDevice->pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (pActualAttr != NULL) { attr = *pActualAttr; } - pDevice->capture.internalPeriods = attr.maxlength / attr.fragsize; - pDevice->capture.internalPeriodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels) / pDevice->capture.internalPeriods; + + pDescriptorCapture->periodCount = attr.maxlength / attr.fragsize; + pDescriptorCapture->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) / pDescriptorCapture->periodCount; #ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->capture.internalPeriodSizeInFrames); + printf("[PulseAudio] Capture actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorCapture->periodSizeInFrames); #endif + /* Name. */ - devCapture = ((ma_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + devCapture = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamCapture); if (devCapture != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } + ma_pa_operation* pOP = ((ma_pa_context_get_source_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_source_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devCapture, ma_device_source_name_callback, pDevice); + ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devPlayback, ma_device_sink_info_callback, &sinkInfo); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } else { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", ma_result_from_pulse(error)); - goto on_error3; + result = ma_context_get_sink_info__pulse(pDevice->pContext, devPlayback, &sinkInfo); + if (result != MA_SUCCESS) { + ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to retrieve sink info for playback device.", result); + goto on_error2; } - ss = sinkInfo.sample_spec; + ss = sinkInfo.sample_spec; cmap = sinkInfo.channel_map; - pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, ss.rate); - pDevice->playback.internalPeriods = pConfig->periods; + /* We now have enough information to calculate the actual buffer size in frames. */ + pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, ss.rate, pConfig->performanceProfile); - attr = ma_device__pa_buffer_attr_new(pDevice->playback.internalPeriodSizeInFrames, pConfig->periods, &ss); + attr = ma_device__pa_buffer_attr_new(pDescriptorPlayback->periodSizeInFrames, pDescriptorPlayback->periodCount, &ss); #ifdef MA_DEBUG_OUTPUT - printf("[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalPeriodSizeInFrames); + printf("[PulseAudio] Playback attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; periodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDescriptorPlayback->periodSizeInFrames); #endif - pDevice->pulse.pStreamPlayback = ma_device__pa_stream_new__pulse(pDevice, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); + pDevice->pulse.pStreamPlayback = ma_context__pa_stream_new__pulse(pDevice->pContext, pConfig->pulse.pStreamNamePlayback, &ss, &cmap); if (pDevice->pulse.pStreamPlayback == NULL) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio playback stream.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); - goto on_error3; + goto on_error2; } - streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; + + /* + Note that this callback will be fired as soon as the stream is connected, even though it's started as corked. The callback needs to handle a + device state of MA_STATE_UNINITIALIZED. + */ + ((ma_pa_stream_set_write_callback_proc)pDevice->pContext->pulse.pa_stream_set_write_callback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_device_on_write__pulse, pDevice); + + + /* Connect after we've got all of our internal state set up. */ + streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_FIX_FORMAT | MA_PA_STREAM_FIX_RATE | MA_PA_STREAM_FIX_CHANNELS; if (devPlayback != NULL) { streamFlags |= MA_PA_STREAM_DONT_MOVE; } - error = ((ma_pa_stream_connect_playback_proc)pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); + error = ((ma_pa_stream_connect_playback_proc)pDevice->pContext->pulse.pa_stream_connect_playback)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, devPlayback, &attr, streamFlags, NULL, NULL); if (error != MA_PA_OK) { result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error6; + goto on_error3; } - while (((ma_pa_stream_get_state_proc)pContext->pulse.pa_stream_get_state)((ma_pa_stream*)pDevice->pulse.pStreamPlayback) != MA_PA_STREAM_READY) { - error = ((ma_pa_mainloop_iterate_proc)pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] The PulseAudio main loop returned an error while connecting the PulseAudio playback stream.", ma_result_from_pulse(error)); - goto on_error7; - } + result = ma_context_wait_for_pa_stream_to_connect__pulse(pDevice->pContext, (ma_pa_stream*)pDevice->pulse.pStreamPlayback); + if (result != MA_SUCCESS) { + goto on_error3; } + /* Internal format. */ - pActualSS = ((ma_pa_stream_get_sample_spec_proc)pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + pActualSS = ((ma_pa_stream_get_sample_spec_proc)pDevice->pContext->pulse.pa_stream_get_sample_spec)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualSS != NULL) { - /* If anything has changed between the requested and the actual sample spec, we need to update the buffer. */ - if (ss.format != pActualSS->format || ss.channels != pActualSS->channels || ss.rate != pActualSS->rate) { - attr = ma_device__pa_buffer_attr_new(pDevice->playback.internalPeriodSizeInFrames, pConfig->periods, pActualSS); - - pOP = ((ma_pa_stream_set_buffer_attr_proc)pContext->pulse.pa_stream_set_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &attr, NULL, NULL); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } - } - ss = *pActualSS; } - pDevice->playback.internalFormat = ma_format_from_pulse(ss.format); - pDevice->playback.internalChannels = ss.channels; - pDevice->playback.internalSampleRate = ss.rate; + pDescriptorPlayback->format = ma_format_from_pulse(ss.format); + pDescriptorPlayback->channels = ss.channels; + pDescriptorPlayback->sampleRate = ss.rate; /* Internal channel map. */ - pActualCMap = ((ma_pa_stream_get_channel_map_proc)pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + pActualCMap = ((ma_pa_stream_get_channel_map_proc)pDevice->pContext->pulse.pa_stream_get_channel_map)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualCMap != NULL) { cmap = *pActualCMap; } + for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { - pDevice->playback.internalChannelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); + pDescriptorPlayback->channelMap[iChannel] = ma_channel_position_from_pulse(cmap.map[iChannel]); } + /* Buffer. */ - pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + pActualAttr = ((ma_pa_stream_get_buffer_attr_proc)pDevice->pContext->pulse.pa_stream_get_buffer_attr)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (pActualAttr != NULL) { attr = *pActualAttr; } - pDevice->playback.internalPeriods = attr.maxlength / attr.tlength; - pDevice->playback.internalPeriodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels) / pDevice->playback.internalPeriods; + + pDescriptorPlayback->periodCount = attr.maxlength / attr.tlength; + pDescriptorPlayback->periodSizeInFrames = attr.maxlength / ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) / pDescriptorPlayback->periodCount; #ifdef MA_DEBUG_OUTPUT printf("[PulseAudio] Playback actual attr: maxlength=%d, tlength=%d, prebuf=%d, minreq=%d, fragsize=%d; internalPeriodSizeInFrames=%d\n", attr.maxlength, attr.tlength, attr.prebuf, attr.minreq, attr.fragsize, pDevice->playback.internalPeriodSizeInFrames); #endif + /* Name. */ - devPlayback = ((ma_pa_stream_get_device_name_proc)pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + devPlayback = ((ma_pa_stream_get_device_name_proc)pDevice->pContext->pulse.pa_stream_get_device_name)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); if (devPlayback != NULL) { - ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - } + ma_pa_operation* pOP = ((ma_pa_context_get_sink_info_by_name_proc)pDevice->pContext->pulse.pa_context_get_sink_info_by_name)((ma_pa_context*)pDevice->pContext->pulse.pPulseContext, devPlayback, ma_device_sink_name_callback, pDevice); + ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); + } + } + + + /* + We need a ring buffer for handling duplex mode. We can use the main duplex ring buffer in the main + part of the ma_device struct. We cannot, however, depend on ma_device_init() initializing this for + us later on because that will only do it if it's a fully asynchronous backend - i.e. the + onDeviceDataLoop callback is NULL, which is not the case for PulseAudio. + */ + if (pConfig->deviceType == ma_device_type_duplex) { + result = ma_duplex_rb_init(format, channels, sampleRate, pDescriptorCapture->sampleRate, pDescriptorCapture->periodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); + if (result != MA_SUCCESS) { + result = ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to initialize ring buffer.", result); + goto on_error4; } } return MA_SUCCESS; -on_error7: - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - } -on_error6: - if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - } -on_error5: - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_disconnect_proc)pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - } on_error4: - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ((ma_pa_stream_unref_proc)pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ((ma_pa_stream_disconnect_proc)pDevice->pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + } +on_error3: + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + ((ma_pa_stream_unref_proc)pDevice->pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); + } +on_error2: + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + ((ma_pa_stream_disconnect_proc)pDevice->pContext->pulse.pa_stream_disconnect)((ma_pa_stream*)pDevice->pulse.pStreamCapture); + } +on_error1: + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + ((ma_pa_stream_unref_proc)pDevice->pContext->pulse.pa_stream_unref)((ma_pa_stream*)pDevice->pulse.pStreamCapture); } -on_error3: ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pDevice->pulse.pPulseContext); -on_error2: ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pDevice->pulse.pPulseContext); -on_error1: ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pDevice->pulse.pMainLoop); on_error0: return result; } @@ -21066,9 +22835,7 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to cork PulseAudio stream.", (cork == 0) ? MA_FAILED_TO_START_BACKEND_DEVICE : MA_FAILED_TO_STOP_BACKEND_DEVICE); } - result = ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pContext->pulse.pa_operation_unref)(pOP); - + result = ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); if (result != MA_SUCCESS) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] An error occurred while waiting for the PulseAudio stream to cork.", result); } @@ -21084,11 +22851,39 @@ static ma_result ma_device__cork_stream__pulse(ma_device* pDevice, ma_device_typ return MA_SUCCESS; } +static ma_result ma_device_start__pulse(ma_device* pDevice) +{ + ma_result result; + + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + /* We need to fill some data before uncorking. Not doing this will result in the write callback never getting fired. */ + result = ma_device_write_to_stream__pulse(pDevice, (ma_pa_stream*)(pDevice->pulse.pStreamPlayback), NULL); + if (result != MA_SUCCESS) { + return result; /* Failed to write data. Not sure what to do here... Just aborting. */ + } + + result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); + if (result != MA_SUCCESS) { + return result; + } + } + + return MA_SUCCESS; +} + static ma_result ma_device_stop__pulse(ma_device* pDevice) { ma_result result; ma_bool32 wasSuccessful; - ma_pa_operation* pOP; MA_ASSERT(pDevice != NULL); @@ -21101,11 +22896,8 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { /* The stream needs to be drained if it's a playback device. */ - pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); - if (pOP != NULL) { - ma_device__wait_for_operation__pulse(pDevice, pOP); - ((ma_pa_operation_unref_proc)pDevice->pContext->pulse.pa_operation_unref)(pOP); - } + ma_pa_operation* pOP = ((ma_pa_stream_drain_proc)pDevice->pContext->pulse.pa_stream_drain)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, ma_pulse_operation_complete_callback, &wasSuccessful); + ma_wait_for_operation_and_unref__pulse(pDevice->pContext, pOP); result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 1); if (result != MA_SUCCESS) { @@ -21116,443 +22908,46 @@ static ma_result ma_device_stop__pulse(ma_device* pDevice) return MA_SUCCESS; } -static ma_result ma_device_write__pulse(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) +static ma_result ma_device_data_loop__pulse(ma_device* pDevice) { - ma_uint32 totalFramesWritten; + int resultPA; MA_ASSERT(pDevice != NULL); - MA_ASSERT(pPCMFrames != NULL); - MA_ASSERT(frameCount > 0); - if (pFramesWritten != NULL) { - *pFramesWritten = 0; - } + /* NOTE: Don't start the device here. It'll be done at a higher level. */ - totalFramesWritten = 0; - while (totalFramesWritten < frameCount) { - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - return MA_DEVICE_NOT_STARTED; - } - - /* Place the data into the mapped buffer if we have one. */ - if (pDevice->pulse.pMappedBufferPlayback != NULL && pDevice->pulse.mappedBufferFramesRemainingPlayback > 0) { - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 mappedBufferFramesConsumed = pDevice->pulse.mappedBufferFramesCapacityPlayback - pDevice->pulse.mappedBufferFramesRemainingPlayback; - - void* pDst = (ma_uint8*)pDevice->pulse.pMappedBufferPlayback + (mappedBufferFramesConsumed * bpf); - const void* pSrc = (const ma_uint8*)pPCMFrames + (totalFramesWritten * bpf); - ma_uint32 framesToCopy = ma_min(pDevice->pulse.mappedBufferFramesRemainingPlayback, (frameCount - totalFramesWritten)); - MA_COPY_MEMORY(pDst, pSrc, framesToCopy * bpf); - - pDevice->pulse.mappedBufferFramesRemainingPlayback -= framesToCopy; - totalFramesWritten += framesToCopy; - } - - /* - Getting here means we've run out of data in the currently mapped chunk. We need to write this to the device and then try - mapping another chunk. If this fails we need to wait for space to become available. - */ - if (pDevice->pulse.mappedBufferFramesCapacityPlayback > 0 && pDevice->pulse.mappedBufferFramesRemainingPlayback == 0) { - size_t nbytes = pDevice->pulse.mappedBufferFramesCapacityPlayback * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - - int error = ((ma_pa_stream_write_proc)pDevice->pContext->pulse.pa_stream_write)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, pDevice->pulse.pMappedBufferPlayback, nbytes, NULL, 0, MA_PA_SEEK_RELATIVE); - if (error < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to write data to the PulseAudio stream.", ma_result_from_pulse(error)); - } - - pDevice->pulse.pMappedBufferPlayback = NULL; - pDevice->pulse.mappedBufferFramesRemainingPlayback = 0; - pDevice->pulse.mappedBufferFramesCapacityPlayback = 0; - } - - MA_ASSERT(totalFramesWritten <= frameCount); - if (totalFramesWritten == frameCount) { + /* + All data is handled through callbacks. All we need to do is iterate over the main loop and let + the callbacks deal with it. + */ + while (ma_device_get_state(pDevice) == MA_STATE_STARTED) { + resultPA = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop, 1, NULL); + if (resultPA < 0) { break; } - - /* Getting here means we need to map a new buffer. If we don't have enough space we need to wait for more. */ - for (;;) { - size_t writableSizeInBytes; - - /* If the device has been corked, don't try to continue. */ - if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamPlayback)) { - break; - } - - writableSizeInBytes = ((ma_pa_stream_writable_size_proc)pDevice->pContext->pulse.pa_stream_writable_size)((ma_pa_stream*)pDevice->pulse.pStreamPlayback); - if (writableSizeInBytes != (size_t)-1) { - if (writableSizeInBytes > 0) { - /* Data is avaialable. */ - size_t bytesToMap = writableSizeInBytes; - int error = ((ma_pa_stream_begin_write_proc)pDevice->pContext->pulse.pa_stream_begin_write)((ma_pa_stream*)pDevice->pulse.pStreamPlayback, &pDevice->pulse.pMappedBufferPlayback, &bytesToMap); - if (error < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to map write buffer.", ma_result_from_pulse(error)); - } - - pDevice->pulse.mappedBufferFramesCapacityPlayback = bytesToMap / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - pDevice->pulse.mappedBufferFramesRemainingPlayback = pDevice->pulse.mappedBufferFramesCapacityPlayback; - - break; - } else { - /* No data available. Need to wait for more. */ - int error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 1, NULL); - if (error < 0) { - return ma_result_from_pulse(error); - } - - continue; - } - } else { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to query the stream's writable size.", MA_ERROR); - } - } - } - - if (pFramesWritten != NULL) { - *pFramesWritten = totalFramesWritten; } + /* NOTE: Don't stop the device here. It'll be done at a higher level. */ return MA_SUCCESS; } -static ma_result ma_device_read__pulse(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) +static ma_result ma_device_data_loop_wakeup__pulse(ma_device* pDevice) { - ma_uint32 totalFramesRead; - MA_ASSERT(pDevice != NULL); - MA_ASSERT(pPCMFrames != NULL); - MA_ASSERT(frameCount > 0); - if (pFramesRead != NULL) { - *pFramesRead = 0; - } - - totalFramesRead = 0; - while (totalFramesRead < frameCount) { - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - return MA_DEVICE_NOT_STARTED; - } - - /* - If a buffer is mapped we need to read from that first. Once it's consumed we need to drop it. Note that pDevice->pulse.pMappedBufferCapture can be null in which - case it could be a hole. In this case we just write zeros into the output buffer. - */ - if (pDevice->pulse.mappedBufferFramesRemainingCapture > 0) { - ma_uint32 bpf = ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 mappedBufferFramesConsumed = pDevice->pulse.mappedBufferFramesCapacityCapture - pDevice->pulse.mappedBufferFramesRemainingCapture; - - ma_uint32 framesToCopy = ma_min(pDevice->pulse.mappedBufferFramesRemainingCapture, (frameCount - totalFramesRead)); - void* pDst = (ma_uint8*)pPCMFrames + (totalFramesRead * bpf); - - /* - This little bit of logic here is specifically for PulseAudio and it's hole management. The buffer pointer will be set to NULL - when the current fragment is a hole. For a hole we just output silence. - */ - if (pDevice->pulse.pMappedBufferCapture != NULL) { - const void* pSrc = (const ma_uint8*)pDevice->pulse.pMappedBufferCapture + (mappedBufferFramesConsumed * bpf); - MA_COPY_MEMORY(pDst, pSrc, framesToCopy * bpf); - } else { - MA_ZERO_MEMORY(pDst, framesToCopy * bpf); - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Filling hole with silence.\n"); - #endif - } - - pDevice->pulse.mappedBufferFramesRemainingCapture -= framesToCopy; - totalFramesRead += framesToCopy; - } - - /* - Getting here means we've run out of data in the currently mapped chunk. We need to drop this from the device and then try - mapping another chunk. If this fails we need to wait for data to become available. - */ - if (pDevice->pulse.mappedBufferFramesCapacityCapture > 0 && pDevice->pulse.mappedBufferFramesRemainingCapture == 0) { - int error; - - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Call pa_stream_drop()\n"); - #endif - - error = ((ma_pa_stream_drop_proc)pDevice->pContext->pulse.pa_stream_drop)((ma_pa_stream*)pDevice->pulse.pStreamCapture); - if (error != 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to drop fragment.", ma_result_from_pulse(error)); - } - - pDevice->pulse.pMappedBufferCapture = NULL; - pDevice->pulse.mappedBufferFramesRemainingCapture = 0; - pDevice->pulse.mappedBufferFramesCapacityCapture = 0; - } - - MA_ASSERT(totalFramesRead <= frameCount); - if (totalFramesRead == frameCount) { - break; - } - - /* Getting here means we need to map a new buffer. If we don't have enough data we wait for more. */ - for (;;) { - int error; - size_t bytesMapped; - - if (ma_device__get_state(pDevice) != MA_STATE_STARTED) { - break; - } - - /* If the device has been corked, don't try to continue. */ - if (((ma_pa_stream_is_corked_proc)pDevice->pContext->pulse.pa_stream_is_corked)((ma_pa_stream*)pDevice->pulse.pStreamCapture)) { - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Corked.\n"); - #endif - break; - } - - MA_ASSERT(pDevice->pulse.pMappedBufferCapture == NULL); /* <-- We're about to map a buffer which means we shouldn't have an existing mapping. */ - - error = ((ma_pa_stream_peek_proc)pDevice->pContext->pulse.pa_stream_peek)((ma_pa_stream*)pDevice->pulse.pStreamCapture, &pDevice->pulse.pMappedBufferCapture, &bytesMapped); - if (error < 0) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to peek capture buffer.", ma_result_from_pulse(error)); - } - - if (bytesMapped > 0) { - pDevice->pulse.mappedBufferFramesCapacityCapture = bytesMapped / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - pDevice->pulse.mappedBufferFramesRemainingCapture = pDevice->pulse.mappedBufferFramesCapacityCapture; - - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Mapped. mappedBufferFramesCapacityCapture=%d, mappedBufferFramesRemainingCapture=%d\n", pDevice->pulse.mappedBufferFramesCapacityCapture, pDevice->pulse.mappedBufferFramesRemainingCapture); - #endif - - if (pDevice->pulse.pMappedBufferCapture == NULL) { - /* It's a hole. */ - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: Call pa_stream_peek(). Hole.\n"); - #endif - } - - break; - } else { - if (pDevice->pulse.pMappedBufferCapture == NULL) { - /* Nothing available yet. Need to wait for more. */ - - /* - I have had reports of a deadlock in this part of the code. I have reproduced this when using the "Built-in Audio Analogue Stereo" device without - an actual microphone connected. I'm experimenting here by not blocking in pa_mainloop_iterate() and instead sleep for a bit when there are no - dispatches. - */ - error = ((ma_pa_mainloop_iterate_proc)pDevice->pContext->pulse.pa_mainloop_iterate)((ma_pa_mainloop*)pDevice->pulse.pMainLoop, 0, NULL); - if (error < 0) { - return ma_result_from_pulse(error); - } - - /* Sleep for a bit if nothing was dispatched. */ - if (error == 0) { - ma_sleep(1); - } - - #if defined(MA_DEBUG_OUTPUT) - printf("[PulseAudio] ma_device_read__pulse: No data available. Waiting. mappedBufferFramesCapacityCapture=%d, mappedBufferFramesRemainingCapture=%d\n", pDevice->pulse.mappedBufferFramesCapacityCapture, pDevice->pulse.mappedBufferFramesRemainingCapture); - #endif - } else { - /* Getting here means we mapped 0 bytes, but have a non-NULL buffer. I don't think this should ever happen. */ - MA_ASSERT(MA_FALSE); - } - } - } - } - - if (pFramesRead != NULL) { - *pFramesRead = totalFramesRead; - } + ((ma_pa_mainloop_wakeup_proc)pDevice->pContext->pulse.pa_mainloop_wakeup)((ma_pa_mainloop*)pDevice->pContext->pulse.pMainLoop); return MA_SUCCESS; } -static ma_result ma_device_main_loop__pulse(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - MA_ASSERT(pDevice != NULL); - - /* The stream needs to be uncorked first. We do this at the top for both capture and playback for PulseAudio. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_capture, 0); - if (result != MA_SUCCESS) { - return result; - } - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - result = ma_device__cork_stream__pulse(pDevice, ma_device_type_playback, 0); - if (result != MA_SUCCESS) { - return result; - } - } - - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__pulse(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__pulse(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__pulse()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__pulse(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__pulse(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - /* Here is where the device needs to be stopped. */ - ma_device_stop__pulse(pDevice); - - return result; -} - - static ma_result ma_context_uninit__pulse(ma_context* pContext) { MA_ASSERT(pContext != NULL); MA_ASSERT(pContext->backend == ma_backend_pulseaudio); - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - pContext->pulse.pServerName = NULL; - - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - pContext->pulse.pApplicationName = NULL; + ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)((ma_pa_context*)pContext->pulse.pPulseContext); + ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)((ma_pa_context*)pContext->pulse.pPulseContext); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)pContext->pulse.pMainLoop); #ifndef MA_NO_RUNTIME_LINKING ma_dlclose(pContext, pContext->pulse.pulseSO); @@ -21561,8 +22956,9 @@ static ma_result ma_context_uninit__pulse(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { + ma_result result; #ifndef MA_NO_RUNTIME_LINKING const char* libpulseNames[] = { "libpulse.so", @@ -21583,9 +22979,23 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->pulse.pa_mainloop_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_new"); pContext->pulse.pa_mainloop_free = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_free"); + pContext->pulse.pa_mainloop_quit = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_quit"); pContext->pulse.pa_mainloop_get_api = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_get_api"); pContext->pulse.pa_mainloop_iterate = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_iterate"); pContext->pulse.pa_mainloop_wakeup = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_mainloop_wakeup"); + pContext->pulse.pa_threaded_mainloop_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_new"); + pContext->pulse.pa_threaded_mainloop_free = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_free"); + pContext->pulse.pa_threaded_mainloop_start = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_start"); + pContext->pulse.pa_threaded_mainloop_stop = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_stop"); + pContext->pulse.pa_threaded_mainloop_lock = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_lock"); + pContext->pulse.pa_threaded_mainloop_unlock = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_unlock"); + pContext->pulse.pa_threaded_mainloop_wait = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_wait"); + pContext->pulse.pa_threaded_mainloop_signal = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_signal"); + pContext->pulse.pa_threaded_mainloop_accept = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_accept"); + pContext->pulse.pa_threaded_mainloop_get_retval = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_get_retval"); + pContext->pulse.pa_threaded_mainloop_get_api = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_get_api"); + pContext->pulse.pa_threaded_mainloop_in_thread = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_in_thread"); + pContext->pulse.pa_threaded_mainloop_set_name = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_threaded_mainloop_set_name"); pContext->pulse.pa_context_new = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_new"); pContext->pulse.pa_context_unref = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_unref"); pContext->pulse.pa_context_connect = (ma_proc)ma_dlsym(pContext, pContext->pulse.pulseSO, "pa_context_connect"); @@ -21629,9 +23039,23 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con /* This strange assignment system is just for type safety. */ ma_pa_mainloop_new_proc _pa_mainloop_new = pa_mainloop_new; ma_pa_mainloop_free_proc _pa_mainloop_free = pa_mainloop_free; + ma_pa_mainloop_quit_proc _pa_mainloop_quit = pa_mainloop_quit; ma_pa_mainloop_get_api_proc _pa_mainloop_get_api = pa_mainloop_get_api; ma_pa_mainloop_iterate_proc _pa_mainloop_iterate = pa_mainloop_iterate; ma_pa_mainloop_wakeup_proc _pa_mainloop_wakeup = pa_mainloop_wakeup; + ma_pa_threaded_mainloop_new_proc _pa_threaded_mainloop_new = pa_threaded_mainloop_new; + ma_pa_threaded_mainloop_free_proc _pa_threaded_mainloop_free = pa_threaded_mainloop_free; + ma_pa_threaded_mainloop_start_proc _pa_threaded_mainloop_start = pa_threaded_mainloop_start; + ma_pa_threaded_mainloop_stop_proc _pa_threaded_mainloop_stop = pa_threaded_mainloop_stop; + ma_pa_threaded_mainloop_lock_proc _pa_threaded_mainloop_lock = pa_threaded_mainloop_lock; + ma_pa_threaded_mainloop_unlock_proc _pa_threaded_mainloop_unlock = pa_threaded_mainloop_unlock; + ma_pa_threaded_mainloop_wait_proc _pa_threaded_mainloop_wait = pa_threaded_mainloop_wait; + ma_pa_threaded_mainloop_signal_proc _pa_threaded_mainloop_signal = pa_threaded_mainloop_signal; + ma_pa_threaded_mainloop_accept_proc _pa_threaded_mainloop_accept = pa_threaded_mainloop_accept; + ma_pa_threaded_mainloop_get_retval_proc _pa_threaded_mainloop_get_retval = pa_threaded_mainloop_get_retval; + ma_pa_threaded_mainloop_get_api_proc _pa_threaded_mainloop_get_api = pa_threaded_mainloop_get_api; + ma_pa_threaded_mainloop_in_thread_proc _pa_threaded_mainloop_in_thread = pa_threaded_mainloop_in_thread; + ma_pa_threaded_mainloop_set_name_proc _pa_threaded_mainloop_set_name = pa_threaded_mainloop_set_name; ma_pa_context_new_proc _pa_context_new = pa_context_new; ma_pa_context_unref_proc _pa_context_unref = pa_context_unref; ma_pa_context_connect_proc _pa_context_connect = pa_context_connect; @@ -21674,9 +23098,23 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->pulse.pa_mainloop_new = (ma_proc)_pa_mainloop_new; pContext->pulse.pa_mainloop_free = (ma_proc)_pa_mainloop_free; + pContext->pulse.pa_mainloop_quit = (ma_proc)_pa_mainloop_quit; pContext->pulse.pa_mainloop_get_api = (ma_proc)_pa_mainloop_get_api; pContext->pulse.pa_mainloop_iterate = (ma_proc)_pa_mainloop_iterate; pContext->pulse.pa_mainloop_wakeup = (ma_proc)_pa_mainloop_wakeup; + pContext->pulse.pa_threaded_mainloop_new = (ma_proc)_pa_threaded_mainloop_new; + pContext->pulse.pa_threaded_mainloop_free = (ma_proc)_pa_threaded_mainloop_free; + pContext->pulse.pa_threaded_mainloop_start = (ma_proc)_pa_threaded_mainloop_start; + pContext->pulse.pa_threaded_mainloop_stop = (ma_proc)_pa_threaded_mainloop_stop; + pContext->pulse.pa_threaded_mainloop_lock = (ma_proc)_pa_threaded_mainloop_lock; + pContext->pulse.pa_threaded_mainloop_unlock = (ma_proc)_pa_threaded_mainloop_unlock; + pContext->pulse.pa_threaded_mainloop_wait = (ma_proc)_pa_threaded_mainloop_wait; + pContext->pulse.pa_threaded_mainloop_signal = (ma_proc)_pa_threaded_mainloop_signal; + pContext->pulse.pa_threaded_mainloop_accept = (ma_proc)_pa_threaded_mainloop_accept; + pContext->pulse.pa_threaded_mainloop_get_retval = (ma_proc)_pa_threaded_mainloop_get_retval; + pContext->pulse.pa_threaded_mainloop_get_api = (ma_proc)_pa_threaded_mainloop_get_api; + pContext->pulse.pa_threaded_mainloop_in_thread = (ma_proc)_pa_threaded_mainloop_in_thread; + pContext->pulse.pa_threaded_mainloop_set_name = (ma_proc)_pa_threaded_mainloop_set_name; pContext->pulse.pa_context_new = (ma_proc)_pa_context_new; pContext->pulse.pa_context_unref = (ma_proc)_pa_context_unref; pContext->pulse.pa_context_connect = (ma_proc)_pa_context_connect; @@ -21718,83 +23156,62 @@ static ma_result ma_context_init__pulse(const ma_context_config* pConfig, ma_con pContext->pulse.pa_stream_readable_size = (ma_proc)_pa_stream_readable_size; #endif - pContext->onUninit = ma_context_uninit__pulse; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__pulse; - pContext->onEnumDevices = ma_context_enumerate_devices__pulse; - pContext->onGetDeviceInfo = ma_context_get_device_info__pulse; - pContext->onDeviceInit = ma_device_init__pulse; - pContext->onDeviceUninit = ma_device_uninit__pulse; - pContext->onDeviceStart = NULL; - pContext->onDeviceStop = NULL; - pContext->onDeviceMainLoop = ma_device_main_loop__pulse; - - if (pConfig->pulse.pApplicationName) { - pContext->pulse.pApplicationName = ma_copy_string(pConfig->pulse.pApplicationName, &pContext->allocationCallbacks); + /* The PulseAudio context maps well to miniaudio's notion of a context. The pa_context object will be initialized as part of the ma_context. */ + pContext->pulse.pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); + if (pContext->pulse.pMainLoop == NULL) { + result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create mainloop.", MA_FAILED_TO_INIT_BACKEND); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; } - if (pConfig->pulse.pServerName) { - pContext->pulse.pServerName = ma_copy_string(pConfig->pulse.pServerName, &pContext->allocationCallbacks); + + pContext->pulse.pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)((ma_pa_mainloop*)pContext->pulse.pMainLoop), pConfig->pulse.pApplicationName); + if (pContext->pulse.pPulseContext == NULL) { + result = ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to create PulseAudio context.", MA_FAILED_TO_INIT_BACKEND); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; } - pContext->pulse.tryAutoSpawn = pConfig->pulse.tryAutoSpawn; - - /* - Although we have found the libpulse library, it doesn't necessarily mean PulseAudio is useable. We need to initialize - and connect a dummy PulseAudio context to test PulseAudio's usability. - */ - { - ma_pa_mainloop* pMainLoop; - ma_pa_mainloop_api* pAPI; - ma_pa_context* pPulseContext; - int error; - pMainLoop = ((ma_pa_mainloop_new_proc)pContext->pulse.pa_mainloop_new)(); - if (pMainLoop == NULL) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - pAPI = ((ma_pa_mainloop_get_api_proc)pContext->pulse.pa_mainloop_get_api)(pMainLoop); - if (pAPI == NULL) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - pPulseContext = ((ma_pa_context_new_proc)pContext->pulse.pa_context_new)(pAPI, pContext->pulse.pApplicationName); - if (pPulseContext == NULL) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - error = ((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)(pPulseContext, pContext->pulse.pServerName, 0, NULL); - if (error != MA_PA_OK) { - ma_free(pContext->pulse.pServerName, &pContext->allocationCallbacks); - ma_free(pContext->pulse.pApplicationName, &pContext->allocationCallbacks); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); - #ifndef MA_NO_RUNTIME_LINKING - ma_dlclose(pContext, pContext->pulse.pulseSO); - #endif - return MA_NO_BACKEND; - } - - ((ma_pa_context_disconnect_proc)pContext->pulse.pa_context_disconnect)(pPulseContext); - ((ma_pa_context_unref_proc)pContext->pulse.pa_context_unref)(pPulseContext); - ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)(pMainLoop); + /* Now we need to connect to the context. Everything is asynchronous so we need to wait for it to connect before returning. */ + result = ma_result_from_pulse(((ma_pa_context_connect_proc)pContext->pulse.pa_context_connect)((ma_pa_context*)pContext->pulse.pPulseContext, pConfig->pulse.pServerName, (pConfig->pulse.tryAutoSpawn) ? 0 : MA_PA_CONTEXT_NOAUTOSPAWN, NULL)); + if (result != MA_SUCCESS) { + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[PulseAudio] Failed to connect PulseAudio context.", result); + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; } + /* Since ma_context_init() runs synchronously we need to wait for the PulseAudio context to connect before we return. */ + result = ma_context_wait_for_pa_context_to_connect__pulse(pContext); + if (result != MA_SUCCESS) { + ((ma_pa_mainloop_free_proc)pContext->pulse.pa_mainloop_free)((ma_pa_mainloop*)(pContext->pulse.pMainLoop)); + #ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(pContext, pContext->pulse.pulseSO); + #endif + return result; + } + + + /* With pa_mainloop we run a synchronous backend, but we implement our own main loop. */ + pCallbacks->onContextInit = ma_context_init__pulse; + pCallbacks->onContextUninit = ma_context_uninit__pulse; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__pulse; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__pulse; + pCallbacks->onDeviceInit = ma_device_init__pulse; + pCallbacks->onDeviceUninit = ma_device_uninit__pulse; + pCallbacks->onDeviceStart = ma_device_start__pulse; + pCallbacks->onDeviceStop = ma_device_stop__pulse; + pCallbacks->onDeviceRead = NULL; /* Not used because we're implementing onDeviceDataLoop. */ + pCallbacks->onDeviceWrite = NULL; /* Not used because we're implementing onDeviceDataLoop. */ + pCallbacks->onDeviceDataLoop = ma_device_data_loop__pulse; + pCallbacks->onDeviceDataLoopWakeup = ma_device_data_loop_wakeup__pulse; + return MA_SUCCESS; } #endif @@ -21886,15 +23303,6 @@ static ma_result ma_context_open_client__jack(ma_context* pContext, ma_jack_clie return MA_SUCCESS; } -static ma_bool32 ma_context_is_device_id_equal__jack(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return pID0->jack == pID1->jack; -} static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { @@ -21908,6 +23316,7 @@ static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enu ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + deviceInfo.isDefault = MA_TRUE; /* JACK only uses default devices. */ cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -21916,13 +23325,16 @@ static ma_result ma_context_enumerate_devices__jack(ma_context* pContext, ma_enu ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + deviceInfo.isDefault = MA_TRUE; /* JACK only uses default devices. */ cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } + (void)cbResult; /* For silencing a static analysis warning. */ + return MA_SUCCESS; } -static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_jack_client_t* pClient; ma_result result; @@ -21930,11 +23342,6 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic MA_ASSERT(pContext != NULL); - /* No exclusive mode with the JACK backend. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - if (pDeviceID != NULL && pDeviceID->jack != 0) { return MA_NO_DEVICE; /* Don't know the device. */ } @@ -21946,9 +23353,11 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + /* Jack only uses default devices. */ + pDeviceInfo->isDefault = MA_TRUE; + /* Jack only supports f32 and has a specific channel count and sample rate. */ - pDeviceInfo->formatCount = 1; - pDeviceInfo->formats[0] = ma_format_f32; + pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; /* The channel count and sample rate can only be determined by opening the device. */ result = ma_context_open_client__jack(pContext, &pClient); @@ -21956,11 +23365,8 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); } - pDeviceInfo->minSampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pClient); - pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; - - pDeviceInfo->minChannels = 0; - pDeviceInfo->maxChannels = 0; + pDeviceInfo->nativeDataFormats[0].sampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pClient); + pDeviceInfo->nativeDataFormats[0].channels = 0; ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ((deviceType == ma_device_type_playback) ? ma_JackPortIsInput : ma_JackPortIsOutput)); if (ppPorts == NULL) { @@ -21968,11 +23374,13 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - while (ppPorts[pDeviceInfo->minChannels] != NULL) { - pDeviceInfo->minChannels += 1; - pDeviceInfo->maxChannels += 1; + while (ppPorts[pDeviceInfo->nativeDataFormats[0].channels] != NULL) { + pDeviceInfo->nativeDataFormats[0].channels += 1; } + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormatCount = 1; + ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pClient); @@ -21981,7 +23389,7 @@ static ma_result ma_context_get_device_info__jack(ma_context* pContext, ma_devic } -static void ma_device_uninit__jack(ma_device* pDevice) +static ma_result ma_device_uninit__jack(ma_device* pDevice) { ma_context* pContext; @@ -22002,9 +23410,7 @@ static void ma_device_uninit__jack(ma_device* pDevice) ma__free_from_callbacks(pDevice->jack.pIntermediaryBufferPlayback, &pDevice->pContext->allocationCallbacks); } - if (pDevice->type == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDevice->jack.duplexRB); - } + return MA_SUCCESS; } static void ma_device__jack_shutdown_callback(void* pUserData) @@ -22078,19 +23484,11 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* } } - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, frameCount, pDevice->jack.pIntermediaryBufferCapture, &pDevice->jack.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, frameCount, pDevice->jack.pIntermediaryBufferCapture); - } + ma_device_handle_backend_data_callback(pDevice, NULL, pDevice->jack.pIntermediaryBufferCapture, frameCount); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_playback(pDevice, frameCount, pDevice->jack.pIntermediaryBufferPlayback, &pDevice->jack.duplexRB); - } else { - ma_device__read_frames_from_client(pDevice, frameCount, pDevice->jack.pIntermediaryBufferPlayback); - } + ma_device_handle_backend_data_callback(pDevice, pDevice->jack.pIntermediaryBufferPlayback, NULL, frameCount); /* Channels need to be deinterleaved. */ for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { @@ -22111,13 +23509,11 @@ static int ma_device__jack_process_callback(ma_jack_nframes_t frameCount, void* return 0; } -static ma_result ma_device_init__jack(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__jack(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; - ma_uint32 periods; ma_uint32 periodSizeInFrames; - MA_ASSERT(pContext != NULL); MA_ASSERT(pConfig != NULL); MA_ASSERT(pDevice != NULL); @@ -22126,72 +23522,71 @@ static ma_result ma_device_init__jack(ma_context* pContext, const ma_device_conf } /* Only supporting default devices with JACK. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.pDeviceID != NULL && pConfig->playback.pDeviceID->jack != 0) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.pDeviceID != NULL && pConfig->capture.pDeviceID->jack != 0)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->pDeviceID != NULL && pDescriptorPlayback->pDeviceID->jack != 0) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->pDeviceID != NULL && pDescriptorCapture->pDeviceID->jack != 0)) { return MA_NO_DEVICE; } /* No exclusive mode with the JACK backend. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } /* Open the client. */ - result = ma_context_open_client__jack(pContext, (ma_jack_client_t**)&pDevice->jack.pClient); + result = ma_context_open_client__jack(pDevice->pContext, (ma_jack_client_t**)&pDevice->jack.pClient); if (result != MA_SUCCESS) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to open client.", result); } /* Callbacks. */ - if (((ma_jack_set_process_callback_proc)pContext->jack.jack_set_process_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_process_callback, pDevice) != 0) { + if (((ma_jack_set_process_callback_proc)pDevice->pContext->jack.jack_set_process_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_process_callback, pDevice) != 0) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set process callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - if (((ma_jack_set_buffer_size_callback_proc)pContext->jack.jack_set_buffer_size_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { + if (((ma_jack_set_buffer_size_callback_proc)pDevice->pContext->jack.jack_set_buffer_size_callback)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_buffer_size_callback, pDevice) != 0) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to set buffer size callback.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - ((ma_jack_on_shutdown_proc)pContext->jack.jack_on_shutdown)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_shutdown_callback, pDevice); + ((ma_jack_on_shutdown_proc)pDevice->pContext->jack.jack_on_shutdown)((ma_jack_client_t*)pDevice->jack.pClient, ma_device__jack_shutdown_callback, pDevice); /* The buffer size in frames can change. */ - periods = pConfig->periods; - periodSizeInFrames = ((ma_jack_get_buffer_size_proc)pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient); - + periodSizeInFrames = ((ma_jack_get_buffer_size_proc)pDevice->pContext->jack.jack_get_buffer_size)((ma_jack_client_t*)pDevice->jack.pClient); + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { const char** ppPorts; - pDevice->capture.internalFormat = ma_format_f32; - pDevice->capture.internalChannels = 0; - pDevice->capture.internalSampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); + pDescriptorCapture->format = ma_format_f32; + pDescriptorCapture->channels = 0; + pDescriptorCapture->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); + ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorCapture->channels, pDescriptorCapture->channelMap); - ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); + ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsOutput); if (ppPorts == NULL) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - while (ppPorts[pDevice->capture.internalChannels] != NULL) { + while (ppPorts[pDescriptorCapture->channels] != NULL) { char name[64]; ma_strcpy_s(name, sizeof(name), "capture"); - ma_itoa_s((int)pDevice->capture.internalChannels, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ + ma_itoa_s((int)pDescriptorCapture->channels, name+7, sizeof(name)-7, 10); /* 7 = length of "capture" */ - pDevice->jack.pPortsCapture[pDevice->capture.internalChannels] = ((ma_jack_port_register_proc)pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); - if (pDevice->jack.pPortsCapture[pDevice->capture.internalChannels] == NULL) { - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + pDevice->jack.pPortsCapture[pDescriptorCapture->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsInput, 0); + if (pDevice->jack.pPortsCapture[pDescriptorCapture->channels] == NULL) { + ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - pDevice->capture.internalChannels += 1; + pDescriptorCapture->channels += 1; } - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); - pDevice->capture.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->capture.internalPeriods = periods; + pDescriptorCapture->periodSizeInFrames = periodSizeInFrames; + pDescriptorCapture->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferCapture = (float*)ma__calloc_from_callbacks(pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels), &pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferCapture = (float*)ma__calloc_from_callbacks(pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferCapture == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; @@ -22201,63 +23596,43 @@ static ma_result ma_device_init__jack(ma_context* pContext, const ma_device_conf if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { const char** ppPorts; - pDevice->playback.internalFormat = ma_format_f32; - pDevice->playback.internalChannels = 0; - pDevice->playback.internalSampleRate = ((ma_jack_get_sample_rate_proc)pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); - ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + pDescriptorPlayback->format = ma_format_f32; + pDescriptorPlayback->channels = 0; + pDescriptorPlayback->sampleRate = ((ma_jack_get_sample_rate_proc)pDevice->pContext->jack.jack_get_sample_rate)((ma_jack_client_t*)pDevice->jack.pClient); + ma_get_standard_channel_map(ma_standard_channel_map_alsa, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); - ppPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); + ppPorts = ((ma_jack_get_ports_proc)pDevice->pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppPorts == NULL) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to query physical ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - while (ppPorts[pDevice->playback.internalChannels] != NULL) { + while (ppPorts[pDescriptorPlayback->channels] != NULL) { char name[64]; ma_strcpy_s(name, sizeof(name), "playback"); - ma_itoa_s((int)pDevice->playback.internalChannels, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ + ma_itoa_s((int)pDescriptorPlayback->channels, name+8, sizeof(name)-8, 10); /* 8 = length of "playback" */ - pDevice->jack.pPortsPlayback[pDevice->playback.internalChannels] = ((ma_jack_port_register_proc)pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); - if (pDevice->jack.pPortsPlayback[pDevice->playback.internalChannels] == NULL) { - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] = ((ma_jack_port_register_proc)pDevice->pContext->jack.jack_port_register)((ma_jack_client_t*)pDevice->jack.pClient, name, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsOutput, 0); + if (pDevice->jack.pPortsPlayback[pDescriptorPlayback->channels] == NULL) { + ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); ma_device_uninit__jack(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to register ports.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } - pDevice->playback.internalChannels += 1; + pDescriptorPlayback->channels += 1; } - ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppPorts); + ((ma_jack_free_proc)pDevice->pContext->jack.jack_free)((void*)ppPorts); - pDevice->playback.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->playback.internalPeriods = periods; + pDescriptorPlayback->periodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodCount = 1; /* There's no notion of a period in JACK. Just set to 1. */ - pDevice->jack.pIntermediaryBufferPlayback = (float*)ma__calloc_from_callbacks(pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels), &pContext->allocationCallbacks); + pDevice->jack.pIntermediaryBufferPlayback = (float*)ma__calloc_from_callbacks(pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels), &pDevice->pContext->allocationCallbacks); if (pDevice->jack.pIntermediaryBufferPlayback == NULL) { ma_device_uninit__jack(pDevice); return MA_OUT_OF_MEMORY; } } - if (pDevice->type == ma_device_type_duplex) { - ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods); - result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->pContext->allocationCallbacks, &pDevice->jack.duplexRB); - if (result != MA_SUCCESS) { - ma_device_uninit__jack(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] Failed to initialize ring buffer.", result); - } - - /* We need a period to act as a buffer for cases where the playback and capture device's end up desyncing. */ - { - ma_uint32 marginSizeInFrames = rbSizeInFrames / pDevice->capture.internalPeriods; - void* pMarginData; - ma_pcm_rb_acquire_write(&pDevice->jack.duplexRB, &marginSizeInFrames, &pMarginData); - { - MA_ZERO_MEMORY(pMarginData, marginSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); - } - ma_pcm_rb_commit_write(&pDevice->jack.duplexRB, marginSizeInFrames, pMarginData); - } - } - return MA_SUCCESS; } @@ -22294,7 +23669,7 @@ static ma_result ma_device_start__jack(ma_device* pDevice) ((ma_jack_free_proc)pContext->jack.jack_free)((void*)ppServerPorts); } - + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { const char** ppServerPorts = ((ma_jack_get_ports_proc)pContext->jack.jack_get_ports)((ma_jack_client_t*)pDevice->jack.pClient, NULL, MA_JACK_DEFAULT_AUDIO_TYPE, ma_JackPortIsPhysical | ma_JackPortIsInput); if (ppServerPorts == NULL) { @@ -22328,7 +23703,7 @@ static ma_result ma_device_stop__jack(ma_device* pDevice) if (((ma_jack_deactivate_proc)pContext->jack.jack_deactivate)((ma_jack_client_t*)pDevice->jack.pClient) != 0) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[JACK] An error occurred when deactivating the JACK client.", MA_ERROR); } - + onStop = pDevice->onStop; if (onStop) { onStop(pDevice); @@ -22353,12 +23728,13 @@ static ma_result ma_context_uninit__jack(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__jack(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__jack(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { #ifndef MA_NO_RUNTIME_LINKING const char* libjackNames[] = { #ifdef MA_WIN32 - "libjack.dll" + "libjack.dll", + "libjack64.dll" #else "libjack.so", "libjack.so.0" @@ -22433,17 +23809,6 @@ static ma_result ma_context_init__jack(const ma_context_config* pConfig, ma_cont pContext->jack.jack_free = (ma_proc)_jack_free; #endif - pContext->isBackendAsynchronous = MA_TRUE; - - pContext->onUninit = ma_context_uninit__jack; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__jack; - pContext->onEnumDevices = ma_context_enumerate_devices__jack; - pContext->onGetDeviceInfo = ma_context_get_device_info__jack; - pContext->onDeviceInit = ma_device_init__jack; - pContext->onDeviceUninit = ma_device_uninit__jack; - pContext->onDeviceStart = ma_device_start__jack; - pContext->onDeviceStop = ma_device_stop__jack; - if (pConfig->jack.pClientName != NULL) { pContext->jack.pClientName = ma_copy_string(pConfig->jack.pClientName, &pContext->allocationCallbacks); } @@ -22467,6 +23832,19 @@ static ma_result ma_context_init__jack(const ma_context_config* pConfig, ma_cont ((ma_jack_client_close_proc)pContext->jack.jack_client_close)((ma_jack_client_t*)pDummyClient); } + + pCallbacks->onContextInit = ma_context_init__jack; + pCallbacks->onContextUninit = ma_context_uninit__jack; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__jack; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__jack; + pCallbacks->onDeviceInit = ma_device_init__jack; + pCallbacks->onDeviceUninit = ma_device_uninit__jack; + pCallbacks->onDeviceStart = ma_device_start__jack; + pCallbacks->onDeviceStop = ma_device_stop__jack; + pCallbacks->onDeviceRead = NULL; /* Not used because JACK is asynchronous. */ + pCallbacks->onDeviceWrite = NULL; /* Not used because JACK is asynchronous. */ + pCallbacks->onDeviceDataLoop = NULL; /* Not used because JACK is asynchronous. */ + return MA_SUCCESS; } #endif /* JACK */ @@ -22477,6 +23855,11 @@ static ma_result ma_context_init__jack(const ma_context_config* pConfig, ma_cont Core Audio Backend +References +========== +- Technical Note TN2091: Device input using the HAL Output Audio Unit + https://developer.apple.com/library/archive/technotes/tn2091/_index.html + ******************************************************************************/ #ifdef MA_HAS_COREAUDIO #include @@ -22622,14 +24005,14 @@ static ma_result ma_format_from_AudioStreamBasicDescription(const AudioStreamBas { MA_ASSERT(pDescription != NULL); MA_ASSERT(pFormatOut != NULL); - + *pFormatOut = ma_format_unknown; /* Safety. */ - + /* There's a few things miniaudio doesn't support. */ if (pDescription->mFormatID != kAudioFormatLinearPCM) { return MA_FORMAT_NOT_SUPPORTED; } - + /* We don't support any non-packed formats that are aligned high. */ if ((pDescription->mFormatFlags & kLinearPCMFormatFlagIsAlignedHigh) != 0) { return MA_FORMAT_NOT_SUPPORTED; @@ -22639,7 +24022,7 @@ static ma_result ma_format_from_AudioStreamBasicDescription(const AudioStreamBas if ((ma_is_little_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) != 0) || (ma_is_big_endian() && (pDescription->mFormatFlags & kAudioFormatFlagIsBigEndian) == 0)) { return MA_FORMAT_NOT_SUPPORTED; } - + /* We are not currently supporting non-interleaved formats (this will be added in a future version of miniaudio). */ /*if ((pDescription->mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0) { return MA_FORMAT_NOT_SUPPORTED; @@ -22678,7 +24061,7 @@ static ma_result ma_format_from_AudioStreamBasicDescription(const AudioStreamBas } } } - + /* Getting here means the format is not supported. */ return MA_FORMAT_NOT_SUPPORTED; } @@ -22752,7 +24135,7 @@ static ma_channel ma_channel_from_AudioChannelLabel(AudioChannelLabel label) case kAudioChannelLabel_Discrete_14: return MA_CHANNEL_AUX_14; case kAudioChannelLabel_Discrete_15: return MA_CHANNEL_AUX_15; case kAudioChannelLabel_Discrete_65535: return MA_CHANNEL_NONE; - + #if 0 /* Introduced in a later version of macOS. */ case kAudioChannelLabel_HOA_ACN: return MA_CHANNEL_NONE; case kAudioChannelLabel_HOA_ACN_0: return MA_CHANNEL_AUX_0; @@ -22773,7 +24156,7 @@ static ma_channel ma_channel_from_AudioChannelLabel(AudioChannelLabel label) case kAudioChannelLabel_HOA_ACN_15: return MA_CHANNEL_AUX_15; case kAudioChannelLabel_HOA_ACN_65024: return MA_CHANNEL_NONE; #endif - + default: return MA_CHANNEL_NONE; } } @@ -22781,7 +24164,7 @@ static ma_channel ma_channel_from_AudioChannelLabel(AudioChannelLabel label) static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelLayout, ma_channel* pChannelMap, size_t channelMapCap) { MA_ASSERT(pChannelLayout != NULL); - + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { UInt32 iChannel; for (iChannel = 0; iChannel < pChannelLayout->mNumberChannelDescriptions && iChannel < channelMapCap; ++iChannel) { @@ -22807,7 +24190,14 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* Need to use the tag to determine the channel map. For now I'm just assuming a default channel map, but later on this should be updated to determine the mapping based on the tag. */ - UInt32 channelCount = ma_min(AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag), channelMapCap); + UInt32 channelCount; + + /* Our channel map retrieval APIs below take 32-bit integers, so we'll want to clamp the channel map capacity. */ + if (channelMapCap > 0xFFFFFFFF) { + channelMapCap = 0xFFFFFFFF; + } + + channelCount = ma_min(AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag), (UInt32)channelMapCap); switch (pChannelLayout->mChannelLayoutTag) { @@ -22822,7 +24212,7 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* { ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); } break; - + case kAudioChannelLayoutTag_Octagonal: { pChannelMap[7] = MA_CHANNEL_SIDE_RIGHT; @@ -22843,16 +24233,16 @@ static ma_result ma_get_channel_map_from_AudioChannelLayout(AudioChannelLayout* pChannelMap[1] = MA_CHANNEL_RIGHT; pChannelMap[0] = MA_CHANNEL_LEFT; } break; - + /* TODO: Add support for more tags here. */ - + default: { ma_get_standard_channel_map(ma_standard_channel_map_default, channelCount, pChannelMap); } break; } } - + return MA_SUCCESS; } @@ -22870,7 +24260,7 @@ static ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt3 /* Safety. */ *pDeviceCount = 0; *ppDeviceObjectIDs = NULL; - + propAddressDevices.mSelector = kAudioHardwarePropertyDevices; propAddressDevices.mScope = kAudioObjectPropertyScopeGlobal; propAddressDevices.mElement = kAudioObjectPropertyElementMaster; @@ -22879,18 +24269,18 @@ static ma_result ma_get_device_object_ids__coreaudio(ma_context* pContext, UInt3 if (status != noErr) { return ma_result_from_OSStatus(status); } - + pDeviceObjectIDs = (AudioObjectID*)ma_malloc(deviceObjectsDataSize, &pContext->allocationCallbacks); if (pDeviceObjectIDs == NULL) { return MA_OUT_OF_MEMORY; } - + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDevices, 0, NULL, &deviceObjectsDataSize, pDeviceObjectIDs); if (status != noErr) { ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } - + *pDeviceCount = deviceObjectsDataSize / sizeof(AudioObjectID); *ppDeviceObjectIDs = pDeviceObjectIDs; @@ -22914,7 +24304,7 @@ static ma_result ma_get_AudioObject_uid_as_CFStringRef(ma_context* pContext, Aud if (status != noErr) { return ma_result_from_OSStatus(status); } - + return MA_SUCCESS; } @@ -22929,11 +24319,11 @@ static ma_result ma_get_AudioObject_uid(ma_context* pContext, AudioObjectID obje if (result != MA_SUCCESS) { return result; } - + if (!((ma_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(uid, bufferOut, bufferSize, kCFStringEncodingUTF8)) { return MA_ERROR; } - + ((ma_CFRelease_proc)pContext->coreaudio.CFRelease)(uid); return MA_SUCCESS; } @@ -22956,11 +24346,11 @@ static ma_result ma_get_AudioObject_name(ma_context* pContext, AudioObjectID obj if (status != noErr) { return ma_result_from_OSStatus(status); } - + if (!((ma_CFStringGetCString_proc)pContext->coreaudio.CFStringGetCString)(deviceName, bufferOut, bufferSize, kCFStringEncodingUTF8)) { return MA_ERROR; } - + ((ma_CFRelease_proc)pContext->coreaudio.CFRelease)(deviceName); return MA_SUCCESS; } @@ -22979,17 +24369,17 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb propAddress.mSelector = kAudioDevicePropertyStreamConfiguration; propAddress.mScope = scope; propAddress.mElement = kAudioObjectPropertyElementMaster; - + status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); if (status != noErr) { return MA_FALSE; } - + pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(dataSize, &pContext->allocationCallbacks); if (pBufferList == NULL) { return MA_FALSE; /* Out of memory. */ } - + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pBufferList); if (status != noErr) { ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); @@ -23000,7 +24390,7 @@ static ma_bool32 ma_does_AudioObject_support_scope(ma_context* pContext, AudioOb if (pBufferList->mNumberBuffers > 0) { isSupported = MA_TRUE; } - + ma__free_from_callbacks(pBufferList, &pContext->allocationCallbacks); return isSupported; } @@ -23026,7 +24416,7 @@ static ma_result ma_get_AudioObject_stream_descriptions(ma_context* pContext, Au MA_ASSERT(pContext != NULL); MA_ASSERT(pDescriptionCount != NULL); MA_ASSERT(ppDescriptions != NULL); - + /* TODO: Experiment with kAudioStreamPropertyAvailablePhysicalFormats instead of (or in addition to) kAudioStreamPropertyAvailableVirtualFormats. My MacBook Pro uses s24/32 format, however, which miniaudio does not currently support. @@ -23034,23 +24424,23 @@ static ma_result ma_get_AudioObject_stream_descriptions(ma_context* pContext, Au propAddress.mSelector = kAudioStreamPropertyAvailableVirtualFormats; /*kAudioStreamPropertyAvailablePhysicalFormats;*/ propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; propAddress.mElement = kAudioObjectPropertyElementMaster; - + status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); if (status != noErr) { return ma_result_from_OSStatus(status); } - + pDescriptions = (AudioStreamRangedDescription*)ma_malloc(dataSize, &pContext->allocationCallbacks); if (pDescriptions == NULL) { return MA_OUT_OF_MEMORY; } - + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pDescriptions); if (status != noErr) { ma_free(pDescriptions, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } - + *pDescriptionCount = dataSize / sizeof(*pDescriptions); *ppDescriptions = pDescriptions; return MA_SUCCESS; @@ -23066,29 +24456,29 @@ static ma_result ma_get_AudioObject_channel_layout(ma_context* pContext, AudioOb MA_ASSERT(pContext != NULL); MA_ASSERT(ppChannelLayout != NULL); - + *ppChannelLayout = NULL; /* Safety. */ - + propAddress.mSelector = kAudioDevicePropertyPreferredChannelLayout; propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; propAddress.mElement = kAudioObjectPropertyElementMaster; - + status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); if (status != noErr) { return ma_result_from_OSStatus(status); } - + pChannelLayout = (AudioChannelLayout*)ma_malloc(dataSize, &pContext->allocationCallbacks); if (pChannelLayout == NULL) { return MA_OUT_OF_MEMORY; } - + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pChannelLayout); if (status != noErr) { ma_free(pChannelLayout, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } - + *ppChannelLayout = pChannelLayout; return MA_SUCCESS; } @@ -23100,14 +24490,14 @@ static ma_result ma_get_AudioObject_channel_count(ma_context* pContext, AudioObj MA_ASSERT(pContext != NULL); MA_ASSERT(pChannelCount != NULL); - + *pChannelCount = 0; /* Safety. */ result = ma_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); if (result != MA_SUCCESS) { return result; } - + if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelDescriptions) { *pChannelCount = pChannelLayout->mNumberChannelDescriptions; } else if (pChannelLayout->mChannelLayoutTag == kAudioChannelLayoutTag_UseChannelBitmap) { @@ -23115,7 +24505,7 @@ static ma_result ma_get_AudioObject_channel_count(ma_context* pContext, AudioObj } else { *pChannelCount = AudioChannelLayoutTag_GetNumberOfChannels(pChannelLayout->mChannelLayoutTag); } - + ma_free(pChannelLayout, &pContext->allocationCallbacks); return MA_SUCCESS; } @@ -23127,18 +24517,18 @@ static ma_result ma_get_AudioObject_channel_map(ma_context* pContext, AudioObjec ma_result result; MA_ASSERT(pContext != NULL); - + result = ma_get_AudioObject_channel_layout(pContext, deviceObjectID, deviceType, &pChannelLayout); if (result != MA_SUCCESS) { return result; /* Rather than always failing here, would it be more robust to simply assume a default? */ } - + result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { ma_free(pChannelLayout, &pContext->allocationCallbacks); return result; } - + ma_free(pChannelLayout, &pContext->allocationCallbacks); return result; } @@ -23154,31 +24544,31 @@ static ma_result ma_get_AudioObject_sample_rates(ma_context* pContext, AudioObje MA_ASSERT(pContext != NULL); MA_ASSERT(pSampleRateRangesCount != NULL); MA_ASSERT(ppSampleRateRanges != NULL); - + /* Safety. */ *pSampleRateRangesCount = 0; *ppSampleRateRanges = NULL; - + propAddress.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; propAddress.mElement = kAudioObjectPropertyElementMaster; - + status = ((ma_AudioObjectGetPropertyDataSize_proc)pContext->coreaudio.AudioObjectGetPropertyDataSize)(deviceObjectID, &propAddress, 0, NULL, &dataSize); if (status != noErr) { return ma_result_from_OSStatus(status); } - + pSampleRateRanges = (AudioValueRange*)ma_malloc(dataSize, &pContext->allocationCallbacks); if (pSampleRateRanges == NULL) { return MA_OUT_OF_MEMORY; } - + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, pSampleRateRanges); if (status != noErr) { ma_free(pSampleRateRanges, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } - + *pSampleRateRangesCount = dataSize / sizeof(*pSampleRateRanges); *ppSampleRateRanges = pSampleRateRanges; return MA_SUCCESS; @@ -23193,19 +24583,19 @@ static ma_result ma_get_AudioObject_get_closest_sample_rate(ma_context* pContext MA_ASSERT(pContext != NULL); MA_ASSERT(pSampleRateOut != NULL); - + *pSampleRateOut = 0; /* Safety. */ - + result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); if (result != MA_SUCCESS) { return result; } - + if (sampleRateRangeCount == 0) { ma_free(pSampleRateRanges, &pContext->allocationCallbacks); return MA_ERROR; /* Should never hit this case should we? */ } - + if (sampleRateIn == 0) { /* Search in order of miniaudio's preferred priority. */ UInt32 iMALSampleRate; @@ -23221,13 +24611,13 @@ static ma_result ma_get_AudioObject_get_closest_sample_rate(ma_context* pContext } } } - + /* If we get here it means none of miniaudio's standard sample rates matched any of the supported sample rates from the device. In this case we just fall back to the first one reported by Core Audio. */ MA_ASSERT(sampleRateRangeCount > 0); - + *pSampleRateOut = pSampleRateRanges[0].mMinimum; ma_free(pSampleRateRanges, &pContext->allocationCallbacks); return MA_SUCCESS; @@ -23248,21 +24638,21 @@ static ma_result ma_get_AudioObject_get_closest_sample_rate(ma_context* pContext } else { absoluteDifference = sampleRateIn - pSampleRateRanges[iRange].mMaximum; } - + if (currentAbsoluteDifference > absoluteDifference) { currentAbsoluteDifference = absoluteDifference; iCurrentClosestRange = iRange; } } } - + MA_ASSERT(iCurrentClosestRange != (UInt32)-1); - + *pSampleRateOut = pSampleRateRanges[iCurrentClosestRange].mMinimum; ma_free(pSampleRateRanges, &pContext->allocationCallbacks); return MA_SUCCESS; } - + /* Should never get here, but it would mean we weren't able to find any suitable sample rates. */ /*ma_free(pSampleRateRanges, &pContext->allocationCallbacks);*/ /*return MA_ERROR;*/ @@ -23278,9 +24668,9 @@ static ma_result ma_get_AudioObject_closest_buffer_size_in_frames(ma_context* pC MA_ASSERT(pContext != NULL); MA_ASSERT(pBufferSizeInFramesOut != NULL); - + *pBufferSizeInFramesOut = 0; /* Safety. */ - + propAddress.mSelector = kAudioDevicePropertyBufferFrameSizeRange; propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; propAddress.mElement = kAudioObjectPropertyElementMaster; @@ -23290,7 +24680,7 @@ static ma_result ma_get_AudioObject_closest_buffer_size_in_frames(ma_context* pC if (status != noErr) { return ma_result_from_OSStatus(status); } - + /* This is just a clamp. */ if (bufferSizeInFramesIn < bufferSizeRange.mMinimum) { *pBufferSizeInFramesOut = (ma_uint32)bufferSizeRange.mMinimum; @@ -23322,20 +24712,51 @@ static ma_result ma_set_AudioObject_buffer_size_in_frames(ma_context* pContext, propAddress.mSelector = kAudioDevicePropertyBufferFrameSize; propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; propAddress.mElement = kAudioObjectPropertyElementMaster; - + ((ma_AudioObjectSetPropertyData_proc)pContext->coreaudio.AudioObjectSetPropertyData)(deviceObjectID, &propAddress, 0, NULL, sizeof(chosenBufferSizeInFrames), &chosenBufferSizeInFrames); - + /* Get the actual size of the buffer. */ dataSize = sizeof(*pPeriodSizeInOut); status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(deviceObjectID, &propAddress, 0, NULL, &dataSize, &chosenBufferSizeInFrames); if (status != noErr) { return ma_result_from_OSStatus(status); } - + *pPeriodSizeInOut = chosenBufferSizeInFrames; return MA_SUCCESS; } +static ma_result ma_find_default_AudioObjectID(ma_context* pContext, ma_device_type deviceType, AudioObjectID* pDeviceObjectID) +{ + AudioObjectPropertyAddress propAddressDefaultDevice; + UInt32 defaultDeviceObjectIDSize = sizeof(AudioObjectID); + AudioObjectID defaultDeviceObjectID; + OSStatus status; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDeviceObjectID != NULL); + + /* Safety. */ + *pDeviceObjectID = 0; + + propAddressDefaultDevice.mScope = kAudioObjectPropertyScopeGlobal; + propAddressDefaultDevice.mElement = kAudioObjectPropertyElementMaster; + if (deviceType == ma_device_type_playback) { + propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + } else { + propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultInputDevice; + } + + defaultDeviceObjectIDSize = sizeof(AudioObjectID); + status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDefaultDevice, 0, NULL, &defaultDeviceObjectIDSize, &defaultDeviceObjectID); + if (status == noErr) { + *pDeviceObjectID = defaultDeviceObjectID; + return MA_SUCCESS; + } + + /* If we get here it means we couldn't find the device. */ + return MA_NO_DEVICE; +} static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, AudioObjectID* pDeviceObjectID) { @@ -23344,28 +24765,10 @@ static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type devi /* Safety. */ *pDeviceObjectID = 0; - + if (pDeviceID == NULL) { /* Default device. */ - AudioObjectPropertyAddress propAddressDefaultDevice; - UInt32 defaultDeviceObjectIDSize = sizeof(AudioObjectID); - AudioObjectID defaultDeviceObjectID; - OSStatus status; - - propAddressDefaultDevice.mScope = kAudioObjectPropertyScopeGlobal; - propAddressDefaultDevice.mElement = kAudioObjectPropertyElementMaster; - if (deviceType == ma_device_type_playback) { - propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - } else { - propAddressDefaultDevice.mSelector = kAudioHardwarePropertyDefaultInputDevice; - } - - defaultDeviceObjectIDSize = sizeof(AudioObjectID); - status = ((ma_AudioObjectGetPropertyData_proc)pContext->coreaudio.AudioObjectGetPropertyData)(kAudioObjectSystemObject, &propAddressDefaultDevice, 0, NULL, &defaultDeviceObjectIDSize, &defaultDeviceObjectID); - if (status == noErr) { - *pDeviceObjectID = defaultDeviceObjectID; - return MA_SUCCESS; - } + return ma_find_default_AudioObjectID(pContext, deviceType, pDeviceObjectID); } else { /* Explicit device. */ UInt32 deviceCount; @@ -23377,15 +24780,15 @@ static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type devi if (result != MA_SUCCESS) { return result; } - + for (iDevice = 0; iDevice < deviceCount; ++iDevice) { AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; - + char uid[256]; if (ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(uid), uid) != MA_SUCCESS) { continue; } - + if (deviceType == ma_device_type_playback) { if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { if (strcmp(uid, pDeviceID->coreaudio) == 0) { @@ -23407,13 +24810,13 @@ static ma_result ma_find_AudioObjectID(ma_context* pContext, ma_device_type devi ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); } - + /* If we get here it means we couldn't find the device. */ return MA_NO_DEVICE; } -static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_bool32 usingDefaultFormat, ma_bool32 usingDefaultChannels, ma_bool32 usingDefaultSampleRate, AudioStreamBasicDescription* pFormat) +static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjectID deviceObjectID, ma_device_type deviceType, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const AudioStreamBasicDescription* pOrigFormat, AudioStreamBasicDescription* pFormat) { UInt32 deviceFormatDescriptionCount; AudioStreamRangedDescription* pDeviceFormatDescriptions; @@ -23429,51 +24832,31 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec if (result != MA_SUCCESS) { return result; } - - desiredSampleRate = sampleRate; - if (usingDefaultSampleRate) { - /* - When using the device's default sample rate, we get the highest priority standard rate supported by the device. Otherwise - we just use the pre-set rate. - */ - ma_uint32 iStandardRate; - for (iStandardRate = 0; iStandardRate < ma_countof(g_maStandardSampleRatePriorities); ++iStandardRate) { - ma_uint32 standardRate = g_maStandardSampleRatePriorities[iStandardRate]; - ma_bool32 foundRate = MA_FALSE; - UInt32 iDeviceRate; - for (iDeviceRate = 0; iDeviceRate < deviceFormatDescriptionCount; ++iDeviceRate) { - ma_uint32 deviceRate = (ma_uint32)pDeviceFormatDescriptions[iDeviceRate].mFormat.mSampleRate; - - if (deviceRate == standardRate) { - desiredSampleRate = standardRate; - foundRate = MA_TRUE; - break; - } - } - - if (foundRate) { - break; - } + desiredSampleRate = sampleRate; + if (desiredSampleRate == 0) { + desiredSampleRate = pOrigFormat->mSampleRate; + } + + desiredChannelCount = channels; + if (desiredChannelCount == 0) { + desiredChannelCount = pOrigFormat->mChannelsPerFrame; + } + + desiredFormat = format; + if (desiredFormat == ma_format_unknown) { + result = ma_format_from_AudioStreamBasicDescription(pOrigFormat, &desiredFormat); + if (result != MA_SUCCESS || desiredFormat == ma_format_unknown) { + desiredFormat = g_maFormatPriorities[0]; } } - - desiredChannelCount = channels; - if (usingDefaultChannels) { - ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &desiredChannelCount); /* <-- Not critical if this fails. */ - } - - desiredFormat = format; - if (usingDefaultFormat) { - desiredFormat = g_maFormatPriorities[0]; - } - + /* If we get here it means we don't have an exact match to what the client is asking for. We'll need to find the closest one. The next loop will check for formats that have the same sample rate to what we're asking for. If there is, we prefer that one in all cases. */ MA_ZERO_OBJECT(&bestDeviceFormatSoFar); - + hasSupportedFormat = MA_FALSE; for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { ma_format format; @@ -23484,13 +24867,13 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec break; } } - + if (!hasSupportedFormat) { ma_free(pDeviceFormatDescriptions, &pContext->allocationCallbacks); return MA_FORMAT_NOT_SUPPORTED; } - - + + for (iFormat = 0; iFormat < deviceFormatDescriptionCount; ++iFormat) { AudioStreamBasicDescription thisDeviceFormat = pDeviceFormatDescriptions[iFormat].mFormat; ma_format thisSampleFormat; @@ -23502,9 +24885,9 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec if (formatResult != MA_SUCCESS || thisSampleFormat == ma_format_unknown) { continue; /* The format is not supported by miniaudio. Skip. */ } - + ma_format_from_AudioStreamBasicDescription(&bestDeviceFormatSoFar, &bestSampleFormatSoFar); - + /* Getting here means the format is supported by miniaudio which makes this format a candidate. */ if (thisDeviceFormat.mSampleRate != desiredSampleRate) { /* @@ -23606,7 +24989,7 @@ static ma_result ma_find_best_format__coreaudio(ma_context* pContext, AudioObjec } } } - + *pFormat = bestDeviceFormatSoFar; ma_free(pDeviceFormatDescriptions, &pContext->allocationCallbacks); @@ -23623,31 +25006,31 @@ static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit au ma_result result; MA_ASSERT(pContext != NULL); - + if (deviceType == ma_device_type_playback) { - deviceScope = kAudioUnitScope_Output; + deviceScope = kAudioUnitScope_Input; deviceBus = MA_COREAUDIO_OUTPUT_BUS; } else { - deviceScope = kAudioUnitScope_Input; + deviceScope = kAudioUnitScope_Output; deviceBus = MA_COREAUDIO_INPUT_BUS; } - + status = ((ma_AudioUnitGetPropertyInfo_proc)pContext->coreaudio.AudioUnitGetPropertyInfo)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, &channelLayoutSize, NULL); if (status != noErr) { return ma_result_from_OSStatus(status); } - + pChannelLayout = (AudioChannelLayout*)ma__malloc_from_callbacks(channelLayoutSize, &pContext->allocationCallbacks); if (pChannelLayout == NULL) { return MA_OUT_OF_MEMORY; } - + status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_AudioChannelLayout, deviceScope, deviceBus, pChannelLayout, &channelLayoutSize); if (status != noErr) { ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); return ma_result_from_OSStatus(status); } - + result = ma_get_channel_map_from_AudioChannelLayout(pChannelLayout, pChannelMap, channelMapCap); if (result != MA_SUCCESS) { ma__free_from_callbacks(pChannelLayout, &pContext->allocationCallbacks); @@ -23659,15 +25042,6 @@ static ma_result ma_get_AudioUnit_channel_map(ma_context* pContext, AudioUnit au } #endif /* MA_APPLE_DESKTOP */ -static ma_bool32 ma_context_is_device_id_equal__coreaudio(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return strcmp(pID0->coreaudio, pID1->coreaudio) == 0; -} #if !defined(MA_APPLE_DESKTOP) static void ma_AVAudioSessionPortDescription_to_device_info(AVAudioSessionPortDescription* pPortDesc, ma_device_info* pInfo) @@ -23683,14 +25057,19 @@ static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, m #if defined(MA_APPLE_DESKTOP) UInt32 deviceCount; AudioObjectID* pDeviceObjectIDs; + AudioObjectID defaultDeviceObjectIDPlayback; + AudioObjectID defaultDeviceObjectIDCapture; ma_result result; UInt32 iDevice; + ma_find_default_AudioObjectID(pContext, ma_device_type_playback, &defaultDeviceObjectIDPlayback); /* OK if this fails. */ + ma_find_default_AudioObjectID(pContext, ma_device_type_capture, &defaultDeviceObjectIDCapture); /* OK if this fails. */ + result = ma_get_device_object_ids__coreaudio(pContext, &deviceCount, &pDeviceObjectIDs); if (result != MA_SUCCESS) { return result; } - + for (iDevice = 0; iDevice < deviceCount; ++iDevice) { AudioObjectID deviceObjectID = pDeviceObjectIDs[iDevice]; ma_device_info info; @@ -23704,30 +25083,38 @@ static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, m } if (ma_does_AudioObject_support_playback(pContext, deviceObjectID)) { + if (deviceObjectID == defaultDeviceObjectIDPlayback) { + info.isDefault = MA_TRUE; + } + if (!callback(pContext, ma_device_type_playback, &info, pUserData)) { break; } } if (ma_does_AudioObject_support_capture(pContext, deviceObjectID)) { + if (deviceObjectID == defaultDeviceObjectIDCapture) { + info.isDefault = MA_TRUE; + } + if (!callback(pContext, ma_device_type_capture, &info, pUserData)) { break; } } } - + ma_free(pDeviceObjectIDs, &pContext->allocationCallbacks); #else ma_device_info info; NSArray *pInputs = [[[AVAudioSession sharedInstance] currentRoute] inputs]; NSArray *pOutputs = [[[AVAudioSession sharedInstance] currentRoute] outputs]; - + for (AVAudioSessionPortDescription* pPortDesc in pOutputs) { ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info); if (!callback(pContext, ma_device_type_playback, &info, pUserData)) { return MA_SUCCESS; } } - + for (AVAudioSessionPortDescription* pPortDesc in pInputs) { ma_AVAudioSessionPortDescription_to_device_info(pPortDesc, &info); if (!callback(pContext, ma_device_type_capture, &info, pUserData)) { @@ -23735,106 +25122,142 @@ static ma_result ma_context_enumerate_devices__coreaudio(ma_context* pContext, m } } #endif - + return MA_SUCCESS; } -static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_result result; MA_ASSERT(pContext != NULL); - /* No exclusive mode with the Core Audio backend for now. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - #if defined(MA_APPLE_DESKTOP) /* Desktop */ { AudioObjectID deviceObjectID; + AudioObjectID defaultDeviceObjectID; UInt32 streamDescriptionCount; AudioStreamRangedDescription* pStreamDescriptions; UInt32 iStreamDescription; UInt32 sampleRateRangeCount; AudioValueRange* pSampleRateRanges; + ma_find_default_AudioObjectID(pContext, deviceType, &defaultDeviceObjectID); /* OK if this fails. */ + result = ma_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); if (result != MA_SUCCESS) { return result; } - + result = ma_get_AudioObject_uid(pContext, deviceObjectID, sizeof(pDeviceInfo->id.coreaudio), pDeviceInfo->id.coreaudio); if (result != MA_SUCCESS) { return result; } - + result = ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(pDeviceInfo->name), pDeviceInfo->name); if (result != MA_SUCCESS) { return result; } - - /* Formats. */ - result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions); - if (result != MA_SUCCESS) { - return result; - } - - for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { - ma_format format; - ma_bool32 formatExists = MA_FALSE; - ma_uint32 iOutputFormat; - result = ma_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); + if (deviceObjectID == defaultDeviceObjectID) { + pDeviceInfo->isDefault = MA_TRUE; + } + + /* + There could be a large number of permutations here. Fortunately there is only a single channel count + being reported which reduces this quite a bit. For sample rates we're only reporting those that are + one of miniaudio's recognized "standard" rates. If there are still more formats than can fit into + our fixed sized array we'll just need to truncate them. This is unlikely and will probably only happen + if some driver performs software data conversion and therefore reports every possible format and + sample rate. + */ + pDeviceInfo->nativeDataFormatCount = 0; + + /* Formats. */ + { + ma_format uniqueFormats[ma_format_count]; + ma_uint32 uniqueFormatCount = 0; + ma_uint32 channels; + + /* Channels. */ + result = ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &channels); if (result != MA_SUCCESS) { - continue; + return result; } - - MA_ASSERT(format != ma_format_unknown); - - /* Make sure the format isn't already in the output list. */ - for (iOutputFormat = 0; iOutputFormat < pDeviceInfo->formatCount; ++iOutputFormat) { - if (pDeviceInfo->formats[iOutputFormat] == format) { - formatExists = MA_TRUE; - break; - } - } - - if (!formatExists) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = format; - } - } - - ma_free(pStreamDescriptions, &pContext->allocationCallbacks); - - - /* Channels. */ - result = ma_get_AudioObject_channel_count(pContext, deviceObjectID, deviceType, &pDeviceInfo->minChannels); - if (result != MA_SUCCESS) { - return result; - } - pDeviceInfo->maxChannels = pDeviceInfo->minChannels; - - - /* Sample rates. */ - result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); - if (result != MA_SUCCESS) { - return result; - } - - if (sampleRateRangeCount > 0) { - UInt32 iSampleRate; - pDeviceInfo->minSampleRate = UINT32_MAX; - pDeviceInfo->maxSampleRate = 0; - for (iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) { - if (pDeviceInfo->minSampleRate > pSampleRateRanges[iSampleRate].mMinimum) { - pDeviceInfo->minSampleRate = pSampleRateRanges[iSampleRate].mMinimum; - } - if (pDeviceInfo->maxSampleRate < pSampleRateRanges[iSampleRate].mMaximum) { - pDeviceInfo->maxSampleRate = pSampleRateRanges[iSampleRate].mMaximum; + + /* Formats. */ + result = ma_get_AudioObject_stream_descriptions(pContext, deviceObjectID, deviceType, &streamDescriptionCount, &pStreamDescriptions); + if (result != MA_SUCCESS) { + return result; + } + + for (iStreamDescription = 0; iStreamDescription < streamDescriptionCount; ++iStreamDescription) { + ma_format format; + ma_bool32 hasFormatBeenHandled = MA_FALSE; + ma_uint32 iOutputFormat; + ma_uint32 iSampleRate; + + result = ma_format_from_AudioStreamBasicDescription(&pStreamDescriptions[iStreamDescription].mFormat, &format); + if (result != MA_SUCCESS) { + continue; + } + + MA_ASSERT(format != ma_format_unknown); + + /* Make sure the format isn't already in the output list. */ + for (iOutputFormat = 0; iOutputFormat < uniqueFormatCount; ++iOutputFormat) { + if (uniqueFormats[iOutputFormat] == format) { + hasFormatBeenHandled = MA_TRUE; + break; + } + } + + /* If we've already handled this format just skip it. */ + if (hasFormatBeenHandled) { + continue; + } + + uniqueFormatCount += 1; + + + /* Sample Rates */ + result = ma_get_AudioObject_sample_rates(pContext, deviceObjectID, deviceType, &sampleRateRangeCount, &pSampleRateRanges); + if (result != MA_SUCCESS) { + return result; + } + + /* + Annoyingly Core Audio reports a sample rate range. We just get all the standard rates that are + between this range. + */ + for (iSampleRate = 0; iSampleRate < sampleRateRangeCount; ++iSampleRate) { + ma_uint32 iStandardSampleRate; + for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) { + ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate]; + if (standardSampleRate >= pSampleRateRanges[iSampleRate].mMinimum && standardSampleRate <= pSampleRateRanges[iSampleRate].mMaximum) { + /* We have a new data format. Add it to the list. */ + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = standardSampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; + pDeviceInfo->nativeDataFormatCount += 1; + + if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) { + break; /* No more room for any more formats. */ + } + } + } + } + + ma_free(pSampleRateRanges, &pContext->allocationCallbacks); + + if (pDeviceInfo->nativeDataFormatCount >= ma_countof(pDeviceInfo->nativeDataFormats)) { + break; /* No more room for any more formats. */ } } + + ma_free(pStreamDescriptions, &pContext->allocationCallbacks); } } #else @@ -23848,7 +25271,7 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ AudioUnitElement formatElement; AudioStreamBasicDescription bestFormat; UInt32 propSize; - + /* We want to ensure we use a consistent device name to device enumeration. */ if (pDeviceID != NULL) { ma_bool32 found = MA_FALSE; @@ -23871,7 +25294,7 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ } } } - + if (!found) { return MA_DOES_NOT_EXIST; } @@ -23882,8 +25305,8 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } } - - + + /* Retrieving device information is more annoying on mobile than desktop. For simplicity I'm locking this down to whatever format is reported on a temporary I/O unit. The problem, however, is that this doesn't return a value for the sample rate which we need to @@ -23894,40 +25317,40 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; - + component = ((ma_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); if (component == NULL) { return MA_FAILED_TO_INIT_BACKEND; } - + status = ((ma_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)(component, &audioUnit); if (status != noErr) { return ma_result_from_OSStatus(status); } - + formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; - + propSize = sizeof(bestFormat); status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); return ma_result_from_OSStatus(status); } - + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(audioUnit); audioUnit = NULL; - - - pDeviceInfo->minChannels = bestFormat.mChannelsPerFrame; - pDeviceInfo->maxChannels = bestFormat.mChannelsPerFrame; - - pDeviceInfo->formatCount = 1; - result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->formats[0]); + + /* Only a single format is being reported for iOS. */ + pDeviceInfo->nativeDataFormatCount = 1; + + result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pDeviceInfo->nativeDataFormats[0].format); if (result != MA_SUCCESS) { return result; } - + + pDeviceInfo->nativeDataFormats[0].channels = bestFormat.mChannelsPerFrame; + /* It looks like Apple are wanting to push the whole AVAudioSession thing. Thus, we need to use that to determine device settings. To do this we just get the shared instance and inspect. @@ -23936,16 +25359,86 @@ static ma_result ma_context_get_device_info__coreaudio(ma_context* pContext, ma_ AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; MA_ASSERT(pAudioSession != NULL); - pDeviceInfo->minSampleRate = (ma_uint32)pAudioSession.sampleRate; - pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; + pDeviceInfo->nativeDataFormats[0].sampleRate = (ma_uint32)pAudioSession.sampleRate; } } #endif - + (void)pDeviceInfo; /* Unused. */ return MA_SUCCESS; } +static AudioBufferList* ma_allocate_AudioBufferList__coreaudio(ma_uint32 sizeInFrames, ma_format format, ma_uint32 channels, ma_stream_layout layout, const ma_allocation_callbacks* pAllocationCallbacks) +{ + AudioBufferList* pBufferList; + UInt32 audioBufferSizeInBytes; + size_t allocationSize; + + MA_ASSERT(sizeInFrames > 0); + MA_ASSERT(format != ma_format_unknown); + MA_ASSERT(channels > 0); + + allocationSize = sizeof(AudioBufferList) - sizeof(AudioBuffer); /* Subtract sizeof(AudioBuffer) because that part is dynamically sized. */ + if (layout == ma_stream_layout_interleaved) { + /* Interleaved case. This is the simple case because we just have one buffer. */ + allocationSize += sizeof(AudioBuffer) * 1; + } else { + /* Non-interleaved case. This is the more complex case because there's more than one buffer. */ + allocationSize += sizeof(AudioBuffer) * channels; + } + + allocationSize += sizeInFrames * ma_get_bytes_per_frame(format, channels); + + pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(allocationSize, pAllocationCallbacks); + if (pBufferList == NULL) { + return NULL; + } + + audioBufferSizeInBytes = (UInt32)(sizeInFrames * ma_get_bytes_per_sample(format)); + + if (layout == ma_stream_layout_interleaved) { + pBufferList->mNumberBuffers = 1; + pBufferList->mBuffers[0].mNumberChannels = channels; + pBufferList->mBuffers[0].mDataByteSize = audioBufferSizeInBytes * channels; + pBufferList->mBuffers[0].mData = (ma_uint8*)pBufferList + sizeof(AudioBufferList); + } else { + ma_uint32 iBuffer; + pBufferList->mNumberBuffers = channels; + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { + pBufferList->mBuffers[iBuffer].mNumberChannels = 1; + pBufferList->mBuffers[iBuffer].mDataByteSize = audioBufferSizeInBytes; + pBufferList->mBuffers[iBuffer].mData = (ma_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * channels)) + (audioBufferSizeInBytes * iBuffer); + } + } + + return pBufferList; +} + +static ma_result ma_device_realloc_AudioBufferList__coreaudio(ma_device* pDevice, ma_uint32 sizeInFrames, ma_format format, ma_uint32 channels, ma_stream_layout layout) +{ + MA_ASSERT(pDevice != NULL); + MA_ASSERT(format != ma_format_unknown); + MA_ASSERT(channels > 0); + + /* Only resize the buffer if necessary. */ + if (pDevice->coreaudio.audioBufferCapInFrames < sizeInFrames) { + AudioBufferList* pNewAudioBufferList; + + pNewAudioBufferList = ma_allocate_AudioBufferList__coreaudio(sizeInFrames, format, channels, layout, &pDevice->pContext->allocationCallbacks); + if (pNewAudioBufferList != NULL) { + return MA_OUT_OF_MEMORY; + } + + /* At this point we'll have a new AudioBufferList and we can free the old one. */ + ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); + pDevice->coreaudio.pAudioBufferList = pNewAudioBufferList; + pDevice->coreaudio.audioBufferCapInFrames = sizeInFrames; + } + + /* Getting here means the capacity of the audio is fine. */ + return MA_SUCCESS; +} + static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFlags* pActionFlags, const AudioTimeStamp* pTimeStamp, UInt32 busNumber, UInt32 frameCount, AudioBufferList* pBufferList) { @@ -23963,7 +25456,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl if (pBufferList->mBuffers[0].mNumberChannels != pDevice->playback.internalChannels) { layout = ma_stream_layout_deinterleaved; } - + if (layout == ma_stream_layout_interleaved) { /* For now we can assume everything is interleaved. */ UInt32 iBuffer; @@ -23971,13 +25464,9 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl if (pBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->playback.internalChannels) { ma_uint32 frameCountForThisBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); if (frameCountForThisBuffer > 0) { - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_playback(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData, &pDevice->coreaudio.duplexRB); - } else { - ma_device__read_frames_from_client(pDevice, frameCountForThisBuffer, pBufferList->mBuffers[iBuffer].mData); - } + ma_device_handle_backend_data_callback(pDevice, pBufferList->mBuffers[iBuffer].mData, NULL, frameCountForThisBuffer); } - + #if defined(MA_DEBUG_OUTPUT) printf(" frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pBufferList->mBuffers[iBuffer].mNumberChannels, pBufferList->mBuffers[iBuffer].mDataByteSize); #endif @@ -23997,7 +25486,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl } else { /* This is the deinterleaved case. We need to update each buffer in groups of internalChannels. This assumes each buffer is the same size. */ MA_ASSERT(pDevice->playback.internalChannels <= MA_MAX_CHANNELS); /* This should heve been validated at initialization time. */ - + /* For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something very strange has happened and we're not going to support it. @@ -24005,7 +25494,7 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl if ((pBufferList->mNumberBuffers % pDevice->playback.internalChannels) == 0) { ma_uint8 tempBuffer[4096]; UInt32 iBuffer; - + for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; iBuffer += pDevice->playback.internalChannels) { ma_uint32 frameCountPerBuffer = pBufferList->mBuffers[iBuffer].mDataByteSize / ma_get_bytes_per_sample(pDevice->playback.internalFormat); ma_uint32 framesRemaining = frameCountPerBuffer; @@ -24018,24 +25507,20 @@ static OSStatus ma_on_output__coreaudio(void* pUserData, AudioUnitRenderActionFl framesToRead = framesRemaining; } - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_playback(pDevice, framesToRead, tempBuffer, &pDevice->coreaudio.duplexRB); - } else { - ma_device__read_frames_from_client(pDevice, framesToRead, tempBuffer); - } - + ma_device_handle_backend_data_callback(pDevice, tempBuffer, NULL, framesToRead); + for (iChannel = 0; iChannel < pDevice->playback.internalChannels; ++iChannel) { ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pBufferList->mBuffers[iBuffer+iChannel].mData, (frameCountPerBuffer - framesRemaining) * ma_get_bytes_per_sample(pDevice->playback.internalFormat)); } - + ma_deinterleave_pcm_frames(pDevice->playback.internalFormat, pDevice->playback.internalChannels, framesToRead, tempBuffer, ppDeinterleavedBuffers); - + framesRemaining -= framesToRead; } } } } - + (void)pActionFlags; (void)pTimeStamp; (void)busNumber; @@ -24048,24 +25533,52 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla { ma_device* pDevice = (ma_device*)pUserData; AudioBufferList* pRenderedBufferList; + ma_result result; ma_stream_layout layout; + ma_uint32 iBuffer; OSStatus status; MA_ASSERT(pDevice != NULL); - + pRenderedBufferList = (AudioBufferList*)pDevice->coreaudio.pAudioBufferList; MA_ASSERT(pRenderedBufferList); - + /* We need to check whether or not we are outputting interleaved or non-interleaved samples. The way we do this is slightly different for each type. */ layout = ma_stream_layout_interleaved; if (pRenderedBufferList->mBuffers[0].mNumberChannels != pDevice->capture.internalChannels) { layout = ma_stream_layout_deinterleaved; } - + #if defined(MA_DEBUG_OUTPUT) printf("INFO: Input Callback: busNumber=%d, frameCount=%d, mNumberBuffers=%d\n", busNumber, frameCount, pRenderedBufferList->mNumberBuffers); #endif - + + /* + There has been a situation reported where frame count passed into this function is greater than the capacity of + our capture buffer. There doesn't seem to be a reliable way to determine what the maximum frame count will be, + so we need to instead resort to dynamically reallocating our buffer to ensure it's large enough to capture the + number of frames requested by this callback. + */ + result = ma_device_realloc_AudioBufferList__coreaudio(pDevice, frameCount, pDevice->capture.internalFormat, pDevice->capture.internalChannels, layout); + if (result != MA_SUCCESS) { + #if defined(MA_DEBUG_OUTPUT) + printf("Failed to allocate AudioBufferList for capture."); + #endif + return noErr; + } + + /* + When you call AudioUnitRender(), Core Audio tries to be helpful by setting the mDataByteSize to the number of bytes + that were actually rendered. The problem with this is that the next call can fail with -50 due to the size no longer + being set to the capacity of the buffer, but instead the size in bytes of the previous render. This will cause a + problem when a future call to this callback specifies a larger number of frames. + + To work around this we need to explicitly set the size of each buffer to their respective size in bytes. + */ + for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { + pRenderedBufferList->mBuffers[iBuffer].mDataByteSize = pDevice->coreaudio.audioBufferCapInFrames * ma_get_bytes_per_sample(pDevice->capture.internalFormat) * pRenderedBufferList->mBuffers[iBuffer].mNumberChannels; + } + status = ((ma_AudioUnitRender_proc)pDevice->pContext->coreaudio.AudioUnitRender)((AudioUnit)pDevice->coreaudio.audioUnitCapture, pActionFlags, pTimeStamp, busNumber, frameCount, pRenderedBufferList); if (status != noErr) { #if defined(MA_DEBUG_OUTPUT) @@ -24073,16 +25586,12 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla #endif return status; } - + if (layout == ma_stream_layout_interleaved) { - UInt32 iBuffer; for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; ++iBuffer) { if (pRenderedBufferList->mBuffers[iBuffer].mNumberChannels == pDevice->capture.internalChannels) { - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData, &pDevice->coreaudio.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, frameCount, pRenderedBufferList->mBuffers[iBuffer].mData); - } + ma_device_handle_backend_data_callback(pDevice, NULL, pRenderedBufferList->mBuffers[iBuffer].mData, frameCount); + #if defined(MA_DEBUG_OUTPUT) printf(" mDataByteSize=%d\n", pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); #endif @@ -24093,9 +25602,9 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla */ ma_uint8 silentBuffer[4096]; ma_uint32 framesRemaining; - + MA_ZERO_MEMORY(silentBuffer, sizeof(silentBuffer)); - + framesRemaining = frameCount; while (framesRemaining > 0) { ma_uint32 framesToSend = sizeof(silentBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); @@ -24103,15 +25612,11 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla framesToSend = framesRemaining; } - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, framesToSend, silentBuffer, &pDevice->coreaudio.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, framesToSend, silentBuffer); - } - + ma_device_handle_backend_data_callback(pDevice, NULL, silentBuffer, framesToSend); + framesRemaining -= framesToSend; } - + #if defined(MA_DEBUG_OUTPUT) printf(" WARNING: Outputting silence. frameCount=%d, mNumberChannels=%d, mDataByteSize=%d\n", frameCount, pRenderedBufferList->mBuffers[iBuffer].mNumberChannels, pRenderedBufferList->mBuffers[iBuffer].mDataByteSize); #endif @@ -24120,35 +25625,29 @@ static OSStatus ma_on_input__coreaudio(void* pUserData, AudioUnitRenderActionFla } else { /* This is the deinterleaved case. We need to interleave the audio data before sending it to the client. This assumes each buffer is the same size. */ MA_ASSERT(pDevice->capture.internalChannels <= MA_MAX_CHANNELS); /* This should have been validated at initialization time. */ - + /* For safety we'll check that the internal channels is a multiple of the buffer count. If it's not it means something very strange has happened and we're not going to support it. */ if ((pRenderedBufferList->mNumberBuffers % pDevice->capture.internalChannels) == 0) { ma_uint8 tempBuffer[4096]; - UInt32 iBuffer; for (iBuffer = 0; iBuffer < pRenderedBufferList->mNumberBuffers; iBuffer += pDevice->capture.internalChannels) { ma_uint32 framesRemaining = frameCount; while (framesRemaining > 0) { void* ppDeinterleavedBuffers[MA_MAX_CHANNELS]; ma_uint32 iChannel; - ma_uint32 framesToSend = sizeof(tempBuffer) / ma_get_bytes_per_sample(pDevice->capture.internalFormat); + ma_uint32 framesToSend = sizeof(tempBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); if (framesToSend > framesRemaining) { framesToSend = framesRemaining; } - + for (iChannel = 0; iChannel < pDevice->capture.internalChannels; ++iChannel) { ppDeinterleavedBuffers[iChannel] = (void*)ma_offset_ptr(pRenderedBufferList->mBuffers[iBuffer+iChannel].mData, (frameCount - framesRemaining) * ma_get_bytes_per_sample(pDevice->capture.internalFormat)); } - - ma_interleave_pcm_frames(pDevice->capture.internalFormat, pDevice->capture.internalChannels, framesToSend, (const void**)ppDeinterleavedBuffers, tempBuffer); - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, framesToSend, tempBuffer, &pDevice->coreaudio.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, framesToSend, tempBuffer); - } + ma_interleave_pcm_frames(pDevice->capture.internalFormat, pDevice->capture.internalChannels, framesToSend, (const void**)ppDeinterleavedBuffers, tempBuffer); + ma_device_handle_backend_data_callback(pDevice, NULL, tempBuffer, framesToSend); framesRemaining -= framesToSend; } @@ -24169,19 +25668,19 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio { ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - + /* There's been a report of a deadlock here when triggered by ma_device_uninit(). It looks like AudioUnitGetProprty (called below) and AudioComponentInstanceDispose (called in ma_device_uninit) can try waiting on the same lock. I'm going to try working around this by not calling any Core Audio APIs in the callback when the device has been stopped or uninitialized. */ - if (ma_device__get_state(pDevice) == MA_STATE_UNINITIALIZED || ma_device__get_state(pDevice) == MA_STATE_STOPPING || ma_device__get_state(pDevice) == MA_STATE_STOPPED) { + if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED || ma_device_get_state(pDevice) == MA_STATE_STOPPING || ma_device_get_state(pDevice) == MA_STATE_STOPPED) { ma_stop_proc onStop = pDevice->onStop; if (onStop) { onStop(pDevice); } - + ma_event_signal(&pDevice->coreaudio.stopEvent); } else { UInt32 isRunning; @@ -24190,16 +25689,16 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio if (status != noErr) { return; /* Don't really know what to do in this case... just ignore it, I suppose... */ } - + if (!isRunning) { ma_stop_proc onStop; /* The stop event is a bit annoying in Core Audio because it will be called when we automatically switch the default device. Some scenarios to consider: - + 1) When the device is unplugged, this will be called _before_ the default device change notification. 2) When the device is changed via the default device change notification, this will be called _after_ the switch. - + For case #1, we just check if there's a new default device available. If so, we just ignore the stop event. For case #2 we check a flag. */ if (((audioUnit == pDevice->coreaudio.audioUnitPlayback) && pDevice->coreaudio.isDefaultPlaybackDevice) || @@ -24214,17 +25713,17 @@ static void on_start_stop__coreaudio(void* pUserData, AudioUnit audioUnit, Audio ((audioUnit == pDevice->coreaudio.audioUnitCapture) && pDevice->coreaudio.isSwitchingCaptureDevice)) { return; } - + /* Getting here means the device is not reinitializing which means it may have been unplugged. From what I can see, it looks like Core Audio will try switching to the new default device seamlessly. We need to somehow find a way to determine whether or not Core Audio will most likely be successful in switching to the new device. - + TODO: Try to predict if Core Audio will switch devices. If not, the onStop callback needs to be posted. */ return; } - + /* Getting here means we need to stop the device. */ onStop = pDevice->onStop; if (onStop) { @@ -24247,12 +25746,12 @@ static ma_uint32 g_TrackedDeviceCount_CoreAudio = 0; static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UInt32 addressCount, const AudioObjectPropertyAddress* pAddresses, void* pUserData) { ma_device_type deviceType; - + /* Not sure if I really need to check this, but it makes me feel better. */ if (addressCount == 0) { return noErr; } - + if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultOutputDevice) { deviceType = ma_device_type_playback; } else if (pAddresses[0].mSelector == kAudioHardwarePropertyDefaultInputDevice) { @@ -24260,14 +25759,14 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn } else { return noErr; /* Should never hit this. */ } - + ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); { ma_uint32 iDevice; for (iDevice = 0; iDevice < g_TrackedDeviceCount_CoreAudio; iDevice += 1) { ma_result reinitResult; ma_device* pDevice; - + pDevice = g_ppTrackedDevices_CoreAudio[iDevice]; if (pDevice->type == deviceType || pDevice->type == ma_device_type_duplex) { if (deviceType == ma_device_type_playback) { @@ -24279,12 +25778,12 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn reinitResult = ma_device_reinit_internal__coreaudio(pDevice, deviceType, MA_TRUE); pDevice->coreaudio.isSwitchingCaptureDevice = MA_FALSE; } - + if (reinitResult == MA_SUCCESS) { ma_device__post_init_setup(pDevice, deviceType); - + /* Restart the device if required. If this fails we need to stop the device entirely. */ - if (ma_device__get_state(pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { OSStatus status; if (deviceType == ma_device_type_playback) { status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); @@ -24309,7 +25808,7 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn } } ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); - + /* Unused parameters. */ (void)objectID; (void)pUserData; @@ -24320,20 +25819,25 @@ static OSStatus ma_default_device_changed__coreaudio(AudioObjectID objectID, UIn static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContext) { MA_ASSERT(pContext != NULL); - + ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); { - AudioObjectPropertyAddress propAddress; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = kAudioObjectPropertyElementMaster; - - ma_mutex_init(&g_DeviceTrackingMutex_CoreAudio); - - propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - - propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + /* Don't do anything if we've already initializd device tracking. */ + if (g_DeviceTrackingInitCounter_CoreAudio == 0) { + AudioObjectPropertyAddress propAddress; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + ma_mutex_init(&g_DeviceTrackingMutex_CoreAudio); + + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + ((ma_AudioObjectAddPropertyListener_proc)pContext->coreaudio.AudioObjectAddPropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + g_DeviceTrackingInitCounter_CoreAudio += 1; + } } ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); @@ -24343,34 +25847,39 @@ static ma_result ma_context__init_device_tracking__coreaudio(ma_context* pContex static ma_result ma_context__uninit_device_tracking__coreaudio(ma_context* pContext) { MA_ASSERT(pContext != NULL); - + ma_spinlock_lock(&g_DeviceTrackingInitLock_CoreAudio); { - AudioObjectPropertyAddress propAddress; - propAddress.mScope = kAudioObjectPropertyScopeGlobal; - propAddress.mElement = kAudioObjectPropertyElementMaster; - - propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; - ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - - propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); - - /* At this point there should be no tracked devices. If so there's an error somewhere. */ - MA_ASSERT(g_ppTrackedDevices_CoreAudio == NULL); - MA_ASSERT(g_TrackedDeviceCount_CoreAudio == 0); - - ma_mutex_uninit(&g_DeviceTrackingMutex_CoreAudio); + g_DeviceTrackingInitCounter_CoreAudio -= 1; + + if (g_DeviceTrackingInitCounter_CoreAudio == 0) { + AudioObjectPropertyAddress propAddress; + propAddress.mScope = kAudioObjectPropertyScopeGlobal; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice; + ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + propAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; + ((ma_AudioObjectRemovePropertyListener_proc)pContext->coreaudio.AudioObjectRemovePropertyListener)(kAudioObjectSystemObject, &propAddress, &ma_default_device_changed__coreaudio, NULL); + + /* At this point there should be no tracked devices. If not there's an error somewhere. */ + if (g_ppTrackedDevices_CoreAudio != NULL) { + ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "You have uninitialized all contexts while an associated device is still active.", MA_INVALID_OPERATION); + } + + ma_mutex_uninit(&g_DeviceTrackingMutex_CoreAudio); + } } ma_spinlock_unlock(&g_DeviceTrackingInitLock_CoreAudio); - + return MA_SUCCESS; } static ma_result ma_device__track__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - + ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); { /* Allocate memory if required. */ @@ -24378,35 +25887,35 @@ static ma_result ma_device__track__coreaudio(ma_device* pDevice) ma_uint32 oldCap; ma_uint32 newCap; ma_device** ppNewDevices; - + oldCap = g_TrackedDeviceCap_CoreAudio; newCap = g_TrackedDeviceCap_CoreAudio * 2; if (newCap == 0) { newCap = 1; } - + ppNewDevices = (ma_device**)ma__realloc_from_callbacks(g_ppTrackedDevices_CoreAudio, sizeof(*g_ppTrackedDevices_CoreAudio)*newCap, sizeof(*g_ppTrackedDevices_CoreAudio)*oldCap, &pDevice->pContext->allocationCallbacks); if (ppNewDevices == NULL) { ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); return MA_OUT_OF_MEMORY; } - + g_ppTrackedDevices_CoreAudio = ppNewDevices; g_TrackedDeviceCap_CoreAudio = newCap; } - + g_ppTrackedDevices_CoreAudio[g_TrackedDeviceCount_CoreAudio] = pDevice; g_TrackedDeviceCount_CoreAudio += 1; } ma_mutex_unlock(&g_DeviceTrackingMutex_CoreAudio); - + return MA_SUCCESS; } static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - + ma_mutex_lock(&g_DeviceTrackingMutex_CoreAudio); { ma_uint32 iDevice; @@ -24417,16 +25926,16 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) for (jDevice = iDevice; jDevice < g_TrackedDeviceCount_CoreAudio-1; jDevice += 1) { g_ppTrackedDevices_CoreAudio[jDevice] = g_ppTrackedDevices_CoreAudio[jDevice+1]; } - + g_TrackedDeviceCount_CoreAudio -= 1; - + /* If there's nothing else in the list we need to free memory. */ if (g_TrackedDeviceCount_CoreAudio == 0) { ma__free_from_callbacks(g_ppTrackedDevices_CoreAudio, &pDevice->pContext->allocationCallbacks); g_ppTrackedDevices_CoreAudio = NULL; g_TrackedDeviceCap_CoreAudio = 0; } - + break; } } @@ -24461,7 +25970,7 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) -(void)remove_handler { - [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AVAudioSessionRouteChangeNotification" object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil]; } -(void)handle_route_change:(NSNotification*)pNotification @@ -24522,25 +26031,39 @@ static ma_result ma_device__untrack__coreaudio(ma_device* pDevice) } break; } - m_pDevice->sampleRate = (ma_uint32)pSession.sampleRate; +#if defined(MA_DEBUG_OUTPUT) + printf("[Core Audio] Changing Route. inputNumberChannels=%d; outputNumberOfChannels=%d\n", (int)pSession.inputNumberOfChannels, (int)pSession.outputNumberOfChannels); +#endif + + ma_uint32 previousState = ma_device_get_state(m_pDevice); + + if (previousState == MA_STATE_STARTED) { + ma_device_stop(m_pDevice); + } if (m_pDevice->type == ma_device_type_capture || m_pDevice->type == ma_device_type_duplex) { - m_pDevice->capture.channels = (ma_uint32)pSession.inputNumberOfChannels; + m_pDevice->capture.internalChannels = (ma_uint32)pSession.inputNumberOfChannels; + m_pDevice->capture.internalSampleRate = (ma_uint32)pSession.sampleRate; ma_device__post_init_setup(m_pDevice, ma_device_type_capture); } if (m_pDevice->type == ma_device_type_playback || m_pDevice->type == ma_device_type_duplex) { - m_pDevice->playback.channels = (ma_uint32)pSession.outputNumberOfChannels; + m_pDevice->playback.internalChannels = (ma_uint32)pSession.outputNumberOfChannels; + m_pDevice->playback.internalSampleRate = (ma_uint32)pSession.sampleRate; ma_device__post_init_setup(m_pDevice, ma_device_type_playback); } + + if (previousState == MA_STATE_STARTED) { + ma_device_start(m_pDevice); + } } @end #endif -static void ma_device_uninit__coreaudio(ma_device* pDevice) +static ma_result ma_device_uninit__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - MA_ASSERT(ma_device__get_state(pDevice) == MA_STATE_UNINITIALIZED); - + MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED); + #if defined(MA_APPLE_DESKTOP) /* Make sure we're no longer tracking the device. It doesn't matter if we call this for a non-default device because it'll @@ -24554,25 +26077,25 @@ static void ma_device_uninit__coreaudio(ma_device* pDevice) [pRouteChangeHandler remove_handler]; } #endif - + if (pDevice->coreaudio.audioUnitCapture != NULL) { ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); } if (pDevice->coreaudio.audioUnitPlayback != NULL) { ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); } - + if (pDevice->coreaudio.pAudioBufferList) { ma__free_from_callbacks(pDevice->coreaudio.pAudioBufferList, &pDevice->pContext->allocationCallbacks); } - if (pDevice->type == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDevice->coreaudio.duplexRB); - } + return MA_SUCCESS; } typedef struct { + ma_bool32 allowNominalSampleRateChange; + /* Input. */ ma_format formatIn; ma_uint32 channelsIn; @@ -24581,11 +26104,8 @@ typedef struct ma_uint32 periodSizeInFramesIn; ma_uint32 periodSizeInMillisecondsIn; ma_uint32 periodsIn; - ma_bool32 usingDefaultFormat; - ma_bool32 usingDefaultChannels; - ma_bool32 usingDefaultSampleRate; - ma_bool32 usingDefaultChannelMap; ma_share_mode shareMode; + ma_performance_profile performanceProfile; ma_bool32 registerStopEvent; /* Output. */ @@ -24610,10 +26130,12 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev OSStatus status; UInt32 enableIOFlag; AudioStreamBasicDescription bestFormat; - ma_uint32 actualPeriodSizeInFrames; + UInt32 actualPeriodSizeInFrames; AURenderCallbackStruct callbackInfo; #if defined(MA_APPLE_DESKTOP) AudioObjectID deviceObjectID; +#else + UInt32 actualPeriodSizeInFramesSize = sizeof(actualPeriodSizeInFrames); #endif /* This API should only be used for a single device type: playback or capture. No full-duplex mode. */ @@ -24630,16 +26152,16 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev pData->component = NULL; pData->audioUnit = NULL; pData->pAudioBufferList = NULL; - + #if defined(MA_APPLE_DESKTOP) result = ma_find_AudioObjectID(pContext, deviceType, pDeviceID, &deviceObjectID); if (result != MA_SUCCESS) { return result; } - + pData->deviceObjectID = deviceObjectID; #endif - + /* Core audio doesn't really use the notion of a period so we can leave this unmodified, but not too over the top. */ pData->periodsOut = pData->periodsIn; if (pData->periodsOut == 0) { @@ -24648,38 +26170,38 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev if (pData->periodsOut > 16) { pData->periodsOut = 16; } - - + + /* Audio unit. */ status = ((ma_AudioComponentInstanceNew_proc)pContext->coreaudio.AudioComponentInstanceNew)((AudioComponent)pContext->coreaudio.component, (AudioUnit*)&pData->audioUnit); if (status != noErr) { return ma_result_from_OSStatus(status); } - - + + /* The input/output buses need to be explicitly enabled and disabled. We set the flag based on the output unit first, then we just swap it for input. */ enableIOFlag = 1; if (deviceType == ma_device_type_capture) { enableIOFlag = 0; } - + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); } - + enableIOFlag = (enableIOFlag == 0) ? 1 : 0; status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &enableIOFlag, sizeof(enableIOFlag)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); } - - + + /* Set the device to use with this audio unit. This is only used on desktop since we are using defaults on mobile. */ #if defined(MA_APPLE_DESKTOP) - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS, &deviceObjectID, sizeof(AudioDeviceID)); + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceObjectID, sizeof(deviceObjectID)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(result); @@ -24700,68 +26222,103 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev break; } } - + if (found == MA_FALSE) { return MA_DOES_NOT_EXIST; } } } #endif - + /* Format. This is the hardest part of initialization because there's a few variables to take into account. 1) The format must be supported by the device. 2) The format must be supported miniaudio. 3) There's a priority that miniaudio prefers. - + Ideally we would like to use a format that's as close to the hardware as possible so we can get as close to a passthrough as possible. The most important property is the sample rate. miniaudio can do format conversion for any sample rate and channel count, but cannot do the same for the sample data format. If the sample data format is not supported by miniaudio it must be ignored completely. - + On mobile platforms this is a bit different. We just force the use of whatever the audio unit's current format is set to. */ { + AudioStreamBasicDescription origFormat; + UInt32 origFormatSize = sizeof(origFormat); AudioUnitScope formatScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; AudioUnitElement formatElement = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; - #if defined(MA_APPLE_DESKTOP) - AudioStreamBasicDescription origFormat; - UInt32 origFormatSize; - - result = ma_find_best_format__coreaudio(pContext, deviceObjectID, deviceType, pData->formatIn, pData->channelsIn, pData->sampleRateIn, pData->usingDefaultFormat, pData->usingDefaultChannels, pData->usingDefaultSampleRate, &bestFormat); - if (result != MA_SUCCESS) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return result; - } - - /* From what I can see, Apple's documentation implies that we should keep the sample rate consistent. */ - origFormatSize = sizeof(origFormat); if (deviceType == ma_device_type_playback) { status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, MA_COREAUDIO_OUTPUT_BUS, &origFormat, &origFormatSize); } else { status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, MA_COREAUDIO_INPUT_BUS, &origFormat, &origFormatSize); } - if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return ma_result_from_OSStatus(status); + } + + #if defined(MA_APPLE_DESKTOP) + result = ma_find_best_format__coreaudio(pContext, deviceObjectID, deviceType, pData->formatIn, pData->channelsIn, pData->sampleRateIn, &origFormat, &bestFormat); + if (result != MA_SUCCESS) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return result; } - - bestFormat.mSampleRate = origFormat.mSampleRate; - + + /* + Technical Note TN2091: Device input using the HAL Output Audio Unit + https://developer.apple.com/library/archive/technotes/tn2091/_index.html + + This documentation says the following: + + The internal AudioConverter can handle any *simple* conversion. Typically, this means that a client can specify ANY + variant of the PCM formats. Consequently, the device's sample rate should match the desired sample rate. If sample rate + conversion is needed, it can be accomplished by buffering the input and converting the data on a separate thread with + another AudioConverter. + + The important part here is the mention that it can handle *simple* conversions, which does *not* include sample rate. We + therefore want to ensure the sample rate stays consistent. This document is specifically for input, but I'm going to play it + safe and apply the same rule to output as well. + + I have tried going against the documentation by setting the sample rate anyway, but this just results in AudioUnitRender() + returning a result code of -10863. I have also tried changing the format directly on the input scope on the input bus, but + this just results in `ca_require: IsStreamFormatWritable(inScope, inElement) NotWritable` when trying to set the format. + + Something that does seem to work, however, has been setting the nominal sample rate on the deivce object. The problem with + this, however, is that it actually changes the sample rate at the operating system level and not just the application. This + could be intrusive to the user, however, so I don't think it's wise to make this the default. Instead I'm making this a + configuration option. When the `coreaudio.allowNominalSampleRateChange` config option is set to true, changing the sample + rate will be allowed. Otherwise it'll be fixed to the current sample rate. To check the system-defined sample rate, run + the Audio MIDI Setup program that comes installed on macOS and observe how the sample rate changes as the sample rate is + changed by miniaudio. + */ + if (pData->allowNominalSampleRateChange) { + AudioValueRange sampleRateRange; + AudioObjectPropertyAddress propAddress; + + sampleRateRange.mMinimum = bestFormat.mSampleRate; + sampleRateRange.mMaximum = bestFormat.mSampleRate; + + propAddress.mSelector = kAudioDevicePropertyNominalSampleRate; + propAddress.mScope = (deviceType == ma_device_type_playback) ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput; + propAddress.mElement = kAudioObjectPropertyElementMaster; + + status = ((ma_AudioObjectSetPropertyData_proc)pContext->coreaudio.AudioObjectSetPropertyData)(deviceObjectID, &propAddress, 0, NULL, sizeof(sampleRateRange), &sampleRateRange); + if (status != noErr) { + bestFormat.mSampleRate = origFormat.mSampleRate; + } + } else { + bestFormat.mSampleRate = origFormat.mSampleRate; + } + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); if (status != noErr) { /* We failed to set the format, so fall back to the current format of the audio unit. */ bestFormat = origFormat; } #else - UInt32 propSize = sizeof(bestFormat); - status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, &propSize); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } - + bestFormat = origFormat; + /* Sample rate is a little different here because for some reason kAudioUnitProperty_StreamFormat returns 0... Oh well. We need to instead try setting the sample rate to what the user has requested and then just see the results of it. Need to use some Objective-C here for this since @@ -24771,7 +26328,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev @autoreleasepool { AVAudioSession* pAudioSession = [AVAudioSession sharedInstance]; MA_ASSERT(pAudioSession != NULL); - + [pAudioSession setPreferredSampleRate:(double)pData->sampleRateIn error:nil]; bestFormat.mSampleRate = pAudioSession.sampleRate; @@ -24786,26 +26343,26 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev bestFormat.mChannelsPerFrame = (UInt32)pAudioSession.inputNumberOfChannels; } } - + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_StreamFormat, formatScope, formatElement, &bestFormat, sizeof(bestFormat)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); } #endif - + result = ma_format_from_AudioStreamBasicDescription(&bestFormat, &pData->formatOut); if (result != MA_SUCCESS) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return result; } - + if (pData->formatOut == ma_format_unknown) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return MA_FORMAT_NOT_SUPPORTED; } - - pData->channelsOut = bestFormat.mChannelsPerFrame; + + pData->channelsOut = bestFormat.mChannelsPerFrame; pData->sampleRateOut = bestFormat.mSampleRate; } @@ -24813,7 +26370,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev if (pData->channelsOut > MA_MAX_CHANNELS) { pData->channelsOut = MA_MAX_CHANNELS; } - + /* Internal channel map. This is weird in my testing. If I use the AudioObject to get the channel map, the channel descriptions are set to "Unknown" for some reason. To work around @@ -24839,112 +26396,89 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev /* TODO: Figure out how to get the channel map using AVAudioSession. */ ma_get_standard_channel_map(ma_standard_channel_map_default, pData->channelsOut, pData->channelMapOut); #endif - + /* Buffer size. Not allowing this to be configurable on iOS. */ - actualPeriodSizeInFrames = pData->periodSizeInFramesIn; - -#if defined(MA_APPLE_DESKTOP) - if (actualPeriodSizeInFrames == 0) { - actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, pData->sampleRateOut); + if (pData->periodSizeInFramesIn == 0) { + if (pData->periodSizeInMillisecondsIn == 0) { + if (pData->performanceProfile == ma_performance_profile_low_latency) { + actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY, pData->sampleRateOut); + } else { + actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE, pData->sampleRateOut); + } + } else { + actualPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pData->periodSizeInMillisecondsIn, pData->sampleRateOut); + } + } else { + actualPeriodSizeInFrames = pData->periodSizeInFramesIn; } - + +#if defined(MA_APPLE_DESKTOP) result = ma_set_AudioObject_buffer_size_in_frames(pContext, deviceObjectID, deviceType, &actualPeriodSizeInFrames); if (result != MA_SUCCESS) { return result; } - - pData->periodSizeInFramesOut = actualPeriodSizeInFrames; #else - actualPeriodSizeInFrames = 2048; - pData->periodSizeInFramesOut = actualPeriodSizeInFrames; + /* + I don't know how to configure buffer sizes on iOS so for now we're not allowing it to be configured. Instead we're + just going to set it to the value of kAudioUnitProperty_MaximumFramesPerSlice. + */ + status = ((ma_AudioUnitGetProperty_proc)pContext->coreaudio.AudioUnitGetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, &actualPeriodSizeInFramesSize); + if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return ma_result_from_OSStatus(status); + } #endif /* During testing I discovered that the buffer size can be too big. You'll get an error like this: - + kAudioUnitErr_TooManyFramesToProcess : inFramesToProcess=4096, mMaxFramesPerSlice=512 - + Note how inFramesToProcess is smaller than mMaxFramesPerSlice. To fix, we need to set kAudioUnitProperty_MaximumFramesPerSlice to that of the size of our buffer, or do it the other way around and set our buffer size to the kAudioUnitProperty_MaximumFramesPerSlice. */ - { - /*AudioUnitScope propScope = (deviceType == ma_device_type_playback) ? kAudioUnitScope_Input : kAudioUnitScope_Output; - AudioUnitElement propBus = (deviceType == ma_device_type_playback) ? MA_COREAUDIO_OUTPUT_BUS : MA_COREAUDIO_INPUT_BUS; - - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, propScope, propBus, &actualBufferSizeInFrames, sizeof(actualBufferSizeInFrames)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - }*/ - - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, sizeof(actualPeriodSizeInFrames)); - if (status != noErr) { - ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); - return ma_result_from_OSStatus(status); - } + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &actualPeriodSizeInFrames, sizeof(actualPeriodSizeInFrames)); + if (status != noErr) { + ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); + return ma_result_from_OSStatus(status); } - + + pData->periodSizeInFramesOut = (ma_uint32)actualPeriodSizeInFrames; + /* We need a buffer list if this is an input device. We render into this in the input callback. */ if (deviceType == ma_device_type_capture) { ma_bool32 isInterleaved = (bestFormat.mFormatFlags & kAudioFormatFlagIsNonInterleaved) == 0; - size_t allocationSize; AudioBufferList* pBufferList; - allocationSize = sizeof(AudioBufferList) - sizeof(AudioBuffer); /* Subtract sizeof(AudioBuffer) because that part is dynamically sized. */ - if (isInterleaved) { - /* Interleaved case. This is the simple case because we just have one buffer. */ - allocationSize += sizeof(AudioBuffer) * 1; - allocationSize += actualPeriodSizeInFrames * ma_get_bytes_per_frame(pData->formatOut, pData->channelsOut); - } else { - /* Non-interleaved case. This is the more complex case because there's more than one buffer. */ - allocationSize += sizeof(AudioBuffer) * pData->channelsOut; - allocationSize += actualPeriodSizeInFrames * ma_get_bytes_per_sample(pData->formatOut) * pData->channelsOut; - } - - pBufferList = (AudioBufferList*)ma__malloc_from_callbacks(allocationSize, &pContext->allocationCallbacks); + pBufferList = ma_allocate_AudioBufferList__coreaudio(pData->periodSizeInFramesOut, pData->formatOut, pData->channelsOut, (isInterleaved) ? ma_stream_layout_interleaved : ma_stream_layout_deinterleaved, &pContext->allocationCallbacks); if (pBufferList == NULL) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return MA_OUT_OF_MEMORY; } - - if (isInterleaved) { - pBufferList->mNumberBuffers = 1; - pBufferList->mBuffers[0].mNumberChannels = pData->channelsOut; - pBufferList->mBuffers[0].mDataByteSize = actualPeriodSizeInFrames * ma_get_bytes_per_frame(pData->formatOut, pData->channelsOut); - pBufferList->mBuffers[0].mData = (ma_uint8*)pBufferList + sizeof(AudioBufferList); - } else { - ma_uint32 iBuffer; - pBufferList->mNumberBuffers = pData->channelsOut; - for (iBuffer = 0; iBuffer < pBufferList->mNumberBuffers; ++iBuffer) { - pBufferList->mBuffers[iBuffer].mNumberChannels = 1; - pBufferList->mBuffers[iBuffer].mDataByteSize = actualPeriodSizeInFrames * ma_get_bytes_per_sample(pData->formatOut); - pBufferList->mBuffers[iBuffer].mData = (ma_uint8*)pBufferList + ((sizeof(AudioBufferList) - sizeof(AudioBuffer)) + (sizeof(AudioBuffer) * pData->channelsOut)) + (actualPeriodSizeInFrames * ma_get_bytes_per_sample(pData->formatOut) * iBuffer); - } - } - + pData->pAudioBufferList = pBufferList; } - + /* Callbacks. */ callbackInfo.inputProcRefCon = pDevice_DoNotReference; if (deviceType == ma_device_type_playback) { callbackInfo.inputProc = ma_on_output__coreaudio; - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, MA_COREAUDIO_OUTPUT_BUS, &callbackInfo, sizeof(callbackInfo)); + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, 0, &callbackInfo, sizeof(callbackInfo)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); } } else { callbackInfo.inputProc = ma_on_input__coreaudio; - status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, MA_COREAUDIO_INPUT_BUS, &callbackInfo, sizeof(callbackInfo)); + status = ((ma_AudioUnitSetProperty_proc)pContext->coreaudio.AudioUnitSetProperty)(pData->audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callbackInfo, sizeof(callbackInfo)); if (status != noErr) { ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); } } - + /* We need to listen for stop events. */ if (pData->registerStopEvent) { status = ((ma_AudioUnitAddPropertyListener_proc)pContext->coreaudio.AudioUnitAddPropertyListener)(pData->audioUnit, kAudioOutputUnitProperty_IsRunning, on_start_stop__coreaudio, pDevice_DoNotReference); @@ -24953,7 +26487,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev return ma_result_from_OSStatus(status); } } - + /* Initialize the audio unit. */ status = ((ma_AudioUnitInitialize_proc)pContext->coreaudio.AudioUnitInitialize)(pData->audioUnit); if (status != noErr) { @@ -24962,7 +26496,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev ((ma_AudioComponentInstanceDispose_proc)pContext->coreaudio.AudioComponentInstanceDispose)(pData->audioUnit); return ma_result_from_OSStatus(status); } - + /* Grab the name. */ #if defined(MA_APPLE_DESKTOP) ma_get_AudioObject_name(pContext, deviceObjectID, sizeof(pData->deviceName), pData->deviceName); @@ -24973,7 +26507,7 @@ static ma_result ma_device_init_internal__coreaudio(ma_context* pContext, ma_dev ma_strcpy_s(pData->deviceName, sizeof(pData->deviceName), MA_DEFAULT_CAPTURE_DEVICE_NAME); } #endif - + return result; } @@ -24988,18 +26522,17 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev return MA_INVALID_ARGS; } + data.allowNominalSampleRateChange = MA_FALSE; /* Don't change the nominal sample rate when switching devices. */ + if (deviceType == ma_device_type_capture) { data.formatIn = pDevice->capture.format; data.channelsIn = pDevice->capture.channels; data.sampleRateIn = pDevice->sampleRate; MA_COPY_MEMORY(data.channelMapIn, pDevice->capture.channelMap, sizeof(pDevice->capture.channelMap)); - data.usingDefaultFormat = pDevice->capture.usingDefaultFormat; - data.usingDefaultChannels = pDevice->capture.usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->capture.usingDefaultChannelMap; data.shareMode = pDevice->capture.shareMode; + data.performanceProfile = pDevice->coreaudio.originalPerformanceProfile; data.registerStopEvent = MA_TRUE; - + if (disposePreviousAudioUnit) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitCapture); ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); @@ -25012,13 +26545,10 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev data.channelsIn = pDevice->playback.channels; data.sampleRateIn = pDevice->sampleRate; MA_COPY_MEMORY(data.channelMapIn, pDevice->playback.channelMap, sizeof(pDevice->playback.channelMap)); - data.usingDefaultFormat = pDevice->playback.usingDefaultFormat; - data.usingDefaultChannels = pDevice->playback.usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->playback.usingDefaultChannelMap; data.shareMode = pDevice->playback.shareMode; + data.performanceProfile = pDevice->coreaudio.originalPerformanceProfile; data.registerStopEvent = (pDevice->type != ma_device_type_duplex); - + if (disposePreviousAudioUnit) { ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); @@ -25037,14 +26567,15 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev if (result != MA_SUCCESS) { return result; } - + if (deviceType == ma_device_type_capture) { #if defined(MA_APPLE_DESKTOP) pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; #endif pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; - + pDevice->coreaudio.audioBufferCapInFrames = data.periodSizeInFramesOut; + pDevice->capture.internalFormat = data.formatOut; pDevice->capture.internalChannels = data.channelsOut; pDevice->capture.internalSampleRate = data.sampleRateOut; @@ -25056,7 +26587,7 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; #endif pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; - + pDevice->playback.internalFormat = data.formatOut; pDevice->playback.internalChannels = data.channelsOut; pDevice->playback.internalSampleRate = data.sampleRateOut; @@ -25064,70 +26595,72 @@ static ma_result ma_device_reinit_internal__coreaudio(ma_device* pDevice, ma_dev pDevice->playback.internalPeriodSizeInFrames = data.periodSizeInFramesOut; pDevice->playback.internalPeriods = data.periodsOut; } - + return MA_SUCCESS; } #endif /* MA_APPLE_DESKTOP */ -static ma_result ma_device_init__coreaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__coreaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; - MA_ASSERT(pContext != NULL); - MA_ASSERT(pConfig != NULL); MA_ASSERT(pDevice != NULL); + MA_ASSERT(pConfig != NULL); if (pConfig->deviceType == ma_device_type_loopback) { return MA_DEVICE_TYPE_NOT_SUPPORTED; } /* No exclusive mode with the Core Audio backend for now. */ - if (((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } - + /* Capture needs to be initialized first. */ if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ma_device_init_internal_data__coreaudio data; - data.formatIn = pConfig->capture.format; - data.channelsIn = pConfig->capture.channels; - data.sampleRateIn = pConfig->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pConfig->capture.channelMap, sizeof(pConfig->capture.channelMap)); - data.usingDefaultFormat = pDevice->capture.usingDefaultFormat; - data.usingDefaultChannels = pDevice->capture.usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->capture.usingDefaultChannelMap; - data.shareMode = pConfig->capture.shareMode; - data.periodSizeInFramesIn = pConfig->periodSizeInFrames; - data.periodSizeInMillisecondsIn = pConfig->periodSizeInMilliseconds; - data.periodsIn = pConfig->periods; - data.registerStopEvent = MA_TRUE; + data.allowNominalSampleRateChange = pConfig->coreaudio.allowNominalSampleRateChange; + data.formatIn = pDescriptorCapture->format; + data.channelsIn = pDescriptorCapture->channels; + data.sampleRateIn = pDescriptorCapture->sampleRate; + MA_COPY_MEMORY(data.channelMapIn, pDescriptorCapture->channelMap, sizeof(pDescriptorCapture->channelMap)); + data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; + data.periodSizeInMillisecondsIn = pDescriptorCapture->periodSizeInMilliseconds; + data.periodsIn = pDescriptorCapture->periodCount; + data.shareMode = pDescriptorCapture->shareMode; + data.performanceProfile = pConfig->performanceProfile; + data.registerStopEvent = MA_TRUE; /* Need at least 3 periods for duplex. */ if (data.periodsIn < 3 && pConfig->deviceType == ma_device_type_duplex) { data.periodsIn = 3; } - - result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_capture, pConfig->capture.pDeviceID, &data, (void*)pDevice); + + result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_capture, pDescriptorCapture->pDeviceID, &data, (void*)pDevice); if (result != MA_SUCCESS) { return result; } - - pDevice->coreaudio.isDefaultCaptureDevice = (pConfig->capture.pDeviceID == NULL); + + pDevice->coreaudio.isDefaultCaptureDevice = (pConfig->capture.pDeviceID == NULL); #if defined(MA_APPLE_DESKTOP) - pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; + pDevice->coreaudio.deviceObjectIDCapture = (ma_uint32)data.deviceObjectID; #endif - pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; - pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; - - pDevice->capture.internalFormat = data.formatOut; - pDevice->capture.internalChannels = data.channelsOut; - pDevice->capture.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->capture.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->capture.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->capture.internalPeriods = data.periodsOut; - + pDevice->coreaudio.audioUnitCapture = (ma_ptr)data.audioUnit; + pDevice->coreaudio.pAudioBufferList = (ma_ptr)data.pAudioBufferList; + pDevice->coreaudio.audioBufferCapInFrames = data.periodSizeInFramesOut; + pDevice->coreaudio.originalPeriodSizeInFrames = pDescriptorCapture->periodSizeInFrames; + pDevice->coreaudio.originalPeriodSizeInMilliseconds = pDescriptorCapture->periodSizeInMilliseconds; + pDevice->coreaudio.originalPeriods = pDescriptorCapture->periodCount; + pDevice->coreaudio.originalPerformanceProfile = pConfig->performanceProfile; + + pDescriptorCapture->format = data.formatOut; + pDescriptorCapture->channels = data.channelsOut; + pDescriptorCapture->sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(pDescriptorCapture->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDescriptorCapture->periodSizeInFrames = data.periodSizeInFramesOut; + pDescriptorCapture->periodCount = data.periodsOut; + #if defined(MA_APPLE_DESKTOP) /* If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly @@ -25138,33 +26671,31 @@ static ma_result ma_device_init__coreaudio(ma_context* pContext, const ma_device } #endif } - + /* Playback. */ if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { ma_device_init_internal_data__coreaudio data; - data.formatIn = pConfig->playback.format; - data.channelsIn = pConfig->playback.channels; - data.sampleRateIn = pConfig->sampleRate; - MA_COPY_MEMORY(data.channelMapIn, pConfig->playback.channelMap, sizeof(pConfig->playback.channelMap)); - data.usingDefaultFormat = pDevice->playback.usingDefaultFormat; - data.usingDefaultChannels = pDevice->playback.usingDefaultChannels; - data.usingDefaultSampleRate = pDevice->usingDefaultSampleRate; - data.usingDefaultChannelMap = pDevice->playback.usingDefaultChannelMap; - data.shareMode = pConfig->playback.shareMode; - + data.allowNominalSampleRateChange = pConfig->coreaudio.allowNominalSampleRateChange; + data.formatIn = pDescriptorPlayback->format; + data.channelsIn = pDescriptorPlayback->channels; + data.sampleRateIn = pDescriptorPlayback->sampleRate; + MA_COPY_MEMORY(data.channelMapIn, pDescriptorPlayback->channelMap, sizeof(pDescriptorPlayback->channelMap)); + data.shareMode = pDescriptorPlayback->shareMode; + data.performanceProfile = pConfig->performanceProfile; + /* In full-duplex mode we want the playback buffer to be the same size as the capture buffer. */ if (pConfig->deviceType == ma_device_type_duplex) { - data.periodSizeInFramesIn = pDevice->capture.internalPeriodSizeInFrames; - data.periodsIn = pDevice->capture.internalPeriods; + data.periodSizeInFramesIn = pDescriptorCapture->periodSizeInFrames; + data.periodsIn = pDescriptorCapture->periodCount; data.registerStopEvent = MA_FALSE; } else { - data.periodSizeInFramesIn = pConfig->periodSizeInFrames; - data.periodSizeInMillisecondsIn = pConfig->periodSizeInMilliseconds; - data.periodsIn = pConfig->periods; + data.periodSizeInFramesIn = pDescriptorPlayback->periodSizeInFrames; + data.periodSizeInMillisecondsIn = pDescriptorPlayback->periodSizeInMilliseconds; + data.periodsIn = pDescriptorPlayback->periodCount; data.registerStopEvent = MA_TRUE; } - - result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_playback, pConfig->playback.pDeviceID, &data, (void*)pDevice); + + result = ma_device_init_internal__coreaudio(pDevice->pContext, ma_device_type_playback, pDescriptorPlayback->pDeviceID, &data, (void*)pDevice); if (result != MA_SUCCESS) { if (pConfig->deviceType == ma_device_type_duplex) { ((ma_AudioComponentInstanceDispose_proc)pDevice->pContext->coreaudio.AudioComponentInstanceDispose)((AudioUnit)pDevice->coreaudio.audioUnitCapture); @@ -25174,61 +26705,43 @@ static ma_result ma_device_init__coreaudio(ma_context* pContext, const ma_device } return result; } - - pDevice->coreaudio.isDefaultPlaybackDevice = (pConfig->playback.pDeviceID == NULL); + + pDevice->coreaudio.isDefaultPlaybackDevice = (pConfig->playback.pDeviceID == NULL); #if defined(MA_APPLE_DESKTOP) - pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; + pDevice->coreaudio.deviceObjectIDPlayback = (ma_uint32)data.deviceObjectID; #endif - pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; - - pDevice->playback.internalFormat = data.formatOut; - pDevice->playback.internalChannels = data.channelsOut; - pDevice->playback.internalSampleRate = data.sampleRateOut; - MA_COPY_MEMORY(pDevice->playback.internalChannelMap, data.channelMapOut, sizeof(data.channelMapOut)); - pDevice->playback.internalPeriodSizeInFrames = data.periodSizeInFramesOut; - pDevice->playback.internalPeriods = data.periodsOut; - + pDevice->coreaudio.audioUnitPlayback = (ma_ptr)data.audioUnit; + pDevice->coreaudio.originalPeriodSizeInFrames = pDescriptorPlayback->periodSizeInFrames; + pDevice->coreaudio.originalPeriodSizeInMilliseconds = pDescriptorPlayback->periodSizeInMilliseconds; + pDevice->coreaudio.originalPeriods = pDescriptorPlayback->periodCount; + pDevice->coreaudio.originalPerformanceProfile = pConfig->performanceProfile; + + pDescriptorPlayback->format = data.formatOut; + pDescriptorPlayback->channels = data.channelsOut; + pDescriptorPlayback->sampleRate = data.sampleRateOut; + MA_COPY_MEMORY(pDescriptorPlayback->channelMap, data.channelMapOut, sizeof(data.channelMapOut)); + pDescriptorPlayback->periodSizeInFrames = data.periodSizeInFramesOut; + pDescriptorPlayback->periodCount = data.periodsOut; + #if defined(MA_APPLE_DESKTOP) /* If we are using the default device we'll need to listen for changes to the system's default device so we can seemlessly switch the device in the background. */ - if (pConfig->playback.pDeviceID == NULL && (pConfig->deviceType != ma_device_type_duplex || pConfig->capture.pDeviceID != NULL)) { + if (pDescriptorPlayback->pDeviceID == NULL && (pConfig->deviceType != ma_device_type_duplex || pDescriptorCapture->pDeviceID != NULL)) { ma_device__track__coreaudio(pDevice); } #endif } + - pDevice->coreaudio.originalPeriodSizeInFrames = pConfig->periodSizeInFrames; - pDevice->coreaudio.originalPeriodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; - pDevice->coreaudio.originalPeriods = pConfig->periods; - + /* When stopping the device, a callback is called on another thread. We need to wait for this callback before returning from ma_device_stop(). This event is used for this. */ ma_event_init(&pDevice->coreaudio.stopEvent); - /* Need a ring buffer for duplex mode. */ - if (pConfig->deviceType == ma_device_type_duplex) { - ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods); - ma_result result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->pContext->allocationCallbacks, &pDevice->coreaudio.duplexRB); - if (result != MA_SUCCESS) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[Core Audio] Failed to initialize ring buffer.", result); - } - - /* We need a period to act as a buffer for cases where the playback and capture device's end up desyncing. */ - { - ma_uint32 bufferSizeInFrames = rbSizeInFrames / pDevice->capture.internalPeriods; - void* pBufferData; - ma_pcm_rb_acquire_write(&pDevice->coreaudio.duplexRB, &bufferSizeInFrames, &pBufferData); - { - MA_ZERO_MEMORY(pBufferData, bufferSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); - } - ma_pcm_rb_commit_write(&pDevice->coreaudio.duplexRB, bufferSizeInFrames, pBufferData); - } - } - /* We need to detect when a route has changed so we can update the data conversion pipeline accordingly. This is done differently on non-Desktop Apple platforms. @@ -25244,14 +26757,14 @@ static ma_result ma_device_init__coreaudio(ma_context* pContext, const ma_device static ma_result ma_device_start__coreaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); - + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { OSStatus status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitCapture); if (status != noErr) { return ma_result_from_OSStatus(status); } } - + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { OSStatus status = ((ma_AudioOutputUnitStart_proc)pDevice->pContext->coreaudio.AudioOutputUnitStart)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); if (status != noErr) { @@ -25261,7 +26774,7 @@ static ma_result ma_device_start__coreaudio(ma_device* pDevice) return ma_result_from_OSStatus(status); } } - + return MA_SUCCESS; } @@ -25277,14 +26790,14 @@ static ma_result ma_device_stop__coreaudio(ma_device* pDevice) return ma_result_from_OSStatus(status); } } - + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { OSStatus status = ((ma_AudioOutputUnitStop_proc)pDevice->pContext->coreaudio.AudioOutputUnitStop)((AudioUnit)pDevice->coreaudio.audioUnitPlayback); if (status != noErr) { return ma_result_from_OSStatus(status); } } - + /* We need to wait for the callback to finish before returning. */ ma_event_wait(&pDevice->coreaudio.stopEvent); return MA_SUCCESS; @@ -25295,7 +26808,7 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) { MA_ASSERT(pContext != NULL); MA_ASSERT(pContext->backend == ma_backend_coreaudio); - + #if defined(MA_APPLE_MOBILE) if (!pContext->coreaudio.noAudioSessionDeactivate) { if (![[AVAudioSession sharedInstance] setActive:false error:nil]) { @@ -25303,7 +26816,7 @@ static ma_result ma_context_uninit__coreaudio(ma_context* pContext) } } #endif - + #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) ma_dlclose(pContext, pContext->coreaudio.hAudioUnit); ma_dlclose(pContext, pContext->coreaudio.hCoreAudio); @@ -25339,7 +26852,7 @@ static AVAudioSessionCategory ma_to_AVAudioSessionCategory(ma_ios_session_catego } #endif -static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__coreaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { #if !defined(MA_APPLE_MOBILE) ma_result result; @@ -25380,7 +26893,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma } } } - + if (!pConfig->coreaudio.noAudioSessionActivate) { if (![pAudioSession setActive:true error:nil]) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to activate audio session.", MA_FAILED_TO_INIT_BACKEND); @@ -25388,23 +26901,23 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma } } #endif - + #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) pContext->coreaudio.hCoreFoundation = ma_dlopen(pContext, "CoreFoundation.framework/CoreFoundation"); if (pContext->coreaudio.hCoreFoundation == NULL) { return MA_API_NOT_FOUND; } - + pContext->coreaudio.CFStringGetCString = ma_dlsym(pContext, pContext->coreaudio.hCoreFoundation, "CFStringGetCString"); pContext->coreaudio.CFRelease = ma_dlsym(pContext, pContext->coreaudio.hCoreFoundation, "CFRelease"); - - + + pContext->coreaudio.hCoreAudio = ma_dlopen(pContext, "CoreAudio.framework/CoreAudio"); if (pContext->coreaudio.hCoreAudio == NULL) { ma_dlclose(pContext, pContext->coreaudio.hCoreFoundation); return MA_API_NOT_FOUND; } - + pContext->coreaudio.AudioObjectGetPropertyData = ma_dlsym(pContext, pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyData"); pContext->coreaudio.AudioObjectGetPropertyDataSize = ma_dlsym(pContext, pContext->coreaudio.hCoreAudio, "AudioObjectGetPropertyDataSize"); pContext->coreaudio.AudioObjectSetPropertyData = ma_dlsym(pContext, pContext->coreaudio.hCoreAudio, "AudioObjectSetPropertyData"); @@ -25423,7 +26936,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma ma_dlclose(pContext, pContext->coreaudio.hCoreFoundation); return MA_API_NOT_FOUND; } - + if (ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioComponentFindNext") == NULL) { /* Couldn't find the required symbols in AudioUnit, so fall back to AudioToolbox. */ ma_dlclose(pContext, pContext->coreaudio.hAudioUnit); @@ -25434,7 +26947,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma return MA_API_NOT_FOUND; } } - + pContext->coreaudio.AudioComponentFindNext = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioComponentFindNext"); pContext->coreaudio.AudioComponentInstanceDispose = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioComponentInstanceDispose"); pContext->coreaudio.AudioComponentInstanceNew = ma_dlsym(pContext, pContext->coreaudio.hAudioUnit, "AudioComponentInstanceNew"); @@ -25449,7 +26962,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma #else pContext->coreaudio.CFStringGetCString = (ma_proc)CFStringGetCString; pContext->coreaudio.CFRelease = (ma_proc)CFRelease; - + #if defined(MA_APPLE_DESKTOP) pContext->coreaudio.AudioObjectGetPropertyData = (ma_proc)AudioObjectGetPropertyData; pContext->coreaudio.AudioObjectGetPropertyDataSize = (ma_proc)AudioObjectGetPropertyDataSize; @@ -25457,7 +26970,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma pContext->coreaudio.AudioObjectAddPropertyListener = (ma_proc)AudioObjectAddPropertyListener; pContext->coreaudio.AudioObjectRemovePropertyListener = (ma_proc)AudioObjectRemovePropertyListener; #endif - + pContext->coreaudio.AudioComponentFindNext = (ma_proc)AudioComponentFindNext; pContext->coreaudio.AudioComponentInstanceDispose = (ma_proc)AudioComponentInstanceDispose; pContext->coreaudio.AudioComponentInstanceNew = (ma_proc)AudioComponentInstanceNew; @@ -25471,17 +26984,6 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma pContext->coreaudio.AudioUnitRender = (ma_proc)AudioUnitRender; #endif - pContext->isBackendAsynchronous = MA_TRUE; - - pContext->onUninit = ma_context_uninit__coreaudio; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__coreaudio; - pContext->onEnumDevices = ma_context_enumerate_devices__coreaudio; - pContext->onGetDeviceInfo = ma_context_get_device_info__coreaudio; - pContext->onDeviceInit = ma_device_init__coreaudio; - pContext->onDeviceUninit = ma_device_uninit__coreaudio; - pContext->onDeviceStart = ma_device_start__coreaudio; - pContext->onDeviceStop = ma_device_stop__coreaudio; - /* Audio component. */ { AudioComponentDescription desc; @@ -25494,7 +26996,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; - + pContext->coreaudio.component = ((ma_AudioComponentFindNext_proc)pContext->coreaudio.AudioComponentFindNext)(NULL, &desc); if (pContext->coreaudio.component == NULL) { #if !defined(MA_NO_RUNTIME_LINKING) && !defined(MA_APPLE_MOBILE) @@ -25505,7 +27007,7 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma return MA_FAILED_TO_INIT_BACKEND; } } - + #if !defined(MA_APPLE_MOBILE) result = ma_context__init_device_tracking__coreaudio(pContext); if (result != MA_SUCCESS) { @@ -25519,6 +27021,18 @@ static ma_result ma_context_init__coreaudio(const ma_context_config* pConfig, ma #endif pContext->coreaudio.noAudioSessionDeactivate = pConfig->coreaudio.noAudioSessionDeactivate; + + pCallbacks->onContextInit = ma_context_init__coreaudio; + pCallbacks->onContextUninit = ma_context_uninit__coreaudio; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__coreaudio; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__coreaudio; + pCallbacks->onDeviceInit = ma_device_init__coreaudio; + pCallbacks->onDeviceUninit = ma_device_uninit__coreaudio; + pCallbacks->onDeviceStart = ma_device_start__coreaudio; + pCallbacks->onDeviceStop = ma_device_stop__coreaudio; + pCallbacks->onDeviceRead = NULL; + pCallbacks->onDeviceWrite = NULL; + pCallbacks->onDeviceDataLoop = NULL; return MA_SUCCESS; } @@ -25634,7 +27148,7 @@ static ma_format ma_format_from_sio_enc__sndio(unsigned int bits, unsigned int b if ((ma_is_little_endian() && le == 0) || (ma_is_big_endian() && le == 1)) { return ma_format_unknown; } - + if (bits == 8 && bps == 1 && sig == 0) { return ma_format_u8; } @@ -25650,7 +27164,7 @@ static ma_format ma_format_from_sio_enc__sndio(unsigned int bits, unsigned int b if (bits == 32 && bps == 4 && sig == 1) { return ma_format_s32; } - + return ma_format_unknown; } @@ -25660,7 +27174,7 @@ static ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps unsigned int iConfig; MA_ASSERT(caps != NULL); - + bestFormat = ma_format_unknown; for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { unsigned int iEncoding; @@ -25675,7 +27189,7 @@ static ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { continue; } - + bits = caps->enc[iEncoding].bits; bps = caps->enc[iEncoding].bps; sig = caps->enc[iEncoding].sig; @@ -25685,7 +27199,7 @@ static ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps if (format == ma_format_unknown) { continue; /* Format not supported. */ } - + if (bestFormat == ma_format_unknown) { bestFormat = format; } else { @@ -25695,7 +27209,7 @@ static ma_format ma_find_best_format_from_sio_cap__sndio(struct ma_sio_cap* caps } } } - + return bestFormat; } @@ -25706,7 +27220,7 @@ static ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* ca MA_ASSERT(caps != NULL); MA_ASSERT(requiredFormat != ma_format_unknown); - + /* Just pick whatever configuration has the most channels. */ maxChannels = 0; for (iConfig = 0; iConfig < caps->nconf; iConfig += 1) { @@ -25724,7 +27238,7 @@ static ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* ca if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { continue; } - + bits = caps->enc[iEncoding].bits; bps = caps->enc[iEncoding].bps; sig = caps->enc[iEncoding].sig; @@ -25734,7 +27248,7 @@ static ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* ca if (format != requiredFormat) { continue; } - + /* Getting here means the format is supported. Iterate over each channel count and grab the biggest one. */ for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { unsigned int chan = 0; @@ -25745,24 +27259,24 @@ static ma_uint32 ma_find_best_channels_from_sio_cap__sndio(struct ma_sio_cap* ca } else { chan = caps->confs[iConfig].rchan; } - + if ((chan & (1UL << iChannel)) == 0) { continue; } - + if (deviceType == ma_device_type_playback) { channels = caps->pchan[iChannel]; } else { channels = caps->rchan[iChannel]; } - + if (maxChannels < channels) { maxChannels = channels; } } } } - + return maxChannels; } @@ -25776,7 +27290,7 @@ static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* MA_ASSERT(requiredFormat != ma_format_unknown); MA_ASSERT(requiredChannels > 0); MA_ASSERT(requiredChannels <= MA_MAX_CHANNELS); - + firstSampleRate = 0; /* <-- If the device does not support a standard rate we'll fall back to the first one that's found. */ bestSampleRate = 0; @@ -25795,7 +27309,7 @@ static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* if ((caps->confs[iConfig].enc & (1UL << iEncoding)) == 0) { continue; } - + bits = caps->enc[iEncoding].bits; bps = caps->enc[iEncoding].bps; sig = caps->enc[iEncoding].sig; @@ -25805,7 +27319,7 @@ static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* if (format != requiredFormat) { continue; } - + /* Getting here means the format is supported. Iterate over each channel count and grab the biggest one. */ for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { unsigned int chan = 0; @@ -25817,36 +27331,36 @@ static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* } else { chan = caps->confs[iConfig].rchan; } - + if ((chan & (1UL << iChannel)) == 0) { continue; } - + if (deviceType == ma_device_type_playback) { channels = caps->pchan[iChannel]; } else { channels = caps->rchan[iChannel]; } - + if (channels != requiredChannels) { continue; } - + /* Getting here means we have found a compatible encoding/channel pair. */ for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { ma_uint32 rate = (ma_uint32)caps->rate[iRate]; ma_uint32 ratePriority; - + if (firstSampleRate == 0) { firstSampleRate = rate; } - + /* Disregard this rate if it's not a standard one. */ ratePriority = ma_get_standard_sample_rate_priority_index__sndio(rate); if (ratePriority == (ma_uint32)-1) { continue; } - + if (ma_get_standard_sample_rate_priority_index__sndio(bestSampleRate) > ratePriority) { /* Lower = better. */ bestSampleRate = rate; } @@ -25854,26 +27368,16 @@ static ma_uint32 ma_find_best_sample_rate_from_sio_cap__sndio(struct ma_sio_cap* } } } - + /* If a standard sample rate was not found just fall back to the first one that was iterated. */ if (bestSampleRate == 0) { bestSampleRate = firstSampleRate; } - + return bestSampleRate; } -static ma_bool32 ma_context_is_device_id_equal__sndio(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return ma_strcmp(pID0->sndio, pID1->sndio) == 0; -} - static ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { ma_bool32 isTerminating = MA_FALSE; @@ -25881,9 +27385,9 @@ static ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_en MA_ASSERT(pContext != NULL); MA_ASSERT(callback != NULL); - + /* sndio doesn't seem to have a good device enumeration API, so I'm therefore only enumerating over default devices for now. */ - + /* Playback. */ if (!isTerminating) { handle = ((ma_sio_open_proc)pContext->sndio.sio_open)(MA_SIO_DEVANY, MA_SIO_PLAY, 0); @@ -25893,13 +27397,13 @@ static ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_en MA_ZERO_OBJECT(&deviceInfo); ma_strcpy_s(deviceInfo.id.sndio, sizeof(deviceInfo.id.sndio), MA_SIO_DEVANY); ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME); - + isTerminating = !callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); - + ((ma_sio_close_proc)pContext->sndio.sio_close)(handle); } } - + /* Capture. */ if (!isTerminating) { handle = ((ma_sio_open_proc)pContext->sndio.sio_open)(MA_SIO_DEVANY, MA_SIO_REC, 0); @@ -25911,15 +27415,15 @@ static ma_result ma_context_enumerate_devices__sndio(ma_context* pContext, ma_en ma_strcpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME); isTerminating = !callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); - + ((ma_sio_close_proc)pContext->sndio.sio_close)(handle); } } - + return MA_SUCCESS; } -static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { char devid[256]; struct ma_sio_hdl* handle; @@ -25927,8 +27431,7 @@ static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_devi unsigned int iConfig; MA_ASSERT(pContext != NULL); - (void)shareMode; - + /* We need to open the device before we can get information about it. */ if (pDeviceID == NULL) { ma_strcpy_s(devid, sizeof(devid), MA_SIO_DEVANY); @@ -25937,16 +27440,18 @@ static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_devi ma_strcpy_s(devid, sizeof(devid), pDeviceID->sndio); ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), devid); } - + handle = ((ma_sio_open_proc)pContext->sndio.sio_open)(devid, (deviceType == ma_device_type_playback) ? MA_SIO_PLAY : MA_SIO_REC, 0); if (handle == NULL) { return MA_NO_DEVICE; } - + if (((ma_sio_getcap_proc)pContext->sndio.sio_getcap)(handle, &caps) == 0) { return MA_ERROR; } - + + pDeviceInfo->nativeDataFormatCount = 0; + for (iConfig = 0; iConfig < caps.nconf; iConfig += 1) { /* The main thing we care about is that the encoding is supported by miniaudio. If it is, we want to give @@ -25963,13 +27468,11 @@ static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_devi unsigned int le; unsigned int msb; ma_format format; - ma_bool32 formatExists = MA_FALSE; - ma_uint32 iExistingFormat; if ((caps.confs[iConfig].enc & (1UL << iEncoding)) == 0) { continue; } - + bits = caps.enc[iEncoding].bits; bps = caps.enc[iEncoding].bps; sig = caps.enc[iEncoding].sig; @@ -25979,58 +27482,35 @@ static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_devi if (format == ma_format_unknown) { continue; /* Format not supported. */ } - - /* Add this format if it doesn't already exist. */ - for (iExistingFormat = 0; iExistingFormat < pDeviceInfo->formatCount; iExistingFormat += 1) { - if (pDeviceInfo->formats[iExistingFormat] == format) { - formatExists = MA_TRUE; - break; - } - } - - if (!formatExists) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = format; - } - } - - /* Channels. */ - for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { - unsigned int chan = 0; - unsigned int channels; - if (deviceType == ma_device_type_playback) { - chan = caps.confs[iConfig].pchan; - } else { - chan = caps.confs[iConfig].rchan; - } - - if ((chan & (1UL << iChannel)) == 0) { - continue; - } - - if (deviceType == ma_device_type_playback) { - channels = caps.pchan[iChannel]; - } else { - channels = caps.rchan[iChannel]; - } - - if (pDeviceInfo->minChannels > channels) { - pDeviceInfo->minChannels = channels; - } - if (pDeviceInfo->maxChannels < channels) { - pDeviceInfo->maxChannels = channels; - } - } - - /* Sample rates. */ - for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { - if ((caps.confs[iConfig].rate & (1UL << iRate)) != 0) { - unsigned int rate = caps.rate[iRate]; - if (pDeviceInfo->minSampleRate > rate) { - pDeviceInfo->minSampleRate = rate; + + /* Channels. */ + for (iChannel = 0; iChannel < MA_SIO_NCHAN; iChannel += 1) { + unsigned int chan = 0; + unsigned int channels; + + if (deviceType == ma_device_type_playback) { + chan = caps.confs[iConfig].pchan; + } else { + chan = caps.confs[iConfig].rchan; } - if (pDeviceInfo->maxSampleRate < rate) { - pDeviceInfo->maxSampleRate = rate; + + if ((chan & (1UL << iChannel)) == 0) { + continue; + } + + if (deviceType == ma_device_type_playback) { + channels = caps.pchan[iChannel]; + } else { + channels = caps.rchan[iChannel]; + } + + + /* Sample Rates. */ + for (iRate = 0; iRate < MA_SIO_NRATE; iRate += 1) { + if ((caps.confs[iConfig].rate & (1UL << iRate)) != 0) { + ma_device_info_add_native_data_format(pDeviceInfo, format, channels, caps.rate[iRate], 0); + } } } } @@ -26040,7 +27520,7 @@ static ma_result ma_context_get_device_info__sndio(ma_context* pContext, ma_devi return MA_SUCCESS; } -static void ma_device_uninit__sndio(ma_device* pDevice) +static ma_result ma_device_uninit__sndio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -26051,16 +27531,18 @@ static void ma_device_uninit__sndio(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); } + + return MA_SUCCESS; } -static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +static ma_result ma_device_init_handle__sndio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { const char* pDeviceName; ma_ptr handle; int openFlags = 0; struct ma_sio_cap caps; struct ma_sio_par par; - ma_device_id* pDeviceID; + const ma_device_id* pDeviceID; ma_format format; ma_uint32 channels; ma_uint32 sampleRate; @@ -26070,38 +27552,34 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; - MA_ASSERT(pContext != NULL); MA_ASSERT(pConfig != NULL); MA_ASSERT(deviceType != ma_device_type_duplex); MA_ASSERT(pDevice != NULL); if (deviceType == ma_device_type_capture) { openFlags = MA_SIO_REC; - pDeviceID = pConfig->capture.pDeviceID; - format = pConfig->capture.format; - channels = pConfig->capture.channels; - sampleRate = pConfig->sampleRate; } else { openFlags = MA_SIO_PLAY; - pDeviceID = pConfig->playback.pDeviceID; - format = pConfig->playback.format; - channels = pConfig->playback.channels; - sampleRate = pConfig->sampleRate; } + pDeviceID = pDescriptor->pDeviceID; + format = pDescriptor->format; + channels = pDescriptor->channels; + sampleRate = pDescriptor->sampleRate; + pDeviceName = MA_SIO_DEVANY; if (pDeviceID != NULL) { pDeviceName = pDeviceID->sndio; } - handle = (ma_ptr)((ma_sio_open_proc)pContext->sndio.sio_open)(pDeviceName, openFlags, 0); + handle = (ma_ptr)((ma_sio_open_proc)pDevice->pContext->sndio.sio_open)(pDeviceName, openFlags, 0); if (handle == NULL) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to open device.", MA_FAILED_TO_OPEN_BACKEND_DEVICE); } /* We need to retrieve the device caps to determine the most appropriate format to use. */ - if (((ma_sio_getcap_proc)pContext->sndio.sio_getcap)((struct ma_sio_hdl*)handle, &caps) == 0) { - ((ma_sio_close_proc)pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); + if (((ma_sio_getcap_proc)pDevice->pContext->sndio.sio_getcap)((struct ma_sio_hdl*)handle, &caps) == 0) { + ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve device caps.", MA_ERROR); } @@ -26109,31 +27587,37 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev Note: sndio reports a huge range of available channels. This is inconvenient for us because there's no real way, as far as I can tell, to get the _actual_ channel count of the device. I'm therefore restricting this to the requested channels, regardless of whether or not the default channel count is requested. - + For hardware devices, I'm suspecting only a single channel count will be reported and we can safely use the value returned by ma_find_best_channels_from_sio_cap__sndio(). */ if (deviceType == ma_device_type_capture) { - if (pDevice->capture.usingDefaultFormat) { + if (format == ma_format_unknown) { format = ma_find_best_format_from_sio_cap__sndio(&caps); } - if (pDevice->capture.usingDefaultChannels) { + + if (channels == 0) { if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); + } else { + channels = MA_DEFAULT_CHANNELS; } } } else { - if (pDevice->playback.usingDefaultFormat) { + if (format == ma_format_unknown) { format = ma_find_best_format_from_sio_cap__sndio(&caps); } - if (pDevice->playback.usingDefaultChannels) { + + if (channels == 0) { if (strlen(pDeviceName) > strlen("rsnd/") && strncmp(pDeviceName, "rsnd/", strlen("rsnd/")) == 0) { channels = ma_find_best_channels_from_sio_cap__sndio(&caps, deviceType, format); + } else { + channels = MA_DEFAULT_CHANNELS; } } } - - if (pDevice->usingDefaultSampleRate) { + + if (sampleRate == 0) { sampleRate = ma_find_best_sample_rate_from_sio_cap__sndio(&caps, pConfig->deviceType, format, channels); } @@ -26141,7 +27625,7 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev ((ma_sio_initpar_proc)pDevice->pContext->sndio.sio_initpar)(&par); par.msb = 0; par.le = ma_is_little_endian(); - + switch (format) { case ma_format_u8: { @@ -26149,23 +27633,24 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev par.bps = 1; par.sig = 0; } break; - + case ma_format_s24: { par.bits = 24; par.bps = 3; par.sig = 1; } break; - + case ma_format_s32: { par.bits = 32; par.bps = 4; par.sig = 1; } break; - + case ma_format_s16: case ma_format_f32: + case ma_format_unknown: default: { par.bits = 16; @@ -26182,20 +27667,18 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev par.rate = sampleRate; - internalPeriodSizeInFrames = pConfig->periodSizeInFrames; - if (internalPeriodSizeInFrames == 0) { - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, par.rate); - } + internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, par.rate, pConfig->performanceProfile); par.round = internalPeriodSizeInFrames; - par.appbufsz = par.round * pConfig->periods; - - if (((ma_sio_setpar_proc)pContext->sndio.sio_setpar)((struct ma_sio_hdl*)handle, &par) == 0) { - ((ma_sio_close_proc)pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); + par.appbufsz = par.round * pDescriptor->periodCount; + + if (((ma_sio_setpar_proc)pDevice->pContext->sndio.sio_setpar)((struct ma_sio_hdl*)handle, &par) == 0) { + ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to set buffer size.", MA_FORMAT_NOT_SUPPORTED); } - if (((ma_sio_getpar_proc)pContext->sndio.sio_getpar)((struct ma_sio_hdl*)handle, &par) == 0) { - ((ma_sio_close_proc)pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); + + if (((ma_sio_getpar_proc)pDevice->pContext->sndio.sio_getpar)((struct ma_sio_hdl*)handle, &par) == 0) { + ((ma_sio_close_proc)pDevice->pContext->sndio.sio_close)((struct ma_sio_hdl*)handle); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[sndio] Failed to retrieve buffer size.", MA_FORMAT_NOT_SUPPORTED); } @@ -26206,23 +27689,18 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev internalPeriodSizeInFrames = par.round; if (deviceType == ma_device_type_capture) { - pDevice->sndio.handleCapture = handle; - pDevice->capture.internalFormat = internalFormat; - pDevice->capture.internalChannels = internalChannels; - pDevice->capture.internalSampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sndio, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); - pDevice->capture.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->capture.internalPeriods = internalPeriods; + pDevice->sndio.handleCapture = handle; } else { - pDevice->sndio.handlePlayback = handle; - pDevice->playback.internalFormat = internalFormat; - pDevice->playback.internalChannels = internalChannels; - pDevice->playback.internalSampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sndio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); - pDevice->playback.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->playback.internalPeriods = internalPeriods; + pDevice->sndio.handlePlayback = handle; } + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_sndio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; + #ifdef MA_DEBUG_OUTPUT printf("DEVICE INFO\n"); printf(" Format: %s\n", ma_get_format_name(internalFormat)); @@ -26237,7 +27715,7 @@ static ma_result ma_device_init_handle__sndio(ma_context* pContext, const ma_dev return MA_SUCCESS; } -static ma_result ma_device_init__sndio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__sndio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { MA_ASSERT(pDevice != NULL); @@ -26248,14 +27726,14 @@ static ma_result ma_device_init__sndio(ma_context* pContext, const ma_device_con } if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_handle__sndio(pContext, pConfig, ma_device_type_capture, pDevice); + ma_result result = ma_device_init_handle__sndio(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_handle__sndio(pContext, pConfig, ma_device_type_playback, pDevice); + ma_result result = ma_device_init_handle__sndio(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { return result; } @@ -26264,6 +27742,21 @@ static ma_result ma_device_init__sndio(ma_context* pContext, const ma_device_con return MA_SUCCESS; } +static ma_result ma_device_start__sndio(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); /* <-- Doesn't actually playback until data is written. */ + } + + return MA_SUCCESS; +} + static ma_result ma_device_stop__sndio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -26305,7 +27798,7 @@ static ma_result ma_device_write__sndio(ma_device* pDevice, const void* pPCMFram if (pFramesWritten != NULL) { *pFramesWritten = frameCount; } - + return MA_SUCCESS; } @@ -26325,181 +27818,10 @@ static ma_result ma_device_read__sndio(ma_device* pDevice, void* pPCMFrames, ma_ if (pFramesRead != NULL) { *pFramesRead = frameCount; } - + return MA_SUCCESS; } -static ma_result ma_device_main_loop__sndio(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - /* Devices need to be started here. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handleCapture); - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ((ma_sio_start_proc)pDevice->pContext->sndio.sio_start)((struct ma_sio_hdl*)pDevice->sndio.handlePlayback); /* <-- Doesn't actually playback until data is written. */ - } - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__sndio(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__sndio(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__sndio()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__sndio(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__sndio(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - - /* Here is where the device is stopped. */ - ma_device_stop__sndio(pDevice); - - return result; -} - static ma_result ma_context_uninit__sndio(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -26509,7 +27831,7 @@ static ma_result ma_context_uninit__sndio(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__sndio(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__sndio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { #ifndef MA_NO_RUNTIME_LINKING const char* libsndioNames[] = { @@ -26527,7 +27849,7 @@ static ma_result ma_context_init__sndio(const ma_context_config* pConfig, ma_con if (pContext->sndio.sndioSO == NULL) { return MA_NO_BACKEND; } - + pContext->sndio.sio_open = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_open"); pContext->sndio.sio_close = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_close"); pContext->sndio.sio_setpar = (ma_proc)ma_dlsym(pContext, pContext->sndio.sndioSO, "sio_setpar"); @@ -26551,15 +27873,17 @@ static ma_result ma_context_init__sndio(const ma_context_config* pConfig, ma_con pContext->sndio.sio_initpar = sio_initpar; #endif - pContext->onUninit = ma_context_uninit__sndio; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__sndio; - pContext->onEnumDevices = ma_context_enumerate_devices__sndio; - pContext->onGetDeviceInfo = ma_context_get_device_info__sndio; - pContext->onDeviceInit = ma_device_init__sndio; - pContext->onDeviceUninit = ma_device_uninit__sndio; - pContext->onDeviceStart = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceStop = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceMainLoop = ma_device_main_loop__sndio; + pCallbacks->onContextInit = ma_context_init__sndio; + pCallbacks->onContextUninit = ma_context_uninit__sndio; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__sndio; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__sndio; + pCallbacks->onDeviceInit = ma_device_init__sndio; + pCallbacks->onDeviceUninit = ma_device_uninit__sndio; + pCallbacks->onDeviceStart = ma_device_start__sndio; + pCallbacks->onDeviceStop = ma_device_stop__sndio; + pCallbacks->onDeviceRead = ma_device_read__sndio; + pCallbacks->onDeviceWrite = ma_device_write__sndio; + pCallbacks->onDeviceDataLoop = NULL; (void)pConfig; return MA_SUCCESS; @@ -26596,10 +27920,10 @@ static void ma_construct_device_id__audio4(char* id, size_t idSize, const char* MA_ASSERT(id != NULL); MA_ASSERT(idSize > 0); MA_ASSERT(deviceIndex >= 0); - + baseLen = strlen(base); MA_ASSERT(idSize > baseLen); - + ma_strcpy_s(id, idSize, base); ma_itoa_s(deviceIndex, id+baseLen, idSize-baseLen, 10); } @@ -26613,38 +27937,29 @@ static ma_result ma_extract_device_index_from_id__audio4(const char* id, const c MA_ASSERT(id != NULL); MA_ASSERT(base != NULL); MA_ASSERT(pIndexOut != NULL); - + idLen = strlen(id); baseLen = strlen(base); if (idLen <= baseLen) { return MA_ERROR; /* Doesn't look like the id starts with the base. */ } - + if (strncmp(id, base, baseLen) != 0) { return MA_ERROR; /* ID does not begin with base. */ } - + deviceIndexStr = id + baseLen; if (deviceIndexStr[0] == '\0') { return MA_ERROR; /* No index specified in the ID. */ } - + if (pIndexOut) { *pIndexOut = atoi(deviceIndexStr); } - + return MA_SUCCESS; } -static ma_bool32 ma_context_is_device_id_equal__audio4(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return ma_strcmp(pID0->audio4, pID1->audio4) == 0; -} #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ static ma_format ma_format_from_encoding__audio4(unsigned int encoding, unsigned int precision) @@ -26676,7 +27991,6 @@ static ma_format ma_format_from_encoding__audio4(unsigned int encoding, unsigned static void ma_encoding_from_format__audio4(ma_format format, unsigned int* pEncoding, unsigned int* pPrecision) { - MA_ASSERT(format != ma_format_unknown); MA_ASSERT(pEncoding != NULL); MA_ASSERT(pPrecision != NULL); @@ -26702,6 +28016,7 @@ static void ma_encoding_from_format__audio4(ma_format format, unsigned int* pEnc case ma_format_s16: case ma_format_f32: + case ma_format_unknown: default: { *pEncoding = (ma_is_little_endian()) ? AUDIO_ENCODING_SLINEAR_LE : AUDIO_ENCODING_SLINEAR_BE; @@ -26714,6 +28029,56 @@ static ma_format ma_format_from_prinfo__audio4(struct audio_prinfo* prinfo) { return ma_format_from_encoding__audio4(prinfo->encoding, prinfo->precision); } + +static ma_format ma_best_format_from_fd__audio4(int fd, ma_format preferredFormat) +{ + audio_encoding_t encoding; + ma_uint32 iFormat; + int counter = 0; + + /* First check to see if the preferred format is supported. */ + if (preferredFormat != ma_format_unknown) { + counter = 0; + for (;;) { + MA_ZERO_OBJECT(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } + + if (preferredFormat == ma_format_from_encoding__audio4(encoding.encoding, encoding.precision)) { + return preferredFormat; /* Found the preferred format. */ + } + + /* Getting here means this encoding does not match our preferred format so we need to more on to the next encoding. */ + counter += 1; + } + } + + /* Getting here means our preferred format is not supported, so fall back to our standard priorities. */ + for (iFormat = 0; iFormat < ma_countof(g_maFormatPriorities); iFormat += 1) { + ma_format format = g_maFormatPriorities[iFormat]; + + counter = 0; + for (;;) { + MA_ZERO_OBJECT(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } + + if (format == ma_format_from_encoding__audio4(encoding.encoding, encoding.precision)) { + return format; /* Found a workable format. */ + } + + /* Getting here means this encoding does not match our preferred format so we need to more on to the next encoding. */ + counter += 1; + } + } + + /* Getting here means not appropriate format was found. */ + return ma_format_unknown; +} #else static ma_format ma_format_from_swpar__audio4(struct audio_swpar* par) { @@ -26735,21 +28100,14 @@ static ma_format ma_format_from_swpar__audio4(struct audio_swpar* par) } #endif -static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext, ma_device_type deviceType, int fd, ma_device_info* pInfoOut) +static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext, ma_device_type deviceType, int fd, ma_device_info* pDeviceInfo) { audio_device_t fdDevice; -#if !defined(MA_AUDIO4_USE_NEW_API) - int counter = 0; - audio_info_t fdInfo; -#else - struct audio_swpar fdPar; - ma_format format; -#endif MA_ASSERT(pContext != NULL); MA_ASSERT(fd >= 0); - MA_ASSERT(pInfoOut != NULL); - + MA_ASSERT(pDeviceInfo != NULL); + (void)pContext; (void)deviceType; @@ -26758,66 +28116,76 @@ static ma_result ma_context_get_device_info_from_fd__audio4(ma_context* pContext } /* Name. */ - ma_strcpy_s(pInfoOut->name, sizeof(pInfoOut->name), fdDevice.name); + ma_strcpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), fdDevice.name); -#if !defined(MA_AUDIO4_USE_NEW_API) - /* Supported formats. We get this by looking at the encodings. */ - for (;;) { - audio_encoding_t encoding; + #if !defined(MA_AUDIO4_USE_NEW_API) + { + audio_info_t fdInfo; + int counter = 0; + ma_uint32 channels; + ma_uint32 sampleRate; + + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { + return MA_ERROR; + } + + if (deviceType == ma_device_type_playback) { + channels = fdInfo.play.channels; + sampleRate = fdInfo.play.sample_rate; + } else { + channels = fdInfo.record.channels; + sampleRate = fdInfo.record.sample_rate; + } + + /* Supported formats. We get this by looking at the encodings. */ + pDeviceInfo->nativeDataFormatCount = 0; + for (;;) { + audio_encoding_t encoding; + ma_format format; + + MA_ZERO_OBJECT(&encoding); + encoding.index = counter; + if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { + break; + } + + format = ma_format_from_encoding__audio4(encoding.encoding, encoding.precision); + if (format != ma_format_unknown) { + ma_device_info_add_native_data_format(pDeviceInfo, format, channels, sampleRate, 0); + } + + counter += 1; + } + } + #else + { + struct audio_swpar fdPar; ma_format format; + ma_uint32 channels; + ma_uint32 sampleRate; - MA_ZERO_OBJECT(&encoding); - encoding.index = counter; - if (ioctl(fd, AUDIO_GETENC, &encoding) < 0) { - break; + if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { + return MA_ERROR; } - format = ma_format_from_encoding__audio4(encoding.encoding, encoding.precision); - if (format != ma_format_unknown) { - pInfoOut->formats[pInfoOut->formatCount++] = format; + format = ma_format_from_swpar__audio4(&fdPar); + if (format == ma_format_unknown) { + return MA_FORMAT_NOT_SUPPORTED; } - counter += 1; - } + if (deviceType == ma_device_type_playback) { + channels = fdPar.pchan; + } else { + channels = fdPar.rchan; + } - if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { - return MA_ERROR; - } + sampleRate = fdPar.rate; - if (deviceType == ma_device_type_playback) { - pInfoOut->minChannels = fdInfo.play.channels; - pInfoOut->maxChannels = fdInfo.play.channels; - pInfoOut->minSampleRate = fdInfo.play.sample_rate; - pInfoOut->maxSampleRate = fdInfo.play.sample_rate; - } else { - pInfoOut->minChannels = fdInfo.record.channels; - pInfoOut->maxChannels = fdInfo.record.channels; - pInfoOut->minSampleRate = fdInfo.record.sample_rate; - pInfoOut->maxSampleRate = fdInfo.record.sample_rate; + pDeviceInfo->nativeDataFormatCount = 0; + ma_device_info_add_native_data_format(pDeviceInfo, format, channels, sampleRate, 0); } -#else - if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { - return MA_ERROR; - } - - format = ma_format_from_swpar__audio4(&fdPar); - if (format == ma_format_unknown) { - return MA_FORMAT_NOT_SUPPORTED; - } - pInfoOut->formats[pInfoOut->formatCount++] = format; - - if (deviceType == ma_device_type_playback) { - pInfoOut->minChannels = fdPar.pchan; - pInfoOut->maxChannels = fdPar.pchan; - } else { - pInfoOut->minChannels = fdPar.rchan; - pInfoOut->maxChannels = fdPar.rchan; - } - - pInfoOut->minSampleRate = fdPar.rate; - pInfoOut->maxSampleRate = fdPar.rate; -#endif - + #endif + return MA_SUCCESS; } @@ -26829,7 +28197,7 @@ static ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_e MA_ASSERT(pContext != NULL); MA_ASSERT(callback != NULL); - + /* Every device will be named "/dev/audioN", with a "/dev/audioctlN" equivalent. We use the "/dev/audioctlN" version here since we can open it even when another process has control of the "/dev/audioN" device. @@ -26841,13 +28209,13 @@ static ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_e ma_strcpy_s(devpath, sizeof(devpath), "/dev/audioctl"); ma_itoa_s(iDevice, devpath+strlen(devpath), sizeof(devpath)-strlen(devpath), 10); - + if (stat(devpath, &st) < 0) { break; } /* The device exists, but we need to check if it's usable as playback and/or capture. */ - + /* Playback. */ if (!isTerminating) { fd = open(devpath, O_RDONLY, 0); @@ -26859,11 +28227,11 @@ static ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_e if (ma_context_get_device_info_from_fd__audio4(pContext, ma_device_type_playback, fd, &deviceInfo) == MA_SUCCESS) { isTerminating = !callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } - + close(fd); } } - + /* Capture. */ if (!isTerminating) { fd = open(devpath, O_WRONLY, 0); @@ -26875,20 +28243,20 @@ static ma_result ma_context_enumerate_devices__audio4(ma_context* pContext, ma_e if (ma_context_get_device_info_from_fd__audio4(pContext, ma_device_type_capture, fd, &deviceInfo) == MA_SUCCESS) { isTerminating = !callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } - + close(fd); } } - + if (isTerminating) { break; } } - + return MA_SUCCESS; } -static ma_result ma_context_get_device_info__audio4(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__audio4(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { int fd = -1; int deviceIndex = -1; @@ -26896,8 +28264,7 @@ static ma_result ma_context_get_device_info__audio4(ma_context* pContext, ma_dev ma_result result; MA_ASSERT(pContext != NULL); - (void)shareMode; - + /* We need to open the "/dev/audioctlN" device to get the info. To do this we need to extract the number from the device ID which will be in "/dev/audioN" format. @@ -26911,28 +28278,28 @@ static ma_result ma_context_get_device_info__audio4(ma_context* pContext, ma_dev if (result != MA_SUCCESS) { return result; } - + ma_construct_device_id__audio4(ctlid, sizeof(ctlid), "/dev/audioctl", deviceIndex); } - + fd = open(ctlid, (deviceType == ma_device_type_playback) ? O_WRONLY : O_RDONLY, 0); if (fd == -1) { return MA_NO_DEVICE; } - + if (deviceIndex == -1) { ma_strcpy_s(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio"); } else { ma_construct_device_id__audio4(pDeviceInfo->id.audio4, sizeof(pDeviceInfo->id.audio4), "/dev/audio", deviceIndex); } - + result = ma_context_get_device_info_from_fd__audio4(pContext, deviceType, fd, pDeviceInfo); - + close(fd); return result; } -static void ma_device_uninit__audio4(ma_device* pDevice) +static ma_result ma_device_uninit__audio4(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -26943,9 +28310,11 @@ static void ma_device_uninit__audio4(ma_device* pDevice) if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { close(pDevice->audio4.fdPlayback); } + + return MA_SUCCESS; } -static ma_result ma_device_init_fd__audio4(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +static ma_result ma_device_init_fd__audio4(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { const char* pDefaultDeviceNames[] = { "/dev/audio", @@ -26953,24 +28322,16 @@ static ma_result ma_device_init_fd__audio4(ma_context* pContext, const ma_device }; int fd; int fdFlags = 0; -#if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ - audio_info_t fdInfo; -#else - struct audio_swpar fdPar; -#endif ma_format internalFormat; ma_uint32 internalChannels; ma_uint32 internalSampleRate; ma_uint32 internalPeriodSizeInFrames; ma_uint32 internalPeriods; - MA_ASSERT(pContext != NULL); MA_ASSERT(pConfig != NULL); MA_ASSERT(deviceType != ma_device_type_duplex); MA_ASSERT(pDevice != NULL); - (void)pContext; - /* The first thing to do is open the file. */ if (deviceType == ma_device_type_capture) { fdFlags = O_RDONLY; @@ -26979,7 +28340,7 @@ static ma_result ma_device_init_fd__audio4(ma_context* pContext, const ma_device } /*fdFlags |= O_NONBLOCK;*/ - if ((deviceType == ma_device_type_capture && pConfig->capture.pDeviceID == NULL) || (deviceType == ma_device_type_playback && pConfig->playback.pDeviceID == NULL)) { + if (pDescriptor->pDeviceID == NULL) { /* Default device. */ size_t iDevice; for (iDevice = 0; iDevice < ma_countof(pDefaultDeviceNames); ++iDevice) { @@ -26990,137 +28351,167 @@ static ma_result ma_device_init_fd__audio4(ma_context* pContext, const ma_device } } else { /* Specific device. */ - fd = open((deviceType == ma_device_type_capture) ? pConfig->capture.pDeviceID->audio4 : pConfig->playback.pDeviceID->audio4, fdFlags, 0); + fd = open(pDescriptor->pDeviceID->audio4, fdFlags, 0); } if (fd == -1) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to open device.", ma_result_from_errno(errno)); } -#if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ - AUDIO_INITINFO(&fdInfo); - - /* We get the driver to do as much of the data conversion as possible. */ - if (deviceType == ma_device_type_capture) { - fdInfo.mode = AUMODE_RECORD; - ma_encoding_from_format__audio4(pConfig->capture.format, &fdInfo.record.encoding, &fdInfo.record.precision); - fdInfo.record.channels = pConfig->capture.channels; - fdInfo.record.sample_rate = pConfig->sampleRate; - } else { - fdInfo.mode = AUMODE_PLAY; - ma_encoding_from_format__audio4(pConfig->playback.format, &fdInfo.play.encoding, &fdInfo.play.precision); - fdInfo.play.channels = pConfig->playback.channels; - fdInfo.play.sample_rate = pConfig->sampleRate; - } - - if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { - close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); - } - - if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { - close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed.", MA_FORMAT_NOT_SUPPORTED); - } - - if (deviceType == ma_device_type_capture) { - internalFormat = ma_format_from_prinfo__audio4(&fdInfo.record); - internalChannels = fdInfo.record.channels; - internalSampleRate = fdInfo.record.sample_rate; - } else { - internalFormat = ma_format_from_prinfo__audio4(&fdInfo.play); - internalChannels = fdInfo.play.channels; - internalSampleRate = fdInfo.play.sample_rate; - } - - if (internalFormat == ma_format_unknown) { - close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); - } - - /* Buffer. */ + #if !defined(MA_AUDIO4_USE_NEW_API) /* Old API */ { - ma_uint32 internalPeriodSizeInBytes; + audio_info_t fdInfo; - internalPeriodSizeInFrames = pConfig->periodSizeInFrames; - if (internalPeriodSizeInFrames == 0) { - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, internalSampleRate); - } + /* + The documentation is a little bit unclear to me as to how it handles formats. It says the + following: - internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); - if (internalPeriodSizeInBytes < 16) { - internalPeriodSizeInBytes = 16; - } + Regardless of formats supported by underlying driver, the audio driver accepts the + following formats. - internalPeriods = pConfig->periods; - if (internalPeriods < 2) { - internalPeriods = 2; - } + By then the next sentence says this: - /* What miniaudio calls a period, audio4 calls a block. */ + `encoding` and `precision` are one of the values obtained by AUDIO_GETENC. + + It sounds like a direct contradiction to me. I'm going to play this safe any only use the + best sample format returned by AUDIO_GETENC. If the requested format is supported we'll + use that, but otherwise we'll just use our standard format priorities to pick an + appropriate one. + */ AUDIO_INITINFO(&fdInfo); - fdInfo.hiwat = internalPeriods; - fdInfo.lowat = internalPeriods-1; - fdInfo.blocksize = internalPeriodSizeInBytes; + + /* We get the driver to do as much of the data conversion as possible. */ + if (deviceType == ma_device_type_capture) { + fdInfo.mode = AUMODE_RECORD; + ma_encoding_from_format__audio4(ma_best_format_from_fd__audio4(fd, pDescriptor->format), &fdInfo.record.encoding, &fdInfo.record.precision); + + if (pDescriptor->channels != 0) { + fdInfo.record.channels = ma_clamp(pDescriptor->channels, 1, 12); /* From the documentation: `channels` ranges from 1 to 12. */ + } + + if (pDescriptor->sampleRate != 0) { + fdInfo.record.sample_rate = ma_clamp(pDescriptor->sampleRate, 1000, 192000); /* From the documentation: `frequency` ranges from 1000Hz to 192000Hz. (They mean `sample_rate` instead of `frequency`.) */ + } + } else { + fdInfo.mode = AUMODE_PLAY; + ma_encoding_from_format__audio4(ma_best_format_from_fd__audio4(fd, pDescriptor->format), &fdInfo.play.encoding, &fdInfo.play.precision); + + if (pDescriptor->channels != 0) { + fdInfo.play.channels = ma_clamp(pDescriptor->channels, 1, 12); /* From the documentation: `channels` ranges from 1 to 12. */ + } + + if (pDescriptor->sampleRate != 0) { + fdInfo.play.sample_rate = ma_clamp(pDescriptor->sampleRate, 1000, 192000); /* From the documentation: `frequency` ranges from 1000Hz to 192000Hz. (They mean `sample_rate` instead of `frequency`.) */ + } + } + if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device format. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); } - internalPeriods = fdInfo.hiwat; - internalPeriodSizeInFrames = fdInfo.blocksize / ma_get_bytes_per_frame(internalFormat, internalChannels); - } -#else - /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ - if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { - close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters.", MA_FORMAT_NOT_SUPPORTED); - } - - internalFormat = ma_format_from_swpar__audio4(&fdPar); - internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; - internalSampleRate = fdPar.rate; - - if (internalFormat == ma_format_unknown) { - close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); - } - - /* Buffer. */ - { - ma_uint32 internalPeriodSizeInBytes; - - internalPeriodSizeInFrames = pConfig->periodSizeInFrames; - if (internalPeriodSizeInFrames == 0) { - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, internalSampleRate); - } - - /* What miniaudio calls a period, audio4 calls a block. */ - internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); - if (internalPeriodSizeInBytes < 16) { - internalPeriodSizeInBytes = 16; - } - - fdPar.nblks = pConfig->periods; - fdPar.round = internalPeriodSizeInBytes; - - if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { + if (ioctl(fd, AUDIO_GETINFO, &fdInfo) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters.", MA_FORMAT_NOT_SUPPORTED); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] AUDIO_GETINFO failed.", MA_FORMAT_NOT_SUPPORTED); } + if (deviceType == ma_device_type_capture) { + internalFormat = ma_format_from_prinfo__audio4(&fdInfo.record); + internalChannels = fdInfo.record.channels; + internalSampleRate = fdInfo.record.sample_rate; + } else { + internalFormat = ma_format_from_prinfo__audio4(&fdInfo.play); + internalChannels = fdInfo.play.channels; + internalSampleRate = fdInfo.play.sample_rate; + } + + if (internalFormat == ma_format_unknown) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + } + + /* Buffer. */ + { + ma_uint32 internalPeriodSizeInBytes; + + internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate, pConfig->performanceProfile); + + internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (internalPeriodSizeInBytes < 16) { + internalPeriodSizeInBytes = 16; + } + + internalPeriods = pDescriptor->periodCount; + if (internalPeriods < 2) { + internalPeriods = 2; + } + + /* What miniaudio calls a period, audio4 calls a block. */ + AUDIO_INITINFO(&fdInfo); + fdInfo.hiwat = internalPeriods; + fdInfo.lowat = internalPeriods-1; + fdInfo.blocksize = internalPeriodSizeInBytes; + if (ioctl(fd, AUDIO_SETINFO, &fdInfo) < 0) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set internal buffer size. AUDIO_SETINFO failed.", MA_FORMAT_NOT_SUPPORTED); + } + + internalPeriods = fdInfo.hiwat; + internalPeriodSizeInFrames = fdInfo.blocksize / ma_get_bytes_per_frame(internalFormat, internalChannels); + } + } + #else + { + struct audio_swpar fdPar; + + /* We need to retrieve the format of the device so we can know the channel count and sample rate. Then we can calculate the buffer size. */ if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { close(fd); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters.", MA_FORMAT_NOT_SUPPORTED); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve initial device parameters.", MA_FORMAT_NOT_SUPPORTED); } - } - internalFormat = ma_format_from_swpar__audio4(&fdPar); - internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; - internalSampleRate = fdPar.rate; - internalPeriods = fdPar.nblks; - internalPeriodSizeInFrames = fdPar.round / ma_get_bytes_per_frame(internalFormat, internalChannels); -#endif + internalFormat = ma_format_from_swpar__audio4(&fdPar); + internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; + internalSampleRate = fdPar.rate; + + if (internalFormat == ma_format_unknown) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] The device's internal device format is not supported by miniaudio. The device is unusable.", MA_FORMAT_NOT_SUPPORTED); + } + + /* Buffer. */ + { + ma_uint32 internalPeriodSizeInBytes; + + internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, internalSampleRate, pConfig->performanceProfile); + + /* What miniaudio calls a period, audio4 calls a block. */ + internalPeriodSizeInBytes = internalPeriodSizeInFrames * ma_get_bytes_per_frame(internalFormat, internalChannels); + if (internalPeriodSizeInBytes < 16) { + internalPeriodSizeInBytes = 16; + } + + fdPar.nblks = pDescriptor->periodCount; + fdPar.round = internalPeriodSizeInBytes; + + if (ioctl(fd, AUDIO_SETPAR, &fdPar) < 0) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to set device parameters.", MA_FORMAT_NOT_SUPPORTED); + } + + if (ioctl(fd, AUDIO_GETPAR, &fdPar) < 0) { + close(fd); + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[audio4] Failed to retrieve actual device parameters.", MA_FORMAT_NOT_SUPPORTED); + } + } + + internalFormat = ma_format_from_swpar__audio4(&fdPar); + internalChannels = (deviceType == ma_device_type_capture) ? fdPar.rchan : fdPar.pchan; + internalSampleRate = fdPar.rate; + internalPeriods = fdPar.nblks; + internalPeriodSizeInFrames = fdPar.round / ma_get_bytes_per_frame(internalFormat, internalChannels); + } + #endif if (internalFormat == ma_format_unknown) { close(fd); @@ -27128,27 +28519,22 @@ static ma_result ma_device_init_fd__audio4(ma_context* pContext, const ma_device } if (deviceType == ma_device_type_capture) { - pDevice->audio4.fdCapture = fd; - pDevice->capture.internalFormat = internalFormat; - pDevice->capture.internalChannels = internalChannels; - pDevice->capture.internalSampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, internalChannels, pDevice->capture.internalChannelMap); - pDevice->capture.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->capture.internalPeriods = internalPeriods; + pDevice->audio4.fdCapture = fd; } else { - pDevice->audio4.fdPlayback = fd; - pDevice->playback.internalFormat = internalFormat; - pDevice->playback.internalChannels = internalChannels; - pDevice->playback.internalSampleRate = internalSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, internalChannels, pDevice->playback.internalChannelMap); - pDevice->playback.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->playback.internalPeriods = internalPeriods; + pDevice->audio4.fdPlayback = fd; } + pDescriptor->format = internalFormat; + pDescriptor->channels = internalChannels; + pDescriptor->sampleRate = internalSampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_sound4, internalChannels, pDescriptor->channelMap); + pDescriptor->periodSizeInFrames = internalPeriodSizeInFrames; + pDescriptor->periodCount = internalPeriods; + return MA_SUCCESS; } -static ma_result ma_device_init__audio4(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__audio4(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { MA_ASSERT(pDevice != NULL); @@ -27157,7 +28543,7 @@ static ma_result ma_device_init__audio4(ma_context* pContext, const ma_device_co if (pConfig->deviceType == ma_device_type_loopback) { return MA_DEVICE_TYPE_NOT_SUPPORTED; } - + pDevice->audio4.fdCapture = -1; pDevice->audio4.fdPlayback = -1; @@ -27168,8 +28554,8 @@ static ma_result ma_device_init__audio4(ma_context* pContext, const ma_device_co */ #if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 800000000 /* NetBSD 8.0+ */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } #else @@ -27177,14 +28563,14 @@ static ma_result ma_device_init__audio4(ma_context* pContext, const ma_device_co #endif if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__audio4(pContext, pConfig, ma_device_type_capture, pDevice); + ma_result result = ma_device_init_fd__audio4(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__audio4(pContext, pConfig, ma_device_type_playback, pDevice); + ma_result result = ma_device_init_fd__audio4(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { if (pConfig->deviceType == ma_device_type_duplex) { close(pDevice->audio4.fdCapture); @@ -27196,7 +28582,6 @@ static ma_result ma_device_init__audio4(ma_context* pContext, const ma_device_co return MA_SUCCESS; } -#if 0 static ma_result ma_device_start__audio4(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -27215,7 +28600,6 @@ static ma_result ma_device_start__audio4(ma_device* pDevice) return MA_SUCCESS; } -#endif static ma_result ma_device_stop_fd__audio4(ma_device* pDevice, int fd) { @@ -27307,171 +28691,6 @@ static ma_result ma_device_read__audio4(ma_device* pDevice, void* pPCMFrames, ma return MA_SUCCESS; } -static ma_result ma_device_main_loop__audio4(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - /* No need to explicitly start the device like the other backends. */ - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__audio4(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__audio4(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__audio4()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__audio4(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[8192]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__audio4(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - - /* Here is where the device is stopped. */ - ma_device_stop__audio4(pDevice); - - return result; -} - static ma_result ma_context_uninit__audio4(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -27481,21 +28700,23 @@ static ma_result ma_context_uninit__audio4(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__audio4(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__audio4(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { MA_ASSERT(pContext != NULL); (void)pConfig; - pContext->onUninit = ma_context_uninit__audio4; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__audio4; - pContext->onEnumDevices = ma_context_enumerate_devices__audio4; - pContext->onGetDeviceInfo = ma_context_get_device_info__audio4; - pContext->onDeviceInit = ma_device_init__audio4; - pContext->onDeviceUninit = ma_device_uninit__audio4; - pContext->onDeviceStart = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceStop = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceMainLoop = ma_device_main_loop__audio4; + pCallbacks->onContextInit = ma_context_init__audio4; + pCallbacks->onContextUninit = ma_context_uninit__audio4; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__audio4; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__audio4; + pCallbacks->onDeviceInit = ma_device_init__audio4; + pCallbacks->onDeviceUninit = ma_device_uninit__audio4; + pCallbacks->onDeviceStart = ma_device_start__audio4; + pCallbacks->onDeviceStop = ma_device_stop__audio4; + pCallbacks->onDeviceRead = ma_device_read__audio4; + pCallbacks->onDeviceWrite = ma_device_write__audio4; + pCallbacks->onDeviceDataLoop = NULL; return MA_SUCCESS; } @@ -27517,6 +28738,8 @@ OSS Backend #define SNDCTL_DSP_HALT SNDCTL_DSP_RESET #endif +#define MA_OSS_DEFAULT_DEVICE_NAME "/dev/dsp" + static int ma_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. */ @@ -27544,7 +28767,7 @@ static ma_result ma_context_open_device__oss(ma_context* pContext, ma_device_typ return MA_INVALID_ARGS; } - deviceName = "/dev/dsp"; + deviceName = MA_OSS_DEFAULT_DEVICE_NAME; if (pDeviceID != NULL) { deviceName = pDeviceID->oss; } @@ -27562,16 +28785,6 @@ static ma_result ma_context_open_device__oss(ma_context* pContext, ma_device_typ return MA_SUCCESS; } -static ma_bool32 ma_context_is_device_id_equal__oss(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return ma_strcmp(pID0->oss, pID1->oss) == 0; -} - static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { int fd; @@ -27637,7 +28850,57 @@ static ma_result ma_context_enumerate_devices__oss(ma_context* pContext, ma_enum return MA_SUCCESS; } -static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static void ma_context_add_native_data_format__oss(ma_context* pContext, oss_audioinfo* pAudioInfo, ma_format format, ma_device_info* pDeviceInfo) +{ + unsigned int minChannels; + unsigned int maxChannels; + unsigned int iRate; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pAudioInfo != NULL); + MA_ASSERT(pDeviceInfo != NULL); + + /* If we support all channels we just report 0. */ + minChannels = ma_clamp(pAudioInfo->min_channels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + maxChannels = ma_clamp(pAudioInfo->max_channels, MA_MIN_CHANNELS, MA_MAX_CHANNELS); + + /* + OSS has this annoying thing where sample rates can be reported in two ways. We prefer explicitness, + which OSS has in the form of nrates/rates, however there are times where nrates can be 0, in which + case we'll need to use min_rate and max_rate and report only standard rates. + */ + if (pAudioInfo->nrates > 0) { + for (iRate = 0; iRate < pAudioInfo->nrates; iRate += 1) { + unsigned int rate = pAudioInfo->rates[iRate]; + + if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { + ma_device_info_add_native_data_format(pDeviceInfo, format, 0, rate, 0); /* Set the channel count to 0 to indicate that all channel counts are supported. */ + } else { + unsigned int iChannel; + for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { + ma_device_info_add_native_data_format(pDeviceInfo, format, iChannel, rate, 0); + } + } + } + } else { + for (iRate = 0; iRate < ma_countof(g_maStandardSampleRatePriorities); iRate += 1) { + ma_uint32 standardRate = g_maStandardSampleRatePriorities[iRate]; + + if (standardRate >= (ma_uint32)pAudioInfo->min_rate && standardRate <= (ma_uint32)pAudioInfo->max_rate) { + if (minChannels == MA_MIN_CHANNELS && maxChannels == MA_MAX_CHANNELS) { + ma_device_info_add_native_data_format(pDeviceInfo, format, 0, standardRate, 0); /* Set the channel count to 0 to indicate that all channel counts are supported. */ + } else { + unsigned int iChannel; + for (iChannel = minChannels; iChannel <= maxChannels; iChannel += 1) { + ma_device_info_add_native_data_format(pDeviceInfo, format, iChannel, standardRate, 0); + } + } + } + } + } +} + +static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_bool32 foundDevice; int fdTemp; @@ -27645,7 +28908,6 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device int result; MA_ASSERT(pContext != NULL); - (void)shareMode; /* Handle the default device a little differently. */ if (pDeviceID == NULL) { @@ -27695,11 +28957,8 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ai.name, (size_t)-1); } - pDeviceInfo->minChannels = ai.min_channels; - pDeviceInfo->maxChannels = ai.max_channels; - pDeviceInfo->minSampleRate = ai.min_rate; - pDeviceInfo->maxSampleRate = ai.max_rate; - pDeviceInfo->formatCount = 0; + + pDeviceInfo->nativeDataFormatCount = 0; if (deviceType == ma_device_type_playback) { formatMask = ai.oformats; @@ -27707,14 +28966,14 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device formatMask = ai.iformats; } - if ((formatMask & AFMT_U8) != 0) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_u8; - } if (((formatMask & AFMT_S16_LE) != 0 && ma_is_little_endian()) || (AFMT_S16_BE && ma_is_big_endian())) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_s16; + ma_context_add_native_data_format__oss(pContext, &ai, ma_format_s16, pDeviceInfo); } if (((formatMask & AFMT_S32_LE) != 0 && ma_is_little_endian()) || (AFMT_S32_BE && ma_is_big_endian())) { - pDeviceInfo->formats[pDeviceInfo->formatCount++] = ma_format_s32; + ma_context_add_native_data_format__oss(pContext, &ai, ma_format_s32, pDeviceInfo); + } + if ((formatMask & AFMT_U8) != 0) { + ma_context_add_native_data_format__oss(pContext, &ai, ma_format_u8, pDeviceInfo); } foundDevice = MA_TRUE; @@ -27738,17 +28997,19 @@ static ma_result ma_context_get_device_info__oss(ma_context* pContext, ma_device return MA_SUCCESS; } -static void ma_device_uninit__oss(ma_device* pDevice) +static ma_result ma_device_uninit__oss(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { close(pDevice->oss.fdCapture); } - + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { close(pDevice->oss.fdPlayback); } + + return MA_SUCCESS; } static int ma_format_to_oss(ma_format format) @@ -27789,7 +29050,7 @@ static ma_format ma_format_from_oss(int ossFormat) return ma_format_unknown; } -static ma_result ma_device_init_fd__oss(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +static ma_result ma_device_init_fd__oss(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { ma_result result; int ossResult; @@ -27801,28 +29062,17 @@ static ma_result ma_device_init_fd__oss(ma_context* pContext, const ma_device_co int ossSampleRate; int ossFragment; - MA_ASSERT(pContext != NULL); + MA_ASSERT(pDevice != NULL); MA_ASSERT(pConfig != NULL); MA_ASSERT(deviceType != ma_device_type_duplex); - MA_ASSERT(pDevice != NULL); + + pDeviceID = pDescriptor->pDeviceID; + shareMode = pDescriptor->shareMode; + ossFormat = ma_format_to_oss((pDescriptor->format != ma_format_unknown) ? pDescriptor->format : ma_format_s16); /* Use s16 by default because OSS doesn't like floating point. */ + ossChannels = (int)(pDescriptor->channels > 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS; + ossSampleRate = (int)(pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; - (void)pContext; - - if (deviceType == ma_device_type_capture) { - pDeviceID = pConfig->capture.pDeviceID; - shareMode = pConfig->capture.shareMode; - ossFormat = ma_format_to_oss(pConfig->capture.format); - ossChannels = (int)pConfig->capture.channels; - ossSampleRate = (int)pConfig->sampleRate; - } else { - pDeviceID = pConfig->playback.pDeviceID; - shareMode = pConfig->playback.shareMode; - ossFormat = ma_format_to_oss(pConfig->playback.format); - ossChannels = (int)pConfig->playback.channels; - ossSampleRate = (int)pConfig->sampleRate; - } - - result = ma_context_open_device__oss(pContext, deviceType, pDeviceID, shareMode, &fd); + result = ma_context_open_device__oss(pDevice->pContext, deviceType, pDeviceID, shareMode, &fd); if (result != MA_SUCCESS) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); } @@ -27860,7 +29110,7 @@ static ma_result ma_device_init_fd__oss(ma_context* pContext, const ma_device_co 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. */ @@ -27868,11 +29118,8 @@ static ma_result ma_device_init_fd__oss(ma_context* pContext, const ma_device_co ma_uint32 periodSizeInFrames; ma_uint32 periodSizeInBytes; ma_uint32 ossFragmentSizePower; - - periodSizeInFrames = pConfig->periodSizeInFrames; - if (periodSizeInFrames == 0) { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, (ma_uint32)ossSampleRate); - } + + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, (ma_uint32)ossSampleRate, pConfig->performanceProfile); periodSizeInBytes = ma_round_to_power_of_2(periodSizeInFrames * ma_get_bytes_per_frame(ma_format_from_oss(ossFormat), ossChannels)); if (periodSizeInBytes < 16) { @@ -27895,39 +29142,29 @@ static ma_result ma_device_init_fd__oss(ma_context* pContext, const ma_device_co /* Internal settings. */ if (deviceType == ma_device_type_capture) { - pDevice->oss.fdCapture = fd; - pDevice->capture.internalFormat = ma_format_from_oss(ossFormat); - pDevice->capture.internalChannels = ossChannels; - pDevice->capture.internalSampleRate = ossSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); - pDevice->capture.internalPeriods = (ma_uint32)(ossFragment >> 16); - pDevice->capture.internalPeriodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - - if (pDevice->capture.internalFormat == ma_format_unknown) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); - } + pDevice->oss.fdCapture = fd; } else { - pDevice->oss.fdPlayback = fd; - pDevice->playback.internalFormat = ma_format_from_oss(ossFormat); - pDevice->playback.internalChannels = ossChannels; - pDevice->playback.internalSampleRate = ossSampleRate; - ma_get_standard_channel_map(ma_standard_channel_map_sound4, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); - pDevice->playback.internalPeriods = (ma_uint32)(ossFragment >> 16); - pDevice->playback.internalPeriodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + pDevice->oss.fdPlayback = fd; + } - if (pDevice->playback.internalFormat == ma_format_unknown) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); - } + pDescriptor->format = ma_format_from_oss(ossFormat); + pDescriptor->channels = ossChannels; + pDescriptor->sampleRate = ossSampleRate; + ma_get_standard_channel_map(ma_standard_channel_map_sound4, pDescriptor->channels, pDescriptor->channelMap); + pDescriptor->periodCount = (ma_uint32)(ossFragment >> 16); + pDescriptor->periodSizeInFrames = (ma_uint32)(1 << (ossFragment & 0xFFFF)) / ma_get_bytes_per_frame(pDescriptor->format, pDescriptor->channels); + + if (pDescriptor->format == ma_format_unknown) { + return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] The device's internal format is not supported by miniaudio.", MA_FORMAT_NOT_SUPPORTED); } return MA_SUCCESS; } -static ma_result ma_device_init__oss(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__oss(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pConfig != NULL); MA_ASSERT(pDevice != NULL); + MA_ASSERT(pConfig != NULL); MA_ZERO_OBJECT(&pDevice->oss); @@ -27936,14 +29173,14 @@ static ma_result ma_device_init__oss(ma_context* pContext, const ma_device_confi } if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__oss(pContext, pConfig, ma_device_type_capture, pDevice); + ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - ma_result result = ma_device_init_fd__oss(pContext, pConfig, ma_device_type_playback, pDevice); + ma_result result = ma_device_init_fd__oss(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to open device.", result); } @@ -27952,36 +29189,41 @@ static ma_result ma_device_init__oss(ma_context* pContext, const ma_device_confi return MA_SUCCESS; } +/* +Note on Starting and Stopping +============================= +In the past I was using SNDCTL_DSP_HALT to stop the device, however this results in issues when +trying to resume the device again. If we use SNDCTL_DSP_HALT, the next write() or read() will +fail. Instead what we need to do is just not write or read to and from the device when the +device is not running. + +As a result, both the start and stop functions for OSS are just empty stubs. The starting and +stopping logic is handled by ma_device_write__oss() and ma_device_read__oss(). These will check +the device state, and if the device is stopped they will simply not do any kind of processing. + +The downside to this technique is that I've noticed a fairly lengthy delay in stopping the +device, up to a second. This is on a virtual machine, and as such might just be due to the +virtual drivers, but I'm not fully sure. I am not sure how to work around this problem so for +the moment that's just how it's going to have to be. + +When starting the device, OSS will automatically start it when write() or read() is called. +*/ +static ma_result ma_device_start__oss(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + /* The device is automatically started with reading and writing. */ + (void)pDevice; + + return MA_SUCCESS; +} + static ma_result ma_device_stop__oss(ma_device* pDevice) { MA_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... - */ - - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - int result = ioctl(pDevice->oss.fdCapture, SNDCTL_DSP_HALT, 0); - if (result == -1) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to stop device. SNDCTL_DSP_HALT failed.", ma_result_from_errno(errno)); - } - } - - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - int result = ioctl(pDevice->oss.fdPlayback, SNDCTL_DSP_HALT, 0); - if (result == -1) { - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to stop device. SNDCTL_DSP_HALT failed.", ma_result_from_errno(errno)); - } - } + /* See note above on why this is empty. */ + (void)pDevice; return MA_SUCCESS; } @@ -27989,11 +29231,18 @@ static ma_result ma_device_stop__oss(ma_device* pDevice) static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesWritten) { int resultOSS; + ma_uint32 deviceState; if (pFramesWritten != NULL) { *pFramesWritten = 0; } + /* Don't do any processing if the device is stopped. */ + deviceState = ma_device_get_state(pDevice); + if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + return MA_SUCCESS; + } + resultOSS = write(pDevice->oss.fdPlayback, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels)); if (resultOSS < 0) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to send data from the client to the device.", ma_result_from_errno(errno)); @@ -28002,23 +29251,30 @@ static ma_result ma_device_write__oss(ma_device* pDevice, const void* pPCMFrames if (pFramesWritten != NULL) { *pFramesWritten = (ma_uint32)resultOSS / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); } - + return MA_SUCCESS; } static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_uint32 frameCount, ma_uint32* pFramesRead) { int resultOSS; + ma_uint32 deviceState; if (pFramesRead != NULL) { *pFramesRead = 0; } + /* Don't do any processing if the device is stopped. */ + deviceState = ma_device_get_state(pDevice); + if (deviceState != MA_STATE_STARTED && deviceState != MA_STATE_STARTING) { + return MA_SUCCESS; + } + resultOSS = read(pDevice->oss.fdCapture, pPCMFrames, frameCount * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); if (resultOSS < 0) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OSS] Failed to read data from the device to be sent to the client.", ma_result_from_errno(errno)); } - + if (pFramesRead != NULL) { *pFramesRead = (ma_uint32)resultOSS / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); } @@ -28026,171 +29282,6 @@ static ma_result ma_device_read__oss(ma_device* pDevice, void* pPCMFrames, ma_ui return MA_SUCCESS; } -static ma_result ma_device_main_loop__oss(ma_device* pDevice) -{ - ma_result result = MA_SUCCESS; - ma_bool32 exitLoop = MA_FALSE; - - /* No need to explicitly start the device like the other backends. */ - - while (ma_device__get_state(pDevice) == MA_STATE_STARTED && !exitLoop) { - switch (pDevice->type) - { - case ma_device_type_duplex: - { - /* The process is: device_read -> convert -> callback -> convert -> device_write */ - ma_uint32 totalCapturedDeviceFramesProcessed = 0; - ma_uint32 capturedDevicePeriodSizeInFrames = ma_min(pDevice->capture.internalPeriodSizeInFrames, pDevice->playback.internalPeriodSizeInFrames); - - while (totalCapturedDeviceFramesProcessed < capturedDevicePeriodSizeInFrames) { - ma_uint8 capturedDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackDeviceData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedDeviceDataCapInFrames = sizeof(capturedDeviceData) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 playbackDeviceDataCapInFrames = sizeof(playbackDeviceData) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 capturedDeviceFramesRemaining; - ma_uint32 capturedDeviceFramesProcessed; - ma_uint32 capturedDeviceFramesToProcess; - ma_uint32 capturedDeviceFramesToTryProcessing = capturedDevicePeriodSizeInFrames - totalCapturedDeviceFramesProcessed; - if (capturedDeviceFramesToTryProcessing > capturedDeviceDataCapInFrames) { - capturedDeviceFramesToTryProcessing = capturedDeviceDataCapInFrames; - } - - result = ma_device_read__oss(pDevice, capturedDeviceData, capturedDeviceFramesToTryProcessing, &capturedDeviceFramesToProcess); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedDeviceFramesRemaining = capturedDeviceFramesToProcess; - capturedDeviceFramesProcessed = 0; - - for (;;) { - ma_uint8 capturedClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint8 playbackClientData[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 capturedClientDataCapInFrames = sizeof(capturedClientData) / ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels); - ma_uint32 playbackClientDataCapInFrames = sizeof(playbackClientData) / ma_get_bytes_per_frame(pDevice->playback.format, pDevice->playback.channels); - ma_uint64 capturedClientFramesToProcessThisIteration = ma_min(capturedClientDataCapInFrames, playbackClientDataCapInFrames); - ma_uint64 capturedDeviceFramesToProcessThisIteration = capturedDeviceFramesRemaining; - ma_uint8* pRunningCapturedDeviceFrames = ma_offset_ptr(capturedDeviceData, capturedDeviceFramesProcessed * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels)); - - /* Convert capture data from device format to client format. */ - result = ma_data_converter_process_pcm_frames(&pDevice->capture.converter, pRunningCapturedDeviceFrames, &capturedDeviceFramesToProcessThisIteration, capturedClientData, &capturedClientFramesToProcessThisIteration); - if (result != MA_SUCCESS) { - break; - } - - /* - If we weren't able to generate any output frames it must mean we've exhaused all of our input. The only time this would not be the case is if capturedClientData was too small - which should never be the case when it's of the size MA_DATA_CONVERTER_STACK_BUFFER_SIZE. - */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - - ma_device__on_data(pDevice, playbackClientData, capturedClientData, (ma_uint32)capturedClientFramesToProcessThisIteration); /* Safe cast .*/ - - capturedDeviceFramesProcessed += (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - capturedDeviceFramesRemaining -= (ma_uint32)capturedDeviceFramesToProcessThisIteration; /* Safe cast. */ - - /* At this point the playbackClientData buffer should be holding data that needs to be written to the device. */ - for (;;) { - ma_uint64 convertedClientFrameCount = capturedClientFramesToProcessThisIteration; - ma_uint64 convertedDeviceFrameCount = playbackDeviceDataCapInFrames; - result = ma_data_converter_process_pcm_frames(&pDevice->playback.converter, playbackClientData, &convertedClientFrameCount, playbackDeviceData, &convertedDeviceFrameCount); - if (result != MA_SUCCESS) { - break; - } - - result = ma_device_write__oss(pDevice, playbackDeviceData, (ma_uint32)convertedDeviceFrameCount, NULL); /* Safe cast. */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - capturedClientFramesToProcessThisIteration -= (ma_uint32)convertedClientFrameCount; /* Safe cast. */ - if (capturedClientFramesToProcessThisIteration == 0) { - break; - } - } - - /* In case an error happened from ma_device_write__oss()... */ - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - } - - totalCapturedDeviceFramesProcessed += capturedDeviceFramesProcessed; - } - } break; - - case ma_device_type_capture: - { - /* We read in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->capture.internalPeriodSizeInFrames; - ma_uint32 framesReadThisPeriod = 0; - while (framesReadThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesReadThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToReadThisIteration = framesRemainingInPeriod; - if (framesToReadThisIteration > intermediaryBufferSizeInFrames) { - framesToReadThisIteration = intermediaryBufferSizeInFrames; - } - - result = ma_device_read__oss(pDevice, intermediaryBuffer, framesToReadThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - ma_device__send_frames_to_client(pDevice, framesProcessed, intermediaryBuffer); - - framesReadThisPeriod += framesProcessed; - } - } break; - - case ma_device_type_playback: - { - /* We write in chunks of the period size, but use a stack allocated buffer for the intermediary. */ - ma_uint8 intermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; - ma_uint32 intermediaryBufferSizeInFrames = sizeof(intermediaryBuffer) / ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); - ma_uint32 periodSizeInFrames = pDevice->playback.internalPeriodSizeInFrames; - ma_uint32 framesWrittenThisPeriod = 0; - while (framesWrittenThisPeriod < periodSizeInFrames) { - ma_uint32 framesRemainingInPeriod = periodSizeInFrames - framesWrittenThisPeriod; - ma_uint32 framesProcessed; - ma_uint32 framesToWriteThisIteration = framesRemainingInPeriod; - if (framesToWriteThisIteration > intermediaryBufferSizeInFrames) { - framesToWriteThisIteration = intermediaryBufferSizeInFrames; - } - - ma_device__read_frames_from_client(pDevice, framesToWriteThisIteration, intermediaryBuffer); - - result = ma_device_write__oss(pDevice, intermediaryBuffer, framesToWriteThisIteration, &framesProcessed); - if (result != MA_SUCCESS) { - exitLoop = MA_TRUE; - break; - } - - framesWrittenThisPeriod += framesProcessed; - } - } break; - - /* To silence a warning. Will never hit this. */ - case ma_device_type_loopback: - default: break; - } - } - - - /* Here is where the device is stopped. */ - ma_device_stop__oss(pDevice); - - return result; -} - static ma_result ma_context_uninit__oss(ma_context* pContext) { MA_ASSERT(pContext != NULL); @@ -28200,7 +29291,7 @@ static ma_result ma_context_uninit__oss(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__oss(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__oss(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { int fd; int ossVersion; @@ -28224,20 +29315,24 @@ static ma_result ma_context_init__oss(const ma_context_config* pConfig, ma_conte return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "[OSS] Failed to retrieve OSS version.", MA_NO_BACKEND); } + /* The file handle to temp device is no longer needed. Close ASAP. */ + close(fd); + pContext->oss.versionMajor = ((ossVersion & 0xFF0000) >> 16); pContext->oss.versionMinor = ((ossVersion & 0x00FF00) >> 8); - pContext->onUninit = ma_context_uninit__oss; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__oss; - pContext->onEnumDevices = ma_context_enumerate_devices__oss; - pContext->onGetDeviceInfo = ma_context_get_device_info__oss; - pContext->onDeviceInit = ma_device_init__oss; - pContext->onDeviceUninit = ma_device_uninit__oss; - pContext->onDeviceStart = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceStop = NULL; /* Not required for synchronous backends. */ - pContext->onDeviceMainLoop = ma_device_main_loop__oss; + pCallbacks->onContextInit = ma_context_init__oss; + pCallbacks->onContextUninit = ma_context_uninit__oss; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__oss; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__oss; + pCallbacks->onDeviceInit = ma_device_init__oss; + pCallbacks->onDeviceUninit = ma_device_uninit__oss; + pCallbacks->onDeviceStart = ma_device_start__oss; + pCallbacks->onDeviceStop = ma_device_stop__oss; + pCallbacks->onDeviceRead = ma_device_read__oss; + pCallbacks->onDeviceWrite = ma_device_write__oss; + pCallbacks->onDeviceDataLoop = NULL; - close(fd); return MA_SUCCESS; } #endif /* OSS */ @@ -28249,61 +29344,96 @@ AAudio Backend ******************************************************************************/ #ifdef MA_HAS_AAUDIO + /*#include */ -#define MA_AAUDIO_UNSPECIFIED 0 +typedef int32_t ma_aaudio_result_t; +typedef int32_t ma_aaudio_direction_t; +typedef int32_t ma_aaudio_sharing_mode_t; +typedef int32_t ma_aaudio_format_t; +typedef int32_t ma_aaudio_stream_state_t; +typedef int32_t ma_aaudio_performance_mode_t; +typedef int32_t ma_aaudio_usage_t; +typedef int32_t ma_aaudio_content_type_t; +typedef int32_t ma_aaudio_input_preset_t; +typedef int32_t ma_aaudio_data_callback_result_t; +typedef struct ma_AAudioStreamBuilder_t* ma_AAudioStreamBuilder; +typedef struct ma_AAudioStream_t* ma_AAudioStream; -typedef int32_t ma_aaudio_result_t; -typedef int32_t ma_aaudio_direction_t; -typedef int32_t ma_aaudio_sharing_mode_t; -typedef int32_t ma_aaudio_format_t; -typedef int32_t ma_aaudio_stream_state_t; -typedef int32_t ma_aaudio_performance_mode_t; -typedef int32_t ma_aaudio_data_callback_result_t; +#define MA_AAUDIO_UNSPECIFIED 0 /* Result codes. miniaudio only cares about the success code. */ -#define MA_AAUDIO_OK 0 +#define MA_AAUDIO_OK 0 /* Directions. */ -#define MA_AAUDIO_DIRECTION_OUTPUT 0 -#define MA_AAUDIO_DIRECTION_INPUT 1 +#define MA_AAUDIO_DIRECTION_OUTPUT 0 +#define MA_AAUDIO_DIRECTION_INPUT 1 /* Sharing modes. */ -#define MA_AAUDIO_SHARING_MODE_EXCLUSIVE 0 -#define MA_AAUDIO_SHARING_MODE_SHARED 1 +#define MA_AAUDIO_SHARING_MODE_EXCLUSIVE 0 +#define MA_AAUDIO_SHARING_MODE_SHARED 1 /* Formats. */ -#define MA_AAUDIO_FORMAT_PCM_I16 1 -#define MA_AAUDIO_FORMAT_PCM_FLOAT 2 +#define MA_AAUDIO_FORMAT_PCM_I16 1 +#define MA_AAUDIO_FORMAT_PCM_FLOAT 2 /* Stream states. */ -#define MA_AAUDIO_STREAM_STATE_UNINITIALIZED 0 -#define MA_AAUDIO_STREAM_STATE_UNKNOWN 1 -#define MA_AAUDIO_STREAM_STATE_OPEN 2 -#define MA_AAUDIO_STREAM_STATE_STARTING 3 -#define MA_AAUDIO_STREAM_STATE_STARTED 4 -#define MA_AAUDIO_STREAM_STATE_PAUSING 5 -#define MA_AAUDIO_STREAM_STATE_PAUSED 6 -#define MA_AAUDIO_STREAM_STATE_FLUSHING 7 -#define MA_AAUDIO_STREAM_STATE_FLUSHED 8 -#define MA_AAUDIO_STREAM_STATE_STOPPING 9 -#define MA_AAUDIO_STREAM_STATE_STOPPED 10 -#define MA_AAUDIO_STREAM_STATE_CLOSING 11 -#define MA_AAUDIO_STREAM_STATE_CLOSED 12 -#define MA_AAUDIO_STREAM_STATE_DISCONNECTED 13 +#define MA_AAUDIO_STREAM_STATE_UNINITIALIZED 0 +#define MA_AAUDIO_STREAM_STATE_UNKNOWN 1 +#define MA_AAUDIO_STREAM_STATE_OPEN 2 +#define MA_AAUDIO_STREAM_STATE_STARTING 3 +#define MA_AAUDIO_STREAM_STATE_STARTED 4 +#define MA_AAUDIO_STREAM_STATE_PAUSING 5 +#define MA_AAUDIO_STREAM_STATE_PAUSED 6 +#define MA_AAUDIO_STREAM_STATE_FLUSHING 7 +#define MA_AAUDIO_STREAM_STATE_FLUSHED 8 +#define MA_AAUDIO_STREAM_STATE_STOPPING 9 +#define MA_AAUDIO_STREAM_STATE_STOPPED 10 +#define MA_AAUDIO_STREAM_STATE_CLOSING 11 +#define MA_AAUDIO_STREAM_STATE_CLOSED 12 +#define MA_AAUDIO_STREAM_STATE_DISCONNECTED 13 /* Performance modes. */ -#define MA_AAUDIO_PERFORMANCE_MODE_NONE 10 -#define MA_AAUDIO_PERFORMANCE_MODE_POWER_SAVING 11 -#define MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 12 +#define MA_AAUDIO_PERFORMANCE_MODE_NONE 10 +#define MA_AAUDIO_PERFORMANCE_MODE_POWER_SAVING 11 +#define MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 12 + +/* Usage types. */ +#define MA_AAUDIO_USAGE_MEDIA 1 +#define MA_AAUDIO_USAGE_VOICE_COMMUNICATION 2 +#define MA_AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING 3 +#define MA_AAUDIO_USAGE_ALARM 4 +#define MA_AAUDIO_USAGE_NOTIFICATION 5 +#define MA_AAUDIO_USAGE_NOTIFICATION_RINGTONE 6 +#define MA_AAUDIO_USAGE_NOTIFICATION_EVENT 10 +#define MA_AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY 11 +#define MA_AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE 12 +#define MA_AAUDIO_USAGE_ASSISTANCE_SONIFICATION 13 +#define MA_AAUDIO_USAGE_GAME 14 +#define MA_AAUDIO_USAGE_ASSISTANT 16 +#define MA_AAUDIO_SYSTEM_USAGE_EMERGENCY 1000 +#define MA_AAUDIO_SYSTEM_USAGE_SAFETY 1001 +#define MA_AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS 1002 +#define MA_AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT 1003 + +/* Content types. */ +#define MA_AAUDIO_CONTENT_TYPE_SPEECH 1 +#define MA_AAUDIO_CONTENT_TYPE_MUSIC 2 +#define MA_AAUDIO_CONTENT_TYPE_MOVIE 3 +#define MA_AAUDIO_CONTENT_TYPE_SONIFICATION 4 + +/* Input presets. */ +#define MA_AAUDIO_INPUT_PRESET_GENERIC 1 +#define MA_AAUDIO_INPUT_PRESET_CAMCORDER 5 +#define MA_AAUDIO_INPUT_PRESET_VOICE_RECOGNITION 6 +#define MA_AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION 7 +#define MA_AAUDIO_INPUT_PRESET_UNPROCESSED 9 +#define MA_AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE 10 /* Callback results. */ -#define MA_AAUDIO_CALLBACK_RESULT_CONTINUE 0 -#define MA_AAUDIO_CALLBACK_RESULT_STOP 1 +#define MA_AAUDIO_CALLBACK_RESULT_CONTINUE 0 +#define MA_AAUDIO_CALLBACK_RESULT_STOP 1 -/* Objects. */ -typedef struct ma_AAudioStreamBuilder_t* ma_AAudioStreamBuilder; -typedef struct ma_AAudioStream_t* ma_AAudioStream; typedef ma_aaudio_data_callback_result_t (* ma_AAudioStream_dataCallback) (ma_AAudioStream* pStream, void* pUserData, void* pAudioData, int32_t numFrames); typedef void (* ma_AAudioStream_errorCallback)(ma_AAudioStream *pStream, void *pUserData, ma_aaudio_result_t error); @@ -28321,6 +29451,9 @@ typedef void (* MA_PFN_AAudioStreamBuilder_setFramesPerDataC typedef void (* MA_PFN_AAudioStreamBuilder_setDataCallback) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_dataCallback callback, void* pUserData); typedef void (* MA_PFN_AAudioStreamBuilder_setErrorCallback) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream_errorCallback callback, void* pUserData); typedef void (* MA_PFN_AAudioStreamBuilder_setPerformanceMode) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_performance_mode_t mode); +typedef void (* MA_PFN_AAudioStreamBuilder_setUsage) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_usage_t contentType); +typedef void (* MA_PFN_AAudioStreamBuilder_setContentType) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_content_type_t contentType); +typedef void (* MA_PFN_AAudioStreamBuilder_setInputPreset) (ma_AAudioStreamBuilder* pBuilder, ma_aaudio_input_preset_t inputPreset); typedef ma_aaudio_result_t (* MA_PFN_AAudioStreamBuilder_openStream) (ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream** ppStream); typedef ma_aaudio_result_t (* MA_PFN_AAudioStream_close) (ma_AAudioStream* pStream); typedef ma_aaudio_stream_state_t (* MA_PFN_AAudioStream_getState) (ma_AAudioStream* pStream); @@ -28345,6 +29478,59 @@ static ma_result ma_result_from_aaudio(ma_aaudio_result_t resultAA) return MA_ERROR; } +static ma_aaudio_usage_t ma_to_usage__aaudio(ma_aaudio_usage usage) +{ + switch (usage) { + case ma_aaudio_usage_announcement: return MA_AAUDIO_USAGE_MEDIA; + case ma_aaudio_usage_emergency: return MA_AAUDIO_USAGE_VOICE_COMMUNICATION; + case ma_aaudio_usage_safety: return MA_AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING; + case ma_aaudio_usage_vehicle_status: return MA_AAUDIO_USAGE_ALARM; + case ma_aaudio_usage_alarm: return MA_AAUDIO_USAGE_NOTIFICATION; + case ma_aaudio_usage_assistance_accessibility: return MA_AAUDIO_USAGE_NOTIFICATION_RINGTONE; + case ma_aaudio_usage_assistance_navigation_guidance: return MA_AAUDIO_USAGE_NOTIFICATION_EVENT; + case ma_aaudio_usage_assistance_sonification: return MA_AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY; + case ma_aaudio_usage_assitant: return MA_AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; + case ma_aaudio_usage_game: return MA_AAUDIO_USAGE_ASSISTANCE_SONIFICATION; + case ma_aaudio_usage_media: return MA_AAUDIO_USAGE_GAME; + case ma_aaudio_usage_notification: return MA_AAUDIO_USAGE_ASSISTANT; + case ma_aaudio_usage_notification_event: return MA_AAUDIO_SYSTEM_USAGE_EMERGENCY; + case ma_aaudio_usage_notification_ringtone: return MA_AAUDIO_SYSTEM_USAGE_SAFETY; + case ma_aaudio_usage_voice_communication: return MA_AAUDIO_SYSTEM_USAGE_VEHICLE_STATUS; + case ma_aaudio_usage_voice_communication_signalling: return MA_AAUDIO_SYSTEM_USAGE_ANNOUNCEMENT; + default: break; + } + + return MA_AAUDIO_USAGE_MEDIA; +} + +static ma_aaudio_content_type_t ma_to_content_type__aaudio(ma_aaudio_content_type contentType) +{ + switch (contentType) { + case ma_aaudio_content_type_movie: return MA_AAUDIO_CONTENT_TYPE_MOVIE; + case ma_aaudio_content_type_music: return MA_AAUDIO_CONTENT_TYPE_MUSIC; + case ma_aaudio_content_type_sonification: return MA_AAUDIO_CONTENT_TYPE_SONIFICATION; + case ma_aaudio_content_type_speech: return MA_AAUDIO_CONTENT_TYPE_SPEECH; + default: break; + } + + return MA_AAUDIO_CONTENT_TYPE_SPEECH; +} + +static ma_aaudio_input_preset_t ma_to_input_preset__aaudio(ma_aaudio_input_preset inputPreset) +{ + switch (inputPreset) { + case ma_aaudio_input_preset_generic: return MA_AAUDIO_INPUT_PRESET_GENERIC; + case ma_aaudio_input_preset_camcorder: return MA_AAUDIO_INPUT_PRESET_CAMCORDER; + case ma_aaudio_input_preset_unprocessed: return MA_AAUDIO_INPUT_PRESET_UNPROCESSED; + case ma_aaudio_input_preset_voice_recognition: return MA_AAUDIO_INPUT_PRESET_VOICE_RECOGNITION; + case ma_aaudio_input_preset_voice_communication: return MA_AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION; + case ma_aaudio_input_preset_voice_performance: return MA_AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE; + default: break; + } + + return MA_AAUDIO_INPUT_PRESET_GENERIC; +} + static void ma_stream_error_callback__aaudio(ma_AAudioStream* pStream, void* pUserData, ma_aaudio_result_t error) { ma_device* pDevice = (ma_device*)pUserData; @@ -28372,11 +29558,7 @@ static ma_aaudio_data_callback_result_t ma_stream_data_callback_capture__aaudio( ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, frameCount, pAudioData, &pDevice->aaudio.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, frameCount, pAudioData); /* Send directly to the client. */ - } + ma_device_handle_backend_data_callback(pDevice, NULL, pAudioData, frameCount); (void)pStream; return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; @@ -28387,24 +29569,20 @@ static ma_aaudio_data_callback_result_t ma_stream_data_callback_playback__aaudio ma_device* pDevice = (ma_device*)pUserData; MA_ASSERT(pDevice != NULL); - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_playback(pDevice, frameCount, pAudioData, &pDevice->aaudio.duplexRB); - } else { - ma_device__read_frames_from_client(pDevice, frameCount, pAudioData); /* Read directly from the client. */ - } + ma_device_handle_backend_data_callback(pDevice, pAudioData, NULL, frameCount); (void)pStream; return MA_AAUDIO_CALLBACK_RESULT_CONTINUE; } -static ma_result ma_open_stream__aaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, const ma_device_config* pConfig, const ma_device* pDevice, ma_AAudioStream** ppStream) +static ma_result ma_create_and_configure_AAudioStreamBuilder__aaudio(ma_context* pContext, const ma_device_id* pDeviceID, ma_device_type deviceType, ma_share_mode shareMode, const ma_device_descriptor* pDescriptor, const ma_device_config* pConfig, ma_device* pDevice, ma_AAudioStreamBuilder** ppBuilder) { ma_AAudioStreamBuilder* pBuilder; ma_aaudio_result_t resultAA; + ma_uint32 bufferCapacityInFrames; - MA_ASSERT(deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ - - *ppStream = NULL; + /* Safety. */ + *ppBuilder = NULL; resultAA = ((MA_PFN_AAudio_createStreamBuilder)pContext->aaudio.AAudio_createStreamBuilder)(&pBuilder); if (resultAA != MA_AAUDIO_OK) { @@ -28418,60 +29596,118 @@ static ma_result ma_open_stream__aaudio(ma_context* pContext, ma_device_type dev ((MA_PFN_AAudioStreamBuilder_setDirection)pContext->aaudio.AAudioStreamBuilder_setDirection)(pBuilder, (deviceType == ma_device_type_playback) ? MA_AAUDIO_DIRECTION_OUTPUT : MA_AAUDIO_DIRECTION_INPUT); ((MA_PFN_AAudioStreamBuilder_setSharingMode)pContext->aaudio.AAudioStreamBuilder_setSharingMode)(pBuilder, (shareMode == ma_share_mode_shared) ? MA_AAUDIO_SHARING_MODE_SHARED : MA_AAUDIO_SHARING_MODE_EXCLUSIVE); - if (pConfig != NULL) { - ma_uint32 bufferCapacityInFrames; - if (pDevice == NULL || !pDevice->usingDefaultSampleRate) { - ((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pConfig->sampleRate); + /* If we have a device descriptor make sure we configure the stream builder to take our requested parameters. */ + if (pDescriptor != NULL) { + MA_ASSERT(pConfig != NULL); /* We must have a device config if we also have a descriptor. The config is required for AAudio specific configuration options. */ + + if (pDescriptor->sampleRate != 0) { + ((MA_PFN_AAudioStreamBuilder_setSampleRate)pContext->aaudio.AAudioStreamBuilder_setSampleRate)(pBuilder, pDescriptor->sampleRate); } if (deviceType == ma_device_type_capture) { - if (pDevice == NULL || !pDevice->capture.usingDefaultChannels) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pConfig->capture.channels); + if (pDescriptor->channels != 0) { + ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); } - if (pDevice == NULL || !pDevice->capture.usingDefaultFormat) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pConfig->capture.format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); + if (pDescriptor->format != ma_format_unknown) { + ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); } } else { - if (pDevice == NULL || !pDevice->playback.usingDefaultChannels) { - ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pConfig->playback.channels); + if (pDescriptor->channels != 0) { + ((MA_PFN_AAudioStreamBuilder_setChannelCount)pContext->aaudio.AAudioStreamBuilder_setChannelCount)(pBuilder, pDescriptor->channels); } - if (pDevice == NULL || !pDevice->playback.usingDefaultFormat) { - ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pConfig->playback.format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); + if (pDescriptor->format != ma_format_unknown) { + ((MA_PFN_AAudioStreamBuilder_setFormat)pContext->aaudio.AAudioStreamBuilder_setFormat)(pBuilder, (pDescriptor->format == ma_format_s16) ? MA_AAUDIO_FORMAT_PCM_I16 : MA_AAUDIO_FORMAT_PCM_FLOAT); } } - bufferCapacityInFrames = pConfig->periodSizeInFrames * pConfig->periods; - if (bufferCapacityInFrames == 0) { - bufferCapacityInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, pConfig->sampleRate) * pConfig->periods; - } + /* + AAudio is annoying when it comes to it's buffer calculation stuff because it doesn't let you + retrieve the actual sample rate until after you've opened the stream. But you need to configure + the buffer capacity before you open the stream... :/ + + To solve, we're just going to assume MA_DEFAULT_SAMPLE_RATE (48000) and move on. + */ + bufferCapacityInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile) * pDescriptor->periodCount; ((MA_PFN_AAudioStreamBuilder_setBufferCapacityInFrames)pContext->aaudio.AAudioStreamBuilder_setBufferCapacityInFrames)(pBuilder, bufferCapacityInFrames); - ((MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback)pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback)(pBuilder, bufferCapacityInFrames / pConfig->periods); + ((MA_PFN_AAudioStreamBuilder_setFramesPerDataCallback)pContext->aaudio.AAudioStreamBuilder_setFramesPerDataCallback)(pBuilder, bufferCapacityInFrames / pDescriptor->periodCount); if (deviceType == ma_device_type_capture) { + if (pConfig->aaudio.inputPreset != ma_aaudio_input_preset_default && pContext->aaudio.AAudioStreamBuilder_setInputPreset != NULL) { + ((MA_PFN_AAudioStreamBuilder_setInputPreset)pContext->aaudio.AAudioStreamBuilder_setInputPreset)(pBuilder, ma_to_input_preset__aaudio(pConfig->aaudio.inputPreset)); + } + ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_capture__aaudio, (void*)pDevice); } else { + if (pConfig->aaudio.usage != ma_aaudio_usage_default && pContext->aaudio.AAudioStreamBuilder_setUsage != NULL) { + ((MA_PFN_AAudioStreamBuilder_setUsage)pContext->aaudio.AAudioStreamBuilder_setUsage)(pBuilder, ma_to_usage__aaudio(pConfig->aaudio.usage)); + } + + if (pConfig->aaudio.contentType != ma_aaudio_content_type_default && pContext->aaudio.AAudioStreamBuilder_setContentType != NULL) { + ((MA_PFN_AAudioStreamBuilder_setContentType)pContext->aaudio.AAudioStreamBuilder_setContentType)(pBuilder, ma_to_content_type__aaudio(pConfig->aaudio.contentType)); + } + ((MA_PFN_AAudioStreamBuilder_setDataCallback)pContext->aaudio.AAudioStreamBuilder_setDataCallback)(pBuilder, ma_stream_data_callback_playback__aaudio, (void*)pDevice); } /* Not sure how this affects things, but since there's a mapping between miniaudio's performance profiles and AAudio's performance modes, let go ahead and set it. */ ((MA_PFN_AAudioStreamBuilder_setPerformanceMode)pContext->aaudio.AAudioStreamBuilder_setPerformanceMode)(pBuilder, (pConfig->performanceProfile == ma_performance_profile_low_latency) ? MA_AAUDIO_PERFORMANCE_MODE_LOW_LATENCY : MA_AAUDIO_PERFORMANCE_MODE_NONE); + + /* We need to set an error callback to detect device changes. */ + if (pDevice != NULL) { /* <-- pDevice should never be null if pDescriptor is not null, which is always the case if we hit this branch. Check anyway for safety. */ + ((MA_PFN_AAudioStreamBuilder_setErrorCallback)pContext->aaudio.AAudioStreamBuilder_setErrorCallback)(pBuilder, ma_stream_error_callback__aaudio, (void*)pDevice); + } } - ((MA_PFN_AAudioStreamBuilder_setErrorCallback)pContext->aaudio.AAudioStreamBuilder_setErrorCallback)(pBuilder, ma_stream_error_callback__aaudio, (void*)pDevice); + *ppBuilder = pBuilder; - resultAA = ((MA_PFN_AAudioStreamBuilder_openStream)pContext->aaudio.AAudioStreamBuilder_openStream)(pBuilder, ppStream); - if (resultAA != MA_AAUDIO_OK) { - *ppStream = NULL; - ((MA_PFN_AAudioStreamBuilder_delete)pContext->aaudio.AAudioStreamBuilder_delete)(pBuilder); - return ma_result_from_aaudio(resultAA); - } - - ((MA_PFN_AAudioStreamBuilder_delete)pContext->aaudio.AAudioStreamBuilder_delete)(pBuilder); return MA_SUCCESS; } +static ma_result ma_open_stream_and_close_builder__aaudio(ma_context* pContext, ma_AAudioStreamBuilder* pBuilder, ma_AAudioStream** ppStream) +{ + ma_result result; + + result = ma_result_from_aaudio(((MA_PFN_AAudioStreamBuilder_openStream)pContext->aaudio.AAudioStreamBuilder_openStream)(pBuilder, ppStream)); + ((MA_PFN_AAudioStreamBuilder_delete)pContext->aaudio.AAudioStreamBuilder_delete)(pBuilder); + + return result; +} + +static ma_result ma_open_stream_basic__aaudio(ma_context* pContext, const ma_device_id* pDeviceID, ma_device_type deviceType, ma_share_mode shareMode, ma_AAudioStream** ppStream) +{ + ma_result result; + ma_AAudioStreamBuilder* pBuilder; + + *ppStream = NULL; + + result = ma_create_and_configure_AAudioStreamBuilder__aaudio(pContext, pDeviceID, deviceType, shareMode, NULL, NULL, NULL, &pBuilder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_open_stream_and_close_builder__aaudio(pContext, pBuilder, ppStream); +} + +static ma_result ma_open_stream__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_type deviceType, const ma_device_descriptor* pDescriptor, ma_AAudioStream** ppStream) +{ + ma_result result; + ma_AAudioStreamBuilder* pBuilder; + + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pConfig->deviceType != ma_device_type_duplex); /* This function should not be called for a full-duplex device type. */ + + *ppStream = NULL; + + result = ma_create_and_configure_AAudioStreamBuilder__aaudio(pDevice->pContext, pDescriptor->pDeviceID, deviceType, pDescriptor->shareMode, pDescriptor, pConfig, pDevice, &pBuilder); + if (result != MA_SUCCESS) { + return result; + } + + return ma_open_stream_and_close_builder__aaudio(pDevice->pContext, pBuilder, ppStream); +} + static ma_result ma_close_stream__aaudio(ma_context* pContext, ma_AAudioStream* pStream) { return ma_result_from_aaudio(((MA_PFN_AAudioStream_close)pContext->aaudio.AAudioStream_close)(pStream)); @@ -28481,7 +29717,7 @@ static ma_bool32 ma_has_default_device__aaudio(ma_context* pContext, ma_device_t { /* The only way to know this is to try creating a stream. */ ma_AAudioStream* pStream; - ma_result result = ma_open_stream__aaudio(pContext, deviceType, NULL, ma_share_mode_shared, NULL, NULL, &pStream); + ma_result result = ma_open_stream_basic__aaudio(pContext, NULL, deviceType, ma_share_mode_shared, &pStream); if (result != MA_SUCCESS) { return MA_FALSE; } @@ -28506,16 +29742,6 @@ static ma_result ma_wait_for_simple_state_transition__aaudio(ma_context* pContex } -static ma_bool32 ma_context_is_device_id_equal__aaudio(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return pID0->aaudio == pID1->aaudio; -} - static ma_result ma_context_enumerate_devices__aaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { ma_bool32 cbResult = MA_TRUE; @@ -28552,25 +29778,40 @@ static ma_result ma_context_enumerate_devices__aaudio(ma_context* pContext, ma_e return MA_SUCCESS; } -static ma_result ma_context_get_device_info__aaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static void ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_format format, ma_uint32 flags, ma_device_info* pDeviceInfo) +{ + MA_ASSERT(pContext != NULL); + MA_ASSERT(pStream != NULL); + MA_ASSERT(pDeviceInfo != NULL); + + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = ((MA_PFN_AAudioStream_getChannelCount)pContext->aaudio.AAudioStream_getChannelCount)(pStream); + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = ((MA_PFN_AAudioStream_getSampleRate)pContext->aaudio.AAudioStream_getSampleRate)(pStream); + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = flags; + pDeviceInfo->nativeDataFormatCount += 1; +} + +static void ma_context_add_native_data_format_from_AAudioStream__aaudio(ma_context* pContext, ma_AAudioStream* pStream, ma_uint32 flags, ma_device_info* pDeviceInfo) +{ + /* AAudio supports s16 and f32. */ + ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(pContext, pStream, ma_format_f32, flags, pDeviceInfo); + ma_context_add_native_data_format_from_AAudioStream_ex__aaudio(pContext, pStream, ma_format_s16, flags, pDeviceInfo); +} + +static ma_result ma_context_get_device_info__aaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { ma_AAudioStream* pStream; ma_result result; MA_ASSERT(pContext != NULL); - /* No exclusive mode with AAudio. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - /* ID */ if (pDeviceID != NULL) { pDeviceInfo->id.aaudio = pDeviceID->aaudio; } else { pDeviceInfo->id.aaudio = MA_AAUDIO_UNSPECIFIED; } - + /* Name */ if (deviceType == ma_device_type_playback) { ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); @@ -28579,31 +29820,24 @@ static ma_result ma_context_get_device_info__aaudio(ma_context* pContext, ma_dev } + pDeviceInfo->nativeDataFormatCount = 0; + /* We'll need to open the device to get accurate sample rate and channel count information. */ - result = ma_open_stream__aaudio(pContext, deviceType, pDeviceID, shareMode, NULL, NULL, &pStream); + result = ma_open_stream_basic__aaudio(pContext, pDeviceID, deviceType, ma_share_mode_shared, &pStream); if (result != MA_SUCCESS) { return result; } - pDeviceInfo->minChannels = ((MA_PFN_AAudioStream_getChannelCount)pContext->aaudio.AAudioStream_getChannelCount)(pStream); - pDeviceInfo->maxChannels = pDeviceInfo->minChannels; - pDeviceInfo->minSampleRate = ((MA_PFN_AAudioStream_getSampleRate)pContext->aaudio.AAudioStream_getSampleRate)(pStream); - pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; + ma_context_add_native_data_format_from_AAudioStream__aaudio(pContext, pStream, 0, pDeviceInfo); ma_close_stream__aaudio(pContext, pStream); pStream = NULL; - - /* AAudio supports s16 and f32. */ - pDeviceInfo->formatCount = 2; - pDeviceInfo->formats[0] = ma_format_s16; - pDeviceInfo->formats[1] = ma_format_f32; - return MA_SUCCESS; } -static void ma_device_uninit__aaudio(ma_device* pDevice) +static ma_result ma_device_uninit__aaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -28617,12 +29851,57 @@ static void ma_device_uninit__aaudio(ma_device* pDevice) pDevice->aaudio.pStreamPlayback = NULL; } - if (pDevice->type == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDevice->aaudio.duplexRB); - } + return MA_SUCCESS; } -static ma_result ma_device_init__aaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init_by_type__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_type deviceType, ma_device_descriptor* pDescriptor, ma_AAudioStream** ppStream) +{ + ma_result result; + int32_t bufferCapacityInFrames; + int32_t framesPerDataCallback; + ma_AAudioStream* pStream; + + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pDescriptor != NULL); + + *ppStream = NULL; /* Safety. */ + + /* First step is to open the stream. From there we'll be able to extract the internal configuration. */ + result = ma_open_stream__aaudio(pDevice, pConfig, deviceType, pDescriptor, &pStream); + if (result != MA_SUCCESS) { + return result; /* Failed to open the AAudio stream. */ + } + + /* Now extract the internal configuration. */ + pDescriptor->format = (((MA_PFN_AAudioStream_getFormat)pDevice->pContext->aaudio.AAudioStream_getFormat)(pStream) == MA_AAUDIO_FORMAT_PCM_I16) ? ma_format_s16 : ma_format_f32; + pDescriptor->channels = ((MA_PFN_AAudioStream_getChannelCount)pDevice->pContext->aaudio.AAudioStream_getChannelCount)(pStream); + pDescriptor->sampleRate = ((MA_PFN_AAudioStream_getSampleRate)pDevice->pContext->aaudio.AAudioStream_getSampleRate)(pStream); + + /* For the channel map we need to be sure we don't overflow any buffers. */ + if (pDescriptor->channels <= MA_MAX_CHANNELS) { + ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptor->channels, pDescriptor->channelMap); /* <-- Cannot find info on channel order, so assuming a default. */ + } else { + ma_channel_map_init_blank(MA_MAX_CHANNELS, pDescriptor->channelMap); /* Too many channels. Use a blank channel map. */ + } + + bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pDevice->pContext->aaudio.AAudioStream_getBufferCapacityInFrames)(pStream); + framesPerDataCallback = ((MA_PFN_AAudioStream_getFramesPerDataCallback)pDevice->pContext->aaudio.AAudioStream_getFramesPerDataCallback)(pStream); + + if (framesPerDataCallback > 0) { + pDescriptor->periodSizeInFrames = framesPerDataCallback; + pDescriptor->periodCount = bufferCapacityInFrames / framesPerDataCallback; + } else { + pDescriptor->periodSizeInFrames = bufferCapacityInFrames; + pDescriptor->periodCount = 1; + } + + *ppStream = pStream; + + return MA_SUCCESS; +} + +static ma_result ma_device_init__aaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; @@ -28633,86 +29912,22 @@ static ma_result ma_device_init__aaudio(ma_context* pContext, const ma_device_co } /* No exclusive mode with AAudio. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } - /* We first need to try opening the stream. */ if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - int32_t bufferCapacityInFrames; - int32_t framesPerDataCallback; - - result = ma_open_stream__aaudio(pContext, ma_device_type_capture, pConfig->capture.pDeviceID, pConfig->capture.shareMode, pConfig, pDevice, (ma_AAudioStream**)&pDevice->aaudio.pStreamCapture); + result = ma_device_init_by_type__aaudio(pDevice, pConfig, ma_device_type_capture, pDescriptorCapture, (ma_AAudioStream**)&pDevice->aaudio.pStreamCapture); if (result != MA_SUCCESS) { - return result; /* Failed to open the AAudio stream. */ - } - - pDevice->capture.internalFormat = (((MA_PFN_AAudioStream_getFormat)pContext->aaudio.AAudioStream_getFormat)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture) == MA_AAUDIO_FORMAT_PCM_I16) ? ma_format_s16 : ma_format_f32; - pDevice->capture.internalChannels = ((MA_PFN_AAudioStream_getChannelCount)pContext->aaudio.AAudioStream_getChannelCount)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - pDevice->capture.internalSampleRate = ((MA_PFN_AAudioStream_getSampleRate)pContext->aaudio.AAudioStream_getSampleRate)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); /* <-- Cannot find info on channel order, so assuming a default. */ - - bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pContext->aaudio.AAudioStream_getBufferCapacityInFrames)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - framesPerDataCallback = ((MA_PFN_AAudioStream_getFramesPerDataCallback)pContext->aaudio.AAudioStream_getFramesPerDataCallback)((ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - - if (framesPerDataCallback > 0) { - pDevice->capture.internalPeriodSizeInFrames = framesPerDataCallback; - pDevice->capture.internalPeriods = bufferCapacityInFrames / framesPerDataCallback; - } else { - pDevice->capture.internalPeriodSizeInFrames = bufferCapacityInFrames; - pDevice->capture.internalPeriods = 1; + return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - int32_t bufferCapacityInFrames; - int32_t framesPerDataCallback; - - result = ma_open_stream__aaudio(pContext, ma_device_type_playback, pConfig->playback.pDeviceID, pConfig->playback.shareMode, pConfig, pDevice, (ma_AAudioStream**)&pDevice->aaudio.pStreamPlayback); + result = ma_device_init_by_type__aaudio(pDevice, pConfig, ma_device_type_playback, pDescriptorPlayback, (ma_AAudioStream**)&pDevice->aaudio.pStreamPlayback); if (result != MA_SUCCESS) { - return result; /* Failed to open the AAudio stream. */ - } - - pDevice->playback.internalFormat = (((MA_PFN_AAudioStream_getFormat)pContext->aaudio.AAudioStream_getFormat)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback) == MA_AAUDIO_FORMAT_PCM_I16) ? ma_format_s16 : ma_format_f32; - pDevice->playback.internalChannels = ((MA_PFN_AAudioStream_getChannelCount)pContext->aaudio.AAudioStream_getChannelCount)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - pDevice->playback.internalSampleRate = ((MA_PFN_AAudioStream_getSampleRate)pContext->aaudio.AAudioStream_getSampleRate)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); /* <-- Cannot find info on channel order, so assuming a default. */ - - bufferCapacityInFrames = ((MA_PFN_AAudioStream_getBufferCapacityInFrames)pContext->aaudio.AAudioStream_getBufferCapacityInFrames)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - framesPerDataCallback = ((MA_PFN_AAudioStream_getFramesPerDataCallback)pContext->aaudio.AAudioStream_getFramesPerDataCallback)((ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - - if (framesPerDataCallback > 0) { - pDevice->playback.internalPeriodSizeInFrames = framesPerDataCallback; - pDevice->playback.internalPeriods = bufferCapacityInFrames / framesPerDataCallback; - } else { - pDevice->playback.internalPeriodSizeInFrames = bufferCapacityInFrames; - pDevice->playback.internalPeriods = 1; - } - } - - if (pConfig->deviceType == ma_device_type_duplex) { - ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames) * pDevice->capture.internalPeriods; - ma_result result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->pContext->allocationCallbacks, &pDevice->aaudio.duplexRB); - if (result != MA_SUCCESS) { - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamCapture); - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_close_stream__aaudio(pDevice->pContext, (ma_AAudioStream*)pDevice->aaudio.pStreamPlayback); - } - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[AAudio] Failed to initialize ring buffer.", result); - } - - /* We need a period to act as a buffer for cases where the playback and capture device's end up desyncing. */ - { - ma_uint32 marginSizeInFrames = rbSizeInFrames / pDevice->capture.internalPeriods; - void* pMarginData; - ma_pcm_rb_acquire_write(&pDevice->aaudio.duplexRB, &marginSizeInFrames, &pMarginData); - { - MA_ZERO_MEMORY(pMarginData, marginSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); - } - ma_pcm_rb_commit_write(&pDevice->aaudio.duplexRB, marginSizeInFrames, pMarginData); + return result; } } @@ -28846,19 +30061,19 @@ static ma_result ma_context_uninit__aaudio(ma_context* pContext) { MA_ASSERT(pContext != NULL); MA_ASSERT(pContext->backend == ma_backend_aaudio); - + ma_dlclose(pContext, pContext->aaudio.hAAudio); pContext->aaudio.hAAudio = NULL; return MA_SUCCESS; } -static ma_result ma_context_init__aaudio(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__aaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { + size_t i; const char* libNames[] = { "libaaudio.so" }; - size_t i; for (i = 0; i < ma_countof(libNames); ++i) { pContext->aaudio.hAAudio = ma_dlopen(pContext, libNames[i]); @@ -28884,6 +30099,9 @@ static ma_result ma_context_init__aaudio(const ma_context_config* pConfig, ma_co pContext->aaudio.AAudioStreamBuilder_setDataCallback = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setDataCallback"); pContext->aaudio.AAudioStreamBuilder_setErrorCallback = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setErrorCallback"); pContext->aaudio.AAudioStreamBuilder_setPerformanceMode = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setPerformanceMode"); + pContext->aaudio.AAudioStreamBuilder_setUsage = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setUsage"); + pContext->aaudio.AAudioStreamBuilder_setContentType = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setContentType"); + pContext->aaudio.AAudioStreamBuilder_setInputPreset = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_setInputPreset"); pContext->aaudio.AAudioStreamBuilder_openStream = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStreamBuilder_openStream"); pContext->aaudio.AAudioStream_close = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_close"); pContext->aaudio.AAudioStream_getState = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_getState"); @@ -28897,16 +30115,18 @@ static ma_result ma_context_init__aaudio(const ma_context_config* pConfig, ma_co pContext->aaudio.AAudioStream_requestStart = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_requestStart"); pContext->aaudio.AAudioStream_requestStop = (ma_proc)ma_dlsym(pContext, pContext->aaudio.hAAudio, "AAudioStream_requestStop"); - pContext->isBackendAsynchronous = MA_TRUE; - pContext->onUninit = ma_context_uninit__aaudio; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__aaudio; - pContext->onEnumDevices = ma_context_enumerate_devices__aaudio; - pContext->onGetDeviceInfo = ma_context_get_device_info__aaudio; - pContext->onDeviceInit = ma_device_init__aaudio; - pContext->onDeviceUninit = ma_device_uninit__aaudio; - pContext->onDeviceStart = ma_device_start__aaudio; - pContext->onDeviceStop = ma_device_stop__aaudio; + pCallbacks->onContextInit = ma_context_init__aaudio; + pCallbacks->onContextUninit = ma_context_uninit__aaudio; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__aaudio; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__aaudio; + pCallbacks->onDeviceInit = ma_device_init__aaudio; + pCallbacks->onDeviceUninit = ma_device_uninit__aaudio; + pCallbacks->onDeviceStart = ma_device_start__aaudio; + pCallbacks->onDeviceStop = ma_device_stop__aaudio; + pCallbacks->onDeviceRead = NULL; /* Not used because AAudio is asynchronous. */ + pCallbacks->onDeviceWrite = NULL; /* Not used because AAudio is asynchronous. */ + pCallbacks->onDeviceDataLoop = NULL; /* Not used because AAudio is asynchronous. */ (void)pConfig; return MA_SUCCESS; @@ -29113,16 +30333,36 @@ static SLuint32 ma_round_to_standard_sample_rate__opensl(SLuint32 samplesPerSec) } -static ma_bool32 ma_context_is_device_id_equal__opensl(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) +static SLint32 ma_to_stream_type__opensl(ma_opensl_stream_type streamType) { - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; + switch (streamType) { + case ma_opensl_stream_type_voice: return SL_ANDROID_STREAM_VOICE; + case ma_opensl_stream_type_system: return SL_ANDROID_STREAM_SYSTEM; + case ma_opensl_stream_type_ring: return SL_ANDROID_STREAM_RING; + case ma_opensl_stream_type_media: return SL_ANDROID_STREAM_MEDIA; + case ma_opensl_stream_type_alarm: return SL_ANDROID_STREAM_ALARM; + case ma_opensl_stream_type_notification: return SL_ANDROID_STREAM_NOTIFICATION; + default: break; + } - return pID0->opensl == pID1->opensl; + return SL_ANDROID_STREAM_VOICE; } +static SLint32 ma_to_recording_preset__opensl(ma_opensl_recording_preset recordingPreset) +{ + switch (recordingPreset) { + case ma_opensl_recording_preset_generic: return SL_ANDROID_RECORDING_PRESET_GENERIC; + case ma_opensl_recording_preset_camcorder: return SL_ANDROID_RECORDING_PRESET_CAMCORDER; + case ma_opensl_recording_preset_voice_recognition: return SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + case ma_opensl_recording_preset_voice_communication: return SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; + case ma_opensl_recording_preset_voice_unprocessed: return SL_ANDROID_RECORDING_PRESET_UNPROCESSED; + default: break; + } + + return SL_ANDROID_RECORDING_PRESET_NONE; +} + + static ma_result ma_context_enumerate_devices__opensl(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { ma_bool32 cbResult; @@ -29137,7 +30377,7 @@ static ma_result ma_context_enumerate_devices__opensl(ma_context* pContext, ma_e /* TODO: Test Me. - + This is currently untested, so for now we are just returning default devices. */ #if 0 && !defined(MA_ANDROID) @@ -29232,7 +30472,45 @@ return_default_device:; return MA_SUCCESS; } -static ma_result ma_context_get_device_info__opensl(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static void ma_context_add_data_format_ex__opensl(ma_context* pContext, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_device_info* pDeviceInfo) +{ + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDeviceInfo != NULL); + + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format = format; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels = channels; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate; + pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags = 0; + pDeviceInfo->nativeDataFormatCount += 1; +} + +static void ma_context_add_data_format__opensl(ma_context* pContext, ma_format format, ma_device_info* pDeviceInfo) +{ + ma_uint32 minChannels = 1; + ma_uint32 maxChannels = 2; + ma_uint32 minSampleRate = (ma_uint32)ma_standard_sample_rate_8000; + ma_uint32 maxSampleRate = (ma_uint32)ma_standard_sample_rate_48000; + ma_uint32 iChannel; + ma_uint32 iSampleRate; + + MA_ASSERT(pContext != NULL); + MA_ASSERT(pDeviceInfo != NULL); + + /* + Each sample format can support mono and stereo, and we'll support a small subset of standard + rates (up to 48000). A better solution would be to somehow find a native sample rate. + */ + for (iChannel = minChannels; iChannel < maxChannels; iChannel += 1) { + for (iSampleRate = 0; iSampleRate < ma_countof(g_maStandardSampleRatePriorities); iSampleRate += 1) { + ma_uint32 standardSampleRate = g_maStandardSampleRatePriorities[iSampleRate]; + if (standardSampleRate >= minSampleRate && standardSampleRate <= maxSampleRate) { + ma_context_add_data_format_ex__opensl(pContext, format, iChannel, standardSampleRate, pDeviceInfo); + } + } + } +} + +static ma_result ma_context_get_device_info__opensl(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { MA_ASSERT(pContext != NULL); @@ -29241,14 +30519,9 @@ static ma_result ma_context_get_device_info__opensl(ma_context* pContext, ma_dev return MA_INVALID_OPERATION; } - /* No exclusive mode with OpenSL|ES. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - /* TODO: Test Me. - + This is currently untested, so for now we are just returning default devices. */ #if 0 && !defined(MA_ANDROID) @@ -29297,6 +30570,8 @@ return_default_device: ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } + pDeviceInfo->isDefault = MA_TRUE; + goto return_detailed_info; @@ -29307,17 +30582,12 @@ return_detailed_info: by the device natively. Later on we should work on this so that it more closely reflects the device's actual native format. */ - pDeviceInfo->minChannels = 1; - pDeviceInfo->maxChannels = 2; - pDeviceInfo->minSampleRate = 8000; - pDeviceInfo->maxSampleRate = 48000; - pDeviceInfo->formatCount = 2; - pDeviceInfo->formats[0] = ma_format_u8; - pDeviceInfo->formats[1] = ma_format_s16; + pDeviceInfo->nativeDataFormatCount = 0; #if defined(MA_ANDROID) && __ANDROID_API__ >= 21 - pDeviceInfo->formats[pDeviceInfo->formatCount] = ma_format_f32; - pDeviceInfo->formatCount += 1; + ma_context_add_data_format__opensl(pContext, ma_format_f32, pDeviceInfo); #endif + ma_context_add_data_format__opensl(pContext, ma_format_s16, pDeviceInfo); + ma_context_add_data_format__opensl(pContext, ma_format_u8, pDeviceInfo); return MA_SUCCESS; } @@ -29343,7 +30613,7 @@ static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBuff */ /* Don't do anything if the device is not started. */ - if (pDevice->state != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { return; } @@ -29355,11 +30625,7 @@ static void ma_buffer_queue_callback_capture__opensl_android(SLAndroidSimpleBuff periodSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels); pBuffer = pDevice->opensl.pBufferCapture + (pDevice->opensl.currentBufferIndexCapture * periodSizeInBytes); - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, pDevice->capture.internalPeriodSizeInFrames, pBuffer, &pDevice->opensl.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, pDevice->capture.internalPeriodSizeInFrames, pBuffer); - } + ma_device_handle_backend_data_callback(pDevice, NULL, pBuffer, pDevice->capture.internalPeriodSizeInFrames); resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueueCapture)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueueCapture, pBuffer, periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { @@ -29381,7 +30647,7 @@ static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBuf (void)pBufferQueue; /* Don't do anything if the device is not started. */ - if (pDevice->state != MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) != MA_STATE_STARTED) { return; } @@ -29393,11 +30659,7 @@ static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBuf periodSizeInBytes = pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); pBuffer = pDevice->opensl.pBufferPlayback + (pDevice->opensl.currentBufferIndexPlayback * periodSizeInBytes); - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_playback(pDevice, pDevice->playback.internalPeriodSizeInFrames, pBuffer, &pDevice->opensl.duplexRB); - } else { - ma_device__read_frames_from_client(pDevice, pDevice->playback.internalPeriodSizeInFrames, pBuffer); - } + ma_device_handle_backend_data_callback(pDevice, pBuffer, NULL, pDevice->playback.internalPeriodSizeInFrames); resultSL = MA_OPENSL_BUFFERQUEUE(pDevice->opensl.pBufferQueuePlayback)->Enqueue((SLAndroidSimpleBufferQueueItf)pDevice->opensl.pBufferQueuePlayback, pBuffer, periodSizeInBytes); if (resultSL != SL_RESULT_SUCCESS) { @@ -29408,13 +30670,13 @@ static void ma_buffer_queue_callback_playback__opensl_android(SLAndroidSimpleBuf } #endif -static void ma_device_uninit__opensl(ma_device* pDevice) +static ma_result ma_device_uninit__opensl(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it before uninitializing the device. */ if (g_maOpenSLInitCounter == 0) { - return; + return MA_INVALID_OPERATION; } if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { @@ -29436,9 +30698,7 @@ static void ma_device_uninit__opensl(ma_device* pDevice) ma__free_from_callbacks(pDevice->opensl.pBufferPlayback, &pDevice->pContext->allocationCallbacks); } - if (pDevice->type == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDevice->opensl.duplexRB); - } + return MA_SUCCESS; } #if defined(MA_ANDROID) && __ANDROID_API__ >= 21 @@ -29449,6 +30709,17 @@ typedef SLDataFormat_PCM ma_SLDataFormat_PCM; static ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, const ma_channel* channelMap, ma_SLDataFormat_PCM* pDataFormat) { + /* We need to convert our format/channels/rate so that they aren't set to default. */ + if (format == ma_format_unknown) { + format = MA_DEFAULT_FORMAT; + } + if (channels == 0) { + channels = MA_DEFAULT_CHANNELS; + } + if (sampleRate == 0) { + sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + #if defined(MA_ANDROID) && __ANDROID_API__ >= 21 if (format == ma_format_f32) { pDataFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; @@ -29461,7 +30732,7 @@ static ma_result ma_SLDataFormat_PCM_init__opensl(ma_format format, ma_uint32 ch #endif pDataFormat->numChannels = channels; - ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate * 1000); /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ + ((SLDataFormat_PCM*)pDataFormat)->samplesPerSec = ma_round_to_standard_sample_rate__opensl(sampleRate) * 1000; /* In millihertz. Annoyingly, the sample rate variable is named differently between SLAndroidDataFormat_PCM_EX and SLDataFormat_PCM */ pDataFormat->bitsPerSample = ma_get_bytes_per_sample(format)*8; pDataFormat->channelMask = ma_channel_map_to_channel_mask__opensl(channelMap, channels); pDataFormat->endianness = (ma_is_little_endian()) ? SL_BYTEORDER_LITTLEENDIAN : SL_BYTEORDER_BIGENDIAN; @@ -29535,19 +30806,16 @@ static ma_result ma_deconstruct_SLDataFormat_PCM__opensl(ma_SLDataFormat_PCM* pD return MA_SUCCESS; } -static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__opensl(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { #ifdef MA_ANDROID SLDataLocator_AndroidSimpleBufferQueue queue; SLresult resultSL; - ma_uint32 periodSizeInFrames; size_t bufferSizeInBytes; SLInterfaceID itfIDs1[1]; const SLboolean itfIDsRequired1[] = {SL_BOOLEAN_TRUE}; #endif - (void)pContext; - MA_ASSERT(g_maOpenSLInitCounter > 0); /* <-- If you trigger this it means you've either not initialized the context, or you've uninitialized it and then attempted to initialize a new device. */ if (g_maOpenSLInitCounter == 0) { return MA_INVALID_OPERATION; @@ -29563,11 +30831,11 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co queues). */ #ifdef MA_ANDROID - itfIDs1[0] = (SLInterfaceID)pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + itfIDs1[0] = (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE; /* No exclusive mode with OpenSL|ES. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } @@ -29576,25 +30844,26 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co MA_ZERO_OBJECT(&pDevice->opensl); queue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - queue.numBuffers = pConfig->periods; - if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { ma_SLDataFormat_PCM pcm; SLDataLocator_IODevice locatorDevice; SLDataSource source; SLDataSink sink; + SLAndroidConfigurationItf pRecorderConfig; - ma_SLDataFormat_PCM_init__opensl(pConfig->capture.format, pConfig->capture.channels, pConfig->sampleRate, pConfig->capture.channelMap, &pcm); + ma_SLDataFormat_PCM_init__opensl(pDescriptorCapture->format, pDescriptorCapture->channels, pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, &pcm); locatorDevice.locatorType = SL_DATALOCATOR_IODEVICE; locatorDevice.deviceType = SL_IODEVICE_AUDIOINPUT; - locatorDevice.deviceID = (pConfig->capture.pDeviceID == NULL) ? SL_DEFAULTDEVICEID_AUDIOINPUT : pConfig->capture.pDeviceID->opensl; + locatorDevice.deviceID = (pDescriptorCapture->pDeviceID == NULL) ? SL_DEFAULTDEVICEID_AUDIOINPUT : pDescriptorCapture->pDeviceID->opensl; locatorDevice.device = NULL; source.pLocator = &locatorDevice; source.pFormat = NULL; + queue.numBuffers = pDescriptorCapture->periodCount; + sink.pLocator = &queue; sink.pFormat = (SLDataFormat_PCM*)&pcm; @@ -29615,19 +30884,32 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio recorder.", ma_result_from_OpenSL(resultSL)); } + + /* Set the recording preset before realizing the player. */ + if (pConfig->opensl.recordingPreset != ma_opensl_recording_preset_default) { + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDCONFIGURATION, &pRecorderConfig); + if (resultSL == SL_RESULT_SUCCESS) { + SLint32 recordingPreset = ma_to_recording_preset__opensl(pConfig->opensl.recordingPreset); + resultSL = (*pRecorderConfig)->SetConfiguration(pRecorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, &recordingPreset, sizeof(SLint32)); + if (resultSL != SL_RESULT_SUCCESS) { + /* Failed to set the configuration. Just keep going. */ + } + } + } + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->Realize((SLObjectItf)pDevice->opensl.pAudioRecorderObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio recorder.", ma_result_from_OpenSL(resultSL)); } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pContext->opensl.SL_IID_RECORD, &pDevice->opensl.pAudioRecorder); + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_RECORD, &pDevice->opensl.pAudioRecorder); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_RECORD interface.", ma_result_from_OpenSL(resultSL)); } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture); + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioRecorderObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioRecorderObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueueCapture); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); @@ -29640,19 +30922,14 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co } /* The internal format is determined by the "pcm" object. */ - ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDevice->capture.internalFormat, &pDevice->capture.internalChannels, &pDevice->capture.internalSampleRate, pDevice->capture.internalChannelMap, ma_countof(pDevice->capture.internalChannelMap)); + ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDescriptorCapture->format, &pDescriptorCapture->channels, &pDescriptorCapture->sampleRate, pDescriptorCapture->channelMap, ma_countof(pDescriptorCapture->channelMap)); /* Buffer. */ - periodSizeInFrames = pConfig->periodSizeInFrames; - if (periodSizeInFrames == 0) { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, pDevice->capture.internalSampleRate); - } - pDevice->capture.internalPeriods = pConfig->periods; - pDevice->capture.internalPeriodSizeInFrames = periodSizeInFrames; - pDevice->opensl.currentBufferIndexCapture = 0; + pDescriptorCapture->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorCapture, pDescriptorCapture->sampleRate, pConfig->performanceProfile); + pDevice->opensl.currentBufferIndexCapture = 0; - bufferSizeInBytes = pDevice->capture.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.internalFormat, pDevice->capture.internalChannels) * pDevice->capture.internalPeriods; - pDevice->opensl.pBufferCapture = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pContext->allocationCallbacks); + bufferSizeInBytes = pDescriptorCapture->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorCapture->format, pDescriptorCapture->channels) * pDescriptorCapture->periodCount; + pDevice->opensl.pBufferCapture = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferCapture == NULL) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); @@ -29665,8 +30942,9 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co SLDataSource source; SLDataLocator_OutputMix outmixLocator; SLDataSink sink; + SLAndroidConfigurationItf pPlayerConfig; - ma_SLDataFormat_PCM_init__opensl(pConfig->playback.format, pConfig->playback.channels, pConfig->sampleRate, pConfig->playback.channelMap, &pcm); + ma_SLDataFormat_PCM_init__opensl(pDescriptorPlayback->format, pDescriptorPlayback->channels, pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, &pcm); resultSL = (*g_maEngineSL)->CreateOutputMix(g_maEngineSL, (SLObjectItf*)&pDevice->opensl.pOutputMixObj, 0, NULL, NULL); if (resultSL != SL_RESULT_SUCCESS) { @@ -29680,18 +30958,20 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize output mix object.", ma_result_from_OpenSL(resultSL)); } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, (SLInterfaceID)pContext->opensl.SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix); + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pOutputMixObj)->GetInterface((SLObjectItf)pDevice->opensl.pOutputMixObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_OUTPUTMIX, &pDevice->opensl.pOutputMix); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_OUTPUTMIX interface.", ma_result_from_OpenSL(resultSL)); } /* Set the output device. */ - if (pConfig->playback.pDeviceID != NULL) { - SLuint32 deviceID_OpenSL = pConfig->playback.pDeviceID->opensl; + if (pDescriptorPlayback->pDeviceID != NULL) { + SLuint32 deviceID_OpenSL = pDescriptorPlayback->pDeviceID->opensl; MA_OPENSL_OUTPUTMIX(pDevice->opensl.pOutputMix)->ReRoute((SLOutputMixItf)pDevice->opensl.pOutputMix, 1, &deviceID_OpenSL); } - + + queue.numBuffers = pDescriptorPlayback->periodCount; + source.pLocator = &queue; source.pFormat = (SLDataFormat_PCM*)&pcm; @@ -29718,19 +30998,32 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to create audio player.", ma_result_from_OpenSL(resultSL)); } + + /* Set the stream type before realizing the player. */ + if (pConfig->opensl.streamType != ma_opensl_stream_type_default) { + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDCONFIGURATION, &pPlayerConfig); + if (resultSL == SL_RESULT_SUCCESS) { + SLint32 streamType = ma_to_stream_type__opensl(pConfig->opensl.streamType); + resultSL = (*pPlayerConfig)->SetConfiguration(pPlayerConfig, SL_ANDROID_KEY_STREAM_TYPE, &streamType, sizeof(SLint32)); + if (resultSL != SL_RESULT_SUCCESS) { + /* Failed to set the configuration. Just keep going. */ + } + } + } + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->Realize((SLObjectItf)pDevice->opensl.pAudioPlayerObj, SL_BOOLEAN_FALSE); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to realize audio player.", ma_result_from_OpenSL(resultSL)); } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pContext->opensl.SL_IID_PLAY, &pDevice->opensl.pAudioPlayer); + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_PLAY, &pDevice->opensl.pAudioPlayer); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_PLAY interface.", ma_result_from_OpenSL(resultSL)); } - resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback); + resultSL = MA_OPENSL_OBJ(pDevice->opensl.pAudioPlayerObj)->GetInterface((SLObjectItf)pDevice->opensl.pAudioPlayerObj, (SLInterfaceID)pDevice->pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &pDevice->opensl.pBufferQueuePlayback); if (resultSL != SL_RESULT_SUCCESS) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to retrieve SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface.", ma_result_from_OpenSL(resultSL)); @@ -29743,19 +31036,14 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co } /* The internal format is determined by the "pcm" object. */ - ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDevice->playback.internalFormat, &pDevice->playback.internalChannels, &pDevice->playback.internalSampleRate, pDevice->playback.internalChannelMap, ma_countof(pDevice->playback.internalChannelMap)); + ma_deconstruct_SLDataFormat_PCM__opensl(&pcm, &pDescriptorPlayback->format, &pDescriptorPlayback->channels, &pDescriptorPlayback->sampleRate, pDescriptorPlayback->channelMap, ma_countof(pDescriptorPlayback->channelMap)); /* Buffer. */ - periodSizeInFrames = pConfig->periodSizeInFrames; - if (periodSizeInFrames == 0) { - periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pConfig->periodSizeInMilliseconds, pDevice->playback.internalSampleRate); - } - pDevice->playback.internalPeriods = pConfig->periods; - pDevice->playback.internalPeriodSizeInFrames = periodSizeInFrames; + pDescriptorPlayback->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); pDevice->opensl.currentBufferIndexPlayback = 0; - bufferSizeInBytes = pDevice->playback.internalPeriodSizeInFrames * ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels) * pDevice->playback.internalPeriods; - pDevice->opensl.pBufferPlayback = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pContext->allocationCallbacks); + bufferSizeInBytes = pDescriptorPlayback->periodSizeInFrames * ma_get_bytes_per_frame(pDescriptorPlayback->format, pDescriptorPlayback->channels) * pDescriptorPlayback->periodCount; + pDevice->opensl.pBufferPlayback = (ma_uint8*)ma__calloc_from_callbacks(bufferSizeInBytes, &pDevice->pContext->allocationCallbacks); if (pDevice->opensl.pBufferPlayback == NULL) { ma_device_uninit__opensl(pDevice); return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to allocate memory for data buffer.", MA_OUT_OF_MEMORY); @@ -29763,26 +31051,6 @@ static ma_result ma_device_init__opensl(ma_context* pContext, const ma_device_co MA_ZERO_MEMORY(pDevice->opensl.pBufferPlayback, bufferSizeInBytes); } - if (pConfig->deviceType == ma_device_type_duplex) { - ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames) * pDevice->capture.internalPeriods; - ma_result result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->pContext->allocationCallbacks, &pDevice->opensl.duplexRB); - if (result != MA_SUCCESS) { - ma_device_uninit__opensl(pDevice); - return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "[OpenSL] Failed to initialize ring buffer.", result); - } - - /* We need a period to act as a buffer for cases where the playback and capture device's end up desyncing. */ - { - ma_uint32 marginSizeInFrames = rbSizeInFrames / pDevice->capture.internalPeriods; - void* pMarginData; - ma_pcm_rb_acquire_write(&pDevice->opensl.duplexRB, &marginSizeInFrames, &pMarginData); - { - MA_ZERO_MEMORY(pMarginData, marginSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); - } - ma_pcm_rb_commit_write(&pDevice->opensl.duplexRB, marginSizeInFrames, pMarginData); - } - } - return MA_SUCCESS; #else return MA_NO_BACKEND; /* Non-Android implementations are not supported. */ @@ -29981,18 +31249,22 @@ static ma_result ma_context_init_engine_nolock__opensl(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__opensl(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__opensl(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { ma_result result; + +#if !defined(MA_NO_RUNTIME_LINKING) size_t i; const char* libOpenSLESNames[] = { "libOpenSLES.so" }; +#endif MA_ASSERT(pContext != NULL); (void)pConfig; +#if !defined(MA_NO_RUNTIME_LINKING) /* Dynamically link against libOpenSLES.so. I have now had multiple reports that SL_IID_ANDROIDSIMPLEBUFFERQUEUE cannot be found. One report was happening at compile time and another at runtime. To try working around this, I'm going to link to libOpenSLES at runtime @@ -30007,44 +31279,68 @@ static ma_result ma_context_init__opensl(const ma_context_config* pConfig, ma_co } if (pContext->opensl.libOpenSLES == NULL) { - return MA_NO_BACKEND; /* Couldn't find libOpenSLES.so */ + ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Could not find libOpenSLES.so"); + return MA_NO_BACKEND; } result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_ENGINE", &pContext->opensl.SL_IID_ENGINE); if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); return result; } result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_AUDIOIODEVICECAPABILITIES", &pContext->opensl.SL_IID_AUDIOIODEVICECAPABILITIES); if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); return result; } result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE", &pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE); if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); return result; } result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_RECORD", &pContext->opensl.SL_IID_RECORD); if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); return result; } result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_PLAY", &pContext->opensl.SL_IID_PLAY); if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); return result; } result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_OUTPUTMIX", &pContext->opensl.SL_IID_OUTPUTMIX); if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); + return result; + } + + result = ma_dlsym_SLInterfaceID__opensl(pContext, "SL_IID_ANDROIDCONFIGURATION", &pContext->opensl.SL_IID_ANDROIDCONFIGURATION); + if (result != MA_SUCCESS) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); return result; } pContext->opensl.slCreateEngine = (ma_proc)ma_dlsym(pContext, pContext->opensl.libOpenSLES, "slCreateEngine"); if (pContext->opensl.slCreateEngine == NULL) { + ma_dlclose(pContext, pContext->opensl.libOpenSLES); ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Cannot find symbol slCreateEngine."); return MA_NO_BACKEND; } +#else + pContext->opensl.SL_IID_ENGINE = (ma_handle)SL_IID_ENGINE; + pContext->opensl.SL_IID_AUDIOIODEVICECAPABILITIES = (ma_handle)SL_IID_AUDIOIODEVICECAPABILITIES; + pContext->opensl.SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (ma_handle)SL_IID_ANDROIDSIMPLEBUFFERQUEUE; + pContext->opensl.SL_IID_RECORD = (ma_handle)SL_IID_RECORD; + pContext->opensl.SL_IID_PLAY = (ma_handle)SL_IID_PLAY; + pContext->opensl.SL_IID_OUTPUTMIX = (ma_handle)SL_IID_OUTPUTMIX; + pContext->opensl.SL_IID_ANDROIDCONFIGURATION = (ma_handle)SL_IID_ANDROIDCONFIGURATION; + pContext->opensl.slCreateEngine = (ma_proc)slCreateEngine; +#endif /* Initialize global data first if applicable. */ @@ -30055,20 +31351,22 @@ static ma_result ma_context_init__opensl(const ma_context_config* pConfig, ma_co ma_spinlock_unlock(&g_maOpenSLSpinlock); if (result != MA_SUCCESS) { - return result; /* Failed to initialize the OpenSL engine. */ + ma_dlclose(pContext, pContext->opensl.libOpenSLES); + ma_post_log_message(pContext, NULL, MA_LOG_LEVEL_INFO, "[OpenSL|ES] Failed to initialize OpenSL engine."); + return result; } - - pContext->isBackendAsynchronous = MA_TRUE; - - pContext->onUninit = ma_context_uninit__opensl; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__opensl; - pContext->onEnumDevices = ma_context_enumerate_devices__opensl; - pContext->onGetDeviceInfo = ma_context_get_device_info__opensl; - pContext->onDeviceInit = ma_device_init__opensl; - pContext->onDeviceUninit = ma_device_uninit__opensl; - pContext->onDeviceStart = ma_device_start__opensl; - pContext->onDeviceStop = ma_device_stop__opensl; + pCallbacks->onContextInit = ma_context_init__opensl; + pCallbacks->onContextUninit = ma_context_uninit__opensl; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__opensl; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__opensl; + pCallbacks->onDeviceInit = ma_device_init__opensl; + pCallbacks->onDeviceUninit = ma_device_uninit__opensl; + pCallbacks->onDeviceStart = ma_device_start__opensl; + pCallbacks->onDeviceStop = ma_device_stop__opensl; + pCallbacks->onDeviceRead = NULL; /* Not needed because OpenSL|ES is asynchronous. */ + pCallbacks->onDeviceWrite = NULL; /* Not needed because OpenSL|ES is asynchronous. */ + pCallbacks->onDeviceDataLoop = NULL; /* Not needed because OpenSL|ES is asynchronous. */ return MA_SUCCESS; } @@ -30095,35 +31393,17 @@ extern "C" { #endif void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_capture__webaudio(ma_device* pDevice, int frameCount, float* pFrames) { - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_capture(pDevice, (ma_uint32)frameCount, pFrames, &pDevice->webaudio.duplexRB); - } else { - ma_device__send_frames_to_client(pDevice, (ma_uint32)frameCount, pFrames); /* Send directly to the client. */ - } + ma_device_handle_backend_data_callback(pDevice, NULL, pFrames, (ma_uint32)frameCount); } void EMSCRIPTEN_KEEPALIVE ma_device_process_pcm_frames_playback__webaudio(ma_device* pDevice, int frameCount, float* pFrames) { - if (pDevice->type == ma_device_type_duplex) { - ma_device__handle_duplex_callback_playback(pDevice, (ma_uint32)frameCount, pFrames, &pDevice->webaudio.duplexRB); - } else { - ma_device__read_frames_from_client(pDevice, (ma_uint32)frameCount, pFrames); /* Read directly from the device. */ - } + ma_device_handle_backend_data_callback(pDevice, pFrames, NULL, (ma_uint32)frameCount); } #ifdef __cplusplus } #endif -static ma_bool32 ma_context_is_device_id_equal__webaudio(ma_context* pContext, const ma_device_id* pID0, const ma_device_id* pID1) -{ - MA_ASSERT(pContext != NULL); - MA_ASSERT(pID0 != NULL); - MA_ASSERT(pID1 != NULL); - (void)pContext; - - return ma_strcmp(pID0->webaudio, pID1->webaudio) == 0; -} - static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma_enum_devices_callback_proc callback, void* pUserData) { ma_bool32 cbResult = MA_TRUE; @@ -30138,6 +31418,7 @@ static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + deviceInfo.isDefault = MA_TRUE; /* Only supporting default devices. */ cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); } @@ -30147,6 +31428,7 @@ static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma ma_device_info deviceInfo; MA_ZERO_OBJECT(&deviceInfo); ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + deviceInfo.isDefault = MA_TRUE; /* Only supporting default devices. */ cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); } } @@ -30154,20 +31436,14 @@ static ma_result ma_context_enumerate_devices__webaudio(ma_context* pContext, ma return MA_SUCCESS; } -static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) +static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) { MA_ASSERT(pContext != NULL); - /* No exclusive mode with Web Audio. */ - if (shareMode == ma_share_mode_exclusive) { - return MA_SHARE_MODE_NOT_SUPPORTED; - } - if (deviceType == ma_device_type_capture && !ma_is_capture_supported__webaudio()) { return MA_NO_DEVICE; } - MA_ZERO_MEMORY(pDeviceInfo->id.webaudio, sizeof(pDeviceInfo->id.webaudio)); /* Only supporting default devices for now. */ @@ -30178,15 +31454,14 @@ static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_d ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); } - /* Web Audio can support any number of channels and sample rates. It only supports f32 formats, however. */ - pDeviceInfo->minChannels = 1; - pDeviceInfo->maxChannels = MA_MAX_CHANNELS; - if (pDeviceInfo->maxChannels > 32) { - pDeviceInfo->maxChannels = 32; /* Maximum output channel count is 32 for createScriptProcessor() (JavaScript). */ - } + /* Only supporting default devices. */ + pDeviceInfo->isDefault = MA_TRUE; - /* We can query the sample rate by just using a temporary audio context. */ - pDeviceInfo->minSampleRate = EM_ASM_INT({ + /* Web Audio can support any number of channels and sample rates. It only supports f32 formats, however. */ + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormats[0].format = ma_format_unknown; + pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channels are supported. */ + pDeviceInfo->nativeDataFormats[0].sampleRate = EM_ASM_INT({ try { var temp = new (window.AudioContext || window.webkitAudioContext)(); var sampleRate = temp.sampleRate; @@ -30196,14 +31471,12 @@ static ma_result ma_context_get_device_info__webaudio(ma_context* pContext, ma_d return 0; } }, 0); /* Must pass in a dummy argument for C99 compatibility. */ - pDeviceInfo->maxSampleRate = pDeviceInfo->minSampleRate; - if (pDeviceInfo->minSampleRate == 0) { + + if (pDeviceInfo->nativeDataFormats[0].sampleRate == 0) { return MA_NO_DEVICE; } - /* Web Audio only supports f32. */ - pDeviceInfo->formatCount = 1; - pDeviceInfo->formats[0] = ma_format_f32; + pDeviceInfo->nativeDataFormatCount = 1; return MA_SUCCESS; } @@ -30247,7 +31520,7 @@ static void ma_device_uninit_by_index__webaudio(ma_device* pDevice, ma_device_ty }, deviceIndex, deviceType); } -static void ma_device_uninit__webaudio(ma_device* pDevice) +static ma_result ma_device_uninit__webaudio(ma_device* pDevice) { MA_ASSERT(pDevice != NULL); @@ -30259,51 +31532,64 @@ static void ma_device_uninit__webaudio(ma_device* pDevice) ma_device_uninit_by_index__webaudio(pDevice, ma_device_type_playback, pDevice->webaudio.indexPlayback); } - if (pDevice->type == ma_device_type_duplex) { - ma_pcm_rb_uninit(&pDevice->webaudio.duplexRB); - } + return MA_SUCCESS; } -static ma_result ma_device_init_by_type__webaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device_type deviceType, ma_device* pDevice) +static ma_uint32 ma_calculate_period_size_in_frames_from_descriptor__webaudio(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +{ + /* + There have been reports of the default buffer size being too small on some browsers. There have been reports of the default buffer + size being too small on some browsers. If we're using default buffer size, we'll make sure the period size is a big biffer than our + standard defaults. + */ + ma_uint32 periodSizeInFrames; + + if (pDescriptor->periodSizeInFrames == 0) { + if (pDescriptor->periodSizeInMilliseconds == 0) { + if (performanceProfile == ma_performance_profile_low_latency) { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(33, nativeSampleRate); /* 1 frame @ 30 FPS */ + } else { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(333, nativeSampleRate); + } + } else { + periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); + } + } else { + periodSizeInFrames = pDescriptor->periodSizeInFrames; + } + + /* The size of the buffer must be a power of 2 and between 256 and 16384. */ + if (periodSizeInFrames < 256) { + periodSizeInFrames = 256; + } else if (periodSizeInFrames > 16384) { + periodSizeInFrames = 16384; + } else { + periodSizeInFrames = ma_next_power_of_2(periodSizeInFrames); + } + + return periodSizeInFrames; +} + +static ma_result ma_device_init_by_type__webaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptor, ma_device_type deviceType) { int deviceIndex; - ma_uint32 internalPeriodSizeInFrames; + ma_uint32 channels; + ma_uint32 sampleRate; + ma_uint32 periodSizeInFrames; - MA_ASSERT(pContext != NULL); + MA_ASSERT(pDevice != NULL); MA_ASSERT(pConfig != NULL); MA_ASSERT(deviceType != ma_device_type_duplex); - MA_ASSERT(pDevice != NULL); if (deviceType == ma_device_type_capture && !ma_is_capture_supported__webaudio()) { return MA_NO_DEVICE; } - /* - Try calculating an appropriate buffer size. There have been reports of the default buffer size being too small on some browsers. If we're using default buffer size, we'll make sure - the period size is a big biffer than our standard defaults. - */ - internalPeriodSizeInFrames = pConfig->periodSizeInFrames; - if (internalPeriodSizeInFrames == 0) { - ma_uint32 periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; - if (pDevice->usingDefaultBufferSize) { - if (pConfig->performanceProfile == ma_performance_profile_low_latency) { - periodSizeInMilliseconds = 33; /* 1 frame @ 30 FPS */ - } else { - periodSizeInMilliseconds = 333; - } - } + /* We're going to calculate some stuff in C just to simplify the JS code. */ + channels = (pDescriptor->channels > 0) ? pDescriptor->channels : MA_DEFAULT_CHANNELS; + sampleRate = (pDescriptor->sampleRate > 0) ? pDescriptor->sampleRate : MA_DEFAULT_SAMPLE_RATE; + periodSizeInFrames = ma_calculate_period_size_in_frames_from_descriptor__webaudio(pDescriptor, sampleRate, pConfig->performanceProfile); - internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(periodSizeInMilliseconds, pConfig->sampleRate); - } - - /* The size of the buffer must be a power of 2 and between 256 and 16384. */ - if (internalPeriodSizeInFrames < 256) { - internalPeriodSizeInFrames = 256; - } else if (internalPeriodSizeInFrames > 16384) { - internalPeriodSizeInFrames = 16384; - } else { - internalPeriodSizeInFrames = ma_next_power_of_2(internalPeriodSizeInFrames); - } /* We create the device on the JavaScript side and reference it using an index. We use this to make it possible to reference the device between JavaScript and C. */ deviceIndex = EM_ASM_INT({ @@ -30322,6 +31608,7 @@ static ma_result ma_device_init_by_type__webaudio(ma_context* pContext, const ma /* The AudioContext must be created in a suspended state. */ device.webaudio = new (window.AudioContext || window.webkitAudioContext)({sampleRate:sampleRate}); device.webaudio.suspend(); + device.state = 1; /* MA_STATE_STOPPED */ /* We need an intermediary buffer which we use for JavaScript and C interop. This buffer stores interleaved f32 PCM data. Because it's passed between @@ -30355,6 +31642,11 @@ static ma_result ma_device_init_by_type__webaudio(ma_context* pContext, const ma return; /* This means the device has been uninitialized. */ } + if(device.intermediaryBufferView.length == 0) { + /* Recreate intermediaryBufferView when losing reference to the underlying buffer, probably due to emscripten resizing heap. */ + device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); + } + /* Make sure silence it output to the AudioContext destination. Not doing this will cause sound to come out of the speakers! */ for (var iChannel = 0; iChannel < e.outputBuffer.numberOfChannels; ++iChannel) { e.outputBuffer.getChannelData(iChannel).fill(0.0); @@ -30415,6 +31707,11 @@ static ma_result ma_device_init_by_type__webaudio(ma_context* pContext, const ma return; /* This means the device has been uninitialized. */ } + if(device.intermediaryBufferView.length == 0) { + /* Recreate intermediaryBufferView when losing reference to the underlying buffer, probably due to emscripten resizing heap. */ + device.intermediaryBufferView = new Float32Array(Module.HEAPF32.buffer, device.intermediaryBuffer, device.intermediaryBufferSizeInBytes); + } + var outputSilence = false; /* Sanity check. This will never happen, right? */ @@ -30457,34 +31754,29 @@ static ma_result ma_device_init_by_type__webaudio(ma_context* pContext, const ma } return miniaudio.track_device(device); - }, (deviceType == ma_device_type_capture) ? pConfig->capture.channels : pConfig->playback.channels, pConfig->sampleRate, internalPeriodSizeInFrames, deviceType == ma_device_type_capture, pDevice); + }, channels, sampleRate, periodSizeInFrames, deviceType == ma_device_type_capture, pDevice); if (deviceIndex < 0) { return MA_FAILED_TO_OPEN_BACKEND_DEVICE; } if (deviceType == ma_device_type_capture) { - pDevice->webaudio.indexCapture = deviceIndex; - pDevice->capture.internalFormat = ma_format_f32; - pDevice->capture.internalChannels = pConfig->capture.channels; - ma_get_standard_channel_map(ma_standard_channel_map_webaudio, pDevice->capture.internalChannels, pDevice->capture.internalChannelMap); - pDevice->capture.internalSampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - pDevice->capture.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->capture.internalPeriods = 1; + pDevice->webaudio.indexCapture = deviceIndex; } else { - pDevice->webaudio.indexPlayback = deviceIndex; - pDevice->playback.internalFormat = ma_format_f32; - pDevice->playback.internalChannels = pConfig->playback.channels; - ma_get_standard_channel_map(ma_standard_channel_map_webaudio, pDevice->playback.internalChannels, pDevice->playback.internalChannelMap); - pDevice->playback.internalSampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); - pDevice->playback.internalPeriodSizeInFrames = internalPeriodSizeInFrames; - pDevice->playback.internalPeriods = 1; + pDevice->webaudio.indexPlayback = deviceIndex; } + pDescriptor->format = ma_format_f32; + pDescriptor->channels = channels; + ma_get_standard_channel_map(ma_standard_channel_map_webaudio, pDescriptor->channels, pDescriptor->channelMap); + pDescriptor->sampleRate = EM_ASM_INT({ return miniaudio.get_device_by_index($0).webaudio.sampleRate; }, deviceIndex); + pDescriptor->periodSizeInFrames = periodSizeInFrames; + pDescriptor->periodCount = 1; + return MA_SUCCESS; } -static ma_result ma_device_init__webaudio(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) +static ma_result ma_device_init__webaudio(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) { ma_result result; @@ -30493,20 +31785,20 @@ static ma_result ma_device_init__webaudio(ma_context* pContext, const ma_device_ } /* No exclusive mode with Web Audio. */ - if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pConfig->playback.shareMode == ma_share_mode_exclusive) || - ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pConfig->capture.shareMode == ma_share_mode_exclusive)) { + if (((pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) && pDescriptorPlayback->shareMode == ma_share_mode_exclusive) || + ((pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) && pDescriptorCapture->shareMode == ma_share_mode_exclusive)) { return MA_SHARE_MODE_NOT_SUPPORTED; } if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_by_type__webaudio(pContext, pConfig, ma_device_type_capture, pDevice); + result = ma_device_init_by_type__webaudio(pDevice, pConfig, pDescriptorCapture, ma_device_type_capture); if (result != MA_SUCCESS) { return result; } } if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { - result = ma_device_init_by_type__webaudio(pContext, pConfig, ma_device_type_playback, pDevice); + result = ma_device_init_by_type__webaudio(pDevice, pConfig, pDescriptorPlayback, ma_device_type_playback); if (result != MA_SUCCESS) { if (pConfig->deviceType == ma_device_type_duplex) { ma_device_uninit_by_index__webaudio(pDevice, ma_device_type_capture, pDevice->webaudio.indexCapture); @@ -30515,36 +31807,6 @@ static ma_result ma_device_init__webaudio(ma_context* pContext, const ma_device_ } } - /* - We need a ring buffer for moving data from the capture device to the playback device. The capture callback is the producer - and the playback callback is the consumer. The buffer needs to be large enough to hold internalPeriodSizeInFrames based on - the external sample rate. - */ - if (pConfig->deviceType == ma_device_type_duplex) { - ma_uint32 rbSizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames) * 2; - result = ma_pcm_rb_init(pDevice->capture.format, pDevice->capture.channels, rbSizeInFrames, NULL, &pDevice->pContext->allocationCallbacks, &pDevice->webaudio.duplexRB); - if (result != MA_SUCCESS) { - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - ma_device_uninit_by_index__webaudio(pDevice, ma_device_type_capture, pDevice->webaudio.indexCapture); - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { - ma_device_uninit_by_index__webaudio(pDevice, ma_device_type_playback, pDevice->webaudio.indexPlayback); - } - return result; - } - - /* We need a period to act as a buffer for cases where the playback and capture device's end up desyncing. */ - { - ma_uint32 marginSizeInFrames = rbSizeInFrames / 3; /* <-- Dividing by 3 because internalPeriods is always set to 1 for WebAudio. */ - void* pMarginData; - ma_pcm_rb_acquire_write(&pDevice->webaudio.duplexRB, &marginSizeInFrames, &pMarginData); - { - MA_ZERO_MEMORY(pMarginData, marginSizeInFrames * ma_get_bytes_per_frame(pDevice->capture.format, pDevice->capture.channels)); - } - ma_pcm_rb_commit_write(&pDevice->webaudio.duplexRB, marginSizeInFrames, pMarginData); - } - } - return MA_SUCCESS; } @@ -30554,13 +31816,17 @@ static ma_result ma_device_start__webaudio(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { EM_ASM({ - miniaudio.get_device_by_index($0).webaudio.resume(); + var device = miniaudio.get_device_by_index($0); + device.webaudio.resume(); + device.state = 2; /* MA_STATE_STARTED */ }, pDevice->webaudio.indexCapture); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { EM_ASM({ - miniaudio.get_device_by_index($0).webaudio.resume(); + var device = miniaudio.get_device_by_index($0); + device.webaudio.resume(); + device.state = 2; /* MA_STATE_STARTED */ }, pDevice->webaudio.indexPlayback); } @@ -30583,13 +31849,17 @@ static ma_result ma_device_stop__webaudio(ma_device* pDevice) if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { EM_ASM({ - miniaudio.get_device_by_index($0).webaudio.suspend(); + var device = miniaudio.get_device_by_index($0); + device.webaudio.suspend(); + device.state = 1; /* MA_STATE_STOPPED */ }, pDevice->webaudio.indexCapture); } if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { EM_ASM({ - miniaudio.get_device_by_index($0).webaudio.suspend(); + var device = miniaudio.get_device_by_index($0); + device.webaudio.suspend(); + device.state = 1; /* MA_STATE_STOPPED */ }, pDevice->webaudio.indexPlayback); } @@ -30612,12 +31882,14 @@ static ma_result ma_context_uninit__webaudio(ma_context* pContext) return MA_SUCCESS; } -static ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_context* pContext) +static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) { int resultFromJS; MA_ASSERT(pContext != NULL); + (void)pConfig; /* Unused. */ + /* Here is where our global JavaScript object is initialized. */ resultFromJS = EM_ASM_INT({ if ((window.AudioContext || window.webkitAudioContext) === undefined) { @@ -30627,7 +31899,7 @@ static ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_ if (typeof(miniaudio) === 'undefined') { miniaudio = {}; miniaudio.devices = []; /* Device cache for mapping devices to indexes for JavaScript/C interop. */ - + miniaudio.track_device = function(device) { /* Try inserting into a free slot first. */ for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { @@ -30636,16 +31908,16 @@ static ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_ return iDevice; } } - + /* Getting here means there is no empty slots in the array so we just push to the end. */ miniaudio.devices.push(device); return miniaudio.devices.length - 1; }; - + miniaudio.untrack_device_by_index = function(deviceIndex) { /* We just set the device's slot to null. The slot will get reused in the next call to ma_track_device. */ miniaudio.devices[deviceIndex] = null; - + /* Trim the array if possible. */ while (miniaudio.devices.length > 0) { if (miniaudio.devices[miniaudio.devices.length-1] == null) { @@ -30655,7 +31927,7 @@ static ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_ } } }; - + miniaudio.untrack_device = function(device) { for (var iDevice = 0; iDevice < miniaudio.devices.length; ++iDevice) { if (miniaudio.devices[iDevice] == device) { @@ -30663,12 +31935,32 @@ static ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_ } } }; - + miniaudio.get_device_by_index = function(deviceIndex) { return miniaudio.devices[deviceIndex]; }; + + miniaudio.unlock_event_types = (function(){ + return ['touchstart', 'touchend', 'click']; + })(); + + miniaudio.unlock = function() { + for(var i = 0; i < miniaudio.devices.length; ++i) { + var device = miniaudio.devices[i]; + if (device != null && device.webaudio != null && device.state === 2 /* MA_STATE_STARTED */) { + device.webaudio.resume(); + } + } + miniaudio.unlock_event_types.map(function(event_type) { + document.removeEventListener(event_type, miniaudio.unlock, true); + }); + }; + + miniaudio.unlock_event_types.map(function(event_type) { + document.addEventListener(event_type, miniaudio.unlock, true); + }); } - + return 1; }, 0); /* Must pass in a dummy argument for C99 compatibility. */ @@ -30676,19 +31968,18 @@ static ma_result ma_context_init__webaudio(const ma_context_config* pConfig, ma_ return MA_FAILED_TO_INIT_BACKEND; } + pCallbacks->onContextInit = ma_context_init__webaudio; + pCallbacks->onContextUninit = ma_context_uninit__webaudio; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__webaudio; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__webaudio; + pCallbacks->onDeviceInit = ma_device_init__webaudio; + pCallbacks->onDeviceUninit = ma_device_uninit__webaudio; + pCallbacks->onDeviceStart = ma_device_start__webaudio; + pCallbacks->onDeviceStop = ma_device_stop__webaudio; + pCallbacks->onDeviceRead = NULL; /* Not needed because WebAudio is asynchronous. */ + pCallbacks->onDeviceWrite = NULL; /* Not needed because WebAudio is asynchronous. */ + pCallbacks->onDeviceDataLoop = NULL; /* Not needed because WebAudio is asynchronous. */ - pContext->isBackendAsynchronous = MA_TRUE; - - pContext->onUninit = ma_context_uninit__webaudio; - pContext->onDeviceIDEqual = ma_context_is_device_id_equal__webaudio; - pContext->onEnumDevices = ma_context_enumerate_devices__webaudio; - pContext->onGetDeviceInfo = ma_context_get_device_info__webaudio; - pContext->onDeviceInit = ma_device_init__webaudio; - pContext->onDeviceUninit = ma_device_uninit__webaudio; - pContext->onDeviceStart = ma_device_start__webaudio; - pContext->onDeviceStop = ma_device_stop__webaudio; - - (void)pConfig; /* Unused. */ return MA_SUCCESS; } #endif /* Web Audio */ @@ -30727,40 +32018,48 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d MA_ASSERT(pDevice != NULL); if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { - if (pDevice->capture.usingDefaultFormat) { + if (pDevice->capture.format == ma_format_unknown) { pDevice->capture.format = pDevice->capture.internalFormat; } - if (pDevice->capture.usingDefaultChannels) { + if (pDevice->capture.channels == 0) { pDevice->capture.channels = pDevice->capture.internalChannels; } - if (pDevice->capture.usingDefaultChannelMap) { + if (pDevice->capture.channelMap[0] == MA_CHANNEL_NONE) { MA_ASSERT(pDevice->capture.channels <= MA_MAX_CHANNELS); if (pDevice->capture.internalChannels == pDevice->capture.channels) { ma_channel_map_copy(pDevice->capture.channelMap, pDevice->capture.internalChannelMap, pDevice->capture.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->capture.channels, pDevice->capture.channelMap); + if (pDevice->capture.channelMixMode == ma_channel_mix_mode_simple) { + ma_channel_map_init_blank(pDevice->capture.channels, pDevice->capture.channelMap); + } else { + ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->capture.channels, pDevice->capture.channelMap); + } } } } if (deviceType == ma_device_type_playback || deviceType == ma_device_type_duplex) { - if (pDevice->playback.usingDefaultFormat) { + if (pDevice->playback.format == ma_format_unknown) { pDevice->playback.format = pDevice->playback.internalFormat; } - if (pDevice->playback.usingDefaultChannels) { + if (pDevice->playback.channels == 0) { pDevice->playback.channels = pDevice->playback.internalChannels; } - if (pDevice->playback.usingDefaultChannelMap) { + if (pDevice->playback.channelMap[0] == MA_CHANNEL_NONE) { MA_ASSERT(pDevice->playback.channels <= MA_MAX_CHANNELS); if (pDevice->playback.internalChannels == pDevice->playback.channels) { ma_channel_map_copy(pDevice->playback.channelMap, pDevice->playback.internalChannelMap, pDevice->playback.channels); } else { - ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->playback.channels, pDevice->playback.channelMap); + if (pDevice->playback.channelMixMode == ma_channel_mix_mode_simple) { + ma_channel_map_init_blank(pDevice->playback.channels, pDevice->playback.channelMap); + } else { + ma_get_standard_channel_map(ma_standard_channel_map_default, pDevice->playback.channels, pDevice->playback.channelMap); + } } } } - if (pDevice->usingDefaultSampleRate) { + if (pDevice->sampleRate == 0) { if (deviceType == ma_device_type_capture || deviceType == ma_device_type_duplex) { pDevice->sampleRate = pDevice->capture.internalSampleRate; } else { @@ -30780,6 +32079,7 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d converterConfig.channelsOut = pDevice->capture.channels; converterConfig.sampleRateOut = pDevice->sampleRate; ma_channel_map_copy(converterConfig.channelMapOut, pDevice->capture.channelMap, ma_min(pDevice->capture.channels, MA_MAX_CHANNELS)); + converterConfig.channelMixMode = pDevice->capture.channelMixMode; converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; converterConfig.resampling.algorithm = pDevice->resampling.algorithm; converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; @@ -30802,6 +32102,7 @@ static ma_result ma_device__post_init_setup(ma_device* pDevice, ma_device_type d converterConfig.channelsOut = pDevice->playback.internalChannels; converterConfig.sampleRateOut = pDevice->playback.internalSampleRate; ma_channel_map_copy(converterConfig.channelMapOut, pDevice->playback.internalChannelMap, ma_min(pDevice->playback.internalChannels, MA_MAX_CHANNELS)); + converterConfig.channelMixMode = pDevice->playback.channelMixMode; converterConfig.resampling.allowDynamicSampleRate = MA_FALSE; converterConfig.resampling.algorithm = pDevice->resampling.algorithm; converterConfig.resampling.linear.lpfOrder = pDevice->resampling.linear.lpfOrder; @@ -30845,7 +32146,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) pDevice->workResult = MA_SUCCESS; /* If the reason for the wake up is that we are terminating, just break from the loop. */ - if (ma_device__get_state(pDevice) == MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { break; } @@ -30854,16 +32155,25 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) be started will be waiting on an event (pDevice->startEvent) which means we need to make sure we signal the event in both the success and error case. It's important that the state of the device is set _before_ signaling the event. */ - MA_ASSERT(ma_device__get_state(pDevice) == MA_STATE_STARTING); + MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTING); + + /* If the device has a start callback, start it now. */ + if (pDevice->pContext->callbacks.onDeviceStart != NULL) { + ma_result result = pDevice->pContext->callbacks.onDeviceStart(pDevice); + if (result != MA_SUCCESS) { + pDevice->workResult = result; /* Failed to start the device. */ + } + } /* Make sure the state is set appropriately. */ ma_device__set_state(pDevice, MA_STATE_STARTED); ma_event_signal(&pDevice->startEvent); - if (pDevice->pContext->onDeviceMainLoop != NULL) { - pDevice->pContext->onDeviceMainLoop(pDevice); + if (pDevice->pContext->callbacks.onDeviceDataLoop != NULL) { + pDevice->pContext->callbacks.onDeviceDataLoop(pDevice); } else { - ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "No main loop implementation.", MA_API_NOT_FOUND); + /* The backend is not using a custom main loop implementation, so now fall back to the blocking read-write implementation. */ + ma_device_audio_thread__default_read_write(pDevice); } /* @@ -30871,9 +32181,9 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) may have actually already happened above if the device was lost and miniaudio has attempted to re-initialize the device. In this case we don't want to be doing this a second time. */ - if (ma_device__get_state(pDevice) != MA_STATE_UNINITIALIZED) { - if (pDevice->pContext->onDeviceStop) { - pDevice->pContext->onDeviceStop(pDevice); + if (ma_device_get_state(pDevice) != MA_STATE_UNINITIALIZED) { + if (pDevice->pContext->callbacks.onDeviceStop != NULL) { + pDevice->pContext->callbacks.onDeviceStop(pDevice); } } @@ -30888,7 +32198,7 @@ static ma_thread_result MA_THREADCALL ma_worker_thread(void* pData) it's possible that the device has been uninitialized which means we need to _not_ change the status to stopped. We cannot go from an uninitialized state to stopped state. */ - if (ma_device__get_state(pDevice) != MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) != MA_STATE_UNINITIALIZED) { ma_device__set_state(pDevice, MA_STATE_STOPPED); ma_event_signal(&pDevice->stopEvent); } @@ -30912,7 +32222,7 @@ static ma_bool32 ma_device__is_initialized(ma_device* pDevice) return MA_FALSE; } - return ma_device__get_state(pDevice) != MA_STATE_UNINITIALIZED; + return ma_device_get_state(pDevice) != MA_STATE_UNINITIALIZED; } @@ -31068,7 +32378,17 @@ static ma_result ma_context_uninit_backend_apis(ma_context* pContext) static ma_bool32 ma_context_is_backend_asynchronous(ma_context* pContext) { - return pContext->isBackendAsynchronous; + MA_ASSERT(pContext != NULL); + + if (pContext->callbacks.onDeviceRead == NULL && pContext->callbacks.onDeviceWrite == NULL) { + if (pContext->callbacks.onDeviceDataLoop == NULL) { + return MA_TRUE; + } else { + return MA_FALSE; + } + } else { + return MA_FALSE; + } } @@ -31083,7 +32403,7 @@ MA_API ma_context_config ma_context_config_init() MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendCount, const ma_context_config* pConfig, ma_context* pContext) { ma_result result; - ma_context_config config; + ma_context_config defaultConfig; ma_backend defaultBackends[ma_backend_null+1]; ma_uint32 iBackend; ma_backend* pBackendsToIterate; @@ -31096,18 +32416,17 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC MA_ZERO_OBJECT(pContext); /* Always make sure the config is set first to ensure properties are available as soon as possible. */ - if (pConfig != NULL) { - config = *pConfig; - } else { - config = ma_context_config_init(); + if (pConfig == NULL) { + defaultConfig = ma_context_config_init(); + pConfig = &defaultConfig; } - pContext->logCallback = config.logCallback; - pContext->threadPriority = config.threadPriority; - pContext->threadStackSize = config.threadStackSize; - pContext->pUserData = config.pUserData; + pContext->logCallback = pConfig->logCallback; + pContext->threadPriority = pConfig->threadPriority; + pContext->threadStackSize = pConfig->threadStackSize; + pContext->pUserData = pConfig->pUserData; - result = ma_allocation_callbacks_init_copy(&pContext->allocationCallbacks, &config.allocationCallbacks); + result = ma_allocation_callbacks_init_copy(&pContext->allocationCallbacks, &pConfig->allocationCallbacks); if (result != MA_SUCCESS) { return result; } @@ -31131,120 +32450,142 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC MA_ASSERT(pBackendsToIterate != NULL); - for (iBackend = 0; iBackend < backendsToIterateCount; ++iBackend) { + for (iBackend = 0; iBackend < backendsToIterateCount; iBackend += 1) { ma_backend backend = pBackendsToIterate[iBackend]; - result = MA_NO_BACKEND; + /* Make sure all callbacks are reset so we don't accidentally drag in any from previously failed initialization attempts. */ + MA_ZERO_OBJECT(&pContext->callbacks); + + /* These backends are using the new callback system. */ switch (backend) { #ifdef MA_HAS_WASAPI case ma_backend_wasapi: { - result = ma_context_init__wasapi(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__wasapi; } break; #endif #ifdef MA_HAS_DSOUND case ma_backend_dsound: { - result = ma_context_init__dsound(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__dsound; } break; #endif #ifdef MA_HAS_WINMM case ma_backend_winmm: { - result = ma_context_init__winmm(&config, pContext); - } break; - #endif - #ifdef MA_HAS_ALSA - case ma_backend_alsa: - { - result = ma_context_init__alsa(&config, pContext); - } break; - #endif - #ifdef MA_HAS_PULSEAUDIO - case ma_backend_pulseaudio: - { - result = ma_context_init__pulse(&config, pContext); - } break; - #endif - #ifdef MA_HAS_JACK - case ma_backend_jack: - { - result = ma_context_init__jack(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__winmm; } break; #endif #ifdef MA_HAS_COREAUDIO case ma_backend_coreaudio: { - result = ma_context_init__coreaudio(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__coreaudio; } break; #endif #ifdef MA_HAS_SNDIO case ma_backend_sndio: { - result = ma_context_init__sndio(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__sndio; } break; #endif #ifdef MA_HAS_AUDIO4 case ma_backend_audio4: { - result = ma_context_init__audio4(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__audio4; } break; #endif #ifdef MA_HAS_OSS case ma_backend_oss: { - result = ma_context_init__oss(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__oss; + } break; + #endif + #ifdef MA_HAS_PULSEAUDIO + case ma_backend_pulseaudio: + { + pContext->callbacks.onContextInit = ma_context_init__pulse; + } break; + #endif + #ifdef MA_HAS_ALSA + case ma_backend_alsa: + { + pContext->callbacks.onContextInit = ma_context_init__alsa; + } break; + #endif + #ifdef MA_HAS_JACK + case ma_backend_jack: + { + pContext->callbacks.onContextInit = ma_context_init__jack; } break; #endif #ifdef MA_HAS_AAUDIO case ma_backend_aaudio: { - result = ma_context_init__aaudio(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__aaudio; } break; #endif #ifdef MA_HAS_OPENSL case ma_backend_opensl: { - result = ma_context_init__opensl(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__opensl; } break; #endif #ifdef MA_HAS_WEBAUDIO case ma_backend_webaudio: { - result = ma_context_init__webaudio(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__webaudio; + } break; + #endif + #ifdef MA_HAS_CUSTOM + case ma_backend_custom: + { + /* Slightly different logic for custom backends. Custom backends can optionally set all of their callbacks in the config. */ + pContext->callbacks = pConfig->custom; } break; #endif #ifdef MA_HAS_NULL case ma_backend_null: { - result = ma_context_init__null(&config, pContext); + pContext->callbacks.onContextInit = ma_context_init__null; } break; #endif default: break; } + if (pContext->callbacks.onContextInit != NULL) { + ma_post_log_messagef(pContext, NULL, MA_LOG_LEVEL_VERBOSE, "Attempting to initialize %s backend...", ma_get_backend_name(backend)); + result = pContext->callbacks.onContextInit(pContext, pConfig, &pContext->callbacks); + } else { + result = MA_NO_BACKEND; + } + /* If this iteration was successful, return. */ if (result == MA_SUCCESS) { result = ma_mutex_init(&pContext->deviceEnumLock); if (result != MA_SUCCESS) { ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device enumeration. ma_context_get_devices() is not thread safe.", result); } + result = ma_mutex_init(&pContext->deviceInfoLock); if (result != MA_SUCCESS) { ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_WARNING, "Failed to initialize mutex for device info retrieval. ma_context_get_device_info() is not thread safe.", result); } -#ifdef MA_DEBUG_OUTPUT - printf("[miniaudio] Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); - printf("[miniaudio] SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); - printf("[miniaudio] AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); - printf("[miniaudio] AVX512F: %s\n", ma_has_avx512f() ? "YES" : "NO"); - printf("[miniaudio] NEON: %s\n", ma_has_neon() ? "YES" : "NO"); -#endif + #ifdef MA_DEBUG_OUTPUT + { + printf("[miniaudio] Endian: %s\n", ma_is_little_endian() ? "LE" : "BE"); + printf("[miniaudio] SSE2: %s\n", ma_has_sse2() ? "YES" : "NO"); + printf("[miniaudio] AVX2: %s\n", ma_has_avx2() ? "YES" : "NO"); + printf("[miniaudio] AVX512F: %s\n", ma_has_avx512f() ? "YES" : "NO"); + printf("[miniaudio] NEON: %s\n", ma_has_neon() ? "YES" : "NO"); + } + #endif pContext->backend = backend; return result; + } else { + ma_post_log_messagef(pContext, NULL, MA_LOG_LEVEL_VERBOSE, "Failed to initialize %s backend.", ma_get_backend_name(backend)); } } @@ -31259,7 +32600,9 @@ MA_API ma_result ma_context_uninit(ma_context* pContext) return MA_INVALID_ARGS; } - pContext->onUninit(pContext); + if (pContext->callbacks.onContextUninit != NULL) { + pContext->callbacks.onContextUninit(pContext); + } ma_mutex_uninit(&pContext->deviceEnumLock); ma_mutex_uninit(&pContext->deviceInfoLock); @@ -31279,13 +32622,17 @@ MA_API ma_result ma_context_enumerate_devices(ma_context* pContext, ma_enum_devi { ma_result result; - if (pContext == NULL || pContext->onEnumDevices == NULL || callback == NULL) { + if (pContext == NULL || callback == NULL) { return MA_INVALID_ARGS; } + if (pContext->callbacks.onContextEnumerateDevices == NULL) { + return MA_INVALID_OPERATION; + } + ma_mutex_lock(&pContext->deviceEnumLock); { - result = pContext->onEnumDevices(pContext, callback, pUserData); + result = pContext->callbacks.onContextEnumerateDevices(pContext, callback, pUserData); } ma_mutex_unlock(&pContext->deviceEnumLock); @@ -31307,7 +32654,7 @@ static ma_bool32 ma_context_get_devices__enum_callback(ma_context* pContext, ma_ const ma_uint32 bufferExpansionCount = 2; const ma_uint32 totalDeviceInfoCount = pContext->playbackDeviceInfoCount + pContext->captureDeviceInfoCount; - if (pContext->deviceInfoCapacity >= totalDeviceInfoCount) { + if (totalDeviceInfoCount >= pContext->deviceInfoCapacity) { ma_uint32 oldCapacity = pContext->deviceInfoCapacity; ma_uint32 newCapacity = oldCapacity + bufferExpansionCount; ma_device_info* pNewInfos = (ma_device_info*)ma__realloc_from_callbacks(pContext->pDeviceInfos, sizeof(*pContext->pDeviceInfos)*newCapacity, sizeof(*pContext->pDeviceInfos)*oldCapacity, &pContext->allocationCallbacks); @@ -31352,10 +32699,14 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p if (ppCaptureDeviceInfos != NULL) *ppCaptureDeviceInfos = NULL; if (pCaptureDeviceCount != NULL) *pCaptureDeviceCount = 0; - if (pContext == NULL || pContext->onEnumDevices == NULL) { + if (pContext == NULL) { return MA_INVALID_ARGS; } + if (pContext->callbacks.onContextEnumerateDevices == NULL) { + return MA_INVALID_OPERATION; + } + /* Note that we don't use ma_context_enumerate_devices() here because we want to do locking at a higher level. */ ma_mutex_lock(&pContext->deviceEnumLock); { @@ -31364,7 +32715,7 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p pContext->captureDeviceInfoCount = 0; /* Now enumerate over available devices. */ - result = pContext->onEnumDevices(pContext, ma_context_get_devices__enum_callback, NULL); + result = pContext->callbacks.onContextEnumerateDevices(pContext, ma_context_get_devices__enum_callback, NULL); if (result == MA_SUCCESS) { /* Playback devices. */ if (ppPlaybackDeviceInfos != NULL) { @@ -31390,8 +32741,11 @@ MA_API ma_result ma_context_get_devices(ma_context* pContext, ma_device_info** p MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_share_mode shareMode, ma_device_info* pDeviceInfo) { + ma_result result; ma_device_info deviceInfo; + (void)shareMode; /* Unused. This parameter will be removed in version 0.11. */ + /* NOTE: Do not clear pDeviceInfo on entry. The reason is the pDeviceID may actually point to pDeviceInfo->id which will break things. */ if (pContext == NULL || pDeviceInfo == NULL) { return MA_INVALID_ARGS; @@ -31404,27 +32758,93 @@ MA_API ma_result ma_context_get_device_info(ma_context* pContext, ma_device_type MA_COPY_MEMORY(&deviceInfo.id, pDeviceID, sizeof(*pDeviceID)); } - /* The backend may have an optimized device info retrieval function. If so, try that first. */ - if (pContext->onGetDeviceInfo != NULL) { - ma_result result; - ma_mutex_lock(&pContext->deviceInfoLock); - { - result = pContext->onGetDeviceInfo(pContext, deviceType, pDeviceID, shareMode, &deviceInfo); - } - ma_mutex_unlock(&pContext->deviceInfoLock); - - /* Clamp ranges. */ - deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS); - deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS); - deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, MA_MIN_SAMPLE_RATE); - deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, MA_MAX_SAMPLE_RATE); - - *pDeviceInfo = deviceInfo; - return result; + if (pContext->callbacks.onContextGetDeviceInfo == NULL) { + return MA_INVALID_OPERATION; } - /* Getting here means onGetDeviceInfo has not been set. */ - return MA_ERROR; + ma_mutex_lock(&pContext->deviceInfoLock); + { + result = pContext->callbacks.onContextGetDeviceInfo(pContext, deviceType, pDeviceID, &deviceInfo); + } + ma_mutex_unlock(&pContext->deviceInfoLock); + + /* + If the backend is using the new device info system, do a pass to fill out the old settings for backwards compatibility. This will be removed in + the future when all backends have implemented the new device info system. + */ + if (deviceInfo.nativeDataFormatCount > 0) { + ma_uint32 iNativeFormat; + ma_uint32 iSampleFormat; + + deviceInfo.minChannels = 0xFFFFFFFF; + deviceInfo.maxChannels = 0; + deviceInfo.minSampleRate = 0xFFFFFFFF; + deviceInfo.maxSampleRate = 0; + + for (iNativeFormat = 0; iNativeFormat < deviceInfo.nativeDataFormatCount; iNativeFormat += 1) { + /* Formats. */ + if (deviceInfo.nativeDataFormats[iNativeFormat].format == ma_format_unknown) { + /* All formats are supported. */ + deviceInfo.formats[0] = ma_format_u8; + deviceInfo.formats[1] = ma_format_s16; + deviceInfo.formats[2] = ma_format_s24; + deviceInfo.formats[3] = ma_format_s32; + deviceInfo.formats[4] = ma_format_f32; + deviceInfo.formatCount = 5; + } else { + /* Make sure the format isn't already in the list. If so, skip. */ + ma_bool32 alreadyExists = MA_FALSE; + for (iSampleFormat = 0; iSampleFormat < deviceInfo.formatCount; iSampleFormat += 1) { + if (deviceInfo.formats[iSampleFormat] == deviceInfo.nativeDataFormats[iNativeFormat].format) { + alreadyExists = MA_TRUE; + break; + } + } + + if (!alreadyExists) { + deviceInfo.formats[deviceInfo.formatCount++] = deviceInfo.nativeDataFormats[iNativeFormat].format; + } + } + + /* Channels. */ + if (deviceInfo.nativeDataFormats[iNativeFormat].channels == 0) { + /* All channels supported. */ + deviceInfo.minChannels = MA_MIN_CHANNELS; + deviceInfo.maxChannels = MA_MAX_CHANNELS; + } else { + if (deviceInfo.minChannels > deviceInfo.nativeDataFormats[iNativeFormat].channels) { + deviceInfo.minChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; + } + if (deviceInfo.maxChannels < deviceInfo.nativeDataFormats[iNativeFormat].channels) { + deviceInfo.maxChannels = deviceInfo.nativeDataFormats[iNativeFormat].channels; + } + } + + /* Sample rate. */ + if (deviceInfo.nativeDataFormats[iNativeFormat].sampleRate == 0) { + /* All sample rates supported. */ + deviceInfo.minSampleRate = (ma_uint32)ma_standard_sample_rate_min; + deviceInfo.maxSampleRate = (ma_uint32)ma_standard_sample_rate_max; + } else { + if (deviceInfo.minSampleRate > deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { + deviceInfo.minSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; + } + if (deviceInfo.maxSampleRate < deviceInfo.nativeDataFormats[iNativeFormat].sampleRate) { + deviceInfo.maxSampleRate = deviceInfo.nativeDataFormats[iNativeFormat].sampleRate; + } + } + } + } + + + /* Clamp ranges. */ + deviceInfo.minChannels = ma_max(deviceInfo.minChannels, MA_MIN_CHANNELS); + deviceInfo.maxChannels = ma_min(deviceInfo.maxChannels, MA_MAX_CHANNELS); + deviceInfo.minSampleRate = ma_max(deviceInfo.minSampleRate, (ma_uint32)ma_standard_sample_rate_min); + deviceInfo.maxSampleRate = ma_min(deviceInfo.maxSampleRate, (ma_uint32)ma_standard_sample_rate_max); + + *pDeviceInfo = deviceInfo; + return result; } MA_API ma_bool32 ma_context_is_loopback_supported(ma_context* pContext) @@ -31454,52 +32874,60 @@ MA_API ma_device_config ma_device_config_init(ma_device_type deviceType) MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pConfig, ma_device* pDevice) { ma_result result; - ma_device_config config; + ma_device_descriptor descriptorPlayback; + ma_device_descriptor descriptorCapture; + /* The context can be null, in which case we self-manage it. */ if (pContext == NULL) { return ma_device_init_ex(NULL, 0, NULL, pConfig, pDevice); } + if (pDevice == NULL) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); } + + MA_ZERO_OBJECT(pDevice); + if (pConfig == NULL) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid arguments (pConfig == NULL).", MA_INVALID_ARGS); } - /* We need to make a copy of the config so we can set default values if they were left unset in the input config. */ - config = *pConfig; + + /* Check that we have our callbacks defined. */ + if (pContext->callbacks.onDeviceInit == NULL) { + return MA_INVALID_OPERATION; + } + /* Basic config validation. */ - if (config.deviceType != ma_device_type_playback && config.deviceType != ma_device_type_capture && config.deviceType != ma_device_type_duplex && config.deviceType != ma_device_type_loopback) { + if (pConfig->deviceType != ma_device_type_playback && pConfig->deviceType != ma_device_type_capture && pConfig->deviceType != ma_device_type_duplex && pConfig->deviceType != ma_device_type_loopback) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Device type is invalid. Make sure the device type has been set in the config.", MA_INVALID_DEVICE_CONFIG); } - if (config.deviceType == ma_device_type_capture || config.deviceType == ma_device_type_duplex) { - if (config.capture.channels > MA_MAX_CHANNELS) { + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + if (pConfig->capture.channels > MA_MAX_CHANNELS) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Capture channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); } - if (!ma__is_channel_map_valid(config.capture.channelMap, config.capture.channels)) { + if (!ma__is_channel_map_valid(pConfig->capture.channelMap, pConfig->capture.channels)) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Capture channel map is invalid.", MA_INVALID_DEVICE_CONFIG); } } - if (config.deviceType == ma_device_type_playback || config.deviceType == ma_device_type_duplex || config.deviceType == ma_device_type_loopback) { - if (config.playback.channels > MA_MAX_CHANNELS) { + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { + if (pConfig->playback.channels > MA_MAX_CHANNELS) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with an invalid config. Playback channel count cannot exceed 32.", MA_INVALID_DEVICE_CONFIG); } - if (!ma__is_channel_map_valid(config.playback.channelMap, config.playback.channels)) { + if (!ma__is_channel_map_valid(pConfig->playback.channelMap, pConfig->playback.channels)) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "ma_device_init() called with invalid config. Playback channel map is invalid.", MA_INVALID_DEVICE_CONFIG); } } - - MA_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 = config.pUserData; - pDevice->onData = config.dataCallback; - pDevice->onStop = config.stopCallback; + pDevice->pUserData = pConfig->pUserData; + pDevice->onData = pConfig->dataCallback; + pDevice->onStop = pConfig->stopCallback; if (((ma_uintptr)pDevice % sizeof(pDevice)) != 0) { if (pContext->logCallback) { @@ -31507,103 +32935,38 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC } } - if (config.playback.pDeviceID != NULL) { - MA_COPY_MEMORY(&pDevice->playback.id, config.playback.pDeviceID, sizeof(pDevice->playback.id)); - } - if (config.capture.pDeviceID != NULL) { - MA_COPY_MEMORY(&pDevice->capture.id, config.capture.pDeviceID, sizeof(pDevice->capture.id)); + if (pConfig->playback.pDeviceID != NULL) { + MA_COPY_MEMORY(&pDevice->playback.id, pConfig->playback.pDeviceID, sizeof(pDevice->playback.id)); } - pDevice->noPreZeroedOutputBuffer = config.noPreZeroedOutputBuffer; - pDevice->noClip = config.noClip; + if (pConfig->capture.pDeviceID != NULL) { + MA_COPY_MEMORY(&pDevice->capture.id, pConfig->capture.pDeviceID, sizeof(pDevice->capture.id)); + } + + pDevice->noPreZeroedOutputBuffer = pConfig->noPreZeroedOutputBuffer; + pDevice->noClip = pConfig->noClip; pDevice->masterVolumeFactor = 1; - /* - When passing in 0 for the format/channels/rate/chmap it means the device will be using whatever is chosen by the backend. If everything is set - to defaults it means the format conversion pipeline will run on a fast path where data transfer is just passed straight through to the backend. - */ - if (config.sampleRate == 0) { - config.sampleRate = MA_DEFAULT_SAMPLE_RATE; - pDevice->usingDefaultSampleRate = MA_TRUE; - } + pDevice->type = pConfig->deviceType; + pDevice->sampleRate = pConfig->sampleRate; + pDevice->resampling.algorithm = pConfig->resampling.algorithm; + pDevice->resampling.linear.lpfOrder = pConfig->resampling.linear.lpfOrder; + pDevice->resampling.speex.quality = pConfig->resampling.speex.quality; - if (config.capture.format == ma_format_unknown) { - config.capture.format = MA_DEFAULT_FORMAT; - pDevice->capture.usingDefaultFormat = MA_TRUE; - } - if (config.capture.channels == 0) { - config.capture.channels = MA_DEFAULT_CHANNELS; - pDevice->capture.usingDefaultChannels = MA_TRUE; - } - if (config.capture.channelMap[0] == MA_CHANNEL_NONE) { - pDevice->capture.usingDefaultChannelMap = MA_TRUE; - } - - if (config.playback.format == ma_format_unknown) { - config.playback.format = MA_DEFAULT_FORMAT; - pDevice->playback.usingDefaultFormat = MA_TRUE; - } - if (config.playback.channels == 0) { - config.playback.channels = MA_DEFAULT_CHANNELS; - pDevice->playback.usingDefaultChannels = MA_TRUE; - } - if (config.playback.channelMap[0] == MA_CHANNEL_NONE) { - pDevice->playback.usingDefaultChannelMap = MA_TRUE; - } - - - /* Default periods. */ - if (config.periods == 0) { - config.periods = MA_DEFAULT_PERIODS; - pDevice->usingDefaultPeriods = MA_TRUE; - } - - /* - Must have at least 3 periods for full-duplex mode. The idea is that the playback and capture positions hang out in the middle period, with the surrounding - periods acting as a buffer in case the capture and playback devices get's slightly out of sync. - */ - if (config.deviceType == ma_device_type_duplex && config.periods < 3) { - config.periods = 3; - } - - /* Default buffer size. */ - if (config.periodSizeInMilliseconds == 0 && config.periodSizeInFrames == 0) { - config.periodSizeInMilliseconds = (config.performanceProfile == ma_performance_profile_low_latency) ? MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY : MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE; - pDevice->usingDefaultBufferSize = MA_TRUE; - } + pDevice->capture.shareMode = pConfig->capture.shareMode; + pDevice->capture.format = pConfig->capture.format; + pDevice->capture.channels = pConfig->capture.channels; + ma_channel_map_copy(pDevice->capture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); + pDevice->capture.channelMixMode = pConfig->capture.channelMixMode; - MA_ASSERT(config.capture.channels <= MA_MAX_CHANNELS); - MA_ASSERT(config.playback.channels <= MA_MAX_CHANNELS); - - pDevice->type = config.deviceType; - pDevice->sampleRate = config.sampleRate; - pDevice->resampling.algorithm = config.resampling.algorithm; - pDevice->resampling.linear.lpfOrder = config.resampling.linear.lpfOrder; - pDevice->resampling.speex.quality = config.resampling.speex.quality; - - pDevice->capture.shareMode = config.capture.shareMode; - pDevice->capture.format = config.capture.format; - pDevice->capture.channels = config.capture.channels; - ma_channel_map_copy(pDevice->capture.channelMap, config.capture.channelMap, config.capture.channels); - - pDevice->playback.shareMode = config.playback.shareMode; - pDevice->playback.format = config.playback.format; - pDevice->playback.channels = config.playback.channels; - ma_channel_map_copy(pDevice->playback.channelMap, config.playback.channelMap, config.playback.channels); - - - /* The internal format, channel count and sample rate can be modified by the backend. */ - pDevice->capture.internalFormat = pDevice->capture.format; - pDevice->capture.internalChannels = pDevice->capture.channels; - pDevice->capture.internalSampleRate = pDevice->sampleRate; - ma_channel_map_copy(pDevice->capture.internalChannelMap, pDevice->capture.channelMap, pDevice->capture.channels); - - pDevice->playback.internalFormat = pDevice->playback.format; - pDevice->playback.internalChannels = pDevice->playback.channels; - pDevice->playback.internalSampleRate = pDevice->sampleRate; - ma_channel_map_copy(pDevice->playback.internalChannelMap, pDevice->playback.channelMap, pDevice->playback.channels); + pDevice->playback.shareMode = pConfig->playback.shareMode; + pDevice->playback.format = pConfig->playback.format; + pDevice->playback.channels = pConfig->playback.channels; + ma_channel_map_copy(pDevice->playback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); + pDevice->playback.channelMixMode = pConfig->playback.channelMixMode; - result = ma_mutex_init(&pDevice->lock); + + result = ma_mutex_init(&pDevice->startStopLock); if (result != MA_SUCCESS) { return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create mutex.", result); } @@ -31611,20 +32974,20 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC /* 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. */ result = ma_event_init(&pDevice->wakeupEvent); if (result != MA_SUCCESS) { - ma_mutex_uninit(&pDevice->lock); + ma_mutex_uninit(&pDevice->startStopLock); return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread wakeup event.", result); } result = ma_event_init(&pDevice->startEvent); if (result != MA_SUCCESS) { ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->lock); + ma_mutex_uninit(&pDevice->startStopLock); return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread start event.", result); } @@ -31632,36 +32995,133 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC if (result != MA_SUCCESS) { ma_event_uninit(&pDevice->startEvent); ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->lock); + ma_mutex_uninit(&pDevice->startStopLock); return ma_context_post_error(pContext, NULL, MA_LOG_LEVEL_ERROR, "Failed to create worker thread stop event.", result); } - result = pContext->onDeviceInit(pContext, &config, pDevice); + MA_ZERO_OBJECT(&descriptorPlayback); + descriptorPlayback.pDeviceID = pConfig->playback.pDeviceID; + descriptorPlayback.shareMode = pConfig->playback.shareMode; + descriptorPlayback.format = pConfig->playback.format; + descriptorPlayback.channels = pConfig->playback.channels; + descriptorPlayback.sampleRate = pConfig->sampleRate; + ma_channel_map_copy(descriptorPlayback.channelMap, pConfig->playback.channelMap, pConfig->playback.channels); + descriptorPlayback.periodSizeInFrames = pConfig->periodSizeInFrames; + descriptorPlayback.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; + descriptorPlayback.periodCount = pConfig->periods; + + if (descriptorPlayback.periodCount == 0) { + descriptorPlayback.periodCount = MA_DEFAULT_PERIODS; + } + + + MA_ZERO_OBJECT(&descriptorCapture); + descriptorCapture.pDeviceID = pConfig->capture.pDeviceID; + descriptorCapture.shareMode = pConfig->capture.shareMode; + descriptorCapture.format = pConfig->capture.format; + descriptorCapture.channels = pConfig->capture.channels; + descriptorCapture.sampleRate = pConfig->sampleRate; + ma_channel_map_copy(descriptorCapture.channelMap, pConfig->capture.channelMap, pConfig->capture.channels); + descriptorCapture.periodSizeInFrames = pConfig->periodSizeInFrames; + descriptorCapture.periodSizeInMilliseconds = pConfig->periodSizeInMilliseconds; + descriptorCapture.periodCount = pConfig->periods; + + if (descriptorCapture.periodCount == 0) { + descriptorCapture.periodCount = MA_DEFAULT_PERIODS; + } + + + result = pContext->callbacks.onDeviceInit(pDevice, pConfig, &descriptorPlayback, &descriptorCapture); if (result != MA_SUCCESS) { + ma_event_uninit(&pDevice->startEvent); + ma_event_uninit(&pDevice->wakeupEvent); + ma_mutex_uninit(&pDevice->startStopLock); return result; } + + /* + On output the descriptors will contain the *actual* data format of the device. We need this to know how to convert the data between + the requested format and the internal format. + */ + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { + if (!ma_device_descriptor_is_valid(&descriptorCapture)) { + ma_device_uninit(pDevice); + return MA_INVALID_ARGS; + } + + pDevice->capture.internalFormat = descriptorCapture.format; + pDevice->capture.internalChannels = descriptorCapture.channels; + pDevice->capture.internalSampleRate = descriptorCapture.sampleRate; + ma_channel_map_copy(pDevice->capture.internalChannelMap, descriptorCapture.channelMap, descriptorCapture.channels); + pDevice->capture.internalPeriodSizeInFrames = descriptorCapture.periodSizeInFrames; + pDevice->capture.internalPeriods = descriptorCapture.periodCount; + + if (pDevice->capture.internalPeriodSizeInFrames == 0) { + pDevice->capture.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorCapture.periodSizeInMilliseconds, descriptorCapture.sampleRate); + } + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + if (!ma_device_descriptor_is_valid(&descriptorPlayback)) { + ma_device_uninit(pDevice); + return MA_INVALID_ARGS; + } + + pDevice->playback.internalFormat = descriptorPlayback.format; + pDevice->playback.internalChannels = descriptorPlayback.channels; + pDevice->playback.internalSampleRate = descriptorPlayback.sampleRate; + ma_channel_map_copy(pDevice->playback.internalChannelMap, descriptorPlayback.channelMap, descriptorPlayback.channels); + pDevice->playback.internalPeriodSizeInFrames = descriptorPlayback.periodSizeInFrames; + pDevice->playback.internalPeriods = descriptorPlayback.periodCount; + + if (pDevice->playback.internalPeriodSizeInFrames == 0) { + pDevice->playback.internalPeriodSizeInFrames = ma_calculate_buffer_size_in_frames_from_milliseconds(descriptorPlayback.periodSizeInMilliseconds, descriptorPlayback.sampleRate); + } + } + + + /* + The name of the device can be retrieved from device info. This may be temporary and replaced with a `ma_device_get_info(pDevice, deviceType)` instead. + For loopback devices, we need to retrieve the name of the playback device. + */ + { + ma_device_info deviceInfo; + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex || pConfig->deviceType == ma_device_type_loopback) { + result = ma_context_get_device_info(pContext, (pConfig->deviceType == ma_device_type_loopback) ? ma_device_type_playback : ma_device_type_capture, descriptorCapture.pDeviceID, descriptorCapture.shareMode, &deviceInfo); + if (result == MA_SUCCESS) { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), deviceInfo.name, (size_t)-1); + } else { + /* We failed to retrieve the device info. Fall back to a default name. */ + if (descriptorCapture.pDeviceID == NULL) { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), "Capture Device", (size_t)-1); + } + } + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + result = ma_context_get_device_info(pContext, ma_device_type_playback, descriptorPlayback.pDeviceID, descriptorPlayback.shareMode, &deviceInfo); + if (result == MA_SUCCESS) { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), deviceInfo.name, (size_t)-1); + } else { + /* We failed to retrieve the device info. Fall back to a default name. */ + if (descriptorPlayback.pDeviceID == NULL) { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), "Playback Device", (size_t)-1); + } + } + } + } + + ma_device__post_init_setup(pDevice, pConfig->deviceType); - /* If the backend did not fill out a name for the device, try a generic method. */ - if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { - if (pDevice->capture.name[0] == '\0') { - if (ma_context__try_get_device_name_by_id(pContext, ma_device_type_capture, config.capture.pDeviceID, pDevice->capture.name, sizeof(pDevice->capture.name)) != MA_SUCCESS) { - ma_strncpy_s(pDevice->capture.name, sizeof(pDevice->capture.name), (config.capture.pDeviceID == NULL) ? MA_DEFAULT_CAPTURE_DEVICE_NAME : "Capture Device", (size_t)-1); - } - } - } - if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex || pDevice->type == ma_device_type_loopback) { - if (pDevice->playback.name[0] == '\0') { - if (ma_context__try_get_device_name_by_id(pContext, ma_device_type_playback, config.playback.pDeviceID, pDevice->playback.name, sizeof(pDevice->playback.name)) != MA_SUCCESS) { - ma_strncpy_s(pDevice->playback.name, sizeof(pDevice->playback.name), (config.playback.pDeviceID == NULL) ? MA_DEFAULT_PLAYBACK_DEVICE_NAME : "Playback Device", (size_t)-1); - } - } - } - - /* Some backends don't require the worker thread. */ if (!ma_context_is_backend_asynchronous(pContext)) { /* The worker thread. */ @@ -31673,7 +33133,22 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC /* Wait for the worker thread to put the device into it's stopped state for real. */ ma_event_wait(&pDevice->stopEvent); + MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); } else { + /* + If the backend is asynchronous and the device is duplex, we'll need an intermediary ring buffer. Note that this needs to be done + after ma_device__post_init_setup(). + */ + if (ma_context_is_backend_asynchronous(pContext)) { + if (pConfig->deviceType == ma_device_type_duplex) { + result = ma_duplex_rb_init(pDevice->capture.format, pDevice->capture.channels, pDevice->sampleRate, pDevice->capture.internalSampleRate, pDevice->capture.internalPeriodSizeInFrames, &pDevice->pContext->allocationCallbacks, &pDevice->duplexRB); + if (result != MA_SUCCESS) { + ma_device_uninit(pDevice); + return result; + } + } + } + ma_device__set_state(pDevice, MA_STATE_STOPPED); } @@ -31681,9 +33156,9 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, "[%s]", ma_get_backend_name(pDevice->pContext->backend)); if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " %s (%s)", pDevice->capture.name, "Capture"); - ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Format: %s -> %s", ma_get_format_name(pDevice->capture.format), ma_get_format_name(pDevice->capture.internalFormat)); - ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Channels: %d -> %d", pDevice->capture.channels, pDevice->capture.internalChannels); - ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d", pDevice->sampleRate, pDevice->capture.internalSampleRate); + ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Format: %s -> %s", ma_get_format_name(pDevice->capture.internalFormat), ma_get_format_name(pDevice->capture.format)); + ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Channels: %d -> %d", pDevice->capture.internalChannels, pDevice->capture.channels); + ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Sample Rate: %d -> %d", pDevice->capture.internalSampleRate, pDevice->sampleRate); ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Buffer Size: %d*%d (%d)", pDevice->capture.internalPeriodSizeInFrames, pDevice->capture.internalPeriods, (pDevice->capture.internalPeriodSizeInFrames * pDevice->capture.internalPeriods)); ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Conversion:"); ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Pre Format Conversion: %s", pDevice->capture.converter.hasPreFormatConversion ? "YES" : "NO"); @@ -31706,7 +33181,7 @@ MA_API ma_result ma_device_init(ma_context* pContext, const ma_device_config* pC ma_post_log_messagef(pContext, pDevice, MA_LOG_LEVEL_INFO, " Passthrough: %s", pDevice->playback.converter.isPassthrough ? "YES" : "NO"); } - MA_ASSERT(ma_device__get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); return MA_SUCCESS; } @@ -31732,7 +33207,7 @@ MA_API ma_result ma_device_init_ex(const ma_backend backends[], ma_uint32 backen } else { allocationCallbacks = ma_allocation_callbacks_init_default(); } - + pContext = (ma_context*)ma__malloc_from_callbacks(sizeof(*pContext), &allocationCallbacks); if (pContext == NULL) { @@ -31793,12 +33268,21 @@ MA_API void ma_device_uninit(ma_device* pDevice) ma_thread_wait(&pDevice->thread); } - pDevice->pContext->onDeviceUninit(pDevice); + if (pDevice->pContext->callbacks.onDeviceUninit != NULL) { + pDevice->pContext->callbacks.onDeviceUninit(pDevice); + } + ma_event_uninit(&pDevice->stopEvent); ma_event_uninit(&pDevice->startEvent); ma_event_uninit(&pDevice->wakeupEvent); - ma_mutex_uninit(&pDevice->lock); + ma_mutex_uninit(&pDevice->startStopLock); + + if (ma_context_is_backend_asynchronous(pDevice->pContext)) { + if (pDevice->type == ma_device_type_duplex) { + ma_duplex_rb_uninit(&pDevice->duplexRB); + } + } if (pDevice->isOwnerOfContext) { ma_allocation_callbacks allocationCallbacks = pDevice->pContext->allocationCallbacks; @@ -31818,25 +33302,29 @@ MA_API ma_result ma_device_start(ma_device* pDevice) return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); } - if (ma_device__get_state(pDevice) == MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_start() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); } - if (ma_device__get_state(pDevice) == MA_STATE_STARTED) { + if (ma_device_get_state(pDevice) == MA_STATE_STARTED) { return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_start() called when the device is already started.", MA_INVALID_OPERATION); /* Already started. Returning an error to let the application know because it probably means they're doing something wrong. */ } - result = MA_ERROR; - ma_mutex_lock(&pDevice->lock); + ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a stopped or paused state. */ - MA_ASSERT(ma_device__get_state(pDevice) == MA_STATE_STOPPED); + MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STOPPED); ma_device__set_state(pDevice, MA_STATE_STARTING); /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { - result = pDevice->pContext->onDeviceStart(pDevice); + if (pDevice->pContext->callbacks.onDeviceStart != NULL) { + result = pDevice->pContext->callbacks.onDeviceStart(pDevice); + } else { + result = MA_INVALID_OPERATION; + } + if (result == MA_SUCCESS) { ma_device__set_state(pDevice, MA_STATE_STARTED); } @@ -31854,8 +33342,13 @@ MA_API ma_result ma_device_start(ma_device* pDevice) ma_event_wait(&pDevice->startEvent); result = pDevice->workResult; } + + /* We changed the state from stopped to started, so if we failed, make sure we put the state back to stopped. */ + if (result != MA_SUCCESS) { + ma_device__set_state(pDevice, MA_STATE_STOPPED); + } } - ma_mutex_unlock(&pDevice->lock); + ma_mutex_unlock(&pDevice->startStopLock); return result; } @@ -31868,34 +33361,43 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called with invalid arguments (pDevice == NULL).", MA_INVALID_ARGS); } - if (ma_device__get_state(pDevice) == MA_STATE_UNINITIALIZED) { + if (ma_device_get_state(pDevice) == MA_STATE_UNINITIALIZED) { return ma_post_error(pDevice, MA_LOG_LEVEL_ERROR, "ma_device_stop() called for an uninitialized device.", MA_DEVICE_NOT_INITIALIZED); } - if (ma_device__get_state(pDevice) == MA_STATE_STOPPED) { + if (ma_device_get_state(pDevice) == MA_STATE_STOPPED) { return ma_post_error(pDevice, MA_LOG_LEVEL_WARNING, "ma_device_stop() called when the device is already stopped.", MA_INVALID_OPERATION); /* Already stopped. Returning an error to let the application know because it probably means they're doing something wrong. */ } - result = MA_ERROR; - ma_mutex_lock(&pDevice->lock); + ma_mutex_lock(&pDevice->startStopLock); { /* Starting and stopping are wrapped in a mutex which means we can assert that the device is in a started or paused state. */ - MA_ASSERT(ma_device__get_state(pDevice) == MA_STATE_STARTED); + MA_ASSERT(ma_device_get_state(pDevice) == MA_STATE_STARTED); ma_device__set_state(pDevice, MA_STATE_STOPPING); - /* There's no need to wake up the thread like we do when starting. */ - if (pDevice->pContext->onDeviceStop) { - result = pDevice->pContext->onDeviceStop(pDevice); - } else { - result = MA_SUCCESS; - } - /* Asynchronous backends need to be handled differently. */ if (ma_context_is_backend_asynchronous(pDevice->pContext)) { + /* Asynchronous backends must have a stop operation. */ + if (pDevice->pContext->callbacks.onDeviceStop != NULL) { + result = pDevice->pContext->callbacks.onDeviceStop(pDevice); + } else { + result = MA_INVALID_OPERATION; + } + ma_device__set_state(pDevice, MA_STATE_STOPPED); } else { - /* Synchronous backends. */ + /* + Synchronous backends. The stop callback is always called from the worker thread. Do not call the stop callback here. If + the backend is implementing it's own audio thread loop we'll need to wake it up if required. Note that we need to make + sure the state of the device is *not* playing right now, which it shouldn't be since we set it above. This is super + important though, so I'm asserting it here as well for extra safety in case we accidentally change something later. + */ + MA_ASSERT(ma_device_get_state(pDevice) != MA_STATE_STARTED); + + if (pDevice->pContext->callbacks.onDeviceDataLoopWakeup != NULL) { + pDevice->pContext->callbacks.onDeviceDataLoopWakeup(pDevice); + } /* We need to wait for the worker thread to become available for work before returning. Note that the worker thread will be @@ -31905,18 +33407,23 @@ MA_API ma_result ma_device_stop(ma_device* pDevice) result = MA_SUCCESS; } } - ma_mutex_unlock(&pDevice->lock); + ma_mutex_unlock(&pDevice->startStopLock); return result; } -MA_API ma_bool32 ma_device_is_started(ma_device* pDevice) +MA_API ma_bool32 ma_device_is_started(const ma_device* pDevice) +{ + return ma_device_get_state(pDevice) == MA_STATE_STARTED; +} + +MA_API ma_uint32 ma_device_get_state(const ma_device* pDevice) { if (pDevice == NULL) { - return MA_FALSE; + return MA_STATE_UNINITIALIZED; } - return ma_device__get_state(pDevice) == MA_STATE_STARTED; + return c89atomic_load_32((ma_uint32*)&pDevice->state); /* Naughty cast to get rid of a const warning. */ } MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) @@ -31929,7 +33436,7 @@ MA_API ma_result ma_device_set_master_volume(ma_device* pDevice, float volume) return MA_INVALID_ARGS; } - pDevice->masterVolumeFactor = volume; + c89atomic_exchange_f32(&pDevice->masterVolumeFactor, volume); return MA_SUCCESS; } @@ -31945,7 +33452,7 @@ MA_API ma_result ma_device_get_master_volume(ma_device* pDevice, float* pVolume) return MA_INVALID_ARGS; } - *pVolume = pDevice->masterVolumeFactor; + *pVolume = c89atomic_load_f32(&pDevice->masterVolumeFactor); return MA_SUCCESS; } @@ -31978,6 +33485,82 @@ MA_API ma_result ma_device_get_master_gain_db(ma_device* pDevice, float* pGainDB return MA_SUCCESS; } + + +MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + if (pDevice == NULL) { + return MA_INVALID_ARGS; + } + + if (pOutput == NULL && pInput == NULL) { + return MA_INVALID_ARGS; + } + + if (pDevice->type == ma_device_type_duplex) { + if (pInput != NULL) { + ma_device__handle_duplex_callback_capture(pDevice, frameCount, pInput, &pDevice->duplexRB.rb); + } + + if (pOutput != NULL) { + ma_device__handle_duplex_callback_playback(pDevice, frameCount, pOutput, &pDevice->duplexRB.rb); + } + } else { + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_loopback) { + if (pInput == NULL) { + return MA_INVALID_ARGS; + } + + ma_device__send_frames_to_client(pDevice, frameCount, pInput); + } + + if (pDevice->type == ma_device_type_playback) { + if (pOutput == NULL) { + return MA_INVALID_ARGS; + } + + ma_device__read_frames_from_client(pDevice, frameCount, pOutput); + } + } + + return MA_SUCCESS; +} + +MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_descriptor(const ma_device_descriptor* pDescriptor, ma_uint32 nativeSampleRate, ma_performance_profile performanceProfile) +{ + if (pDescriptor == NULL) { + return 0; + } + + /* + We must have a non-0 native sample rate, but some backends don't allow retrieval of this at the + time when the size of the buffer needs to be determined. In this case we need to just take a best + guess and move on. We'll try using the sample rate in pDescriptor first. If that's not set we'll + just fall back to MA_DEFAULT_SAMPLE_RATE. + */ + if (nativeSampleRate == 0) { + nativeSampleRate = pDescriptor->sampleRate; + } + if (nativeSampleRate == 0) { + nativeSampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + MA_ASSERT(nativeSampleRate != 0); + + if (pDescriptor->periodSizeInFrames == 0) { + if (pDescriptor->periodSizeInMilliseconds == 0) { + if (performanceProfile == ma_performance_profile_low_latency) { + return ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY, nativeSampleRate); + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE, nativeSampleRate); + } + } else { + return ma_calculate_buffer_size_in_frames_from_milliseconds(pDescriptor->periodSizeInMilliseconds, nativeSampleRate); + } + } else { + return pDescriptor->periodSizeInFrames; + } +} #endif /* MA_NO_DEVICE_IO */ @@ -31988,12 +33571,22 @@ MA_API ma_uint32 ma_scale_buffer_size(ma_uint32 baseBufferSize, float scale) MA_API ma_uint32 ma_calculate_buffer_size_in_milliseconds_from_frames(ma_uint32 bufferSizeInFrames, ma_uint32 sampleRate) { - return bufferSizeInFrames / (sampleRate/1000); + /* Prevent a division by zero. */ + if (sampleRate == 0) { + return 0; + } + + return bufferSizeInFrames*1000 / sampleRate; } MA_API ma_uint32 ma_calculate_buffer_size_in_frames_from_milliseconds(ma_uint32 bufferSizeInMilliseconds, ma_uint32 sampleRate) { - return bufferSizeInMilliseconds * (sampleRate/1000); + /* Prevent a division by zero. */ + if (sampleRate == 0) { + return 0; + } + + return bufferSizeInMilliseconds*sampleRate / 1000; } MA_API void ma_copy_pcm_frames(void* dst, const void* src, ma_uint64 frameCount, ma_format format, ma_uint32 channels) @@ -32999,7 +34592,7 @@ static MA_INLINE void ma_pcm_s24_to_u8__reference(void* dst, const void* src, ma } else { x = 0x7FFFFFFF; } - + x = x >> 24; x = x + 128; dst_u8[i] = (ma_uint8)x; @@ -33369,7 +34962,7 @@ static MA_INLINE void ma_pcm_s32_to_u8__reference(void* dst, const void* src, ma } else { x = 0x7FFFFFFF; } - + x = x >> 24; x = x + 128; dst_u8[i] = (ma_uint8)x; @@ -33450,7 +35043,7 @@ static MA_INLINE void ma_pcm_s32_to_s16__reference(void* dst, const void* src, m } else { x = 0x7FFFFFFF; } - + x = x >> 16; dst_s16[i] = (ma_int16)x; } @@ -33841,7 +35434,7 @@ static MA_INLINE void ma_pcm_f32_to_s16__optimized(void* dst, const void* src, m float d1 = ma_dither_f32(ditherMode, ditherMin, ditherMax); float d2 = ma_dither_f32(ditherMode, ditherMin, ditherMax); float d3 = ma_dither_f32(ditherMode, ditherMin, ditherMax); - + float x0 = src_f32[i+0]; float x1 = src_f32[i+1]; float x2 = src_f32[i+2]; @@ -33959,7 +35552,7 @@ static MA_INLINE void ma_pcm_f32_to_s16__sse2(void* dst, const void* src, ma_uin x1 = _mm_mul_ps(x1, _mm_set1_ps(32767.0f)); _mm_stream_si128(((__m128i*)(dst_s16 + i)), _mm_packs_epi32(_mm_cvttps_epi32(x0), _mm_cvttps_epi32(x1))); - + i += 8; } @@ -34548,7 +36141,7 @@ MA_API void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ } } } break; - + case ma_format_f32: { const float* pSrcF32 = (const float*)pInterleavedPCMFrames; @@ -34561,7 +36154,7 @@ MA_API void ma_deinterleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ } } } break; - + default: { ma_uint32 sampleSizeInBytes = ma_get_bytes_per_sample(format); @@ -34594,7 +36187,7 @@ MA_API void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ui } } } break; - + case ma_format_f32: { float* pDstF32 = (float*)pInterleavedPCMFrames; @@ -34607,7 +36200,7 @@ MA_API void ma_interleave_pcm_frames(ma_format format, ma_uint32 channels, ma_ui } } } break; - + default: { ma_uint32 sampleSizeInBytes = ma_get_bytes_per_sample(format); @@ -34725,13 +36318,15 @@ MA_API ma_result ma_biquad_reinit(const ma_biquad_config* pConfig, ma_biquad* pB static MA_INLINE void ma_biquad_process_pcm_frame_f32__direct_form_2_transposed(ma_biquad* pBQ, float* pY, const float* pX) { ma_uint32 c; + const ma_uint32 channels = pBQ->channels; const float b0 = pBQ->b0.f32; const float b1 = pBQ->b1.f32; const float b2 = pBQ->b2.f32; const float a1 = pBQ->a1.f32; const float a2 = pBQ->a2.f32; - - for (c = 0; c < pBQ->channels; c += 1) { + + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { float r1 = pBQ->r1[c].f32; float r2 = pBQ->r2[c].f32; float x = pX[c]; @@ -34755,13 +36350,15 @@ static MA_INLINE void ma_biquad_process_pcm_frame_f32(ma_biquad* pBQ, float* pY, static MA_INLINE void ma_biquad_process_pcm_frame_s16__direct_form_2_transposed(ma_biquad* pBQ, ma_int16* pY, const ma_int16* pX) { ma_uint32 c; + const ma_uint32 channels = pBQ->channels; const ma_int32 b0 = pBQ->b0.s32; const ma_int32 b1 = pBQ->b1.s32; const ma_int32 b2 = pBQ->b2.s32; const ma_int32 a1 = pBQ->a1.s32; const ma_int32 a2 = pBQ->a2.s32; - - for (c = 0; c < pBQ->channels; c += 1) { + + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { ma_int32 r1 = pBQ->r1[c].s32; ma_int32 r2 = pBQ->r2[c].s32; ma_int32 x = pX[c]; @@ -34818,7 +36415,7 @@ MA_API ma_result ma_biquad_process_pcm_frames(ma_biquad* pBQ, void* pFramesOut, return MA_SUCCESS; } -MA_API ma_uint32 ma_biquad_get_latency(ma_biquad* pBQ) +MA_API ma_uint32 ma_biquad_get_latency(const ma_biquad* pBQ) { if (pBQ == NULL) { return 0; @@ -34836,7 +36433,7 @@ Low-Pass Filter MA_API ma_lpf1_config ma_lpf1_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency) { ma_lpf1_config config; - + MA_ZERO_OBJECT(&config); config.format = format; config.channels = channels; @@ -34850,7 +36447,7 @@ MA_API ma_lpf1_config ma_lpf1_config_init(ma_format format, ma_uint32 channels, MA_API ma_lpf2_config ma_lpf2_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, double q) { ma_lpf2_config config; - + MA_ZERO_OBJECT(&config); config.format = format; config.channels = channels; @@ -34925,10 +36522,12 @@ MA_API ma_result ma_lpf1_reinit(const ma_lpf1_config* pConfig, ma_lpf1* pLPF) static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, const float* pX) { ma_uint32 c; + const ma_uint32 channels = pLPF->channels; const float a = pLPF->a.f32; const float b = 1 - a; - - for (c = 0; c < pLPF->channels; c += 1) { + + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { float r1 = pLPF->r1[c].f32; float x = pX[c]; float y; @@ -34943,10 +36542,12 @@ static MA_INLINE void ma_lpf1_process_pcm_frame_f32(ma_lpf1* pLPF, float* pY, co static MA_INLINE void ma_lpf1_process_pcm_frame_s16(ma_lpf1* pLPF, ma_int16* pY, const ma_int16* pX) { ma_uint32 c; + const ma_uint32 channels = pLPF->channels; const ma_int32 a = pLPF->a.s32; const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - - for (c = 0; c < pLPF->channels; c += 1) { + + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { ma_int32 r1 = pLPF->r1[c].s32; ma_int32 x = pX[c]; ma_int32 y; @@ -34994,7 +36595,7 @@ MA_API ma_result ma_lpf1_process_pcm_frames(ma_lpf1* pLPF, void* pFramesOut, con return MA_SUCCESS; } -MA_API ma_uint32 ma_lpf1_get_latency(ma_lpf1* pLPF) +MA_API ma_uint32 ma_lpf1_get_latency(const ma_lpf1* pLPF) { if (pLPF == NULL) { return 0; @@ -35095,7 +36696,7 @@ MA_API ma_result ma_lpf2_process_pcm_frames(ma_lpf2* pLPF, void* pFramesOut, con return ma_biquad_process_pcm_frames(&pLPF->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_lpf2_get_latency(ma_lpf2* pLPF) +MA_API ma_uint32 ma_lpf2_get_latency(const ma_lpf2* pLPF) { if (pLPF == NULL) { return 0; @@ -35203,10 +36804,11 @@ static ma_result ma_lpf_reinit__internal(const ma_lpf_config* pConfig, ma_lpf* p } } - pLPF->lpf1Count = lpf1Count; - pLPF->lpf2Count = lpf2Count; - pLPF->format = pConfig->format; - pLPF->channels = pConfig->channels; + pLPF->lpf1Count = lpf1Count; + pLPF->lpf2Count = lpf2Count; + pLPF->format = pConfig->format; + pLPF->channels = pConfig->channels; + pLPF->sampleRate = pConfig->sampleRate; return MA_SUCCESS; } @@ -35325,7 +36927,7 @@ MA_API ma_result ma_lpf_process_pcm_frames(ma_lpf* pLPF, void* pFramesOut, const return MA_SUCCESS; } -MA_API ma_uint32 ma_lpf_get_latency(ma_lpf* pLPF) +MA_API ma_uint32 ma_lpf_get_latency(const ma_lpf* pLPF) { if (pLPF == NULL) { return 0; @@ -35343,7 +36945,7 @@ High-Pass Filtering MA_API ma_hpf1_config ma_hpf1_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency) { ma_hpf1_config config; - + MA_ZERO_OBJECT(&config); config.format = format; config.channels = channels; @@ -35356,7 +36958,7 @@ MA_API ma_hpf1_config ma_hpf1_config_init(ma_format format, ma_uint32 channels, MA_API ma_hpf2_config ma_hpf2_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, double q) { ma_hpf2_config config; - + MA_ZERO_OBJECT(&config); config.format = format; config.channels = channels; @@ -35431,10 +37033,12 @@ MA_API ma_result ma_hpf1_reinit(const ma_hpf1_config* pConfig, ma_hpf1* pHPF) static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, const float* pX) { ma_uint32 c; + const ma_uint32 channels = pHPF->channels; const float a = 1 - pHPF->a.f32; const float b = 1 - a; - - for (c = 0; c < pHPF->channels; c += 1) { + + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { float r1 = pHPF->r1[c].f32; float x = pX[c]; float y; @@ -35449,10 +37053,12 @@ static MA_INLINE void ma_hpf1_process_pcm_frame_f32(ma_hpf1* pHPF, float* pY, co static MA_INLINE void ma_hpf1_process_pcm_frame_s16(ma_hpf1* pHPF, ma_int16* pY, const ma_int16* pX) { ma_uint32 c; + const ma_uint32 channels = pHPF->channels; const ma_int32 a = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - pHPF->a.s32); const ma_int32 b = ((1 << MA_BIQUAD_FIXED_POINT_SHIFT) - a); - - for (c = 0; c < pHPF->channels; c += 1) { + + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { ma_int32 r1 = pHPF->r1[c].s32; ma_int32 x = pX[c]; ma_int32 y; @@ -35500,7 +37106,7 @@ MA_API ma_result ma_hpf1_process_pcm_frames(ma_hpf1* pHPF, void* pFramesOut, con return MA_SUCCESS; } -MA_API ma_uint32 ma_hpf1_get_latency(ma_hpf1* pHPF) +MA_API ma_uint32 ma_hpf1_get_latency(const ma_hpf1* pHPF) { if (pHPF == NULL) { return 0; @@ -35601,7 +37207,7 @@ MA_API ma_result ma_hpf2_process_pcm_frames(ma_hpf2* pHPF, void* pFramesOut, con return ma_biquad_process_pcm_frames(&pHPF->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_hpf2_get_latency(ma_hpf2* pHPF) +MA_API ma_uint32 ma_hpf2_get_latency(const ma_hpf2* pHPF) { if (pHPF == NULL) { return 0; @@ -35709,10 +37315,11 @@ static ma_result ma_hpf_reinit__internal(const ma_hpf_config* pConfig, ma_hpf* p } } - pHPF->hpf1Count = hpf1Count; - pHPF->hpf2Count = hpf2Count; - pHPF->format = pConfig->format; - pHPF->channels = pConfig->channels; + pHPF->hpf1Count = hpf1Count; + pHPF->hpf2Count = hpf2Count; + pHPF->format = pConfig->format; + pHPF->channels = pConfig->channels; + pHPF->sampleRate = pConfig->sampleRate; return MA_SUCCESS; } @@ -35813,7 +37420,7 @@ MA_API ma_result ma_hpf_process_pcm_frames(ma_hpf* pHPF, void* pFramesOut, const return MA_SUCCESS; } -MA_API ma_uint32 ma_hpf_get_latency(ma_hpf* pHPF) +MA_API ma_uint32 ma_hpf_get_latency(const ma_hpf* pHPF) { if (pHPF == NULL) { return 0; @@ -35831,7 +37438,7 @@ Band-Pass Filtering MA_API ma_bpf2_config ma_bpf2_config_init(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, double cutoffFrequency, double q) { ma_bpf2_config config; - + MA_ZERO_OBJECT(&config); config.format = format; config.channels = channels; @@ -35939,7 +37546,7 @@ MA_API ma_result ma_bpf2_process_pcm_frames(ma_bpf2* pBPF, void* pFramesOut, con return ma_biquad_process_pcm_frames(&pBPF->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_bpf2_get_latency(ma_bpf2* pBPF) +MA_API ma_uint32 ma_bpf2_get_latency(const ma_bpf2* pBPF) { if (pBPF == NULL) { return 0; @@ -36115,7 +37722,7 @@ MA_API ma_result ma_bpf_process_pcm_frames(ma_bpf* pBPF, void* pFramesOut, const return MA_SUCCESS; } -MA_API ma_uint32 ma_bpf_get_latency(ma_bpf* pBPF) +MA_API ma_uint32 ma_bpf_get_latency(const ma_bpf* pBPF) { if (pBPF == NULL) { return 0; @@ -36240,7 +37847,7 @@ MA_API ma_result ma_notch2_process_pcm_frames(ma_notch2* pFilter, void* pFramesO return ma_biquad_process_pcm_frames(&pFilter->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_notch2_get_latency(ma_notch2* pFilter) +MA_API ma_uint32 ma_notch2_get_latency(const ma_notch2* pFilter) { if (pFilter == NULL) { return 0; @@ -36369,7 +37976,7 @@ MA_API ma_result ma_peak2_process_pcm_frames(ma_peak2* pFilter, void* pFramesOut return ma_biquad_process_pcm_frames(&pFilter->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_peak2_get_latency(ma_peak2* pFilter) +MA_API ma_uint32 ma_peak2_get_latency(const ma_peak2* pFilter) { if (pFilter == NULL) { return 0; @@ -36495,7 +38102,7 @@ MA_API ma_result ma_loshelf2_process_pcm_frames(ma_loshelf2* pFilter, void* pFra return ma_biquad_process_pcm_frames(&pFilter->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_loshelf2_get_latency(ma_loshelf2* pFilter) +MA_API ma_uint32 ma_loshelf2_get_latency(const ma_loshelf2* pFilter) { if (pFilter == NULL) { return 0; @@ -36621,7 +38228,7 @@ MA_API ma_result ma_hishelf2_process_pcm_frames(ma_hishelf2* pFilter, void* pFra return ma_biquad_process_pcm_frames(&pFilter->bq, pFramesOut, pFramesIn, frameCount); } -MA_API ma_uint32 ma_hishelf2_get_latency(ma_hishelf2* pFilter) +MA_API ma_uint32 ma_hishelf2_get_latency(const ma_hishelf2* pFilter) { if (pFilter == NULL) { return 0; @@ -36780,14 +38387,15 @@ static MA_INLINE ma_int16 ma_linear_resampler_mix_s16(ma_int16 x, ma_int16 y, ma b = x * ((1<> shift); } -static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResampler, ma_int16* pFrameOut) +static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResampler, ma_int16* MA_RESTRICT pFrameOut) { ma_uint32 c; ma_uint32 a; + const ma_uint32 channels = pResampler->config.channels; const ma_uint32 shift = 12; MA_ASSERT(pResampler != NULL); @@ -36795,24 +38403,27 @@ static void ma_linear_resampler_interpolate_frame_s16(ma_linear_resampler* pResa a = (pResampler->inTimeFrac << shift) / pResampler->config.sampleRateOut; - for (c = 0; c < pResampler->config.channels; c += 1) { + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { ma_int16 s = ma_linear_resampler_mix_s16(pResampler->x0.s16[c], pResampler->x1.s16[c], a, shift); pFrameOut[c] = s; } } -static void ma_linear_resampler_interpolate_frame_f32(ma_linear_resampler* pResampler, float* pFrameOut) +static void ma_linear_resampler_interpolate_frame_f32(ma_linear_resampler* pResampler, float* MA_RESTRICT pFrameOut) { ma_uint32 c; float a; + const ma_uint32 channels = pResampler->config.channels; MA_ASSERT(pResampler != NULL); MA_ASSERT(pFrameOut != NULL); a = (float)pResampler->inTimeFrac / pResampler->config.sampleRateOut; - for (c = 0; c < pResampler->config.channels; c += 1) { + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); + for (c = 0; c < channels; c += 1) { float s = ma_mix_f32_fast(pResampler->x0.f32[c], pResampler->x1.f32[c], a); pFrameOut[c] = s; } @@ -37174,12 +38785,12 @@ MA_API ma_result ma_linear_resampler_set_rate_ratio(ma_linear_resampler* pResamp } MA_ASSERT(n != 0); - + return ma_linear_resampler_set_rate(pResampler, n, d); } -MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(ma_linear_resampler* pResampler, ma_uint64 outputFrameCount) +MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(const ma_linear_resampler* pResampler, ma_uint64 outputFrameCount) { ma_uint64 inputFrameCount; @@ -37202,12 +38813,12 @@ MA_API ma_uint64 ma_linear_resampler_get_required_input_frame_count(ma_linear_re return inputFrameCount; } -MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(ma_linear_resampler* pResampler, ma_uint64 inputFrameCount) +MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(const ma_linear_resampler* pResampler, ma_uint64 inputFrameCount) { ma_uint64 outputFrameCount; ma_uint64 preliminaryInputFrameCountFromFrac; ma_uint64 preliminaryInputFrameCount; - + if (pResampler == NULL) { return 0; } @@ -37239,7 +38850,7 @@ MA_API ma_uint64 ma_linear_resampler_get_expected_output_frame_count(ma_linear_r return outputFrameCount; } -MA_API ma_uint64 ma_linear_resampler_get_input_latency(ma_linear_resampler* pResampler) +MA_API ma_uint64 ma_linear_resampler_get_input_latency(const ma_linear_resampler* pResampler) { if (pResampler == NULL) { return 0; @@ -37248,7 +38859,7 @@ MA_API ma_uint64 ma_linear_resampler_get_input_latency(ma_linear_resampler* pRes return 1 + ma_lpf_get_latency(&pResampler->lpf); } -MA_API ma_uint64 ma_linear_resampler_get_output_latency(ma_linear_resampler* pResampler) +MA_API ma_uint64 ma_linear_resampler_get_output_latency(const ma_linear_resampler* pResampler) { if (pResampler == NULL) { return 0; @@ -37474,7 +39085,7 @@ static ma_result ma_resampler_process_pcm_frames__read(ma_resampler* pResampler, break; #endif } - + default: break; } @@ -37496,7 +39107,7 @@ static ma_result ma_resampler_process_pcm_frames__seek__linear(ma_resampler* pRe static ma_result ma_resampler_process_pcm_frames__seek__speex(ma_resampler* pResampler, const void* pFramesIn, ma_uint64* pFrameCountIn, ma_uint64* pFrameCountOut) { /* The generic seek method is implemented in on top of ma_resampler_process_pcm_frames__read() by just processing into a dummy buffer. */ - float devnull[8192]; + float devnull[4096]; ma_uint64 totalOutputFramesToProcess; ma_uint64 totalOutputFramesProcessed; ma_uint64 totalInputFramesProcessed; @@ -37673,12 +39284,12 @@ MA_API ma_result ma_resampler_set_rate_ratio(ma_resampler* pResampler, float rat } MA_ASSERT(n != 0); - + return ma_resampler_set_rate(pResampler, n, d); } } -MA_API ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResampler, ma_uint64 outputFrameCount) +MA_API ma_uint64 ma_resampler_get_required_input_frame_count(const ma_resampler* pResampler, ma_uint64 outputFrameCount) { if (pResampler == NULL) { return 0; @@ -37698,13 +39309,13 @@ MA_API ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResa case ma_resample_algorithm_speex: { #if defined(MA_HAS_SPEEX_RESAMPLER) - ma_uint64 count; + spx_uint64_t count; int speexErr = ma_speex_resampler_get_required_input_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, outputFrameCount, &count); if (speexErr != RESAMPLER_ERR_SUCCESS) { return 0; } - return count; + return (ma_uint64)count; #else break; #endif @@ -37718,7 +39329,7 @@ MA_API ma_uint64 ma_resampler_get_required_input_frame_count(ma_resampler* pResa return 0; } -MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pResampler, ma_uint64 inputFrameCount) +MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(const ma_resampler* pResampler, ma_uint64 inputFrameCount) { if (pResampler == NULL) { return 0; /* Invalid args. */ @@ -37738,13 +39349,13 @@ MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pRes case ma_resample_algorithm_speex: { #if defined(MA_HAS_SPEEX_RESAMPLER) - ma_uint64 count; + spx_uint64_t count; int speexErr = ma_speex_resampler_get_expected_output_frame_count((SpeexResamplerState*)pResampler->state.speex.pSpeexResamplerState, inputFrameCount, &count); if (speexErr != RESAMPLER_ERR_SUCCESS) { return 0; } - return count; + return (ma_uint64)count; #else break; #endif @@ -37758,7 +39369,7 @@ MA_API ma_uint64 ma_resampler_get_expected_output_frame_count(ma_resampler* pRes return 0; } -MA_API ma_uint64 ma_resampler_get_input_latency(ma_resampler* pResampler) +MA_API ma_uint64 ma_resampler_get_input_latency(const ma_resampler* pResampler) { if (pResampler == NULL) { return 0; @@ -37788,7 +39399,7 @@ MA_API ma_uint64 ma_resampler_get_input_latency(ma_resampler* pResampler) return 0; } -MA_API ma_uint64 ma_resampler_get_output_latency(ma_resampler* pResampler) +MA_API ma_uint64 ma_resampler_get_output_latency(const ma_resampler* pResampler) { if (pResampler == NULL) { return 0; @@ -37894,34 +39505,34 @@ static float ma_calculate_channel_position_rectangular_weight(ma_channel channel /* Imagine the following simplified example: You have a single input speaker which is the front/left speaker which you want to convert to the following output configuration: - + - front/left - side/left - back/left - + The front/left output is easy - it the same speaker position so it receives the full contribution of the front/left input. The amount of contribution to apply to the side/left and back/left speakers, however, is a bit more complicated. - + Imagine the front/left speaker as emitting audio from two planes - the front plane and the left plane. You can think of the front/left speaker emitting half of it's total volume from the front, and the other half from the left. Since part of it's volume is being emitted from the left side, and the side/left and back/left channels also emit audio from the left plane, one would expect that they would receive some amount of contribution from front/left speaker. The amount of contribution depends on how many planes are shared between the two speakers. Note that in the examples below I've added a top/front/left speaker as an example just to show how the math works across 3 spatial dimensions. - + The first thing to do is figure out how each speaker's volume is spread over each of plane: - front/left: 2 planes (front and left) = 1/2 = half it's total volume on each plane - side/left: 1 plane (left only) = 1/1 = entire volume from left plane - back/left: 2 planes (back and left) = 1/2 = half it's total volume on each plane - top/front/left: 3 planes (top, front and left) = 1/3 = one third it's total volume on each plane - + The amount of volume each channel contributes to each of it's planes is what controls how much it is willing to given and take to other channels on the same plane. The volume that is willing to the given by one channel is multiplied by the volume that is willing to be taken by the other to produce the final contribution. */ /* Contribution = Sum(Volume to Give * Volume to Take) */ - float contribution = + float contribution = g_maChannelPlaneRatios[channelPositionA][0] * g_maChannelPlaneRatios[channelPositionB][0] + g_maChannelPlaneRatios[channelPositionA][1] * g_maChannelPlaneRatios[channelPositionB][1] + g_maChannelPlaneRatios[channelPositionA][2] * g_maChannelPlaneRatios[channelPositionB][2] + @@ -38017,7 +39628,7 @@ MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pC } } } - + /* If the input and output channels and channel maps are the same we should use a passthrough. */ @@ -38059,7 +39670,7 @@ MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pC /* Here is where we do a bit of pre-processing to know how each channel should be combined to make up the output. Rules: - + 1) If it's a passthrough, do nothing - it's just a simple memcpy(). 2) If the channel counts are the same and every channel position in the input map is present in the output map, use a simple shuffle. An example might be different 5.1 channel layouts. @@ -38106,7 +39717,7 @@ MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pC /* Here is where weights are calculated. Note that we calculate the weights at all times, even when using a passthrough and simple shuffling. We use different algorithms for calculating weights depending on our mixing mode. - + In simple mode we don't do any blending (except for converting between mono, which is done in a later step). Instead we just map 1:1 matching channels. In this mode, if no channels in the input channel map correspond to anything in the output channel map, nothing will be heard! @@ -38254,8 +39865,24 @@ MA_API ma_result ma_channel_converter_init(const ma_channel_converter_config* pC } } break; - case ma_channel_mix_mode_custom_weights: case ma_channel_mix_mode_simple: + { + /* In simple mode, excess channels need to be silenced or dropped. */ + ma_uint32 iChannel; + for (iChannel = 0; iChannel < ma_min(pConverter->channelsIn, pConverter->channelsOut); iChannel += 1) { + if (pConverter->format == ma_format_f32) { + if (pConverter->weights.f32[iChannel][iChannel] == 0) { + pConverter->weights.f32[iChannel][iChannel] = 1; + } + } else { + if (pConverter->weights.s16[iChannel][iChannel] == 0) { + pConverter->weights.s16[iChannel][iChannel] = ma_channel_converter_float_to_fixed(1); + } + } + } + } break; + + case ma_channel_mix_mode_custom_weights: default: { /* Fallthrough. */ @@ -38357,7 +39984,7 @@ static ma_result ma_channel_converter_process_pcm_frames__simple_shuffle(ma_chan pFramesInS32 += pConverter->channelsIn; } } break; - + case ma_format_f32: { /* */ float* pFramesOutF32 = ( float*)pFramesOut; @@ -38452,7 +40079,7 @@ static ma_result ma_channel_converter_process_pcm_frames__simple_mono_expansion( } } } break; - + case ma_format_f32: { /* */ float* pFramesOutF32 = ( float*)pFramesOut; @@ -38532,7 +40159,7 @@ static ma_result ma_channel_converter_process_pcm_frames__stereo_to_mono(ma_chan pFramesOutS32[iFrame] = (ma_int16)(((ma_int32)pFramesInS32[iFrame*2+0] + (ma_int32)pFramesInS32[iFrame*2+1]) / 2); } } break; - + case ma_format_f32: { /* */ float* pFramesOutF32 = ( float*)pFramesOut; @@ -38634,7 +40261,7 @@ static ma_result ma_channel_converter_process_pcm_frames__weights(ma_channel_con } } } break; - + case ma_format_f32: { /* */ float* pFramesOutF32 = ( float*)pFramesOut; @@ -38717,7 +40344,7 @@ MA_API ma_data_converter_config ma_data_converter_config_init(ma_format formatIn config.channelsOut = ma_min(channelsOut, MA_MAX_CHANNELS); config.sampleRateIn = sampleRateIn; config.sampleRateOut = sampleRateOut; - + return config; } @@ -38765,14 +40392,14 @@ MA_API ma_result ma_data_converter_init(const ma_data_converter_config* pConfig, ma_channel_converter_config channelConverterConfig; channelConverterConfig = ma_channel_converter_config_init(midFormat, pConverter->config.channelsIn, pConverter->config.channelMapIn, pConverter->config.channelsOut, pConverter->config.channelMapOut, pConverter->config.channelMixMode); - + /* Channel weights. */ for (iChannelIn = 0; iChannelIn < pConverter->config.channelsIn; iChannelIn += 1) { for (iChannelOut = 0; iChannelOut < pConverter->config.channelsOut; iChannelOut += 1) { channelConverterConfig.weights[iChannelIn][iChannelOut] = pConverter->config.channelWeights[iChannelIn][iChannelOut]; } } - + result = ma_channel_converter_init(&channelConverterConfig, &pConverter->channelConverter); if (result != MA_SUCCESS) { return result; @@ -38867,7 +40494,7 @@ static ma_result ma_data_converter_process_pcm_frames__passthrough(ma_data_conve ma_uint64 frameCount; MA_ASSERT(pConverter != NULL); - + frameCountIn = 0; if (pFrameCountIn != NULL) { frameCountIn = *pFrameCountIn; @@ -38905,7 +40532,7 @@ static ma_result ma_data_converter_process_pcm_frames__format_only(ma_data_conve ma_uint64 frameCount; MA_ASSERT(pConverter != NULL); - + frameCountIn = 0; if (pFrameCountIn != NULL) { frameCountIn = *pFrameCountIn; @@ -39477,7 +41104,7 @@ static ma_result ma_data_converter_process_pcm_frames__channels_first(ma_data_co if (pFrameCountOut != NULL) { *pFrameCountOut = framesProcessedOut; } - + return MA_SUCCESS; } @@ -39556,7 +41183,7 @@ MA_API ma_result ma_data_converter_set_rate_ratio(ma_data_converter* pConverter, return ma_resampler_set_rate_ratio(&pConverter->resampler, ratioInOut); } -MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(ma_data_converter* pConverter, ma_uint64 outputFrameCount) +MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(const ma_data_converter* pConverter, ma_uint64 outputFrameCount) { if (pConverter == NULL) { return 0; @@ -39569,7 +41196,7 @@ MA_API ma_uint64 ma_data_converter_get_required_input_frame_count(ma_data_conver } } -MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(ma_data_converter* pConverter, ma_uint64 inputFrameCount) +MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(const ma_data_converter* pConverter, ma_uint64 inputFrameCount) { if (pConverter == NULL) { return 0; @@ -39582,7 +41209,7 @@ MA_API ma_uint64 ma_data_converter_get_expected_output_frame_count(ma_data_conve } } -MA_API ma_uint64 ma_data_converter_get_input_latency(ma_data_converter* pConverter) +MA_API ma_uint64 ma_data_converter_get_input_latency(const ma_data_converter* pConverter) { if (pConverter == NULL) { return 0; @@ -39595,7 +41222,7 @@ MA_API ma_uint64 ma_data_converter_get_input_latency(ma_data_converter* pConvert return 0; /* No latency without a resampler. */ } -MA_API ma_uint64 ma_data_converter_get_output_latency(ma_data_converter* pConverter) +MA_API ma_uint64 ma_data_converter_get_output_latency(const ma_data_converter* pConverter) { if (pConverter == NULL) { return 0; @@ -39615,6 +41242,15 @@ MA_API ma_uint64 ma_data_converter_get_output_latency(ma_data_converter* pConver Channel Maps **************************************************************************************************************************************************************/ +MA_API void ma_channel_map_init_blank(ma_uint32 channels, ma_channel* pChannelMap) +{ + if (pChannelMap == NULL) { + return; + } + + MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channels); +} + static void ma_get_standard_channel_map_microsoft(ma_uint32 channels, ma_channel* pChannelMap) { /* Based off the speaker configurations mentioned here: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ksmedia/ns-ksmedia-ksaudio_channel_config */ @@ -40216,13 +41852,14 @@ MA_API void ma_get_standard_channel_map(ma_standard_channel_map standardChannelM { ma_get_standard_channel_map_sound4(channels, pChannelMap); } break; - + case ma_standard_channel_map_sndio: { ma_get_standard_channel_map_sndio(channels, pChannelMap); } break; - case ma_standard_channel_map_microsoft: + case ma_standard_channel_map_microsoft: /* Also default. */ + /*case ma_standard_channel_map_default;*/ default: { ma_get_standard_channel_map_microsoft(channels, pChannelMap); @@ -40382,13 +42019,13 @@ static MA_INLINE ma_uint32 ma_rb__extract_offset_loop_flag(ma_uint32 encodedOffs static MA_INLINE void* ma_rb__get_read_ptr(ma_rb* pRB) { MA_ASSERT(pRB != NULL); - return ma_offset_ptr(pRB->pBuffer, ma_rb__extract_offset_in_bytes(pRB->encodedReadOffset)); + return ma_offset_ptr(pRB->pBuffer, ma_rb__extract_offset_in_bytes(c89atomic_load_32(&pRB->encodedReadOffset))); } static MA_INLINE void* ma_rb__get_write_ptr(ma_rb* pRB) { MA_ASSERT(pRB != NULL); - return ma_offset_ptr(pRB->pBuffer, ma_rb__extract_offset_in_bytes(pRB->encodedWriteOffset)); + return ma_offset_ptr(pRB->pBuffer, ma_rb__extract_offset_in_bytes(c89atomic_load_32(&pRB->encodedWriteOffset))); } static MA_INLINE ma_uint32 ma_rb__construct_offset(ma_uint32 offsetInBytes, ma_uint32 offsetLoopFlag) @@ -40481,8 +42118,8 @@ MA_API void ma_rb_reset(ma_rb* pRB) return; } - pRB->encodedReadOffset = 0; - pRB->encodedWriteOffset = 0; + c89atomic_exchange_32(&pRB->encodedReadOffset, 0); + c89atomic_exchange_32(&pRB->encodedWriteOffset, 0); } MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppBufferOut) @@ -40501,10 +42138,10 @@ MA_API ma_result ma_rb_acquire_read(ma_rb* pRB, size_t* pSizeInBytes, void** ppB } /* The returned buffer should never move ahead of the write pointer. */ - writeOffset = pRB->encodedWriteOffset; + writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); - readOffset = pRB->encodedReadOffset; + readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); /* @@ -40545,7 +42182,7 @@ MA_API ma_result ma_rb_commit_read(ma_rb* pRB, size_t sizeInBytes, void* pBuffer return MA_INVALID_ARGS; } - readOffset = pRB->encodedReadOffset; + readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); /* Check that sizeInBytes is correct. It should never go beyond the end of the buffer. */ @@ -40581,10 +42218,10 @@ MA_API ma_result ma_rb_acquire_write(ma_rb* pRB, size_t* pSizeInBytes, void** pp } /* The returned buffer should never overtake the read buffer. */ - readOffset = pRB->encodedReadOffset; + readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - writeOffset = pRB->encodedWriteOffset; + writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); /* @@ -40631,7 +42268,7 @@ MA_API ma_result ma_rb_commit_write(ma_rb* pRB, size_t sizeInBytes, void* pBuffe return MA_INVALID_ARGS; } - writeOffset = pRB->encodedWriteOffset; + writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); /* Check that sizeInBytes is correct. It should never go beyond the end of the buffer. */ @@ -40666,13 +42303,12 @@ MA_API ma_result ma_rb_seek_read(ma_rb* pRB, size_t offsetInBytes) return MA_INVALID_ARGS; } - readOffset = pRB->encodedReadOffset; + readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - writeOffset = pRB->encodedWriteOffset; + writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); - newReadOffsetInBytes = readOffsetInBytes; newReadOffsetLoopFlag = readOffsetLoopFlag; /* We cannot go past the write buffer. */ @@ -40711,13 +42347,12 @@ MA_API ma_result ma_rb_seek_write(ma_rb* pRB, size_t offsetInBytes) return MA_INVALID_ARGS; } - readOffset = pRB->encodedReadOffset; + readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - writeOffset = pRB->encodedWriteOffset; + writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); - newWriteOffsetInBytes = writeOffsetInBytes; newWriteOffsetLoopFlag = writeOffsetLoopFlag; /* We cannot go past the write buffer. */ @@ -40754,10 +42389,10 @@ MA_API ma_int32 ma_rb_pointer_distance(ma_rb* pRB) return 0; } - readOffset = pRB->encodedReadOffset; + readOffset = c89atomic_load_32(&pRB->encodedReadOffset); ma_rb__deconstruct_offset(readOffset, &readOffsetInBytes, &readOffsetLoopFlag); - writeOffset = pRB->encodedWriteOffset; + writeOffset = c89atomic_load_32(&pRB->encodedWriteOffset); ma_rb__deconstruct_offset(writeOffset, &writeOffsetInBytes, &writeOffsetLoopFlag); if (readOffsetLoopFlag == writeOffsetLoopFlag) { @@ -40833,6 +42468,7 @@ MA_API void* ma_rb_get_subbuffer_ptr(ma_rb* pRB, size_t subbufferIndex, void* pB } + static MA_INLINE ma_uint32 ma_pcm_rb_get_bpf(ma_pcm_rb* pRB) { MA_ASSERT(pRB != NULL); @@ -41031,6 +42667,35 @@ MA_API void* ma_pcm_rb_get_subbuffer_ptr(ma_pcm_rb* pRB, ma_uint32 subbufferInde +MA_API ma_result ma_duplex_rb_init(ma_format captureFormat, ma_uint32 captureChannels, ma_uint32 sampleRate, ma_uint32 captureInternalSampleRate, ma_uint32 captureInternalPeriodSizeInFrames, const ma_allocation_callbacks* pAllocationCallbacks, ma_duplex_rb* pRB) +{ + ma_result result; + ma_uint32 sizeInFrames; + + sizeInFrames = (ma_uint32)ma_calculate_frame_count_after_resampling(sampleRate, captureInternalSampleRate, captureInternalPeriodSizeInFrames * 5); + if (sizeInFrames == 0) { + return MA_INVALID_ARGS; + } + + result = ma_pcm_rb_init(captureFormat, captureChannels, sizeInFrames, NULL, pAllocationCallbacks, &pRB->rb); + if (result != MA_SUCCESS) { + return result; + } + + /* Seek forward a bit so we have a bit of a buffer in case of desyncs. */ + ma_pcm_rb_seek_write((ma_pcm_rb*)pRB, captureInternalPeriodSizeInFrames * 2); + + return MA_SUCCESS; +} + +MA_API ma_result ma_duplex_rb_uninit(ma_duplex_rb* pRB) +{ + ma_pcm_rb_uninit((ma_pcm_rb*)pRB); + return MA_SUCCESS; +} + + + /************************************************************************************************************************************************************** Miscellaneous Helpers @@ -41215,6 +42880,12 @@ MA_API ma_uint32 ma_get_bytes_per_sample(ma_format format) MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead, ma_bool32 loop) { ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + + /* Safety. */ + if (pFramesRead != NULL) { + *pFramesRead = 0; + } + if (pCallbacks == NULL) { return MA_INVALID_ARGS; } @@ -41247,7 +42918,7 @@ MA_API ma_result ma_data_source_read_pcm_frames(ma_data_source* pDataSource, voi /* If we encounted an error from the read callback, make sure it's propagated to the caller. The caller may need to know whether or not MA_BUSY is returned which is - not necessarily considered an error. + not necessarily considered an error. */ if (result != MA_SUCCESS && result != MA_AT_END) { break; @@ -41320,6 +42991,18 @@ MA_API ma_result ma_data_source_get_data_format(ma_data_source* pDataSource, ma_ ma_uint32 sampleRate; ma_data_source_callbacks* pCallbacks = (ma_data_source_callbacks*)pDataSource; + if (pFormat != NULL) { + *pFormat = ma_format_unknown; + } + + if (pChannels != NULL) { + *pChannels = 0; + } + + if (pSampleRate != NULL) { + *pSampleRate = 0; + } + if (pCallbacks == NULL || pCallbacks->onGetDataFormat == NULL) { return MA_INVALID_ARGS; } @@ -41386,6 +43069,245 @@ MA_API ma_result ma_data_source_get_length_in_pcm_frames(ma_data_source* pDataSo + +static ma_result ma_audio_buffer_ref__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) +{ + ma_uint64 framesRead = ma_audio_buffer_ref_read_pcm_frames((ma_audio_buffer_ref*)pDataSource, pFramesOut, frameCount, MA_FALSE); + + if (pFramesRead != NULL) { + *pFramesRead = framesRead; + } + + if (framesRead < frameCount) { + return MA_AT_END; + } + + return MA_SUCCESS; +} + +static ma_result ma_audio_buffer_ref__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) +{ + return ma_audio_buffer_ref_seek_to_pcm_frame((ma_audio_buffer_ref*)pDataSource, frameIndex); +} + +static ma_result ma_audio_buffer_ref__data_source_on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) +{ + return ma_audio_buffer_ref_map((ma_audio_buffer_ref*)pDataSource, ppFramesOut, pFrameCount); +} + +static ma_result ma_audio_buffer_ref__data_source_on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) +{ + return ma_audio_buffer_ref_unmap((ma_audio_buffer_ref*)pDataSource, frameCount); +} + +static ma_result ma_audio_buffer_ref__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) +{ + ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; + + *pFormat = pAudioBufferRef->format; + *pChannels = pAudioBufferRef->channels; + *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ + + return MA_SUCCESS; +} + +static ma_result ma_audio_buffer_ref__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) +{ + ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; + + *pCursor = pAudioBufferRef->cursor; + + return MA_SUCCESS; +} + +static ma_result ma_audio_buffer_ref__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) +{ + ma_audio_buffer_ref* pAudioBufferRef = (ma_audio_buffer_ref*)pDataSource; + + *pLength = pAudioBufferRef->sizeInFrames; + + return MA_SUCCESS; +} + +MA_API ma_result ma_audio_buffer_ref_init(ma_format format, ma_uint32 channels, const void* pData, ma_uint64 sizeInFrames, ma_audio_buffer_ref* pAudioBufferRef) +{ + if (pAudioBufferRef == NULL) { + return MA_INVALID_ARGS; + } + + MA_ZERO_OBJECT(pAudioBufferRef); + + pAudioBufferRef->ds.onRead = ma_audio_buffer_ref__data_source_on_read; + pAudioBufferRef->ds.onSeek = ma_audio_buffer_ref__data_source_on_seek; + pAudioBufferRef->ds.onMap = ma_audio_buffer_ref__data_source_on_map; + pAudioBufferRef->ds.onUnmap = ma_audio_buffer_ref__data_source_on_unmap; + pAudioBufferRef->ds.onGetDataFormat = ma_audio_buffer_ref__data_source_on_get_data_format; + pAudioBufferRef->ds.onGetCursor = ma_audio_buffer_ref__data_source_on_get_cursor; + pAudioBufferRef->ds.onGetLength = ma_audio_buffer_ref__data_source_on_get_length; + pAudioBufferRef->format = format; + pAudioBufferRef->channels = channels; + pAudioBufferRef->cursor = 0; + pAudioBufferRef->sizeInFrames = sizeInFrames; + pAudioBufferRef->pData = pData; + + return MA_SUCCESS; +} + +MA_API ma_result ma_audio_buffer_ref_set_data(ma_audio_buffer_ref* pAudioBufferRef, const void* pData, ma_uint64 sizeInFrames) +{ + if (pAudioBufferRef == NULL) { + return MA_INVALID_ARGS; + } + + pAudioBufferRef->cursor = 0; + pAudioBufferRef->sizeInFrames = sizeInFrames; + pAudioBufferRef->pData = pData; + + return MA_SUCCESS; +} + +MA_API ma_uint64 ma_audio_buffer_ref_read_pcm_frames(ma_audio_buffer_ref* pAudioBufferRef, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop) +{ + ma_uint64 totalFramesRead = 0; + + if (pAudioBufferRef == NULL) { + return 0; + } + + if (frameCount == 0) { + return 0; + } + + while (totalFramesRead < frameCount) { + ma_uint64 framesAvailable = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; + ma_uint64 framesRemaining = frameCount - totalFramesRead; + ma_uint64 framesToRead; + + framesToRead = framesRemaining; + if (framesToRead > framesAvailable) { + framesToRead = framesAvailable; + } + + if (pFramesOut != NULL) { + ma_copy_pcm_frames(pFramesOut, ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)), framesToRead, pAudioBufferRef->format, pAudioBufferRef->channels); + } + + totalFramesRead += framesToRead; + + pAudioBufferRef->cursor += framesToRead; + if (pAudioBufferRef->cursor == pAudioBufferRef->sizeInFrames) { + if (loop) { + pAudioBufferRef->cursor = 0; + } else { + break; /* We've reached the end and we're not looping. Done. */ + } + } + + MA_ASSERT(pAudioBufferRef->cursor < pAudioBufferRef->sizeInFrames); + } + + return totalFramesRead; +} + +MA_API ma_result ma_audio_buffer_ref_seek_to_pcm_frame(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameIndex) +{ + if (pAudioBufferRef == NULL) { + return MA_INVALID_ARGS; + } + + if (frameIndex > pAudioBufferRef->sizeInFrames) { + return MA_INVALID_ARGS; + } + + pAudioBufferRef->cursor = (size_t)frameIndex; + + return MA_SUCCESS; +} + +MA_API ma_result ma_audio_buffer_ref_map(ma_audio_buffer_ref* pAudioBufferRef, void** ppFramesOut, ma_uint64* pFrameCount) +{ + ma_uint64 framesAvailable; + ma_uint64 frameCount = 0; + + if (ppFramesOut != NULL) { + *ppFramesOut = NULL; /* Safety. */ + } + + if (pFrameCount != NULL) { + frameCount = *pFrameCount; + *pFrameCount = 0; /* Safety. */ + } + + if (pAudioBufferRef == NULL || ppFramesOut == NULL || pFrameCount == NULL) { + return MA_INVALID_ARGS; + } + + framesAvailable = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; + if (frameCount > framesAvailable) { + frameCount = framesAvailable; + } + + *ppFramesOut = ma_offset_ptr(pAudioBufferRef->pData, pAudioBufferRef->cursor * ma_get_bytes_per_frame(pAudioBufferRef->format, pAudioBufferRef->channels)); + *pFrameCount = frameCount; + + return MA_SUCCESS; +} + +MA_API ma_result ma_audio_buffer_ref_unmap(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64 frameCount) +{ + ma_uint64 framesAvailable; + + if (pAudioBufferRef == NULL) { + return MA_INVALID_ARGS; + } + + framesAvailable = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; + if (frameCount > framesAvailable) { + return MA_INVALID_ARGS; /* The frame count was too big. This should never happen in an unmapping. Need to make sure the caller is aware of this. */ + } + + pAudioBufferRef->cursor += frameCount; + + if (pAudioBufferRef->cursor == pAudioBufferRef->sizeInFrames) { + return MA_AT_END; /* Successful. Need to tell the caller that the end has been reached so that it can loop if desired. */ + } else { + return MA_SUCCESS; + } +} + +MA_API ma_result ma_audio_buffer_ref_at_end(ma_audio_buffer_ref* pAudioBufferRef) +{ + if (pAudioBufferRef == NULL) { + return MA_FALSE; + } + + return pAudioBufferRef->cursor == pAudioBufferRef->sizeInFrames; +} + +MA_API ma_result ma_audio_buffer_ref_get_available_frames(ma_audio_buffer_ref* pAudioBufferRef, ma_uint64* pAvailableFrames) +{ + if (pAvailableFrames == NULL) { + return MA_INVALID_ARGS; + } + + *pAvailableFrames = 0; + + if (pAudioBufferRef == NULL) { + return MA_INVALID_ARGS; + } + + if (pAudioBufferRef->sizeInFrames <= pAudioBufferRef->cursor) { + *pAvailableFrames = 0; + } else { + *pAvailableFrames = pAudioBufferRef->sizeInFrames - pAudioBufferRef->cursor; + } + + return MA_SUCCESS; +} + + + + MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_uint32 channels, ma_uint64 sizeInFrames, const void* pData, const ma_allocation_callbacks* pAllocationCallbacks) { ma_audio_buffer_config config; @@ -41400,68 +43322,10 @@ MA_API ma_audio_buffer_config ma_audio_buffer_config_init(ma_format format, ma_u return config; } - -static ma_result ma_audio_buffer__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) -{ - ma_uint64 framesRead = ma_audio_buffer_read_pcm_frames((ma_audio_buffer*)pDataSource, pFramesOut, frameCount, MA_FALSE); - - if (pFramesRead != NULL) { - *pFramesRead = framesRead; - } - - if (framesRead < frameCount) { - return MA_AT_END; - } - - return MA_SUCCESS; -} - -static ma_result ma_audio_buffer__data_source_on_seek(ma_data_source* pDataSource, ma_uint64 frameIndex) -{ - return ma_audio_buffer_seek_to_pcm_frame((ma_audio_buffer*)pDataSource, frameIndex); -} - -static ma_result ma_audio_buffer__data_source_on_map(ma_data_source* pDataSource, void** ppFramesOut, ma_uint64* pFrameCount) -{ - return ma_audio_buffer_map((ma_audio_buffer*)pDataSource, ppFramesOut, pFrameCount); -} - -static ma_result ma_audio_buffer__data_source_on_unmap(ma_data_source* pDataSource, ma_uint64 frameCount) -{ - return ma_audio_buffer_unmap((ma_audio_buffer*)pDataSource, frameCount); -} - -static ma_result ma_audio_buffer__data_source_on_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate) -{ - ma_audio_buffer* pAudioBuffer = (ma_audio_buffer*)pDataSource; - - *pFormat = pAudioBuffer->format; - *pChannels = pAudioBuffer->channels; - *pSampleRate = 0; /* There is no notion of a sample rate with audio buffers. */ - - return MA_SUCCESS; -} - -static ma_result ma_audio_buffer__data_source_on_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor) -{ - ma_audio_buffer* pAudioBuffer = (ma_audio_buffer*)pDataSource; - - *pCursor = pAudioBuffer->cursor; - - return MA_SUCCESS; -} - -static ma_result ma_audio_buffer__data_source_on_get_length(ma_data_source* pDataSource, ma_uint64* pLength) -{ - ma_audio_buffer* pAudioBuffer = (ma_audio_buffer*)pDataSource; - - *pLength = pAudioBuffer->sizeInFrames; - - return MA_SUCCESS; -} - static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, ma_bool32 doCopy, ma_audio_buffer* pAudioBuffer) { + ma_result result; + if (pAudioBuffer == NULL) { return MA_INVALID_ARGS; } @@ -41476,25 +43340,18 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, return MA_INVALID_ARGS; /* Not allowing buffer sizes of 0 frames. */ } - pAudioBuffer->ds.onRead = ma_audio_buffer__data_source_on_read; - pAudioBuffer->ds.onSeek = ma_audio_buffer__data_source_on_seek; - pAudioBuffer->ds.onMap = ma_audio_buffer__data_source_on_map; - pAudioBuffer->ds.onUnmap = ma_audio_buffer__data_source_on_unmap; - pAudioBuffer->ds.onGetDataFormat = ma_audio_buffer__data_source_on_get_data_format; - pAudioBuffer->ds.onGetCursor = ma_audio_buffer__data_source_on_get_cursor; - pAudioBuffer->ds.onGetLength = ma_audio_buffer__data_source_on_get_length; - pAudioBuffer->format = pConfig->format; - pAudioBuffer->channels = pConfig->channels; - pAudioBuffer->cursor = 0; - pAudioBuffer->sizeInFrames = pConfig->sizeInFrames; - pAudioBuffer->pData = NULL; /* Set properly later. */ + result = ma_audio_buffer_ref_init(pConfig->format, pConfig->channels, NULL, 0, &pAudioBuffer->ref); + if (result != MA_SUCCESS) { + return result; + } + ma_allocation_callbacks_init_copy(&pAudioBuffer->allocationCallbacks, &pConfig->allocationCallbacks); if (doCopy) { ma_uint64 allocationSizeInBytes; void* pData; - allocationSizeInBytes = pAudioBuffer->sizeInFrames * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels); + allocationSizeInBytes = pConfig->sizeInFrames * ma_get_bytes_per_frame(pConfig->format, pConfig->channels); if (allocationSizeInBytes > MA_SIZE_MAX) { return MA_OUT_OF_MEMORY; /* Too big. */ } @@ -41505,15 +43362,15 @@ static ma_result ma_audio_buffer_init_ex(const ma_audio_buffer_config* pConfig, } if (pConfig->pData != NULL) { - ma_copy_pcm_frames(pData, pConfig->pData, pAudioBuffer->sizeInFrames, pAudioBuffer->format, pAudioBuffer->channels); + ma_copy_pcm_frames(pData, pConfig->pData, pConfig->sizeInFrames, pConfig->format, pConfig->channels); } else { - ma_silence_pcm_frames(pData, pAudioBuffer->sizeInFrames, pAudioBuffer->format, pAudioBuffer->channels); + ma_silence_pcm_frames(pData, pConfig->sizeInFrames, pConfig->format, pConfig->channels); } - pAudioBuffer->pData = pData; + ma_audio_buffer_ref_set_data(&pAudioBuffer->ref, pData, pConfig->sizeInFrames); pAudioBuffer->ownsData = MA_TRUE; } else { - pAudioBuffer->pData = pConfig->pData; + ma_audio_buffer_ref_set_data(&pAudioBuffer->ref, pConfig->pData, pConfig->sizeInFrames); pAudioBuffer->ownsData = MA_FALSE; } @@ -41526,13 +43383,12 @@ static void ma_audio_buffer_uninit_ex(ma_audio_buffer* pAudioBuffer, ma_bool32 d return; } - if (pAudioBuffer->ownsData && pAudioBuffer->pData != &pAudioBuffer->_pExtraData[0]) { - ma__free_from_callbacks((void*)pAudioBuffer->pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ + if (pAudioBuffer->ownsData && pAudioBuffer->ref.pData != &pAudioBuffer->_pExtraData[0]) { + ma__free_from_callbacks((void*)pAudioBuffer->ref.pData, &pAudioBuffer->allocationCallbacks); /* Naugty const cast, but OK in this case since we've guarded it with the ownsData check. */ } if (doFree) { - ma_allocation_callbacks allocationCallbacks = pAudioBuffer->allocationCallbacks; - ma__free_from_callbacks(pAudioBuffer, &allocationCallbacks); + ma__free_from_callbacks(pAudioBuffer, &pAudioBuffer->allocationCallbacks); } } @@ -41607,45 +43463,11 @@ MA_API void ma_audio_buffer_uninit_and_free(ma_audio_buffer* pAudioBuffer) MA_API ma_uint64 ma_audio_buffer_read_pcm_frames(ma_audio_buffer* pAudioBuffer, void* pFramesOut, ma_uint64 frameCount, ma_bool32 loop) { - ma_uint64 totalFramesRead = 0; - if (pAudioBuffer == NULL) { return 0; } - if (frameCount == 0) { - return 0; - } - - while (totalFramesRead < frameCount) { - ma_uint64 framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; - ma_uint64 framesRemaining = frameCount - totalFramesRead; - ma_uint64 framesToRead; - - framesToRead = framesRemaining; - if (framesToRead > framesAvailable) { - framesToRead = framesAvailable; - } - - if (pFramesOut != NULL) { - ma_copy_pcm_frames(pFramesOut, ma_offset_ptr(pAudioBuffer->pData, pAudioBuffer->cursor * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels)), frameCount, pAudioBuffer->format, pAudioBuffer->channels); - } - - totalFramesRead += framesToRead; - - pAudioBuffer->cursor += framesToRead; - if (pAudioBuffer->cursor == pAudioBuffer->sizeInFrames) { - if (loop) { - pAudioBuffer->cursor = 0; - } else { - break; /* We've reached the end and we're not looping. Done. */ - } - } - - MA_ASSERT(pAudioBuffer->cursor < pAudioBuffer->sizeInFrames); - } - - return totalFramesRead; + return ma_audio_buffer_ref_read_pcm_frames(&pAudioBuffer->ref, pFramesOut, frameCount, loop); } MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer, ma_uint64 frameIndex) @@ -41654,64 +43476,33 @@ MA_API ma_result ma_audio_buffer_seek_to_pcm_frame(ma_audio_buffer* pAudioBuffer return MA_INVALID_ARGS; } - if (frameIndex > pAudioBuffer->sizeInFrames) { - return MA_INVALID_ARGS; - } - - pAudioBuffer->cursor = (size_t)frameIndex; - - return MA_SUCCESS; + return ma_audio_buffer_ref_seek_to_pcm_frame(&pAudioBuffer->ref, frameIndex); } MA_API ma_result ma_audio_buffer_map(ma_audio_buffer* pAudioBuffer, void** ppFramesOut, ma_uint64* pFrameCount) { - ma_uint64 framesAvailable; - ma_uint64 frameCount = 0; - if (ppFramesOut != NULL) { *ppFramesOut = NULL; /* Safety. */ } - if (pFrameCount != NULL) { - frameCount = *pFrameCount; - *pFrameCount = 0; /* Safety. */ - } + if (pAudioBuffer == NULL) { + if (pFrameCount != NULL) { + *pFrameCount = 0; + } - if (pAudioBuffer == NULL || ppFramesOut == NULL || pFrameCount == NULL) { return MA_INVALID_ARGS; } - framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; - if (frameCount > framesAvailable) { - frameCount = framesAvailable; - } - - *ppFramesOut = ma_offset_ptr(pAudioBuffer->pData, pAudioBuffer->cursor * ma_get_bytes_per_frame(pAudioBuffer->format, pAudioBuffer->channels)); - *pFrameCount = frameCount; - - return MA_SUCCESS; + return ma_audio_buffer_ref_map(&pAudioBuffer->ref, ppFramesOut, pFrameCount); } MA_API ma_result ma_audio_buffer_unmap(ma_audio_buffer* pAudioBuffer, ma_uint64 frameCount) { - ma_uint64 framesAvailable; - if (pAudioBuffer == NULL) { return MA_INVALID_ARGS; } - framesAvailable = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; - if (frameCount > framesAvailable) { - return MA_INVALID_ARGS; /* The frame count was too big. This should never happen in an unmapping. Need to make sure the caller is aware of this. */ - } - - pAudioBuffer->cursor += frameCount; - - if (pAudioBuffer->cursor == pAudioBuffer->sizeInFrames) { - return MA_AT_END; /* Successful. Need to tell the caller that the end has been reached so that it can loop if desired. */ - } else { - return MA_SUCCESS; - } + return ma_audio_buffer_ref_unmap(&pAudioBuffer->ref, frameCount); } MA_API ma_result ma_audio_buffer_at_end(ma_audio_buffer* pAudioBuffer) @@ -41720,7 +43511,7 @@ MA_API ma_result ma_audio_buffer_at_end(ma_audio_buffer* pAudioBuffer) return MA_FALSE; } - return pAudioBuffer->cursor == pAudioBuffer->sizeInFrames; + return ma_audio_buffer_ref_at_end(&pAudioBuffer->ref); } MA_API ma_result ma_audio_buffer_get_available_frames(ma_audio_buffer* pAudioBuffer, ma_uint64* pAvailableFrames) @@ -41735,13 +43526,7 @@ MA_API ma_result ma_audio_buffer_get_available_frames(ma_audio_buffer* pAudioBuf return MA_INVALID_ARGS; } - if (pAudioBuffer->sizeInFrames <= pAudioBuffer->cursor) { - *pAvailableFrames = 0; - } else { - *pAvailableFrames = pAudioBuffer->sizeInFrames - pAudioBuffer->cursor; - } - - return MA_SUCCESS; + return ma_audio_buffer_ref_get_available_frames(&pAudioBuffer->ref, pAvailableFrames); } @@ -41831,7 +43616,7 @@ MA_API ma_result ma_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, { ma_vfs_callbacks* pCallbacks = (ma_vfs_callbacks*)pVFS; - if (pBytesWritten == NULL) { + if (pBytesWritten != NULL) { *pBytesWritten = 0; } @@ -41904,7 +43689,7 @@ MA_API ma_result ma_vfs_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo } -static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks, ma_uint32 allocationType) +static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePath, const wchar_t* pFilePathW, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks, ma_uint32 allocationType) { ma_result result; ma_vfs_file file; @@ -41925,7 +43710,11 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat return MA_INVALID_ARGS; } - result = ma_vfs_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); + if (pFilePath != NULL) { + result = ma_vfs_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); + } else { + result = ma_vfs_open_w(pVFS, pFilePathW, MA_OPEN_MODE_READ, &file); + } if (result != MA_SUCCESS) { return result; } @@ -41965,9 +43754,14 @@ static ma_result ma_vfs_open_and_read_file_ex(ma_vfs* pVFS, const char* pFilePat return MA_SUCCESS; } -ma_result ma_vfs_open_and_read_file(ma_vfs* pVFS, const char* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) +MA_API ma_result ma_vfs_open_and_read_file(ma_vfs* pVFS, const char* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) { - return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); + return ma_vfs_open_and_read_file_ex(pVFS, pFilePath, NULL, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); +} + +MA_API ma_result ma_vfs_open_and_read_file_w(ma_vfs* pVFS, const wchar_t* pFilePath, void** ppData, size_t* pSize, const ma_allocation_callbacks* pAllocationCallbacks) +{ + return ma_vfs_open_and_read_file_ex(pVFS, NULL, pFilePath, ppData, pSize, pAllocationCallbacks, 0 /*MA_ALLOCATION_TYPE_GENERAL*/); } @@ -42061,16 +43855,20 @@ static ma_result ma_default_vfs_read__win32(ma_vfs* pVFS, ma_vfs_file file, void BOOL readResult; bytesRemaining = sizeInBytes - totalBytesRead; - if (bytesRemaining > 0xFFFFFFFF) { + if (bytesRemaining >= 0xFFFFFFFF) { bytesToRead = 0xFFFFFFFF; } else { bytesToRead = (DWORD)bytesRemaining; } readResult = ReadFile((HANDLE)file, ma_offset_ptr(pDst, totalBytesRead), bytesToRead, &bytesRead, NULL); + if (readResult == 1 && bytesRead == 0) { + break; /* EOF */ + } + totalBytesRead += bytesRead; - if (bytesRead < bytesToRead || (readResult == 1 && bytesRead == 0)) { + if (bytesRead < bytesToRead) { break; /* EOF */ } @@ -42102,7 +43900,7 @@ static ma_result ma_default_vfs_write__win32(ma_vfs* pVFS, ma_vfs_file file, con BOOL writeResult; bytesRemaining = sizeInBytes - totalBytesWritten; - if (bytesRemaining > 0xFFFFFFFF) { + if (bytesRemaining >= 0xFFFFFFFF) { bytesToWrite = 0xFFFFFFFF; } else { bytesToWrite = (DWORD)bytesRemaining; @@ -42117,7 +43915,7 @@ static ma_result ma_default_vfs_write__win32(ma_vfs* pVFS, ma_vfs_file file, con } } - if (pBytesWritten == NULL) { + if (pBytesWritten != NULL) { *pBytesWritten = totalBytesWritten; } @@ -42143,7 +43941,7 @@ static ma_result ma_default_vfs_seek__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i dwMoveMethod = FILE_BEGIN; } -#if defined(_MSC_VER) && _MSC_VER <= 1200 +#if (defined(_MSC_VER) && _MSC_VER <= 1200) || defined(__DMC__) /* No SetFilePointerEx() so restrict to 31 bits. */ if (origin > 0x7FFFFFFF) { return MA_OUT_OF_RANGE; @@ -42165,7 +43963,7 @@ static ma_result ma_default_vfs_tell__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i LARGE_INTEGER liZero; LARGE_INTEGER liTell; BOOL result; -#if defined(_MSC_VER) && _MSC_VER <= 1200 +#if (defined(_MSC_VER) && _MSC_VER <= 1200) || defined(__DMC__) LONG tell; #endif @@ -42173,7 +43971,7 @@ static ma_result ma_default_vfs_tell__win32(ma_vfs* pVFS, ma_vfs_file file, ma_i liZero.QuadPart = 0; -#if defined(_MSC_VER) && _MSC_VER <= 1200 +#if (defined(_MSC_VER) && _MSC_VER <= 1200) || defined(__DMC__) result = SetFilePointer((HANDLE)file, (LONG)liZero.QuadPart, &tell, FILE_CURRENT); liTell.QuadPart = tell; #else @@ -42290,13 +44088,13 @@ static ma_result ma_default_vfs_read__stdio(ma_vfs* pVFS, ma_vfs_file file, void MA_ASSERT(pDst != NULL); (void)pVFS; - + result = fread(pDst, 1, sizeInBytes, (FILE*)file); if (pBytesRead != NULL) { *pBytesRead = result; } - + if (result != sizeInBytes) { if (feof((FILE*)file)) { return MA_END_OF_FILE; @@ -42337,7 +44135,7 @@ static ma_result ma_default_vfs_seek__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_i MA_ASSERT(file != NULL); (void)pVFS; - + #if defined(_WIN32) #if defined(_MSC_VER) && _MSC_VER > 1200 result = _fseeki64((FILE*)file, offset, origin); @@ -42383,7 +44181,7 @@ static ma_result ma_default_vfs_tell__stdio(ma_vfs* pVFS, ma_vfs_file file, ma_i return MA_SUCCESS; } -#if !((defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)) +#if !defined(_MSC_VER) && !((defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)) && !defined(MA_BSD) int fileno(FILE *stream); #endif @@ -42467,6 +44265,10 @@ static ma_result ma_default_vfs_close(ma_vfs* pVFS, ma_vfs_file file) static ma_result ma_default_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) { + if (pBytesRead != NULL) { + *pBytesRead = 0; + } + if (file == NULL || pDst == NULL) { return MA_INVALID_ARGS; } @@ -42480,6 +44282,10 @@ static ma_result ma_default_vfs_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, static ma_result ma_default_vfs_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) { + if (pBytesWritten != NULL) { + *pBytesWritten = 0; + } + if (file == NULL || pSrc == NULL) { return MA_INVALID_ARGS; } @@ -42563,6 +44369,80 @@ MA_API ma_result ma_default_vfs_init(ma_default_vfs* pVFS, const ma_allocation_c } +MA_API ma_result ma_vfs_or_default_open(ma_vfs* pVFS, const char* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) +{ + if (pVFS != NULL) { + return ma_vfs_open(pVFS, pFilePath, openMode, pFile); + } else { + return ma_default_vfs_open(pVFS, pFilePath, openMode, pFile); + } +} + +MA_API ma_result ma_vfs_or_default_open_w(ma_vfs* pVFS, const wchar_t* pFilePath, ma_uint32 openMode, ma_vfs_file* pFile) +{ + if (pVFS != NULL) { + return ma_vfs_open_w(pVFS, pFilePath, openMode, pFile); + } else { + return ma_default_vfs_open_w(pVFS, pFilePath, openMode, pFile); + } +} + +MA_API ma_result ma_vfs_or_default_close(ma_vfs* pVFS, ma_vfs_file file) +{ + if (pVFS != NULL) { + return ma_vfs_close(pVFS, file); + } else { + return ma_default_vfs_close(pVFS, file); + } +} + +MA_API ma_result ma_vfs_or_default_read(ma_vfs* pVFS, ma_vfs_file file, void* pDst, size_t sizeInBytes, size_t* pBytesRead) +{ + if (pVFS != NULL) { + return ma_vfs_read(pVFS, file, pDst, sizeInBytes, pBytesRead); + } else { + return ma_default_vfs_read(pVFS, file, pDst, sizeInBytes, pBytesRead); + } +} + +MA_API ma_result ma_vfs_or_default_write(ma_vfs* pVFS, ma_vfs_file file, const void* pSrc, size_t sizeInBytes, size_t* pBytesWritten) +{ + if (pVFS != NULL) { + return ma_vfs_write(pVFS, file, pSrc, sizeInBytes, pBytesWritten); + } else { + return ma_default_vfs_write(pVFS, file, pSrc, sizeInBytes, pBytesWritten); + } +} + +MA_API ma_result ma_vfs_or_default_seek(ma_vfs* pVFS, ma_vfs_file file, ma_int64 offset, ma_seek_origin origin) +{ + if (pVFS != NULL) { + return ma_vfs_seek(pVFS, file, offset, origin); + } else { + return ma_default_vfs_seek(pVFS, file, offset, origin); + } +} + +MA_API ma_result ma_vfs_or_default_tell(ma_vfs* pVFS, ma_vfs_file file, ma_int64* pCursor) +{ + if (pVFS != NULL) { + return ma_vfs_tell(pVFS, file, pCursor); + } else { + return ma_default_vfs_tell(pVFS, file, pCursor); + } +} + +MA_API ma_result ma_vfs_or_default_info(ma_vfs* pVFS, ma_vfs_file file, ma_file_info* pInfo) +{ + if (pVFS != NULL) { + return ma_vfs_info(pVFS, file, pInfo); + } else { + return ma_default_vfs_info(pVFS, file, pInfo); + } +} + + + /************************************************************************************************************************************************************** Decoding and Encoding Headers. These are auto-generated from a tool. @@ -42579,7 +44459,7 @@ extern "C" { #define DRWAV_XSTRINGIFY(x) DRWAV_STRINGIFY(x) #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 12 -#define DRWAV_VERSION_REVISION 10 +#define DRWAV_VERSION_REVISION 19 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include typedef signed char drwav_int8; @@ -42592,7 +44472,7 @@ typedef unsigned int drwav_uint32; typedef signed __int64 drwav_int64; typedef unsigned __int64 drwav_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -42601,7 +44481,7 @@ typedef unsigned int drwav_uint32; #endif typedef signed long long drwav_int64; typedef unsigned long long drwav_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -42718,7 +44598,8 @@ typedef enum typedef enum { drwav_container_riff, - drwav_container_w64 + drwav_container_w64, + drwav_container_rf64 } drwav_container; typedef struct { @@ -42951,7 +44832,7 @@ extern "C" { #define DRFLAC_XSTRINGIFY(x) DRFLAC_STRINGIFY(x) #define DRFLAC_VERSION_MAJOR 0 #define DRFLAC_VERSION_MINOR 12 -#define DRFLAC_VERSION_REVISION 19 +#define DRFLAC_VERSION_REVISION 29 #define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION) #include typedef signed char drflac_int8; @@ -42964,7 +44845,7 @@ typedef unsigned int drflac_uint32; typedef signed __int64 drflac_int64; typedef unsigned __int64 drflac_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -42973,7 +44854,7 @@ typedef unsigned int drflac_uint32; #endif typedef signed long long drflac_int64; typedef unsigned long long drflac_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -43312,7 +45193,7 @@ extern "C" { #define DRMP3_XSTRINGIFY(x) DRMP3_STRINGIFY(x) #define DRMP3_VERSION_MAJOR 0 #define DRMP3_VERSION_MINOR 6 -#define DRMP3_VERSION_REVISION 16 +#define DRMP3_VERSION_REVISION 27 #define DRMP3_VERSION_STRING DRMP3_XSTRINGIFY(DRMP3_VERSION_MAJOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_MINOR) "." DRMP3_XSTRINGIFY(DRMP3_VERSION_REVISION) #include typedef signed char drmp3_int8; @@ -43325,7 +45206,7 @@ typedef unsigned int drmp3_uint32; typedef signed __int64 drmp3_int64; typedef unsigned __int64 drmp3_uint64; #else - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wlong-long" #if defined(__clang__) @@ -43334,7 +45215,7 @@ typedef unsigned int drmp3_uint32; #endif typedef signed long long drmp3_int64; typedef unsigned long long drmp3_uint64; - #if defined(__GNUC__) + #if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -43440,6 +45321,8 @@ typedef drmp3_int32 drmp3_result; #else #define DRMP3_INLINE inline __attribute__((always_inline)) #endif +#elif defined(__WATCOMC__) + #define DRMP3_INLINE __inline #else #define DRMP3_INLINE #endif @@ -43458,12 +45341,6 @@ typedef struct DRMP3_API void drmp3dec_init(drmp3dec *dec); DRMP3_API int drmp3dec_decode_frame(drmp3dec *dec, const drmp3_uint8 *mp3, int mp3_bytes, void *pcm, drmp3dec_frame_info *info); DRMP3_API void drmp3dec_f32_to_s16(const float *in, drmp3_int16 *out, size_t num_samples); -#ifndef DRMP3_DEFAULT_CHANNELS -#define DRMP3_DEFAULT_CHANNELS 2 -#endif -#ifndef DRMP3_DEFAULT_SAMPLE_RATE -#define DRMP3_DEFAULT_SAMPLE_RATE 44100 -#endif typedef enum { drmp3_seek_origin_start, @@ -43664,9 +45541,9 @@ static ma_result ma_decoder__init_data_converter(ma_decoder* pDecoder, const ma_ MA_COPY_MEMORY(pDecoder->outputChannelMap, pConfig->channelMap, sizeof(pConfig->channelMap)); } - + converterConfig = ma_data_converter_config_init( - pDecoder->internalFormat, pDecoder->outputFormat, + pDecoder->internalFormat, pDecoder->outputFormat, pDecoder->internalChannels, pDecoder->outputChannels, pDecoder->internalSampleRate, pDecoder->outputSampleRate ); @@ -43760,6 +45637,8 @@ static ma_result ma_decoder_init_wav__internal(const ma_decoder_config* pConfig, MA_ASSERT(pConfig != NULL); MA_ASSERT(pDecoder != NULL); + (void)pConfig; + pWav = (drwav*)ma__malloc_from_callbacks(sizeof(*pWav), &pDecoder->allocationCallbacks); if (pWav == NULL) { return MA_OUT_OF_MEMORY; @@ -44021,6 +45900,8 @@ static ma_result ma_decoder_init_mp3__internal(const ma_decoder_config* pConfig, MA_ASSERT(pConfig != NULL); MA_ASSERT(pDecoder != NULL); + (void)pConfig; + pMP3 = (drmp3*)ma__malloc_from_callbacks(sizeof(*pMP3), &pDecoder->allocationCallbacks); if (pMP3 == NULL) { return MA_OUT_OF_MEMORY; @@ -44099,7 +45980,7 @@ static ma_uint64 ma_vorbis_decoder_read_pcm_frames(ma_vorbis_decoder* pVorbis, m for (iFrame = 0; iFrame < framesToReadFromCache; iFrame += 1) { ma_uint32 iChannel; for (iChannel = 0; iChannel < pDecoder->internalChannels; ++iChannel) { - pFramesOutF[iChannel] = pVorbis->ppPacketData[iChannel][pVorbis->framesConsumed+iFrame]; + pFramesOutF[iChannel] = pVorbis->ppPacketData[iChannel][pVorbis->framesConsumed+iFrame]; } pFramesOutF += pDecoder->internalChannels; } @@ -44486,6 +46367,8 @@ static ma_result ma_decoder_init_raw__internal(const ma_decoder_config* pConfigI MA_ASSERT(pConfigOut != NULL); MA_ASSERT(pDecoder != NULL); + (void)pConfigOut; + pDecoder->onReadPCMFrames = ma_decoder_internal_on_read_pcm_frames__raw; pDecoder->onSeekToPCMFrame = ma_decoder_internal_on_seek_to_pcm_frame__raw; pDecoder->onUninit = ma_decoder_internal_on_uninit__raw; @@ -45184,7 +47067,7 @@ static ma_bool32 ma_path_extension_equal_w(const wchar_t* path, const wchar_t* e ext1 = extension; ext2 = ma_path_extension_w(path); -#if defined(_MSC_VER) || defined(__DMC__) +#if defined(_MSC_VER) || defined(__WATCOMC__) || defined(__DMC__) return _wcsicmp(ext1, ext2) == 0; #else /* @@ -45224,12 +47107,8 @@ static size_t ma_decoder__on_read_vfs(ma_decoder* pDecoder, void* pBufferOut, si MA_ASSERT(pDecoder != NULL); MA_ASSERT(pBufferOut != NULL); - if (pDecoder->backend.vfs.pVFS == NULL) { - ma_default_vfs_read(NULL, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); - } else { - ma_vfs_read(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); - } - + ma_vfs_or_default_read(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, pBufferOut, bytesToRead, &bytesRead); + return bytesRead; } @@ -45239,12 +47118,7 @@ static ma_bool32 ma_decoder__on_seek_vfs(ma_decoder* pDecoder, int offset, ma_se MA_ASSERT(pDecoder != NULL); - if (pDecoder->backend.vfs.pVFS == NULL) { - result = ma_default_vfs_seek(NULL, pDecoder->backend.vfs.file, offset, origin); - } else { - result = ma_vfs_seek(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, offset, origin); - } - + result = ma_vfs_or_default_seek(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file, offset, origin); if (result != MA_SUCCESS) { return MA_FALSE; } @@ -45266,12 +47140,7 @@ static ma_result ma_decoder__preinit_vfs(ma_vfs* pVFS, const char* pFilePath, co return MA_INVALID_ARGS; } - if (pVFS == NULL) { - result = ma_default_vfs_open(NULL, pFilePath, MA_OPEN_MODE_READ, &file); - } else { - result = ma_vfs_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); - } - + result = ma_vfs_or_default_open(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); if (result != MA_SUCCESS) { return result; } @@ -45328,7 +47197,7 @@ MA_API ma_result ma_decoder_init_vfs(ma_vfs* pVFS, const char* pFilePath, const } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); return result; } @@ -45353,7 +47222,7 @@ MA_API ma_result ma_decoder_init_vfs_wav(ma_vfs* pVFS, const char* pFilePath, co } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45384,7 +47253,7 @@ MA_API ma_result ma_decoder_init_vfs_flac(ma_vfs* pVFS, const char* pFilePath, c } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45415,7 +47284,7 @@ MA_API ma_result ma_decoder_init_vfs_mp3(ma_vfs* pVFS, const char* pFilePath, co } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45446,7 +47315,7 @@ MA_API ma_result ma_decoder_init_vfs_vorbis(ma_vfs* pVFS, const char* pFilePath, } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45475,12 +47344,7 @@ static ma_result ma_decoder__preinit_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePat return MA_INVALID_ARGS; } - if (pVFS == NULL) { - result = ma_default_vfs_open_w(NULL, pFilePath, MA_OPEN_MODE_READ, &file); - } else { - result = ma_vfs_open_w(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); - } - + result = ma_vfs_or_default_open_w(pVFS, pFilePath, MA_OPEN_MODE_READ, &file); if (result != MA_SUCCESS) { return result; } @@ -45537,7 +47401,7 @@ MA_API ma_result ma_decoder_init_vfs_w(ma_vfs* pVFS, const wchar_t* pFilePath, c } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); return result; } @@ -45562,7 +47426,7 @@ MA_API ma_result ma_decoder_init_vfs_wav_w(ma_vfs* pVFS, const wchar_t* pFilePat } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45593,7 +47457,7 @@ MA_API ma_result ma_decoder_init_vfs_flac_w(ma_vfs* pVFS, const wchar_t* pFilePa } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45624,7 +47488,7 @@ MA_API ma_result ma_decoder_init_vfs_mp3_w(ma_vfs* pVFS, const wchar_t* pFilePat } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45655,7 +47519,7 @@ MA_API ma_result ma_decoder_init_vfs_vorbis_w(ma_vfs* pVFS, const wchar_t* pFile } if (result != MA_SUCCESS) { - ma_vfs_close(pVFS, pDecoder->backend.vfs.file); + ma_vfs_or_default_close(pVFS, pDecoder->backend.vfs.file); } return result; @@ -45733,11 +47597,7 @@ MA_API ma_result ma_decoder_uninit(ma_decoder* pDecoder) } if (pDecoder->onRead == ma_decoder__on_read_vfs) { - if (pDecoder->backend.vfs.pVFS == NULL) { - ma_default_vfs_close(NULL, pDecoder->backend.vfs.file); - } else { - ma_vfs_close(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file); - } + ma_vfs_or_default_close(pDecoder->backend.vfs.pVFS, pDecoder->backend.vfs.file); } ma_data_converter_uninit(&pDecoder->converter); @@ -45786,7 +47646,7 @@ MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO ma_uint64 totalFramesReadOut; ma_uint64 totalFramesReadIn; void* pRunningFramesOut; - + if (pDecoder == NULL) { return 0; } @@ -45810,7 +47670,7 @@ MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO totalFramesReadOut = 0; totalFramesReadIn = 0; pRunningFramesOut = pFramesOut; - + while (totalFramesReadOut < frameCount) { ma_uint8 pIntermediaryBuffer[MA_DATA_CONVERTER_STACK_BUFFER_SIZE]; /* In internal format. */ ma_uint64 intermediaryBufferCap = sizeof(pIntermediaryBuffer) / ma_get_bytes_per_frame(pDecoder->internalFormat, pDecoder->internalChannels); @@ -45834,6 +47694,8 @@ MA_API ma_uint64 ma_decoder_read_pcm_frames(ma_decoder* pDecoder, void* pFramesO if (requiredInputFrameCount > 0) { framesReadThisIterationIn = pDecoder->onReadPCMFrames(pDecoder, pIntermediaryBuffer, framesToReadThisIterationIn); totalFramesReadIn += framesReadThisIterationIn; + } else { + framesReadThisIterationIn = 0; } /* @@ -45883,6 +47745,8 @@ MA_API ma_result ma_decoder_seek_to_pcm_frame(ma_decoder* pDecoder, ma_uint64 fr if (result == MA_SUCCESS) { pDecoder->readPointerInPCMFrames = frameIndex; } + + return result; } /* Should never get here, but if we do it means onSeekToPCMFrame was not set by the backend. */ @@ -45926,7 +47790,7 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec void* pPCMFramesOut; MA_ASSERT(pDecoder != NULL); - + totalFrameCount = 0; bpf = ma_get_bytes_per_frame(pDecoder->outputFormat, pDecoder->outputChannels); @@ -45973,7 +47837,7 @@ static ma_result ma_decoder__full_decode_and_uninit(ma_decoder* pDecoder, ma_dec } } - + if (pConfigOut != NULL) { pConfigOut->format = pDecoder->outputFormat; pConfigOut->channels = pDecoder->outputChannels; @@ -46043,7 +47907,7 @@ MA_API ma_result ma_decode_memory(const void* pData, size_t dataSize, ma_decoder } config = ma_decoder_config_init_copy(pConfig); - + result = ma_decoder_init_memory(pData, dataSize, &config, &decoder); if (result != MA_SUCCESS) { return result; @@ -46100,7 +47964,7 @@ static ma_result ma_encoder__on_init_wav(ma_encoder* pEncoder) allocationCallbacks.onMalloc = pEncoder->config.allocationCallbacks.onMalloc; allocationCallbacks.onRealloc = pEncoder->config.allocationCallbacks.onRealloc; allocationCallbacks.onFree = pEncoder->config.allocationCallbacks.onFree; - + if (!drwav_init_write(pWav, &wavFormat, ma_encoder__internal_on_write_wav, ma_encoder__internal_on_seek_wav, pEncoder, &allocationCallbacks)) { return MA_ERROR; } @@ -46377,6 +48241,16 @@ static ma_result ma_waveform__data_source_on_get_cursor(ma_data_source* pDataSou return MA_SUCCESS; } +static double ma_waveform__calculate_advance(ma_uint32 sampleRate, double frequency) +{ + return (1.0 / (sampleRate / frequency)); +} + +static void ma_waveform__update_advance(ma_waveform* pWaveform) +{ + pWaveform->advance = ma_waveform__calculate_advance(pWaveform->config.sampleRate, pWaveform->config.frequency); +} + MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform* pWaveform) { if (pWaveform == NULL) { @@ -46390,7 +48264,7 @@ MA_API ma_result ma_waveform_init(const ma_waveform_config* pConfig, ma_waveform pWaveform->ds.onGetCursor = ma_waveform__data_source_on_get_cursor; pWaveform->ds.onGetLength = NULL; /* Intentionally set to NULL since there's no notion of a length in waveforms. */ pWaveform->config = *pConfig; - pWaveform->advance = 1.0 / pWaveform->config.sampleRate; + pWaveform->advance = ma_waveform__calculate_advance(pWaveform->config.sampleRate, pWaveform->config.frequency); pWaveform->time = 0; return MA_SUCCESS; @@ -46413,6 +48287,18 @@ MA_API ma_result ma_waveform_set_frequency(ma_waveform* pWaveform, double freque } pWaveform->config.frequency = frequency; + ma_waveform__update_advance(pWaveform); + + return MA_SUCCESS; +} + +MA_API ma_result ma_waveform_set_type(ma_waveform* pWaveform, ma_waveform_type type) +{ + if (pWaveform == NULL) { + return MA_INVALID_ARGS; + } + + pWaveform->config.type = type; return MA_SUCCESS; } @@ -46422,26 +48308,27 @@ MA_API ma_result ma_waveform_set_sample_rate(ma_waveform* pWaveform, ma_uint32 s return MA_INVALID_ARGS; } - pWaveform->advance = 1.0 / sampleRate; + pWaveform->config.sampleRate = sampleRate; + ma_waveform__update_advance(pWaveform); + return MA_SUCCESS; } -static float ma_waveform_sine_f32(double time, double frequency, double amplitude) +static float ma_waveform_sine_f32(double time, double amplitude) { - return (float)(ma_sin(MA_TAU_D * time * frequency) * amplitude); + return (float)(ma_sin(MA_TAU_D * time) * amplitude); } -static ma_int16 ma_waveform_sine_s16(double time, double frequency, double amplitude) +static ma_int16 ma_waveform_sine_s16(double time, double amplitude) { - return ma_pcm_sample_f32_to_s16(ma_waveform_sine_f32(time, frequency, amplitude)); + return ma_pcm_sample_f32_to_s16(ma_waveform_sine_f32(time, amplitude)); } -static float ma_waveform_square_f32(double time, double frequency, double amplitude) +static float ma_waveform_square_f32(double time, double amplitude) { - double t = time * frequency; - double f = t - (ma_int64)t; + double f = time - (ma_int64)time; double r; - + if (f < 0.5) { r = amplitude; } else { @@ -46451,15 +48338,14 @@ static float ma_waveform_square_f32(double time, double frequency, double amplit return (float)r; } -static ma_int16 ma_waveform_square_s16(double time, double frequency, double amplitude) +static ma_int16 ma_waveform_square_s16(double time, double amplitude) { - return ma_pcm_sample_f32_to_s16(ma_waveform_square_f32(time, frequency, amplitude)); + return ma_pcm_sample_f32_to_s16(ma_waveform_square_f32(time, amplitude)); } -static float ma_waveform_triangle_f32(double time, double frequency, double amplitude) +static float ma_waveform_triangle_f32(double time, double amplitude) { - double t = time * frequency; - double f = t - (ma_int64)t; + double f = time - (ma_int64)time; double r; r = 2 * ma_abs(2 * (f - 0.5)) - 1; @@ -46467,15 +48353,14 @@ static float ma_waveform_triangle_f32(double time, double frequency, double ampl return (float)(r * amplitude); } -static ma_int16 ma_waveform_triangle_s16(double time, double frequency, double amplitude) +static ma_int16 ma_waveform_triangle_s16(double time, double amplitude) { - return ma_pcm_sample_f32_to_s16(ma_waveform_triangle_f32(time, frequency, amplitude)); + return ma_pcm_sample_f32_to_s16(ma_waveform_triangle_f32(time, amplitude)); } -static float ma_waveform_sawtooth_f32(double time, double frequency, double amplitude) +static float ma_waveform_sawtooth_f32(double time, double amplitude) { - double t = time * frequency; - double f = t - (ma_int64)t; + double f = time - (ma_int64)time; double r; r = 2 * (f - 0.5); @@ -46483,9 +48368,9 @@ static float ma_waveform_sawtooth_f32(double time, double frequency, double ampl return (float)(r * amplitude); } -static ma_int16 ma_waveform_sawtooth_s16(double time, double frequency, double amplitude) +static ma_int16 ma_waveform_sawtooth_s16(double time, double amplitude) { - return ma_pcm_sample_f32_to_s16(ma_waveform_sawtooth_f32(time, frequency, amplitude)); + return ma_pcm_sample_f32_to_s16(ma_waveform_sawtooth_f32(time, amplitude)); } static void ma_waveform_read_pcm_frames__sine(ma_waveform* pWaveform, void* pFramesOut, ma_uint64 frameCount) @@ -46501,7 +48386,7 @@ static void ma_waveform_read_pcm_frames__sine(ma_waveform* pWaveform, void* pFra if (pWaveform->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_sine_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_sine_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46511,7 +48396,7 @@ static void ma_waveform_read_pcm_frames__sine(ma_waveform* pWaveform, void* pFra } else if (pWaveform->config.format == ma_format_s16) { ma_int16* pFramesOutS16 = (ma_int16*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - ma_int16 s = ma_waveform_sine_s16(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + ma_int16 s = ma_waveform_sine_s16(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46520,7 +48405,7 @@ static void ma_waveform_read_pcm_frames__sine(ma_waveform* pWaveform, void* pFra } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_sine_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_sine_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46543,7 +48428,7 @@ static void ma_waveform_read_pcm_frames__square(ma_waveform* pWaveform, void* pF if (pWaveform->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_square_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_square_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46553,7 +48438,7 @@ static void ma_waveform_read_pcm_frames__square(ma_waveform* pWaveform, void* pF } else if (pWaveform->config.format == ma_format_s16) { ma_int16* pFramesOutS16 = (ma_int16*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - ma_int16 s = ma_waveform_square_s16(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + ma_int16 s = ma_waveform_square_s16(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46562,7 +48447,7 @@ static void ma_waveform_read_pcm_frames__square(ma_waveform* pWaveform, void* pF } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_square_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_square_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46585,7 +48470,7 @@ static void ma_waveform_read_pcm_frames__triangle(ma_waveform* pWaveform, void* if (pWaveform->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_triangle_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_triangle_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46595,7 +48480,7 @@ static void ma_waveform_read_pcm_frames__triangle(ma_waveform* pWaveform, void* } else if (pWaveform->config.format == ma_format_s16) { ma_int16* pFramesOutS16 = (ma_int16*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - ma_int16 s = ma_waveform_triangle_s16(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + ma_int16 s = ma_waveform_triangle_s16(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46604,7 +48489,7 @@ static void ma_waveform_read_pcm_frames__triangle(ma_waveform* pWaveform, void* } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_triangle_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_triangle_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46627,7 +48512,7 @@ static void ma_waveform_read_pcm_frames__sawtooth(ma_waveform* pWaveform, void* if (pWaveform->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_sawtooth_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_sawtooth_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46637,7 +48522,7 @@ static void ma_waveform_read_pcm_frames__sawtooth(ma_waveform* pWaveform, void* } else if (pWaveform->config.format == ma_format_s16) { ma_int16* pFramesOutS16 = (ma_int16*)pFramesOut; for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - ma_int16 s = ma_waveform_sawtooth_s16(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + ma_int16 s = ma_waveform_sawtooth_s16(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46646,7 +48531,7 @@ static void ma_waveform_read_pcm_frames__sawtooth(ma_waveform* pWaveform, void* } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - float s = ma_waveform_sawtooth_f32(pWaveform->time, pWaveform->config.frequency, pWaveform->config.amplitude); + float s = ma_waveform_sawtooth_f32(pWaveform->time, pWaveform->config.amplitude); pWaveform->time += pWaveform->advance; for (iChannel = 0; iChannel < pWaveform->config.channels; iChannel += 1) { @@ -46728,7 +48613,7 @@ MA_API ma_noise_config ma_noise_config_init(ma_format format, ma_uint32 channels static ma_result ma_noise__data_source_on_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead) { ma_uint64 framesRead = ma_noise_read_pcm_frames((ma_noise*)pDataSource, pFramesOut, frameCount); - + if (pFramesRead != NULL) { *pFramesRead = framesRead; } @@ -46801,6 +48686,37 @@ MA_API ma_result ma_noise_init(const ma_noise_config* pConfig, ma_noise* pNoise) return MA_SUCCESS; } +MA_API ma_result ma_noise_set_amplitude(ma_noise* pNoise, double amplitude) +{ + if (pNoise == NULL) { + return MA_INVALID_ARGS; + } + + pNoise->config.amplitude = amplitude; + return MA_SUCCESS; +} + +MA_API ma_result ma_noise_set_seed(ma_noise* pNoise, ma_int32 seed) +{ + if (pNoise == NULL) { + return MA_INVALID_ARGS; + } + + pNoise->lcg.state = seed; + return MA_SUCCESS; +} + + +MA_API ma_result ma_noise_set_type(ma_noise* pNoise, ma_noise_type type) +{ + if (pNoise == NULL) { + return MA_INVALID_ARGS; + } + + pNoise->config.type = type; + return MA_SUCCESS; +} + static MA_INLINE float ma_noise_f32_white(ma_noise* pNoise) { return (float)(ma_lcg_rand_f64(&pNoise->lcg) * pNoise->config.amplitude); @@ -46815,20 +48731,22 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__white(ma_noise* pNoise, voi { ma_uint64 iFrame; ma_uint32 iChannel; + const ma_uint32 channels = pNoise->config.channels; + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { float s = ma_noise_f32_white(pNoise); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutF32[iFrame*pNoise->config.channels + iChannel] = s; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutF32[iFrame*channels + iChannel] = s; } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutF32[iFrame*pNoise->config.channels + iChannel] = ma_noise_f32_white(pNoise); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutF32[iFrame*channels + iChannel] = ma_noise_f32_white(pNoise); } } } @@ -46837,31 +48755,31 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__white(ma_noise* pNoise, voi if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { ma_int16 s = ma_noise_s16_white(pNoise); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutS16[iFrame*pNoise->config.channels + iChannel] = s; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutS16[iFrame*channels + iChannel] = s; } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutS16[iFrame*pNoise->config.channels + iChannel] = ma_noise_s16_white(pNoise); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutS16[iFrame*channels + iChannel] = ma_noise_s16_white(pNoise); } } } } else { - ma_uint32 bps = ma_get_bytes_per_sample(pNoise->config.format); - ma_uint32 bpf = bps * pNoise->config.channels; - + const ma_uint32 bps = ma_get_bytes_per_sample(pNoise->config.format); + const ma_uint32 bpf = bps * channels; + if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { float s = ma_noise_f32_white(pNoise); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { ma_pcm_convert(ma_offset_ptr(pFramesOut, iFrame*bpf + iChannel*bps), pNoise->config.format, &s, ma_format_f32, 1, ma_dither_mode_none); } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { float s = ma_noise_f32_white(pNoise); ma_pcm_convert(ma_offset_ptr(pFramesOut, iFrame*bpf + iChannel*bps), pNoise->config.format, &s, ma_format_f32, 1, ma_dither_mode_none); } @@ -46932,20 +48850,22 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__pink(ma_noise* pNoise, void { ma_uint64 iFrame; ma_uint32 iChannel; + const ma_uint32 channels = pNoise->config.channels; + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { float s = ma_noise_f32_pink(pNoise, 0); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutF32[iFrame*pNoise->config.channels + iChannel] = s; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutF32[iFrame*channels + iChannel] = s; } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutF32[iFrame*pNoise->config.channels + iChannel] = ma_noise_f32_pink(pNoise, iChannel); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutF32[iFrame*channels + iChannel] = ma_noise_f32_pink(pNoise, iChannel); } } } @@ -46954,31 +48874,31 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__pink(ma_noise* pNoise, void if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { ma_int16 s = ma_noise_s16_pink(pNoise, 0); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutS16[iFrame*pNoise->config.channels + iChannel] = s; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutS16[iFrame*channels + iChannel] = s; } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutS16[iFrame*pNoise->config.channels + iChannel] = ma_noise_s16_pink(pNoise, iChannel); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutS16[iFrame*channels + iChannel] = ma_noise_s16_pink(pNoise, iChannel); } } } } else { - ma_uint32 bps = ma_get_bytes_per_sample(pNoise->config.format); - ma_uint32 bpf = bps * pNoise->config.channels; - + const ma_uint32 bps = ma_get_bytes_per_sample(pNoise->config.format); + const ma_uint32 bpf = bps * channels; + if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { float s = ma_noise_f32_pink(pNoise, 0); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { ma_pcm_convert(ma_offset_ptr(pFramesOut, iFrame*bpf + iChannel*bps), pNoise->config.format, &s, ma_format_f32, 1, ma_dither_mode_none); } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { float s = ma_noise_f32_pink(pNoise, iChannel); ma_pcm_convert(ma_offset_ptr(pFramesOut, iFrame*bpf + iChannel*bps), pNoise->config.format, &s, ma_format_f32, 1, ma_dither_mode_none); } @@ -46993,7 +48913,7 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__pink(ma_noise* pNoise, void static MA_INLINE float ma_noise_f32_brownian(ma_noise* pNoise, ma_uint32 iChannel) { double result; - + result = (ma_lcg_rand_f64(&pNoise->lcg) + pNoise->state.brownian.accumulation[iChannel]); result /= 1.005; /* Don't escape the -1..1 range on average. */ @@ -47012,20 +48932,22 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, { ma_uint64 iFrame; ma_uint32 iChannel; + const ma_uint32 channels = pNoise->config.channels; + MA_ASSUME(channels >= MA_MIN_CHANNELS && channels <= MA_MAX_CHANNELS); if (pNoise->config.format == ma_format_f32) { float* pFramesOutF32 = (float*)pFramesOut; if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { float s = ma_noise_f32_brownian(pNoise, 0); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutF32[iFrame*pNoise->config.channels + iChannel] = s; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutF32[iFrame*channels + iChannel] = s; } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutF32[iFrame*pNoise->config.channels + iChannel] = ma_noise_f32_brownian(pNoise, iChannel); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutF32[iFrame*channels + iChannel] = ma_noise_f32_brownian(pNoise, iChannel); } } } @@ -47034,31 +48956,31 @@ static MA_INLINE ma_uint64 ma_noise_read_pcm_frames__brownian(ma_noise* pNoise, if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { ma_int16 s = ma_noise_s16_brownian(pNoise, 0); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutS16[iFrame*pNoise->config.channels + iChannel] = s; + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutS16[iFrame*channels + iChannel] = s; } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { - pFramesOutS16[iFrame*pNoise->config.channels + iChannel] = ma_noise_s16_brownian(pNoise, iChannel); + for (iChannel = 0; iChannel < channels; iChannel += 1) { + pFramesOutS16[iFrame*channels + iChannel] = ma_noise_s16_brownian(pNoise, iChannel); } } } } else { - ma_uint32 bps = ma_get_bytes_per_sample(pNoise->config.format); - ma_uint32 bpf = bps * pNoise->config.channels; - + const ma_uint32 bps = ma_get_bytes_per_sample(pNoise->config.format); + const ma_uint32 bpf = bps * channels; + if (pNoise->config.duplicateChannels) { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { float s = ma_noise_f32_brownian(pNoise, 0); - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { ma_pcm_convert(ma_offset_ptr(pFramesOut, iFrame*bpf + iChannel*bps), pNoise->config.format, &s, ma_format_f32, 1, ma_dither_mode_none); } } } else { for (iFrame = 0; iFrame < frameCount; iFrame += 1) { - for (iChannel = 0; iChannel < pNoise->config.channels; iChannel += 1) { + for (iChannel = 0; iChannel < channels; iChannel += 1) { float s = ma_noise_f32_brownian(pNoise, iChannel); ma_pcm_convert(ma_offset_ptr(pFramesOut, iFrame*bpf + iChannel*bps), pNoise->config.format, &s, ma_format_f32, 1, ma_dither_mode_none); } @@ -47165,6 +49087,8 @@ code below please report the bug to the respective repository for the relevant p #else #define DRWAV_INLINE inline __attribute__((always_inline)) #endif +#elif defined(__WATCOMC__) + #define DRWAV_INLINE __inline #else #define DRWAV_INLINE #endif @@ -47229,29 +49153,10 @@ DRWAV_API const char* drwav_version_string(void) #endif static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; -static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; -static DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) -{ - int i; - for (i = 0; i < 16; i += 1) { - if (a[i] != b[i]) { - return DRWAV_FALSE; - } - } - return DRWAV_TRUE; -} -static DRWAV_INLINE drwav_bool32 drwav__fourcc_equal(const drwav_uint8* a, const char* b) -{ - return - a[0] == b[0] && - a[1] == b[1] && - a[2] == b[2] && - a[3] == b[3]; -} static DRWAV_INLINE int drwav__is_little_endian(void) { #if defined(DRWAV_X86) || defined(DRWAV_X64) @@ -47263,32 +49168,6 @@ static DRWAV_INLINE int drwav__is_little_endian(void) return (*(char*)&n) == 1; #endif } -static DRWAV_INLINE drwav_uint16 drwav__bytes_to_u16(const drwav_uint8* data) -{ - return (data[0] << 0) | (data[1] << 8); -} -static DRWAV_INLINE drwav_int16 drwav__bytes_to_s16(const drwav_uint8* data) -{ - return (short)drwav__bytes_to_u16(data); -} -static DRWAV_INLINE drwav_uint32 drwav__bytes_to_u32(const drwav_uint8* data) -{ - return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); -} -static DRWAV_INLINE drwav_int32 drwav__bytes_to_s32(const drwav_uint8* data) -{ - return (drwav_int32)drwav__bytes_to_u32(data); -} -static DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const drwav_uint8* data) -{ - return - ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | - ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); -} -static DRWAV_INLINE drwav_int64 drwav__bytes_to_s64(const drwav_uint8* data) -{ - return (drwav_int64)drwav__bytes_to_u64(data); -} static DRWAV_INLINE void drwav__bytes_to_guid(const drwav_uint8* data, drwav_uint8* guid) { int i; @@ -47351,14 +49230,14 @@ static DRWAV_INLINE drwav_uint64 drwav__bswap64(drwav_uint64 n) #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drwav_uint64)0xFF00000000000000) >> 56) | - ((n & (drwav_uint64)0x00FF000000000000) >> 40) | - ((n & (drwav_uint64)0x0000FF0000000000) >> 24) | - ((n & (drwav_uint64)0x000000FF00000000) >> 8) | - ((n & (drwav_uint64)0x00000000FF000000) << 8) | - ((n & (drwav_uint64)0x0000000000FF0000) << 24) | - ((n & (drwav_uint64)0x000000000000FF00) << 40) | - ((n & (drwav_uint64)0x00000000000000FF) << 56); + return ((n & ((drwav_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drwav_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drwav_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drwav_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drwav_uint64)0xFF000000 )) << 8) | + ((n & ((drwav_uint64)0x00FF0000 )) << 24) | + ((n & ((drwav_uint64)0x0000FF00 )) << 40) | + ((n & ((drwav_uint64)0x000000FF )) << 56); #endif } static DRWAV_INLINE drwav_int16 drwav__bswap_s16(drwav_int16 n) @@ -47503,22 +49382,22 @@ static DRWAV_INLINE void drwav__bswap_samples(void* pSamples, drwav_uint64 sampl } break; } } -static void* drwav__malloc_default(size_t sz, void* pUserData) +DRWAV_PRIVATE void* drwav__malloc_default(size_t sz, void* pUserData) { (void)pUserData; return DRWAV_MALLOC(sz); } -static void* drwav__realloc_default(void* p, size_t sz, void* pUserData) +DRWAV_PRIVATE void* drwav__realloc_default(void* p, size_t sz, void* pUserData) { (void)pUserData; return DRWAV_REALLOC(p, sz); } -static void drwav__free_default(void* p, void* pUserData) +DRWAV_PRIVATE void drwav__free_default(void* p, void* pUserData) { (void)pUserData; DRWAV_FREE(p); } -static void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; @@ -47531,7 +49410,7 @@ static void* drwav__malloc_from_callbacks(size_t sz, const drwav_allocation_call } return NULL; } -static void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks == NULL) { return NULL; @@ -47553,7 +49432,7 @@ static void* drwav__realloc_from_callbacks(void* p, size_t szNew, size_t szOld, } return NULL; } -static void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks* pAllocationCallbacks) { if (p == NULL || pAllocationCallbacks == NULL) { return; @@ -47562,7 +49441,7 @@ static void drwav__free_from_callbacks(void* p, const drwav_allocation_callbacks pAllocationCallbacks->onFree(p, pAllocationCallbacks->pUserData); } } -static drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_allocation_callbacks drwav_copy_allocation_callbacks_or_defaults(const drwav_allocation_callbacks* pAllocationCallbacks) { if (pAllocationCallbacks != NULL) { return *pAllocationCallbacks; @@ -47581,20 +49460,20 @@ static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 fo formatTag == DR_WAVE_FORMAT_ADPCM || formatTag == DR_WAVE_FORMAT_DVI_ADPCM; } -static unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_riff(drwav_uint64 chunkSize) { return (unsigned int)(chunkSize % 2); } -static unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) +DRWAV_PRIVATE unsigned int drwav__chunk_padding_size_w64(drwav_uint64 chunkSize) { return (unsigned int)(chunkSize % 8); } -static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); -static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); -static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); -static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); +DRWAV_PRIVATE drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) { - if (container == drwav_container_riff) { + if (container == drwav_container_riff || container == drwav_container_rf64) { drwav_uint8 sizeInBytes[4]; if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { return DRWAV_AT_END; @@ -47602,7 +49481,7 @@ static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUser if (onRead(pUserData, sizeInBytes, 4) != 4) { return DRWAV_INVALID_FILE; } - pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes); + pHeaderOut->sizeInBytes = drwav_bytes_to_u32(sizeInBytes); pHeaderOut->paddingSize = drwav__chunk_padding_size_riff(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 8; } else { @@ -47613,13 +49492,13 @@ static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUser if (onRead(pUserData, sizeInBytes, 8) != 8) { return DRWAV_INVALID_FILE; } - pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; + pHeaderOut->sizeInBytes = drwav_bytes_to_u64(sizeInBytes) - 24; pHeaderOut->paddingSize = drwav__chunk_padding_size_w64(pHeaderOut->sizeInBytes); *pRunningBytesReadOut += 24; } return DRWAV_SUCCESS; } -static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +DRWAV_PRIVATE drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { drwav_uint64 bytesRemainingToSeek = offset; while (bytesRemainingToSeek > 0) { @@ -47637,7 +49516,7 @@ static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 off } return DRWAV_TRUE; } -static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +DRWAV_PRIVATE drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) { if (offset <= 0x7FFFFFFF) { return onSeek(pUserData, (int)offset, drwav_seek_origin_start); @@ -47656,14 +49535,14 @@ static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset -= 0x7FFFFFFF; } } -static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) +DRWAV_PRIVATE drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) { drwav_chunk_header header; drwav_uint8 fmt[16]; if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { return DRWAV_FALSE; } - while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + while (((container == drwav_container_riff || container == drwav_container_rf64) && !drwav_fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT))) { if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { return DRWAV_FALSE; } @@ -47672,12 +49551,12 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe return DRWAV_FALSE; } } - if (container == drwav_container_riff) { - if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { + if (container == drwav_container_riff || container == drwav_container_rf64) { + if (!drwav_fourcc_equal(header.id.fourcc, "fmt ")) { return DRWAV_FALSE; } } else { - if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) { + if (!drwav_guid_equal(header.id.guid, drwavGUID_W64_FMT)) { return DRWAV_FALSE; } } @@ -47685,12 +49564,12 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe return DRWAV_FALSE; } *pRunningBytesReadOut += sizeof(fmt); - fmtOut->formatTag = drwav__bytes_to_u16(fmt + 0); - fmtOut->channels = drwav__bytes_to_u16(fmt + 2); - fmtOut->sampleRate = drwav__bytes_to_u32(fmt + 4); - fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8); - fmtOut->blockAlign = drwav__bytes_to_u16(fmt + 12); - fmtOut->bitsPerSample = drwav__bytes_to_u16(fmt + 14); + fmtOut->formatTag = drwav_bytes_to_u16(fmt + 0); + fmtOut->channels = drwav_bytes_to_u16(fmt + 2); + fmtOut->sampleRate = drwav_bytes_to_u32(fmt + 4); + fmtOut->avgBytesPerSec = drwav_bytes_to_u32(fmt + 8); + fmtOut->blockAlign = drwav_bytes_to_u16(fmt + 12); + fmtOut->bitsPerSample = drwav_bytes_to_u16(fmt + 14); fmtOut->extendedSize = 0; fmtOut->validBitsPerSample = 0; fmtOut->channelMask = 0; @@ -47703,7 +49582,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe } *pRunningBytesReadOut += sizeof(fmt_cbSize); bytesReadSoFar = 18; - fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize); + fmtOut->extendedSize = drwav_bytes_to_u16(fmt_cbSize); if (fmtOut->extendedSize > 0) { if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { if (fmtOut->extendedSize != 22) { @@ -47715,8 +49594,8 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { return DRWAV_FALSE; } - fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0); - fmtOut->channelMask = drwav__bytes_to_u32(fmtext + 2); + fmtOut->validBitsPerSample = drwav_bytes_to_u16(fmtext + 0); + fmtOut->channelMask = drwav_bytes_to_u32(fmtext + 2); drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat); } else { if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { @@ -47739,7 +49618,7 @@ static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSe } return DRWAV_TRUE; } -static size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +DRWAV_PRIVATE size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) { size_t bytesRead; DRWAV_ASSERT(onRead != NULL); @@ -47749,7 +49628,7 @@ static size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBuf return bytesRead; } #if 0 -static drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) { DRWAV_ASSERT(onSeek != NULL); DRWAV_ASSERT(pCursor != NULL); @@ -47764,7 +49643,7 @@ static drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int return DRWAV_TRUE; } #endif -static drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +DRWAV_PRIVATE drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) { if ((pWav->bitsPerSample & 0x7) == 0) { return (pWav->bitsPerSample * pWav->fmt.channels) >> 3; @@ -47780,10 +49659,10 @@ DRWAV_API drwav_uint16 drwav_fmt_get_format(const drwav_fmt* pFMT) if (pFMT->formatTag != DR_WAVE_FORMAT_EXTENSIBLE) { return pFMT->formatTag; } else { - return drwav__bytes_to_u16(pFMT->subFormat); + return drwav_bytes_to_u16(pFMT->subFormat); } } -static drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onRead == NULL || onSeek == NULL) { return DRWAV_FALSE; @@ -47798,25 +49677,25 @@ static drwav_bool32 drwav_preinit(drwav* pWav, drwav_read_proc onRead, drwav_see } return DRWAV_TRUE; } -static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +DRWAV_PRIVATE drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) { drwav_uint64 cursor; drwav_bool32 sequential; drwav_uint8 riff[4]; drwav_fmt fmt; unsigned short translatedFormatTag; - drwav_uint64 sampleCountFromFactChunk; drwav_bool32 foundDataChunk; - drwav_uint64 dataChunkSize; + drwav_uint64 dataChunkSize = 0; + drwav_uint64 sampleCountFromFactChunk = 0; drwav_uint64 chunkSize; cursor = 0; sequential = (flags & DRWAV_SEQUENTIAL) != 0; if (drwav__on_read(pWav->onRead, pWav->pUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { return DRWAV_FALSE; } - if (drwav__fourcc_equal(riff, "RIFF")) { + if (drwav_fourcc_equal(riff, "RIFF")) { pWav->container = drwav_container_riff; - } else if (drwav__fourcc_equal(riff, "riff")) { + } else if (drwav_fourcc_equal(riff, "riff")) { int i; drwav_uint8 riff2[12]; pWav->container = drwav_container_w64; @@ -47828,22 +49707,30 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, return DRWAV_FALSE; } } + } else if (drwav_fourcc_equal(riff, "RF64")) { + pWav->container = drwav_container_rf64; } else { return DRWAV_FALSE; } - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { drwav_uint8 chunkSizeBytes[4]; drwav_uint8 wave[4]; if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } - if (drwav__bytes_to_u32(chunkSizeBytes) < 36) { - return DRWAV_FALSE; + if (pWav->container == drwav_container_riff) { + if (drwav_bytes_to_u32(chunkSizeBytes) < 36) { + return DRWAV_FALSE; + } + } else { + if (drwav_bytes_to_u32(chunkSizeBytes) != 0xFFFFFFFF) { + return DRWAV_FALSE; + } } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } - if (!drwav__fourcc_equal(wave, "WAVE")) { + if (!drwav_fourcc_equal(wave, "WAVE")) { return DRWAV_FALSE; } } else { @@ -47852,16 +49739,48 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, if (drwav__on_read(pWav->onRead, pWav->pUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { return DRWAV_FALSE; } - if (drwav__bytes_to_u64(chunkSizeBytes) < 80) { + if (drwav_bytes_to_u64(chunkSizeBytes) < 80) { return DRWAV_FALSE; } if (drwav__on_read(pWav->onRead, pWav->pUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { return DRWAV_FALSE; } - if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) { + if (!drwav_guid_equal(wave, drwavGUID_W64_WAVE)) { return DRWAV_FALSE; } } + if (pWav->container == drwav_container_rf64) { + drwav_uint8 sizeBytes[8]; + drwav_uint64 bytesRemainingInChunk; + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(pWav->onRead, pWav->pUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + if (!drwav_fourcc_equal(header.id.fourcc, "ds64")) { + return DRWAV_FALSE; + } + bytesRemainingInChunk = header.sizeInBytes + header.paddingSize; + if (!drwav__seek_forward(pWav->onSeek, 8, pWav->pUserData)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + cursor += 8; + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + dataChunkSize = drwav_bytes_to_u64(sizeBytes); + if (drwav__on_read(pWav->onRead, pWav->pUserData, sizeBytes, sizeof(sizeBytes), &cursor) != sizeof(sizeBytes)) { + return DRWAV_FALSE; + } + bytesRemainingInChunk -= 8; + sampleCountFromFactChunk = drwav_bytes_to_u64(sizeBytes); + if (!drwav__seek_forward(pWav->onSeek, bytesRemainingInChunk, pWav->pUserData)) { + return DRWAV_FALSE; + } + cursor += bytesRemainingInChunk; + } if (!drwav__read_fmt(pWav->onRead, pWav->onSeek, pWav->pUserData, pWav->container, &cursor, &fmt)) { return DRWAV_FALSE; } @@ -47873,11 +49792,9 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } translatedFormatTag = fmt.formatTag; if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { - translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); + translatedFormatTag = drwav_bytes_to_u16(fmt.subFormat + 0); } - sampleCountFromFactChunk = 0; foundDataChunk = DRWAV_FALSE; - dataChunkSize = 0; for (;;) { drwav_chunk_header header; @@ -47901,13 +49818,15 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } chunkSize = header.sizeInBytes; - if (pWav->container == drwav_container_riff) { - if (drwav__fourcc_equal(header.id.fourcc, "data")) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + if (drwav_fourcc_equal(header.id.fourcc, "data")) { foundDataChunk = DRWAV_TRUE; - dataChunkSize = chunkSize; + if (pWav->container != drwav_container_rf64) { + dataChunkSize = chunkSize; + } } } else { - if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_DATA)) { foundDataChunk = DRWAV_TRUE; dataChunkSize = chunkSize; } @@ -47916,7 +49835,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, break; } if (pWav->container == drwav_container_riff) { - if (drwav__fourcc_equal(header.id.fourcc, "fact")) { + if (drwav_fourcc_equal(header.id.fourcc, "fact")) { drwav_uint32 sampleCount; if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCount, 4, &cursor) != 4) { return DRWAV_FALSE; @@ -47931,8 +49850,8 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, sampleCountFromFactChunk = 0; } } - } else { - if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { + } else if (pWav->container == drwav_container_w64) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_FACT)) { if (drwav__on_read(pWav->onRead, pWav->pUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { return DRWAV_FALSE; } @@ -47941,35 +49860,36 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, pWav->dataChunkDataPos = cursor; } } + } else if (pWav->container == drwav_container_rf64) { } - if (pWav->container == drwav_container_riff) { - if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { + if (drwav_fourcc_equal(header.id.fourcc, "smpl")) { drwav_uint8 smplHeaderData[36]; if (chunkSize >= sizeof(smplHeaderData)) { drwav_uint64 bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplHeaderData, sizeof(smplHeaderData), &cursor); chunkSize -= bytesJustRead; if (bytesJustRead == sizeof(smplHeaderData)) { drwav_uint32 iLoop; - pWav->smpl.manufacturer = drwav__bytes_to_u32(smplHeaderData+0); - pWav->smpl.product = drwav__bytes_to_u32(smplHeaderData+4); - pWav->smpl.samplePeriod = drwav__bytes_to_u32(smplHeaderData+8); - pWav->smpl.midiUnityNotes = drwav__bytes_to_u32(smplHeaderData+12); - pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16); - pWav->smpl.smpteFormat = drwav__bytes_to_u32(smplHeaderData+20); - pWav->smpl.smpteOffset = drwav__bytes_to_u32(smplHeaderData+24); - pWav->smpl.numSampleLoops = drwav__bytes_to_u32(smplHeaderData+28); - pWav->smpl.samplerData = drwav__bytes_to_u32(smplHeaderData+32); + pWav->smpl.manufacturer = drwav_bytes_to_u32(smplHeaderData+0); + pWav->smpl.product = drwav_bytes_to_u32(smplHeaderData+4); + pWav->smpl.samplePeriod = drwav_bytes_to_u32(smplHeaderData+8); + pWav->smpl.midiUnityNotes = drwav_bytes_to_u32(smplHeaderData+12); + pWav->smpl.midiPitchFraction = drwav_bytes_to_u32(smplHeaderData+16); + pWav->smpl.smpteFormat = drwav_bytes_to_u32(smplHeaderData+20); + pWav->smpl.smpteOffset = drwav_bytes_to_u32(smplHeaderData+24); + pWav->smpl.numSampleLoops = drwav_bytes_to_u32(smplHeaderData+28); + pWav->smpl.samplerData = drwav_bytes_to_u32(smplHeaderData+32); for (iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) { drwav_uint8 smplLoopData[24]; bytesJustRead = drwav__on_read(pWav->onRead, pWav->pUserData, smplLoopData, sizeof(smplLoopData), &cursor); chunkSize -= bytesJustRead; if (bytesJustRead == sizeof(smplLoopData)) { - pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0); - pWav->smpl.loops[iLoop].type = drwav__bytes_to_u32(smplLoopData+4); - pWav->smpl.loops[iLoop].start = drwav__bytes_to_u32(smplLoopData+8); - pWav->smpl.loops[iLoop].end = drwav__bytes_to_u32(smplLoopData+12); - pWav->smpl.loops[iLoop].fraction = drwav__bytes_to_u32(smplLoopData+16); - pWav->smpl.loops[iLoop].playCount = drwav__bytes_to_u32(smplLoopData+20); + pWav->smpl.loops[iLoop].cuePointId = drwav_bytes_to_u32(smplLoopData+0); + pWav->smpl.loops[iLoop].type = drwav_bytes_to_u32(smplLoopData+4); + pWav->smpl.loops[iLoop].start = drwav_bytes_to_u32(smplLoopData+8); + pWav->smpl.loops[iLoop].end = drwav_bytes_to_u32(smplLoopData+12); + pWav->smpl.loops[iLoop].fraction = drwav_bytes_to_u32(smplLoopData+16); + pWav->smpl.loops[iLoop].playCount = drwav_bytes_to_u32(smplLoopData+20); } else { break; } @@ -47979,7 +49899,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } } } else { - if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { + if (drwav_guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { } } chunkSize += header.paddingSize; @@ -48059,16 +49979,15 @@ DRWAV_API drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_ } return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); } -static drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint32 drwav__riff_chunk_size_riff(drwav_uint64 dataChunkSize) { - drwav_uint32 dataSubchunkPaddingSize = drwav__chunk_padding_size_riff(dataChunkSize); - if (dataChunkSize <= (0xFFFFFFFFUL - 36 - dataSubchunkPaddingSize)) { - return 36 + (drwav_uint32)(dataChunkSize + dataSubchunkPaddingSize); - } else { - return 0xFFFFFFFF; + drwav_uint64 chunkSize = 4 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; } + return (drwav_uint32)chunkSize; } -static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) { if (dataChunkSize <= 0xFFFFFFFFUL) { return (drwav_uint32)dataChunkSize; @@ -48076,22 +49995,34 @@ static drwav_uint32 drwav__data_chunk_size_riff(drwav_uint64 dataChunkSize) return 0xFFFFFFFFUL; } } -static drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_w64(drwav_uint64 dataChunkSize) { drwav_uint64 dataSubchunkPaddingSize = drwav__chunk_padding_size_w64(dataChunkSize); return 80 + 24 + dataChunkSize + dataSubchunkPaddingSize; } -static drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_w64(drwav_uint64 dataChunkSize) { return 24 + dataChunkSize; } -static size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) +DRWAV_PRIVATE drwav_uint64 drwav__riff_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + drwav_uint64 chunkSize = 4 + 36 + 24 + dataChunkSize + drwav__chunk_padding_size_riff(dataChunkSize); + if (chunkSize > 0xFFFFFFFFUL) { + chunkSize = 0xFFFFFFFFUL; + } + return chunkSize; +} +DRWAV_PRIVATE drwav_uint64 drwav__data_chunk_size_rf64(drwav_uint64 dataChunkSize) +{ + return dataChunkSize; +} +DRWAV_PRIVATE size_t drwav__write(drwav* pWav, const void* pData, size_t dataSize) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); return pWav->onWrite(pWav->pUserData, pData, dataSize); } -static size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) +DRWAV_PRIVATE size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); @@ -48100,7 +50031,7 @@ static size_t drwav__write_u16ne_to_le(drwav* pWav, drwav_uint16 value) } return drwav__write(pWav, &value, 2); } -static size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) +DRWAV_PRIVATE size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); @@ -48109,7 +50040,7 @@ static size_t drwav__write_u32ne_to_le(drwav* pWav, drwav_uint32 value) } return drwav__write(pWav, &value, 4); } -static size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) +DRWAV_PRIVATE size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) { DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(pWav->onWrite != NULL); @@ -48118,7 +50049,7 @@ static size_t drwav__write_u64ne_to_le(drwav* pWav, drwav_uint64 value) } return drwav__write(pWav, &value, 8); } -static drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pFormat, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) { if (pWav == NULL || onWrite == NULL) { return DRWAV_FALSE; @@ -48150,7 +50081,7 @@ static drwav_bool32 drwav_preinit_write(drwav* pWav, const drwav_data_format* pF pWav->isSequentialWrite = isSequential; return DRWAV_TRUE; } -static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +DRWAV_PRIVATE drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) { size_t runningPos = 0; drwav_uint64 initialDataChunkSize = 0; @@ -48165,21 +50096,35 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for } pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; if (pFormat->container == drwav_container_riff) { - drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; + drwav_uint32 chunkSizeRIFF = 28 + (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "RIFF", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, "WAVE", 4); - } else { + } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; runningPos += drwav__write(pWav, drwavGUID_W64_RIFF, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeRIFF); runningPos += drwav__write(pWav, drwavGUID_W64_WAVE, 16); + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "RF64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); + runningPos += drwav__write(pWav, "WAVE", 4); } - if (pFormat->container == drwav_container_riff) { + if (pFormat->container == drwav_container_rf64) { + drwav_uint32 initialds64ChunkSize = 28; + drwav_uint64 initialRiffChunkSize = 8 + initialds64ChunkSize + initialDataChunkSize; + runningPos += drwav__write(pWav, "ds64", 4); + runningPos += drwav__write_u32ne_to_le(pWav, initialds64ChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, initialRiffChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, initialDataChunkSize); + runningPos += drwav__write_u64ne_to_le(pWav, totalSampleCount); + runningPos += drwav__write_u32ne_to_le(pWav, 0); + } + if (pFormat->container == drwav_container_riff || pFormat->container == drwav_container_rf64) { chunkSizeFMT = 16; runningPos += drwav__write(pWav, "fmt ", 4); runningPos += drwav__write_u32ne_to_le(pWav, (drwav_uint32)chunkSizeFMT); - } else { + } else if (pFormat->container == drwav_container_w64) { chunkSizeFMT = 40; runningPos += drwav__write(pWav, drwavGUID_W64_FMT, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeFMT); @@ -48190,30 +50135,24 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for runningPos += drwav__write_u32ne_to_le(pWav, pWav->fmt.avgBytesPerSec); runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.blockAlign); runningPos += drwav__write_u16ne_to_le(pWav, pWav->fmt.bitsPerSample); - pWav->dataChunkDataPos = runningPos; if (pFormat->container == drwav_container_riff) { drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; runningPos += drwav__write(pWav, "data", 4); runningPos += drwav__write_u32ne_to_le(pWav, chunkSizeDATA); - } else { + } else if (pFormat->container == drwav_container_w64) { drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; runningPos += drwav__write(pWav, drwavGUID_W64_DATA, 16); runningPos += drwav__write_u64ne_to_le(pWav, chunkSizeDATA); - } - if (pFormat->container == drwav_container_riff) { - if (runningPos != 20 + chunkSizeFMT + 8) { - return DRWAV_FALSE; - } - } else { - if (runningPos != 40 + chunkSizeFMT + 24) { - return DRWAV_FALSE; - } + } else if (pFormat->container == drwav_container_rf64) { + runningPos += drwav__write(pWav, "data", 4); + runningPos += drwav__write_u32ne_to_le(pWav, 0xFFFFFFFF); } pWav->container = pFormat->container; pWav->channels = (drwav_uint16)pFormat->channels; pWav->sampleRate = pFormat->sampleRate; pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + pWav->dataChunkDataPos = runningPos; return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData, const drwav_allocation_callbacks* pAllocationCallbacks) @@ -48241,19 +50180,22 @@ DRWAV_API drwav_uint64 drwav_target_write_size_bytes(const drwav_data_format* pF { drwav_uint64 targetDataSizeBytes = (drwav_uint64)((drwav_int64)totalSampleCount * pFormat->channels * pFormat->bitsPerSample/8.0); drwav_uint64 riffChunkSizeBytes; - drwav_uint64 fileSizeBytes; + drwav_uint64 fileSizeBytes = 0; if (pFormat->container == drwav_container_riff) { riffChunkSizeBytes = drwav__riff_chunk_size_riff(targetDataSizeBytes); fileSizeBytes = (8 + riffChunkSizeBytes); - } else { + } else if (pFormat->container == drwav_container_w64) { riffChunkSizeBytes = drwav__riff_chunk_size_w64(targetDataSizeBytes); fileSizeBytes = riffChunkSizeBytes; + } else if (pFormat->container == drwav_container_rf64) { + riffChunkSizeBytes = drwav__riff_chunk_size_rf64(targetDataSizeBytes); + fileSizeBytes = (8 + riffChunkSizeBytes); } return fileSizeBytes; } #ifndef DR_WAV_NO_STDIO #include -static drwav_result drwav_result_from_errno(int e) +DRWAV_PRIVATE drwav_result drwav_result_from_errno(int e) { switch (e) { @@ -48654,9 +50596,9 @@ static drwav_result drwav_result_from_errno(int e) default: return DRWAV_ERROR; } } -static drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) +DRWAV_PRIVATE drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif if (ppFile != NULL) { @@ -48665,7 +50607,7 @@ static drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRWAV_INVALID_ARGS; } -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drwav_result_from_errno(err); @@ -48691,11 +50633,11 @@ static drwav_result drwav_fopen(FILE** ppFile, const char* pFilePath, const char return DRWAV_SUCCESS; } #if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRWAV_HAS_WFOPEN #endif #endif -static drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const wchar_t* pOpenMode, const drwav_allocation_callbacks* pAllocationCallbacks) { if (ppFile != NULL) { *ppFile = NULL; @@ -48757,15 +50699,15 @@ static drwav_result drwav_wfopen(FILE** ppFile, const wchar_t* pFilePath, const #endif return DRWAV_SUCCESS; } -static size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +DRWAV_PRIVATE size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) { return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); } -static size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +DRWAV_PRIVATE size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) { return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); } -static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) { return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; } @@ -48773,7 +50715,7 @@ DRWAV_API drwav_bool32 drwav_init_file(drwav* pWav, const char* filename, const { return drwav_init_file_ex(pWav, filename, NULL, NULL, 0, pAllocationCallbacks); } -static drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file__internal_FILE(drwav* pWav, FILE* pFile, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav_bool32 result; result = drwav_preinit(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); @@ -48808,7 +50750,7 @@ DRWAV_API drwav_bool32 drwav_init_file_ex_w(drwav* pWav, const wchar_t* filename } return drwav_init_file__internal_FILE(pWav, pFile, onChunk, pChunkUserData, flags, pAllocationCallbacks); } -static drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFile, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { drwav_bool32 result; result = drwav_preinit_write(pWav, pFormat, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile, pAllocationCallbacks); @@ -48823,7 +50765,7 @@ static drwav_bool32 drwav_init_file_write__internal_FILE(drwav* pWav, FILE* pFil } return DRWAV_TRUE; } -static drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_fopen(&pFile, filename, "wb") != DRWAV_SUCCESS) { @@ -48831,7 +50773,7 @@ static drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* fil } return drwav_init_file_write__internal_FILE(pWav, pFile, pFormat, totalSampleCount, isSequential, pAllocationCallbacks); } -static drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_file_write_w__internal(drwav* pWav, const wchar_t* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { FILE* pFile; if (drwav_wfopen(&pFile, filename, L"wb", pAllocationCallbacks) != DRWAV_SUCCESS) { @@ -48870,7 +50812,7 @@ DRWAV_API drwav_bool32 drwav_init_file_write_sequential_pcm_frames_w(drwav* pWav return drwav_init_file_write_sequential_w(pWav, filename, pFormat, totalPCMFrameCount*pFormat->channels, pAllocationCallbacks); } #endif -static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +DRWAV_PRIVATE size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) { drwav* pWav = (drwav*)pUserData; size_t bytesRemaining; @@ -48886,7 +50828,7 @@ static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t by } return bytesToRead; } -static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; DRWAV_ASSERT(pWav != NULL); @@ -48910,7 +50852,7 @@ static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_see } return DRWAV_TRUE; } -static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +DRWAV_PRIVATE size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) { drwav* pWav = (drwav*)pUserData; size_t bytesRemaining; @@ -48938,7 +50880,7 @@ static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_ *pWav->memoryStreamWrite.pDataSize = pWav->memoryStreamWrite.dataSize; return bytesToWrite; } -static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) { drwav* pWav = (drwav*)pUserData; DRWAV_ASSERT(pWav != NULL); @@ -48979,7 +50921,7 @@ DRWAV_API drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_ pWav->memoryStream.currentReadPos = 0; return drwav_init__internal(pWav, onChunk, pChunkUserData, flags); } -static drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) +DRWAV_PRIVATE drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, const drwav_allocation_callbacks* pAllocationCallbacks) { if (ppData == NULL || pDataSize == NULL) { return DRWAV_FALSE; @@ -49019,7 +50961,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) } if (pWav->onWrite != NULL) { drwav_uint32 paddingSize = 0; - if (pWav->container == drwav_container_riff) { + if (pWav->container == drwav_container_riff || pWav->container == drwav_container_rf64) { paddingSize = drwav__chunk_padding_size_riff(pWav->dataChunkDataSize); } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); @@ -49034,19 +50976,29 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) drwav_uint32 riffChunkSize = drwav__riff_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, riffChunkSize); } - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 4, drwav_seek_origin_start)) { drwav_uint32 dataChunkSize = drwav__data_chunk_size_riff(pWav->dataChunkDataSize); drwav__write_u32ne_to_le(pWav, dataChunkSize); } - } else { + } else if (pWav->container == drwav_container_w64) { if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { drwav_uint64 riffChunkSize = drwav__riff_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, riffChunkSize); } - if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) { + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos - 8, drwav_seek_origin_start)) { drwav_uint64 dataChunkSize = drwav__data_chunk_size_w64(pWav->dataChunkDataSize); drwav__write_u64ne_to_le(pWav, dataChunkSize); } + } else if (pWav->container == drwav_container_rf64) { + int ds64BodyPos = 12 + 8; + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 0, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav__riff_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, riffChunkSize); + } + if (pWav->onSeek(pWav->pUserData, ds64BodyPos + 8, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav__data_chunk_size_rf64(pWav->dataChunkDataSize); + drwav__write_u64ne_to_le(pWav, dataChunkSize); + } } } if (pWav->isSequentialWrite) { @@ -49105,6 +51057,7 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { drwav_uint32 bytesPerFrame; + drwav_uint64 bytesToRead; if (pWav == NULL || framesToRead == 0) { return 0; } @@ -49115,10 +51068,14 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames_le(drwav* pWav, drwav_uint64 frames if (bytesPerFrame == 0) { return 0; } - if (framesToRead * bytesPerFrame > DRWAV_SIZE_MAX) { - framesToRead = DRWAV_SIZE_MAX / bytesPerFrame; + bytesToRead = framesToRead * bytesPerFrame; + if (bytesToRead > DRWAV_SIZE_MAX) { + bytesToRead = (DRWAV_SIZE_MAX / bytesPerFrame) * bytesPerFrame; } - return drwav_read_raw(pWav, (size_t)(framesToRead * bytesPerFrame), pBufferOut) / bytesPerFrame; + if (bytesToRead == 0) { + return 0; + } + return drwav_read_raw(pWav, (size_t)bytesToRead, pBufferOut) / bytesPerFrame; } DRWAV_API drwav_uint64 drwav_read_pcm_frames_be(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) { @@ -49136,7 +51093,7 @@ DRWAV_API drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToR return drwav_read_pcm_frames_be(pWav, framesToRead, pBufferOut); } } -DRWAV_API drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) +DRWAV_PRIVATE drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) { if (pWav->onWrite != NULL) { return DRWAV_FALSE; @@ -49313,12 +51270,13 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesTo return drwav_write_pcm_frames_be(pWav, framesToWrite, pData); } } -static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead = 0; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); - while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + while (pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); if (pWav->msadpcm.cachedFrameCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { drwav_uint8 header[7]; @@ -49327,9 +51285,9 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 } pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); pWav->msadpcm.predictor[0] = header[0]; - pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 1); - pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 3); - pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 5); + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 1); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 3); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 5); pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[3] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.cachedFrameCount = 2; @@ -49341,12 +51299,12 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); pWav->msadpcm.predictor[0] = header[0]; pWav->msadpcm.predictor[1] = header[1]; - pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 2); - pWav->msadpcm.delta[1] = drwav__bytes_to_s16(header + 4); - pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 6); - pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav__bytes_to_s16(header + 8); - pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 10); - pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav__bytes_to_s16(header + 12); + pWav->msadpcm.delta[0] = drwav_bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav_bytes_to_s16(header + 4); + pWav->msadpcm.prevFrames[0][1] = (drwav_int32)drwav_bytes_to_s16(header + 6); + pWav->msadpcm.prevFrames[1][1] = (drwav_int32)drwav_bytes_to_s16(header + 8); + pWav->msadpcm.prevFrames[0][0] = (drwav_int32)drwav_bytes_to_s16(header + 10); + pWav->msadpcm.prevFrames[1][0] = (drwav_int32)drwav_bytes_to_s16(header + 12); pWav->msadpcm.cachedFrames[0] = pWav->msadpcm.prevFrames[0][0]; pWav->msadpcm.cachedFrames[1] = pWav->msadpcm.prevFrames[1][0]; pWav->msadpcm.cachedFrames[2] = pWav->msadpcm.prevFrames[0][1]; @@ -49368,7 +51326,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 pWav->msadpcm.cachedFrameCount -= 1; } if (framesToRead == 0) { - return totalFramesRead; + break; } if (pWav->msadpcm.cachedFrameCount == 0) { if (pWav->msadpcm.bytesRemainingInBlock == 0) { @@ -49443,7 +51401,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_uint32 iChannel; @@ -49464,7 +51422,8 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra }; DRWAV_ASSERT(pWav != NULL); DRWAV_ASSERT(framesToRead > 0); - while (framesToRead > 0 && pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + while (pWav->compressed.iCurrentPCMFrame < pWav->totalPCMFrameCount) { + DRWAV_ASSERT(framesToRead > 0); if (pWav->ima.cachedFrameCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { if (pWav->channels == 1) { drwav_uint8 header[4]; @@ -49477,7 +51436,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; } - pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); pWav->ima.stepIndex[0] = header[2]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[0]; pWav->ima.cachedFrameCount = 1; @@ -49492,9 +51451,9 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra pWav->ima.bytesRemainingInBlock = 0; return totalFramesRead; } - pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + pWav->ima.predictor[0] = drwav_bytes_to_s16(header + 0); pWav->ima.stepIndex[0] = header[2]; - pWav->ima.predictor[1] = drwav__bytes_to_s16(header + 4); + pWav->ima.predictor[1] = drwav_bytes_to_s16(header + 4); pWav->ima.stepIndex[1] = header[6]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 2] = pWav->ima.predictor[0]; pWav->ima.cachedFrames[drwav_countof(pWav->ima.cachedFrames) - 1] = pWav->ima.predictor[1]; @@ -49515,7 +51474,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra pWav->ima.cachedFrameCount -= 1; } if (framesToRead == 0) { - return totalFramesRead; + break; } if (pWav->ima.cachedFrameCount == 0) { if (pWav->ima.bytesRemainingInBlock == 0) { @@ -49607,7 +51566,7 @@ static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) { return (short)g_drwavMulawTable[sampleIn]; } -static void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { unsigned int i; if (bytesPerSample == 1) { @@ -49645,7 +51604,7 @@ static void drwav__pcm_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); } } -static void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); @@ -49658,11 +51617,11 @@ static void drwav__ieee_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t return; } } -static drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { - drwav_uint32 bytesPerFrame; drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; + drwav_uint32 bytesPerFrame; if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } @@ -49683,7 +51642,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 fra } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -49708,7 +51667,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 fr } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -49733,7 +51692,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 fr } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -49878,7 +51837,7 @@ DRWAV_API void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, siz pOut[i] = drwav__mulaw_to_s16(pIn[i]); } } -static void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) { unsigned int i; if (bytesPerSample == 1) { @@ -49914,7 +51873,7 @@ static void drwav__pcm_to_f32(float* pOut, const drwav_uint8* pIn, size_t sample *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); } } -static void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { unsigned int i; @@ -49930,7 +51889,7 @@ static void drwav__ieee_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampl return; } } -static drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -49951,7 +51910,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__pcm(drwav* pWav, drwav_uint64 fra } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; @@ -49967,7 +51926,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__msadpcm(drwav* pWav, drwav_uint64 } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; @@ -49983,7 +51942,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__ima(drwav* pWav, drwav_uint64 fra } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50008,7 +51967,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 fr } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50029,7 +51988,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__alaw(drwav* pWav, drwav_uint64 fr } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_f32__mulaw(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50181,7 +52140,7 @@ DRWAV_API void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sa *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; } } -static void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { unsigned int i; if (bytesPerSample == 1) { @@ -50219,7 +52178,7 @@ static void drwav__pcm_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); } } -static void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +DRWAV_PRIVATE void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t totalSampleCount, unsigned int bytesPerSample) { if (bytesPerSample == 4) { drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); @@ -50232,7 +52191,7 @@ static void drwav__ieee_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t return; } } -static drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50257,7 +52216,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 fra } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; @@ -50273,7 +52232,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__msadpcm(drwav* pWav, drwav_uint64 } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead = 0; drwav_int16 samples16[2048]; @@ -50289,7 +52248,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__ima(drwav* pWav, drwav_uint64 fra } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50310,7 +52269,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__ieee(drwav* pWav, drwav_uint64 fr } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50331,7 +52290,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__alaw(drwav* pWav, drwav_uint64 fr } return totalFramesRead; } -static drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s32__mulaw(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) { drwav_uint64 totalFramesRead; drwav_uint8 sampleData[4096]; @@ -50473,7 +52432,7 @@ DRWAV_API void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, siz *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; } } -static drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; drwav_int16* pSampleData; @@ -50507,7 +52466,7 @@ static drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, unsigned i } return pSampleData; } -static float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; float* pSampleData; @@ -50541,7 +52500,7 @@ static float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned int* ch } return pSampleData; } -static drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) +DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount) { drwav_uint64 sampleDataSize; drwav_int32* pSampleData; @@ -50792,35 +52751,47 @@ DRWAV_API void drwav_free(void* p, const drwav_allocation_callbacks* pAllocation } DRWAV_API drwav_uint16 drwav_bytes_to_u16(const drwav_uint8* data) { - return drwav__bytes_to_u16(data); + return (data[0] << 0) | (data[1] << 8); } DRWAV_API drwav_int16 drwav_bytes_to_s16(const drwav_uint8* data) { - return drwav__bytes_to_s16(data); + return (short)drwav_bytes_to_u16(data); } DRWAV_API drwav_uint32 drwav_bytes_to_u32(const drwav_uint8* data) { - return drwav__bytes_to_u32(data); + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } DRWAV_API drwav_int32 drwav_bytes_to_s32(const drwav_uint8* data) { - return drwav__bytes_to_s32(data); + return (drwav_int32)drwav_bytes_to_u32(data); } DRWAV_API drwav_uint64 drwav_bytes_to_u64(const drwav_uint8* data) { - return drwav__bytes_to_u64(data); + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); } DRWAV_API drwav_int64 drwav_bytes_to_s64(const drwav_uint8* data) { - return drwav__bytes_to_s64(data); + return (drwav_int64)drwav_bytes_to_u64(data); } DRWAV_API drwav_bool32 drwav_guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) { - return drwav__guid_equal(a, b); + int i; + for (i = 0; i < 16; i += 1) { + if (a[i] != b[i]) { + return DRWAV_FALSE; + } + } + return DRWAV_TRUE; } DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) { - return drwav__fourcc_equal(a, b); + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; } #endif /* dr_wav_c end */ @@ -50832,7 +52803,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) /* dr_flac_c begin */ #ifndef dr_flac_c #define dr_flac_c -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic push #if __GNUC__ >= 7 #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" @@ -50842,6 +52813,9 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #ifndef _BSD_SOURCE #define _BSD_SOURCE #endif + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif #ifndef __USE_BSD #define __USE_BSD #endif @@ -50857,6 +52831,8 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #else #define DRFLAC_INLINE inline __attribute__((always_inline)) #endif +#elif defined(__WATCOMC__) + #define DRFLAC_INLINE __inline #else #define DRFLAC_INLINE #endif @@ -50864,7 +52840,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #define DRFLAC_X64 #elif defined(__i386) || defined(_M_IX86) #define DRFLAC_X86 -#elif defined(__arm__) || defined(_M_ARM) +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64) #define DRFLAC_ARM #endif #if !defined(DR_FLAC_NO_SIMD) @@ -50876,7 +52852,7 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) #if _MSC_VER >= 1600 && !defined(DRFLAC_NO_SSE41) #define DRFLAC_SUPPORT_SSE41 #endif - #else + #elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) #if defined(__SSE2__) && !defined(DRFLAC_NO_SSE2) #define DRFLAC_SUPPORT_SSE2 #endif @@ -51030,6 +53006,27 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void) #if ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) #define DRFLAC_HAS_BYTESWAP16_INTRINSIC #endif +#elif defined(__WATCOMC__) && defined(__386__) + #define DRFLAC_HAS_BYTESWAP16_INTRINSIC + #define DRFLAC_HAS_BYTESWAP32_INTRINSIC + #define DRFLAC_HAS_BYTESWAP64_INTRINSIC + extern __inline drflac_uint16 _watcom_bswap16(drflac_uint16); + extern __inline drflac_uint32 _watcom_bswap32(drflac_uint32); + extern __inline drflac_uint64 _watcom_bswap64(drflac_uint64); +#pragma aux _watcom_bswap16 = \ + "xchg al, ah" \ + parm [ax] \ + modify [ax]; +#pragma aux _watcom_bswap32 = \ + "bswap eax" \ + parm [eax] \ + modify [eax]; +#pragma aux _watcom_bswap64 = \ + "bswap eax" \ + "bswap edx" \ + "xchg eax,edx" \ + parm [eax edx] \ + modify [eax edx]; #endif #ifndef DRFLAC_ASSERT #include @@ -51211,6 +53208,8 @@ static DRFLAC_INLINE drflac_uint16 drflac__swap_endian_uint16(drflac_uint16 n) return _byteswap_ushort(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap16(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap16(n); #else #error "This compiler does not support the byte swap intrinsic." #endif @@ -51238,6 +53237,8 @@ static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n) #else return __builtin_bswap32(n); #endif + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap32(n); #else #error "This compiler does not support the byte swap intrinsic." #endif @@ -51255,18 +53256,20 @@ static DRFLAC_INLINE drflac_uint64 drflac__swap_endian_uint64(drflac_uint64 n) return _byteswap_uint64(n); #elif defined(__GNUC__) || defined(__clang__) return __builtin_bswap64(n); + #elif defined(__WATCOMC__) && defined(__386__) + return _watcom_bswap64(n); #else #error "This compiler does not support the byte swap intrinsic." #endif #else - return ((n & (drflac_uint64)0xFF00000000000000) >> 56) | - ((n & (drflac_uint64)0x00FF000000000000) >> 40) | - ((n & (drflac_uint64)0x0000FF0000000000) >> 24) | - ((n & (drflac_uint64)0x000000FF00000000) >> 8) | - ((n & (drflac_uint64)0x00000000FF000000) << 8) | - ((n & (drflac_uint64)0x0000000000FF0000) << 24) | - ((n & (drflac_uint64)0x000000000000FF00) << 40) | - ((n & (drflac_uint64)0x00000000000000FF) << 56); + return ((n & ((drflac_uint64)0xFF000000 << 32)) >> 56) | + ((n & ((drflac_uint64)0x00FF0000 << 32)) >> 40) | + ((n & ((drflac_uint64)0x0000FF00 << 32)) >> 24) | + ((n & ((drflac_uint64)0x000000FF << 32)) >> 8) | + ((n & ((drflac_uint64)0xFF000000 )) << 8) | + ((n & ((drflac_uint64)0x00FF0000 )) << 24) | + ((n & ((drflac_uint64)0x0000FF00 )) << 40) | + ((n & ((drflac_uint64)0x000000FF )) << 56); #endif } static DRFLAC_INLINE drflac_uint16 drflac__be2host_16(drflac_uint16 n) @@ -51872,6 +53875,9 @@ static drflac_bool32 drflac__find_and_seek_to_next_sync_code(drflac_bs* bs) #if defined(_MSC_VER) && _MSC_VER >= 1400 && (defined(DRFLAC_X64) || defined(DRFLAC_X86)) && !defined(__clang__) #define DRFLAC_IMPLEMENT_CLZ_MSVC #endif +#if defined(__WATCOMC__) && defined(__386__) +#define DRFLAC_IMPLEMENT_CLZ_WATCOM +#endif static DRFLAC_INLINE drflac_uint32 drflac__clz_software(drflac_cache_t x) { drflac_uint32 n; @@ -51984,6 +53990,15 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_msvc(drflac_cache_t x) return sizeof(x)*8 - n - 1; } #endif +#ifdef DRFLAC_IMPLEMENT_CLZ_WATCOM +static __inline drflac_uint32 drflac__clz_watcom (drflac_uint32); +#pragma aux drflac__clz_watcom = \ + "bsr eax, eax" \ + "xor eax, 31" \ + parm [eax] nomemory \ + value [eax] \ + modify exact [eax] nomemory; +#endif static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) { #ifdef DRFLAC_IMPLEMENT_CLZ_LZCNT @@ -51994,6 +54009,8 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz(drflac_cache_t x) { #ifdef DRFLAC_IMPLEMENT_CLZ_MSVC return drflac__clz_msvc(x); +#elif defined(DRFLAC_IMPLEMENT_CLZ_WATCOM) + return (x == 0) ? sizeof(x)*8 : drflac__clz_watcom(x); #else return drflac__clz_software(x); #endif @@ -52310,7 +54327,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__reference(drfla { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); DRFLAC_ASSERT(pSamplesOut != NULL); for (i = 0; i < count; ++i) { drflac_uint32 zeroCounter = 0; @@ -52579,7 +54595,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar_zeroorde drflac_uint32 riceParamMask; drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); DRFLAC_ASSERT(pSamplesOut != NULL); (void)bitsPerSample; (void)order; @@ -52614,7 +54629,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__scalar(drflac_b const drflac_int32* pSamplesOutEnd; drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); DRFLAC_ASSERT(pSamplesOut != NULL); if (order == 0) { return drflac__decode_samples_with_residual__rice__scalar_zeroorder(bs, bitsPerSample, count, riceParam, order, shift, coefficients, pSamplesOut); @@ -53033,7 +55047,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41_64(drflac static drflac_bool32 drflac__decode_samples_with_residual__rice__sse41(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); DRFLAC_ASSERT(pSamplesOut != NULL); if (order > 0 && order <= 12) { if (bitsPerSample+shift > 32) { @@ -53384,7 +55397,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon_64(drflac_ static drflac_bool32 drflac__decode_samples_with_residual__rice__neon(drflac_bs* bs, drflac_uint32 bitsPerSample, drflac_uint32 count, drflac_uint8 riceParam, drflac_uint32 order, drflac_int32 shift, const drflac_int32* coefficients, drflac_int32* pSamplesOut) { DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); DRFLAC_ASSERT(pSamplesOut != NULL); if (order > 0 && order <= 12) { if (bitsPerSample+shift > 32) { @@ -53420,7 +55432,6 @@ static drflac_bool32 drflac__read_and_seek_residual__rice(drflac_bs* bs, drflac_ { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); for (i = 0; i < count; ++i) { if (!drflac__seek_rice_parts(bs, riceParam)) { return DRFLAC_FALSE; @@ -53432,7 +55443,6 @@ static drflac_bool32 drflac__decode_samples_with_residual__unencoded(drflac_bs* { drflac_uint32 i; DRFLAC_ASSERT(bs != NULL); - DRFLAC_ASSERT(count > 0); DRFLAC_ASSERT(unencodedBitsPerSample <= 31); DRFLAC_ASSERT(pSamplesOut != NULL); for (i = 0; i < count; ++i) { @@ -53473,7 +55483,7 @@ static drflac_bool32 drflac__decode_samples_with_residual(drflac_bs* bs, drflac_ if (partitionOrder > 8) { return DRFLAC_FALSE; } - if ((blockSize / (1 << partitionOrder)) <= order) { + if ((blockSize / (1 << partitionOrder)) < order) { return DRFLAC_FALSE; } samplesInPartition = (blockSize / (1 << partitionOrder)) - order; @@ -53756,7 +55766,8 @@ static drflac_bool32 drflac__read_next_flac_frame_header(drflac_bs* bs, drflac_u DRFLAC_ASSERT(blockSize > 0); if (blockSize == 1) { header->blockSizeInPCMFrames = 192; - } else if (blockSize >= 2 && blockSize <= 5) { + } else if (blockSize <= 5) { + DRFLAC_ASSERT(blockSize >= 2); header->blockSizeInPCMFrames = 576 * (1 << (blockSize - 2)); } else if (blockSize == 6) { if (!drflac__read_uint16(bs, 8, &header->blockSizeInPCMFrames)) { @@ -54207,6 +56218,7 @@ static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFla DRFLAC_ASSERT(targetByte <= rangeHi); *pLastSuccessfulSeekOffset = pFlac->firstFLACFramePosInBytes; for (;;) { + drflac_uint64 lastTargetByte = targetByte; if (!drflac__seek_to_byte(&pFlac->bs, targetByte)) { if (targetByte == 0) { drflac__seek_to_first_frame(pFlac); @@ -54232,6 +56244,9 @@ static drflac_bool32 drflac__seek_to_approximate_flac_frame_to_byte(drflac* pFla } #endif } + if(targetByte == lastTargetByte) { + return DRFLAC_FALSE; + } } drflac__get_pcm_frame_range_of_current_flac_frame(pFlac, &pFlac->currentPCMFrame, NULL); DRFLAC_ASSERT(targetByte <= rangeHi); @@ -56171,7 +58186,7 @@ static drflac_result drflac_result_from_errno(int e) } static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif if (ppFile != NULL) { @@ -56180,7 +58195,7 @@ static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const ch if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRFLAC_INVALID_ARGS; } -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drflac_result_from_errno(err); @@ -56206,7 +58221,7 @@ static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const ch return DRFLAC_SUCCESS; } #if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRFLAC_HAS_WFOPEN #endif #endif @@ -58613,6 +60628,7 @@ DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 p return drflac__seek_to_first_frame(pFlac); } else { drflac_bool32 wasSuccessful = DRFLAC_FALSE; + drflac_uint64 originalPCMFrame = pFlac->currentPCMFrame; if (pcmFrameIndex > pFlac->totalPCMFrameCount) { pcmFrameIndex = pFlac->totalPCMFrameCount; } @@ -58653,7 +60669,13 @@ DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 p wasSuccessful = drflac__seek_to_pcm_frame__brute_force(pFlac, pcmFrameIndex); } } - pFlac->currentPCMFrame = pcmFrameIndex; + if (wasSuccessful) { + pFlac->currentPCMFrame = pcmFrameIndex; + } else { + if (drflac_seek_to_pcm_frame(pFlac, originalPCMFrame) == DRFLAC_FALSE) { + drflac_seek_to_pcm_frame(pFlac, 0); + } + } return wasSuccessful; } } @@ -58970,7 +60992,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat } return DRFLAC_TRUE; } -#if defined(__GNUC__) +#if defined(__clang__) || (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))) #pragma GCC diagnostic pop #endif #endif @@ -59040,7 +61062,7 @@ DRMP3_API const char* drmp3_version_string(void) #define DRMP3_MIN(a, b) ((a) > (b) ? (b) : (a)) #define DRMP3_MAX(a, b) ((a) < (b) ? (b) : (a)) #if !defined(DR_MP3_NO_SIMD) -#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(_M_ARM64) || defined(__x86_64__) || defined(__aarch64__)) +#if !defined(DR_MP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || defined(__aarch64__) || defined(_M_ARM64)) #define DR_MP3_ONLY_SIMD #endif #if ((defined(_MSC_VER) && _MSC_VER >= 1400) && (defined(_M_IX86) || defined(_M_X64))) || ((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__)) @@ -59113,7 +61135,7 @@ end: return g_have_simd - 1; #endif } -#elif defined(__ARM_NEON) || defined(__aarch64__) +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64) #include #define DRMP3_HAVE_SSE 0 #define DRMP3_HAVE_SIMD 1 @@ -59142,7 +61164,7 @@ static int drmp3_have_simd(void) #else #define DRMP3_HAVE_SIMD 0 #endif -#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) +#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && !defined(_M_ARM64) #define DRMP3_HAVE_ARMV6 1 static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_arm(int32_t a) { @@ -59150,6 +61172,8 @@ static __inline__ __attribute__((always_inline)) drmp3_int32 drmp3_clip_int16_ar __asm__ ("ssat %0, #16, %1" : "=r"(x) : "r"(a)); return x; } +#else +#define DRMP3_HAVE_ARMV6 0 #endif typedef struct { @@ -60904,6 +62928,8 @@ static drmp3_uint32 drmp3_decode_next_frame_ex__callbacks(drmp3* pMP3, drmp3d_sa pMP3->atEnd = DRMP3_TRUE; return 0; } + DRMP3_ASSERT(pMP3->pData != NULL); + DRMP3_ASSERT(pMP3->dataCapacity > 0); pcmFramesRead = drmp3dec_decode_frame(&pMP3->decoder, pMP3->pData + pMP3->dataConsumed, (int)pMP3->dataSize, pPCMFrames, &info); if (info.frame_bytes > 0) { pMP3->dataConsumed += (size_t)info.frame_bytes; @@ -61001,7 +63027,7 @@ static drmp3_bool32 drmp3_init_internal(drmp3* pMP3, drmp3_read_proc onRead, drm return DRMP3_FALSE; } if (!drmp3_decode_next_frame(pMP3)) { - drmp3_uninit(pMP3); + drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); return DRMP3_FALSE; } pMP3->channels = pMP3->mp3FrameChannels; @@ -61477,7 +63503,7 @@ static drmp3_result drmp3_result_from_errno(int e) } static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode) { -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 errno_t err; #endif if (ppFile != NULL) { @@ -61486,7 +63512,7 @@ static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char if (pFilePath == NULL || pOpenMode == NULL || ppFile == NULL) { return DRMP3_INVALID_ARGS; } -#if _MSC_VER && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 err = fopen_s(ppFile, pFilePath, pOpenMode); if (err != 0) { return drmp3_result_from_errno(err); @@ -61512,7 +63538,7 @@ static drmp3_result drmp3_fopen(FILE** ppFile, const char* pFilePath, const char return DRMP3_SUCCESS; } #if defined(_WIN32) - #if defined(_MSC_VER) || defined(__MINGW64__) || !defined(__STRICT_ANSI__) + #if defined(_MSC_VER) || defined(__MINGW64__) || (!defined(__STRICT_ANSI__) && !defined(_NO_EXT_KEYS)) #define DRMP3_HAS_WFOPEN #endif #endif @@ -61588,19 +63614,31 @@ static drmp3_bool32 drmp3__on_seek_stdio(void* pUserData, int offset, drmp3_seek } DRMP3_API drmp3_bool32 drmp3_init_file(drmp3* pMP3, const char* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) { + drmp3_bool32 result; FILE* pFile; if (drmp3_fopen(&pFile, pFilePath, "rb") != DRMP3_SUCCESS) { return DRMP3_FALSE; } - return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + return DRMP3_TRUE; } DRMP3_API drmp3_bool32 drmp3_init_file_w(drmp3* pMP3, const wchar_t* pFilePath, const drmp3_allocation_callbacks* pAllocationCallbacks) { + drmp3_bool32 result; FILE* pFile; if (drmp3_wfopen(&pFile, pFilePath, L"rb", pAllocationCallbacks) != DRMP3_SUCCESS) { return DRMP3_FALSE; } - return drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + result = drmp3_init(pMP3, drmp3__on_read_stdio, drmp3__on_seek_stdio, (void*)pFile, pAllocationCallbacks); + if (result != DRMP3_TRUE) { + fclose(pFile); + return result; + } + return DRMP3_TRUE; } #endif DRMP3_API void drmp3_uninit(drmp3* pMP3) @@ -61610,7 +63648,11 @@ DRMP3_API void drmp3_uninit(drmp3* pMP3) } #ifndef DR_MP3_NO_STDIO if (pMP3->onRead == drmp3__on_read_stdio) { - fclose((FILE*)pMP3->pUserData); + FILE* pFile = (FILE*)pMP3->pUserData; + if (pFile != NULL) { + fclose(pFile); + pMP3->pUserData = NULL; + } } #endif drmp3__free_from_callbacks(pMP3->pData, &pMP3->allocationCallbacks); @@ -62529,6 +64571,131 @@ The following miscellaneous changes have also been made. /* REVISION HISTORY ================ +v0.10.33 - 2021-04-04 + - Core Audio: Fix a memory leak. + - Core Audio: Fix a bug where the performance profile is not being used by playback devices. + - JACK: Fix loading of 64-bit JACK on Windows. + - Fix a calculation error and add a safety check to the following APIs to prevent a division by zero: + - ma_calculate_buffer_size_in_milliseconds_from_frames() + - ma_calculate_buffer_size_in_frames_from_milliseconds() + - Fix compilation errors relating to c89atomic. + - Update FLAC decoder. + +v0.10.32 - 2021-02-23 + - WASAPI: Fix a deadlock in exclusive mode. + - WASAPI: No longer return an error from ma_context_get_device_info() when an exclusive mode format + cannot be retrieved. + - WASAPI: Attempt to fix some bugs with device uninitialization. + - PulseAudio: Yet another refactor, this time to remove the dependency on `pa_threaded_mainloop`. + - Web Audio: Fix a bug on Chrome and any other browser using the same engine. + - Web Audio: Automatically start the device on some user input if the device has been started. This + is to work around Google's policy of not starting audio if no user input has yet been performed. + - Fix a bug where thread handles are not being freed. + - Fix some static analysis warnings in FLAC, WAV and MP3 decoders. + - Fix a warning due to referencing _MSC_VER when it is undefined. + - Update to latest version of c89atomic. + - Internal refactoring to migrate over to the new backend callback system for the following backends: + - PulseAudio + - ALSA + - Core Audio + - AAudio + - OpenSL|ES + - OSS + - audio(4) + - sndio + +v0.10.31 - 2021-01-17 + - Make some functions const correct. + - Update ma_data_source_read_pcm_frames() to initialize pFramesRead to 0 for safety. + - Add the MA_ATOMIC annotation for use with variables that should be used atomically and remove unnecessary volatile qualifiers. + - Add support for enabling only specific backends at compile time. This is the reverse of the pre-existing system. With the new + system, all backends are first disabled with `MA_ENABLE_ONLY_SPECIFIC_BACKENDS`, which is then followed with `MA_ENABLE_*`. The + old system where you disable backends with `MA_NO_*` still exists and is still the default. + +v0.10.30 - 2021-01-10 + - Fix a crash in ma_audio_buffer_read_pcm_frames(). + - Update spinlock APIs to take a volatile parameter as input. + - Silence some unused parameter warnings. + - Fix a warning on GCC when compiling as C++. + +v0.10.29 - 2020-12-26 + - Fix some subtle multi-threading bugs on non-x86 platforms. + - Fix a bug resulting in superfluous memory allocations when enumerating devices. + - Core Audio: Fix a compilation error when compiling for iOS. + +v0.10.28 - 2020-12-16 + - Fix a crash when initializing a POSIX thread. + - OpenSL|ES: Respect the MA_NO_RUNTIME_LINKING option. + +v0.10.27 - 2020-12-04 + - Add support for dynamically configuring some properties of `ma_noise` objects post-initialization. + - Add support for configuring the channel mixing mode in the device config. + - Fix a bug with simple channel mixing mode (drop or silence excess channels). + - Fix some bugs with trying to access uninitialized variables. + - Fix some errors with stopping devices for synchronous backends where the backend's stop callback would get fired twice. + - Fix a bug in the decoder due to using an uninitialized variable. + - Fix some data race errors. + +v0.10.26 - 2020-11-24 + - WASAPI: Fix a bug where the exclusive mode format may not be retrieved correctly due to accessing freed memory. + - Fix a bug with ma_waveform where glitching occurs after changing frequency. + - Fix compilation with OpenWatcom. + - Fix compilation with TCC. + - Fix compilation with Digital Mars. + - Fix compilation warnings. + - Remove bitfields from public structures to aid in binding maintenance. + +v0.10.25 - 2020-11-15 + - PulseAudio: Fix a bug where the stop callback isn't fired. + - WebAudio: Fix an error that occurs when Emscripten increases the size of it's heap. + - Custom Backends: Change the onContextInit and onDeviceInit callbacks to take a parameter which is a pointer to the config that was + passed into ma_context_init() and ma_device_init(). This replaces the deviceType parameter of onDeviceInit. + - Fix compilation warnings on older versions of GCC. + +v0.10.24 - 2020-11-10 + - Fix a bug where initialization of a backend can fail due to some bad state being set from a prior failed attempt at initializing a + lower priority backend. + +v0.10.23 - 2020-11-09 + - AAudio: Add support for configuring a playback stream's usage. + - Fix a compilation error when all built-in asynchronous backends are disabled at compile time. + - Fix compilation errors when compiling as C++. + +v0.10.22 - 2020-11-08 + - Add support for custom backends. + - Add support for detecting default devices during device enumeration and with `ma_context_get_device_info()`. + - Refactor to the PulseAudio backend. This simplifies the implementation and fixes a capture bug. + - ALSA: Fix a bug in `ma_context_get_device_info()` where the PCM handle is left open in the event of an error. + - Core Audio: Further improvements to sample rate selection. + - Core Audio: Fix some bugs with capture mode. + - OpenSL: Add support for configuring stream types and recording presets. + - AAudio: Add support for configuring content types and input presets. + - Fix bugs in `ma_decoder_init_file*()` where the file handle is not closed after a decoding error. + - Fix some compilation warnings on GCC and Clang relating to the Speex resampler. + - Fix a compilation error for the Linux build when the ALSA and JACK backends are both disabled. + - Fix a compilation error for the BSD build. + - Fix some compilation errors on older versions of GCC. + - Add documentation for `MA_NO_RUNTIME_LINKING`. + +v0.10.21 - 2020-10-30 + - Add ma_is_backend_enabled() and ma_get_enabled_backends() for retrieving enabled backends at run-time. + - WASAPI: Fix a copy and paste bug relating to loopback mode. + - Core Audio: Fix a bug when using multiple contexts. + - Core Audio: Fix a compilation warning. + - Core Audio: Improvements to sample rate selection. + - Core Audio: Improvements to format/channels/rate selection when requesting defaults. + - Core Audio: Add notes regarding the Apple notarization process. + - Fix some bugs due to null pointer dereferences. + +v0.10.20 - 2020-10-06 + - Fix build errors with UWP. + - Minor documentation updates. + +v0.10.19 - 2020-09-22 + - WASAPI: Return an error when exclusive mode is requested, but the native format is not supported by miniaudio. + - Fix a bug where ma_decoder_seek_to_pcm_frames() never returns MA_SUCCESS even though it was successful. + - Store the sample rate in the `ma_lpf` and `ma_hpf` structures. + v0.10.18 - 2020-08-30 - Fix build errors with VC6. - Fix a bug in channel converter for s32 format. diff --git a/raylib/external/msf_gif.h b/raylib/external/msf_gif.h new file mode 100644 index 0000000..348b1b1 --- /dev/null +++ b/raylib/external/msf_gif.h @@ -0,0 +1,608 @@ +/* +HOW TO USE: + + In exactly one translation unit (.c or .cpp file), #define MSF_GIF_IMPL before including the header, like so: + + #define MSF_GIF_IMPL + #include "msf_gif.h" + + Everywhere else, just include the header like normal. + + +USAGE EXAMPLE: + + int width = 480, height = 320, centisecondsPerFrame = 5, bitDepth = 16; + MsfGifState gifState = {}; + msf_gif_begin(&gifState, width, height); + msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 1 + msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 2 + msf_gif_frame(&gifState, ..., centisecondsPerFrame, bitDepth, width * 4); //frame 3, etc... + MsfGifResult result = msf_gif_end(&gifState); + FILE * fp = fopen("MyGif.gif", "wb"); + fwrite(result.data, result.dataSize, 1, fp); + fclose(fp); + msf_gif_free(result); + +Detailed function documentation can be found in the header section below. + + +REPLACING MALLOC: + + This library uses malloc+realloc+free internally for memory allocation. + To facilitate integration with custom memory allocators, these calls go through macros, which can be redefined. + The expected function signature equivalents of the macros are as follows: + + void * MSF_GIF_MALLOC(void * context, size_t newSize) + void * MSF_GIF_REALLOC(void * context, void * oldMemory, size_t oldSize, size_t newSize) + void MSF_GIF_FREE(void * context, void * oldMemory, size_t oldSize) + + If your allocator needs a context pointer, you can set the `customAllocatorContext` field of the MsfGifState struct + before calling msf_gif_begin(), and it will be passed to all subsequent allocator macro calls. + +See end of file for license information. +*/ + +//version 2.1 + +#ifndef MSF_GIF_H +#define MSF_GIF_H + +#include +#include + +typedef struct { + void * data; + size_t dataSize; + + size_t allocSize; //internal use + void * contextPointer; //internal use +} MsfGifResult; + +typedef struct { //internal use + uint32_t * pixels; + int depth, count, rbits, gbits, bbits; +} MsfCookedFrame; + +typedef struct { + MsfCookedFrame previousFrame; + uint8_t * listHead; + uint8_t * listTail; + int width, height; + void * customAllocatorContext; +} MsfGifState; + +#ifdef __cplusplus +extern "C" { +#endif //__cplusplus + +/** + * @param width Image width in pixels. + * @param height Image height in pixels. + * @return Non-zero on success, 0 on error. + */ +int msf_gif_begin(MsfGifState * handle, int width, int height); + +/** + * @param pixelData Pointer to raw framebuffer data. Rows must be contiguous in memory, in RGBA8 format. + * Note: This function does NOT free `pixelData`. You must free it yourself afterwards. + * @param centiSecondsPerFrame How many hundredths of a second this frame should be displayed for. + * Note: This being specified in centiseconds is a limitation of the GIF format. + * @param maxBitDepth Limits how many bits per pixel can be used when quantizing the gif. + * The actual bit depth chosen for a given frame will be less than or equal to + * the supplied maximum, depending on the variety of colors used in the frame. + * `maxBitDepth` will be clamped between 1 and 16. The recommended default is 16. + * Lowering this value can result in faster exports and smaller gifs, + * but the quality may suffer. + * Please experiment with this value to find what works best for your application. + * @param pitchInBytes The number of bytes from the beginning of one row of pixels to the beginning of the next. + * If you want to flip the image, just pass in a negative pitch. + * @return Non-zero on success, 0 on error. + */ +int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes); + +/** + * @return A block of memory containing the gif file data, or NULL on error. + * You are responsible for freeing this via `msf_gif_free()`. + */ +MsfGifResult msf_gif_end(MsfGifState * handle); + +/** + * @param result The MsfGifResult struct, verbatim as it was returned from `msf_gif_end()`. + */ +void msf_gif_free(MsfGifResult result); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif //MSF_GIF_H + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// IMPLEMENTATION /// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef MSF_GIF_IMPL +#ifndef MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT +#define MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT + +#ifndef MSF_GIF_BUFFER_INIT_SIZE +#define MSF_GIF_BUFFER_INIT_SIZE 1024 * 1024 * 4 //4MB by default, you can increase this if you want to realloc less +#endif + +//ensure the library user has either defined all of malloc/realloc/free, or none +#if defined(MSF_GIF_MALLOC) && defined(MSF_GIF_REALLOC) && defined(MSF_GIF_FREE) //ok +#elif !defined(MSF_GIF_MALLOC) && !defined(MSF_GIF_REALLOC) && !defined(MSF_GIF_FREE) //ok +#else +#error "You must either define all of MSF_GIF_MALLOC, MSF_GIF_REALLOC, and MSF_GIF_FREE, or define none of them" +#endif + +//provide default allocator definitions that redirect to the standard global allocator +#if !defined(MSF_GIF_MALLOC) +#include //malloc, etc. +#define MSF_GIF_MALLOC(contextPointer, newSize) malloc(newSize) +#define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) realloc(oldMemory, newSize) +#define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) free(oldMemory) +#endif + +//instrumentation for capturing profiling traces (useless for the library user, but useful for the library author) +#ifdef MSF_GIF_ENABLE_TRACING +#define MsfTimeFunc TimeFunc +#define MsfTimeLoop TimeLoop +#define msf_init_profiling_thread init_profiling_thread +#else +#define MsfTimeFunc +#define MsfTimeLoop(name) +#define msf_init_profiling_thread() +#endif //MSF_GIF_ENABLE_TRACING + +#include //memcpy + +//TODO: use compiler-specific notation to force-inline functions currently marked inline +#if defined(__GNUC__) //gcc, clang +static inline int msf_bit_log(int i) { return 32 - __builtin_clz(i); } +#elif defined(_MSC_VER) //msvc +#include +static inline int msf_bit_log(int i) { unsigned long idx; _BitScanReverse(&idx, i); return idx + 1; } +#else //fallback implementation for other compilers +//from https://stackoverflow.com/a/31718095/3064745 - thanks! +static inline int msf_bit_log(int i) { + static const int MultiplyDeBruijnBitPosition[32] = { + 0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, + 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31, + }; + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return MultiplyDeBruijnBitPosition[(uint32_t)(i * 0x07C4ACDDU) >> 27] + 1; +} +#endif +static inline int msf_imin(int a, int b) { return a < b? a : b; } +static inline int msf_imax(int a, int b) { return b < a? a : b; } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// Frame Cooking /// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if (defined (__SSE2__) || defined (_M_X64) || _M_IX86_FP == 2) && !defined(MSF_GIF_NO_SSE2) +#include +#endif + +static MsfCookedFrame msf_cook_frame(void * allocContext, uint8_t * raw, uint8_t * used, + int width, int height, int pitch, int depth) +{ MsfTimeFunc + //bit depth for each channel + const static int rdepths[17] = { 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5 }; + const static int gdepths[17] = { 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6 }; + const static int bdepths[17] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5 }; + + const static int ditherKernel[16] = { + 0 << 12, 8 << 12, 2 << 12, 10 << 12, + 12 << 12, 4 << 12, 14 << 12, 6 << 12, + 3 << 12, 11 << 12, 1 << 12, 9 << 12, + 15 << 12, 7 << 12, 13 << 12, 5 << 12, + }; + + uint32_t * cooked = (uint32_t *) MSF_GIF_MALLOC(allocContext, width * height * sizeof(uint32_t)); + if (!cooked) { MsfCookedFrame blank = {0}; return blank; } + + int count = 0; + MsfTimeLoop("do") do { + int rbits = rdepths[depth], gbits = gdepths[depth], bbits = bdepths[depth]; + int paletteSize = 1 << (rbits + gbits + bbits); + memset(used, 0, paletteSize * sizeof(uint8_t)); + + //TODO: document what this math does and why it's correct + int rdiff = (1 << (8 - rbits)) - 1; + int gdiff = (1 << (8 - gbits)) - 1; + int bdiff = (1 << (8 - bbits)) - 1; + short rmul = (short) ((255.0f - rdiff) / 255.0f * 257); + short gmul = (short) ((255.0f - gdiff) / 255.0f * 257); + short bmul = (short) ((255.0f - bdiff) / 255.0f * 257); + + int gmask = ((1 << gbits) - 1) << rbits; + int bmask = ((1 << bbits) - 1) << rbits << gbits; + + MsfTimeLoop("cook") for (int y = 0; y < height; ++y) { + int x = 0; + + #if (defined (__SSE2__) || defined (_M_X64) || _M_IX86_FP == 2) && !defined(MSF_GIF_NO_SSE2) + __m128i k = _mm_loadu_si128((__m128i *) &ditherKernel[(y & 3) * 4]); + __m128i k2 = _mm_or_si128(_mm_srli_epi32(k, rbits), _mm_slli_epi32(_mm_srli_epi32(k, bbits), 16)); + // MsfTimeLoop("SIMD") + for (; x < width - 3; x += 4) { + uint8_t * pixels = &raw[y * pitch + x * 4]; + __m128i p = _mm_loadu_si128((__m128i *) pixels); + + __m128i rb = _mm_and_si128(p, _mm_set1_epi32(0x00FF00FF)); + __m128i rb1 = _mm_mullo_epi16(rb, _mm_set_epi16(bmul, rmul, bmul, rmul, bmul, rmul, bmul, rmul)); + __m128i rb2 = _mm_adds_epu16(rb1, k2); + __m128i r3 = _mm_srli_epi32(_mm_and_si128(rb2, _mm_set1_epi32(0x0000FFFF)), 16 - rbits); + __m128i b3 = _mm_and_si128(_mm_srli_epi32(rb2, 32 - rbits - gbits - bbits), _mm_set1_epi32(bmask)); + + __m128i g = _mm_and_si128(_mm_srli_epi32(p, 8), _mm_set1_epi32(0x000000FF)); + __m128i g1 = _mm_mullo_epi16(g, _mm_set1_epi32(gmul)); + __m128i g2 = _mm_adds_epu16(g1, _mm_srli_epi32(k, gbits)); + __m128i g3 = _mm_and_si128(_mm_srli_epi32(g2, 16 - rbits - gbits), _mm_set1_epi32(gmask)); + + //TODO: does storing this as a __m128i then reading it back as a uint32_t violate strict aliasing? + uint32_t * c = &cooked[y * width + x]; + __m128i out = _mm_or_si128(_mm_or_si128(r3, g3), b3); + _mm_storeu_si128((__m128i *) c, out); + } + #endif + + //scalar cleanup loop + // MsfTimeLoop("scalar") + for (; x < width; ++x) { + uint8_t * p = &raw[y * pitch + x * 4]; + int dx = x & 3, dy = y & 3; + int k = ditherKernel[dy * 4 + dx]; + cooked[y * width + x] = + (msf_imin(65535, p[2] * bmul + (k >> bbits)) >> (16 - rbits - gbits - bbits) & bmask) | + (msf_imin(65535, p[1] * gmul + (k >> gbits)) >> (16 - rbits - gbits ) & gmask) | + msf_imin(65535, p[0] * rmul + (k >> rbits)) >> (16 - rbits ); + } + } + + count = 0; + MsfTimeLoop("mark and count") for (int i = 0; i < width * height; ++i) { + used[cooked[i]] = 1; + } + + //count used colors + MsfTimeLoop("count") for (int j = 0; j < paletteSize; ++j) { + count += used[j]; + } + } while (count >= 256 && --depth); + + MsfCookedFrame ret = { cooked, depth, count, rdepths[depth], gdepths[depth], bdepths[depth] }; + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// Frame Compression /// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + uint8_t * next; + size_t size; +} MsfBufferHeader; + +static inline int msf_put_code(uint8_t * * writeHead, uint32_t * blockBits, int len, uint32_t code) { + //insert new code into block buffer + int idx = *blockBits / 8; + int bit = *blockBits % 8; + (*writeHead)[idx + 0] |= code << bit ; + (*writeHead)[idx + 1] |= code >> ( 8 - bit); + (*writeHead)[idx + 2] |= code >> (16 - bit); + *blockBits += len; + + //prep the next block buffer if the current one is full + if (*blockBits >= 256 * 8) { + *blockBits -= 255 * 8; + (*writeHead) += 256; + (*writeHead)[2] = (*writeHead)[1]; + (*writeHead)[1] = (*writeHead)[0]; + (*writeHead)[0] = 255; + memset((*writeHead) + 4, 0, 256); + } + + return 1; +} + +typedef struct { + int16_t * data; + int len; + int stride; +} MsfStridedList; + +static inline void msf_lzw_reset(MsfStridedList * lzw, int tableSize, int stride) { MsfTimeFunc + memset(lzw->data, 0xFF, 4096 * stride * sizeof(int16_t)); + lzw->len = tableSize + 2; + lzw->stride = stride; +} + +static uint8_t * msf_compress_frame(void * allocContext, int width, int height, int centiSeconds, + MsfCookedFrame frame, MsfCookedFrame previous, uint8_t * used) +{ MsfTimeFunc + //NOTE: we reserve enough memory for theoretical the worst case upfront because it's a reasonable amount, + // and prevents us from ever having to check size or realloc during compression + int maxBufSize = sizeof(MsfBufferHeader) + 32 + 256 * 3 + width * height * 3 / 2; //headers + color table + data + uint8_t * allocation = (uint8_t *) MSF_GIF_MALLOC(allocContext, maxBufSize); + if (!allocation) { return NULL; } + uint8_t * writeBase = allocation + sizeof(MsfBufferHeader); + uint8_t * writeHead = writeBase; + int lzwAllocSize = 4096 * (frame.count + 1) * sizeof(int16_t); + MsfStridedList lzw = { (int16_t *) MSF_GIF_MALLOC(allocContext, lzwAllocSize) }; + if (!lzw.data) { MSF_GIF_FREE(allocContext, allocation, maxBufSize); return NULL; } + + //allocate tlb + int totalBits = frame.rbits + frame.gbits + frame.bbits; + int tlbSize = 1 << totalBits; + uint8_t tlb[1 << 16]; //only 64k, so stack allocating is fine + + //generate palette + typedef struct { uint8_t r, g, b; } Color3; + Color3 table[256] = { {0} }; + int tableIdx = 1; //we start counting at 1 because 0 is the transparent color + MsfTimeLoop("table") for (int i = 0; i < tlbSize; ++i) { + if (used[i]) { + tlb[i] = tableIdx; + int rmask = (1 << frame.rbits) - 1; + int gmask = (1 << frame.gbits) - 1; + //isolate components + int r = i & rmask; + int g = i >> frame.rbits & gmask; + int b = i >> (frame.rbits + frame.gbits); + //shift into highest bits + r <<= 8 - frame.rbits; + g <<= 8 - frame.gbits; + b <<= 8 - frame.bbits; + table[tableIdx].r = r | r >> frame.rbits | r >> (frame.rbits * 2) | r >> (frame.rbits * 3); + table[tableIdx].g = g | g >> frame.gbits | g >> (frame.gbits * 2) | g >> (frame.gbits * 3); + table[tableIdx].b = b | b >> frame.bbits | b >> (frame.bbits * 2) | b >> (frame.bbits * 3); + ++tableIdx; + } + } + + //SPEC: "Because of some algorithmic constraints however, black & white images which have one color bit + // must be indicated as having a code size of 2." + int tableBits = msf_imax(2, msf_bit_log(tableIdx - 1)); + int tableSize = 1 << tableBits; + //NOTE: we don't just compare `depth` field here because it will be wrong for the first frame and we will segfault + int hasSamePal = frame.rbits == previous.rbits && frame.gbits == previous.gbits && frame.bbits == previous.bbits; + + //NOTE: because __attribute__((__packed__)) is annoyingly compiler-specific, we do this unreadable weirdness + char headerBytes[19] = "\x21\xF9\x04\x05\0\0\0\0" "\x2C\0\0\0\0\0\0\0\0\x80"; + memcpy(&headerBytes[4], ¢iSeconds, 2); + memcpy(&headerBytes[13], &width, 2); + memcpy(&headerBytes[15], &height, 2); + headerBytes[17] |= tableBits - 1; + memcpy(writeHead, headerBytes, 18); + writeHead += 18; + + //local color table + memcpy(writeHead, table, tableSize * sizeof(Color3)); + writeHead += tableSize * sizeof(Color3); + *writeHead++ = tableBits; + + //prep block + memset(writeHead, 0, 260); + writeHead[0] = 255; + uint32_t blockBits = 8; //relative to block.head + + //SPEC: "Encoders should output a Clear code as the first code of each image data stream." + msf_lzw_reset(&lzw, tableSize, tableIdx); + msf_put_code(&writeHead, &blockBits, msf_bit_log(lzw.len - 1), tableSize); + + int lastCode = hasSamePal && frame.pixels[0] == previous.pixels[0]? 0 : tlb[frame.pixels[0]]; + MsfTimeLoop("compress") for (int i = 1; i < width * height; ++i) { + //PERF: branching vs. branchless version of this line is observed to have no discernable impact on speed + int color = hasSamePal && frame.pixels[i] == previous.pixels[i]? 0 : tlb[frame.pixels[i]]; + //PERF: branchless version must use && otherwise it will segfault on frame 1, but it's well-predicted so OK + // int color = (!(hasSamePal && frame.pixels[i] == previous.pixels[i])) * tlb[frame.pixels[i]]; + int code = (&lzw.data[lastCode * lzw.stride])[color]; + if (code < 0) { + //write to code stream + int codeBits = msf_bit_log(lzw.len - 1); + msf_put_code(&writeHead, &blockBits, codeBits, lastCode); + + if (lzw.len > 4095) { + //reset buffer code table + msf_put_code(&writeHead, &blockBits, codeBits, tableSize); + msf_lzw_reset(&lzw, tableSize, tableIdx); + } else { + (&lzw.data[lastCode * lzw.stride])[color] = lzw.len; + ++lzw.len; + } + + lastCode = color; + } else { + lastCode = code; + } + } + + MSF_GIF_FREE(allocContext, lzw.data, lzwAllocSize); + MSF_GIF_FREE(allocContext, previous.pixels, width * height * sizeof(uint32_t)); + + //write code for leftover index buffer contents, then the end code + msf_put_code(&writeHead, &blockBits, msf_imin(12, msf_bit_log(lzw.len - 1)), lastCode); + msf_put_code(&writeHead, &blockBits, msf_imin(12, msf_bit_log(lzw.len)), tableSize + 1); + + //flush remaining data + if (blockBits > 8) { + int bytes = (blockBits + 7) / 8; //round up + writeHead[0] = bytes - 1; + writeHead += bytes; + } + *writeHead++ = 0; //terminating block + + //filling in buffer header and shrink buffer to fit data + MsfBufferHeader * header = (MsfBufferHeader *) allocation; + header->next = NULL; + header->size = writeHead - writeBase; + uint8_t * moved = (uint8_t *) MSF_GIF_REALLOC(allocContext, allocation, maxBufSize, writeHead - allocation); + if (!moved) { MSF_GIF_FREE(allocContext, allocation, maxBufSize); return NULL; } + return moved; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// Incremental API /// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +int msf_gif_begin(MsfGifState * handle, int width, int height) { MsfTimeFunc + MsfCookedFrame empty = {0}; //god I hate MSVC... + handle->previousFrame = empty; + handle->width = width; + handle->height = height; + + //setup header buffer header (lol) + handle->listHead = (uint8_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, sizeof(MsfBufferHeader) + 32); + if (!handle->listHead) { return 0; } + handle->listTail = handle->listHead; + MsfBufferHeader * header = (MsfBufferHeader *) handle->listHead; + header->next = NULL; + header->size = 32; + + //NOTE: because __attribute__((__packed__)) is annoyingly compiler-specific, we do this unreadable weirdness + char headerBytes[33] = "GIF89a\0\0\0\0\x10\0\0" "\x21\xFF\x0BNETSCAPE2.0\x03\x01\0\0\0"; + memcpy(&headerBytes[6], &width, 2); + memcpy(&headerBytes[8], &height, 2); + memcpy(handle->listHead + sizeof(MsfBufferHeader), headerBytes, 32); + return 1; +} + +int msf_gif_frame(MsfGifState * handle, uint8_t * pixelData, int centiSecondsPerFame, int maxBitDepth, int pitchInBytes) +{ MsfTimeFunc + if (!handle->listHead) { return 0; } + + maxBitDepth = msf_imax(1, msf_imin(16, maxBitDepth)); + if (pitchInBytes == 0) pitchInBytes = handle->width * 4; + if (pitchInBytes < 0) pixelData -= pitchInBytes * (handle->height - 1); + + uint8_t used[1 << 16]; //only 64k, so stack allocating is fine + MsfCookedFrame frame = + msf_cook_frame(handle->customAllocatorContext, pixelData, used, handle->width, handle->height, pitchInBytes, + msf_imin(maxBitDepth, handle->previousFrame.depth + 160 / msf_imax(1, handle->previousFrame.count))); + //TODO: de-duplicate cleanup code + if (!frame.pixels) { + MSF_GIF_FREE(handle->customAllocatorContext, + handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t)); + for (uint8_t * node = handle->listHead; node;) { + MsfBufferHeader * header = (MsfBufferHeader *) node; + node = header->next; + MSF_GIF_FREE(handle->customAllocatorContext, header, sizeof(MsfBufferHeader) + header->size); + } + handle->listHead = handle->listTail = NULL; + return 0; + } + + uint8_t * buffer = msf_compress_frame(handle->customAllocatorContext, + handle->width, handle->height, centiSecondsPerFame, frame, handle->previousFrame, used); + ((MsfBufferHeader *) handle->listTail)->next = buffer; + handle->listTail = buffer; + if (!buffer) { + MSF_GIF_FREE(handle->customAllocatorContext, frame.pixels, handle->width * handle->height * sizeof(uint32_t)); + MSF_GIF_FREE(handle->customAllocatorContext, + handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t)); + for (uint8_t * node = handle->listHead; node;) { + MsfBufferHeader * header = (MsfBufferHeader *) node; + node = header->next; + MSF_GIF_FREE(handle->customAllocatorContext, header, sizeof(MsfBufferHeader) + header->size); + } + handle->listHead = handle->listTail = NULL; + return 0; + } + + handle->previousFrame = frame; + return 1; +} + +MsfGifResult msf_gif_end(MsfGifState * handle) { MsfTimeFunc + if (!handle->listHead) { MsfGifResult empty = {0}; return empty; } + + MSF_GIF_FREE(handle->customAllocatorContext, + handle->previousFrame.pixels, handle->width * handle->height * sizeof(uint32_t)); + + //first pass: determine total size + size_t total = 1; //1 byte for trailing marker + for (uint8_t * node = handle->listHead; node;) { + MsfBufferHeader * header = (MsfBufferHeader *) node; + node = header->next; + total += header->size; + } + + //second pass: write data + uint8_t * buffer = (uint8_t *) MSF_GIF_MALLOC(handle->customAllocatorContext, total); + if (buffer) { + uint8_t * writeHead = buffer; + for (uint8_t * node = handle->listHead; node;) { + MsfBufferHeader * header = (MsfBufferHeader *) node; + memcpy(writeHead, node + sizeof(MsfBufferHeader), header->size); + writeHead += header->size; + node = header->next; + } + *writeHead++ = 0x3B; + } + + //third pass: free buffers + for (uint8_t * node = handle->listHead; node;) { + MsfBufferHeader * header = (MsfBufferHeader *) node; + node = header->next; + MSF_GIF_FREE(handle->customAllocatorContext, header, sizeof(MsfBufferHeader) + header->size); + } + + MsfGifResult ret = { buffer, total, total, handle->customAllocatorContext }; + return ret; +} + +void msf_gif_free(MsfGifResult result) { + if (result.data) { MSF_GIF_FREE(result.contextPointer, result.data, result.allocSize); } +} + +#endif //MSF_GIF_ALREADY_IMPLEMENTED_IN_THIS_TRANSLATION_UNIT +#endif //MSF_GIF_IMPL + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2020 Miles Fogle +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +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 OR COPYRIGHT HOLDERS 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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +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. +------------------------------------------------------------------------------ +*/ diff --git a/raylib/external/rgif.h b/raylib/external/rgif.h deleted file mode 100644 index 2bc655b..0000000 --- a/raylib/external/rgif.h +++ /dev/null @@ -1,930 +0,0 @@ -/********************************************************************************************** -* -* rgif.h v0.5 -* -* Original implementation (gif.h) by Charlie Tangora [ctangora -at- gmail -dot- com] -* adapted to C99, reformatted and renamed by Ramon Santamaria (@raysan5) -* -* This file offers a simple, very limited way to create animated GIFs directly in code. -* -* Those looking for particular cleverness are likely to be disappointed; it's pretty -* much a straight-ahead implementation of the GIF format with optional Floyd-Steinberg -* dithering. (It does at least use delta encoding - only the changed portions of each -* frame are saved.) -* -* So resulting files are often quite large. The hope is that it will be handy nonetheless -* as a quick and easily-integrated way for programs to spit out animations. -* -* Only RGBA8 is currently supported as an input format. (The alpha is ignored.) -* -* CONFIGURATION: -* -* #define RGIF_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. -* -* USAGE: -* 1) Create a GifWriter struct. Pass it to GifBegin() to initialize and write the header. -* 2) Pass subsequent frames to GifWriteFrame(). -* 3) Finally, call GifEnd() to close the file handle and free memory. -* -* -* LICENSE: This software is available under 2 licenses -- choose whichever you prefer -* -* ALTERNATIVE A - MIT License -* -* Copyright (c) 2017-2019 Ramon Santamaria (@raysan5) -* -* Permission is hereby granted, free of charge, to any person obtaining a copy of -* this software and associated documentation files (the "Software"), to deal in -* the Software without restriction, including without limitation the rights to -* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -* of the Software, and to permit persons to whom the Software is furnished to do -* so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in all -* copies or substantial portions of the Software. -* -* 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 OR COPYRIGHT HOLDERS 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. -* -* ------------------------------------------------------------------------------ -* -* ALTERNATIVE B - public domain (www.unlicense.org) -* -* 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. -* -**********************************************************************************************/ - -#ifndef RGIF_H -#define RGIF_H - -#include // Required for: FILE - -//#define RGIF_STATIC -#ifdef RGIF_STATIC - #define RGIFDEF static // Functions just visible to module including this file -#else - #ifdef __cplusplus - #define RGIFDEF extern "C" // Functions visible from other files (no name mangling of functions in C++) - #else - #define RGIFDEF extern // Functions visible from other files - #endif -#endif - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- - -// NOTE: By default use bitDepth = 8, dither = false -RGIFDEF bool GifBegin(const char *filename, unsigned int width, unsigned int height, unsigned int delay, unsigned int bitDepth, bool dither); -RGIFDEF bool GifWriteFrame(const unsigned char *image, unsigned int width, unsigned int height, unsigned int delay, int bitDepth, bool dither); -RGIFDEF bool GifEnd(); - -#endif // RGIF_H - - -/*********************************************************************************** -* - * GIF IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RGIF_IMPLEMENTATION) - -#include // Required for: FILE, fopen(), fclose() -#include // Required for: memcpy() - -// Check if custom malloc/free functions defined, if not, using standard ones -// RGIF_MALLOC and RGIF_FREE are used only by GifBegin and GifEnd respectively, -// to allocate a buffer the size of the image, which is used to find changed pixels for delta-encoding. -#if !defined(RGIF_MALLOC) - #include // Required for: malloc(), free() - - #define RGIF_MALLOC(size) malloc(size) - #define RGIF_FREE(ptr) free(ptr) -#endif - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#define GIFMIN(a, b) (((a)<(b))?(a):(b)) -#define GIFMAX(a, b) (((a)>(b))?(a):(b)) -#define GIFABS(x) ((x)<0?-(x):(x)) - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- - -// Gif palette structure -typedef struct GifPalette { - int bitDepth; - - unsigned char r[256]; - unsigned char g[256]; - unsigned char b[256]; - - // k-d tree over RGB space, organized in heap fashion - // i.e. left child of node i is node i*2, right child is node i*2 + 1 - // nodes 256-511 are implicitly the leaves, containing a color - unsigned char treeSplitElt[255]; - unsigned char treeSplit[255]; -} GifPalette; - - -// Simple structure to write out the LZW-compressed -// portion of the imageone bit at a time -typedef struct GifBitStatus { - unsigned char bitIndex; // how many bits in the partial byte written so far - unsigned char byte; // current partial byte - - unsigned int chunkIndex; - unsigned char chunk[256]; // bytes are written in here until we have 256 of them, then written to the file -} GifBitStatus; - -// The LZW dictionary is a 256-ary tree constructed -// as the file is encoded, this is one node -typedef struct GifLzwNode { - unsigned short m_next[256]; -} GifLzwNode; - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -const int gifTransparentIndex = 0; // Transparent color index - -static FILE *gifFile = NULL; -unsigned char *gifFrame; - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -static void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int *bestInd, int *bestDiff, int treeRoot); -static void GifSwapPixels(unsigned char *image, int pixA, int pixB); -static int GifPartition(unsigned char *image, const int left, const int right, const int elt, int pivotIndex); -static void GifPartitionByMedian(unsigned char *image, int left, int right, int com, int neededCenter); -static void GifSplitPalette(unsigned char *image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, int treeNode, bool buildForDither, GifPalette *pal); -static int GifPickChangedPixels(const unsigned char *lastFrame, unsigned char *frame, int numPixels); -static void GifMakePalette(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned int width, unsigned int height, int bitDepth, bool buildForDither, GifPalette *pPal); -static void GifDitherImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int height, GifPalette *pPal); -static void GifThresholdImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int height, GifPalette *pPal); -static void GifWriteBit(GifBitStatus *stat, unsigned int bit); - -static void GifWriteChunk(FILE *f, GifBitStatus *stat); -static void GifWritePalette(FILE *f, const GifPalette *pPal); -static void GifWriteCode(FILE *f, GifBitStatus *stat, unsigned int code, unsigned int length); -static void GifWriteLzwImage(FILE *f, unsigned char *image, unsigned int left, unsigned int top, unsigned int width, unsigned int height, unsigned int delay, GifPalette *pPal); - -//---------------------------------------------------------------------------------- -// Module Functions Definition -//---------------------------------------------------------------------------------- - -// Creates a gif file -// NOTE: Initializes internal file pointer (only one gif recording at a time) -// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value. -RGIFDEF bool GifBegin(const char *filename, unsigned int width, unsigned int height, unsigned int delay, unsigned int bitDepth, bool dither) -{ -#if _MSC_VER >= 1400 - gifFile = 0; - fopen_s(&gifFile, filename, "wb"); -#else - gifFile = fopen(filename, "wb"); -#endif - - if (!gifFile) return false; - - // Allocate space for one gif frame - gifFrame = (unsigned char *)RGIF_MALLOC(width*height*4); - - // GIF Header - fputs("GIF89a",gifFile); - - // Reference: http://www.onicos.com/staff/iz/formats/gif.html - - // GIF Screen Descriptor - fputc(width & 0xff, gifFile); - fputc((width >> 8) & 0xff, gifFile); // Screen width (2 byte) - fputc(height & 0xff, gifFile); - fputc((height >> 8) & 0xff, gifFile); // Screen height (2 byte) - - fputc(0xf0, gifFile); // Color table flags: unsorted global color table of 2 entries (1 byte, bit-flags) - fputc(0, gifFile); // Background color index - fputc(0, gifFile); // Pixel Aspect Ratio (square, we need to specify this because it's 1989) - - // GIF Global Color table (just a dummy palette) - // Color 0: black - fputc(0, gifFile); - fputc(0, gifFile); - fputc(0, gifFile); - // Color 1: also black - fputc(0, gifFile); - fputc(0, gifFile); - fputc(0, gifFile); - - if (delay != 0) - { - // Application Extension Block (19 bytes long) - fputc(0x21, gifFile); // GIF Extension code - fputc(0xff, gifFile); // Application Extension Label - fputc(11, gifFile); // Length of Application Block (11 byte) - fputs("NETSCAPE2.0", gifFile); // Application Identifier (Netscape 2.0 block) - - fputc(0x03, gifFile); // Length of Data Sub-Block (3 bytes) - fputc(0x01, gifFile); // 0x01 - fputc(0x00, gifFile); // This specifies the number of times, - fputc(0x00, gifFile); // the loop should be executed (infinitely) - - fputc(0x00, gifFile); // Data Sub-Block Terminator. - } - - return true; -} - -// Writes out a new frame to a GIF in progress. -// NOTE: gifFile should have been initialized with GifBegin() -// AFAIK, it is legal to use different bit depths for different frames of an image - -// this may be handy to save bits in animations that don't change much. -RGIFDEF bool GifWriteFrame(const unsigned char *image, unsigned int width, unsigned int height, unsigned int delay, int bitDepth, bool dither) -{ - if (!gifFile) return false; - - const unsigned char *oldImage = gifFrame; - - GifPalette pal; - GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth, dither, &pal); - - if (dither) GifDitherImage(oldImage, image, gifFrame, width, height, &pal); - else GifThresholdImage(oldImage, image, gifFrame, width, height, &pal); - - GifWriteLzwImage(gifFile, gifFrame, 0, 0, width, height, delay, &pal); - - return true; -} - -// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF. -// Many if not most viewers will still display a GIF properly if the EOF code is missing, -// but it's still a good idea to write it out. -RGIFDEF bool GifEnd() -{ - if (!gifFile) return false; - - fputc(0x3b, gifFile); // Trailer (end of file) - fclose(gifFile); - - RGIF_FREE(gifFrame); - - gifFile = NULL; - gifFrame = NULL; - - return true; -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -// walks the k-d tree to pick the palette entry for a desired color. -// Takes as in/out parameters the current best color and its error - -// only changes them if it finds a better color in its subtree. -// this is the major hotspot in the code at the moment. -static void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int *bestInd, int *bestDiff, int treeRoot) -{ - // base case, reached the bottom of the tree - if (treeRoot > (1<bitDepth)-1) - { - int ind = treeRoot-(1<bitDepth); - if (ind == gifTransparentIndex) return; - - // check whether this color is better than the current winner - int r_err = r - ((int)pPal->r[ind]); - int g_err = g - ((int)pPal->g[ind]); - int b_err = b - ((int)pPal->b[ind]); - int diff = GIFABS(r_err)+GIFABS(g_err)+GIFABS(b_err); - - if (diff < *bestDiff) - { - *bestInd = ind; - *bestDiff = diff; - } - - return; - } - - // take the appropriate color (r, g, or b) for this node of the k-d tree - int comps[3]; comps[0] = r; comps[1] = g; comps[2] = b; - int splitComp = comps[pPal->treeSplitElt[treeRoot]]; - - int splitPos = pPal->treeSplit[treeRoot]; - if (splitPos > splitComp) - { - // check the left subtree - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); - - if (*bestDiff > (splitPos - splitComp)) - { - // cannot prove there's not a better value in the right subtree, check that too - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2 + 1); - } - } - else - { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2 + 1); - - if (*bestDiff > splitComp - splitPos) - { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot*2); - } - } -} - -static void GifSwapPixels(unsigned char *image, int pixA, int pixB) -{ - unsigned char rA = image[pixA*4]; - unsigned char gA = image[pixA*4 + 1]; - unsigned char bA = image[pixA*4+2]; - unsigned char aA = image[pixA*4+3]; - - unsigned char rB = image[pixB*4]; - unsigned char gB = image[pixB*4 + 1]; - unsigned char bB = image[pixB*4+2]; - unsigned char aB = image[pixA*4+3]; - - image[pixA*4] = rB; - image[pixA*4 + 1] = gB; - image[pixA*4+2] = bB; - image[pixA*4+3] = aB; - - image[pixB*4] = rA; - image[pixB*4 + 1] = gA; - image[pixB*4+2] = bA; - image[pixB*4+3] = aA; -} - -// just the partition operation from quicksort -static int GifPartition(unsigned char *image, const int left, const int right, const int elt, int pivotIndex) -{ - const int pivotValue = image[(pivotIndex)*4+elt]; - GifSwapPixels(image, pivotIndex, right-1); - int storeIndex = left; - bool split = 0; - for (int ii=left; ii neededCenter) - GifPartitionByMedian(image, left, pivotIndex, com, neededCenter); - - if (pivotIndex < neededCenter) - GifPartitionByMedian(image, pivotIndex + 1, right, com, neededCenter); - } -} - -// Builds a palette by creating a balanced k-d tree of all pixels in the image -static void GifSplitPalette(unsigned char *image, int numPixels, int firstElt, int lastElt, int splitElt, int splitDist, - int treeNode, bool buildForDither, GifPalette *pal) -{ - if (lastElt <= firstElt || numPixels == 0) - return; - - // base case, bottom of the tree - if (lastElt == firstElt + 1) - { - if (buildForDither) - { - // Dithering needs at least one color as dark as anything - // in the image and at least one brightest color - - // otherwise it builds up error and produces strange artifacts - if (firstElt == 1) - { - // special case: the darkest color in the image - unsigned int r=255, g=255, b=255; - for (int ii=0; iir[firstElt] = r; - pal->g[firstElt] = g; - pal->b[firstElt] = b; - - return; - } - - if (firstElt == (1 << pal->bitDepth)-1) - { - // special case: the lightest color in the image - unsigned int r=0, g=0, b=0; - for (int ii=0; iir[firstElt] = r; - pal->g[firstElt] = g; - pal->b[firstElt] = b; - - return; - } - } - - // otherwise, take the average of all colors in this subcube - unsigned long long r=0, g=0, b=0; - for (int ii=0; iir[firstElt] = (unsigned char)r; - pal->g[firstElt] = (unsigned char)g; - pal->b[firstElt] = (unsigned char)b; - - return; - } - - // Find the axis with the largest range - int minR = 255, maxR = 0; - int minG = 255, maxG = 0; - int minB = 255, maxB = 0; - for (int ii=0; ii maxR) maxR = r; - if (r < minR) minR = r; - - if (g > maxG) maxG = g; - if (g < minG) minG = g; - - if (b > maxB) maxB = b; - if (b < minB) minB = b; - } - - int rRange = maxR - minR; - int gRange = maxG - minG; - int bRange = maxB - minB; - - // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) - int splitCom = 1; - if (bRange > gRange) splitCom = 2; - if (rRange > bRange && rRange > gRange) splitCom = 0; - - int subPixelsA = numPixels *(splitElt - firstElt) / (lastElt - firstElt); - int subPixelsB = numPixels-subPixelsA; - - GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA); - - pal->treeSplitElt[treeNode] = splitCom; - pal->treeSplit[treeNode] = image[subPixelsA*4+splitCom]; - - GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt-splitDist, splitDist/2, treeNode*2, buildForDither, pal); - GifSplitPalette(image+subPixelsA*4, subPixelsB, splitElt, lastElt, splitElt+splitDist, splitDist/2, treeNode*2 + 1, buildForDither, pal); -} - -// Finds all pixels that have changed from the previous image and -// moves them to the fromt of th buffer. -// This allows us to build a palette optimized for the colors of the -// changed pixels only. -static int GifPickChangedPixels(const unsigned char *lastFrame, unsigned char *frame, int numPixels) -{ - int numChanged = 0; - unsigned char *writeIter = frame; - - for (int ii=0; iibitDepth = bitDepth; - - // SplitPalette is destructive (it sorts the pixels by color) so - // we must create a copy of the image for it to destroy - int imageSize = width*height*4*sizeof(unsigned char); - unsigned char *destroyableImage = (unsigned char*)RGIF_MALLOC(imageSize); - memcpy(destroyableImage, nextFrame, imageSize); - - int numPixels = width*height; - if (lastFrame) - numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels); - - const int lastElt = 1 << bitDepth; - const int splitElt = lastElt/2; - const int splitDist = splitElt/2; - - GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal); - - RGIF_FREE(destroyableImage); - - // add the bottom node for the transparency index - pPal->treeSplit[1 << (bitDepth-1)] = 0; - pPal->treeSplitElt[1 << (bitDepth-1)] = 0; - - pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; -} - -// Implements Floyd-Steinberg dithering, writes palette value to alpha -static void GifDitherImage(const unsigned char *lastFrame, const unsigned char *nextFrame, unsigned char *outFrame, unsigned int width, unsigned int height, GifPalette *pPal) -{ - int numPixels = width*height; - - // quantPixels initially holds color*256 for all pixels - // The extra 8 bits of precision allow for sub-single-color error values - // to be propagated - int *quantPixels = (int*)RGIF_MALLOC(sizeof(int)*numPixels*4); - - for (int ii=0; iir[bestInd])*256; - int g_err = nextPix[1] - (int)(pPal->g[bestInd])*256; - int b_err = nextPix[2] - (int)(pPal->b[bestInd])*256; - - nextPix[0] = pPal->r[bestInd]; - nextPix[1] = pPal->g[bestInd]; - nextPix[2] = pPal->b[bestInd]; - nextPix[3] = bestInd; - - // Propagate the error to the four adjacent locations - // that we haven't touched yet - int quantloc_7 = (yy*width+xx + 1); - int quantloc_3 = (yy*width+width+xx-1); - int quantloc_5 = (yy*width+width+xx); - int quantloc_1 = (yy*width+width+xx + 1); - - if (quantloc_7 < numPixels) - { - int *pix7 = quantPixels+4*quantloc_7; - pix7[0] += GIFMAX(-pix7[0], r_err*7 / 16); - pix7[1] += GIFMAX(-pix7[1], g_err*7 / 16); - pix7[2] += GIFMAX(-pix7[2], b_err*7 / 16); - } - - if (quantloc_3 < numPixels) - { - int *pix3 = quantPixels+4*quantloc_3; - pix3[0] += GIFMAX(-pix3[0], r_err*3 / 16); - pix3[1] += GIFMAX(-pix3[1], g_err*3 / 16); - pix3[2] += GIFMAX(-pix3[2], b_err*3 / 16); - } - - if (quantloc_5 < numPixels) - { - int *pix5 = quantPixels+4*quantloc_5; - pix5[0] += GIFMAX(-pix5[0], r_err*5 / 16); - pix5[1] += GIFMAX(-pix5[1], g_err*5 / 16); - pix5[2] += GIFMAX(-pix5[2], b_err*5 / 16); - } - - if (quantloc_1 < numPixels) - { - int *pix1 = quantPixels+4*quantloc_1; - pix1[0] += GIFMAX(-pix1[0], r_err / 16); - pix1[1] += GIFMAX(-pix1[1], g_err / 16); - pix1[2] += GIFMAX(-pix1[2], b_err / 16); - } - } - } - - // Copy the palettized result to the output buffer - for (int ii=0; iir[bestInd]; - outFrame[1] = pPal->g[bestInd]; - outFrame[2] = pPal->b[bestInd]; - outFrame[3] = bestInd; - } - - if (lastFrame) lastFrame += 4; - outFrame += 4; - nextFrame += 4; - } -} - - -// insert a single bit -static void GifWriteBit(GifBitStatus *stat, unsigned int bit) -{ - bit = bit & 1; - bit = bit << stat->bitIndex; - stat->byte |= bit; - - ++stat->bitIndex; - if (stat->bitIndex > 7) - { - // move the newly-finished byte to the chunk buffer - stat->chunk[stat->chunkIndex++] = stat->byte; - // and start a new byte - stat->bitIndex = 0; - stat->byte = 0; - } -} - -// write all bytes so far to the file -static void GifWriteChunk(FILE *f, GifBitStatus *stat) -{ - fputc(stat->chunkIndex, f); - fwrite(stat->chunk, 1, stat->chunkIndex, f); - - stat->bitIndex = 0; - stat->byte = 0; - stat->chunkIndex = 0; -} - -static void GifWriteCode(FILE *f, GifBitStatus *stat, unsigned int code, unsigned int length) -{ - for (unsigned int ii=0; ii> 1; - - if (stat->chunkIndex == 255) - { - GifWriteChunk(f, stat); - } - } -} - -// write a 256-color (8-bit) image palette to the file -static void GifWritePalette(FILE *f, const GifPalette *pPal) -{ - fputc(0, f); // first color: transparency - fputc(0, f); - fputc(0, f); - - for (int ii=1; ii<(1 << pPal->bitDepth); ++ii) - { - unsigned int r = pPal->r[ii]; - unsigned int g = pPal->g[ii]; - unsigned int b = pPal->b[ii]; - - fputc(r, f); - fputc(g, f); - fputc(b, f); - } -} - -// write the image header, LZW-compress and write out the image -static void GifWriteLzwImage(FILE *f, unsigned char *image, unsigned int left, unsigned int top, unsigned int width, unsigned int height, unsigned int delay, GifPalette *pPal) -{ - // graphics control extension - fputc(0x21, f); - fputc(0xf9, f); - fputc(0x04, f); - fputc(0x05, f); // leave prev frame in place, this frame has transparency - fputc(delay & 0xff, f); - fputc((delay >> 8) & 0xff, f); - fputc(gifTransparentIndex, f); // transparent color index - fputc(0, f); - - fputc(0x2c, f); // image descriptor block - - fputc(left & 0xff, f); // corner of image in canvas space - fputc((left >> 8) & 0xff, f); - fputc(top & 0xff, f); - fputc((top >> 8) & 0xff, f); - - fputc(width & 0xff, f); // width and height of image - fputc((width >> 8) & 0xff, f); - fputc(height & 0xff, f); - fputc((height >> 8) & 0xff, f); - - //fputc(0, f); // no local color table, no transparency - //fputc(0x80, f); // no local color table, but transparency - - fputc(0x80 + pPal->bitDepth-1, f); // local color table present, 2 ^ bitDepth entries - GifWritePalette(f, pPal); - - const int minCodeSize = pPal->bitDepth; - const unsigned int clearCode = 1 << pPal->bitDepth; - - fputc(minCodeSize, f); // min code size 8 bits - - GifLzwNode *codetree = (GifLzwNode *)RGIF_MALLOC(sizeof(GifLzwNode)*4096); - - memset(codetree, 0, sizeof(GifLzwNode)*4096); - int curCode = -1; - unsigned int codeSize = minCodeSize + 1; - unsigned int maxCode = clearCode + 1; - - GifBitStatus stat; - stat.byte = 0; - stat.bitIndex = 0; - stat.chunkIndex = 0; - - GifWriteCode(f, &stat, clearCode, codeSize); // start with a fresh LZW dictionary - - for (unsigned int yy=0; yy= (1ul << codeSize)) - { - // dictionary entry count has broken a size barrier, - // we need more bits for codes - codeSize++; - } - if (maxCode == 4095) - { - // the dictionary is full, clear it out and begin anew - GifWriteCode(f, &stat, clearCode, codeSize); // clear tree - - memset(codetree, 0, sizeof(GifLzwNode)*4096); - codeSize = minCodeSize + 1; - maxCode = clearCode + 1; - } - - curCode = nextValue; - } - } - } - - // compression footer - GifWriteCode(f, &stat, curCode, codeSize); - GifWriteCode(f, &stat, clearCode, codeSize); - GifWriteCode(f, &stat, clearCode + 1, minCodeSize + 1); - - // write out the last partial chunk - while (stat.bitIndex) GifWriteBit(&stat, 0); - if (stat.chunkIndex) GifWriteChunk(f, &stat); - - fputc(0, f); // image block terminator - - RGIF_FREE(codetree); -} - -#endif // RGIF_IMPLEMENTATION diff --git a/raylib/external/sdefl.h b/raylib/external/sdefl.h new file mode 100644 index 0000000..218e754 --- /dev/null +++ b/raylib/external/sdefl.h @@ -0,0 +1,697 @@ +/* +# Small Deflate +`sdefl` is a small bare bone lossless compression library in ANSI C (ISO C90) +which implements the Deflate (RFC 1951) compressed data format specification standard. +It is mainly tuned to get as much speed and compression ratio from as little code +as needed to keep the implementation as concise as possible. + +## Features +- Portable single header and source file duo written in ANSI C (ISO C90) +- Dual license with either MIT or public domain +- Small implementation + - Deflate: 525 LoC + - Inflate: 320 LoC +- Webassembly: + - Deflate ~3.7 KB (~2.2KB compressed) + - Inflate ~3.6 KB (~2.2KB compressed) + +## Usage: +This file behaves differently depending on what symbols you define +before including it. + +Header-File mode: +If you do not define `SDEFL_IMPLEMENTATION` before including this file, it +will operate in header only mode. In this mode it declares all used structs +and the API of the library without including the implementation of the library. + +Implementation mode: +If you define `SDEFL_IMPLEMENTATION` before including this file, it will +compile the implementation . Make sure that you only include +this file implementation in *one* C or C++ file to prevent collisions. + +### Benchmark + +| Compressor name | Compression| Decompress.| Compr. size | Ratio | +| ------------------------| -----------| -----------| ----------- | ----- | +| sdefl 1.0 -0 | 127 MB/s | 233 MB/s | 40004116 | 39.88 | +| sdefl 1.0 -1 | 111 MB/s | 259 MB/s | 38940674 | 38.82 | +| sdefl 1.0 -5 | 45 MB/s | 275 MB/s | 36577183 | 36.46 | +| sdefl 1.0 -7 | 38 MB/s | 276 MB/s | 36523781 | 36.41 | +| zlib 1.2.11 -1 | 72 MB/s | 307 MB/s | 42298774 | 42.30 | +| zlib 1.2.11 -6 | 24 MB/s | 313 MB/s | 36548921 | 36.55 | +| zlib 1.2.11 -9 | 20 MB/s | 314 MB/s | 36475792 | 36.48 | +| miniz 1.0 -1 | 122 MB/s | 208 MB/s | 48510028 | 48.51 | +| miniz 1.0 -6 | 27 MB/s | 260 MB/s | 36513697 | 36.51 | +| miniz 1.0 -9 | 23 MB/s | 261 MB/s | 36460101 | 36.46 | +| libdeflate 1.3 -1 | 147 MB/s | 667 MB/s | 39597378 | 39.60 | +| libdeflate 1.3 -6 | 69 MB/s | 689 MB/s | 36648318 | 36.65 | +| libdeflate 1.3 -9 | 13 MB/s | 672 MB/s | 35197141 | 35.20 | +| libdeflate 1.3 -12 | 8.13 MB/s | 670 MB/s | 35100568 | 35.10 | + +### Compression +Results on the [Silesia compression corpus](http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia): + +| File | Original | `sdefl 0` | `sdefl 5` | `sdefl 7` | +| :------ | ---------: | -----------------: | ---------: | ----------: | +| dickens | 10.192.446 | 4,260,187| 3,845,261| 3,833,657 | +| mozilla | 51.220.480 | 20,774,706 | 19,607,009 | 19,565,867 | +| mr | 9.970.564 | 3,860,531 | 3,673,460 | 3,665,627 | +| nci | 33.553.445 | 4,030,283 | 3,094,526 | 3,006,075 | +| ooffice | 6.152.192 | 3,320,063 | 3,186,373 | 3,183,815 | +| osdb | 10.085.684 | 3,919,646 | 3,649,510 | 3,649,477 | +| reymont | 6.627.202 | 2,263,378 | 1,857,588 | 1,827,237 | +| samba | 21.606.400 | 6,121,797 | 5,462,670 | 5,450,762 | +| sao | 7.251.944 | 5,612,421 | 5,485,380 | 5,481,765 | +| webster | 41.458.703 | 13,972,648 | 12,059,432 | 11,991,421 | +| xml | 5.345.280 | 886,620| 674,009 | 662,141 | +| x-ray | 8.474.240 | 6,304,655 | 6,244,779 | 6,244,779 | + +## License +``` +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2020 Micha Mettke +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +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 OR COPYRIGHT HOLDERS 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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +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. +------------------------------------------------------------------------------ +``` +*/ +#ifndef SDEFL_H_INCLUDED +#define SDEFL_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDEFL_MAX_OFF (1 << 15) +#define SDEFL_WIN_SIZ SDEFL_MAX_OFF +#define SDEFL_WIN_MSK (SDEFL_WIN_SIZ-1) + +#define SDEFL_HASH_BITS 15 +#define SDEFL_HASH_SIZ (1 << SDEFL_HASH_BITS) +#define SDEFL_HASH_MSK (SDEFL_HASH_SIZ-1) + +#define SDEFL_MIN_MATCH 4 +#define SDEFL_BLK_MAX (256*1024) +#define SDEFL_SEQ_SIZ ((SDEFL_BLK_MAX + SDEFL_MIN_MATCH)/SDEFL_MIN_MATCH) + +#define SDEFL_SYM_MAX (288) +#define SDEFL_OFF_MAX (32) +#define SDEFL_PRE_MAX (19) + +#define SDEFL_LVL_MIN 0 +#define SDEFL_LVL_DEF 5 +#define SDEFL_LVL_MAX 8 + +struct sdefl_freq { + unsigned lit[SDEFL_SYM_MAX]; + unsigned off[SDEFL_OFF_MAX]; +}; +struct sdefl_code_words { + unsigned lit[SDEFL_SYM_MAX]; + unsigned off[SDEFL_OFF_MAX]; +}; +struct sdefl_lens { + unsigned char lit[SDEFL_SYM_MAX]; + unsigned char off[SDEFL_OFF_MAX]; +}; +struct sdefl_codes { + struct sdefl_code_words word; + struct sdefl_lens len; +}; +struct sdefl_seqt { + int off, len; +}; +struct sdefl { + int bits, bitcnt; + int tbl[SDEFL_HASH_SIZ]; + int prv[SDEFL_WIN_SIZ]; + + int seq_cnt; + struct sdefl_seqt seq[SDEFL_SEQ_SIZ]; + struct sdefl_freq freq; + struct sdefl_codes cod; +}; +extern int sdefl_bound(int in_len); +extern int sdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); +extern int zsdeflate(struct sdefl *s, void *o, const void *i, int n, int lvl); + +#ifdef __cplusplus +} +#endif + +#endif /* SDEFL_H_INCLUDED */ + +#ifdef SDEFL_IMPLEMENTATION + +#include /* assert */ +#include /* memcpy */ +#include /* CHAR_BIT */ + +#define SDEFL_NIL (-1) +#define SDEFL_MAX_MATCH 258 +#define SDEFL_MAX_CODE_LEN (15) +#define SDEFL_SYM_BITS (10u) +#define SDEFL_SYM_MSK ((1u << SDEFL_SYM_BITS)-1u) +#define SDEFL_LIT_LEN_CODES (14) +#define SDEFL_OFF_CODES (15) +#define SDEFL_PRE_CODES (7) +#define SDEFL_CNT_NUM(n) ((((n)+3u/4u)+3u)&~3u) +#define SDEFL_EOB (256) + +#define sdefl_npow2(n) (1 << (sdefl_ilog2((n)-1) + 1)) + +static int +sdefl_ilog2(int n) { + if (!n) return 0; +#ifdef _MSC_VER + unsigned long msbp = 0; + _BitScanReverse(&msbp, (unsigned long)n); + return (int)msbp; +#elif defined(__GNUC__) || defined(__clang__) + return (int)sizeof(unsigned long) * CHAR_BIT - 1 - __builtin_clzl((unsigned long)n); +#else + #define lt(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n + static const char tbl[256] = { + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,lt(4), lt(5), lt(5), lt(6), lt(6), lt(6), lt(6), + lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7), lt(7)}; + int tt, t; + if ((tt = (n >> 16))) { + return (t = (tt >> 8)) ? 24 + tbl[t] : 16 + tbl[tt]; + } else { + return (t = (n >> 8)) ? 8 + tbl[t] : tbl[n]; + } + #undef lt +#endif +} +static unsigned +sdefl_uload32(const void *p) { + /* hopefully will be optimized to an unaligned read */ + unsigned n = 0; + memcpy(&n, p, sizeof(n)); + return n; +} +static unsigned +sdefl_hash32(const void *p) { + unsigned n = sdefl_uload32(p); + return (n * 0x9E377989) >> (32 - SDEFL_HASH_BITS); +} +static void +sdefl_put(unsigned char **dst, struct sdefl *s, int code, int bitcnt) { + s->bits |= (code << s->bitcnt); + s->bitcnt += bitcnt; + while (s->bitcnt >= 8) { + unsigned char *tar = *dst; + *tar = (unsigned char)(s->bits & 0xFF); + s->bits >>= 8; + s->bitcnt -= 8; + *dst = *dst + 1; + } +} +static void +sdefl_heap_sub(unsigned A[], unsigned len, unsigned sub) { + unsigned c, p = sub; + unsigned v = A[sub]; + while ((c = p << 1) <= len) { + if (c < len && A[c + 1] > A[c]) c++; + if (v >= A[c]) break; + A[p] = A[c], p = c; + } + A[p] = v; +} +static void +sdefl_heap_array(unsigned *A, unsigned len) { + unsigned sub; + for (sub = len >> 1; sub >= 1; sub--) + sdefl_heap_sub(A, len, sub); +} +static void +sdefl_heap_sort(unsigned *A, unsigned n) { + A--; + sdefl_heap_array(A, n); + while (n >= 2) { + unsigned tmp = A[n]; + A[n--] = A[1]; + A[1] = tmp; + sdefl_heap_sub(A, n, 1); + } +} +static unsigned +sdefl_sort_sym(unsigned sym_cnt, unsigned *freqs, + unsigned char *lens, unsigned *sym_out) { + unsigned cnts[SDEFL_CNT_NUM(SDEFL_SYM_MAX)] = {0}; + unsigned cnt_num = SDEFL_CNT_NUM(sym_cnt); + unsigned used_sym = 0; + unsigned sym, i; + for (sym = 0; sym < sym_cnt; sym++) + cnts[freqs[sym] < cnt_num-1 ? freqs[sym]: cnt_num-1]++; + for (i = 1; i < cnt_num; i++) { + unsigned cnt = cnts[i]; + cnts[i] = used_sym; + used_sym += cnt; + } + for (sym = 0; sym < sym_cnt; sym++) { + unsigned freq = freqs[sym]; + if (freq) { + unsigned idx = freq < cnt_num-1 ? freq : cnt_num-1; + sym_out[cnts[idx]++] = sym | (freq << SDEFL_SYM_BITS); + } else lens[sym] = 0; + } + sdefl_heap_sort(sym_out + cnts[cnt_num-2], cnts[cnt_num-1] - cnts[cnt_num-2]); + return used_sym; +} +static void +sdefl_build_tree(unsigned *A, unsigned sym_cnt) { + unsigned i = 0, b = 0, e = 0; + do { + unsigned m, n, freq_shift; + if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS))) + m = i++; + else m = b++; + if (i != sym_cnt && (b == e || (A[i] >> SDEFL_SYM_BITS) <= (A[b] >> SDEFL_SYM_BITS))) + n = i++; + else n = b++; + + freq_shift = (A[m] & ~SDEFL_SYM_MSK) + (A[n] & ~SDEFL_SYM_MSK); + A[m] = (A[m] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS); + A[n] = (A[n] & SDEFL_SYM_MSK) | (e << SDEFL_SYM_BITS); + A[e] = (A[e] & SDEFL_SYM_MSK) | freq_shift; + } while (sym_cnt - ++e > 1); +} +static void +sdefl_gen_len_cnt(unsigned *A, unsigned root, unsigned *len_cnt, + unsigned max_code_len) { + int n; + unsigned i; + for (i = 0; i <= max_code_len; i++) + len_cnt[i] = 0; + len_cnt[1] = 2; + + A[root] &= SDEFL_SYM_MSK; + for (n = (int)root - 1; n >= 0; n--) { + unsigned p = A[n] >> SDEFL_SYM_BITS; + unsigned pdepth = A[p] >> SDEFL_SYM_BITS; + unsigned depth = pdepth + 1; + unsigned len = depth; + + A[n] = (A[n] & SDEFL_SYM_MSK) | (depth << SDEFL_SYM_BITS); + if (len >= max_code_len) { + len = max_code_len; + do len--; while (!len_cnt[len]); + } + len_cnt[len]--; + len_cnt[len+1] += 2; + } +} +static void +sdefl_gen_codes(unsigned *A, unsigned char *lens, const unsigned *len_cnt, + unsigned max_code_word_len, unsigned sym_cnt) { + unsigned i, sym, len, nxt[SDEFL_MAX_CODE_LEN + 1]; + for (i = 0, len = max_code_word_len; len >= 1; len--) { + unsigned cnt = len_cnt[len]; + while (cnt--) lens[A[i++] & SDEFL_SYM_MSK] = (unsigned char)len; + } + nxt[0] = nxt[1] = 0; + for (len = 2; len <= max_code_word_len; len++) + nxt[len] = (nxt[len-1] + len_cnt[len-1]) << 1; + for (sym = 0; sym < sym_cnt; sym++) + A[sym] = nxt[lens[sym]]++; +} +static unsigned +sdefl_rev(unsigned c, unsigned char n) { + c = ((c & 0x5555) << 1) | ((c & 0xAAAA) >> 1); + c = ((c & 0x3333) << 2) | ((c & 0xCCCC) >> 2); + c = ((c & 0x0F0F) << 4) | ((c & 0xF0F0) >> 4); + c = ((c & 0x00FF) << 8) | ((c & 0xFF00) >> 8); + return c >> (16-n); +} +static void +sdefl_huff(unsigned char *lens, unsigned *codes, unsigned *freqs, + unsigned num_syms, unsigned max_code_len) { + unsigned c, *A = codes; + unsigned len_cnt[SDEFL_MAX_CODE_LEN + 1]; + unsigned used_syms = sdefl_sort_sym(num_syms, freqs, lens, A); + if (!used_syms) return; + if (used_syms == 1) { + unsigned s = A[0] & SDEFL_SYM_MSK; + unsigned i = s ? s : 1; + codes[0] = 0, lens[0] = 1; + codes[i] = 1, lens[i] = 1; + return; + } + sdefl_build_tree(A, used_syms); + sdefl_gen_len_cnt(A, used_syms-2, len_cnt, max_code_len); + sdefl_gen_codes(A, lens, len_cnt, max_code_len, num_syms); + for (c = 0; c < num_syms; c++) { + codes[c] = sdefl_rev(codes[c], lens[c]); + } +} +struct sdefl_symcnt { + int items; + int lit; + int off; +}; +static void +sdefl_precode(struct sdefl_symcnt *cnt, unsigned *freqs, unsigned *items, + const unsigned char *litlen, const unsigned char *offlen) { + unsigned *at = items; + unsigned run_start = 0; + + unsigned total = 0; + unsigned char lens[SDEFL_SYM_MAX + SDEFL_OFF_MAX]; + for (cnt->lit = SDEFL_SYM_MAX; cnt->lit > 257; cnt->lit--) + if (litlen[cnt->lit - 1]) break; + for (cnt->off = SDEFL_OFF_MAX; cnt->off > 1; cnt->off--) + if (offlen[cnt->off - 1]) break; + + total = (unsigned)(cnt->lit + cnt->off); + memcpy(lens, litlen, sizeof(unsigned char) * cnt->lit); + memcpy(lens + cnt->lit, offlen, sizeof(unsigned char) * cnt->off); + do { + unsigned len = lens[run_start]; + unsigned run_end = run_start; + do run_end++; while (run_end != total && len == lens[run_end]); + if (!len) { + while ((run_end - run_start) >= 11) { + unsigned n = (run_end - run_start) - 11; + unsigned xbits = n < 0x7f ? n : 0x7f; + freqs[18]++; + *at++ = 18u | (xbits << 5u); + run_start += 11 + xbits; + } + if ((run_end - run_start) >= 3) { + unsigned n = (run_end - run_start) - 3; + unsigned xbits = n < 0x7 ? n : 0x7; + freqs[17]++; + *at++ = 17u | (xbits << 5u); + run_start += 3 + xbits; + } + } else if ((run_end - run_start) >= 4) { + freqs[len]++; + *at++ = len; + run_start++; + do { + unsigned xbits = (run_end - run_start) - 3; + xbits = xbits < 0x03 ? xbits : 0x03; + *at++ = 16 | (xbits << 5); + run_start += 3 + xbits; + freqs[16]++; + } while ((run_end - run_start) >= 3); + } + while (run_start != run_end) { + freqs[len]++; + *at++ = len; + run_start++; + } + } while (run_start != total); + cnt->items = (int)(at - items); +} +struct sdefl_match_codes { + int ls, lc; + int dc, dx; +}; +static void +sdefl_match_codes(struct sdefl_match_codes *cod, int dist, int len) { + static const short dxmax[] = {0,6,12,24,48,96,192,384,768,1536,3072,6144,12288,24576}; + static const unsigned char lslot[258+1] = { + 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, + 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, + 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, + 18, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, + 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 28 + }; + cod->ls = lslot[len]; + cod->lc = 257 + cod->ls; + cod->dx = sdefl_ilog2(sdefl_npow2(dist) >> 2); + cod->dc = cod->dx ? ((cod->dx + 1) << 1) + (dist > dxmax[cod->dx]) : dist-1; +} +static void +sdefl_match(unsigned char **dst, struct sdefl *s, int dist, int len) { + static const char lxn[] = {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}; + static const short lmin[] = {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}; + static const short dmin[] = {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}; + + struct sdefl_match_codes cod; + sdefl_match_codes(&cod, dist, len); + sdefl_put(dst, s, (int)s->cod.word.lit[cod.lc], s->cod.len.lit[cod.lc]); + sdefl_put(dst, s, len - lmin[cod.ls], lxn[cod.ls]); + sdefl_put(dst, s, (int)s->cod.word.off[cod.dc], s->cod.len.off[cod.dc]); + sdefl_put(dst, s, dist - dmin[cod.dc], cod.dx); +} +static void +sdefl_flush(unsigned char **dst, struct sdefl *s, int is_last, + const unsigned char *in) { + int j, i = 0, item_cnt = 0; + struct sdefl_symcnt symcnt = {0}; + unsigned codes[SDEFL_PRE_MAX]; + unsigned char lens[SDEFL_PRE_MAX]; + unsigned freqs[SDEFL_PRE_MAX] = {0}; + unsigned items[SDEFL_SYM_MAX + SDEFL_OFF_MAX]; + static const unsigned char perm[SDEFL_PRE_MAX] = {16,17,18,0,8,7,9,6,10,5,11, + 4,12,3,13,2,14,1,15}; + + /* huffman codes */ + s->freq.lit[SDEFL_EOB]++; + sdefl_huff(s->cod.len.lit, s->cod.word.lit, s->freq.lit, SDEFL_SYM_MAX, SDEFL_LIT_LEN_CODES); + sdefl_huff(s->cod.len.off, s->cod.word.off, s->freq.off, SDEFL_OFF_MAX, SDEFL_OFF_CODES); + sdefl_precode(&symcnt, freqs, items, s->cod.len.lit, s->cod.len.off); + sdefl_huff(lens, codes, freqs, SDEFL_PRE_MAX, SDEFL_PRE_CODES); + for (item_cnt = SDEFL_PRE_MAX; item_cnt > 4; item_cnt--) { + if (lens[perm[item_cnt - 1]]) break; + } + /* block header */ + sdefl_put(dst, s, is_last ? 0x01 : 0x00, 1); /* block */ + sdefl_put(dst, s, 0x02, 2); /* dynamic huffman */ + sdefl_put(dst, s, symcnt.lit - 257, 5); + sdefl_put(dst, s, symcnt.off - 1, 5); + sdefl_put(dst, s, item_cnt - 4, 4); + for (i = 0; i < item_cnt; ++i) + sdefl_put(dst, s, lens[perm[i]], 3); + for (i = 0; i < symcnt.items; ++i) { + unsigned sym = items[i] & 0x1F; + sdefl_put(dst, s, (int)codes[sym], lens[sym]); + if (sym < 16) continue; + if (sym == 16) sdefl_put(dst, s, items[i] >> 5, 2); + else if(sym == 17) sdefl_put(dst, s, items[i] >> 5, 3); + else sdefl_put(dst, s, items[i] >> 5, 7); + } + /* block sequences */ + for (i = 0; i < s->seq_cnt; ++i) { + if (s->seq[i].off >= 0) + for (j = 0; j < s->seq[i].len; ++j) { + int c = in[s->seq[i].off + j]; + sdefl_put(dst, s, (int)s->cod.word.lit[c], s->cod.len.lit[c]); + } + else sdefl_match(dst, s, -s->seq[i].off, s->seq[i].len); + } + sdefl_put(dst, s, (int)(s)->cod.word.lit[SDEFL_EOB], (s)->cod.len.lit[SDEFL_EOB]); + memset(&s->freq, 0, sizeof(s->freq)); + s->seq_cnt = 0; +} +static void +sdefl_seq(struct sdefl *s, int off, int len) { + assert(s->seq_cnt + 2 < SDEFL_SEQ_SIZ); + s->seq[s->seq_cnt].off = off; + s->seq[s->seq_cnt].len = len; + s->seq_cnt++; +} +static void +sdefl_reg_match(struct sdefl *s, int off, int len) { + struct sdefl_match_codes cod; + sdefl_match_codes(&cod, off, len); + s->freq.lit[cod.lc]++; + s->freq.off[cod.dc]++; +} +struct sdefl_match { + int off; + int len; +}; +static void +sdefl_fnd(struct sdefl_match *m, const struct sdefl *s, + int chain_len, int max_match, const unsigned char *in, int p) { + int i = s->tbl[sdefl_hash32(&in[p])]; + int limit = ((p-SDEFL_WIN_SIZ) limit) { + if (in[i+m->len] == in[p+m->len] && + (sdefl_uload32(&in[i]) == sdefl_uload32(&in[p]))){ + int n = SDEFL_MIN_MATCH; + while (n < max_match && in[i+n] == in[p+n]) n++; + if (n > m->len) { + m->len = n, m->off = p - i; + if (n == max_match) break; + } + } + if (!(--chain_len)) break; + i = s->prv[i&SDEFL_WIN_MSK]; + } +} +static int +sdefl_compr(struct sdefl *s, unsigned char *out, const unsigned char *in, + int in_len, int lvl) { + unsigned char *q = out; + static const unsigned char pref[] = {8,10,14,24,30,48,65,96,130}; + int max_chain = (lvl < 8) ? (1 << (lvl + 1)): (1 << 13); + int n, i = 0, litlen = 0; + for (n = 0; n < SDEFL_HASH_SIZ; ++n) { + s->tbl[n] = SDEFL_NIL; + } + do {int blk_end = i + SDEFL_BLK_MAX < in_len ? i + SDEFL_BLK_MAX : in_len; + while (i < blk_end) { + struct sdefl_match m = {0}; + int max_match = ((in_len-i)>SDEFL_MAX_MATCH) ? SDEFL_MAX_MATCH:(in_len-i); + int nice_match = pref[lvl] < max_match ? pref[lvl] : max_match; + int run = 1, inc = 1, run_inc; + if (max_match > SDEFL_MIN_MATCH) { + sdefl_fnd(&m, s, max_chain, max_match, in, i); + } + if (lvl >= 5 && m.len >= SDEFL_MIN_MATCH && m.len < nice_match){ + struct sdefl_match m2 = {0}; + sdefl_fnd(&m2, s, max_chain, m.len+1, in, i+1); + m.len = (m2.len > m.len) ? 0 : m.len; + } + if (m.len >= SDEFL_MIN_MATCH) { + if (litlen) { + sdefl_seq(s, i - litlen, litlen); + litlen = 0; + } + sdefl_seq(s, -m.off, m.len); + sdefl_reg_match(s, m.off, m.len); + if (lvl < 2 && m.len >= nice_match) { + inc = m.len; + } else { + run = m.len; + } + } else { + s->freq.lit[in[i]]++; + litlen++; + } + run_inc = run * inc; + if (in_len - (i + run_inc) > SDEFL_MIN_MATCH) { + while (run-- > 0) { + unsigned h = sdefl_hash32(&in[i]); + s->prv[i&SDEFL_WIN_MSK] = s->tbl[h]; + s->tbl[h] = i, i += inc; + } + } else { + i += run_inc; + } + } + if (litlen) { + sdefl_seq(s, i - litlen, litlen); + litlen = 0; + } + sdefl_flush(&q, s, blk_end == in_len, in); + } while (i < in_len); + + if (s->bitcnt) + sdefl_put(&q, s, 0x00, 8 - s->bitcnt); + return (int)(q - out); +} +extern int +sdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) { + s->bits = s->bitcnt = 0; + return sdefl_compr(s, (unsigned char*)out, (const unsigned char*)in, n, lvl); +} +static unsigned +sdefl_adler32(unsigned adler32, const unsigned char *in, int in_len) { + #define SDEFL_ADLER_INIT (1) + const unsigned ADLER_MOD = 65521; + unsigned s1 = adler32 & 0xffff; + unsigned s2 = adler32 >> 16; + unsigned blk_len, i; + + blk_len = in_len % 5552; + while (in_len) { + for (i = 0; i + 7 < blk_len; i += 8) { + s1 += in[0]; s2 += s1; + s1 += in[1]; s2 += s1; + s1 += in[2]; s2 += s1; + s1 += in[3]; s2 += s1; + s1 += in[4]; s2 += s1; + s1 += in[5]; s2 += s1; + s1 += in[6]; s2 += s1; + s1 += in[7]; s2 += s1; + in += 8; + } + for (; i < blk_len; ++i) { + s1 += *in++, s2 += s1; + } + s1 %= ADLER_MOD; + s2 %= ADLER_MOD; + in_len -= blk_len; + blk_len = 5552; + } + return (unsigned)(s2 << 16) + (unsigned)s1; +} +extern int +zsdeflate(struct sdefl *s, void *out, const void *in, int n, int lvl) { + int p = 0; + unsigned a = 0; + unsigned char *q = (unsigned char*)out; + + s->bits = s->bitcnt = 0; + sdefl_put(&q, s, 0x78, 8); /* deflate, 32k window */ + sdefl_put(&q, s, 0x01, 8); /* fast compression */ + q += sdefl_compr(s, q, (const unsigned char*)in, n, lvl); + + /* append adler checksum */ + a = sdefl_adler32(SDEFL_ADLER_INIT, (const unsigned char*)in, n); + for (p = 0; p < 4; ++p) { + sdefl_put(&q, s, (a >> 24) & 0xFF, 8); + a <<= 8; + } + return (int)(q - (unsigned char*)out); +} +extern int +sdefl_bound(int len) { + int a = 128 + (len * 110) / 100; + int b = 128 + len + ((len / (31 * 1024)) + 1) * 5; + return (a > b) ? a : b; +} +#endif /* SDEFL_IMPLEMENTATION */ + diff --git a/raylib/external/sinfl.h b/raylib/external/sinfl.h new file mode 100644 index 0000000..37c1aae --- /dev/null +++ b/raylib/external/sinfl.h @@ -0,0 +1,464 @@ +/* +# Small Deflate +`sdefl` is a small bare bone lossless compression library in ANSI C (ISO C90) +which implements the Deflate (RFC 1951) compressed data format specification standard. +It is mainly tuned to get as much speed and compression ratio from as little code +as needed to keep the implementation as concise as possible. + +## Features +- Portable single header and source file duo written in ANSI C (ISO C90) +- Dual license with either MIT or public domain +- Small implementation + - Deflate: 525 LoC + - Inflate: 320 LoC +- Webassembly: + - Deflate ~3.7 KB (~2.2KB compressed) + - Inflate ~3.6 KB (~2.2KB compressed) + +## Usage: +This file behaves differently depending on what symbols you define +before including it. + +Header-File mode: +If you do not define `SINFL_IMPLEMENTATION` before including this file, it +will operate in header only mode. In this mode it declares all used structs +and the API of the library without including the implementation of the library. + +Implementation mode: +If you define `SINFL_IMPLEMENTATION` before including this file, it will +compile the implementation. Make sure that you only include +this file implementation in *one* C or C++ file to prevent collisions. + +### Benchmark + +| Compressor name | Compression| Decompress.| Compr. size | Ratio | +| ------------------------| -----------| -----------| ----------- | ----- | +| sdefl 1.0 -0 | 127 MB/s | 233 MB/s | 40004116 | 39.88 | +| sdefl 1.0 -1 | 111 MB/s | 259 MB/s | 38940674 | 38.82 | +| sdefl 1.0 -5 | 45 MB/s | 275 MB/s | 36577183 | 36.46 | +| sdefl 1.0 -7 | 38 MB/s | 276 MB/s | 36523781 | 36.41 | +| zlib 1.2.11 -1 | 72 MB/s | 307 MB/s | 42298774 | 42.30 | +| zlib 1.2.11 -6 | 24 MB/s | 313 MB/s | 36548921 | 36.55 | +| zlib 1.2.11 -9 | 20 MB/s | 314 MB/s | 36475792 | 36.48 | +| miniz 1.0 -1 | 122 MB/s | 208 MB/s | 48510028 | 48.51 | +| miniz 1.0 -6 | 27 MB/s | 260 MB/s | 36513697 | 36.51 | +| miniz 1.0 -9 | 23 MB/s | 261 MB/s | 36460101 | 36.46 | +| libdeflate 1.3 -1 | 147 MB/s | 667 MB/s | 39597378 | 39.60 | +| libdeflate 1.3 -6 | 69 MB/s | 689 MB/s | 36648318 | 36.65 | +| libdeflate 1.3 -9 | 13 MB/s | 672 MB/s | 35197141 | 35.20 | +| libdeflate 1.3 -12 | 8.13 MB/s | 670 MB/s | 35100568 | 35.10 | + +### Compression +Results on the [Silesia compression corpus](http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia): + +| File | Original | `sdefl 0` | `sdefl 5` | `sdefl 7` | +| :------ | ---------: | -----------------: | ---------: | ----------: | +| dickens | 10.192.446 | 4,260,187| 3,845,261| 3,833,657 | +| mozilla | 51.220.480 | 20,774,706 | 19,607,009 | 19,565,867 | +| mr | 9.970.564 | 3,860,531 | 3,673,460 | 3,665,627 | +| nci | 33.553.445 | 4,030,283 | 3,094,526 | 3,006,075 | +| ooffice | 6.152.192 | 3,320,063 | 3,186,373 | 3,183,815 | +| osdb | 10.085.684 | 3,919,646 | 3,649,510 | 3,649,477 | +| reymont | 6.627.202 | 2,263,378 | 1,857,588 | 1,827,237 | +| samba | 21.606.400 | 6,121,797 | 5,462,670 | 5,450,762 | +| sao | 7.251.944 | 5,612,421 | 5,485,380 | 5,481,765 | +| webster | 41.458.703 | 13,972,648 | 12,059,432 | 11,991,421 | +| xml | 5.345.280 | 886,620| 674,009 | 662,141 | +| x-ray | 8.474.240 | 6,304,655 | 6,244,779 | 6,244,779 | + +## License +``` +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2020 Micha Mettke +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +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 OR COPYRIGHT HOLDERS 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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +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. +------------------------------------------------------------------------------ +``` +*/ +#ifndef SINFL_H_INCLUDED +#define SINFL_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +#define SINFL_PRE_TBL_SIZE 128 +#define SINFL_LIT_TBL_SIZE 1334 +#define SINFL_OFF_TBL_SIZE 402 + +struct sinfl { + int bits, bitcnt; + unsigned lits[SINFL_LIT_TBL_SIZE]; + unsigned dsts[SINFL_OFF_TBL_SIZE]; +}; +extern int sinflate(void *out, const void *in, int size); +extern int zsinflate(void *out, const void *in, int size); + +#ifdef __cplusplus +} +#endif + +#endif /* SINFL_H_INCLUDED */ + +#ifdef SINFL_IMPLEMENTATION + +#include /* memcpy, memset */ + +static int +sinfl_bsr(unsigned n) { +#ifdef _MSC_VER + _BitScanReverse(&n, n); + return n; +#elif defined(__GNUC__) || defined(__clang__) + return 31 - __builtin_clz(n); +#endif +} +static int +sinfl_get(const unsigned char **src, const unsigned char *end, struct sinfl *s, + int n) { + const unsigned char *in = *src; + int v = s->bits & ((1 << n)-1); + s->bits >>= n; + s->bitcnt = s->bitcnt - n; + s->bitcnt = s->bitcnt < 0 ? 0 : s->bitcnt; + while (s->bitcnt < 16 && in < end) { + s->bits |= (*in++) << s->bitcnt; + s->bitcnt += 8; + } + *src = in; + return v; +} +struct sinfl_gen { + int len; + int cnt; + int word; + short* sorted; +}; +static int +sinfl_build_tbl(struct sinfl_gen *gen, unsigned *tbl, int tbl_bits, + const int *cnt) { + int tbl_end = 0; + while (!(gen->cnt = cnt[gen->len])) { + ++gen->len; + } + tbl_end = 1 << gen->len; + while (gen->len <= tbl_bits) { + do {unsigned bit = 0; + tbl[gen->word] = (*gen->sorted++ << 16) | gen->len; + if (gen->word == tbl_end - 1) { + for (; gen->len < tbl_bits; gen->len++) { + memcpy(&tbl[tbl_end], tbl, (size_t)tbl_end * sizeof(tbl[0])); + tbl_end <<= 1; + } + return 1; + } + bit = 1 << sinfl_bsr((unsigned)(gen->word ^ (tbl_end - 1))); + gen->word &= bit - 1; + gen->word |= bit; + } while (--gen->cnt); + do { + if (++gen->len <= tbl_bits) { + memcpy(&tbl[tbl_end], tbl, (size_t)tbl_end * sizeof(tbl[0])); + tbl_end <<= 1; + } + } while (!(gen->cnt = cnt[gen->len])); + } + return 0; +} +static void +sinfl_build_subtbl(struct sinfl_gen *gen, unsigned *tbl, int tbl_bits, + const int *cnt) { + int sub_bits = 0; + int sub_start = 0; + int sub_prefix = -1; + int tbl_end = 1 << tbl_bits; + while (1) { + unsigned entry; + int bit, stride, i; + /* start new subtable */ + if ((gen->word & ((1 << tbl_bits)-1)) != sub_prefix) { + int used = 0; + sub_prefix = gen->word & ((1 << tbl_bits)-1); + sub_start = tbl_end; + sub_bits = gen->len - tbl_bits; + used = gen->cnt; + while (used < (1 << sub_bits)) { + sub_bits++; + used = (used << 1) + cnt[tbl_bits + sub_bits]; + } + tbl_end = sub_start + (1 << sub_bits); + tbl[sub_prefix] = (sub_start << 16) | 0x10 | (sub_bits & 0xf); + } + /* fill subtable */ + entry = (*gen->sorted << 16) | ((gen->len - tbl_bits) & 0xf); + gen->sorted++; + i = sub_start + (gen->word >> tbl_bits); + stride = 1 << (gen->len - tbl_bits); + do { + tbl[i] = entry; + i += stride; + } while (i < tbl_end); + if (gen->word == (1 << gen->len)-1) { + return; + } + bit = 1 << sinfl_bsr(gen->word ^ ((1 << gen->len) - 1)); + gen->word &= bit - 1; + gen->word |= bit; + gen->cnt--; + while (!gen->cnt) { + gen->cnt = cnt[++gen->len]; + } + } +} +static void +sinfl_build(unsigned *tbl, unsigned char *lens, int tbl_bits, int maxlen, + int symcnt) { + int i, used = 0; + short sort[288]; + int cnt[16] = {0}, off[16]= {0}; + struct sinfl_gen gen = {0}; + gen.sorted = sort; + gen.len = 1; + + for (i = 0; i < symcnt; ++i) + cnt[lens[i]]++; + off[1] = cnt[0]; + for (i = 1; i < maxlen; ++i) { + off[i + 1] = off[i] + cnt[i]; + used = (used << 1) + cnt[i]; + } + used = (used << 1) + cnt[i]; + for (i = 0; i < symcnt; ++i) + gen.sorted[off[lens[i]]++] = (short)i; + gen.sorted += off[0]; + + if (used < (1 << maxlen)){ + for (i = 0; i < 1 << tbl_bits; ++i) + tbl[i] = (0 << 16u) | 1; + return; + } + if (!sinfl_build_tbl(&gen, tbl, tbl_bits, cnt)){ + sinfl_build_subtbl(&gen, tbl, tbl_bits, cnt); + } +} +static int +sinfl_decode(const unsigned char **in, const unsigned char *end, + struct sinfl *s, const unsigned *tbl, int bit_len) { + int idx = s->bits & ((1 << bit_len) - 1); + unsigned key = tbl[idx]; + if (key & 0x10) { + /* sub-table lookup */ + int len = key & 0x0f; + sinfl_get(in, end, s, bit_len); + idx = s->bits & ((1 << len)-1); + key = tbl[((key >> 16) & 0xffff) + (unsigned)idx]; + } + sinfl_get(in, end, s, key & 0x0f); + return (key >> 16) & 0x0fff; +} +static int +sinfl_decompress(unsigned char *out, const unsigned char *in, int size) { + static const unsigned char order[] = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; + static const short dbase[30+2] = {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}; + static const unsigned char dbits[30+2] = {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,0,0}; + static const short lbase[29+2] = {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 unsigned char lbits[29+2] = {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}; + + const unsigned char *e = in + size, *o = out; + enum sinfl_states {hdr,stored,fixed,dyn,blk}; + enum sinfl_states state = hdr; + struct sinfl s = {0}; + int last = 0; + + sinfl_get(&in,e,&s,0); /* buffer input */ + while (in < e || s.bitcnt) { + switch (state) { + case hdr: { + int type = 0; /* block header */ + last = sinfl_get(&in,e,&s,1); + type = sinfl_get(&in,e,&s,2); + + switch (type) {default: return (int)(out-o); + case 0x00: state = stored; break; + case 0x01: state = fixed; break; + case 0x02: state = dyn; break;} + } break; + case stored: { + int len; /* uncompressed block */ + sinfl_get(&in,e,&s,s.bitcnt & 7); + len = sinfl_get(&in,e,&s,16); + //int nlen = sinfl_get(&in,e,&s,16); + in -= 2; s.bitcnt = 0; + + if (len > (e-in) || !len) + return (int)(out-o); + memcpy(out, in, (size_t)len); + in += len, out += len; + state = hdr; + } break; + case fixed: { + /* fixed huffman codes */ + int n; unsigned char lens[288+32]; + for (n = 0; n <= 143; n++) lens[n] = 8; + for (n = 144; n <= 255; n++) lens[n] = 9; + for (n = 256; n <= 279; n++) lens[n] = 7; + for (n = 280; n <= 287; n++) lens[n] = 8; + for (n = 0; n < 32; n++) lens[288+n] = 5; + + /* build lit/dist tables */ + sinfl_build(s.lits, lens, 10, 15, 288); + sinfl_build(s.dsts, lens + 288, 8, 15, 32); + state = blk; + } break; + case dyn: { + /* dynamic huffman codes */ + int n, i; + unsigned hlens[SINFL_PRE_TBL_SIZE]; + unsigned char nlens[19] = {0}, lens[288+32]; + int nlit = 257 + sinfl_get(&in,e,&s,5); + int ndist = 1 + sinfl_get(&in,e,&s,5); + int nlen = 4 + sinfl_get(&in,e,&s,4); + for (n = 0; n < nlen; n++) + nlens[order[n]] = (unsigned char)sinfl_get(&in,e,&s,3); + sinfl_build(hlens, nlens, 7, 7, 19); + + /* decode code lengths */ + for (n = 0; n < nlit + ndist;) { + int sym = sinfl_decode(&in, e, &s, hlens, 7); + switch (sym) {default: lens[n++] = (unsigned char)sym; break; + case 16: for (i=3+sinfl_get(&in,e,&s,2);i;i--,n++) lens[n]=lens[n-1]; break; + case 17: for (i=3+sinfl_get(&in,e,&s,3);i;i--,n++) lens[n]=0; break; + case 18: for (i=11+sinfl_get(&in,e,&s,7);i;i--,n++) lens[n]=0; break;} + } + /* build lit/dist tables */ + sinfl_build(s.lits, lens, 10, 15, nlit); + sinfl_build(s.dsts, lens + nlit, 8, 15, ndist); + state = blk; + } break; + case blk: { + /* decompress block */ + int i, sym = sinfl_decode(&in, e, &s, s.lits, 10); + if (sym > 256) {sym -= 257; /* match symbol */ + {int len = sinfl_get(&in, e, &s, lbits[sym]) + lbase[sym]; + int dsym = sinfl_decode(&in, e, &s, s.dsts, 8); + int offs = sinfl_get(&in, e, &s, dbits[dsym]) + dbase[dsym]; + if (offs > (int)(out-o)) { + return (int)(out-o); + } else if (offs == 1) { + /* rle match copying */ + unsigned char c = *(out - offs); + unsigned long w = (c << 24) | (c << 16) | (c << 8) | c; + for (i = 0; i < len >> 2; ++i) { + memcpy(out, &w, 4); + out += 4; + } + len = len & 3; + } else if (offs >= 4) { + /* copy match */ + int wcnt = len >> 2; + for (i = 0; i < wcnt; ++i) { + unsigned long w = 0; + memcpy(&w, out - offs, 4); + memcpy(out, &w, 4); + out += 4; + } + len = len & 3; + } + for (i = 0; i < len; ++i) + {*out = *(out-offs), out++;} + } + } else if (sym == 256) { + /* end of block */ + if (last) return (int)(out-o); + state = hdr; + break; + /* literal */ + } else *out++ = (unsigned char)sym; + } break;} + } + return (int)(out-o); +} +extern int +sinflate(void *out, const void *in, int size) { + return sinfl_decompress((unsigned char*)out, (const unsigned char*)in, size); +} +static unsigned +sinfl_adler32(unsigned adler32, const unsigned char *in, int in_len) { + const unsigned ADLER_MOD = 65521; + unsigned s1 = adler32 & 0xffff; + unsigned s2 = adler32 >> 16; + unsigned blk_len, i; + + blk_len = in_len % 5552; + while (in_len) { + for (i=0; i + 7 < blk_len; i += 8) { + s1 += in[0]; s2 += s1; + s1 += in[1]; s2 += s1; + s1 += in[2]; s2 += s1; + s1 += in[3]; s2 += s1; + s1 += in[4]; s2 += s1; + s1 += in[5]; s2 += s1; + s1 += in[6]; s2 += s1; + s1 += in[7]; s2 += s1; + in += 8; + } + for (; i < blk_len; ++i) + s1 += *in++, s2 += s1; + s1 %= ADLER_MOD; s2 %= ADLER_MOD; + in_len -= blk_len; + blk_len = 5552; + } return (unsigned)(s2 << 16) + (unsigned)s1; +} +extern int +zsinflate(void *out, const void *mem, int size) { + const unsigned char *in = (const unsigned char*)mem; + if (size >= 6) { + const unsigned char *eob = in + size - 4; + int n = sinfl_decompress((unsigned char*)out, in + 2u, size); + unsigned a = sinfl_adler32(1u, (unsigned char*)out, n); + unsigned h = eob[0] << 24 | eob[1] << 16 | eob[2] << 8 | eob[3] << 0; + return a == h ? n : -1; + } else { + return -1; + } +} +#endif + diff --git a/raylib/external/stb_image.h b/raylib/external/stb_image.h index 2857f05..accef48 100644 --- a/raylib/external/stb_image.h +++ b/raylib/external/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.25 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.26 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,7 @@ LICENSE RECENT REVISION HISTORY: + 2.26 (2020-07-13) many minor fixes 2.25 (2020-02-02) fix warnings 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically 2.23 (2019-08-11) fix clang static analysis warning @@ -93,22 +94,30 @@ RECENT REVISION HISTORY: Carmelo J Fdez-Aguera Bug & warning fixes - Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan - Dave Moore Roy Eltham Hayaki Saito Nathan Reed - Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar - Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex - Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 - Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus - Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo - Christian Floisand Kevin Schmidt JR Smith github:darealshinji - Brad Weinberger Matvey Cherevko github:Michaelangel007 - Blazej Dariusz Roszkowski Alexander Veselov + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko [reserved] + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. */ #ifndef STBI_INCLUDE_STB_IMAGE_H @@ -318,7 +327,14 @@ RECENT REVISION HISTORY: // - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still // want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB // - +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. #ifndef STBI_NO_STDIO #include @@ -574,13 +590,19 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #ifndef STBI_NO_THREAD_LOCALS #if defined(__cplusplus) && __cplusplus >= 201103L #define STBI_THREAD_LOCAL thread_local - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define STBI_THREAD_LOCAL _Thread_local - #elif defined(__GNUC__) + #elif defined(__GNUC__) && __GNUC__ < 5 #define STBI_THREAD_LOCAL __thread #elif defined(_MSC_VER) #define STBI_THREAD_LOCAL __declspec(thread) -#endif + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif #endif #ifdef _MSC_VER @@ -734,6 +756,10 @@ static int stbi__sse2_available(void) #define STBI_SIMD_ALIGN(type, name) type name #endif +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + /////////////////////////////////////////////// // // stbi__context struct and start_xxx functions @@ -751,6 +777,7 @@ typedef struct int read_from_callbacks; int buflen; stbi_uc buffer_start[128]; + int callback_already_read; stbi_uc *img_buffer, *img_buffer_end; stbi_uc *img_buffer_original, *img_buffer_original_end; @@ -764,6 +791,7 @@ static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) { s->io.read = NULL; s->read_from_callbacks = 0; + s->callback_already_read = 0; s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; } @@ -775,7 +803,8 @@ static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void * s->io_user_data = user; s->buflen = sizeof(s->buffer_start); s->read_from_callbacks = 1; - s->img_buffer_original = s->buffer_start; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; stbi__refill_buffer(s); s->img_buffer_original_end = s->img_buffer_end; } @@ -789,12 +818,17 @@ static int stbi__stdio_read(void *user, char *data, int size) static void stbi__stdio_skip(void *user, int n) { + int ch; fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } } static int stbi__stdio_eof(void *user) { - return feof((FILE*) user); + return feof((FILE*) user) || ferror((FILE *) user); } static stbi_io_callbacks stbi__stdio_callbacks = @@ -1171,8 +1205,10 @@ static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, if (result == NULL) return NULL; + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + if (ri.bits_per_channel != 8) { - STBI_ASSERT(ri.bits_per_channel == 16); result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); ri.bits_per_channel = 8; } @@ -1195,8 +1231,10 @@ static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, if (result == NULL) return NULL; + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + if (ri.bits_per_channel != 16) { - STBI_ASSERT(ri.bits_per_channel == 8); result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); ri.bits_per_channel = 16; } @@ -1499,6 +1537,7 @@ enum static void stbi__refill_buffer(stbi__context *s) { int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); if (n == 0) { // at end of file, treat same as if from memory, but need to handle case // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file @@ -1544,6 +1583,7 @@ stbi_inline static int stbi__at_eof(stbi__context *s) #else static void stbi__skip(stbi__context *s, int n) { + if (n == 0) return; // already there! if (n < 0) { s->img_buffer = s->img_buffer_end; return; @@ -1686,7 +1726,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); } #undef STBI__CASE } @@ -1743,7 +1783,7 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); } #undef STBI__CASE } @@ -2052,7 +2092,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB k = stbi_lrot(j->code_buffer, n); - STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + if (n < 0 || n >= (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))) return 0; j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; @@ -2163,6 +2203,7 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__ // first scan for DC coefficient, must be first memset(data,0,64*sizeof(data[0])); // 0 all the ac values now t = stbi__jpeg_huff_decode(j, hdc); + if (t == -1) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); diff = t ? stbi__extend_receive(j, t) : 0; dc = j->img_comp[b].dc_pred + diff; @@ -3153,6 +3194,8 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); c = stbi__get8(s); if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); s->img_n = c; @@ -4033,16 +4076,23 @@ typedef struct stbi__zhuffman z_length, z_distance; } stbi__zbuf; +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) { - if (z->zbuffer >= z->zbuffer_end) return 0; - return *z->zbuffer++; + return stbi__zeof(z) ? 0 : *z->zbuffer++; } static void stbi__fill_bits(stbi__zbuf *z) { do { - STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; z->num_bits += 8; } while (z->num_bits <= 24); @@ -4067,10 +4117,11 @@ static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) for (s=STBI__ZFAST_BITS+1; ; ++s) if (k < z->maxcode[s]) break; - if (s == 16) return -1; // invalid code! + if (s >= 16) return -1; // invalid code! // code size is s, so: b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - STBI_ASSERT(z->size[b] == s); + if (b >= sizeof (z->size)) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. a->code_buffer >>= s; a->num_bits -= s; return z->value[b]; @@ -4079,7 +4130,12 @@ static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) { int b,s; - if (a->num_bits < 16) stbi__fill_bits(a); + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; if (b) { s = b >> 9; @@ -4093,13 +4149,16 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes { char *q; - int cur, limit, old_limit; + unsigned int cur, limit, old_limit; z->zout = zout; if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); - cur = (int) (z->zout - z->zout_start); - limit = old_limit = (int) (z->zout_end - z->zout_start); - while (cur + n > limit) + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); limit *= 2; + } q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); STBI_NOTUSED(old_limit); if (q == NULL) return stbi__err("outofmem", "Out of memory"); @@ -4197,11 +4256,12 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) c = stbi__zreceive(a,2)+3; if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); fill = lencodes[n-1]; - } else if (c == 17) + } else if (c == 17) { c = stbi__zreceive(a,3)+3; - else { - STBI_ASSERT(c == 18); + } else if (c == 18) { c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); } if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); memset(lencodes+n, fill, c); @@ -4227,7 +4287,7 @@ static int stbi__parse_uncompressed_block(stbi__zbuf *a) a->code_buffer >>= 8; a->num_bits -= 8; } - STBI_ASSERT(a->num_bits == 0); + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); // now fill header the normal way while (k < 4) header[k++] = stbi__zget8(a); @@ -4249,6 +4309,7 @@ static int stbi__parse_zlib_header(stbi__zbuf *a) int cm = cmf & 15; /* int cinfo = cmf >> 4; */ int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png @@ -4510,7 +4571,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r return stbi__err("invalid filter","Corrupt PNG"); if (depth < 8) { - STBI_ASSERT(img_width_bytes <= x); + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place filter_bytes = 1; width = img_width_bytes; @@ -4905,8 +4966,10 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); first = 0; if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); - s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); @@ -5055,10 +5118,12 @@ static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, st void *result=NULL; if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) + if (p->depth <= 8) ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; else - ri->bits_per_channel = p->depth; + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { @@ -5219,6 +5284,8 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) info->mr = info->mg = info->mb = info->ma = 0; info->extra_read = 14; + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); if (hsz == 12) { s->img_x = stbi__get16le(s); @@ -5310,6 +5377,9 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req flip_vertically = ((int) s->img_y) > 0; s->img_y = abs((int) s->img_y); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + mr = info.mr; mg = info.mg; mb = info.mb; @@ -5324,7 +5394,10 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req psize = (info.offset - info.extra_read - info.hsz) >> 2; } if (psize == 0) { - STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start)); + STBI_ASSERT(info.offset == s->callback_already_read + (int) (s->img_buffer - s->img_buffer_original)); + if (info.offset != s->callback_already_read + (s->img_buffer - s->buffer_start)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } } if (info.bpp == 24 && ma == 0xff000000) @@ -5419,6 +5492,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } } for (j=0; j < (int) s->img_y; ++j) { if (easy) { @@ -5643,6 +5717,9 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req STBI_NOTUSED(tga_x_origin); // @TODO STBI_NOTUSED(tga_y_origin); // @TODO + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + // do a tiny bit of precessing if ( tga_image_type >= 8 ) { @@ -5682,6 +5759,11 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req // do I need to load a palette? if ( tga_indexed) { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + // any data to skip? (offset usually = 0) stbi__skip(s, tga_palette_start ); // load the palette @@ -5890,6 +5972,9 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req h = stbi__get32be(s); w = stbi__get32be(s); + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + // Make sure the depth is 8 bits. bitdepth = stbi__get16be(s); if (bitdepth != 8 && bitdepth != 16) @@ -6244,6 +6329,10 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c x = stbi__get16be(s); y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); @@ -6352,6 +6441,9 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in g->ratio = stbi__get8(s); g->transparent = -1; + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments if (is_info) return 1; @@ -6529,7 +6621,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i memset(g->history, 0x00, pcount); // pixels that were affected previous frame first_frame = 1; } else { - // second frame - how do we dispoase of the previous one? + // second frame - how do we dispose of the previous one? dispose = (g->eflags & 0x1C) >> 2; pcount = g->w * g->h; @@ -6683,6 +6775,8 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, stbi_uc *two_back = 0; stbi__gif g; int stride; + int out_size = 0; + int delays_size = 0; memset(&g, 0, sizeof(g)); if (delays) { *delays = 0; @@ -6699,22 +6793,28 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, stride = g.w * g.h * 4; if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride ); + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); if (NULL == tmp) { STBI_FREE(g.out); STBI_FREE(g.history); STBI_FREE(g.background); return stbi__errpuc("outofmem", "Out of memory"); } - else - out = (stbi_uc*) tmp; + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + if (delays) { - *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + *delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + delays_size = layers * sizeof(int); } } else { out = (stbi_uc*)stbi__malloc( layers * stride ); + out_size = layers * stride; if (delays) { *delays = (int*) stbi__malloc( layers * sizeof(int) ); + delays_size = layers * sizeof(int); } } memcpy( out + ((layers - 1) * stride), u, stride ); @@ -6893,6 +6993,9 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re token += 3; width = (int) strtol(token, NULL, 10); + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + *x = width; *y = height; @@ -7207,6 +7310,9 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) return 0; + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + *x = s->img_x; *y = s->img_y; if (comp) *comp = s->img_n; diff --git a/raylib/external/stb_image_write.h b/raylib/external/stb_image_write.h index cffd473..95943eb 100644 --- a/raylib/external/stb_image_write.h +++ b/raylib/external/stb_image_write.h @@ -1,4 +1,4 @@ -/* stb_image_write - v1.14 - public domain - http://nothings.org/stb +/* stb_image_write - v1.15 - public domain - http://nothings.org/stb writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 no warranty implied; use at your own risk @@ -267,6 +267,8 @@ typedef struct { stbi_write_func *func; void *context; + unsigned char buffer[64]; + int buf_used; } stbi__write_context; // initialize a callback-based context @@ -380,16 +382,36 @@ static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) va_end(v); } +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + static void stbiw__putc(stbi__write_context *s, unsigned char c) { s->func(s->context, &c, 1); } +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if (s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) { - unsigned char arr[3]; - arr[0] = a; arr[1] = b; arr[2] = c; - s->func(s->context, arr, 3); + int n; + if (s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; } static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) @@ -398,7 +420,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in int k; if (write_alpha < 0) - s->func(s->context, &d[comp - 1], 1); + stbiw__write1(s, d[comp - 1]); switch (comp) { case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case @@ -406,7 +428,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in if (expand_mono) stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp else - s->func(s->context, d, 1); // monochrome TGA + stbiw__write1(s, d[0]); // monochrome TGA break; case 4: if (!write_alpha) { @@ -422,7 +444,7 @@ static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, in break; } if (write_alpha > 0) - s->func(s->context, &d[comp - 1], 1); + stbiw__write1(s, d[comp - 1]); } static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) @@ -447,6 +469,7 @@ static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, i unsigned char *d = (unsigned char *) data + (j*x+i)*comp; stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); } + stbiw__write_flush(s); s->func(s->context, &zero, scanline_pad); } } @@ -476,7 +499,7 @@ static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, c STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) { - stbi__write_context s; + stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_bmp_core(&s, x, y, comp, data); } @@ -484,7 +507,7 @@ STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) { - stbi__write_context s; + stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_bmp_core(&s, x, y, comp, data); stbi__end_write_file(&s); @@ -557,24 +580,25 @@ static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, v if (diff) { unsigned char header = STBIW_UCHAR(len - 1); - s->func(s->context, &header, 1); + stbiw__write1(s, header); for (k = 0; k < len; ++k) { stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); } } else { unsigned char header = STBIW_UCHAR(len - 129); - s->func(s->context, &header, 1); + stbiw__write1(s, header); stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); } } } + stbiw__write_flush(s); } return 1; } STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) { - stbi__write_context s; + stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_tga_core(&s, x, y, comp, (void *) data); } @@ -582,7 +606,7 @@ STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) { - stbi__write_context s; + stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); stbi__end_write_file(&s); @@ -748,7 +772,7 @@ static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, f STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) { - stbi__write_context s; + stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_hdr_core(&s, x, y, comp, (float *) data); } @@ -756,7 +780,7 @@ STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) { - stbi__write_context s; + stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); stbi__end_write_file(&s); @@ -1552,7 +1576,7 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) { - stbi__write_context s; + stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); } @@ -1561,7 +1585,7 @@ STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, #ifndef STBI_WRITE_NO_STDIO STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) { - stbi__write_context s; + stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); stbi__end_write_file(&s); diff --git a/raylib/external/stb_vorbis.c b/raylib/external/stb_vorbis.c deleted file mode 100644 index c847242..0000000 --- a/raylib/external/stb_vorbis.c +++ /dev/null @@ -1,5077 +0,0 @@ -#include "stb_vorbis.h" - -#ifndef STB_VORBIS_HEADER_ONLY - -// global configuration settings (e.g. set these in the project/makefile), -// or just set them in this file at the top (although ideally the first few -// should be visible when the header file is compiled too, although it's not -// crucial) - -// STB_VORBIS_NO_PUSHDATA_API -// does not compile the code for the various stb_vorbis_*_pushdata() -// functions -// #define STB_VORBIS_NO_PUSHDATA_API - -// STB_VORBIS_NO_PULLDATA_API -// does not compile the code for the non-pushdata APIs -// #define STB_VORBIS_NO_PULLDATA_API - -// STB_VORBIS_NO_STDIO -// does not compile the code for the APIs that use FILE *s internally -// or externally (implied by STB_VORBIS_NO_PULLDATA_API) -// #define STB_VORBIS_NO_STDIO - -// STB_VORBIS_NO_INTEGER_CONVERSION -// does not compile the code for converting audio sample data from -// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) -// #define STB_VORBIS_NO_INTEGER_CONVERSION - -// STB_VORBIS_NO_FAST_SCALED_FLOAT -// does not use a fast float-to-int trick to accelerate float-to-int on -// most platforms which requires endianness be defined correctly. -//#define STB_VORBIS_NO_FAST_SCALED_FLOAT - - -// STB_VORBIS_MAX_CHANNELS [number] -// globally define this to the maximum number of channels you need. -// The spec does not put a restriction on channels except that -// the count is stored in a byte, so 255 is the hard limit. -// Reducing this saves about 16 bytes per value, so using 16 saves -// (255-16)*16 or around 4KB. Plus anything other memory usage -// I forgot to account for. Can probably go as low as 8 (7.1 audio), -// 6 (5.1 audio), or 2 (stereo only). -#ifndef STB_VORBIS_MAX_CHANNELS -#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? -#endif - -// STB_VORBIS_PUSHDATA_CRC_COUNT [number] -// after a flush_pushdata(), stb_vorbis begins scanning for the -// next valid page, without backtracking. when it finds something -// that looks like a page, it streams through it and verifies its -// CRC32. Should that validation fail, it keeps scanning. But it's -// possible that _while_ streaming through to check the CRC32 of -// one candidate page, it sees another candidate page. This #define -// determines how many "overlapping" candidate pages it can search -// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas -// garbage pages could be as big as 64KB, but probably average ~16KB. -// So don't hose ourselves by scanning an apparent 64KB page and -// missing a ton of real ones in the interim; so minimum of 2 -#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT -#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 -#endif - -// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] -// sets the log size of the huffman-acceleration table. Maximum -// supported value is 24. with larger numbers, more decodings are O(1), -// but the table size is larger so worse cache missing, so you'll have -// to probe (and try multiple ogg vorbis files) to find the sweet spot. -#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH -#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 -#endif - -// STB_VORBIS_FAST_BINARY_LENGTH [number] -// sets the log size of the binary-search acceleration table. this -// is used in similar fashion to the fast-huffman size to set initial -// parameters for the binary search - -// STB_VORBIS_FAST_HUFFMAN_INT -// The fast huffman tables are much more efficient if they can be -// stored as 16-bit results instead of 32-bit results. This restricts -// the codebooks to having only 65535 possible outcomes, though. -// (At least, accelerated by the huffman table.) -#ifndef STB_VORBIS_FAST_HUFFMAN_INT -#define STB_VORBIS_FAST_HUFFMAN_SHORT -#endif - -// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH -// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls -// back on binary searching for the correct one. This requires storing -// extra tables with the huffman codes in sorted order. Defining this -// symbol trades off space for speed by forcing a linear search in the -// non-fast case, except for "sparse" codebooks. -// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH - -// STB_VORBIS_DIVIDES_IN_RESIDUE -// stb_vorbis precomputes the result of the scalar residue decoding -// that would otherwise require a divide per chunk. you can trade off -// space for time by defining this symbol. -// #define STB_VORBIS_DIVIDES_IN_RESIDUE - -// STB_VORBIS_DIVIDES_IN_CODEBOOK -// vorbis VQ codebooks can be encoded two ways: with every case explicitly -// stored, or with all elements being chosen from a small range of values, -// and all values possible in all elements. By default, stb_vorbis expands -// this latter kind out to look like the former kind for ease of decoding, -// because otherwise an integer divide-per-vector-element is required to -// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can -// trade off storage for speed. -//#define STB_VORBIS_DIVIDES_IN_CODEBOOK - -#ifdef STB_VORBIS_CODEBOOK_SHORTS -#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" -#endif - -// STB_VORBIS_DIVIDE_TABLE -// this replaces small integer divides in the floor decode loop with -// table lookups. made less than 1% difference, so disabled by default. - -// STB_VORBIS_NO_INLINE_DECODE -// disables the inlining of the scalar codebook fast-huffman decode. -// might save a little codespace; useful for debugging -// #define STB_VORBIS_NO_INLINE_DECODE - -// STB_VORBIS_NO_DEFER_FLOOR -// Normally we only decode the floor without synthesizing the actual -// full curve. We can instead synthesize the curve immediately. This -// requires more memory and is very likely slower, so I don't think -// you'd ever want to do it except for debugging. -// #define STB_VORBIS_NO_DEFER_FLOOR - - - - -////////////////////////////////////////////////////////////////////////////// - -#ifdef STB_VORBIS_NO_PULLDATA_API - #define STB_VORBIS_NO_INTEGER_CONVERSION - #define STB_VORBIS_NO_STDIO -#endif - -#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) - #define STB_VORBIS_NO_STDIO 1 -#endif - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT - - // only need endianness for fast-float-to-int, which we don't - // use for pushdata - - #ifndef STB_VORBIS_BIG_ENDIAN - #define STB_VORBIS_ENDIAN 0 - #else - #define STB_VORBIS_ENDIAN 1 - #endif - -#endif -#endif - - -#ifndef STB_VORBIS_NO_STDIO -#include -#endif - -#ifndef STB_VORBIS_NO_CRT - #include - #include - #include - #include - - // find definition of alloca if it's not in stdlib.h: - #if defined(_MSC_VER) || defined(__MINGW32__) - #include - #endif - #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) - #include - #endif -#else // STB_VORBIS_NO_CRT - #define NULL 0 - #define malloc(s) 0 - #define free(s) ((void) 0) - #define realloc(s) 0 -#endif // STB_VORBIS_NO_CRT - -#include - -#ifdef __MINGW32__ - // eff you mingw: - // "fixed": - // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ - // "no that broke the build, reverted, who cares about C": - // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ - #ifdef __forceinline - #undef __forceinline - #endif - #define __forceinline - //#define alloca __builtin_alloca -#elif !defined(_MSC_VER) - #if __GNUC__ - #define __forceinline inline - #else - #define __forceinline - #endif -#endif - -#if STB_VORBIS_MAX_CHANNELS > 256 -#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" -#endif - -#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 -#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" -#endif - - -#if 0 -#include -#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) -#else -#define CHECK(f) ((void) 0) -#endif - -#define MAX_BLOCKSIZE_LOG 13 // from specification -#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) - - -typedef unsigned char uint8; -typedef signed char int8; -typedef unsigned short uint16; -typedef signed short int16; -typedef unsigned int uint32; -typedef signed int int32; - -#ifndef TRUE -#define TRUE 1 -#define FALSE 0 -#endif - -typedef float codetype; - -// @NOTE -// -// Some arrays below are tagged "//varies", which means it's actually -// a variable-sized piece of data, but rather than malloc I assume it's -// small enough it's better to just allocate it all together with the -// main thing -// -// Most of the variables are specified with the smallest size I could pack -// them into. It might give better performance to make them all full-sized -// integers. It should be safe to freely rearrange the structures or change -// the sizes larger--nothing relies on silently truncating etc., nor the -// order of variables. - -#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) -#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) - -typedef struct -{ - int dimensions, entries; - uint8 *codeword_lengths; - float minimum_value; - float delta_value; - uint8 value_bits; - uint8 lookup_type; - uint8 sequence_p; - uint8 sparse; - uint32 lookup_values; - codetype *multiplicands; - uint32 *codewords; - #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT - int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; - #else - int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; - #endif - uint32 *sorted_codewords; - int *sorted_values; - int sorted_entries; -} Codebook; - -typedef struct -{ - uint8 order; - uint16 rate; - uint16 bark_map_size; - uint8 amplitude_bits; - uint8 amplitude_offset; - uint8 number_of_books; - uint8 book_list[16]; // varies -} Floor0; - -typedef struct -{ - uint8 partitions; - uint8 partition_class_list[32]; // varies - uint8 class_dimensions[16]; // varies - uint8 class_subclasses[16]; // varies - uint8 class_masterbooks[16]; // varies - int16 subclass_books[16][8]; // varies - uint16 Xlist[31*8+2]; // varies - uint8 sorted_order[31*8+2]; - uint8 neighbors[31*8+2][2]; - uint8 floor1_multiplier; - uint8 rangebits; - int values; -} Floor1; - -typedef union -{ - Floor0 floor0; - Floor1 floor1; -} Floor; - -typedef struct -{ - uint32 begin, end; - uint32 part_size; - uint8 classifications; - uint8 classbook; - uint8 **classdata; - int16 (*residue_books)[8]; -} Residue; - -typedef struct -{ - uint8 magnitude; - uint8 angle; - uint8 mux; -} MappingChannel; - -typedef struct -{ - uint16 coupling_steps; - MappingChannel *chan; - uint8 submaps; - uint8 submap_floor[15]; // varies - uint8 submap_residue[15]; // varies -} Mapping; - -typedef struct -{ - uint8 blockflag; - uint8 mapping; - uint16 windowtype; - uint16 transformtype; -} Mode; - -typedef struct -{ - uint32 goal_crc; // expected crc if match - int bytes_left; // bytes left in packet - uint32 crc_so_far; // running crc - int bytes_done; // bytes processed in _current_ chunk - uint32 sample_loc; // granule pos encoded in page -} CRCscan; - -typedef struct -{ - uint32 page_start, page_end; - uint32 last_decoded_sample; -} ProbedPage; - -struct stb_vorbis -{ - // user-accessible info - unsigned int sample_rate; - int channels; - - unsigned int setup_memory_required; - unsigned int temp_memory_required; - unsigned int setup_temp_memory_required; - - // input config -#ifndef STB_VORBIS_NO_STDIO - FILE *f; - uint32 f_start; - int close_on_free; -#endif - - uint8 *stream; - uint8 *stream_start; - uint8 *stream_end; - - uint32 stream_len; - - uint8 push_mode; - - uint32 first_audio_page_offset; - - ProbedPage p_first, p_last; - - // memory management - stb_vorbis_alloc alloc; - int setup_offset; - int temp_offset; - - // run-time results - int eof; - enum STBVorbisError error; - - // user-useful data - - // header info - int blocksize[2]; - int blocksize_0, blocksize_1; - int codebook_count; - Codebook *codebooks; - int floor_count; - uint16 floor_types[64]; // varies - Floor *floor_config; - int residue_count; - uint16 residue_types[64]; // varies - Residue *residue_config; - int mapping_count; - Mapping *mapping; - int mode_count; - Mode mode_config[64]; // varies - - uint32 total_samples; - - // decode buffer - float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; - float *outputs [STB_VORBIS_MAX_CHANNELS]; - - float *previous_window[STB_VORBIS_MAX_CHANNELS]; - int previous_length; - - #ifndef STB_VORBIS_NO_DEFER_FLOOR - int16 *finalY[STB_VORBIS_MAX_CHANNELS]; - #else - float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; - #endif - - uint32 current_loc; // sample location of next frame to decode - int current_loc_valid; - - // per-blocksize precomputed data - - // twiddle factors - float *A[2],*B[2],*C[2]; - float *window[2]; - uint16 *bit_reverse[2]; - - // current page/packet/segment streaming info - uint32 serial; // stream serial number for verification - int last_page; - int segment_count; - uint8 segments[255]; - uint8 page_flag; - uint8 bytes_in_seg; - uint8 first_decode; - int next_seg; - int last_seg; // flag that we're on the last segment - int last_seg_which; // what was the segment number of the last seg? - uint32 acc; - int valid_bits; - int packet_bytes; - int end_seg_with_known_loc; - uint32 known_loc_for_packet; - int discard_samples_deferred; - uint32 samples_output; - - // push mode scanning - int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching -#ifndef STB_VORBIS_NO_PUSHDATA_API - CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; -#endif - - // sample-access - int channel_buffer_start; - int channel_buffer_end; -}; - -#if defined(STB_VORBIS_NO_PUSHDATA_API) - #define IS_PUSH_MODE(f) FALSE -#elif defined(STB_VORBIS_NO_PULLDATA_API) - #define IS_PUSH_MODE(f) TRUE -#else - #define IS_PUSH_MODE(f) ((f)->push_mode) -#endif - -typedef struct stb_vorbis vorb; - -static int error(vorb *f, enum STBVorbisError e) -{ - f->error = e; - if (!f->eof && e != VORBIS_need_more_data) { - f->error=e; // breakpoint for debugging - } - return 0; -} - - -// these functions are used for allocating temporary memory -// while decoding. if you can afford the stack space, use -// alloca(); otherwise, provide a temp buffer and it will -// allocate out of those. - -#define array_size_required(count,size) (count*(sizeof(void *)+(size))) - -#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) -#ifdef dealloca -#define temp_free(f,p) (f->alloc.alloc_buffer ? 0 : dealloca(size)) -#else -#define temp_free(f,p) 0 -#endif -#define temp_alloc_save(f) ((f)->temp_offset) -#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) - -#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) - -// given a sufficiently large block of memory, make an array of pointers to subblocks of it -static void *make_block_array(void *mem, int count, int size) -{ - int i; - void ** p = (void **) mem; - char *q = (char *) (p + count); - for (i=0; i < count; ++i) { - p[i] = q; - q += size; - } - return p; -} - -static void *setup_malloc(vorb *f, int sz) -{ - sz = (sz+3) & ~3; - f->setup_memory_required += sz; - if (f->alloc.alloc_buffer) { - void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; - if (f->setup_offset + sz > f->temp_offset) return NULL; - f->setup_offset += sz; - return p; - } - return sz ? malloc(sz) : NULL; -} - -static void setup_free(vorb *f, void *p) -{ - if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack - free(p); -} - -static void *setup_temp_malloc(vorb *f, int sz) -{ - sz = (sz+3) & ~3; - if (f->alloc.alloc_buffer) { - if (f->temp_offset - sz < f->setup_offset) return NULL; - f->temp_offset -= sz; - return (char *) f->alloc.alloc_buffer + f->temp_offset; - } - return malloc(sz); -} - -static void setup_temp_free(vorb *f, void *p, int sz) -{ - if (f->alloc.alloc_buffer) { - f->temp_offset += (sz+3)&~3; - return; - } - free(p); -} - -#define CRC32_POLY 0x04c11db7 // from spec - -static uint32 crc_table[256]; -static void crc32_init(void) -{ - int i,j; - uint32 s; - for(i=0; i < 256; i++) { - for (s=(uint32) i << 24, j=0; j < 8; ++j) - s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); - crc_table[i] = s; - } -} - -static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) -{ - return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; -} - - -// used in setup, and for huffman that doesn't go fast path -static unsigned int bit_reverse(unsigned int n) -{ - n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); - n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); - n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); - n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); - return (n >> 16) | (n << 16); -} - -static float square(float x) -{ - return x*x; -} - -// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 -// as required by the specification. fast(?) implementation from stb.h -// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup -static int ilog(int32 n) -{ - static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; - - if (n < 0) return 0; // signed n returns 0 - - // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) - if (n < (1 << 14)) - if (n < (1 << 4)) return 0 + log2_4[n ]; - else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; - else return 10 + log2_4[n >> 10]; - else if (n < (1 << 24)) - if (n < (1 << 19)) return 15 + log2_4[n >> 15]; - else return 20 + log2_4[n >> 20]; - else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; - else return 30 + log2_4[n >> 30]; -} - -#ifndef M_PI - #define M_PI 3.14159265358979323846264f // from CRC -#endif - -// code length assigned to a value with no huffman encoding -#define NO_CODE 255 - -/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// -// -// these functions are only called at setup, and only a few times -// per file - -static float float32_unpack(uint32 x) -{ - // from the specification - uint32 mantissa = x & 0x1fffff; - uint32 sign = x & 0x80000000; - uint32 exp = (x & 0x7fe00000) >> 21; - double res = sign ? -(double)mantissa : (double)mantissa; - return (float) ldexp((float)res, exp-788); -} - - -// zlib & jpeg huffman tables assume that the output symbols -// can either be arbitrarily arranged, or have monotonically -// increasing frequencies--they rely on the lengths being sorted; -// this makes for a very simple generation algorithm. -// vorbis allows a huffman table with non-sorted lengths. This -// requires a more sophisticated construction, since symbols in -// order do not map to huffman codes "in order". -static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) -{ - if (!c->sparse) { - c->codewords [symbol] = huff_code; - } else { - c->codewords [count] = huff_code; - c->codeword_lengths[count] = len; - values [count] = symbol; - } -} - -static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) -{ - int i,k,m=0; - uint32 available[32]; - - memset(available, 0, sizeof(available)); - // find the first entry - for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; - if (k == n) { assert(c->sorted_entries == 0); return TRUE; } - // add to the list - add_entry(c, 0, k, m++, len[k], values); - // add all available leaves - for (i=1; i <= len[k]; ++i) - available[i] = 1U << (32-i); - // note that the above code treats the first case specially, - // but it's really the same as the following code, so they - // could probably be combined (except the initial code is 0, - // and I use 0 in available[] to mean 'empty') - for (i=k+1; i < n; ++i) { - uint32 res; - int z = len[i], y; - if (z == NO_CODE) continue; - // find lowest available leaf (should always be earliest, - // which is what the specification calls for) - // note that this property, and the fact we can never have - // more than one free leaf at a given level, isn't totally - // trivial to prove, but it seems true and the assert never - // fires, so! - while (z > 0 && !available[z]) --z; - if (z == 0) { return FALSE; } - res = available[z]; - assert(z >= 0 && z < 32); - available[z] = 0; - add_entry(c, bit_reverse(res), i, m++, len[i], values); - // propogate availability up the tree - if (z != len[i]) { - assert(len[i] >= 0 && len[i] < 32); - for (y=len[i]; y > z; --y) { - assert(available[y] == 0); - available[y] = res + (1 << (32-y)); - } - } - } - return TRUE; -} - -// accelerated huffman table allows fast O(1) match of all symbols -// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH -static void compute_accelerated_huffman(Codebook *c) -{ - int i, len; - for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) - c->fast_huffman[i] = -1; - - len = c->sparse ? c->sorted_entries : c->entries; - #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT - if (len > 32767) len = 32767; // largest possible value we can encode! - #endif - for (i=0; i < len; ++i) { - if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { - uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; - // set table entries for all bit combinations in the higher bits - while (z < FAST_HUFFMAN_TABLE_SIZE) { - c->fast_huffman[z] = i; - z += 1 << c->codeword_lengths[i]; - } - } - } -} - -#ifdef _MSC_VER -#define STBV_CDECL __cdecl -#else -#define STBV_CDECL -#endif - -static int STBV_CDECL uint32_compare(const void *p, const void *q) -{ - uint32 x = * (uint32 *) p; - uint32 y = * (uint32 *) q; - return x < y ? -1 : x > y; -} - -static int include_in_sort(Codebook *c, uint8 len) -{ - if (c->sparse) { assert(len != NO_CODE); return TRUE; } - if (len == NO_CODE) return FALSE; - if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; - return FALSE; -} - -// if the fast table above doesn't work, we want to binary -// search them... need to reverse the bits -static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) -{ - int i, len; - // build a list of all the entries - // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. - // this is kind of a frivolous optimization--I don't see any performance improvement, - // but it's like 4 extra lines of code, so. - if (!c->sparse) { - int k = 0; - for (i=0; i < c->entries; ++i) - if (include_in_sort(c, lengths[i])) - c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); - assert(k == c->sorted_entries); - } else { - for (i=0; i < c->sorted_entries; ++i) - c->sorted_codewords[i] = bit_reverse(c->codewords[i]); - } - - qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); - c->sorted_codewords[c->sorted_entries] = 0xffffffff; - - len = c->sparse ? c->sorted_entries : c->entries; - // now we need to indicate how they correspond; we could either - // #1: sort a different data structure that says who they correspond to - // #2: for each sorted entry, search the original list to find who corresponds - // #3: for each original entry, find the sorted entry - // #1 requires extra storage, #2 is slow, #3 can use binary search! - for (i=0; i < len; ++i) { - int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; - if (include_in_sort(c,huff_len)) { - uint32 code = bit_reverse(c->codewords[i]); - int x=0, n=c->sorted_entries; - while (n > 1) { - // invariant: sc[x] <= code < sc[x+n] - int m = x + (n >> 1); - if (c->sorted_codewords[m] <= code) { - x = m; - n -= (n>>1); - } else { - n >>= 1; - } - } - assert(c->sorted_codewords[x] == code); - if (c->sparse) { - c->sorted_values[x] = values[i]; - c->codeword_lengths[x] = huff_len; - } else { - c->sorted_values[x] = i; - } - } - } -} - -// only run while parsing the header (3 times) -static int vorbis_validate(uint8 *data) -{ - static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; - return memcmp(data, vorbis, 6) == 0; -} - -// called from setup only, once per code book -// (formula implied by specification) -static int lookup1_values(int entries, int dim) -{ - int r = (int) floor(exp((float) log((float) entries) / dim)); - if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; - ++r; // floor() to avoid _ftol() when non-CRT - assert(pow((float) r+1, dim) > entries); - assert((int) floor(pow((float) r, dim)) <= entries); // (int),floor() as above - return r; -} - -// called twice per file -static void compute_twiddle_factors(int n, float *A, float *B, float *C) -{ - int n4 = n >> 2, n8 = n >> 3; - int k,k2; - - for (k=k2=0; k < n4; ++k,k2+=2) { - A[k2 ] = (float) cos(4*k*M_PI/n); - A[k2+1] = (float) -sin(4*k*M_PI/n); - B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; - B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; - } - for (k=k2=0; k < n8; ++k,k2+=2) { - C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); - C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); - } -} - -static void compute_window(int n, float *window) -{ - int n2 = n >> 1, i; - for (i=0; i < n2; ++i) - window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); -} - -static void compute_bitreverse(int n, uint16 *rev) -{ - int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - int i, n8 = n >> 3; - for (i=0; i < n8; ++i) - rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; -} - -static int init_blocksize(vorb *f, int b, int n) -{ - int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; - f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); - f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); - f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); - if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); - compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); - f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); - if (!f->window[b]) return error(f, VORBIS_outofmem); - compute_window(n, f->window[b]); - f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); - if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); - compute_bitreverse(n, f->bit_reverse[b]); - return TRUE; -} - -static void neighbors(uint16 *x, int n, int *plow, int *phigh) -{ - int low = -1; - int high = 65536; - int i; - for (i=0; i < n; ++i) { - if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } - if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } - } -} - -// this has been repurposed so y is now the original index instead of y -typedef struct -{ - uint16 x,id; -} stbv__floor_ordering; - -static int STBV_CDECL point_compare(const void *p, const void *q) -{ - stbv__floor_ordering *a = (stbv__floor_ordering *) p; - stbv__floor_ordering *b = (stbv__floor_ordering *) q; - return a->x < b->x ? -1 : a->x > b->x; -} - -// -/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// - - -#if defined(STB_VORBIS_NO_STDIO) - #define USE_MEMORY(z) TRUE -#else - #define USE_MEMORY(z) ((z)->stream) -#endif - -static uint8 get8(vorb *z) -{ - if (USE_MEMORY(z)) { - if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } - return *z->stream++; - } - - #ifndef STB_VORBIS_NO_STDIO - { - int c = fgetc(z->f); - if (c == EOF) { z->eof = TRUE; return 0; } - return c; - } - #endif -} - -static uint32 get32(vorb *f) -{ - uint32 x; - x = get8(f); - x += get8(f) << 8; - x += get8(f) << 16; - x += (uint32) get8(f) << 24; - return x; -} - -static int getn(vorb *z, uint8 *data, int n) -{ - if (USE_MEMORY(z)) { - if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } - memcpy(data, z->stream, n); - z->stream += n; - return 1; - } - - #ifndef STB_VORBIS_NO_STDIO - if (fread(data, n, 1, z->f) == 1) - return 1; - else { - z->eof = 1; - return 0; - } - #endif -} - -static void skip(vorb *z, int n) -{ - if (USE_MEMORY(z)) { - z->stream += n; - if (z->stream >= z->stream_end) z->eof = 1; - return; - } - #ifndef STB_VORBIS_NO_STDIO - { - long x = ftell(z->f); - fseek(z->f, x+n, SEEK_SET); - } - #endif -} - -static int set_file_offset(stb_vorbis *f, unsigned int loc) -{ - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (f->push_mode) return 0; - #endif - f->eof = 0; - if (USE_MEMORY(f)) { - if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { - f->stream = f->stream_end; - f->eof = 1; - return 0; - } else { - f->stream = f->stream_start + loc; - return 1; - } - } - #ifndef STB_VORBIS_NO_STDIO - if (loc + f->f_start < loc || loc >= 0x80000000) { - loc = 0x7fffffff; - f->eof = 1; - } else { - loc += f->f_start; - } - if (!fseek(f->f, loc, SEEK_SET)) - return 1; - f->eof = 1; - fseek(f->f, f->f_start, SEEK_END); - return 0; - #endif -} - - -static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; - -static int capture_pattern(vorb *f) -{ - if (0x4f != get8(f)) return FALSE; - if (0x67 != get8(f)) return FALSE; - if (0x67 != get8(f)) return FALSE; - if (0x53 != get8(f)) return FALSE; - return TRUE; -} - -#define PAGEFLAG_continued_packet 1 -#define PAGEFLAG_first_page 2 -#define PAGEFLAG_last_page 4 - -static int start_page_no_capturepattern(vorb *f) -{ - uint32 loc0,loc1,n; - // stream structure version - if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); - // header flag - f->page_flag = get8(f); - // absolute granule position - loc0 = get32(f); - loc1 = get32(f); - // @TODO: validate loc0,loc1 as valid positions? - // stream serial number -- vorbis doesn't interleave, so discard - get32(f); - //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); - // page sequence number - n = get32(f); - f->last_page = n; - // CRC32 - get32(f); - // page_segments - f->segment_count = get8(f); - if (!getn(f, f->segments, f->segment_count)) - return error(f, VORBIS_unexpected_eof); - // assume we _don't_ know any the sample position of any segments - f->end_seg_with_known_loc = -2; - if (loc0 != ~0U || loc1 != ~0U) { - int i; - // determine which packet is the last one that will complete - for (i=f->segment_count-1; i >= 0; --i) - if (f->segments[i] < 255) - break; - // 'i' is now the index of the _last_ segment of a packet that ends - if (i >= 0) { - f->end_seg_with_known_loc = i; - f->known_loc_for_packet = loc0; - } - } - if (f->first_decode) { - int i,len; - ProbedPage p; - len = 0; - for (i=0; i < f->segment_count; ++i) - len += f->segments[i]; - len += 27 + f->segment_count; - p.page_start = f->first_audio_page_offset; - p.page_end = p.page_start + len; - p.last_decoded_sample = loc0; - f->p_first = p; - } - f->next_seg = 0; - return TRUE; -} - -static int start_page(vorb *f) -{ - if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); - return start_page_no_capturepattern(f); -} - -static int start_packet(vorb *f) -{ - while (f->next_seg == -1) { - if (!start_page(f)) return FALSE; - if (f->page_flag & PAGEFLAG_continued_packet) - return error(f, VORBIS_continued_packet_flag_invalid); - } - f->last_seg = FALSE; - f->valid_bits = 0; - f->packet_bytes = 0; - f->bytes_in_seg = 0; - // f->next_seg is now valid - return TRUE; -} - -static int maybe_start_packet(vorb *f) -{ - if (f->next_seg == -1) { - int x = get8(f); - if (f->eof) return FALSE; // EOF at page boundary is not an error! - if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); - if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); - if (!start_page_no_capturepattern(f)) return FALSE; - if (f->page_flag & PAGEFLAG_continued_packet) { - // set up enough state that we can read this packet if we want, - // e.g. during recovery - f->last_seg = FALSE; - f->bytes_in_seg = 0; - return error(f, VORBIS_continued_packet_flag_invalid); - } - } - return start_packet(f); -} - -static int next_segment(vorb *f) -{ - int len; - if (f->last_seg) return 0; - if (f->next_seg == -1) { - f->last_seg_which = f->segment_count-1; // in case start_page fails - if (!start_page(f)) { f->last_seg = 1; return 0; } - if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); - } - len = f->segments[f->next_seg++]; - if (len < 255) { - f->last_seg = TRUE; - f->last_seg_which = f->next_seg-1; - } - if (f->next_seg >= f->segment_count) - f->next_seg = -1; - assert(f->bytes_in_seg == 0); - f->bytes_in_seg = len; - return len; -} - -#define EOP (-1) -#define INVALID_BITS (-1) - -static int get8_packet_raw(vorb *f) -{ - if (!f->bytes_in_seg) { // CLANG! - if (f->last_seg) return EOP; - else if (!next_segment(f)) return EOP; - } - assert(f->bytes_in_seg > 0); - --f->bytes_in_seg; - ++f->packet_bytes; - return get8(f); -} - -static int get8_packet(vorb *f) -{ - int x = get8_packet_raw(f); - f->valid_bits = 0; - return x; -} - -static void flush_packet(vorb *f) -{ - while (get8_packet_raw(f) != EOP); -} - -// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important -// as the huffman decoder? -static uint32 get_bits(vorb *f, int n) -{ - uint32 z; - - if (f->valid_bits < 0) return 0; - if (f->valid_bits < n) { - if (n > 24) { - // the accumulator technique below would not work correctly in this case - z = get_bits(f, 24); - z += get_bits(f, n-24) << 24; - return z; - } - if (f->valid_bits == 0) f->acc = 0; - while (f->valid_bits < n) { - int z = get8_packet_raw(f); - if (z == EOP) { - f->valid_bits = INVALID_BITS; - return 0; - } - f->acc += z << f->valid_bits; - f->valid_bits += 8; - } - } - if (f->valid_bits < 0) return 0; - z = f->acc & ((1 << n)-1); - f->acc >>= n; - f->valid_bits -= n; - return z; -} - -// @OPTIMIZE: primary accumulator for huffman -// expand the buffer to as many bits as possible without reading off end of packet -// it might be nice to allow f->valid_bits and f->acc to be stored in registers, -// e.g. cache them locally and decode locally -static __forceinline void prep_huffman(vorb *f) -{ - if (f->valid_bits <= 24) { - if (f->valid_bits == 0) f->acc = 0; - do { - int z; - if (f->last_seg && !f->bytes_in_seg) return; - z = get8_packet_raw(f); - if (z == EOP) return; - f->acc += (unsigned) z << f->valid_bits; - f->valid_bits += 8; - } while (f->valid_bits <= 24); - } -} - -enum -{ - VORBIS_packet_id = 1, - VORBIS_packet_comment = 3, - VORBIS_packet_setup = 5 -}; - -static int codebook_decode_scalar_raw(vorb *f, Codebook *c) -{ - int i; - prep_huffman(f); - - if (c->codewords == NULL && c->sorted_codewords == NULL) - return -1; - - // cases to use binary search: sorted_codewords && !c->codewords - // sorted_codewords && c->entries > 8 - if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { - // binary search - uint32 code = bit_reverse(f->acc); - int x=0, n=c->sorted_entries, len; - - while (n > 1) { - // invariant: sc[x] <= code < sc[x+n] - int m = x + (n >> 1); - if (c->sorted_codewords[m] <= code) { - x = m; - n -= (n>>1); - } else { - n >>= 1; - } - } - // x is now the sorted index - if (!c->sparse) x = c->sorted_values[x]; - // x is now sorted index if sparse, or symbol otherwise - len = c->codeword_lengths[x]; - if (f->valid_bits >= len) { - f->acc >>= len; - f->valid_bits -= len; - return x; - } - - f->valid_bits = 0; - return -1; - } - - // if small, linear search - assert(!c->sparse); - for (i=0; i < c->entries; ++i) { - if (c->codeword_lengths[i] == NO_CODE) continue; - if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { - if (f->valid_bits >= c->codeword_lengths[i]) { - f->acc >>= c->codeword_lengths[i]; - f->valid_bits -= c->codeword_lengths[i]; - return i; - } - f->valid_bits = 0; - return -1; - } - } - - error(f, VORBIS_invalid_stream); - f->valid_bits = 0; - return -1; -} - -#ifndef STB_VORBIS_NO_INLINE_DECODE - -#define DECODE_RAW(var, f,c) \ - if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ - prep_huffman(f); \ - var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ - var = c->fast_huffman[var]; \ - if (var >= 0) { \ - int n = c->codeword_lengths[var]; \ - f->acc >>= n; \ - f->valid_bits -= n; \ - if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ - } else { \ - var = codebook_decode_scalar_raw(f,c); \ - } - -#else - -static int codebook_decode_scalar(vorb *f, Codebook *c) -{ - int i; - if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) - prep_huffman(f); - // fast huffman table lookup - i = f->acc & FAST_HUFFMAN_TABLE_MASK; - i = c->fast_huffman[i]; - if (i >= 0) { - f->acc >>= c->codeword_lengths[i]; - f->valid_bits -= c->codeword_lengths[i]; - if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } - return i; - } - return codebook_decode_scalar_raw(f,c); -} - -#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); - -#endif - -#define DECODE(var,f,c) \ - DECODE_RAW(var,f,c) \ - if (c->sparse) var = c->sorted_values[var]; - -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) -#else - #define DECODE_VQ(var,f,c) DECODE(var,f,c) -#endif - - - - - - -// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case -// where we avoid one addition -#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) -#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) -#define CODEBOOK_ELEMENT_BASE(c) (0) - -static int codebook_decode_start(vorb *f, Codebook *c) -{ - int z = -1; - - // type 0 is only legal in a scalar context - if (c->lookup_type == 0) - error(f, VORBIS_invalid_stream); - else { - DECODE_VQ(z,f,c); - if (c->sparse) assert(z < c->sorted_entries); - if (z < 0) { // check for EOP - if (!f->bytes_in_seg) - if (f->last_seg) - return z; - error(f, VORBIS_invalid_stream); - } - } - return z; -} - -static int codebook_decode(vorb *f, Codebook *c, float *output, int len) -{ - int i,z = codebook_decode_start(f,c); - if (z < 0) return FALSE; - if (len > c->dimensions) len = c->dimensions; - -#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - float last = CODEBOOK_ELEMENT_BASE(c); - int div = 1; - for (i=0; i < len; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - output[i] += val; - if (c->sequence_p) last = val + c->minimum_value; - div *= c->lookup_values; - } - return TRUE; - } -#endif - - z *= c->dimensions; - if (c->sequence_p) { - float last = CODEBOOK_ELEMENT_BASE(c); - for (i=0; i < len; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - output[i] += val; - last = val + c->minimum_value; - } - } else { - float last = CODEBOOK_ELEMENT_BASE(c); - for (i=0; i < len; ++i) { - output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; - } - } - - return TRUE; -} - -static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) -{ - int i,z = codebook_decode_start(f,c); - float last = CODEBOOK_ELEMENT_BASE(c); - if (z < 0) return FALSE; - if (len > c->dimensions) len = c->dimensions; - -#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int div = 1; - for (i=0; i < len; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - output[i*step] += val; - if (c->sequence_p) last = val; - div *= c->lookup_values; - } - return TRUE; - } -#endif - - z *= c->dimensions; - for (i=0; i < len; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - output[i*step] += val; - if (c->sequence_p) last = val; - } - - return TRUE; -} - -static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) -{ - int c_inter = *c_inter_p; - int p_inter = *p_inter_p; - int i,z, effective = c->dimensions; - - // type 0 is only legal in a scalar context - if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); - - while (total_decode > 0) { - float last = CODEBOOK_ELEMENT_BASE(c); - DECODE_VQ(z,f,c); - #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - assert(!c->sparse || z < c->sorted_entries); - #endif - if (z < 0) { - if (!f->bytes_in_seg) - if (f->last_seg) return FALSE; - return error(f, VORBIS_invalid_stream); - } - - // if this will take us off the end of the buffers, stop short! - // we check by computing the length of the virtual interleaved - // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), - // and the length we'll be using (effective) - if (c_inter + p_inter*ch + effective > len * ch) { - effective = len*ch - (p_inter*ch - c_inter); - } - - #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int div = 1; - for (i=0; i < effective; ++i) { - int off = (z / div) % c->lookup_values; - float val = CODEBOOK_ELEMENT_FAST(c,off) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - if (c->sequence_p) last = val; - div *= c->lookup_values; - } - } else - #endif - { - z *= c->dimensions; - if (c->sequence_p) { - for (i=0; i < effective; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - last = val; - } - } else { - for (i=0; i < effective; ++i) { - float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; - if (outputs[c_inter]) - outputs[c_inter][p_inter] += val; - if (++c_inter == ch) { c_inter = 0; ++p_inter; } - } - } - } - - total_decode -= effective; - } - *c_inter_p = c_inter; - *p_inter_p = p_inter; - return TRUE; -} - -static int predict_point(int x, int x0, int x1, int y0, int y1) -{ - int dy = y1 - y0; - int adx = x1 - x0; - // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? - int err = abs(dy) * (x - x0); - int off = err / adx; - return dy < 0 ? y0 - off : y0 + off; -} - -// the following table is block-copied from the specification -static float inverse_db_table[256] = -{ - 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, - 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, - 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, - 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, - 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, - 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, - 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, - 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, - 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, - 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, - 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, - 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, - 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, - 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, - 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, - 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, - 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, - 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, - 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, - 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, - 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, - 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, - 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, - 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, - 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, - 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, - 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, - 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, - 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, - 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, - 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, - 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, - 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, - 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, - 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, - 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, - 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, - 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, - 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, - 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, - 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, - 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, - 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, - 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, - 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, - 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, - 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, - 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, - 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, - 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, - 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, - 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, - 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, - 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, - 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, - 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, - 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, - 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, - 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, - 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, - 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, - 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, - 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, - 0.82788260f, 0.88168307f, 0.9389798f, 1.0f -}; - - -// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, -// note that you must produce bit-identical output to decode correctly; -// this specific sequence of operations is specified in the spec (it's -// drawing integer-quantized frequency-space lines that the encoder -// expects to be exactly the same) -// ... also, isn't the whole point of Bresenham's algorithm to NOT -// have to divide in the setup? sigh. -#ifndef STB_VORBIS_NO_DEFER_FLOOR -#define LINE_OP(a,b) a *= b -#else -#define LINE_OP(a,b) a = b -#endif - -#ifdef STB_VORBIS_DIVIDE_TABLE -#define DIVTAB_NUMER 32 -#define DIVTAB_DENOM 64 -int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB -#endif - -static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) -{ - int dy = y1 - y0; - int adx = x1 - x0; - int ady = abs(dy); - int base; - int x=x0,y=y0; - int err = 0; - int sy; - -#ifdef STB_VORBIS_DIVIDE_TABLE - if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { - if (dy < 0) { - base = -integer_divide_table[ady][adx]; - sy = base-1; - } else { - base = integer_divide_table[ady][adx]; - sy = base+1; - } - } else { - base = dy / adx; - if (dy < 0) - sy = base - 1; - else - sy = base+1; - } -#else - base = dy / adx; - if (dy < 0) - sy = base - 1; - else - sy = base+1; -#endif - ady -= abs(base) * adx; - if (x1 > n) x1 = n; - if (x < x1) { - LINE_OP(output[x], inverse_db_table[y]); - for (++x; x < x1; ++x) { - err += ady; - if (err >= adx) { - err -= adx; - y += sy; - } else - y += base; - LINE_OP(output[x], inverse_db_table[y]); - } - } -} - -static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) -{ - int k; - if (rtype == 0) { - int step = n / book->dimensions; - for (k=0; k < step; ++k) - if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) - return FALSE; - } else { - for (k=0; k < n; ) { - if (!codebook_decode(f, book, target+offset, n-k)) - return FALSE; - k += book->dimensions; - offset += book->dimensions; - } - } - return TRUE; -} - -// n is 1/2 of the blocksize -- -// specification: "Correct per-vector decode length is [n]/2" -static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) -{ - int i,j,pass; - Residue *r = f->residue_config + rn; - int rtype = f->residue_types[rn]; - int c = r->classbook; - int classwords = f->codebooks[c].dimensions; - unsigned int actual_size = rtype == 2 ? n*2 : n; - unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); - unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); - int n_read = limit_r_end - limit_r_begin; - int part_read = n_read / r->part_size; - int temp_alloc_point = temp_alloc_save(f); - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); - #else - int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); - #endif - - CHECK(f); - - for (i=0; i < ch; ++i) - if (!do_not_decode[i]) - memset(residue_buffers[i], 0, sizeof(float) * n); - - if (rtype == 2 && ch != 1) { - for (j=0; j < ch; ++j) - if (!do_not_decode[j]) - break; - if (j == ch) - goto done; - - for (pass=0; pass < 8; ++pass) { - int pcount = 0, class_set = 0; - if (ch == 2) { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = (z & 1), p_inter = z>>1; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - #else - // saves 1% - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - #endif - } else { - z += r->part_size; - c_inter = z & 1; - p_inter = z >> 1; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } else if (ch == 1) { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = 0, p_inter = z; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - } else { - z += r->part_size; - c_inter = 0; - p_inter = z; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } else { - while (pcount < part_read) { - int z = r->begin + pcount*r->part_size; - int c_inter = z % ch, p_inter = z/ch; - if (pass == 0) { - Codebook *c = f->codebooks+r->classbook; - int q; - DECODE(q,f,c); - if (q == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[0][class_set] = r->classdata[q]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[0][i+pcount] = q % r->classifications; - q /= r->classifications; - } - #endif - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - int z = r->begin + pcount*r->part_size; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[0][class_set][i]; - #else - int c = classifications[0][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - Codebook *book = f->codebooks + b; - if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) - goto done; - } else { - z += r->part_size; - c_inter = z % ch; - p_inter = z / ch; - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } - } - goto done; - } - CHECK(f); - - for (pass=0; pass < 8; ++pass) { - int pcount = 0, class_set=0; - while (pcount < part_read) { - if (pass == 0) { - for (j=0; j < ch; ++j) { - if (!do_not_decode[j]) { - Codebook *c = f->codebooks+r->classbook; - int temp; - DECODE(temp,f,c); - if (temp == EOP) goto done; - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - part_classdata[j][class_set] = r->classdata[temp]; - #else - for (i=classwords-1; i >= 0; --i) { - classifications[j][i+pcount] = temp % r->classifications; - temp /= r->classifications; - } - #endif - } - } - } - for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { - for (j=0; j < ch; ++j) { - if (!do_not_decode[j]) { - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - int c = part_classdata[j][class_set][i]; - #else - int c = classifications[j][pcount]; - #endif - int b = r->residue_books[c][pass]; - if (b >= 0) { - float *target = residue_buffers[j]; - int offset = r->begin + pcount * r->part_size; - int n = r->part_size; - Codebook *book = f->codebooks + b; - if (!residue_decode(f, book, target, offset, n, rtype)) - goto done; - } - } - } - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - ++class_set; - #endif - } - } - done: - CHECK(f); - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - temp_free(f,part_classdata); - #else - temp_free(f,classifications); - #endif - temp_alloc_restore(f,temp_alloc_point); -} - - -#if 0 -// slow way for debugging -void inverse_mdct_slow(float *buffer, int n) -{ - int i,j; - int n2 = n >> 1; - float *x = (float *) malloc(sizeof(*x) * n2); - memcpy(x, buffer, sizeof(*x) * n2); - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n2; ++j) - // formula from paper: - //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); - // formula from wikipedia - //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); - // these are equivalent, except the formula from the paper inverts the multiplier! - // however, what actually works is NO MULTIPLIER!?! - //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); - acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); - buffer[i] = acc; - } - free(x); -} -#elif 0 -// same as above, but just barely able to run in real time on modern machines -void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) -{ - float mcos[16384]; - int i,j; - int n2 = n >> 1, nmask = (n << 2) -1; - float *x = (float *) malloc(sizeof(*x) * n2); - memcpy(x, buffer, sizeof(*x) * n2); - for (i=0; i < 4*n; ++i) - mcos[i] = (float) cos(M_PI / 2 * i / n); - - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n2; ++j) - acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; - buffer[i] = acc; - } - free(x); -} -#elif 0 -// transform to use a slow dct-iv; this is STILL basically trivial, -// but only requires half as many ops -void dct_iv_slow(float *buffer, int n) -{ - float mcos[16384]; - float x[2048]; - int i,j; - int n2 = n >> 1, nmask = (n << 3) - 1; - memcpy(x, buffer, sizeof(*x) * n); - for (i=0; i < 8*n; ++i) - mcos[i] = (float) cos(M_PI / 4 * i / n); - for (i=0; i < n; ++i) { - float acc = 0; - for (j=0; j < n; ++j) - acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; - buffer[i] = acc; - } -} - -void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) -{ - int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; - float temp[4096]; - - memcpy(temp, buffer, n2 * sizeof(float)); - dct_iv_slow(temp, n2); // returns -c'-d, a-b' - - for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' - for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' - for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d -} -#endif - -#ifndef LIBVORBIS_MDCT -#define LIBVORBIS_MDCT 0 -#endif - -#if LIBVORBIS_MDCT -// directly call the vorbis MDCT using an interface documented -// by Jeff Roberts... useful for performance comparison -typedef struct -{ - int n; - int log2n; - - float *trig; - int *bitrev; - - float scale; -} mdct_lookup; - -extern void mdct_init(mdct_lookup *lookup, int n); -extern void mdct_clear(mdct_lookup *l); -extern void mdct_backward(mdct_lookup *init, float *in, float *out); - -mdct_lookup M1,M2; - -void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) -{ - mdct_lookup *M; - if (M1.n == n) M = &M1; - else if (M2.n == n) M = &M2; - else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } - else { - if (M2.n) __asm int 3; - mdct_init(&M2, n); - M = &M2; - } - - mdct_backward(M, buffer, buffer); -} -#endif - - -// the following were split out into separate functions while optimizing; -// they could be pushed back up but eh. __forceinline showed no change; -// they're probably already being inlined. -static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) -{ - float *ee0 = e + i_off; - float *ee2 = ee0 + k_off; - int i; - - assert((n & 3) == 0); - for (i=(n>>2); i > 0; --i) { - float k00_20, k01_21; - k00_20 = ee0[ 0] - ee2[ 0]; - k01_21 = ee0[-1] - ee2[-1]; - ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; - ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; - ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-2] - ee2[-2]; - k01_21 = ee0[-3] - ee2[-3]; - ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; - ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; - ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-4] - ee2[-4]; - k01_21 = ee0[-5] - ee2[-5]; - ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; - ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; - ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - - k00_20 = ee0[-6] - ee2[-6]; - k01_21 = ee0[-7] - ee2[-7]; - ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; - ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; - ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; - ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; - A += 8; - ee0 -= 8; - ee2 -= 8; - } -} - -static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) -{ - int i; - float k00_20, k01_21; - - float *e0 = e + d0; - float *e2 = e0 + k_off; - - for (i=lim >> 2; i > 0; --i) { - k00_20 = e0[-0] - e2[-0]; - k01_21 = e0[-1] - e2[-1]; - e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; - e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; - e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-2] - e2[-2]; - k01_21 = e0[-3] - e2[-3]; - e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; - e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; - e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-4] - e2[-4]; - k01_21 = e0[-5] - e2[-5]; - e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; - e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; - e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; - - A += k1; - - k00_20 = e0[-6] - e2[-6]; - k01_21 = e0[-7] - e2[-7]; - e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; - e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; - e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; - e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; - - e0 -= 8; - e2 -= 8; - - A += k1; - } -} - -static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) -{ - int i; - float A0 = A[0]; - float A1 = A[0+1]; - float A2 = A[0+a_off]; - float A3 = A[0+a_off+1]; - float A4 = A[0+a_off*2+0]; - float A5 = A[0+a_off*2+1]; - float A6 = A[0+a_off*3+0]; - float A7 = A[0+a_off*3+1]; - - float k00,k11; - - float *ee0 = e +i_off; - float *ee2 = ee0+k_off; - - for (i=n; i > 0; --i) { - k00 = ee0[ 0] - ee2[ 0]; - k11 = ee0[-1] - ee2[-1]; - ee0[ 0] = ee0[ 0] + ee2[ 0]; - ee0[-1] = ee0[-1] + ee2[-1]; - ee2[ 0] = (k00) * A0 - (k11) * A1; - ee2[-1] = (k11) * A0 + (k00) * A1; - - k00 = ee0[-2] - ee2[-2]; - k11 = ee0[-3] - ee2[-3]; - ee0[-2] = ee0[-2] + ee2[-2]; - ee0[-3] = ee0[-3] + ee2[-3]; - ee2[-2] = (k00) * A2 - (k11) * A3; - ee2[-3] = (k11) * A2 + (k00) * A3; - - k00 = ee0[-4] - ee2[-4]; - k11 = ee0[-5] - ee2[-5]; - ee0[-4] = ee0[-4] + ee2[-4]; - ee0[-5] = ee0[-5] + ee2[-5]; - ee2[-4] = (k00) * A4 - (k11) * A5; - ee2[-5] = (k11) * A4 + (k00) * A5; - - k00 = ee0[-6] - ee2[-6]; - k11 = ee0[-7] - ee2[-7]; - ee0[-6] = ee0[-6] + ee2[-6]; - ee0[-7] = ee0[-7] + ee2[-7]; - ee2[-6] = (k00) * A6 - (k11) * A7; - ee2[-7] = (k11) * A6 + (k00) * A7; - - ee0 -= k0; - ee2 -= k0; - } -} - -static __forceinline void iter_54(float *z) -{ - float k00,k11,k22,k33; - float y0,y1,y2,y3; - - k00 = z[ 0] - z[-4]; - y0 = z[ 0] + z[-4]; - y2 = z[-2] + z[-6]; - k22 = z[-2] - z[-6]; - - z[-0] = y0 + y2; // z0 + z4 + z2 + z6 - z[-2] = y0 - y2; // z0 + z4 - z2 - z6 - - // done with y0,y2 - - k33 = z[-3] - z[-7]; - - z[-4] = k00 + k33; // z0 - z4 + z3 - z7 - z[-6] = k00 - k33; // z0 - z4 - z3 + z7 - - // done with k33 - - k11 = z[-1] - z[-5]; - y1 = z[-1] + z[-5]; - y3 = z[-3] + z[-7]; - - z[-1] = y1 + y3; // z1 + z5 + z3 + z7 - z[-3] = y1 - y3; // z1 + z5 - z3 - z7 - z[-5] = k11 - k22; // z1 - z5 + z2 - z6 - z[-7] = k11 + k22; // z1 - z5 - z2 + z6 -} - -static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) -{ - int a_off = base_n >> 3; - float A2 = A[0+a_off]; - float *z = e + i_off; - float *base = z - 16 * n; - - while (z > base) { - float k00,k11; - - k00 = z[-0] - z[-8]; - k11 = z[-1] - z[-9]; - z[-0] = z[-0] + z[-8]; - z[-1] = z[-1] + z[-9]; - z[-8] = k00; - z[-9] = k11 ; - - k00 = z[ -2] - z[-10]; - k11 = z[ -3] - z[-11]; - z[ -2] = z[ -2] + z[-10]; - z[ -3] = z[ -3] + z[-11]; - z[-10] = (k00+k11) * A2; - z[-11] = (k11-k00) * A2; - - k00 = z[-12] - z[ -4]; // reverse to avoid a unary negation - k11 = z[ -5] - z[-13]; - z[ -4] = z[ -4] + z[-12]; - z[ -5] = z[ -5] + z[-13]; - z[-12] = k11; - z[-13] = k00; - - k00 = z[-14] - z[ -6]; // reverse to avoid a unary negation - k11 = z[ -7] - z[-15]; - z[ -6] = z[ -6] + z[-14]; - z[ -7] = z[ -7] + z[-15]; - z[-14] = (k00+k11) * A2; - z[-15] = (k00-k11) * A2; - - iter_54(z); - iter_54(z-8); - z -= 16; - } -} - -static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) -{ - int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; - int ld; - // @OPTIMIZE: reduce register pressure by using fewer variables? - int save_point = temp_alloc_save(f); - float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); - float *u=NULL,*v=NULL; - // twiddle factors - float *A = f->A[blocktype]; - - // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" - // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. - - // kernel from paper - - - // merged: - // copy and reflect spectral data - // step 0 - - // note that it turns out that the items added together during - // this step are, in fact, being added to themselves (as reflected - // by step 0). inexplicable inefficiency! this became obvious - // once I combined the passes. - - // so there's a missing 'times 2' here (for adding X to itself). - // this propogates through linearly to the end, where the numbers - // are 1/2 too small, and need to be compensated for. - - { - float *d,*e, *AA, *e_stop; - d = &buf2[n2-2]; - AA = A; - e = &buffer[0]; - e_stop = &buffer[n2]; - while (e != e_stop) { - d[1] = (e[0] * AA[0] - e[2]*AA[1]); - d[0] = (e[0] * AA[1] + e[2]*AA[0]); - d -= 2; - AA += 2; - e += 4; - } - - e = &buffer[n2-3]; - while (d >= buf2) { - d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); - d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); - d -= 2; - AA += 2; - e -= 4; - } - } - - // now we use symbolic names for these, so that we can - // possibly swap their meaning as we change which operations - // are in place - - u = buffer; - v = buf2; - - // step 2 (paper output is w, now u) - // this could be in place, but the data ends up in the wrong - // place... _somebody_'s got to swap it, so this is nominated - { - float *AA = &A[n2-8]; - float *d0,*d1, *e0, *e1; - - e0 = &v[n4]; - e1 = &v[0]; - - d0 = &u[n4]; - d1 = &u[0]; - - while (AA >= A) { - float v40_20, v41_21; - - v41_21 = e0[1] - e1[1]; - v40_20 = e0[0] - e1[0]; - d0[1] = e0[1] + e1[1]; - d0[0] = e0[0] + e1[0]; - d1[1] = v41_21*AA[4] - v40_20*AA[5]; - d1[0] = v40_20*AA[4] + v41_21*AA[5]; - - v41_21 = e0[3] - e1[3]; - v40_20 = e0[2] - e1[2]; - d0[3] = e0[3] + e1[3]; - d0[2] = e0[2] + e1[2]; - d1[3] = v41_21*AA[0] - v40_20*AA[1]; - d1[2] = v40_20*AA[0] + v41_21*AA[1]; - - AA -= 8; - - d0 += 4; - d1 += 4; - e0 += 4; - e1 += 4; - } - } - - // step 3 - ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - - // optimized step 3: - - // the original step3 loop can be nested r inside s or s inside r; - // it's written originally as s inside r, but this is dumb when r - // iterates many times, and s few. So I have two copies of it and - // switch between them halfway. - - // this is iteration 0 of step 3 - imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); - imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); - - // this is iteration 1 of step 3 - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); - imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); - - l=2; - for (; l < (ld-3)>>1; ++l) { - int k0 = n >> (l+2), k0_2 = k0>>1; - int lim = 1 << (l+1); - int i; - for (i=0; i < lim; ++i) - imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); - } - - for (; l < ld-6; ++l) { - int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; - int rlim = n >> (l+6), r; - int lim = 1 << (l+1); - int i_off; - float *A0 = A; - i_off = n2-1; - for (r=rlim; r > 0; --r) { - imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); - A0 += k1*4; - i_off -= 8; - } - } - - // iterations with count: - // ld-6,-5,-4 all interleaved together - // the big win comes from getting rid of needless flops - // due to the constants on pass 5 & 4 being all 1 and 0; - // combining them to be simultaneous to improve cache made little difference - imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); - - // output is u - - // step 4, 5, and 6 - // cannot be in-place because of step 5 - { - uint16 *bitrev = f->bit_reverse[blocktype]; - // weirdly, I'd have thought reading sequentially and writing - // erratically would have been better than vice-versa, but in - // fact that's not what my testing showed. (That is, with - // j = bitreverse(i), do you read i and write j, or read j and write i.) - - float *d0 = &v[n4-4]; - float *d1 = &v[n2-4]; - while (d0 >= v) { - int k4; - - k4 = bitrev[0]; - d1[3] = u[k4+0]; - d1[2] = u[k4+1]; - d0[3] = u[k4+2]; - d0[2] = u[k4+3]; - - k4 = bitrev[1]; - d1[1] = u[k4+0]; - d1[0] = u[k4+1]; - d0[1] = u[k4+2]; - d0[0] = u[k4+3]; - - d0 -= 4; - d1 -= 4; - bitrev += 2; - } - } - // (paper output is u, now v) - - - // data must be in buf2 - assert(v == buf2); - - // step 7 (paper output is v, now v) - // this is now in place - { - float *C = f->C[blocktype]; - float *d, *e; - - d = v; - e = v + n2 - 4; - - while (d < e) { - float a02,a11,b0,b1,b2,b3; - - a02 = d[0] - e[2]; - a11 = d[1] + e[3]; - - b0 = C[1]*a02 + C[0]*a11; - b1 = C[1]*a11 - C[0]*a02; - - b2 = d[0] + e[ 2]; - b3 = d[1] - e[ 3]; - - d[0] = b2 + b0; - d[1] = b3 + b1; - e[2] = b2 - b0; - e[3] = b1 - b3; - - a02 = d[2] - e[0]; - a11 = d[3] + e[1]; - - b0 = C[3]*a02 + C[2]*a11; - b1 = C[3]*a11 - C[2]*a02; - - b2 = d[2] + e[ 0]; - b3 = d[3] - e[ 1]; - - d[2] = b2 + b0; - d[3] = b3 + b1; - e[0] = b2 - b0; - e[1] = b1 - b3; - - C += 4; - d += 4; - e -= 4; - } - } - - // data must be in buf2 - - - // step 8+decode (paper output is X, now buffer) - // this generates pairs of data a la 8 and pushes them directly through - // the decode kernel (pushing rather than pulling) to avoid having - // to make another pass later - - // this cannot POSSIBLY be in place, so we refer to the buffers directly - - { - float *d0,*d1,*d2,*d3; - - float *B = f->B[blocktype] + n2 - 8; - float *e = buf2 + n2 - 8; - d0 = &buffer[0]; - d1 = &buffer[n2-4]; - d2 = &buffer[n2]; - d3 = &buffer[n-4]; - while (e >= v) { - float p0,p1,p2,p3; - - p3 = e[6]*B[7] - e[7]*B[6]; - p2 = -e[6]*B[6] - e[7]*B[7]; - - d0[0] = p3; - d1[3] = - p3; - d2[0] = p2; - d3[3] = p2; - - p1 = e[4]*B[5] - e[5]*B[4]; - p0 = -e[4]*B[4] - e[5]*B[5]; - - d0[1] = p1; - d1[2] = - p1; - d2[1] = p0; - d3[2] = p0; - - p3 = e[2]*B[3] - e[3]*B[2]; - p2 = -e[2]*B[2] - e[3]*B[3]; - - d0[2] = p3; - d1[1] = - p3; - d2[2] = p2; - d3[1] = p2; - - p1 = e[0]*B[1] - e[1]*B[0]; - p0 = -e[0]*B[0] - e[1]*B[1]; - - d0[3] = p1; - d1[0] = - p1; - d2[3] = p0; - d3[0] = p0; - - B -= 8; - e -= 8; - d0 += 4; - d2 += 4; - d1 -= 4; - d3 -= 4; - } - } - - temp_free(f,buf2); - temp_alloc_restore(f,save_point); -} - -#if 0 -// this is the original version of the above code, if you want to optimize it from scratch -void inverse_mdct_naive(float *buffer, int n) -{ - float s; - float A[1 << 12], B[1 << 12], C[1 << 11]; - int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; - int n3_4 = n - n4, ld; - // how can they claim this only uses N words?! - // oh, because they're only used sparsely, whoops - float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; - // set up twiddle factors - - for (k=k2=0; k < n4; ++k,k2+=2) { - A[k2 ] = (float) cos(4*k*M_PI/n); - A[k2+1] = (float) -sin(4*k*M_PI/n); - B[k2 ] = (float) cos((k2+1)*M_PI/n/2); - B[k2+1] = (float) sin((k2+1)*M_PI/n/2); - } - for (k=k2=0; k < n8; ++k,k2+=2) { - C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); - C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); - } - - // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" - // Note there are bugs in that pseudocode, presumably due to them attempting - // to rename the arrays nicely rather than representing the way their actual - // implementation bounces buffers back and forth. As a result, even in the - // "some formulars corrected" version, a direct implementation fails. These - // are noted below as "paper bug". - - // copy and reflect spectral data - for (k=0; k < n2; ++k) u[k] = buffer[k]; - for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; - // kernel from paper - // step 1 - for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { - v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; - v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; - } - // step 2 - for (k=k4=0; k < n8; k+=1, k4+=4) { - w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; - w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; - w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; - w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; - } - // step 3 - ld = ilog(n) - 1; // ilog is off-by-one from normal definitions - for (l=0; l < ld-3; ++l) { - int k0 = n >> (l+2), k1 = 1 << (l+3); - int rlim = n >> (l+4), r4, r; - int s2lim = 1 << (l+2), s2; - for (r=r4=0; r < rlim; r4+=4,++r) { - for (s2=0; s2 < s2lim; s2+=2) { - u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; - u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; - u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] - - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; - u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] - + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; - } - } - if (l+1 < ld-3) { - // paper bug: ping-ponging of u&w here is omitted - memcpy(w, u, sizeof(u)); - } - } - - // step 4 - for (i=0; i < n8; ++i) { - int j = bit_reverse(i) >> (32-ld+3); - assert(j < n8); - if (i == j) { - // paper bug: original code probably swapped in place; if copying, - // need to directly copy in this case - int i8 = i << 3; - v[i8+1] = u[i8+1]; - v[i8+3] = u[i8+3]; - v[i8+5] = u[i8+5]; - v[i8+7] = u[i8+7]; - } else if (i < j) { - int i8 = i << 3, j8 = j << 3; - v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; - v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; - v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; - v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; - } - } - // step 5 - for (k=0; k < n2; ++k) { - w[k] = v[k*2+1]; - } - // step 6 - for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { - u[n-1-k2] = w[k4]; - u[n-2-k2] = w[k4+1]; - u[n3_4 - 1 - k2] = w[k4+2]; - u[n3_4 - 2 - k2] = w[k4+3]; - } - // step 7 - for (k=k2=0; k < n8; ++k, k2 += 2) { - v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; - v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; - v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; - v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; - } - // step 8 - for (k=k2=0; k < n4; ++k,k2 += 2) { - X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; - X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; - } - - // decode kernel to output - // determined the following value experimentally - // (by first figuring out what made inverse_mdct_slow work); then matching that here - // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) - s = 0.5; // theoretically would be n4 - - // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, - // so it needs to use the "old" B values to behave correctly, or else - // set s to 1.0 ]]] - for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; - for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; - for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; -} -#endif - -static float *get_window(vorb *f, int len) -{ - len <<= 1; - if (len == f->blocksize_0) return f->window[0]; - if (len == f->blocksize_1) return f->window[1]; - assert(0); - return NULL; -} - -#ifndef STB_VORBIS_NO_DEFER_FLOOR -typedef int16 YTYPE; -#else -typedef int YTYPE; -#endif -static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) -{ - int n2 = n >> 1; - int s = map->chan[i].mux, floor; - floor = map->submap_floor[s]; - if (f->floor_types[floor] == 0) { - return error(f, VORBIS_invalid_stream); - } else { - Floor1 *g = &f->floor_config[floor].floor1; - int j,q; - int lx = 0, ly = finalY[0] * g->floor1_multiplier; - for (q=1; q < g->values; ++q) { - j = g->sorted_order[q]; - #ifndef STB_VORBIS_NO_DEFER_FLOOR - if (finalY[j] >= 0) - #else - if (step2_flag[j]) - #endif - { - int hy = finalY[j] * g->floor1_multiplier; - int hx = g->Xlist[j]; - if (lx != hx) - draw_line(target, lx,ly, hx,hy, n2); - CHECK(f); - lx = hx, ly = hy; - } - } - if (lx < n2) { - // optimization of: draw_line(target, lx,ly, n,ly, n2); - for (j=lx; j < n2; ++j) - LINE_OP(target[j], inverse_db_table[ly]); - CHECK(f); - } - } - return TRUE; -} - -// The meaning of "left" and "right" -// -// For a given frame: -// we compute samples from 0..n -// window_center is n/2 -// we'll window and mix the samples from left_start to left_end with data from the previous frame -// all of the samples from left_end to right_start can be output without mixing; however, -// this interval is 0-length except when transitioning between short and long frames -// all of the samples from right_start to right_end need to be mixed with the next frame, -// which we don't have, so those get saved in a buffer -// frame N's right_end-right_start, the number of samples to mix with the next frame, -// has to be the same as frame N+1's left_end-left_start (which they are by -// construction) - -static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) -{ - Mode *m; - int i, n, prev, next, window_center; - f->channel_buffer_start = f->channel_buffer_end = 0; - - retry: - if (f->eof) return FALSE; - if (!maybe_start_packet(f)) - return FALSE; - // check packet type - if (get_bits(f,1) != 0) { - if (IS_PUSH_MODE(f)) - return error(f,VORBIS_bad_packet_type); - while (EOP != get8_packet(f)); - goto retry; - } - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - - i = get_bits(f, ilog(f->mode_count-1)); - if (i == EOP) return FALSE; - if (i >= f->mode_count) return FALSE; - *mode = i; - m = f->mode_config + i; - if (m->blockflag) { - n = f->blocksize_1; - prev = get_bits(f,1); - next = get_bits(f,1); - } else { - prev = next = 0; - n = f->blocksize_0; - } - -// WINDOWING - - window_center = n >> 1; - if (m->blockflag && !prev) { - *p_left_start = (n - f->blocksize_0) >> 2; - *p_left_end = (n + f->blocksize_0) >> 2; - } else { - *p_left_start = 0; - *p_left_end = window_center; - } - if (m->blockflag && !next) { - *p_right_start = (n*3 - f->blocksize_0) >> 2; - *p_right_end = (n*3 + f->blocksize_0) >> 2; - } else { - *p_right_start = window_center; - *p_right_end = n; - } - - return TRUE; -} - -static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) -{ - Mapping *map; - int i,j,k,n,n2; - int zero_channel[256]; - int really_zero_channel[256]; - -// WINDOWING - - n = f->blocksize[m->blockflag]; - map = &f->mapping[m->mapping]; - -// FLOORS - n2 = n >> 1; - - CHECK(f); - - for (i=0; i < f->channels; ++i) { - int s = map->chan[i].mux, floor; - zero_channel[i] = FALSE; - floor = map->submap_floor[s]; - if (f->floor_types[floor] == 0) { - return error(f, VORBIS_invalid_stream); - } else { - Floor1 *g = &f->floor_config[floor].floor1; - if (get_bits(f, 1)) { - short *finalY; - uint8 step2_flag[256]; - static int range_list[4] = { 256, 128, 86, 64 }; - int range = range_list[g->floor1_multiplier-1]; - int offset = 2; - finalY = f->finalY[i]; - finalY[0] = get_bits(f, ilog(range)-1); - finalY[1] = get_bits(f, ilog(range)-1); - for (j=0; j < g->partitions; ++j) { - int pclass = g->partition_class_list[j]; - int cdim = g->class_dimensions[pclass]; - int cbits = g->class_subclasses[pclass]; - int csub = (1 << cbits)-1; - int cval = 0; - if (cbits) { - Codebook *c = f->codebooks + g->class_masterbooks[pclass]; - DECODE(cval,f,c); - } - for (k=0; k < cdim; ++k) { - int book = g->subclass_books[pclass][cval & csub]; - cval = cval >> cbits; - if (book >= 0) { - int temp; - Codebook *c = f->codebooks + book; - DECODE(temp,f,c); - finalY[offset++] = temp; - } else - finalY[offset++] = 0; - } - } - if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec - step2_flag[0] = step2_flag[1] = 1; - for (j=2; j < g->values; ++j) { - int low, high, pred, highroom, lowroom, room, val; - low = g->neighbors[j][0]; - high = g->neighbors[j][1]; - //neighbors(g->Xlist, j, &low, &high); - pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); - val = finalY[j]; - highroom = range - pred; - lowroom = pred; - if (highroom < lowroom) - room = highroom * 2; - else - room = lowroom * 2; - if (val) { - step2_flag[low] = step2_flag[high] = 1; - step2_flag[j] = 1; - if (val >= room) - if (highroom > lowroom) - finalY[j] = val - lowroom + pred; - else - finalY[j] = pred - val + highroom - 1; - else - if (val & 1) - finalY[j] = pred - ((val+1)>>1); - else - finalY[j] = pred + (val>>1); - } else { - step2_flag[j] = 0; - finalY[j] = pred; - } - } - -#ifdef STB_VORBIS_NO_DEFER_FLOOR - do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); -#else - // defer final floor computation until _after_ residue - for (j=0; j < g->values; ++j) { - if (!step2_flag[j]) - finalY[j] = -1; - } -#endif - } else { - error: - zero_channel[i] = TRUE; - } - // So we just defer everything else to later - - // at this point we've decoded the floor into buffer - } - } - CHECK(f); - // at this point we've decoded all floors - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - - // re-enable coupled channels if necessary - memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); - for (i=0; i < map->coupling_steps; ++i) - if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { - zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; - } - - CHECK(f); -// RESIDUE DECODE - for (i=0; i < map->submaps; ++i) { - float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; - int r; - uint8 do_not_decode[256]; - int ch = 0; - for (j=0; j < f->channels; ++j) { - if (map->chan[j].mux == i) { - if (zero_channel[j]) { - do_not_decode[ch] = TRUE; - residue_buffers[ch] = NULL; - } else { - do_not_decode[ch] = FALSE; - residue_buffers[ch] = f->channel_buffers[j]; - } - ++ch; - } - } - r = map->submap_residue[i]; - decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); - } - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - CHECK(f); - -// INVERSE COUPLING - for (i = map->coupling_steps-1; i >= 0; --i) { - int n2 = n >> 1; - float *m = f->channel_buffers[map->chan[i].magnitude]; - float *a = f->channel_buffers[map->chan[i].angle ]; - for (j=0; j < n2; ++j) { - float a2,m2; - if (m[j] > 0) - if (a[j] > 0) - m2 = m[j], a2 = m[j] - a[j]; - else - a2 = m[j], m2 = m[j] + a[j]; - else - if (a[j] > 0) - m2 = m[j], a2 = m[j] + a[j]; - else - a2 = m[j], m2 = m[j] - a[j]; - m[j] = m2; - a[j] = a2; - } - } - CHECK(f); - - // finish decoding the floors -#ifndef STB_VORBIS_NO_DEFER_FLOOR - for (i=0; i < f->channels; ++i) { - if (really_zero_channel[i]) { - memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); - } else { - do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); - } - } -#else - for (i=0; i < f->channels; ++i) { - if (really_zero_channel[i]) { - memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); - } else { - for (j=0; j < n2; ++j) - f->channel_buffers[i][j] *= f->floor_buffers[i][j]; - } - } -#endif - -// INVERSE MDCT - CHECK(f); - for (i=0; i < f->channels; ++i) - inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); - CHECK(f); - - // this shouldn't be necessary, unless we exited on an error - // and want to flush to get to the next packet - flush_packet(f); - - if (f->first_decode) { - // assume we start so first non-discarded sample is sample 0 - // this isn't to spec, but spec would require us to read ahead - // and decode the size of all current frames--could be done, - // but presumably it's not a commonly used feature - f->current_loc = -n2; // start of first frame is positioned for discard - // we might have to discard samples "from" the next frame too, - // if we're lapping a large block then a small at the start? - f->discard_samples_deferred = n - right_end; - f->current_loc_valid = TRUE; - f->first_decode = FALSE; - } else if (f->discard_samples_deferred) { - if (f->discard_samples_deferred >= right_start - left_start) { - f->discard_samples_deferred -= (right_start - left_start); - left_start = right_start; - *p_left = left_start; - } else { - left_start += f->discard_samples_deferred; - *p_left = left_start; - f->discard_samples_deferred = 0; - } - } else if (f->previous_length == 0 && f->current_loc_valid) { - // we're recovering from a seek... that means we're going to discard - // the samples from this packet even though we know our position from - // the last page header, so we need to update the position based on - // the discarded samples here - // but wait, the code below is going to add this in itself even - // on a discard, so we don't need to do it here... - } - - // check if we have ogg information about the sample # for this packet - if (f->last_seg_which == f->end_seg_with_known_loc) { - // if we have a valid current loc, and this is final: - if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { - uint32 current_end = f->known_loc_for_packet; - // then let's infer the size of the (probably) short final frame - if (current_end < f->current_loc + (right_end-left_start)) { - if (current_end < f->current_loc) { - // negative truncation, that's impossible! - *len = 0; - } else { - *len = current_end - f->current_loc; - } - *len += left_start; // this doesn't seem right, but has no ill effect on my test files - if (*len > right_end) *len = right_end; // this should never happen - f->current_loc += *len; - return TRUE; - } - } - // otherwise, just set our sample loc - // guess that the ogg granule pos refers to the _middle_ of the - // last frame? - // set f->current_loc to the position of left_start - f->current_loc = f->known_loc_for_packet - (n2-left_start); - f->current_loc_valid = TRUE; - } - if (f->current_loc_valid) - f->current_loc += (right_start - left_start); - - if (f->alloc.alloc_buffer) - assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); - *len = right_end; // ignore samples after the window goes to 0 - CHECK(f); - - return TRUE; -} - -static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) -{ - int mode, left_end, right_end; - if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; - return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); -} - -static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) -{ - int prev,i,j; - // we use right&left (the start of the right- and left-window sin()-regions) - // to determine how much to return, rather than inferring from the rules - // (same result, clearer code); 'left' indicates where our sin() window - // starts, therefore where the previous window's right edge starts, and - // therefore where to start mixing from the previous buffer. 'right' - // indicates where our sin() ending-window starts, therefore that's where - // we start saving, and where our returned-data ends. - - // mixin from previous window - if (f->previous_length) { - int i,j, n = f->previous_length; - float *w = get_window(f, n); - for (i=0; i < f->channels; ++i) { - for (j=0; j < n; ++j) - f->channel_buffers[i][left+j] = - f->channel_buffers[i][left+j]*w[ j] + - f->previous_window[i][ j]*w[n-1-j]; - } - } - - prev = f->previous_length; - - // last half of this data becomes previous window - f->previous_length = len - right; - - // @OPTIMIZE: could avoid this copy by double-buffering the - // output (flipping previous_window with channel_buffers), but - // then previous_window would have to be 2x as large, and - // channel_buffers couldn't be temp mem (although they're NOT - // currently temp mem, they could be (unless we want to level - // performance by spreading out the computation)) - for (i=0; i < f->channels; ++i) - for (j=0; right+j < len; ++j) - f->previous_window[i][j] = f->channel_buffers[i][right+j]; - - if (!prev) - // there was no previous packet, so this data isn't valid... - // this isn't entirely true, only the would-have-overlapped data - // isn't valid, but this seems to be what the spec requires - return 0; - - // truncate a short frame - if (len < right) right = len; - - f->samples_output += right-left; - - return right - left; -} - -static int vorbis_pump_first_frame(stb_vorbis *f) -{ - int len, right, left, res; - res = vorbis_decode_packet(f, &len, &left, &right); - if (res) - vorbis_finish_frame(f, len, left, right); - return res; -} - -#ifndef STB_VORBIS_NO_PUSHDATA_API -static int is_whole_packet_present(stb_vorbis *f, int end_page) -{ - // make sure that we have the packet available before continuing... - // this requires a full ogg parse, but we know we can fetch from f->stream - - // instead of coding this out explicitly, we could save the current read state, - // read the next packet with get8() until end-of-packet, check f->eof, then - // reset the state? but that would be slower, esp. since we'd have over 256 bytes - // of state to restore (primarily the page segment table) - - int s = f->next_seg, first = TRUE; - uint8 *p = f->stream; - - if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag - for (; s < f->segment_count; ++s) { - p += f->segments[s]; - if (f->segments[s] < 255) // stop at first short segment - break; - } - // either this continues, or it ends it... - if (end_page) - if (s < f->segment_count-1) return error(f, VORBIS_invalid_stream); - if (s == f->segment_count) - s = -1; // set 'crosses page' flag - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - first = FALSE; - } - for (; s == -1;) { - uint8 *q; - int n; - - // check that we have the page header ready - if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); - // validate the page - if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); - if (p[4] != 0) return error(f, VORBIS_invalid_stream); - if (first) { // the first segment must NOT have 'continued_packet', later ones MUST - if (f->previous_length) - if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); - // if no previous length, we're resynching, so we can come in on a continued-packet, - // which we'll just drop - } else { - if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); - } - n = p[26]; // segment counts - q = p+27; // q points to segment table - p = q + n; // advance past header - // make sure we've read the segment table - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - for (s=0; s < n; ++s) { - p += q[s]; - if (q[s] < 255) - break; - } - if (end_page) - if (s < n-1) return error(f, VORBIS_invalid_stream); - if (s == n) - s = -1; // set 'crosses page' flag - if (p > f->stream_end) return error(f, VORBIS_need_more_data); - first = FALSE; - } - return TRUE; -} -#endif // !STB_VORBIS_NO_PUSHDATA_API - -static int start_decoder(vorb *f) -{ - uint8 header[6], x,y; - int len,i,j,k, max_submaps = 0; - int longest_floorlist=0; - - // first page, first packet - - if (!start_page(f)) return FALSE; - // validate page flag - if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); - if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); - if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); - // check for expected packet length - if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); - if (f->segments[0] != 30) return error(f, VORBIS_invalid_first_page); - // read packet - // check packet header - if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); - if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); - // vorbis_version - if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); - f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); - if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); - f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); - get32(f); // bitrate_maximum - get32(f); // bitrate_nominal - get32(f); // bitrate_minimum - x = get8(f); - { - int log0,log1; - log0 = x & 15; - log1 = x >> 4; - f->blocksize_0 = 1 << log0; - f->blocksize_1 = 1 << log1; - if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); - if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); - if (log0 > log1) return error(f, VORBIS_invalid_setup); - } - - // framing_flag - x = get8(f); - if (!(x & 1)) return error(f, VORBIS_invalid_first_page); - - // second packet! - if (!start_page(f)) return FALSE; - - if (!start_packet(f)) return FALSE; - do { - len = next_segment(f); - skip(f, len); - f->bytes_in_seg = 0; - } while (len); - - // third packet! - if (!start_packet(f)) return FALSE; - - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (IS_PUSH_MODE(f)) { - if (!is_whole_packet_present(f, TRUE)) { - // convert error in ogg header to write type - if (f->error == VORBIS_invalid_stream) - f->error = VORBIS_invalid_setup; - return FALSE; - } - } - #endif - - crc32_init(); // always init it, to avoid multithread race conditions - - if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); - for (i=0; i < 6; ++i) header[i] = get8_packet(f); - if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); - - // codebooks - - f->codebook_count = get_bits(f,8) + 1; - f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); - if (f->codebooks == NULL) return error(f, VORBIS_outofmem); - memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); - for (i=0; i < f->codebook_count; ++i) { - uint32 *values; - int ordered, sorted_count; - int total=0; - uint8 *lengths; - Codebook *c = f->codebooks+i; - CHECK(f); - x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); - x = get_bits(f, 8); - c->dimensions = (get_bits(f, 8)<<8) + x; - x = get_bits(f, 8); - y = get_bits(f, 8); - c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; - ordered = get_bits(f,1); - c->sparse = ordered ? 0 : get_bits(f,1); - - if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); - - if (c->sparse) - lengths = (uint8 *) setup_temp_malloc(f, c->entries); - else - lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - - if (!lengths) return error(f, VORBIS_outofmem); - - if (ordered) { - int current_entry = 0; - int current_length = get_bits(f,5) + 1; - while (current_entry < c->entries) { - int limit = c->entries - current_entry; - int n = get_bits(f, ilog(limit)); - if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } - memset(lengths + current_entry, current_length, n); - current_entry += n; - ++current_length; - } - } else { - for (j=0; j < c->entries; ++j) { - int present = c->sparse ? get_bits(f,1) : 1; - if (present) { - lengths[j] = get_bits(f, 5) + 1; - ++total; - if (lengths[j] == 32) - return error(f, VORBIS_invalid_setup); - } else { - lengths[j] = NO_CODE; - } - } - } - - if (c->sparse && total >= c->entries >> 2) { - // convert sparse items to non-sparse! - if (c->entries > (int) f->setup_temp_memory_required) - f->setup_temp_memory_required = c->entries; - - c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); - if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); - memcpy(c->codeword_lengths, lengths, c->entries); - setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! - lengths = c->codeword_lengths; - c->sparse = 0; - } - - // compute the size of the sorted tables - if (c->sparse) { - sorted_count = total; - } else { - sorted_count = 0; - #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH - for (j=0; j < c->entries; ++j) - if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) - ++sorted_count; - #endif - } - - c->sorted_entries = sorted_count; - values = NULL; - - CHECK(f); - if (!c->sparse) { - c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); - if (!c->codewords) return error(f, VORBIS_outofmem); - } else { - unsigned int size; - if (c->sorted_entries) { - c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); - if (!c->codeword_lengths) return error(f, VORBIS_outofmem); - c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); - if (!c->codewords) return error(f, VORBIS_outofmem); - values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); - if (!values) return error(f, VORBIS_outofmem); - } - size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; - if (size > f->setup_temp_memory_required) - f->setup_temp_memory_required = size; - } - - if (!compute_codewords(c, lengths, c->entries, values)) { - if (c->sparse) setup_temp_free(f, values, 0); - return error(f, VORBIS_invalid_setup); - } - - if (c->sorted_entries) { - // allocate an extra slot for sentinels - c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); - if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); - // allocate an extra slot at the front so that c->sorted_values[-1] is defined - // so that we can catch that case without an extra if - c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); - if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); - ++c->sorted_values; - c->sorted_values[-1] = -1; - compute_sorted_huffman(c, lengths, values); - } - - if (c->sparse) { - setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); - setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); - setup_temp_free(f, lengths, c->entries); - c->codewords = NULL; - } - - compute_accelerated_huffman(c); - - CHECK(f); - c->lookup_type = get_bits(f, 4); - if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); - if (c->lookup_type > 0) { - uint16 *mults; - c->minimum_value = float32_unpack(get_bits(f, 32)); - c->delta_value = float32_unpack(get_bits(f, 32)); - c->value_bits = get_bits(f, 4)+1; - c->sequence_p = get_bits(f,1); - if (c->lookup_type == 1) { - c->lookup_values = lookup1_values(c->entries, c->dimensions); - } else { - c->lookup_values = c->entries * c->dimensions; - } - if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); - mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); - if (mults == NULL) return error(f, VORBIS_outofmem); - for (j=0; j < (int) c->lookup_values; ++j) { - int q = get_bits(f, c->value_bits); - if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } - mults[j] = q; - } - -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - if (c->lookup_type == 1) { - int len, sparse = c->sparse; - float last=0; - // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop - if (sparse) { - if (c->sorted_entries == 0) goto skip; - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); - } else - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); - if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } - len = sparse ? c->sorted_entries : c->entries; - for (j=0; j < len; ++j) { - unsigned int z = sparse ? c->sorted_values[j] : j; - unsigned int div=1; - for (k=0; k < c->dimensions; ++k) { - int off = (z / div) % c->lookup_values; - float val = mults[off]; - val = mults[off]*c->delta_value + c->minimum_value + last; - c->multiplicands[j*c->dimensions + k] = val; - if (c->sequence_p) - last = val; - if (k+1 < c->dimensions) { - if (div > UINT_MAX / (unsigned int) c->lookup_values) { - setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); - return error(f, VORBIS_invalid_setup); - } - div *= c->lookup_values; - } - } - } - c->lookup_type = 2; - } - else -#endif - { - float last=0; - CHECK(f); - c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); - if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } - for (j=0; j < (int) c->lookup_values; ++j) { - float val = mults[j] * c->delta_value + c->minimum_value + last; - c->multiplicands[j] = val; - if (c->sequence_p) - last = val; - } - } -#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK - skip:; -#endif - setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); - - CHECK(f); - } - CHECK(f); - } - - // time domain transfers (notused) - - x = get_bits(f, 6) + 1; - for (i=0; i < x; ++i) { - uint32 z = get_bits(f, 16); - if (z != 0) return error(f, VORBIS_invalid_setup); - } - - // Floors - f->floor_count = get_bits(f, 6)+1; - f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); - if (f->floor_config == NULL) return error(f, VORBIS_outofmem); - for (i=0; i < f->floor_count; ++i) { - f->floor_types[i] = get_bits(f, 16); - if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); - if (f->floor_types[i] == 0) { - Floor0 *g = &f->floor_config[i].floor0; - g->order = get_bits(f,8); - g->rate = get_bits(f,16); - g->bark_map_size = get_bits(f,16); - g->amplitude_bits = get_bits(f,6); - g->amplitude_offset = get_bits(f,8); - g->number_of_books = get_bits(f,4) + 1; - for (j=0; j < g->number_of_books; ++j) - g->book_list[j] = get_bits(f,8); - return error(f, VORBIS_feature_not_supported); - } else { - stbv__floor_ordering p[31*8+2]; - Floor1 *g = &f->floor_config[i].floor1; - int max_class = -1; - g->partitions = get_bits(f, 5); - for (j=0; j < g->partitions; ++j) { - g->partition_class_list[j] = get_bits(f, 4); - if (g->partition_class_list[j] > max_class) - max_class = g->partition_class_list[j]; - } - for (j=0; j <= max_class; ++j) { - g->class_dimensions[j] = get_bits(f, 3)+1; - g->class_subclasses[j] = get_bits(f, 2); - if (g->class_subclasses[j]) { - g->class_masterbooks[j] = get_bits(f, 8); - if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } - for (k=0; k < 1 << g->class_subclasses[j]; ++k) { - g->subclass_books[j][k] = get_bits(f,8)-1; - if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } - } - g->floor1_multiplier = get_bits(f,2)+1; - g->rangebits = get_bits(f,4); - g->Xlist[0] = 0; - g->Xlist[1] = 1 << g->rangebits; - g->values = 2; - for (j=0; j < g->partitions; ++j) { - int c = g->partition_class_list[j]; - for (k=0; k < g->class_dimensions[c]; ++k) { - g->Xlist[g->values] = get_bits(f, g->rangebits); - ++g->values; - } - } - // precompute the sorting - for (j=0; j < g->values; ++j) { - p[j].x = g->Xlist[j]; - p[j].id = j; - } - qsort(p, g->values, sizeof(p[0]), point_compare); - for (j=0; j < g->values; ++j) - g->sorted_order[j] = (uint8) p[j].id; - // precompute the neighbors - for (j=2; j < g->values; ++j) { - int low,hi; - neighbors(g->Xlist, j, &low,&hi); - g->neighbors[j][0] = low; - g->neighbors[j][1] = hi; - } - - if (g->values > longest_floorlist) - longest_floorlist = g->values; - } - } - - // Residue - f->residue_count = get_bits(f, 6)+1; - f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); - if (f->residue_config == NULL) return error(f, VORBIS_outofmem); - memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); - for (i=0; i < f->residue_count; ++i) { - uint8 residue_cascade[64]; - Residue *r = f->residue_config+i; - f->residue_types[i] = get_bits(f, 16); - if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); - r->begin = get_bits(f, 24); - r->end = get_bits(f, 24); - if (r->end < r->begin) return error(f, VORBIS_invalid_setup); - r->part_size = get_bits(f,24)+1; - r->classifications = get_bits(f,6)+1; - r->classbook = get_bits(f,8); - if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); - for (j=0; j < r->classifications; ++j) { - uint8 high_bits=0; - uint8 low_bits=get_bits(f,3); - if (get_bits(f,1)) - high_bits = get_bits(f,5); - residue_cascade[j] = high_bits*8 + low_bits; - } - r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); - if (r->residue_books == NULL) return error(f, VORBIS_outofmem); - for (j=0; j < r->classifications; ++j) { - for (k=0; k < 8; ++k) { - if (residue_cascade[j] & (1 << k)) { - r->residue_books[j][k] = get_bits(f, 8); - if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); - } else { - r->residue_books[j][k] = -1; - } - } - } - // precompute the classifications[] array to avoid inner-loop mod/divide - // call it 'classdata' since we already have r->classifications - r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); - if (!r->classdata) return error(f, VORBIS_outofmem); - memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); - for (j=0; j < f->codebooks[r->classbook].entries; ++j) { - int classwords = f->codebooks[r->classbook].dimensions; - int temp = j; - r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); - if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); - for (k=classwords-1; k >= 0; --k) { - r->classdata[j][k] = temp % r->classifications; - temp /= r->classifications; - } - } - } - - f->mapping_count = get_bits(f,6)+1; - f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); - if (f->mapping == NULL) return error(f, VORBIS_outofmem); - memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); - for (i=0; i < f->mapping_count; ++i) { - Mapping *m = f->mapping + i; - int mapping_type = get_bits(f,16); - if (mapping_type != 0) return error(f, VORBIS_invalid_setup); - m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); - if (m->chan == NULL) return error(f, VORBIS_outofmem); - if (get_bits(f,1)) - m->submaps = get_bits(f,4)+1; - else - m->submaps = 1; - if (m->submaps > max_submaps) - max_submaps = m->submaps; - if (get_bits(f,1)) { - m->coupling_steps = get_bits(f,8)+1; - for (k=0; k < m->coupling_steps; ++k) { - m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); - m->chan[k].angle = get_bits(f, ilog(f->channels-1)); - if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); - if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); - if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); - } - } else - m->coupling_steps = 0; - - // reserved field - if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); - if (m->submaps > 1) { - for (j=0; j < f->channels; ++j) { - m->chan[j].mux = get_bits(f, 4); - if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); - } - } else - // @SPECIFICATION: this case is missing from the spec - for (j=0; j < f->channels; ++j) - m->chan[j].mux = 0; - - for (j=0; j < m->submaps; ++j) { - get_bits(f,8); // discard - m->submap_floor[j] = get_bits(f,8); - m->submap_residue[j] = get_bits(f,8); - if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); - if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); - } - } - - // Modes - f->mode_count = get_bits(f, 6)+1; - for (i=0; i < f->mode_count; ++i) { - Mode *m = f->mode_config+i; - m->blockflag = get_bits(f,1); - m->windowtype = get_bits(f,16); - m->transformtype = get_bits(f,16); - m->mapping = get_bits(f,8); - if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); - if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); - if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); - } - - flush_packet(f); - - f->previous_length = 0; - - for (i=0; i < f->channels; ++i) { - f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); - f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); - f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); - if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); - #ifdef STB_VORBIS_NO_DEFER_FLOOR - f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); - if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); - #endif - } - - if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; - if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; - f->blocksize[0] = f->blocksize_0; - f->blocksize[1] = f->blocksize_1; - -#ifdef STB_VORBIS_DIVIDE_TABLE - if (integer_divide_table[1][1]==0) - for (i=0; i < DIVTAB_NUMER; ++i) - for (j=1; j < DIVTAB_DENOM; ++j) - integer_divide_table[i][j] = i / j; -#endif - - // compute how much temporary memory is needed - - // 1. - { - uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); - uint32 classify_mem; - int i,max_part_read=0; - for (i=0; i < f->residue_count; ++i) { - Residue *r = f->residue_config + i; - unsigned int actual_size = f->blocksize_1 / 2; - unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; - unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; - int n_read = limit_r_end - limit_r_begin; - int part_read = n_read / r->part_size; - if (part_read > max_part_read) - max_part_read = part_read; - } - #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE - classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); - #else - classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); - #endif - - // maximum reasonable partition size is f->blocksize_1 - - f->temp_memory_required = classify_mem; - if (imdct_mem > f->temp_memory_required) - f->temp_memory_required = imdct_mem; - } - - f->first_decode = TRUE; - - if (f->alloc.alloc_buffer) { - assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); - // check if there's enough temp memory so we don't error later - if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) - return error(f, VORBIS_outofmem); - } - - f->first_audio_page_offset = stb_vorbis_get_file_offset(f); - - return TRUE; -} - -static void vorbis_deinit(stb_vorbis *p) -{ - int i,j; - if (p->residue_config) { - for (i=0; i < p->residue_count; ++i) { - Residue *r = p->residue_config+i; - if (r->classdata) { - for (j=0; j < p->codebooks[r->classbook].entries; ++j) - setup_free(p, r->classdata[j]); - setup_free(p, r->classdata); - } - setup_free(p, r->residue_books); - } - } - - if (p->codebooks) { - CHECK(p); - for (i=0; i < p->codebook_count; ++i) { - Codebook *c = p->codebooks + i; - setup_free(p, c->codeword_lengths); - setup_free(p, c->multiplicands); - setup_free(p, c->codewords); - setup_free(p, c->sorted_codewords); - // c->sorted_values[-1] is the first entry in the array - setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); - } - setup_free(p, p->codebooks); - } - setup_free(p, p->floor_config); - setup_free(p, p->residue_config); - if (p->mapping) { - for (i=0; i < p->mapping_count; ++i) - setup_free(p, p->mapping[i].chan); - setup_free(p, p->mapping); - } - CHECK(p); - for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { - setup_free(p, p->channel_buffers[i]); - setup_free(p, p->previous_window[i]); - #ifdef STB_VORBIS_NO_DEFER_FLOOR - setup_free(p, p->floor_buffers[i]); - #endif - setup_free(p, p->finalY[i]); - } - for (i=0; i < 2; ++i) { - setup_free(p, p->A[i]); - setup_free(p, p->B[i]); - setup_free(p, p->C[i]); - setup_free(p, p->window[i]); - setup_free(p, p->bit_reverse[i]); - } - #ifndef STB_VORBIS_NO_STDIO - if (p->close_on_free) fclose(p->f); - #endif -} - -void stb_vorbis_close(stb_vorbis *p) -{ - if (p == NULL) return; - vorbis_deinit(p); - setup_free(p,p); -} - -static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) -{ - memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start - if (z) { - p->alloc = *z; - p->alloc.alloc_buffer_length_in_bytes = (p->alloc.alloc_buffer_length_in_bytes+3) & ~3; - p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; - } - p->eof = 0; - p->error = VORBIS__no_error; - p->stream = NULL; - p->codebooks = NULL; - p->page_crc_tests = -1; - #ifndef STB_VORBIS_NO_STDIO - p->close_on_free = FALSE; - p->f = NULL; - #endif -} - -int stb_vorbis_get_sample_offset(stb_vorbis *f) -{ - if (f->current_loc_valid) - return f->current_loc; - else - return -1; -} - -stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) -{ - stb_vorbis_info d; - d.channels = f->channels; - d.sample_rate = f->sample_rate; - d.setup_memory_required = f->setup_memory_required; - d.setup_temp_memory_required = f->setup_temp_memory_required; - d.temp_memory_required = f->temp_memory_required; - d.max_frame_size = f->blocksize_1 >> 1; - return d; -} - -int stb_vorbis_get_error(stb_vorbis *f) -{ - int e = f->error; - f->error = VORBIS__no_error; - return e; -} - -static stb_vorbis * vorbis_alloc(stb_vorbis *f) -{ - stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); - return p; -} - -#ifndef STB_VORBIS_NO_PUSHDATA_API - -void stb_vorbis_flush_pushdata(stb_vorbis *f) -{ - f->previous_length = 0; - f->page_crc_tests = 0; - f->discard_samples_deferred = 0; - f->current_loc_valid = FALSE; - f->first_decode = FALSE; - f->samples_output = 0; - f->channel_buffer_start = 0; - f->channel_buffer_end = 0; -} - -static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) -{ - int i,n; - for (i=0; i < f->page_crc_tests; ++i) - f->scan[i].bytes_done = 0; - - // if we have room for more scans, search for them first, because - // they may cause us to stop early if their header is incomplete - if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { - if (data_len < 4) return 0; - data_len -= 3; // need to look for 4-byte sequence, so don't miss - // one that straddles a boundary - for (i=0; i < data_len; ++i) { - if (data[i] == 0x4f) { - if (0==memcmp(data+i, ogg_page_header, 4)) { - int j,len; - uint32 crc; - // make sure we have the whole page header - if (i+26 >= data_len || i+27+data[i+26] >= data_len) { - // only read up to this page start, so hopefully we'll - // have the whole page header start next time - data_len = i; - break; - } - // ok, we have it all; compute the length of the page - len = 27 + data[i+26]; - for (j=0; j < data[i+26]; ++j) - len += data[i+27+j]; - // scan everything up to the embedded crc (which we must 0) - crc = 0; - for (j=0; j < 22; ++j) - crc = crc32_update(crc, data[i+j]); - // now process 4 0-bytes - for ( ; j < 26; ++j) - crc = crc32_update(crc, 0); - // len is the total number of bytes we need to scan - n = f->page_crc_tests++; - f->scan[n].bytes_left = len-j; - f->scan[n].crc_so_far = crc; - f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); - // if the last frame on a page is continued to the next, then - // we can't recover the sample_loc immediately - if (data[i+27+data[i+26]-1] == 255) - f->scan[n].sample_loc = ~0; - else - f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); - f->scan[n].bytes_done = i+j; - if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) - break; - // keep going if we still have room for more - } - } - } - } - - for (i=0; i < f->page_crc_tests;) { - uint32 crc; - int j; - int n = f->scan[i].bytes_done; - int m = f->scan[i].bytes_left; - if (m > data_len - n) m = data_len - n; - // m is the bytes to scan in the current chunk - crc = f->scan[i].crc_so_far; - for (j=0; j < m; ++j) - crc = crc32_update(crc, data[n+j]); - f->scan[i].bytes_left -= m; - f->scan[i].crc_so_far = crc; - if (f->scan[i].bytes_left == 0) { - // does it match? - if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { - // Houston, we have page - data_len = n+m; // consumption amount is wherever that scan ended - f->page_crc_tests = -1; // drop out of page scan mode - f->previous_length = 0; // decode-but-don't-output one frame - f->next_seg = -1; // start a new page - f->current_loc = f->scan[i].sample_loc; // set the current sample location - // to the amount we'd have decoded had we decoded this page - f->current_loc_valid = f->current_loc != ~0U; - return data_len; - } - // delete entry - f->scan[i] = f->scan[--f->page_crc_tests]; - } else { - ++i; - } - } - - return data_len; -} - -// return value: number of bytes we used -int stb_vorbis_decode_frame_pushdata( - stb_vorbis *f, // the file we're decoding - const uint8 *data, int data_len, // the memory available for decoding - int *channels, // place to write number of float * buffers - float ***output, // place to write float ** array of float * buffers - int *samples // place to write number of output samples - ) -{ - int i; - int len,right,left; - - if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - if (f->page_crc_tests >= 0) { - *samples = 0; - return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); - } - - f->stream = (uint8 *) data; - f->stream_end = (uint8 *) data + data_len; - f->error = VORBIS__no_error; - - // check that we have the entire packet in memory - if (!is_whole_packet_present(f, FALSE)) { - *samples = 0; - return 0; - } - - if (!vorbis_decode_packet(f, &len, &left, &right)) { - // save the actual error we encountered - enum STBVorbisError error = f->error; - if (error == VORBIS_bad_packet_type) { - // flush and resynch - f->error = VORBIS__no_error; - while (get8_packet(f) != EOP) - if (f->eof) break; - *samples = 0; - return (int) (f->stream - data); - } - if (error == VORBIS_continued_packet_flag_invalid) { - if (f->previous_length == 0) { - // we may be resynching, in which case it's ok to hit one - // of these; just discard the packet - f->error = VORBIS__no_error; - while (get8_packet(f) != EOP) - if (f->eof) break; - *samples = 0; - return (int) (f->stream - data); - } - } - // if we get an error while parsing, what to do? - // well, it DEFINITELY won't work to continue from where we are! - stb_vorbis_flush_pushdata(f); - // restore the error that actually made us bail - f->error = error; - *samples = 0; - return 1; - } - - // success! - len = vorbis_finish_frame(f, len, left, right); - for (i=0; i < f->channels; ++i) - f->outputs[i] = f->channel_buffers[i] + left; - - if (channels) *channels = f->channels; - *samples = len; - *output = f->outputs; - return (int) (f->stream - data); -} - -stb_vorbis *stb_vorbis_open_pushdata( - const unsigned char *data, int data_len, // the memory available for decoding - int *data_used, // only defined if result is not NULL - int *error, const stb_vorbis_alloc *alloc) -{ - stb_vorbis *f, p; - vorbis_init(&p, alloc); - p.stream = (uint8 *) data; - p.stream_end = (uint8 *) data + data_len; - p.push_mode = TRUE; - if (!start_decoder(&p)) { - if (p.eof) - *error = VORBIS_need_more_data; - else - *error = p.error; - return NULL; - } - f = vorbis_alloc(&p); - if (f) { - *f = p; - *data_used = (int) (f->stream - data); - *error = 0; - return f; - } else { - vorbis_deinit(&p); - return NULL; - } -} -#endif // STB_VORBIS_NO_PUSHDATA_API - -unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) -{ - #ifndef STB_VORBIS_NO_PUSHDATA_API - if (f->push_mode) return 0; - #endif - if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); - #ifndef STB_VORBIS_NO_STDIO - return (unsigned int) (ftell(f->f) - f->f_start); - #endif -} - -#ifndef STB_VORBIS_NO_PULLDATA_API -// -// DATA-PULLING API -// - -static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) -{ - for(;;) { - int n; - if (f->eof) return 0; - n = get8(f); - if (n == 0x4f) { // page header candidate - unsigned int retry_loc = stb_vorbis_get_file_offset(f); - int i; - // check if we're off the end of a file_section stream - if (retry_loc - 25 > f->stream_len) - return 0; - // check the rest of the header - for (i=1; i < 4; ++i) - if (get8(f) != ogg_page_header[i]) - break; - if (f->eof) return 0; - if (i == 4) { - uint8 header[27]; - uint32 i, crc, goal, len; - for (i=0; i < 4; ++i) - header[i] = ogg_page_header[i]; - for (; i < 27; ++i) - header[i] = get8(f); - if (f->eof) return 0; - if (header[4] != 0) goto invalid; - goal = header[22] + (header[23] << 8) + (header[24]<<16) + (header[25]<<24); - for (i=22; i < 26; ++i) - header[i] = 0; - crc = 0; - for (i=0; i < 27; ++i) - crc = crc32_update(crc, header[i]); - len = 0; - for (i=0; i < header[26]; ++i) { - int s = get8(f); - crc = crc32_update(crc, s); - len += s; - } - if (len && f->eof) return 0; - for (i=0; i < len; ++i) - crc = crc32_update(crc, get8(f)); - // finished parsing probable page - if (crc == goal) { - // we could now check that it's either got the last - // page flag set, OR it's followed by the capture - // pattern, but I guess TECHNICALLY you could have - // a file with garbage between each ogg page and recover - // from it automatically? So even though that paranoia - // might decrease the chance of an invalid decode by - // another 2^32, not worth it since it would hose those - // invalid-but-useful files? - if (end) - *end = stb_vorbis_get_file_offset(f); - if (last) { - if (header[5] & 0x04) - *last = 1; - else - *last = 0; - } - set_file_offset(f, retry_loc-1); - return 1; - } - } - invalid: - // not a valid page, so rewind and look for next one - set_file_offset(f, retry_loc); - } - } -} - - -#define SAMPLE_unknown 0xffffffff - -// seeking is implemented with a binary search, which narrows down the range to -// 64K, before using a linear search (because finding the synchronization -// pattern can be expensive, and the chance we'd find the end page again is -// relatively high for small ranges) -// -// two initial interpolation-style probes are used at the start of the search -// to try to bound either side of the binary search sensibly, while still -// working in O(log n) time if they fail. - -static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) -{ - uint8 header[27], lacing[255]; - int i,len; - - // record where the page starts - z->page_start = stb_vorbis_get_file_offset(f); - - // parse the header - getn(f, header, 27); - if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') - return 0; - getn(f, lacing, header[26]); - - // determine the length of the payload - len = 0; - for (i=0; i < header[26]; ++i) - len += lacing[i]; - - // this implies where the page ends - z->page_end = z->page_start + 27 + header[26] + len; - - // read the last-decoded sample out of the data - z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); - - // restore file state to where we were - set_file_offset(f, z->page_start); - return 1; -} - -// rarely used function to seek back to the preceeding page while finding the -// start of a packet -static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) -{ - unsigned int previous_safe, end; - - // now we want to seek back 64K from the limit - if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) - previous_safe = limit_offset - 65536; - else - previous_safe = f->first_audio_page_offset; - - set_file_offset(f, previous_safe); - - while (vorbis_find_page(f, &end, NULL)) { - if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) - return 1; - set_file_offset(f, end); - } - - return 0; -} - -// implements the search logic for finding a page and starting decoding. if -// the function succeeds, current_loc_valid will be true and current_loc will -// be less than or equal to the provided sample number (the closer the -// better). -static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) -{ - ProbedPage left, right, mid; - int i, start_seg_with_known_loc, end_pos, page_start; - uint32 delta, stream_length, padding; - double offset, bytes_per_sample; - int probe = 0; - - // find the last page and validate the target sample - stream_length = stb_vorbis_stream_length_in_samples(f); - if (stream_length == 0) return error(f, VORBIS_seek_without_length); - if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); - - // this is the maximum difference between the window-center (which is the - // actual granule position value), and the right-start (which the spec - // indicates should be the granule position (give or take one)). - padding = ((f->blocksize_1 - f->blocksize_0) >> 2); - if (sample_number < padding) - sample_number = 0; - else - sample_number -= padding; - - left = f->p_first; - while (left.last_decoded_sample == ~0U) { - // (untested) the first page does not have a 'last_decoded_sample' - set_file_offset(f, left.page_end); - if (!get_seek_page_info(f, &left)) goto error; - } - - right = f->p_last; - assert(right.last_decoded_sample != ~0U); - - // starting from the start is handled differently - if (sample_number <= left.last_decoded_sample) { - if (stb_vorbis_seek_start(f)) - return 1; - return 0; - } - - while (left.page_end != right.page_start) { - assert(left.page_end < right.page_start); - // search range in bytes - delta = right.page_start - left.page_end; - if (delta <= 65536) { - // there's only 64K left to search - handle it linearly - set_file_offset(f, left.page_end); - } else { - if (probe < 2) { - if (probe == 0) { - // first probe (interpolate) - double data_bytes = right.page_end - left.page_start; - bytes_per_sample = data_bytes / right.last_decoded_sample; - offset = left.page_start + bytes_per_sample * (sample_number - left.last_decoded_sample); - } else { - // second probe (try to bound the other side) - double error = ((double) sample_number - mid.last_decoded_sample) * bytes_per_sample; - if (error >= 0 && error < 8000) error = 8000; - if (error < 0 && error > -8000) error = -8000; - offset += error * 2; - } - - // ensure the offset is valid - if (offset < left.page_end) - offset = left.page_end; - if (offset > right.page_start - 65536) - offset = right.page_start - 65536; - - set_file_offset(f, (unsigned int) offset); - } else { - // binary search for large ranges (offset by 32K to ensure - // we don't hit the right page) - set_file_offset(f, left.page_end + (delta / 2) - 32768); - } - - if (!vorbis_find_page(f, NULL, NULL)) goto error; - } - - for (;;) { - if (!get_seek_page_info(f, &mid)) goto error; - if (mid.last_decoded_sample != ~0U) break; - // (untested) no frames end on this page - set_file_offset(f, mid.page_end); - assert(mid.page_start < right.page_start); - } - - // if we've just found the last page again then we're in a tricky file, - // and we're close enough. - if (mid.page_start == right.page_start) - break; - - if (sample_number < mid.last_decoded_sample) - right = mid; - else - left = mid; - - ++probe; - } - - // seek back to start of the last packet - page_start = left.page_start; - set_file_offset(f, page_start); - if (!start_page(f)) return error(f, VORBIS_seek_failed); - end_pos = f->end_seg_with_known_loc; - assert(end_pos >= 0); - - for (;;) { - for (i = end_pos; i > 0; --i) - if (f->segments[i-1] != 255) - break; - - start_seg_with_known_loc = i; - - if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) - break; - - // (untested) the final packet begins on an earlier page - if (!go_to_page_before(f, page_start)) - goto error; - - page_start = stb_vorbis_get_file_offset(f); - if (!start_page(f)) goto error; - end_pos = f->segment_count - 1; - } - - // prepare to start decoding - f->current_loc_valid = FALSE; - f->last_seg = FALSE; - f->valid_bits = 0; - f->packet_bytes = 0; - f->bytes_in_seg = 0; - f->previous_length = 0; - f->next_seg = start_seg_with_known_loc; - - for (i = 0; i < start_seg_with_known_loc; i++) - skip(f, f->segments[i]); - - // start decoding (optimizable - this frame is generally discarded) - if (!vorbis_pump_first_frame(f)) - return 0; - if (f->current_loc > sample_number) - return error(f, VORBIS_seek_failed); - return 1; - -error: - // try to restore the file to a valid state - stb_vorbis_seek_start(f); - return error(f, VORBIS_seek_failed); -} - -// the same as vorbis_decode_initial, but without advancing -static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) -{ - int bits_read, bytes_read; - - if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) - return 0; - - // either 1 or 2 bytes were read, figure out which so we can rewind - bits_read = 1 + ilog(f->mode_count-1); - if (f->mode_config[*mode].blockflag) - bits_read += 2; - bytes_read = (bits_read + 7) / 8; - - f->bytes_in_seg += bytes_read; - f->packet_bytes -= bytes_read; - skip(f, -bytes_read); - if (f->next_seg == -1) - f->next_seg = f->segment_count - 1; - else - f->next_seg--; - f->valid_bits = 0; - - return 1; -} - -int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) -{ - uint32 max_frame_samples; - - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - // fast page-level search - if (!seek_to_sample_coarse(f, sample_number)) - return 0; - - assert(f->current_loc_valid); - assert(f->current_loc <= sample_number); - - // linear search for the relevant packet - max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; - while (f->current_loc < sample_number) { - int left_start, left_end, right_start, right_end, mode, frame_samples; - if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) - return error(f, VORBIS_seek_failed); - // calculate the number of samples returned by the next frame - frame_samples = right_start - left_start; - if (f->current_loc + frame_samples > sample_number) { - return 1; // the next frame will contain the sample - } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { - // there's a chance the frame after this could contain the sample - vorbis_pump_first_frame(f); - } else { - // this frame is too early to be relevant - f->current_loc += frame_samples; - f->previous_length = 0; - maybe_start_packet(f); - flush_packet(f); - } - } - // the next frame will start with the sample - assert(f->current_loc == sample_number); - return 1; -} - -int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) -{ - if (!stb_vorbis_seek_frame(f, sample_number)) - return 0; - - if (sample_number != f->current_loc) { - int n; - uint32 frame_start = f->current_loc; - stb_vorbis_get_frame_float(f, &n, NULL); - assert(sample_number > frame_start); - assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); - f->channel_buffer_start += (sample_number - frame_start); - } - - return 1; -} - -int stb_vorbis_seek_start(stb_vorbis *f) -{ - if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } - set_file_offset(f, f->first_audio_page_offset); - f->previous_length = 0; - f->first_decode = TRUE; - f->next_seg = -1; - return vorbis_pump_first_frame(f); -} - -unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) -{ - unsigned int restore_offset, previous_safe; - unsigned int end, last_page_loc; - - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - if (!f->total_samples) { - unsigned int last; - uint32 lo,hi; - char header[6]; - - // first, store the current decode position so we can restore it - restore_offset = stb_vorbis_get_file_offset(f); - - // now we want to seek back 64K from the end (the last page must - // be at most a little less than 64K, but let's allow a little slop) - if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) - previous_safe = f->stream_len - 65536; - else - previous_safe = f->first_audio_page_offset; - - set_file_offset(f, previous_safe); - // previous_safe is now our candidate 'earliest known place that seeking - // to will lead to the final page' - - if (!vorbis_find_page(f, &end, &last)) { - // if we can't find a page, we're hosed! - f->error = VORBIS_cant_find_last_page; - f->total_samples = 0xffffffff; - goto done; - } - - // check if there are more pages - last_page_loc = stb_vorbis_get_file_offset(f); - - // stop when the last_page flag is set, not when we reach eof; - // this allows us to stop short of a 'file_section' end without - // explicitly checking the length of the section - while (!last) { - set_file_offset(f, end); - if (!vorbis_find_page(f, &end, &last)) { - // the last page we found didn't have the 'last page' flag - // set. whoops! - break; - } - previous_safe = last_page_loc+1; - last_page_loc = stb_vorbis_get_file_offset(f); - } - - set_file_offset(f, last_page_loc); - - // parse the header - getn(f, (unsigned char *)header, 6); - // extract the absolute granule position - lo = get32(f); - hi = get32(f); - if (lo == 0xffffffff && hi == 0xffffffff) { - f->error = VORBIS_cant_find_last_page; - f->total_samples = SAMPLE_unknown; - goto done; - } - if (hi) - lo = 0xfffffffe; // saturate - f->total_samples = lo; - - f->p_last.page_start = last_page_loc; - f->p_last.page_end = end; - f->p_last.last_decoded_sample = lo; - - done: - set_file_offset(f, restore_offset); - } - return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; -} - -float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) -{ - return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; -} - - - -int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) -{ - int len, right,left,i; - if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); - - if (!vorbis_decode_packet(f, &len, &left, &right)) { - f->channel_buffer_start = f->channel_buffer_end = 0; - return 0; - } - - len = vorbis_finish_frame(f, len, left, right); - for (i=0; i < f->channels; ++i) - f->outputs[i] = f->channel_buffers[i] + left; - - f->channel_buffer_start = left; - f->channel_buffer_end = left+len; - - if (channels) *channels = f->channels; - if (output) *output = f->outputs; - return len; -} - -#ifndef STB_VORBIS_NO_STDIO - -stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) -{ - stb_vorbis *f, p; - vorbis_init(&p, alloc); - p.f = file; - p.f_start = (uint32) ftell(file); - p.stream_len = length; - p.close_on_free = close_on_free; - if (start_decoder(&p)) { - f = vorbis_alloc(&p); - if (f) { - *f = p; - vorbis_pump_first_frame(f); - return f; - } - } - if (error) *error = p.error; - vorbis_deinit(&p); - return NULL; -} - -stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) -{ - unsigned int len, start; - start = (unsigned int) ftell(file); - fseek(file, 0, SEEK_END); - len = (unsigned int) (ftell(file) - start); - fseek(file, start, SEEK_SET); - return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); -} - -stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) -{ - FILE *f = fopen(filename, "rb"); - if (f) - return stb_vorbis_open_file(f, TRUE, error, alloc); - if (error) *error = VORBIS_file_open_failure; - return NULL; -} -#endif // STB_VORBIS_NO_STDIO - -stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) -{ - stb_vorbis *f, p; - if (data == NULL) return NULL; - vorbis_init(&p, alloc); - p.stream = (uint8 *) data; - p.stream_end = (uint8 *) data + len; - p.stream_start = (uint8 *) p.stream; - p.stream_len = len; - p.push_mode = FALSE; - if (start_decoder(&p)) { - f = vorbis_alloc(&p); - if (f) { - *f = p; - vorbis_pump_first_frame(f); - if (error) *error = VORBIS__no_error; - return f; - } - } - if (error) *error = p.error; - vorbis_deinit(&p); - return NULL; -} - -#ifndef STB_VORBIS_NO_INTEGER_CONVERSION -#define PLAYBACK_MONO 1 -#define PLAYBACK_LEFT 2 -#define PLAYBACK_RIGHT 4 - -#define L (PLAYBACK_LEFT | PLAYBACK_MONO) -#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) -#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) - -static int8 channel_position[7][6] = -{ - { 0 }, - { C }, - { L, R }, - { L, C, R }, - { L, R, L, R }, - { L, C, R, L, R }, - { L, C, R, L, R, C }, -}; - - -#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT - typedef union { - float f; - int i; - } float_conv; - typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; - #define FASTDEF(x) float_conv x - // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round - #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) - #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) - #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) - #define check_endianness() -#else - #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) - #define check_endianness() - #define FASTDEF(x) -#endif - -static void copy_samples(short *dest, float *src, int len) -{ - int i; - check_endianness(); - for (i=0; i < len; ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - dest[i] = v; - } -} - -static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) -{ - #define BUFFER_SIZE 32 - float buffer[BUFFER_SIZE]; - int i,j,o,n = BUFFER_SIZE; - check_endianness(); - for (o = 0; o < len; o += BUFFER_SIZE) { - memset(buffer, 0, sizeof(buffer)); - if (o + n > len) n = len - o; - for (j=0; j < num_c; ++j) { - if (channel_position[num_c][j] & mask) { - for (i=0; i < n; ++i) - buffer[i] += data[j][d_offset+o+i]; - } - } - for (i=0; i < n; ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - output[o+i] = v; - } - } -} - -static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) -{ - #define BUFFER_SIZE 32 - float buffer[BUFFER_SIZE]; - int i,j,o,n = BUFFER_SIZE >> 1; - // o is the offset in the source data - check_endianness(); - for (o = 0; o < len; o += BUFFER_SIZE >> 1) { - // o2 is the offset in the output data - int o2 = o << 1; - memset(buffer, 0, sizeof(buffer)); - if (o + n > len) n = len - o; - for (j=0; j < num_c; ++j) { - int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); - if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { - for (i=0; i < n; ++i) { - buffer[i*2+0] += data[j][d_offset+o+i]; - buffer[i*2+1] += data[j][d_offset+o+i]; - } - } else if (m == PLAYBACK_LEFT) { - for (i=0; i < n; ++i) { - buffer[i*2+0] += data[j][d_offset+o+i]; - } - } else if (m == PLAYBACK_RIGHT) { - for (i=0; i < n; ++i) { - buffer[i*2+1] += data[j][d_offset+o+i]; - } - } - } - for (i=0; i < (n<<1); ++i) { - FASTDEF(temp); - int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - output[o2+i] = v; - } - } -} - -static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) -{ - int i; - if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { - static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; - for (i=0; i < buf_c; ++i) - compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); - } else { - int limit = buf_c < data_c ? buf_c : data_c; - for (i=0; i < limit; ++i) - copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); - for ( ; i < buf_c; ++i) - memset(buffer[i]+b_offset, 0, sizeof(short) * samples); - } -} - -int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) -{ - float **output; - int len = stb_vorbis_get_frame_float(f, NULL, &output); - if (len > num_samples) len = num_samples; - if (len) - convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); - return len; -} - -static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) -{ - int i; - check_endianness(); - if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { - assert(buf_c == 2); - for (i=0; i < buf_c; ++i) - compute_stereo_samples(buffer, data_c, data, d_offset, len); - } else { - int limit = buf_c < data_c ? buf_c : data_c; - int j; - for (j=0; j < len; ++j) { - for (i=0; i < limit; ++i) { - FASTDEF(temp); - float f = data[i][d_offset+j]; - int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); - if ((unsigned int) (v + 32768) > 65535) - v = v < 0 ? -32768 : 32767; - *buffer++ = v; - } - for ( ; i < buf_c; ++i) - *buffer++ = 0; - } - } -} - -int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) -{ - float **output; - int len; - if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); - len = stb_vorbis_get_frame_float(f, NULL, &output); - if (len) { - if (len*num_c > num_shorts) len = num_shorts / num_c; - convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); - } - return len; -} - -int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) -{ - float **outputs; - int len = num_shorts / channels; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - if (k) - convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); - buffer += k*channels; - n += k; - f->channel_buffer_start += k; - if (n == len) break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; - } - return n; -} - -int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) -{ - float **outputs; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - if (k) - convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); - n += k; - f->channel_buffer_start += k; - if (n == len) break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; - } - return n; -} - -#ifndef STB_VORBIS_NO_STDIO -int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) -{ - int data_len, offset, total, limit, error; - short *data; - stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); - if (v == NULL) return -1; - limit = v->channels * 4096; - *channels = v->channels; - if (sample_rate) - *sample_rate = v->sample_rate; - offset = data_len = 0; - total = limit; - data = (short *) malloc(total * sizeof(*data)); - if (data == NULL) { - stb_vorbis_close(v); - return -2; - } - for (;;) { - int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); - if (n == 0) break; - data_len += n; - offset += n * v->channels; - if (offset + limit > total) { - short *data2; - total *= 2; - data2 = (short *) realloc(data, total * sizeof(*data)); - if (data2 == NULL) { - free(data); - stb_vorbis_close(v); - return -2; - } - data = data2; - } - } - *output = data; - stb_vorbis_close(v); - return data_len; -} -#endif // NO_STDIO - -int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) -{ - int data_len, offset, total, limit, error; - short *data; - stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); - if (v == NULL) return -1; - limit = v->channels * 4096; - *channels = v->channels; - if (sample_rate) - *sample_rate = v->sample_rate; - offset = data_len = 0; - total = limit; - data = (short *) malloc(total * sizeof(*data)); - if (data == NULL) { - stb_vorbis_close(v); - return -2; - } - for (;;) { - int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); - if (n == 0) break; - data_len += n; - offset += n * v->channels; - if (offset + limit > total) { - short *data2; - total *= 2; - data2 = (short *) realloc(data, total * sizeof(*data)); - if (data2 == NULL) { - free(data); - stb_vorbis_close(v); - return -2; - } - data = data2; - } - } - *output = data; - stb_vorbis_close(v); - return data_len; -} -#endif // STB_VORBIS_NO_INTEGER_CONVERSION - -int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) -{ - float **outputs; - int len = num_floats / channels; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < len) { - int i,j; - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= len) k = len - n; - for (j=0; j < k; ++j) { - for (i=0; i < z; ++i) - *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; - for ( ; i < channels; ++i) - *buffer++ = 0; - } - n += k; - f->channel_buffer_start += k; - if (n == len) - break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) - break; - } - return n; -} - -int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) -{ - float **outputs; - int n=0; - int z = f->channels; - if (z > channels) z = channels; - while (n < num_samples) { - int i; - int k = f->channel_buffer_end - f->channel_buffer_start; - if (n+k >= num_samples) k = num_samples - n; - if (k) { - for (i=0; i < z; ++i) - memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); - for ( ; i < channels; ++i) - memset(buffer[i]+n, 0, sizeof(float) * k); - } - n += k; - f->channel_buffer_start += k; - if (n == num_samples) - break; - if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) - break; - } - return n; -} -#endif // STB_VORBIS_NO_PULLDATA_API - -/* Version history - 1.12 - 2017/11/21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files - 1.11 - 2017/07/23 - fix MinGW compilation - 1.10 - 2017/03/03 - more robust seeking; fix negative ilog(); clear error in open_memory - 1.09 - 2016/04/04 - back out 'avoid discarding last frame' fix from previous version - 1.08 - 2016/04/02 - fixed multiple warnings; fix setup memory leaks; - avoid discarding last frame of audio data - 1.07 - 2015/01/16 - fixed some warnings, fix mingw, const-correct API - some more crash fixes when out of memory or with corrupt files - 1.06 - 2015/08/31 - full, correct support for seeking API (Dougall Johnson) - some crash fixes when out of memory or with corrupt files - 1.05 - 2015/04/19 - don't define __forceinline if it's redundant - 1.04 - 2014/08/27 - fix missing const-correct case in API - 1.03 - 2014/08/07 - Warning fixes - 1.02 - 2014/07/09 - Declare qsort compare function _cdecl on windows - 1.01 - 2014/06/18 - fix stb_vorbis_get_samples_float - 1.0 - 2014/05/26 - fix memory leaks; fix warnings; fix bugs in multichannel - (API change) report sample rate for decode-full-file funcs - 0.99996 - bracket #include for macintosh compilation by Laurent Gomila - 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem - 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence - 0.99993 - remove assert that fired on legal files with empty tables - 0.99992 - rewind-to-start - 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo - 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ - 0.9998 - add a full-decode function with a memory source - 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition - 0.9996 - query length of vorbis stream in samples/seconds - 0.9995 - bugfix to another optimization that only happened in certain files - 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors - 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation - 0.9992 - performance improvement of IMDCT; now performs close to reference implementation - 0.9991 - performance improvement of IMDCT - 0.999 - (should have been 0.9990) performance improvement of IMDCT - 0.998 - no-CRT support from Casey Muratori - 0.997 - bugfixes for bugs found by Terje Mathisen - 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen - 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen - 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen - 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen - 0.992 - fixes for MinGW warning - 0.991 - turn fast-float-conversion on by default - 0.990 - fix push-mode seek recovery if you seek into the headers - 0.98b - fix to bad release of 0.98 - 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode - 0.97 - builds under c++ (typecasting, don't use 'class' keyword) - 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code - 0.95 - clamping code for 16-bit functions - 0.94 - not publically released - 0.93 - fixed all-zero-floor case (was decoding garbage) - 0.92 - fixed a memory leak - 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION - 0.90 - first public release -*/ - -#endif // STB_VORBIS_HEADER_ONLY - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -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 OR COPYRIGHT HOLDERS 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. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -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. ------------------------------------------------------------------------------- -*/ diff --git a/raylib/external/stb_vorbis.h b/raylib/external/stb_vorbis.h index 90f3b06..c9ee5cc 100644 --- a/raylib/external/stb_vorbis.h +++ b/raylib/external/stb_vorbis.h @@ -570,7 +570,7 @@ enum STBVorbisError #if defined(_MSC_VER) || defined(__MINGW32__) #include #endif - #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) + #if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__) || defined(__APPLE__) #include #endif #else // STB_VORBIS_NO_CRT diff --git a/raylib/external/tinyobj_loader_c.h b/raylib/external/tinyobj_loader_c.h index 7ef55ff..6bd63fc 100644 --- a/raylib/external/tinyobj_loader_c.h +++ b/raylib/external/tinyobj_loader_c.h @@ -748,7 +748,7 @@ static int tinyobj_parse_and_index_mtl_file(tinyobj_material_t **materials_out, fp = fopen(filename, "r"); if (!fp) { - //fprintf(stderr, "TINYOBJ: Error reading file '%s': %s (%d)\n", filename, strerror(errno), errno); // @raysan5: commented + fprintf(stderr, "TINYOBJ: Error reading file '%s': %s (%d)\n", filename, strerror(errno), errno); return TINYOBJ_ERROR_FILE_OPERATION; } @@ -1321,7 +1321,7 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, if (ret != TINYOBJ_SUCCESS) { /* warning. */ - //fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret); // @raysan5: commented + fprintf(stderr, "TINYOBJ: Failed to parse material file '%s': %d\n", filename, ret); } TINYOBJ_FREE(filename); @@ -1377,7 +1377,7 @@ int tinyobj_parse_obj(tinyobj_attrib_t *attrib, tinyobj_shape_t **shapes, /* Create a null terminated string */ char* material_name_null_term = (char*) TINYOBJ_MALLOC(commands[i].material_name_len + 1); memcpy((void*) material_name_null_term, (const void*) commands[i].material_name, commands[i].material_name_len); - material_name_null_term[commands[i].material_name_len - 1] = 0; + material_name_null_term[commands[i].material_name_len] = 0; if (hash_table_exists(material_name_null_term, &material_table)) material_id = (int)hash_table_get(material_name_null_term, &material_table); diff --git a/raylib/gestures.h b/raylib/gestures.h index bb0e433..ece65b3 100644 --- a/raylib/gestures.h +++ b/raylib/gestures.h @@ -24,7 +24,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2021 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. @@ -90,7 +90,7 @@ typedef enum { TOUCH_UP, TOUCH_DOWN, TOUCH_MOVE } TouchAction; -// Gesture events +// Gesture event // NOTE: MAX_TOUCH_POINTS fixed to 4 typedef struct { int touchAction; @@ -115,7 +115,7 @@ void ProcessGestureEvent(GestureEvent event); // Process gesture event void UpdateGestures(void); // Update gestures detected (must be called every frame) #if defined(GESTURES_STANDALONE) -void SetGesturesEnabled(unsigned int gestureFlags); // Enable a set of gestures using flags +void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags bool IsGestureDetected(int gesture); // Check if a gesture have been detected int GetGestureDetected(void); // Get latest detected gesture int GetTouchPointsCount(void); // Get touch points count @@ -234,9 +234,9 @@ static double GetCurrentTime(void); //---------------------------------------------------------------------------------- // Enable only desired getures to be detected -void SetGesturesEnabled(unsigned int gestureFlags) +void SetGesturesEnabled(unsigned int flags) { - GESTURES.enabledFlags = gestureFlags; + GESTURES.enabledFlags = flags; } // Check if a gesture have been detected diff --git a/raylib/models.c b/raylib/models.c index f2bac16..551ee7a 100644 --- a/raylib/models.c +++ b/raylib/models.c @@ -17,7 +17,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -92,9 +92,7 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#ifndef DEFAULT_MESH_VERTEX_BUFFERS - #define DEFAULT_MESH_VERTEX_BUFFERS 7 // Number of vertex buffers (VBO) per mesh -#endif +// ... //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -114,9 +112,16 @@ static Model LoadOBJ(const char *fileName); // Load OBJ mesh data #endif #if defined(SUPPORT_FILEFORMAT_IQM) static Model LoadIQM(const char *fileName); // Load IQM mesh data +static ModelAnimation *LoadIQMModelAnimations(const char *fileName, int *animCount); // Load IQM animation data #endif #if defined(SUPPORT_FILEFORMAT_GLTF) static Model LoadGLTF(const char *fileName); // Load GLTF mesh data +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount); // Load GLTF animation data +static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex); +static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex); +static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex); +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data); +static void InitGLTFBones(Model* model, const cgltf_data* data); #endif //---------------------------------------------------------------------------------- @@ -126,6 +131,11 @@ static Model LoadGLTF(const char *fileName); // Load GLTF mesh data // Draw a line in 3D world space void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) { + // WARNING: Be careful with internal buffer vertex alignment + // when using RL_LINES or RL_TRIANGLES, data is aligned to fit + // lines-triangles-quads in the same indexed buffers!!! + rlCheckRenderBatchLimit(8); + rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); rlVertex3f(startPos.x, startPos.y, startPos.z); @@ -136,7 +146,7 @@ void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color) // Draw a point in 3D space, actually a small line void DrawPoint3D(Vector3 position, Color color) { - if (rlCheckBufferLimit(8)) rlglDraw(); + rlCheckRenderBatchLimit(8); rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -151,7 +161,7 @@ void DrawPoint3D(Vector3 position, Color color) // Draw a circle in 3D world space void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color) { - if (rlCheckBufferLimit(2*36)) rlglDraw(); + rlCheckRenderBatchLimit(2*36); rlPushMatrix(); rlTranslatef(center.x, center.y, center.z); @@ -172,7 +182,7 @@ void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rota // Draw a color-filled triangle (vertex in counter-clockwise order!) void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color) { - if (rlCheckBufferLimit(3)) rlglDraw(); + rlCheckRenderBatchLimit(3); rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -187,7 +197,7 @@ void DrawTriangleStrip3D(Vector3 *points, int pointsCount, Color color) { if (pointsCount >= 3) { - if (rlCheckBufferLimit(3*(pointsCount - 2))) rlglDraw(); + rlCheckRenderBatchLimit(3*(pointsCount - 2)); rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -219,7 +229,7 @@ void DrawCube(Vector3 position, float width, float height, float length, Color c float y = 0.0f; float z = 0.0f; - if (rlCheckBufferLimit(36)) rlglDraw(); + rlCheckRenderBatchLimit(36); rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) @@ -300,7 +310,7 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co float y = 0.0f; float z = 0.0f; - if (rlCheckBufferLimit(36)) rlglDraw(); + rlCheckRenderBatchLimit(36); rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -377,9 +387,9 @@ void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float hei float y = position.y; float z = position.z; - if (rlCheckBufferLimit(36)) rlglDraw(); + rlCheckRenderBatchLimit(36); - rlEnableTexture(texture.id); + rlSetTexture(texture.id); //rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> rotate -> translate) @@ -428,7 +438,7 @@ void DrawCubeTexture(Texture2D texture, Vector3 position, float width, float hei rlEnd(); //rlPopMatrix(); - rlDisableTexture(); + rlSetTexture(0); } // Draw sphere @@ -441,7 +451,7 @@ void DrawSphere(Vector3 centerPos, float radius, Color color) void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color) { int numVertex = (rings + 2)*slices*6; - if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlCheckRenderBatchLimit(numVertex); rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> translate) @@ -455,25 +465,25 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color { for (int j = 0; j < slices; j++) { - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*i)), - cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*i)), - cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i)))*cosf(DEG2RAD*((j+1)*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); } } rlEnd(); @@ -484,7 +494,7 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color) { int numVertex = (rings + 2)*slices*6; - if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlCheckRenderBatchLimit(numVertex); rlPushMatrix(); // NOTE: Transformation is applied in inverse order (scale -> translate) @@ -498,26 +508,26 @@ void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Col { for (int j = 0; j < slices; j++) { - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*i)), - cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*((j+1)*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*((j+1)*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*(j + 1)/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*(j + 1)/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*(i+1))), - cosf(DEG2RAD*(270+(180/(rings + 1))*(i+1)))*cosf(DEG2RAD*(j*360/slices))); - rlVertex3f(cosf(DEG2RAD*(270+(180/(rings + 1))*i))*sinf(DEG2RAD*(j*360/slices)), - sinf(DEG2RAD*(270+(180/(rings + 1))*i)), - cosf(DEG2RAD*(270+(180/(rings + 1))*i))*cosf(DEG2RAD*(j*360/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1))), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*(i + 1)))*cosf(DEG2RAD*(360.0f*j/slices))); + rlVertex3f(cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*sinf(DEG2RAD*(360.0f*j/slices)), + sinf(DEG2RAD*(270 + (180.0f/(rings + 1))*i)), + cosf(DEG2RAD*(270 + (180.0f/(rings + 1))*i))*cosf(DEG2RAD*(360.0f*j/slices))); } } rlEnd(); @@ -531,7 +541,7 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h if (sides < 3) sides = 3; int numVertex = sides*6; - if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlCheckRenderBatchLimit(numVertex); rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -545,12 +555,12 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h for (int i = 0; i < 360; i += 360/sides) { rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); //Bottom Right - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); //Top Right + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom); //Bottom Right + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); //Top Right rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); //Top Left rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); //Bottom Left - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); //Top Right + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); //Top Right } // Draw Cap -------------------------------------------------------------------------------------- @@ -558,7 +568,7 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h { rlVertex3f(0, height, 0); rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); } } else @@ -568,7 +578,7 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h { rlVertex3f(0, height, 0); rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom); } } @@ -576,7 +586,7 @@ void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float h for (int i = 0; i < 360; i += 360/sides) { rlVertex3f(0, 0, 0); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom); rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); } rlEnd(); @@ -590,7 +600,7 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl if (sides < 3) sides = 3; int numVertex = sides*8; - if (rlCheckBufferLimit(numVertex)) rlglDraw(); + rlCheckRenderBatchLimit(numVertex); rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); @@ -601,12 +611,12 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl for (int i = 0; i < 360; i += 360/sides) { rlVertex3f(sinf(DEG2RAD*i)*radiusBottom, 0, cosf(DEG2RAD*i)*radiusBottom); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360/sides))*radiusBottom); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusBottom, 0, cosf(DEG2RAD*(i + 360.0f/sides))*radiusBottom); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); - rlVertex3f(sinf(DEG2RAD*(i + 360/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360/sides))*radiusTop); + rlVertex3f(sinf(DEG2RAD*(i + 360.0f/sides))*radiusTop, height, cosf(DEG2RAD*(i + 360.0f/sides))*radiusTop); rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); rlVertex3f(sinf(DEG2RAD*i)*radiusTop, height, cosf(DEG2RAD*i)*radiusTop); @@ -619,7 +629,7 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl // Draw a plane void DrawPlane(Vector3 centerPos, Vector2 size, Color color) { - if (rlCheckBufferLimit(4)) rlglDraw(); + rlCheckRenderBatchLimit(4); // NOTE: Plane is always created on XZ ground rlPushMatrix(); @@ -657,7 +667,7 @@ void DrawGrid(int slices, float spacing) { int halfSlices = slices/2; - if (rlCheckBufferLimit(slices*4)) rlglDraw(); + rlCheckRenderBatchLimit((slices + 2)*4); rlBegin(RL_LINES); for (int i = -halfSlices; i <= halfSlices; i++) @@ -686,29 +696,6 @@ void DrawGrid(int slices, float spacing) rlEnd(); } -// Draw gizmo -void DrawGizmo(Vector3 position) -{ - // NOTE: RGB = XYZ - float length = 1.0f; - - rlPushMatrix(); - rlTranslatef(position.x, position.y, position.z); - rlScalef(length, length, length); - - rlBegin(RL_LINES); - rlColor3f(1.0f, 0.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 0.0f); - rlColor3f(1.0f, 0.0f, 0.0f); rlVertex3f(1.0f, 0.0f, 0.0f); - - rlColor3f(0.0f, 1.0f, 0.0f); rlVertex3f(0.0f, 0.0f, 0.0f); - rlColor3f(0.0f, 1.0f, 0.0f); rlVertex3f(0.0f, 1.0f, 0.0f); - - rlColor3f(0.0f, 0.0f, 1.0f); rlVertex3f(0.0f, 0.0f, 0.0f); - rlColor3f(0.0f, 0.0f, 1.0f); rlVertex3f(0.0f, 0.0f, 1.0f); - rlEnd(); - rlPopMatrix(); -} - // Load model from files (mesh and material) Model LoadModel(const char *fileName) { @@ -721,7 +708,7 @@ Model LoadModel(const char *fileName) if (IsFileExtension(fileName, ".iqm")) model = LoadIQM(fileName); #endif #if defined(SUPPORT_FILEFORMAT_GLTF) - if (IsFileExtension(fileName, ".gltf") || IsFileExtension(fileName, ".glb")) model = LoadGLTF(fileName); + if (IsFileExtension(fileName, ".gltf;.glb")) model = LoadGLTF(fileName); #endif // Make sure model transform is set to identity matrix! @@ -741,7 +728,7 @@ Model LoadModel(const char *fileName) else { // Upload vertex data to GPU (static mesh) - for (int i = 0; i < model.meshCount; i++) rlLoadMesh(&model.meshes[i], false); + for (int i = 0; i < model.meshCount; i++) UploadMesh(&model.meshes[i], false); } if (model.materialCount == 0) @@ -782,16 +769,21 @@ Model LoadModelFromMesh(Mesh mesh) return model; } -// Unload model from memory (RAM and/or VRAM) +// Unload model (meshes/materials) from memory (RAM and/or VRAM) +// NOTE: This function takes care of all model elements, for a detailed control +// over them, use UnloadMesh() and UnloadMaterial() void UnloadModel(Model model) { + // Unload meshes for (int i = 0; i < model.meshCount; i++) UnloadMesh(model.meshes[i]); - // As the user could be sharing shaders and textures between models, - // we don't unload the material but just free it's maps, the user - // is responsible for freeing models shaders and textures + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, + // the user is responsible for freeing models shaders and textures for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); + // Unload arrays RL_FREE(model.meshes); RL_FREE(model.materials); RL_FREE(model.meshMaterial); @@ -800,38 +792,469 @@ void UnloadModel(Model model) RL_FREE(model.bones); RL_FREE(model.bindPose); - TRACELOG(LOG_INFO, "MODEL: Unloaded model from RAM and VRAM"); + TRACELOG(LOG_INFO, "MODEL: Unloaded model (and meshes) from RAM and VRAM"); } -// Load meshes from model file -Mesh *LoadMeshes(const char *fileName, int *meshCount) +// Unload model (but not meshes) from memory (RAM and/or VRAM) +void UnloadModelKeepMeshes(Model model) { - Mesh *meshes = NULL; - int count = 0; + // Unload materials maps + // NOTE: As the user could be sharing shaders and textures between models, + // we don't unload the material but just free it's maps, + // the user is responsible for freeing models shaders and textures + for (int i = 0; i < model.materialCount; i++) RL_FREE(model.materials[i].maps); - // TODO: Load meshes from file (OBJ, IQM, GLTF) + // Unload arrays + RL_FREE(model.meshes); + RL_FREE(model.materials); + RL_FREE(model.meshMaterial); - *meshCount = count; - return meshes; + // Unload animation data + RL_FREE(model.bones); + RL_FREE(model.bindPose); + + TRACELOG(LOG_INFO, "MODEL: Unloaded model (but not meshes) from RAM and VRAM"); } -// Unload mesh from memory (RAM and/or VRAM) +// Upload vertex data into a VAO (if supported) and VBO +void UploadMesh(Mesh *mesh, bool dynamic) +{ + if (mesh->vaoId > 0) + { + // Check if mesh has already been loaded in GPU + TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId); + return; + } + + mesh->vboId = (unsigned int *)RL_CALLOC(MAX_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); + + mesh->vaoId = 0; // Vertex Array Object + mesh->vboId[0] = 0; // Vertex buffer: positions + mesh->vboId[1] = 0; // Vertex buffer: texcoords + mesh->vboId[2] = 0; // Vertex buffer: normals + mesh->vboId[3] = 0; // Vertex buffer: colors + mesh->vboId[4] = 0; // Vertex buffer: tangents + mesh->vboId[5] = 0; // Vertex buffer: texcoords2 + mesh->vboId[6] = 0; // Vertex buffer: indices + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mesh->vaoId = rlLoadVertexArray(); + rlEnableVertexArray(mesh->vaoId); + + // NOTE: Attributes must be uploaded considering default locations points + + // Enable vertex attributes: position (shader-location = 0) + mesh->vboId[0] = rlLoadVertexBuffer(mesh->vertices, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(0, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(0); + + // Enable vertex attributes: texcoords (shader-location = 1) + mesh->vboId[1] = rlLoadVertexBuffer(mesh->texcoords, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(1, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(1); + + if (mesh->normals != NULL) + { + // Enable vertex attributes: normals (shader-location = 2) + mesh->vboId[2] = rlLoadVertexBuffer(mesh->normals, mesh->vertexCount*3*sizeof(float), dynamic); + rlSetVertexAttribute(2, 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(2); + } + else + { + // Default color vertex attribute set to WHITE + float value[3] = { 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(2, value, SHADER_ATTRIB_VEC3, 3); + rlDisableVertexAttribute(2); + } + + if (mesh->colors != NULL) + { + // Enable vertex attribute: color (shader-location = 3) + mesh->vboId[3] = rlLoadVertexBuffer(mesh->colors, mesh->vertexCount*4*sizeof(unsigned char), dynamic); + rlSetVertexAttribute(3, 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(3); + } + else + { + // Default color vertex attribute set to WHITE + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(3, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(3); + } + + if (mesh->tangents != NULL) + { + // Enable vertex attribute: tangent (shader-location = 4) + mesh->vboId[4] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), dynamic); + rlSetVertexAttribute(4, 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(4); + } + else + { + // Default tangents vertex attribute + float value[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + rlSetVertexAttributeDefault(4, value, SHADER_ATTRIB_VEC4, 4); + rlDisableVertexAttribute(4); + } + + if (mesh->texcoords2 != NULL) + { + // Enable vertex attribute: texcoord2 (shader-location = 5) + mesh->vboId[5] = rlLoadVertexBuffer(mesh->texcoords2, mesh->vertexCount*2*sizeof(float), dynamic); + rlSetVertexAttribute(5, 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(5); + } + else + { + // Default texcoord2 vertex attribute + float value[2] = { 0.0f, 0.0f }; + rlSetVertexAttributeDefault(5, value, SHADER_ATTRIB_VEC2, 2); + rlDisableVertexAttribute(5); + } + + if (mesh->indices != NULL) + { + mesh->vboId[6] = rlLoadVertexBufferElement(mesh->indices, mesh->triangleCount*3*sizeof(unsigned short), dynamic); + } + + if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); + else TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)"); + + rlDisableVertexArray(); +#endif +} + +// Update mesh vertex data in GPU for a specific buffer index +void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset) +{ + rlUpdateVertexBuffer(mesh.vboId[index], data, dataSize, offset); +} + +// Draw a 3d mesh with material and transform +void DrawMesh(Mesh mesh, Material material, Matrix transform) +{ + DrawMeshInstanced(mesh, material, &transform, 1); +} + +// Draw multiple mesh instances with material and different transforms +void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_VERTEX_ARRAY 0x8074 + #define GL_NORMAL_ARRAY 0x8075 + #define GL_COLOR_ARRAY 0x8076 + #define GL_TEXTURE_COORD_ARRAY 0x8078 + + rlEnableTexture(material.maps[MATERIAL_MAP_DIFFUSE].texture.id); + + rlEnableStatePointer(GL_VERTEX_ARRAY, mesh.vertices); + rlEnableStatePointer(GL_TEXTURE_COORD_ARRAY, mesh.texcoords); + rlEnableStatePointer(GL_NORMAL_ARRAY, mesh.normals); + rlEnableStatePointer(GL_COLOR_ARRAY, mesh.colors); + + rlPushMatrix(); + rlMultMatrixf(MatrixToFloat(transforms[0])); + rlColor4ub(material.maps[MATERIAL_MAP_DIFFUSE].color.r, + material.maps[MATERIAL_MAP_DIFFUSE].color.g, + material.maps[MATERIAL_MAP_DIFFUSE].color.b, + material.maps[MATERIAL_MAP_DIFFUSE].color.a); + + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, mesh.indices); + else rlDrawVertexArray(0, mesh.vertexCount); + rlPopMatrix(); + + rlDisableStatePointer(GL_VERTEX_ARRAY); + rlDisableStatePointer(GL_TEXTURE_COORD_ARRAY); + rlDisableStatePointer(GL_NORMAL_ARRAY); + rlDisableStatePointer(GL_COLOR_ARRAY); + + rlDisableTexture(); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check instancing + bool instancing = false; + if (instances < 1) return; + else if (instances > 1) instancing = true; + float16 *instanceTransforms = NULL; + unsigned int instancesVboId = 0; + + // Bind shader program + rlEnableShader(material.shader.id); + + // Send required data to shader (matrices, values) + //----------------------------------------------------- + // Upload to shader material.colDiffuse + if (material.shader.locs[SHADER_LOC_COLOR_DIFFUSE] != -1) + { + float values[4] = { + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.r/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.g/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.b/255.0f, + (float)material.maps[MATERIAL_MAP_DIFFUSE].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_DIFFUSE], values, SHADER_UNIFORM_VEC4, 1); + } + + // Upload to shader material.colSpecular (if location available) + if (material.shader.locs[SHADER_LOC_COLOR_SPECULAR] != -1) + { + float values[4] = { + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.r/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.g/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.b/255.0f, + (float)material.maps[SHADER_LOC_COLOR_SPECULAR].color.a/255.0f + }; + + rlSetUniform(material.shader.locs[SHADER_LOC_COLOR_SPECULAR], values, SHADER_UNIFORM_VEC4, 1); + } + + // Get a copy of current matrices to work with, + // just in case stereo render is required and we need to modify them + // NOTE: At this point the modelview matrix just contains the view matrix (camera) + // That's because BeginMode3D() sets it and there is no model-drawing function + // that modifies it, all use rlPushMatrix() and rlPopMatrix() + Matrix matView = rlGetMatrixModelview(); + Matrix matModelView = matView; + Matrix matProjection = rlGetMatrixProjection(); + + // Upload view and projection matrices (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_VIEW] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_VIEW], matView); + if (material.shader.locs[SHADER_LOC_MATRIX_PROJECTION] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_PROJECTION], matProjection); + + if (instancing) + { + // Create instances buffer + instanceTransforms = RL_MALLOC(instances*sizeof(float16)); + + // Fill buffer with instances transformations as float16 arrays + for (int i = 0; i < instances; i++) instanceTransforms[i] = MatrixToFloatV(transforms[i]); + + // Enable mesh VAO to attach new buffer + rlEnableVertexArray(mesh.vaoId); + + // This could alternatively use a static VBO and either glMapBuffer() or glBufferSubData(). + // It isn't clear which would be reliably faster in all cases and on all platforms, + // anecdotally glMapBuffer() seems very slow (syncs) while glBufferSubData() seems + // no faster, since we're transferring all the transform matrices anyway + instancesVboId = rlLoadVertexBuffer(instanceTransforms, instances*sizeof(float16), false); + + // Instances transformation matrices are send to shader attribute location: SHADER_LOC_MATRIX_MODEL + for (unsigned int i = 0; i < 4; i++) + { + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 4, RL_FLOAT, 0, sizeof(Matrix), (void *)(i*sizeof(Vector4))); + rlSetVertexAttributeDivisor(material.shader.locs[SHADER_LOC_MATRIX_MODEL] + i, 1); + } + + rlDisableVertexBuffer(); + rlDisableVertexArray(); + + // Accumulate internal matrix transform (push/pop) and view matrix + // NOTE: In this case, model instance transformation must be computed in the shader + matModelView = MatrixMultiply(rlGetMatrixTransform(), matView); + } + else + { + // Model transformation matrix is send to shader uniform location: SHADER_LOC_MATRIX_MODEL + if (material.shader.locs[SHADER_LOC_MATRIX_MODEL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MODEL], transforms[0]); + + // Accumulate several transformations: + // matView: rlgl internal modelview matrix (actually, just view matrix) + // rlGetMatrixTransform(): rlgl internal transform matrix due to push/pop matrix stack + // transform: function parameter transformation + matModelView = MatrixMultiply(transforms[0], MatrixMultiply(rlGetMatrixTransform(), matView)); + } + + // Upload model normal matrix (if locations available) + if (material.shader.locs[SHADER_LOC_MATRIX_NORMAL] != -1) rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_NORMAL], MatrixTranspose(MatrixInvert(matModelView))); + //----------------------------------------------------- + + // Bind active texture maps (if available) + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + if (material.maps[i].texture.id > 0) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Enable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlEnableTextureCubemap(material.maps[i].texture.id); + else rlEnableTexture(material.maps[i].texture.id); + + rlSetUniform(material.shader.locs[SHADER_LOC_MAP_DIFFUSE + i], &i, SHADER_UNIFORM_INT, 1); + } + } + + // Try binding vertex array objects (VAO) + // or use VBOs if not possible + if (!rlEnableVertexArray(mesh.vaoId)) + { + // Bind mesh VBO data: vertex position (shader-location = 0) + rlEnableVertexBuffer(mesh.vboId[0]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + rlEnableVertexBuffer(mesh.vboId[0]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind mesh VBO data: vertex texcoords (shader-location = 1) + rlEnableVertexBuffer(mesh.vboId[1]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + if (material.shader.locs[SHADER_LOC_VERTEX_NORMAL] != -1) + { + // Bind mesh VBO data: vertex normals (shader-location = 2) + rlEnableVertexBuffer(mesh.vboId[2]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL], 3, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_NORMAL]); + } + + // Bind mesh VBO data: vertex colors (shader-location = 3, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_COLOR] != -1) + { + if (mesh.vboId[3] != 0) + { + rlEnableVertexBuffer(mesh.vboId[3]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR], 4, RL_UNSIGNED_BYTE, 1, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + else + { + // Set default value for unused attribute + // NOTE: Required when using default shader and no VAO support + float value[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + rlSetVertexAttributeDefault(material.shader.locs[SHADER_LOC_VERTEX_COLOR], value, SHADER_ATTRIB_VEC2, 4); + rlDisableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_COLOR]); + } + } + + // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TANGENT] != -1) + { + rlEnableVertexBuffer(mesh.vboId[4]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT], 4, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TANGENT]); + } + + // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) + if (material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] != -1) + { + rlEnableVertexBuffer(mesh.vboId[5]); + rlSetVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02], 2, RL_FLOAT, 0, 0, 0); + rlEnableVertexAttribute(material.shader.locs[SHADER_LOC_VERTEX_TEXCOORD02]); + } + + if (mesh.indices != NULL) rlEnableVertexBufferElement(mesh.vboId[6]); + } + + int eyesCount = 1; + if (rlIsStereoRenderEnabled()) eyesCount = 2; + + for (int eye = 0; eye < eyesCount; eye++) + { + // Calculate model-view-projection matrix (MVP) + Matrix matMVP = MatrixIdentity(); + if (eyesCount == 1) matMVP = MatrixMultiply(matModelView, matProjection); + else + { + // Setup current eye viewport (half screen width) + rlViewport(eye*rlGetFramebufferWidth()/2, 0, rlGetFramebufferWidth()/2, rlGetFramebufferHeight()); + matMVP = MatrixMultiply(MatrixMultiply(matModelView, rlGetMatrixViewOffsetStereo(eye)), rlGetMatrixProjectionStereo(eye)); + } + + // Send combined model-view-projection matrix to shader + rlSetUniformMatrix(material.shader.locs[SHADER_LOC_MATRIX_MVP], matMVP); + + if (instancing) // Draw mesh instanced + { + if (mesh.indices != NULL) rlDrawVertexArrayElementsInstanced(0, mesh.triangleCount*3, 0, instances); + else rlDrawVertexArrayInstanced(0, mesh.vertexCount, instances); + } + else // Draw mesh + { + if (mesh.indices != NULL) rlDrawVertexArrayElements(0, mesh.triangleCount*3, 0); + else rlDrawVertexArray(0, mesh.vertexCount); + } + } + + // Unbind all binded texture maps + for (int i = 0; i < MAX_MATERIAL_MAPS; i++) + { + // Select current shader texture slot + rlActiveTextureSlot(i); + + // Disable texture for active slot + if ((i == MATERIAL_MAP_IRRADIANCE) || + (i == MATERIAL_MAP_PREFILTER) || + (i == MATERIAL_MAP_CUBEMAP)) rlDisableTextureCubemap(); + else rlDisableTexture(); + } + + // Disable all possible vertex array objects (or VBOs) + rlDisableVertexArray(); + rlDisableVertexBuffer(); + rlDisableVertexBufferElement(); + + // Disable shader program + rlDisableShader(); + + if (instancing) + { + // Remove instance transforms buffer + rlUnloadVertexBuffer(instancesVboId); + RL_FREE(instanceTransforms); + } + else + { + // Restore rlgl internal modelview and projection matrices + rlSetMatrixModelview(matView); + rlSetMatrixProjection(matProjection); + } +#endif +} + +// Unload mesh from memory (RAM and VRAM) void UnloadMesh(Mesh mesh) { - rlUnloadMesh(mesh); + // Unload rlgl mesh vboId data + rlUnloadVertexArray(mesh.vaoId); + + for (int i = 0; i < MAX_MESH_VERTEX_BUFFERS; i++) rlUnloadVertexBuffer(mesh.vboId[i]); RL_FREE(mesh.vboId); + + RL_FREE(mesh.vertices); + RL_FREE(mesh.texcoords); + RL_FREE(mesh.normals); + RL_FREE(mesh.colors); + RL_FREE(mesh.tangents); + RL_FREE(mesh.texcoords2); + RL_FREE(mesh.indices); + + RL_FREE(mesh.animVertices); + RL_FREE(mesh.animNormals); + RL_FREE(mesh.boneWeights); + RL_FREE(mesh.boneIds); } // Export mesh data to file -void ExportMesh(Mesh mesh, const char *fileName) +bool ExportMesh(Mesh mesh, const char *fileName) { + bool success = false; + if (IsFileExtension(fileName, ".obj")) { // Estimated data size, it should be enough... - int dataSize = mesh.vertexCount/3*strlen("v 0000.00f 0000.00f 0000.00f") + - mesh.vertexCount/2*strlen("vt 0.000f 0.00f") + - mesh.vertexCount/3*strlen("vn 0.000f 0.00f 0.00f") + - mesh.triangleCount/3*strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000"); + int dataSize = mesh.vertexCount/3* (int)strlen("v 0000.00f 0000.00f 0000.00f") + + mesh.vertexCount/2* (int)strlen("vt 0.000f 0.00f") + + mesh.vertexCount/3* (int)strlen("vn 0.000f 0.00f 0.00f") + + mesh.triangleCount/3* (int)strlen("f 00000/00000/00000 00000/00000/00000 00000/00000/00000"); // NOTE: Text data buffer size is estimated considering mesh data size char *txtData = (char *)RL_CALLOC(dataSize + 2000, sizeof(char)); @@ -875,16 +1298,19 @@ void ExportMesh(Mesh mesh, const char *fileName) bytesCount += sprintf(txtData + bytesCount, "\n"); // NOTE: Text data length exported is determined by '\0' (NULL) character - SaveFileText(fileName, txtData); + success = SaveFileText(fileName, txtData); RL_FREE(txtData); } - else if (IsFileExtension(fileName, ".raw")) + else if (IsFileExtension(fileName, ".raw")) { // TODO: Support additional file formats to export mesh vertex data } + + return success; } + // Load materials from model file Material *LoadMaterials(const char *fileName, int *materialCount) { @@ -896,12 +1322,10 @@ Material *LoadMaterials(const char *fileName, int *materialCount) #if defined(SUPPORT_FILEFORMAT_MTL) if (IsFileExtension(fileName, ".mtl")) { - tinyobj_material_t *mats; + tinyobj_material_t *mats = NULL; int result = tinyobj_parse_mtl_file(&mats, &count, fileName); - if (result != TINYOBJ_SUCCESS) { - TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName); - } + if (result != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MATERIAL: [%s] Failed to parse materials file", fileName); // TODO: Process materials to return @@ -912,7 +1336,10 @@ Material *LoadMaterials(const char *fileName, int *materialCount) #endif // Set materials shader to default (DIFFUSE, SPECULAR, NORMAL) - for (unsigned int i = 0; i < count; i++) materials[i].shader = GetShaderDefault(); + if (materials != NULL) + { + for (unsigned int i = 0; i < count; i++) materials[i].shader = rlGetShaderDefault(); + } *materialCount = count; return materials; @@ -924,13 +1351,13 @@ Material LoadMaterialDefault(void) Material material = { 0 }; material.maps = (MaterialMap *)RL_CALLOC(MAX_MATERIAL_MAPS, sizeof(MaterialMap)); - material.shader = GetShaderDefault(); - material.maps[MAP_DIFFUSE].texture = GetTextureDefault(); // White texture (1x1 pixel) - //material.maps[MAP_NORMAL].texture; // NOTE: By default, not set - //material.maps[MAP_SPECULAR].texture; // NOTE: By default, not set + material.shader = rlGetShaderDefault(); + material.maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // White texture (1x1 pixel) + //material.maps[MATERIAL_MAP_NORMAL].texture; // NOTE: By default, not set + //material.maps[MATERIAL_MAP_SPECULAR].texture; // NOTE: By default, not set - material.maps[MAP_DIFFUSE].color = WHITE; // Diffuse color - material.maps[MAP_SPECULAR].color = WHITE; // Specular color + material.maps[MATERIAL_MAP_DIFFUSE].color = WHITE; // Diffuse color + material.maps[MATERIAL_MAP_SPECULAR].color = WHITE; // Specular color return material; } @@ -939,18 +1366,18 @@ Material LoadMaterialDefault(void) void UnloadMaterial(Material material) { // Unload material shader (avoid unloading default shader, managed by raylib) - if (material.shader.id != GetShaderDefault().id) UnloadShader(material.shader); + if (material.shader.id != rlGetShaderDefault().id) UnloadShader(material.shader); // Unload loaded texture maps (avoid unloading default texture, managed by raylib) for (int i = 0; i < MAX_MATERIAL_MAPS; i++) { - if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id); + if (material.maps[i].texture.id != rlGetTextureDefault().id) rlUnloadTexture(material.maps[i].texture.id); } RL_FREE(material.maps); } -// Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...) +// Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) // NOTE: Previous texture should be manually unloaded void SetMaterialTexture(Material *material, int mapType, Texture2D texture) { @@ -968,210 +1395,14 @@ void SetModelMeshMaterial(Model *model, int meshId, int materialId) // Load model animations from file ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) { - #define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number - #define IQM_VERSION 2 // only IQM version 2 supported - - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; + ModelAnimation *animations = NULL; - typedef struct IQMHeader { - char magic[16]; - unsigned int version; - unsigned int filesize; - unsigned int flags; - unsigned int num_text, ofs_text; - unsigned int num_meshes, ofs_meshes; - unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; - unsigned int num_triangles, ofs_triangles, ofs_adjacency; - unsigned int num_joints, ofs_joints; - unsigned int num_poses, ofs_poses; - unsigned int num_anims, ofs_anims; - unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; - unsigned int num_comment, ofs_comment; - unsigned int num_extensions, ofs_extensions; - } IQMHeader; - - typedef struct IQMPose { - int parent; - unsigned int mask; - float channeloffset[10]; - float channelscale[10]; - } IQMPose; - - typedef struct IQMAnim { - unsigned int name; - unsigned int first_frame, num_frames; - float framerate; - unsigned int flags; - } IQMAnim; - - // In case file can not be read, return an empty model - if (fileDataPtr == NULL) return NULL; - - // Read IQM header - IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; - - if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) - { - TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); - return NULL; - } - - if (iqmHeader->version != IQM_VERSION) - { - TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); - return NULL; - } - - // Get bones data - IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); - //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); - //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile); - memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); - - // Get animations data - *animCount = iqmHeader->num_anims; - IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); - //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); - //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile); - memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); - - ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); - - // frameposes - unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); - //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); - //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile); - memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); - - for (unsigned int a = 0; a < iqmHeader->num_anims; a++) - { - animations[a].frameCount = anim[a].num_frames; - animations[a].boneCount = iqmHeader->num_poses; - animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); - animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); - //animations[a].framerate = anim.framerate; // TODO: Use framerate? - - for (unsigned int j = 0; j < iqmHeader->num_poses; j++) - { - strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); - animations[a].bones[j].parent = poses[j].parent; - } - - for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); - - int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; - - for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) - { - for (unsigned int i = 0; i < iqmHeader->num_poses; i++) - { - animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0]; - - if (poses[i].mask & 0x01) - { - animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; - dcounter++; - } - - animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1]; - - if (poses[i].mask & 0x02) - { - animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; - dcounter++; - } - - animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2]; - - if (poses[i].mask & 0x04) - { - animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; - - if (poses[i].mask & 0x08) - { - animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; - - if (poses[i].mask & 0x10) - { - animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; - - if (poses[i].mask & 0x20) - { - animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; - - if (poses[i].mask & 0x40) - { - animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; - dcounter++; - } - - animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7]; - - if (poses[i].mask & 0x80) - { - animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; - dcounter++; - } - - animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8]; - - if (poses[i].mask & 0x100) - { - animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; - dcounter++; - } - - animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9]; - - if (poses[i].mask & 0x200) - { - animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; - dcounter++; - } - - animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation); - } - } - - // Build frameposes - for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) - { - for (int i = 0; i < animations[a].boneCount; i++) - { - if (animations[a].bones[i].parent >= 0) - { - animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation); - animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation); - animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation); - animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); - } - } - } - } - - RL_FREE(fileData); - - RL_FREE(framedata); - RL_FREE(poses); - RL_FREE(anim); +#if defined(SUPPORT_FILEFORMAT_IQM) + if (IsFileExtension(fileName, ".iqm")) animations = LoadIQMModelAnimations(fileName, animCount); +#endif +#if defined(SUPPORT_FILEFORMAT_GLTF) + if (IsFileExtension(fileName, ".gltf;.glb")) animations = LoadGLTFModelAnimations(fileName, animCount); +#endif return animations; } @@ -1200,47 +1431,69 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, int frame) int vCounter = 0; int boneCounter = 0; int boneId = 0; + float boneWeight = 0.0; for (int i = 0; i < model.meshes[m].vertexCount; i++) { - boneId = model.meshes[m].boneIds[boneCounter]; - inTranslation = model.bindPose[boneId].translation; - inRotation = model.bindPose[boneId].rotation; - //inScale = model.bindPose[boneId].scale; - outTranslation = anim.framePoses[frame][boneId].translation; - outRotation = anim.framePoses[frame][boneId].rotation; - outScale = anim.framePoses[frame][boneId].scale; + model.meshes[m].animVertices[vCounter] = 0; + model.meshes[m].animVertices[vCounter + 1] = 0; + model.meshes[m].animVertices[vCounter + 2] = 0; - // Vertices processing - // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position) - animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] }; - animVertex = Vector3Multiply(animVertex, outScale); - animVertex = Vector3Subtract(animVertex, inTranslation); - animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); - animVertex = Vector3Add(animVertex, outTranslation); - model.meshes[m].animVertices[vCounter] = animVertex.x; - model.meshes[m].animVertices[vCounter + 1] = animVertex.y; - model.meshes[m].animVertices[vCounter + 2] = animVertex.z; + model.meshes[m].animNormals[vCounter] = 0; + model.meshes[m].animNormals[vCounter + 1] = 0; + model.meshes[m].animNormals[vCounter + 2] = 0; - // Normals processing - // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) - animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; - animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); - model.meshes[m].animNormals[vCounter] = animNormal.x; - model.meshes[m].animNormals[vCounter + 1] = animNormal.y; - model.meshes[m].animNormals[vCounter + 2] = animNormal.z; + for (int j = 0; j < 4; j++) + { + boneId = model.meshes[m].boneIds[boneCounter]; + boneWeight = model.meshes[m].boneWeights[boneCounter]; + inTranslation = model.bindPose[boneId].translation; + inRotation = model.bindPose[boneId].rotation; + //inScale = model.bindPose[boneId].scale; + outTranslation = anim.framePoses[frame][boneId].translation; + outRotation = anim.framePoses[frame][boneId].rotation; + outScale = anim.framePoses[frame][boneId].scale; + + // Vertices processing + // NOTE: We use meshes.vertices (default vertex position) to calculate meshes.animVertices (animated vertex position) + animVertex = (Vector3){ model.meshes[m].vertices[vCounter], model.meshes[m].vertices[vCounter + 1], model.meshes[m].vertices[vCounter + 2] }; + animVertex = Vector3Multiply(animVertex, outScale); + animVertex = Vector3Subtract(animVertex, inTranslation); + animVertex = Vector3RotateByQuaternion(animVertex, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + animVertex = Vector3Add(animVertex, outTranslation); + model.meshes[m].animVertices[vCounter] += animVertex.x * boneWeight; + model.meshes[m].animVertices[vCounter + 1] += animVertex.y * boneWeight; + model.meshes[m].animVertices[vCounter + 2] += animVertex.z * boneWeight; + + // Normals processing + // NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) + if (model.meshes[m].normals != NULL) + { + animNormal = (Vector3){ model.meshes[m].normals[vCounter], model.meshes[m].normals[vCounter + 1], model.meshes[m].normals[vCounter + 2] }; + animNormal = Vector3RotateByQuaternion(animNormal, QuaternionMultiply(outRotation, QuaternionInvert(inRotation))); + model.meshes[m].animNormals[vCounter] += animNormal.x * boneWeight; + model.meshes[m].animNormals[vCounter + 1] += animNormal.y * boneWeight; + model.meshes[m].animNormals[vCounter + 2] += animNormal.z * boneWeight; + } + boneCounter += 1; + } vCounter += 3; - - boneCounter += 4; } // Upload new vertex data to GPU for model drawing - rlUpdateBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex position - rlUpdateBuffer(model.meshes[m].vboId[2], model.meshes[m].animNormals, model.meshes[m].vertexCount*3*sizeof(float)); // Update vertex normals + rlUpdateVertexBuffer(model.meshes[m].vboId[0], model.meshes[m].animVertices, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex position + rlUpdateVertexBuffer(model.meshes[m].vboId[2], model.meshes[m].animNormals, model.meshes[m].vertexCount*3*sizeof(float), 0); // Update vertex normals } } } +// Unload animation array data +void UnloadModelAnimations(ModelAnimation* animations, unsigned int count) +{ + for (unsigned int i = 0; i < count; i++) UnloadModelAnimation(animations[i]); + RL_FREE(animations); +} + // Unload animation data void UnloadModelAnimation(ModelAnimation anim) { @@ -1273,7 +1526,6 @@ bool IsModelAnimationValid(Model model, ModelAnimation anim) Mesh GenMeshPoly(int sides, float radius) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); if (sides < 3) return mesh; @@ -1287,7 +1539,7 @@ Mesh GenMeshPoly(int sides, float radius) { vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f }; vertices[v + 1] = (Vector3){ sinf(DEG2RAD*d)*radius, 0.0f, cosf(DEG2RAD*d)*radius }; - vertices[v + 2] = (Vector3){sinf(DEG2RAD*(d+dStep)) * radius, 0.0f, cosf(DEG2RAD * (d+dStep)) * radius }; + vertices[v + 2] = (Vector3){sinf(DEG2RAD*(d+dStep))*radius, 0.0f, cosf(DEG2RAD*(d+dStep))*radius }; d += dStep; } @@ -1333,7 +1585,8 @@ Mesh GenMeshPoly(int sides, float radius) RL_FREE(texcoords); // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + // NOTE: mesh.vboId array is allocated inside UploadMesh() + UploadMesh(&mesh, false); return mesh; } @@ -1342,7 +1595,6 @@ Mesh GenMeshPoly(int sides, float radius) Mesh GenMeshPlane(float width, float length, int resX, int resZ) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); #define CUSTOM_MESH_GEN_PLANE #if defined(CUSTOM_MESH_GEN_PLANE) @@ -1445,7 +1697,6 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) mesh.vertices = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); mesh.texcoords = (float *)RL_MALLOC(plane->ntriangles*3*2*sizeof(float)); mesh.normals = (float *)RL_MALLOC(plane->ntriangles*3*3*sizeof(float)); - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); mesh.vertexCount = plane->ntriangles*3; mesh.triangleCount = plane->ntriangles; @@ -1468,7 +1719,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) #endif // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + UploadMesh(&mesh, false); return mesh; } @@ -1477,7 +1728,6 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ) Mesh GenMeshCube(float width, float height, float length) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); #define CUSTOM_MESH_GEN_CUBE #if defined(CUSTOM_MESH_GEN_CUBE) @@ -1634,85 +1884,93 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron #endif // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + UploadMesh(&mesh, false); return mesh; } // Generate sphere mesh (standard sphere) -RLAPI Mesh GenMeshSphere(float radius, int rings, int slices) +Mesh GenMeshSphere(float radius, int rings, int slices) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); - par_shapes_scale(sphere, radius, radius, radius); - // NOTE: Soft normals are computed internally - - mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); - - mesh.vertexCount = sphere->ntriangles*3; - mesh.triangleCount = sphere->ntriangles; - - for (int k = 0; k < mesh.vertexCount; k++) + if ((rings >= 3) && (slices >= 3)) { - mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; - mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; - mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally - mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; - mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; - mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); - mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; - mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); } - - par_shapes_free_mesh(sphere); - - // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: sphere"); return mesh; } // Generate hemi-sphere mesh (half sphere, no bottom cap) -RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices) +Mesh GenMeshHemiSphere(float radius, int rings, int slices) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); - par_shapes_scale(sphere, radius, radius, radius); - // NOTE: Soft normals are computed internally - - mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); - - mesh.vertexCount = sphere->ntriangles*3; - mesh.triangleCount = sphere->ntriangles; - - for (int k = 0; k < mesh.vertexCount; k++) + if ((rings >= 3) && (slices >= 3)) { - mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; - mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; - mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + if (radius < 0.0f) radius = 0.0f; - mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; - mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; - mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings); + par_shapes_scale(sphere, radius, radius, radius); + // NOTE: Soft normals are computed internally - mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; - mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + mesh.vertices = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(sphere->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(sphere->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = sphere->ntriangles*3; + mesh.triangleCount = sphere->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3]; + mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2]; + + mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3]; + mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(sphere); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); } - - par_shapes_free_mesh(sphere); - - // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: hemisphere"); return mesh; } @@ -1721,58 +1979,61 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices) Mesh GenMeshCylinder(float radius, float height, int slices) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - // Instance a cylinder that sits on the Z=0 plane using the given tessellation - // levels across the UV domain. Think of "slices" like a number of pizza - // slices, and "stacks" like a number of stacked rings. - // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale - par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); - par_shapes_scale(cylinder, radius, radius, height); - par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 }); - par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 }); - - // Generate an orientable disk shape (top cap) - par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 }); - capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); - for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; - par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); - par_shapes_translate(capTop, 0, height, 0); - - // Generate an orientable disk shape (bottom cap) - par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); - capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); - for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; - par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); - - par_shapes_merge_and_free(cylinder, capTop); - par_shapes_merge_and_free(cylinder, capBottom); - - mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); - - mesh.vertexCount = cylinder->ntriangles*3; - mesh.triangleCount = cylinder->ntriangles; - - for (int k = 0; k < mesh.vertexCount; k++) + if (slices >= 3) { - mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; - mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; - mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; + // Instance a cylinder that sits on the Z=0 plane using the given tessellation + // levels across the UV domain. Think of "slices" like a number of pizza + // slices, and "stacks" like a number of stacked rings. + // Height and radius are both 1.0, but they can easily be changed with par_shapes_scale + par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8); + par_shapes_scale(cylinder, radius, radius, height); + par_shapes_rotate(cylinder, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_rotate(cylinder, PI/2.0f, (float[]){ 0, 1, 0 }); - mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; - mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; - mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; + // Generate an orientable disk shape (top cap) + par_shapes_mesh *capTop = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, 1 }); + capTop->tcoords = PAR_MALLOC(float, 2*capTop->npoints); + for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f; + par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 }); + par_shapes_translate(capTop, 0, height, 0); - mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; - mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; + // Generate an orientable disk shape (bottom cap) + par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 }); + capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints); + for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f; + par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 }); + + par_shapes_merge_and_free(cylinder, capTop); + par_shapes_merge_and_free(cylinder, capBottom); + + mesh.vertices = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(cylinder->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(cylinder->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = cylinder->ntriangles*3; + mesh.triangleCount = cylinder->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3]; + mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2]; + + mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3]; + mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(cylinder); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); } - - par_shapes_free_mesh(cylinder); - - // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: cylinder"); return mesh; } @@ -1781,41 +2042,44 @@ Mesh GenMeshCylinder(float radius, float height, int slices) Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - if (radius > 1.0f) radius = 1.0f; - else if (radius < 0.1f) radius = 0.1f; - - // Create a donut that sits on the Z=0 plane with the specified inner radius - // The outer radius can be controlled with par_shapes_scale - par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); - par_shapes_scale(torus, size/2, size/2, size/2); - - mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); - - mesh.vertexCount = torus->ntriangles*3; - mesh.triangleCount = torus->ntriangles; - - for (int k = 0; k < mesh.vertexCount; k++) + if ((sides >= 3) && (radSeg >= 3)) { - mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; - mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; - mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; + if (radius > 1.0f) radius = 1.0f; + else if (radius < 0.1f) radius = 0.1f; - mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; - mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; - mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; + // Create a donut that sits on the Z=0 plane with the specified inner radius + // The outer radius can be controlled with par_shapes_scale + par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius); + par_shapes_scale(torus, size/2, size/2, size/2); - mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; - mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; + mesh.vertices = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(torus->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(torus->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = torus->ntriangles*3; + mesh.triangleCount = torus->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = torus->points[torus->triangles[k]*3]; + mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2]; + + mesh.normals[k*3] = torus->normals[torus->triangles[k]*3]; + mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(torus); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); } - - par_shapes_free_mesh(torus); - - // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: torus"); return mesh; } @@ -1824,39 +2088,42 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides) Mesh GenMeshKnot(float radius, float size, int radSeg, int sides) { Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - if (radius > 3.0f) radius = 3.0f; - else if (radius < 0.5f) radius = 0.5f; - - par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius); - par_shapes_scale(knot, size, size, size); - - mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); - mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float)); - mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); - - mesh.vertexCount = knot->ntriangles*3; - mesh.triangleCount = knot->ntriangles; - - for (int k = 0; k < mesh.vertexCount; k++) + if ((sides >= 3) && (radSeg >= 3)) { - mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; - mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; - mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; + if (radius > 3.0f) radius = 3.0f; + else if (radius < 0.5f) radius = 0.5f; - mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; - mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; - mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; + par_shapes_mesh *knot = par_shapes_create_trefoil_knot(radSeg, sides, radius); + par_shapes_scale(knot, size, size, size); - mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; - mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; + mesh.vertices = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + mesh.texcoords = (float *)RL_MALLOC(knot->ntriangles*3*2*sizeof(float)); + mesh.normals = (float *)RL_MALLOC(knot->ntriangles*3*3*sizeof(float)); + + mesh.vertexCount = knot->ntriangles*3; + mesh.triangleCount = knot->ntriangles; + + for (int k = 0; k < mesh.vertexCount; k++) + { + mesh.vertices[k*3] = knot->points[knot->triangles[k]*3]; + mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1]; + mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2]; + + mesh.normals[k*3] = knot->normals[knot->triangles[k]*3]; + mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1]; + mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2]; + + mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2]; + mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1]; + } + + par_shapes_free_mesh(knot); + + // Upload vertex data to GPU (static mesh) + UploadMesh(&mesh, false); } - - par_shapes_free_mesh(knot); - - // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + else TRACELOG(LOG_WARNING, "MESH: Failed to generate mesh: knot"); return mesh; } @@ -1868,12 +2135,11 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) #define GRAY_VALUE(c) ((c.r+c.g+c.b)/3) Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); int mapX = heightmap.width; int mapZ = heightmap.height; - Color *pixels = GetImageData(heightmap); + Color *pixels = LoadImageColors(heightmap); // NOTE: One vertex per pixel mesh.triangleCount = (mapX-1)*(mapZ-1)*2; // One quad every four pixels @@ -1893,10 +2159,10 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) Vector3 scaleFactor = { size.x/mapX, size.y/255.0f, size.z/mapZ }; - Vector3 vA; - Vector3 vB; - Vector3 vC; - Vector3 vN; + Vector3 vA = { 0 }; + Vector3 vB = { 0 }; + Vector3 vC = { 0 }; + Vector3 vN = { 0 }; for (int z = 0; z < mapZ-1; z++) { @@ -1989,10 +2255,10 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size) } } - RL_FREE(pixels); + UnloadImageColors(pixels); // Unload pixels color data // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + UploadMesh(&mesh, false); return mesh; } @@ -2004,9 +2270,8 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) Mesh mesh = { 0 }; - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - Color *cubicmapPixels = GetImageData(cubicmap); + Color *pixels = LoadImageColors(cubicmap); int mapWidth = cubicmap.width; int mapHeight = cubicmap.height; @@ -2064,7 +2329,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) Vector3 v8 = { w*(x + 0.5f), 0, h*(z + 0.5f) }; // We check pixel color to be WHITE -> draw full cube - if (COLOR_EQUAL(cubicmapPixels[z*cubicmap.width + x], WHITE)) + if (COLOR_EQUAL(pixels[z*cubicmap.width + x], WHITE)) { // Define triangles and checking collateral cubes //------------------------------------------------ @@ -2121,7 +2386,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) tcCounter += 6; // Checking cube on bottom of current cube - if (((z < cubicmap.height - 1) && COLOR_EQUAL(cubicmapPixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1)) + if (((z < cubicmap.height - 1) && COLOR_EQUAL(pixels[(z + 1)*cubicmap.width + x], BLACK)) || (z == cubicmap.height - 1)) { // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 // NOTE: Collateral occluded faces are not generated @@ -2151,7 +2416,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) } // Checking cube on top of current cube - if (((z > 0) && COLOR_EQUAL(cubicmapPixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0)) + if (((z > 0) && COLOR_EQUAL(pixels[(z - 1)*cubicmap.width + x], BLACK)) || (z == 0)) { // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 // NOTE: Collateral occluded faces are not generated @@ -2181,7 +2446,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) } // Checking cube on right of current cube - if (((x < cubicmap.width - 1) && COLOR_EQUAL(cubicmapPixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1)) + if (((x < cubicmap.width - 1) && COLOR_EQUAL(pixels[z*cubicmap.width + (x + 1)], BLACK)) || (x == cubicmap.width - 1)) { // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 // NOTE: Collateral occluded faces are not generated @@ -2211,7 +2476,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) } // Checking cube on left of current cube - if (((x > 0) && COLOR_EQUAL(cubicmapPixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0)) + if (((x > 0) && COLOR_EQUAL(pixels[z*cubicmap.width + (x - 1)], BLACK)) || (x == 0)) { // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 // NOTE: Collateral occluded faces are not generated @@ -2241,7 +2506,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) } } // We check pixel color to be BLACK, we will only draw floor and roof - else if (COLOR_EQUAL(cubicmapPixels[z*cubicmap.width + x], BLACK)) + else if (COLOR_EQUAL(pixels[z*cubicmap.width + x], BLACK)) { // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) mapVertices[vCounter] = v1; @@ -2341,10 +2606,10 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) RL_FREE(mapNormals); RL_FREE(mapTexcoords); - RL_FREE(cubicmapPixels); // Free image pixel data + UnloadImageColors(pixels); // Unload pixels color data // Upload vertex data to GPU (static mesh) - rlLoadMesh(&mesh, false); + UploadMesh(&mesh, false); return mesh; } @@ -2352,7 +2617,7 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize) // Compute mesh bounding box limits // NOTE: minVertex and maxVertex should be transformed by model transform matrix -BoundingBox MeshBoundingBox(Mesh mesh) +BoundingBox GetMeshBoundingBox(Mesh mesh) { // Get min and max vertex to construct bounds (AABB) Vector3 minVertex = { 0 }; @@ -2455,7 +2720,7 @@ void MeshTangents(Mesh *mesh) RL_FREE(tan2); // Load a new tangent attributes buffer - mesh->vboId[LOC_VERTEX_TANGENT] = rlLoadAttribBuffer(mesh->vaoId, LOC_VERTEX_TANGENT, mesh->tangents, mesh->vertexCount*4*sizeof(float), false); + mesh->vboId[SHADER_LOC_VERTEX_TANGENT] = rlLoadVertexBuffer(mesh->tangents, mesh->vertexCount*4*sizeof(float), false); TRACELOG(LOG_INFO, "MESH: Tangents data computed for provided mesh"); } @@ -2473,73 +2738,6 @@ void MeshBinormals(Mesh *mesh) } } -// Smooth (average) vertex normals -void MeshNormalsSmooth(Mesh *mesh) -{ - #define EPSILON 0.000001 // A small number - - int uvCounter = 0; - Vector3 *uniqueVertices = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3)); - Vector3 *summedNormals = (Vector3 *)RL_CALLOC(mesh->vertexCount, sizeof(Vector3)); - - int *uniqueIndices = (int *)RL_CALLOC(mesh->vertexCount, sizeof(int)); - - // Sum normals grouped by vertex - for (int i = 0; i < mesh->vertexCount; i++) - { - Vector3 v = { mesh->vertices[(i + 0)*3 + 0], mesh->vertices[(i + 0)*3 + 1], mesh->vertices[(i + 0)*3 + 2] }; - Vector3 n = { mesh->normals[(i + 0)*3 + 0], mesh->normals[(i + 0)*3 + 1], mesh->normals[(i + 0)*3 + 2] }; - - bool matched = false; - - // TODO: Matching vertices is brute force O(N^2). Do it more efficiently? - for (int j = 0; j < uvCounter; j++) - { - Vector3 uv = uniqueVertices[j]; - - bool match = true; - match = match && fabs(uv.x - v.x) < EPSILON; - match = match && fabs(uv.y - v.y) < EPSILON; - match = match && fabs(uv.z - v.z) < EPSILON; - - if (match) - { - matched = true; - summedNormals[j] = Vector3Add(summedNormals[j], n); - uniqueIndices[i] = j; - break; - } - } - - if (!matched) - { - int j = uvCounter++; - uniqueVertices[j] = v; - summedNormals[j] = n; - uniqueIndices[i] = j; - } - } - - // Average and update normals - for (int i = 0; i < mesh->vertexCount; i++) - { - int j = uniqueIndices[i]; - Vector3 n = Vector3Normalize(summedNormals[j]); - mesh->normals[(i + 0)*3 + 0] = n.x; - mesh->normals[(i + 0)*3 + 1] = n.y; - mesh->normals[(i + 0)*3 + 2] = n.z; - } - - // 2=normals, see rlUpdateMeshAt() - rlUpdateMesh(*mesh, 2, mesh->vertexCount); - - RL_FREE(uniqueVertices); - RL_FREE(summedNormals); - RL_FREE(uniqueIndices); - - TRACELOG(LOG_INFO, "MESH: Normals smoothed (%d vertices, %d unique)", mesh->vertexCount, uvCounter); -} - // Draw a model (with texture if set) void DrawModel(Model model, Vector3 position, float scale, Color tint) { @@ -2565,8 +2763,7 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota for (int i = 0; i < model.meshCount; i++) { - // TODO: Review color + tint premultiplication mechanism - Color color = model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color; + Color color = model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color; Color colorTint = WHITE; colorTint.r = (unsigned char)((((float)color.r/255.0)*((float)tint.r/255.0))*255.0f); @@ -2574,9 +2771,9 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota colorTint.b = (unsigned char)((((float)color.b/255.0)*((float)tint.b/255.0))*255.0f); colorTint.a = (unsigned char)((((float)color.a/255.0)*((float)tint.a/255.0))*255.0f); - model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color = colorTint; - rlDrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); - model.materials[model.meshMaterial[i]].maps[MAP_DIFFUSE].color = color; + model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = colorTint; + DrawMesh(model.meshes[i], model.materials[model.meshMaterial[i]], model.transform); + model.materials[model.meshMaterial[i]].maps[MATERIAL_MAP_DIFFUSE].color = color; } } @@ -2601,19 +2798,24 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float } // Draw a billboard -void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint) +void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float size, Color tint) { - Rectangle sourceRec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; - DrawBillboardRec(camera, texture, sourceRec, center, size, tint); + DrawBillboardRec(camera, texture, source, position, (Vector2){ size, size }, tint); } // Draw a billboard (part of a texture defined by a rectangle) -void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint) +void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint) { - // NOTE: Billboard size will maintain sourceRec aspect ratio, size will represent billboard width - Vector2 sizeRatio = { size, size*(float)sourceRec.height/sourceRec.width }; + DrawBillboardPro(camera, texture, source, position, size, Vector2Zero(), 0.0f, tint); +} +void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Vector2 origin, float rotation, Color tint) +{ + // NOTE: Billboard size will maintain source rectangle aspect ratio, size will represent billboard width + Vector2 sizeRatio = { size.y, size.x*(float)source.height/source.width }; + Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); Vector3 right = { matView.m0, matView.m4, matView.m8 }; @@ -2621,55 +2823,92 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec // NOTE: Billboard locked on axis-Y Vector3 up = { 0.0f, 1.0f, 0.0f }; -/* - a-------b - | | - | * | - | | - d-------c -*/ - right = Vector3Scale(right, sizeRatio.x/2); - up = Vector3Scale(up, sizeRatio.y/2); - Vector3 p1 = Vector3Add(right, up); - Vector3 p2 = Vector3Subtract(right, up); + Vector3 rightScaled = Vector3Scale(right, sizeRatio.x/2); + Vector3 upScaled = Vector3Scale(up, sizeRatio.y/2); + + Vector3 p1 = Vector3Add(rightScaled, upScaled); + Vector3 p2 = Vector3Subtract(rightScaled, upScaled); - Vector3 a = Vector3Subtract(center, p2); - Vector3 b = Vector3Add(center, p1); - Vector3 c = Vector3Add(center, p2); - Vector3 d = Vector3Subtract(center, p1); + Vector3 topLeft = Vector3Scale(p2, -1); + Vector3 topRight = p1; + Vector3 bottomRight = p2; + Vector3 bottomLeft = Vector3Scale(p1, -1); + + if (rotation != 0.0f) + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + + // NOTE: (-1, 1) is the range where origin.x, origin.y is inside the texture + float rotateAboutX = sizeRatio.x*origin.x/2; + float rotateAboutY = sizeRatio.y*origin.y/2; + + float xtvalue, ytvalue; + float rotatedX, rotatedY; + + xtvalue = Vector3DotProduct(right, topLeft) - rotateAboutX; // Project points to x and y coordinates on the billboard plane + ytvalue = Vector3DotProduct(up, topLeft) - rotateAboutY; + rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; // Rotate about the point origin + rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; + topLeft = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); // Translate back to cartesian coordinates + + xtvalue = Vector3DotProduct(right, topRight) - rotateAboutX; + ytvalue = Vector3DotProduct(up, topRight) - rotateAboutY; + rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; + rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; + topRight = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); - if (rlCheckBufferLimit(4)) rlglDraw(); + xtvalue = Vector3DotProduct(right, bottomRight) - rotateAboutX; + ytvalue = Vector3DotProduct(up, bottomRight) - rotateAboutY; + rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; + rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; + bottomRight = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); + + xtvalue = Vector3DotProduct(right, bottomLeft)-rotateAboutX; + ytvalue = Vector3DotProduct(up, bottomLeft)-rotateAboutY; + rotatedX = xtvalue*cosRotation - ytvalue*sinRotation + rotateAboutX; + rotatedY = xtvalue*sinRotation + ytvalue*cosRotation + rotateAboutY; + bottomLeft = Vector3Add(Vector3Scale(up, rotatedY), Vector3Scale(right, rotatedX)); + } + + // Translate points to the draw center (position) + topLeft = Vector3Add(topLeft, position); + topRight = Vector3Add(topRight, position); + bottomRight = Vector3Add(bottomRight, position); + bottomLeft = Vector3Add(bottomLeft, position); + + rlCheckRenderBatchLimit(4); - rlEnableTexture(texture.id); + rlSetTexture(texture.id); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); // Bottom-left corner for texture and quad - rlTexCoord2f((float)sourceRec.x/texture.width, (float)sourceRec.y/texture.height); - rlVertex3f(a.x, a.y, a.z); + rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height); + rlVertex3f(topLeft.x, topLeft.y, topLeft.z); // Top-left corner for texture and quad - rlTexCoord2f((float)sourceRec.x/texture.width, (float)(sourceRec.y + sourceRec.height)/texture.height); - rlVertex3f(d.x, d.y, d.z); + rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height); + rlVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z); // Top-right corner for texture and quad - rlTexCoord2f((float)(sourceRec.x + sourceRec.width)/texture.width, (float)(sourceRec.y + sourceRec.height)/texture.height); - rlVertex3f(c.x, c.y, c.z); + rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height); + rlVertex3f(bottomRight.x, bottomRight.y, bottomRight.z); // Bottom-right corner for texture and quad - rlTexCoord2f((float)(sourceRec.x + sourceRec.width)/texture.width, (float)sourceRec.y/texture.height); - rlVertex3f(b.x, b.y, b.z); + rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height); + rlVertex3f(topRight.x, topRight.y, topRight.z); rlEnd(); - rlDisableTexture(); + rlSetTexture(0); } // Draw a bounding box with wires void DrawBoundingBox(BoundingBox box, Color color) { - Vector3 size; + Vector3 size = { 0 }; size.x = fabsf(box.max.x - box.min.x); size.y = fabsf(box.max.y - box.min.y); @@ -2681,24 +2920,24 @@ void DrawBoundingBox(BoundingBox box, Color color) } // Detect collision between two spheres -bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB) +bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2) { bool collision = false; // Simple way to check for collision, just checking distance between two points // Unfortunately, sqrtf() is a costly operation, so we avoid it with following solution /* - float dx = centerA.x - centerB.x; // X distance between centers - float dy = centerA.y - centerB.y; // Y distance between centers - float dz = centerA.z - centerB.z; // Z distance between centers + float dx = center1.x - center2.x; // X distance between centers + float dy = center1.y - center2.y; // Y distance between centers + float dz = center1.z - center2.z; // Z distance between centers float distance = sqrtf(dx*dx + dy*dy + dz*dz); // Distance between centers - if (distance <= (radiusA + radiusB)) collision = true; + if (distance <= (radius1 + radius2)) collision = true; */ // Check for distances squared to avoid sqrtf() - if (Vector3DotProduct(Vector3Subtract(centerB, centerA), Vector3Subtract(centerB, centerA)) <= (radiusA + radiusB)*(radiusA + radiusB)) collision = true; + if (Vector3DotProduct(Vector3Subtract(center2, center1), Vector3Subtract(center2, center1)) <= (radius1 + radius2)*(radius1 + radius2)) collision = true; return collision; } @@ -2788,7 +3027,7 @@ bool CheckCollisionRayBox(Ray ray, BoundingBox box) { bool collision = false; - float t[8]; + float t[8] = { 0 }; t[0] = (box.min.x - ray.position.x)/ray.direction.x; t[1] = (box.max.x - ray.position.x)/ray.direction.x; t[2] = (box.min.y - ray.position.y)/ray.direction.y; @@ -2802,6 +3041,50 @@ bool CheckCollisionRayBox(Ray ray, BoundingBox box) return collision; } +// Get collision info between ray and mesh +RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform) +{ + RayHitInfo result = { 0 }; + + // Check if mesh vertex data on CPU for testing + if (mesh.vertices != NULL) + { + int triangleCount = mesh.triangleCount; + + // Test against all triangles in mesh + for (int i = 0; i < triangleCount; i++) + { + Vector3 a, b, c; + Vector3* vertdata = (Vector3*)mesh.vertices; + + if (mesh.indices) + { + a = vertdata[mesh.indices[i*3 + 0]]; + b = vertdata[mesh.indices[i*3 + 1]]; + c = vertdata[mesh.indices[i*3 + 2]]; + } + else + { + a = vertdata[i*3 + 0]; + b = vertdata[i*3 + 1]; + c = vertdata[i*3 + 2]; + } + + a = Vector3Transform(a, transform); + b = Vector3Transform(b, transform); + c = Vector3Transform(c, transform); + + RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); + + if (triHitInfo.hit) + { + // Save the closest hit triangle + if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; + } + } + } + return result; +} // Get collision info between ray and model RayHitInfo GetCollisionRayModel(Ray ray, Model model) @@ -2810,43 +3093,12 @@ RayHitInfo GetCollisionRayModel(Ray ray, Model model) for (int m = 0; m < model.meshCount; m++) { - // Check if meshhas vertex data on CPU for testing - if (model.meshes[m].vertices != NULL) + RayHitInfo meshHitInfo = GetCollisionRayMesh(ray, model.meshes[m], model.transform); + + if (meshHitInfo.hit) { - // model->mesh.triangleCount may not be set, vertexCount is more reliable - int triangleCount = model.meshes[m].vertexCount/3; - - // Test against all triangles in mesh - for (int i = 0; i < triangleCount; i++) - { - Vector3 a, b, c; - Vector3 *vertdata = (Vector3 *)model.meshes[m].vertices; - - if (model.meshes[m].indices) - { - a = vertdata[model.meshes[m].indices[i*3 + 0]]; - b = vertdata[model.meshes[m].indices[i*3 + 1]]; - c = vertdata[model.meshes[m].indices[i*3 + 2]]; - } - else - { - a = vertdata[i*3 + 0]; - b = vertdata[i*3 + 1]; - c = vertdata[i*3 + 2]; - } - - a = Vector3Transform(a, model.transform); - b = Vector3Transform(b, model.transform); - c = Vector3Transform(c, model.transform); - - RayHitInfo triHitInfo = GetCollisionRayTriangle(ray, a, b, c); - - if (triHitInfo.hit) - { - // Save the closest hit triangle - if ((!result.hit) || (result.distance > triHitInfo.distance)) result = triHitInfo; - } - } + // Save the closest hit mesh + if ((!result.hit) || (result.distance > meshHitInfo.distance)) result = meshHitInfo; } } @@ -2946,171 +3198,166 @@ static Model LoadOBJ(const char *fileName) { Model model = { 0 }; - tinyobj_attrib_t attrib; + tinyobj_attrib_t attrib = { 0 }; tinyobj_shape_t *meshes = NULL; unsigned int meshCount = 0; tinyobj_material_t *materials = NULL; unsigned int materialCount = 0; - char *fileData = LoadFileText(fileName); + char *fileText = LoadFileText(fileName); - if (fileData != NULL) + if (fileText != NULL) { - unsigned int dataSize = (unsigned int)strlen(fileData); + unsigned int dataSize = (unsigned int)strlen(fileText); char currentDir[1024] = { 0 }; strcpy(currentDir, GetWorkingDirectory()); - chdir(GetDirectoryPath(fileName)); + const char *workingDir = GetDirectoryPath(fileName); + if (CHDIR(workingDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + } unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; - int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileData, dataSize, flags); + int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileText, dataSize, flags); if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName); else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes / %i materials", fileName, meshCount, materialCount); - // Init model meshes array - // TODO: Support multiple meshes... in the meantime, only one mesh is returned - //model.meshCount = meshCount; - model.meshCount = 1; - model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); + model.meshCount = materialCount; // Init model materials array if (materialCount > 0) { model.materialCount = materialCount; model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + TraceLog(LOG_INFO, "MODEL: model has %i material meshes", materialCount); + } + else + { + model.meshCount = 1; + TraceLog(LOG_INFO, "MODEL: No materials, putting all meshes in a default material"); } + model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); - /* - // Multiple meshes data reference - // NOTE: They are provided as a faces offset - typedef struct { - char *name; // group name or object name - unsigned int face_offset; - unsigned int length; - } tinyobj_shape_t; - */ + // count the faces for each material + int *matFaces = RL_CALLOC(meshCount, sizeof(int)); - // Init model meshes - for (int m = 0; m < 1; m++) + for (unsigned int mi = 0; mi < meshCount; mi++) { - Mesh mesh = { 0 }; - memset(&mesh, 0, sizeof(Mesh)); - mesh.vertexCount = attrib.num_faces*3; - mesh.triangleCount = attrib.num_faces; - mesh.vertices = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float)); - mesh.texcoords = (float *)RL_CALLOC(mesh.vertexCount*2, sizeof(float)); - mesh.normals = (float *)RL_CALLOC(mesh.vertexCount*3, sizeof(float)); - mesh.vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - - int vCount = 0; - int vtCount = 0; - int vnCount = 0; - - for (unsigned int f = 0; f < attrib.num_faces; f++) + for (unsigned int fi = 0; fi < meshes[mi].length; fi++) { - // Get indices for the face - tinyobj_vertex_index_t idx0 = attrib.faces[3*f + 0]; - tinyobj_vertex_index_t idx1 = attrib.faces[3*f + 1]; - tinyobj_vertex_index_t idx2 = attrib.faces[3*f + 2]; + int idx = attrib.material_ids[meshes[mi].face_offset + fi]; + if (idx == -1) idx = 0; // for no material face (which could be the whole model) + matFaces[idx]++; + } + } - // Fill vertices buffer (float) using vertex index of the face - for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount +=3; - for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount +=3; - for (int v = 0; v < 3; v++) { mesh.vertices[vCount + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount +=3; + //-------------------------------------- + // create the material meshes - if (attrib.num_texcoords > 0) - { - // Fill texcoords buffer (float) using vertex index of the face - // NOTE: Y-coordinate must be flipped upside-down - mesh.texcoords[vtCount + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; - mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount += 2; - mesh.texcoords[vtCount + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; - mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount += 2; - mesh.texcoords[vtCount + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; - mesh.texcoords[vtCount + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount += 2; - } + // running counts / indexes for each material mesh as we are + // building them at the same time + int *vCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *vtCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *vnCount = RL_CALLOC(model.meshCount, sizeof(int)); + int *faceCount = RL_CALLOC(model.meshCount, sizeof(int)); - if (attrib.num_normals > 0) - { - // Fill normals buffer (float) using vertex index of the face - for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount +=3; - for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount +=3; - for (int v = 0; v < 3; v++) { mesh.normals[vnCount + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount +=3; - } + // allocate space for each of the material meshes + for (int mi = 0; mi < model.meshCount; mi++) + { + model.meshes[mi].vertexCount = matFaces[mi]*3; + model.meshes[mi].triangleCount = matFaces[mi]; + model.meshes[mi].vertices = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float)); + model.meshes[mi].texcoords = (float *)RL_CALLOC(model.meshes[mi].vertexCount*2, sizeof(float)); + model.meshes[mi].normals = (float *)RL_CALLOC(model.meshes[mi].vertexCount*3, sizeof(float)); + model.meshMaterial[mi] = mi; + } + + // scan through the combined sub meshes and pick out each material mesh + for (unsigned int af = 0; af < attrib.num_faces; af++) + { + int mm = attrib.material_ids[af]; // mesh material for this face + if (mm == -1) { mm = 0; } // no material object.. + + // Get indices for the face + tinyobj_vertex_index_t idx0 = attrib.faces[3*af + 0]; + tinyobj_vertex_index_t idx1 = attrib.faces[3*af + 1]; + tinyobj_vertex_index_t idx2 = attrib.faces[3*af + 2]; + + // Fill vertices buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx0.v_idx*3 + v]; } vCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx1.v_idx*3 + v]; } vCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].vertices[vCount[mm] + v] = attrib.vertices[idx2.v_idx*3 + v]; } vCount[mm] +=3; + + if (attrib.num_texcoords > 0) + { + // Fill texcoords buffer (float) using vertex index of the face + // NOTE: Y-coordinate must be flipped upside-down to account for + // raylib's upside down textures... + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; vtCount[mm] += 2; + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; vtCount[mm] += 2; + model.meshes[mm].texcoords[vtCount[mm] + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; + model.meshes[mm].texcoords[vtCount[mm] + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; vtCount[mm] += 2; } - model.meshes[m] = mesh; // Assign mesh data to model - - // Assign mesh material for current mesh - model.meshMaterial[m] = attrib.material_ids[m]; - - // Set unfound materials to default - if (model.meshMaterial[m] == -1) model.meshMaterial[m] = 0; + if (attrib.num_normals > 0) + { + // Fill normals buffer (float) using vertex index of the face + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx0.vn_idx*3 + v]; } vnCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx1.vn_idx*3 + v]; } vnCount[mm] +=3; + for (int v = 0; v < 3; v++) { model.meshes[mm].normals[vnCount[mm] + v] = attrib.normals[idx2.vn_idx*3 + v]; } vnCount[mm] +=3; + } } // Init model materials for (unsigned int m = 0; m < materialCount; m++) { // Init material to default - // NOTE: Uses default shader, only MAP_DIFFUSE supported + // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE model.materials[m] = LoadMaterialDefault(); - /* - typedef struct { - char *name; + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); // Get default texture, in case no texture is defined - float ambient[3]; - float diffuse[3]; - float specular[3]; - float transmittance[3]; - float emission[3]; - float shininess; - float ior; // index of refraction - float dissolve; // 1 == opaque; 0 == fully transparent - // illumination model (Ref: http://www.fileformat.info/format/material/) - int illum; + if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd + else model.materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = rlGetTextureDefault(); - int pad0; + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; + model.materials[m].maps[MATERIAL_MAP_DIFFUSE].value = 0.0f; - char *ambient_texname; // map_Ka - char *diffuse_texname; // map_Kd - char *specular_texname; // map_Ks - char *specular_highlight_texname; // map_Ns - char *bump_texname; // map_bump, bump - char *displacement_texname; // disp - char *alpha_texname; // map_d - } tinyobj_material_t; - */ + if (materials[m].specular_texname != NULL) model.materials[m].maps[MATERIAL_MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks + model.materials[m].maps[MATERIAL_MAP_SPECULAR].color = (Color){ (unsigned char)(materials[m].specular[0]*255.0f), (unsigned char)(materials[m].specular[1]*255.0f), (unsigned char)(materials[m].specular[2]*255.0f), 255 }; //float specular[3]; + model.materials[m].maps[MATERIAL_MAP_SPECULAR].value = 0.0f; - model.materials[m].maps[MAP_DIFFUSE].texture = GetTextureDefault(); // Get default texture, in case no texture is defined + if (materials[m].bump_texname != NULL) model.materials[m].maps[MATERIAL_MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump + model.materials[m].maps[MATERIAL_MAP_NORMAL].color = WHITE; + model.materials[m].maps[MATERIAL_MAP_NORMAL].value = materials[m].shininess; - if (materials[m].diffuse_texname != NULL) model.materials[m].maps[MAP_DIFFUSE].texture = LoadTexture(materials[m].diffuse_texname); //char *diffuse_texname; // map_Kd - model.materials[m].maps[MAP_DIFFUSE].color = (Color){ (unsigned char)(materials[m].diffuse[0]*255.0f), (unsigned char)(materials[m].diffuse[1]*255.0f), (unsigned char)(materials[m].diffuse[2]*255.0f), 255 }; //float diffuse[3]; - model.materials[m].maps[MAP_DIFFUSE].value = 0.0f; + model.materials[m].maps[MATERIAL_MAP_EMISSION].color = (Color){ (unsigned char)(materials[m].emission[0]*255.0f), (unsigned char)(materials[m].emission[1]*255.0f), (unsigned char)(materials[m].emission[2]*255.0f), 255 }; //float emission[3]; - if (materials[m].specular_texname != NULL) model.materials[m].maps[MAP_SPECULAR].texture = LoadTexture(materials[m].specular_texname); //char *specular_texname; // map_Ks - model.materials[m].maps[MAP_SPECULAR].color = (Color){ (unsigned char)(materials[m].specular[0]*255.0f), (unsigned char)(materials[m].specular[1]*255.0f), (unsigned char)(materials[m].specular[2]*255.0f), 255 }; //float specular[3]; - model.materials[m].maps[MAP_SPECULAR].value = 0.0f; - - if (materials[m].bump_texname != NULL) model.materials[m].maps[MAP_NORMAL].texture = LoadTexture(materials[m].bump_texname); //char *bump_texname; // map_bump, bump - model.materials[m].maps[MAP_NORMAL].color = WHITE; - model.materials[m].maps[MAP_NORMAL].value = materials[m].shininess; - - model.materials[m].maps[MAP_EMISSION].color = (Color){ (unsigned char)(materials[m].emission[0]*255.0f), (unsigned char)(materials[m].emission[1]*255.0f), (unsigned char)(materials[m].emission[2]*255.0f), 255 }; //float emission[3]; - - if (materials[m].displacement_texname != NULL) model.materials[m].maps[MAP_HEIGHT].texture = LoadTexture(materials[m].displacement_texname); //char *displacement_texname; // disp + if (materials[m].displacement_texname != NULL) model.materials[m].maps[MATERIAL_MAP_HEIGHT].texture = LoadTexture(materials[m].displacement_texname); //char *displacement_texname; // disp } tinyobj_attrib_free(&attrib); tinyobj_shapes_free(meshes, meshCount); tinyobj_materials_free(materials, materialCount); - - RL_FREE(fileData); - chdir(currentDir); + UnloadFileText(fileText); + + RL_FREE(matFaces); + RL_FREE(vCount); + RL_FREE(vtCount); + RL_FREE(vnCount); + RL_FREE(faceCount); + + if (CHDIR(currentDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + } } return model; @@ -3127,7 +3374,7 @@ static Model LoadIQM(const char *fileName) #define BONE_NAME_LENGTH 32 // BoneInfo name string length #define MESH_NAME_LENGTH 32 // Mesh name string length #define MATERIAL_NAME_LENGTH 32 // Material name string length - + unsigned int fileSize = 0; unsigned char *fileData = LoadFileData(fileName, &fileSize); unsigned char *fileDataPtr = fileData; @@ -3245,11 +3492,11 @@ static Model LoadIQM(const char *fileName) TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); return model; } - + //fileDataPtr += sizeof(IQMHeader); // Move file data pointer // Meshes data processing - imesh = RL_MALLOC(sizeof(IQMMesh)*iqmHeader->num_meshes); + imesh = RL_MALLOC(iqmHeader->num_meshes*sizeof(IQMMesh)); //fseek(iqmFile, iqmHeader->ofs_meshes, SEEK_SET); //fread(imesh, sizeof(IQMMesh)*iqmHeader->num_meshes, 1, iqmFile); memcpy(imesh, fileDataPtr + iqmHeader->ofs_meshes, iqmHeader->num_meshes*sizeof(IQMMesh)); @@ -3294,8 +3541,6 @@ static Model LoadIQM(const char *fileName) // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) model.meshes[i].animVertices = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); model.meshes[i].animNormals = RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - - model.meshes[i].vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); } // Triangles data processing @@ -3311,7 +3556,7 @@ static Model LoadIQM(const char *fileName) for (unsigned int i = imesh[m].first_triangle; i < (imesh[m].first_triangle + imesh[m].num_triangles); i++) { // IQM triangles indexes are stored in counter-clockwise, but raylib processes the index in linear order, - // expecting they point to the counter-clockwise vertex triangle, so we need to reverse triangle indexes + // expecting they point to the counter-clockwise vertex triangle, so we need to reverse triangle indexes // NOTE: raylib renders vertex data in counter-clockwise order (standard convention) by default model.meshes[m].indices[tcounter + 2] = tri[i].vertex[0] - imesh[m].first_vertex; model.meshes[m].indices[tcounter + 1] = tri[i].vertex[1] - imesh[m].first_vertex; @@ -3466,7 +3711,7 @@ static Model LoadIQM(const char *fileName) } RL_FREE(fileData); - + RL_FREE(imesh); RL_FREE(tri); RL_FREE(va); @@ -3479,6 +3724,218 @@ static Model LoadIQM(const char *fileName) return model; } + +// Load IQM animation data +static ModelAnimation* LoadIQMModelAnimations(const char* fileName, int* animCount) +{ +#define IQM_MAGIC "INTERQUAKEMODEL" // IQM file magic number +#define IQM_VERSION 2 // only IQM version 2 supported + + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + unsigned char *fileDataPtr = fileData; + + typedef struct IQMHeader { + char magic[16]; + unsigned int version; + unsigned int filesize; + unsigned int flags; + unsigned int num_text, ofs_text; + unsigned int num_meshes, ofs_meshes; + unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; + unsigned int num_triangles, ofs_triangles, ofs_adjacency; + unsigned int num_joints, ofs_joints; + unsigned int num_poses, ofs_poses; + unsigned int num_anims, ofs_anims; + unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; + unsigned int num_comment, ofs_comment; + unsigned int num_extensions, ofs_extensions; + } IQMHeader; + + typedef struct IQMPose { + int parent; + unsigned int mask; + float channeloffset[10]; + float channelscale[10]; + } IQMPose; + + typedef struct IQMAnim { + unsigned int name; + unsigned int first_frame, num_frames; + float framerate; + unsigned int flags; + } IQMAnim; + + // In case file can not be read, return an empty model + if (fileDataPtr == NULL) return NULL; + + // Read IQM header + IQMHeader *iqmHeader = (IQMHeader *)fileDataPtr; + + if (memcmp(iqmHeader->magic, IQM_MAGIC, sizeof(IQM_MAGIC)) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file is not a valid model", fileName); + return NULL; + } + + if (iqmHeader->version != IQM_VERSION) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] IQM file version not supported (%i)", fileName, iqmHeader->version); + return NULL; + } + + // Get bones data + IQMPose *poses = RL_MALLOC(iqmHeader->num_poses*sizeof(IQMPose)); + //fseek(iqmFile, iqmHeader->ofs_poses, SEEK_SET); + //fread(poses, iqmHeader->num_poses*sizeof(IQMPose), 1, iqmFile); + memcpy(poses, fileDataPtr + iqmHeader->ofs_poses, iqmHeader->num_poses*sizeof(IQMPose)); + + // Get animations data + *animCount = iqmHeader->num_anims; + IQMAnim *anim = RL_MALLOC(iqmHeader->num_anims*sizeof(IQMAnim)); + //fseek(iqmFile, iqmHeader->ofs_anims, SEEK_SET); + //fread(anim, iqmHeader->num_anims*sizeof(IQMAnim), 1, iqmFile); + memcpy(anim, fileDataPtr + iqmHeader->ofs_anims, iqmHeader->num_anims*sizeof(IQMAnim)); + + ModelAnimation *animations = RL_MALLOC(iqmHeader->num_anims*sizeof(ModelAnimation)); + + // frameposes + unsigned short *framedata = RL_MALLOC(iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + //fseek(iqmFile, iqmHeader->ofs_frames, SEEK_SET); + //fread(framedata, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short), 1, iqmFile); + memcpy(framedata, fileDataPtr + iqmHeader->ofs_frames, iqmHeader->num_frames*iqmHeader->num_framechannels*sizeof(unsigned short)); + + for (unsigned int a = 0; a < iqmHeader->num_anims; a++) + { + animations[a].frameCount = anim[a].num_frames; + animations[a].boneCount = iqmHeader->num_poses; + animations[a].bones = RL_MALLOC(iqmHeader->num_poses*sizeof(BoneInfo)); + animations[a].framePoses = RL_MALLOC(anim[a].num_frames*sizeof(Transform *)); + // animations[a].framerate = anim.framerate; // TODO: Use framerate? + + for (unsigned int j = 0; j < iqmHeader->num_poses; j++) + { + strcpy(animations[a].bones[j].name, "ANIMJOINTNAME"); + animations[a].bones[j].parent = poses[j].parent; + } + + for (unsigned int j = 0; j < anim[a].num_frames; j++) animations[a].framePoses[j] = RL_MALLOC(iqmHeader->num_poses*sizeof(Transform)); + + int dcounter = anim[a].first_frame*iqmHeader->num_framechannels; + + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (unsigned int i = 0; i < iqmHeader->num_poses; i++) + { + animations[a].framePoses[frame][i].translation.x = poses[i].channeloffset[0]; + + if (poses[i].mask & 0x01) + { + animations[a].framePoses[frame][i].translation.x += framedata[dcounter]*poses[i].channelscale[0]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.y = poses[i].channeloffset[1]; + + if (poses[i].mask & 0x02) + { + animations[a].framePoses[frame][i].translation.y += framedata[dcounter]*poses[i].channelscale[1]; + dcounter++; + } + + animations[a].framePoses[frame][i].translation.z = poses[i].channeloffset[2]; + + if (poses[i].mask & 0x04) + { + animations[a].framePoses[frame][i].translation.z += framedata[dcounter]*poses[i].channelscale[2]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.x = poses[i].channeloffset[3]; + + if (poses[i].mask & 0x08) + { + animations[a].framePoses[frame][i].rotation.x += framedata[dcounter]*poses[i].channelscale[3]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.y = poses[i].channeloffset[4]; + + if (poses[i].mask & 0x10) + { + animations[a].framePoses[frame][i].rotation.y += framedata[dcounter]*poses[i].channelscale[4]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.z = poses[i].channeloffset[5]; + + if (poses[i].mask & 0x20) + { + animations[a].framePoses[frame][i].rotation.z += framedata[dcounter]*poses[i].channelscale[5]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation.w = poses[i].channeloffset[6]; + + if (poses[i].mask & 0x40) + { + animations[a].framePoses[frame][i].rotation.w += framedata[dcounter]*poses[i].channelscale[6]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.x = poses[i].channeloffset[7]; + + if (poses[i].mask & 0x80) + { + animations[a].framePoses[frame][i].scale.x += framedata[dcounter]*poses[i].channelscale[7]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.y = poses[i].channeloffset[8]; + + if (poses[i].mask & 0x100) + { + animations[a].framePoses[frame][i].scale.y += framedata[dcounter]*poses[i].channelscale[8]; + dcounter++; + } + + animations[a].framePoses[frame][i].scale.z = poses[i].channeloffset[9]; + + if (poses[i].mask & 0x200) + { + animations[a].framePoses[frame][i].scale.z += framedata[dcounter]*poses[i].channelscale[9]; + dcounter++; + } + + animations[a].framePoses[frame][i].rotation = QuaternionNormalize(animations[a].framePoses[frame][i].rotation); + } + } + + // Build frameposes + for (unsigned int frame = 0; frame < anim[a].num_frames; frame++) + { + for (int i = 0; i < animations[a].boneCount; i++) + { + if (animations[a].bones[i].parent >= 0) + { + animations[a].framePoses[frame][i].rotation = QuaternionMultiply(animations[a].framePoses[frame][animations[a].bones[i].parent].rotation, animations[a].framePoses[frame][i].rotation); + animations[a].framePoses[frame][i].translation = Vector3RotateByQuaternion(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].rotation); + animations[a].framePoses[frame][i].translation = Vector3Add(animations[a].framePoses[frame][i].translation, animations[a].framePoses[frame][animations[a].bones[i].parent].translation); + animations[a].framePoses[frame][i].scale = Vector3Multiply(animations[a].framePoses[frame][i].scale, animations[a].framePoses[frame][animations[a].bones[i].parent].scale); + } + } + } + } + + RL_FREE(fileData); + + RL_FREE(framedata); + RL_FREE(poses); + RL_FREE(anim); + + return animations; +} + #endif #if defined(SUPPORT_FILEFORMAT_GLTF) @@ -3586,7 +4043,7 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co rimage.data = raw; rimage.width = width; rimage.height = height; - rimage.format = UNCOMPRESSED_R8G8B8A8; + rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; rimage.mipmaps = 1; // TODO: Tint shouldn't be applied here! @@ -3620,7 +4077,7 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co rimage.data = raw; rimage.width = width; rimage.height = height; - rimage.format = UNCOMPRESSED_R8G8B8A8; + rimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; rimage.mipmaps = 1; // TODO: Tint shouldn't be applied here! @@ -3631,12 +4088,33 @@ static Image LoadImageFromCgltfImage(cgltf_image *image, const char *texPath, Co return rimage; } + +static bool GLTFReadValue(cgltf_accessor* acc, unsigned int index, void *variable, unsigned int elements, unsigned int size) +{ + if (acc->count == 2) + { + if (index > 1) return false; + + memcpy(variable, index == 0 ? acc->min : acc->max, elements*size); + return true; + } + + unsigned int stride = size*elements; + memset(variable, 0, stride); + + if (acc->buffer_view == NULL || acc->buffer_view->buffer == NULL || acc->buffer_view->buffer->data == NULL) return false; + + void* readPosition = ((char *)acc->buffer_view->buffer->data) + (index*stride) + acc->buffer_view->offset + acc->offset; + memcpy(variable, readPosition, stride); + return true; +} + // LoadGLTF loads in model data from given filename, supporting both .gltf and .glb static Model LoadGLTF(const char *fileName) { /*********************************************************************************** - Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) + Function implemented by Wilhem Barbier(@wbrbr), with modifications by Tyler Bezera(@gamerfiend) and Hristo Stamenov(@object71) Features: - Supports .gltf and .glb files @@ -3652,18 +4130,6 @@ static Model LoadGLTF(const char *fileName) *************************************************************************************/ - #define LOAD_ACCESSOR(type, nbcomp, acc, dst) \ - { \ - int n = 0; \ - type* buf = (type*)acc->buffer_view->buffer->data + acc->buffer_view->offset/sizeof(type) + acc->offset/sizeof(type); \ - for (unsigned int k = 0; k < acc->count; k++) {\ - for (int l = 0; l < nbcomp; l++) {\ - dst[nbcomp*k + l] = buf[n + l];\ - }\ - n += (int)(acc->stride/sizeof(type));\ - }\ - } - Model model = { 0 }; // glTF file loading @@ -3679,8 +4145,8 @@ static Model LoadGLTF(const char *fileName) if (result == cgltf_result_success) { - TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count); - TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count, data->materials_count); + TRACELOG(LOG_INFO, "MODEL: [%s] glTF meshes (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->meshes_count); + TRACELOG(LOG_INFO, "MODEL: [%s] glTF materials (%s) count: %i", fileName, (data->file_type == 2)? "glb" : "gltf", data->materials_count); // Read data buffers result = cgltf_load_buffers(&options, data, fileName); @@ -3688,7 +4154,8 @@ static Model LoadGLTF(const char *fileName) int primitivesCount = 0; - for (unsigned int i = 0; i < data->meshes_count; i++) primitivesCount += (int)data->meshes[i].primitives_count; + for (unsigned int i = 0; i < data->meshes_count; i++) + primitivesCount += (int)data->meshes[i].primitives_count; // Process glTF data and map to model model.meshCount = primitivesCount; @@ -3696,76 +4163,12 @@ static Model LoadGLTF(const char *fileName) model.materialCount = (int)data->materials_count + 1; model.materials = RL_MALLOC(model.materialCount*sizeof(Material)); model.meshMaterial = RL_MALLOC(model.meshCount*sizeof(int)); + model.boneCount = (int)data->nodes_count; + model.bones = RL_CALLOC(model.boneCount, sizeof(BoneInfo)); + model.bindPose = RL_CALLOC(model.boneCount, sizeof(Transform)); - for (int i = 0; i < model.meshCount; i++) model.meshes[i].vboId = (unsigned int *)RL_CALLOC(DEFAULT_MESH_VERTEX_BUFFERS, sizeof(unsigned int)); - - for (int i = 0; i < model.materialCount - 1; i++) - { - model.materials[i] = LoadMaterialDefault(); - Color tint = (Color){ 255, 255, 255, 255 }; - const char *texPath = GetDirectoryPath(fileName); - - //Ensure material follows raylib support for PBR (metallic/roughness flow) - if (data->materials[i].has_pbr_metallic_roughness) - { - tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0] * 255); - tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1] * 255); - tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2] * 255); - tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3] * 255); - - model.materials[i].maps[MAP_ALBEDO].color = tint; - - if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) - { - Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); - model.materials[i].maps[MAP_ALBEDO].texture = LoadTextureFromImage(albedo); - UnloadImage(albedo); - } - - tint = WHITE; // Set tint to white after it's been used by Albedo - - if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) - { - Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); - model.materials[i].maps[MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); - - float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; - model.materials[i].maps[MAP_ROUGHNESS].value = roughness; - - float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; - model.materials[i].maps[MAP_METALNESS].value = metallic; - - UnloadImage(metallicRoughness); - } - - if (data->materials[i].normal_texture.texture) - { - Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); - model.materials[i].maps[MAP_NORMAL].texture = LoadTextureFromImage(normalImage); - UnloadImage(normalImage); - } - - if (data->materials[i].occlusion_texture.texture) - { - Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); - model.materials[i].maps[MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); - UnloadImage(occulsionImage); - } - - if (data->materials[i].emissive_texture.texture) - { - Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); - model.materials[i].maps[MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); - tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); - tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); - tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); - model.materials[i].maps[MAP_EMISSION].color = tint; - UnloadImage(emissiveImage); - } - } - } - - model.materials[model.materialCount - 1] = LoadMaterialDefault(); + InitGLTFBones(&model, data); + LoadGLTFMaterial(&model, fileName, data); int primitiveIndex = 0; @@ -3779,16 +4182,69 @@ static Model LoadGLTF(const char *fileName) { cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; model.meshes[primitiveIndex].vertexCount = (int)acc->count; - model.meshes[primitiveIndex].vertices = RL_MALLOC(model.meshes[primitiveIndex].vertexCount*3*sizeof(float)); + int bufferSize = model.meshes[primitiveIndex].vertexCount*3*sizeof(float); + model.meshes[primitiveIndex].vertices = RL_MALLOC(bufferSize); + model.meshes[primitiveIndex].animVertices = RL_MALLOC(bufferSize); - LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].vertices) + if (acc->component_type == cgltf_component_type_r_32f) + { + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].vertices + (a*3), 3, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + int readValue[3]; + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].vertices[(a*3) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].vertices[(a*3) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].vertices[(a*3) + 2] = (float)readValue[2]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short vertices + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF vertices must be float or int", fileName); + } + + memcpy(model.meshes[primitiveIndex].animVertices, model.meshes[primitiveIndex].vertices, bufferSize); } else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_normal) { cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; - model.meshes[primitiveIndex].normals = RL_MALLOC(acc->count*3*sizeof(float)); - LOAD_ACCESSOR(float, 3, acc, model.meshes[primitiveIndex].normals) + int bufferSize = (int)(acc->count*3*sizeof(float)); + model.meshes[primitiveIndex].normals = RL_MALLOC(bufferSize); + model.meshes[primitiveIndex].animNormals = RL_MALLOC(bufferSize); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].normals + (a*3), 3, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + int readValue[3]; + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 3, sizeof(int)); + model.meshes[primitiveIndex].normals[(a*3) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].normals[(a*3) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].normals[(a*3) + 2] = (float)readValue[2]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short normals + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } + + memcpy(model.meshes[primitiveIndex].animNormals, model.meshes[primitiveIndex].normals, bufferSize); } else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_texcoord) { @@ -3797,7 +4253,11 @@ static Model LoadGLTF(const char *fileName) if (acc->component_type == cgltf_component_type_r_32f) { model.meshes[primitiveIndex].texcoords = RL_MALLOC(acc->count*2*sizeof(float)); - LOAD_ACCESSOR(float, 2, acc, model.meshes[primitiveIndex].texcoords) + + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].texcoords + (a*2), 2, sizeof(float)); + } } else { @@ -3805,29 +4265,46 @@ static Model LoadGLTF(const char *fileName) TRACELOG(LOG_WARNING, "MODEL: [%s] glTF texture coordinates must be float", fileName); } } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_joints) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + LoadGLTFBoneAttribute(&model, acc, data, primitiveIndex); + } + else if (data->meshes[i].primitives[p].attributes[j].type == cgltf_attribute_type_weights) + { + cgltf_accessor *acc = data->meshes[i].primitives[p].attributes[j].data; + + model.meshes[primitiveIndex].boneWeights = RL_MALLOC(acc->count*4*sizeof(float)); + + if (acc->component_type == cgltf_component_type_r_32f) + { + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, model.meshes[primitiveIndex].boneWeights + (a*4), 4, sizeof(float)); + } + } + else if (acc->component_type == cgltf_component_type_r_32u) + { + unsigned int readValue[4]; + for (unsigned int a = 0; a < acc->count; a++) + { + GLTFReadValue(acc, a, readValue, 4, sizeof(unsigned int)); + model.meshes[primitiveIndex].boneWeights[(a*4) + 0] = (float)readValue[0]; + model.meshes[primitiveIndex].boneWeights[(a*4) + 1] = (float)readValue[1]; + model.meshes[primitiveIndex].boneWeights[(a*4) + 2] = (float)readValue[2]; + model.meshes[primitiveIndex].boneWeights[(a*4) + 3] = (float)readValue[3]; + } + } + else + { + // TODO: Support normalized unsigned byte/unsigned short weights + TRACELOG(LOG_WARNING, "MODEL: [%s] glTF normals must be float or int", fileName); + } + } } cgltf_accessor *acc = data->meshes[i].primitives[p].indices; - - if (acc) - { - if (acc->component_type == cgltf_component_type_r_16u) - { - model.meshes[primitiveIndex].triangleCount = (int)acc->count/3; - model.meshes[primitiveIndex].indices = RL_MALLOC(model.meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); - LOAD_ACCESSOR(unsigned short, 1, acc, model.meshes[primitiveIndex].indices) - } - else - { - // TODO: Support unsigned byte/unsigned int - TRACELOG(LOG_WARNING, "MODEL: [%s] glTF index data must be unsigned short", fileName); - } - } - else - { - // Unindexed mesh - model.meshes[primitiveIndex].triangleCount = model.meshes[primitiveIndex].vertexCount/3; - } + LoadGLTFModelIndices(&model, acc, primitiveIndex); if (data->meshes[i].primitives[p].material) { @@ -3836,11 +4313,14 @@ static Model LoadGLTF(const char *fileName) } else { - model.meshMaterial[primitiveIndex] = model.materialCount - 1;; + model.meshMaterial[primitiveIndex] = model.materialCount - 1; } + BindGLTFPrimitiveToBones(&model, data, primitiveIndex); + primitiveIndex++; } + } cgltf_free(data); @@ -3851,4 +4331,541 @@ static Model LoadGLTF(const char *fileName) return model; } + +static void InitGLTFBones(Model* model, const cgltf_data* data) +{ + for (unsigned int j = 0; j < data->nodes_count; j++) + { + strcpy(model->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + model->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; + } + + for (unsigned int i = 0; i < data->nodes_count; i++) + { + if (data->nodes[i].has_translation) memcpy(&model->bindPose[i].translation, data->nodes[i].translation, 3*sizeof(float)); + else model->bindPose[i].translation = Vector3Zero(); + + if (data->nodes[i].has_rotation) memcpy(&model->bindPose[i].rotation, data->nodes[i].rotation, 4*sizeof(float)); + else model->bindPose[i].rotation = QuaternionIdentity(); + + model->bindPose[i].rotation = QuaternionNormalize(model->bindPose[i].rotation); + + if (data->nodes[i].has_scale) memcpy(&model->bindPose[i].scale, data->nodes[i].scale, 3*sizeof(float)); + else model->bindPose[i].scale = Vector3One(); + } + + { + bool* completedBones = RL_CALLOC(model->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < model->boneCount) { + for (int i = 0; i < model->boneCount; i++) + { + if (completedBones[i]) continue; + + if (model->bones[i].parent < 0) { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[model->bones[i].parent]) continue; + + Transform* currentTransform = &model->bindPose[i]; + BoneInfo* currentBone = &model->bones[i]; + int root = currentBone->parent; + if (root >= model->boneCount) + root = 0; + Transform* parentTransform = &model->bindPose[root]; + + currentTransform->rotation = QuaternionMultiply(parentTransform->rotation, currentTransform->rotation); + currentTransform->translation = Vector3RotateByQuaternion(currentTransform->translation, parentTransform->rotation); + currentTransform->translation = Vector3Add(currentTransform->translation, parentTransform->translation); + currentTransform->scale = Vector3Multiply(currentTransform->scale, parentTransform->scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } +} + +static void LoadGLTFMaterial(Model* model, const char* fileName, const cgltf_data* data) +{ + for (int i = 0; i < model->materialCount - 1; i++) + { + model->materials[i] = LoadMaterialDefault(); + Color tint = (Color){ 255, 255, 255, 255 }; + const char *texPath = GetDirectoryPath(fileName); + + // Ensure material follows raylib support for PBR (metallic/roughness flow) + if (data->materials[i].has_pbr_metallic_roughness) + { + tint.r = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[2]*255); + tint.a = (unsigned char)(data->materials[i].pbr_metallic_roughness.base_color_factor[3]*255); + + model->materials[i].maps[MATERIAL_MAP_ALBEDO].color = tint; + + if (data->materials[i].pbr_metallic_roughness.base_color_texture.texture) + { + Image albedo = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.base_color_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ALBEDO].texture = LoadTextureFromImage(albedo); + UnloadImage(albedo); + } + + tint = WHITE; // Set tint to white after it's been used by Albedo + + if (data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture) + { + Image metallicRoughness = LoadImageFromCgltfImage(data->materials[i].pbr_metallic_roughness.metallic_roughness_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].texture = LoadTextureFromImage(metallicRoughness); + + float roughness = data->materials[i].pbr_metallic_roughness.roughness_factor; + model->materials[i].maps[MATERIAL_MAP_ROUGHNESS].value = roughness; + + float metallic = data->materials[i].pbr_metallic_roughness.metallic_factor; + model->materials[i].maps[MATERIAL_MAP_METALNESS].value = metallic; + + UnloadImage(metallicRoughness); + } + + if (data->materials[i].normal_texture.texture) + { + Image normalImage = LoadImageFromCgltfImage(data->materials[i].normal_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_NORMAL].texture = LoadTextureFromImage(normalImage); + UnloadImage(normalImage); + } + + if (data->materials[i].occlusion_texture.texture) + { + Image occulsionImage = LoadImageFromCgltfImage(data->materials[i].occlusion_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_OCCLUSION].texture = LoadTextureFromImage(occulsionImage); + UnloadImage(occulsionImage); + } + + if (data->materials[i].emissive_texture.texture) + { + Image emissiveImage = LoadImageFromCgltfImage(data->materials[i].emissive_texture.texture->image, texPath, tint); + model->materials[i].maps[MATERIAL_MAP_EMISSION].texture = LoadTextureFromImage(emissiveImage); + tint.r = (unsigned char)(data->materials[i].emissive_factor[0]*255); + tint.g = (unsigned char)(data->materials[i].emissive_factor[1]*255); + tint.b = (unsigned char)(data->materials[i].emissive_factor[2]*255); + model->materials[i].maps[MATERIAL_MAP_EMISSION].color = tint; + UnloadImage(emissiveImage); + } + } + } + + model->materials[model->materialCount - 1] = LoadMaterialDefault(); +} + +static void LoadGLTFBoneAttribute(Model* model, cgltf_accessor* jointsAccessor, const cgltf_data* data, int primitiveIndex) +{ + if (jointsAccessor->component_type == cgltf_component_type_r_16u) + { + model->meshes[primitiveIndex].boneIds = RL_MALLOC(jointsAccessor->count*4*sizeof(int)); + short* bones = RL_MALLOC(jointsAccessor->count*4*sizeof(short)); + + for (unsigned int a = 0; a < jointsAccessor->count; a++) + { + GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(short)); + } + + for (unsigned int a = 0; a < jointsAccessor->count*4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model->meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else if (jointsAccessor->component_type == cgltf_component_type_r_8u) + { + model->meshes[primitiveIndex].boneIds = RL_MALLOC(jointsAccessor->count*4*sizeof(int)); + unsigned char* bones = RL_MALLOC(jointsAccessor->count*4*sizeof(unsigned char)); + + for (unsigned int a = 0; a < jointsAccessor->count; a++) + { + GLTFReadValue(jointsAccessor, a, bones + (a*4), 4, sizeof(unsigned char)); + } + + for (unsigned int a = 0; a < jointsAccessor->count*4; a++) + { + cgltf_node* skinJoint = data->skins->joints[bones[a]]; + + for (unsigned int k = 0; k < data->nodes_count; k++) + { + if (&(data->nodes[k]) == skinJoint) + { + model->meshes[primitiveIndex].boneIds[a] = k; + break; + } + } + } + RL_FREE(bones); + } + else + { + // TODO: Support other size of bone index? + TRACELOG(LOG_WARNING, "MODEL: glTF bones in unexpected format"); + } +} + +static void BindGLTFPrimitiveToBones(Model* model, const cgltf_data* data, int primitiveIndex) +{ + if (model->meshes[primitiveIndex].boneIds == NULL && data->nodes_count > 0) + { + for (unsigned int nodeId = 0; nodeId < data->nodes_count; nodeId++) + { + if (data->nodes[nodeId].mesh == &(data->meshes[primitiveIndex])) + { + model->meshes[primitiveIndex].boneIds = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(int)); + model->meshes[primitiveIndex].boneWeights = RL_CALLOC(4*model->meshes[primitiveIndex].vertexCount, sizeof(float)); + + for (int b = 0; b < 4*model->meshes[primitiveIndex].vertexCount; b++) + { + if (b%4 == 0) + { + model->meshes[primitiveIndex].boneIds[b] = nodeId; + model->meshes[primitiveIndex].boneWeights[b] = 1.0f; + } + else + { + model->meshes[primitiveIndex].boneIds[b] = 0; + model->meshes[primitiveIndex].boneWeights[b] = 0.0f; + } + + } + + Vector3 boundVertex = { 0 }; + Vector3 boundNormal = { 0 }; + + Vector3 outTranslation = { 0 }; + Quaternion outRotation = { 0 }; + Vector3 outScale = { 0 }; + + int vCounter = 0; + int boneCounter = 0; + int boneId = 0; + + for (int i = 0; i < model->meshes[primitiveIndex].vertexCount; i++) + { + boneId = model->meshes[primitiveIndex].boneIds[boneCounter]; + outTranslation = model->bindPose[boneId].translation; + outRotation = model->bindPose[boneId].rotation; + outScale = model->bindPose[boneId].scale; + + // Vertices processing + boundVertex = (Vector3){ model->meshes[primitiveIndex].vertices[vCounter], model->meshes[primitiveIndex].vertices[vCounter + 1], model->meshes[primitiveIndex].vertices[vCounter + 2] }; + boundVertex = Vector3Multiply(boundVertex, outScale); + boundVertex = Vector3RotateByQuaternion(boundVertex, outRotation); + boundVertex = Vector3Add(boundVertex, outTranslation); + model->meshes[primitiveIndex].vertices[vCounter] = boundVertex.x; + model->meshes[primitiveIndex].vertices[vCounter + 1] = boundVertex.y; + model->meshes[primitiveIndex].vertices[vCounter + 2] = boundVertex.z; + + // Normals processing + if (model->meshes[primitiveIndex].normals != NULL) + { + boundNormal = (Vector3){ model->meshes[primitiveIndex].normals[vCounter], model->meshes[primitiveIndex].normals[vCounter + 1], model->meshes[primitiveIndex].normals[vCounter + 2] }; + boundNormal = Vector3RotateByQuaternion(boundNormal, outRotation); + model->meshes[primitiveIndex].normals[vCounter] = boundNormal.x; + model->meshes[primitiveIndex].normals[vCounter + 1] = boundNormal.y; + model->meshes[primitiveIndex].normals[vCounter + 2] = boundNormal.z; + } + + vCounter += 3; + boneCounter += 4; + } + } + } + } +} + +static void LoadGLTFModelIndices(Model* model, cgltf_accessor* indexAccessor, int primitiveIndex) +{ + if (indexAccessor) + { + if (indexAccessor->component_type == cgltf_component_type_r_16u || indexAccessor->component_type == cgltf_component_type_r_16) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned short readValue = 0; + for (unsigned int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(short)); + model->meshes[primitiveIndex].indices[a] = readValue; + } + } + else if (indexAccessor->component_type == cgltf_component_type_r_8u || indexAccessor->component_type == cgltf_component_type_r_8) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned char readValue = 0; + for (unsigned int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(char)); + model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; + } + } + else if (indexAccessor->component_type == cgltf_component_type_r_32u) + { + model->meshes[primitiveIndex].triangleCount = (int)indexAccessor->count/3; + model->meshes[primitiveIndex].indices = RL_MALLOC(model->meshes[primitiveIndex].triangleCount*3*sizeof(unsigned short)); + + unsigned int readValue; + for (unsigned int a = 0; a < indexAccessor->count; a++) + { + GLTFReadValue(indexAccessor, a, &readValue, 1, sizeof(unsigned int)); + model->meshes[primitiveIndex].indices[a] = (unsigned short)readValue; + } + } + } + else + { + // Unindexed mesh + model->meshes[primitiveIndex].triangleCount = model->meshes[primitiveIndex].vertexCount/3; + } +} + +// LoadGLTF loads in animation data from given filename +static ModelAnimation *LoadGLTFModelAnimations(const char *fileName, int *animCount) +{ + /*********************************************************************************** + + Function implemented by Hristo Stamenov (@object71) + + Features: + - Supports .gltf and .glb files + + Some restrictions (not exhaustive): + - ... + + *************************************************************************************/ + + // glTF file loading + unsigned int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + ModelAnimation *animations = NULL; + + if (fileData == NULL) return animations; + + // glTF data loading + cgltf_options options = { 0 }; + cgltf_data *data = NULL; + cgltf_result result = cgltf_parse(&options, fileData, dataSize, &data); + + if (result == cgltf_result_success) + { + TRACELOG(LOG_INFO, "MODEL: [%s] glTF animations (%s) count: %i", fileName, (data->file_type == 2)? "glb" : + "gltf", data->animations_count); + + result = cgltf_load_buffers(&options, data, fileName); + if (result != cgltf_result_success) TRACELOG(LOG_WARNING, "MODEL: [%s] unable to load glTF animations data", fileName); + animations = RL_MALLOC(data->animations_count*sizeof(ModelAnimation)); + *animCount = (int)data->animations_count; + + for (unsigned int a = 0; a < data->animations_count; a++) + { + // gltf animation consists of the following structures: + // - nodes - bones + // - channels - single transformation type on a single bone + // - node - bone + // - transformation type (path) - translation, rotation, scale + // - sampler - animation samples + // - input - points in time this transformation happens + // - output - the transformation amount at the given input points in time + // - interpolation - the type of interpolation to use between the frames + + cgltf_animation *animation = data->animations + a; + + ModelAnimation *output = animations + a; + + // 30 frames sampled per second + const float timeStep = (1.0f/30.0f); + float animationDuration = 0.0f; + + // Getting the max animation time to consider for animation duration + for (unsigned int i = 0; i < animation->channels_count; i++) + { + cgltf_animation_channel* channel = animation->channels + i; + int frameCounts = (int)channel->sampler->input->count; + float lastFrameTime = 0.0f; + + if (GLTFReadValue(channel->sampler->input, frameCounts - 1, &lastFrameTime, 1, sizeof(float))) + { + animationDuration = fmaxf(lastFrameTime, animationDuration); + } + } + + output->frameCount = (int)(animationDuration / timeStep); + output->boneCount = (int)data->nodes_count; + output->bones = RL_MALLOC(output->boneCount*sizeof(BoneInfo)); + output->framePoses = RL_MALLOC(output->frameCount*sizeof(Transform *)); + // output->framerate = // TODO: Use framerate instead of const timestep + + // Name and parent bones + for (unsigned int j = 0; j < output->boneCount; j++) + { + strcpy(output->bones[j].name, data->nodes[j].name == 0 ? "ANIMJOINT" : data->nodes[j].name); + output->bones[j].parent = (data->nodes[j].parent != NULL) ? (int)(data->nodes[j].parent - data->nodes) : -1; + } + + // Allocate data for frames + // Initiate with zero bone translations + for (int frame = 0; frame < output->frameCount; frame++) + { + output->framePoses[frame] = RL_MALLOC(output->boneCount*sizeof(Transform)); + + for (unsigned int i = 0; i < output->boneCount; i++) + { + output->framePoses[frame][i].translation = Vector3Zero(); + output->framePoses[frame][i].rotation = QuaternionIdentity(); + output->framePoses[frame][i].rotation = QuaternionNormalize(output->framePoses[frame][i].rotation); + output->framePoses[frame][i].scale = Vector3One(); + } + } + + // for each single transformation type on single bone + for (unsigned int channelId = 0; channelId < animation->channels_count; channelId++) + { + cgltf_animation_channel* channel = animation->channels + channelId; + cgltf_animation_sampler* sampler = channel->sampler; + + int boneId = (int)(channel->target_node - data->nodes); + + for (int frame = 0; frame < output->frameCount; frame++) + { + bool shouldSkipFurtherTransformation = true; + int outputMin = 0; + int outputMax = 0; + float frameTime = frame*timeStep; + float lerpPercent = 0.0f; + + // For this transformation: + // getting between which input values the current frame time position + // and also what is the percent to use in the linear interpolation later + for (unsigned int j = 0; j < sampler->input->count; j++) + { + float inputFrameTime; + if (GLTFReadValue(sampler->input, j, &inputFrameTime, 1, sizeof(float))) + { + if (frameTime < inputFrameTime) + { + shouldSkipFurtherTransformation = false; + outputMin = (j == 0) ? 0 : j - 1; + outputMax = j; + + float previousInputTime = 0.0f; + if (GLTFReadValue(sampler->input, outputMin, &previousInputTime, 1, sizeof(float))) + { + if ((inputFrameTime - previousInputTime) != 0) + { + lerpPercent = (frameTime - previousInputTime)/(inputFrameTime - previousInputTime); + } + } + + break; + } + } + else break; + } + + // If the current transformation has no information for the current frame time point + if (shouldSkipFurtherTransformation) continue; + + if (channel->target_path == cgltf_animation_path_type_translation) + { + Vector3 translationStart; + Vector3 translationEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &translationStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &translationEnd, 3, sizeof(float)) || success; + + if (success) output->framePoses[frame][boneId].translation = Vector3Lerp(translationStart, translationEnd, lerpPercent); + } + if (channel->target_path == cgltf_animation_path_type_rotation) + { + Quaternion rotationStart; + Quaternion rotationEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &rotationStart, 4, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &rotationEnd, 4, sizeof(float)) || success; + + if (success) + { + output->framePoses[frame][boneId].rotation = QuaternionLerp(rotationStart, rotationEnd, lerpPercent); + output->framePoses[frame][boneId].rotation = QuaternionNormalize(output->framePoses[frame][boneId].rotation); + } + } + if (channel->target_path == cgltf_animation_path_type_scale) + { + Vector3 scaleStart; + Vector3 scaleEnd; + + bool success = GLTFReadValue(sampler->output, outputMin, &scaleStart, 3, sizeof(float)); + success = GLTFReadValue(sampler->output, outputMax, &scaleEnd, 3, sizeof(float)) || success; + + if (success) output->framePoses[frame][boneId].scale = Vector3Lerp(scaleStart, scaleEnd, lerpPercent); + } + } + } + + // Build frameposes + for (int frame = 0; frame < output->frameCount; frame++) + { + bool *completedBones = RL_CALLOC(output->boneCount, sizeof(bool)); + int numberCompletedBones = 0; + + while (numberCompletedBones < output->boneCount) + { + for (int i = 0; i < output->boneCount; i++) + { + if (completedBones[i]) continue; + + if (output->bones[i].parent < 0) + { + completedBones[i] = true; + numberCompletedBones++; + continue; + } + + if (!completedBones[output->bones[i].parent]) continue; + + output->framePoses[frame][i].rotation = QuaternionMultiply(output->framePoses[frame][output->bones[i].parent].rotation, output->framePoses[frame][i].rotation); + output->framePoses[frame][i].translation = Vector3RotateByQuaternion(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].rotation); + output->framePoses[frame][i].translation = Vector3Add(output->framePoses[frame][i].translation, output->framePoses[frame][output->bones[i].parent].translation); + output->framePoses[frame][i].scale = Vector3Multiply(output->framePoses[frame][i].scale, output->framePoses[frame][output->bones[i].parent].scale); + completedBones[i] = true; + numberCompletedBones++; + } + } + + RL_FREE(completedBones); + } + + } + + cgltf_free(data); + } + else TRACELOG(LOG_WARNING, ": [%s] Failed to load glTF data", fileName); + + RL_FREE(fileData); + + return animations; +} + #endif diff --git a/raylib/raudio.c b/raylib/raudio.c index 8261c7c..639d6cd 100644 --- a/raylib/raudio.c +++ b/raylib/raudio.c @@ -1,8 +1,6 @@ -// +build !noaudio - /********************************************************************************************** * -* raudio - A simple and easy-to-use audio library based on miniaudio +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio * * FEATURES: * - Manage audio device (init/close) @@ -48,7 +46,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -165,6 +163,7 @@ typedef struct tagBITMAPINFOHEADER { #define MA_NO_FLAC #define MA_NO_MP3 #define MINIAUDIO_IMPLEMENTATION +//#define MA_DEBUG_OUTPUT #include "external/miniaudio.h" // miniaudio library #undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro @@ -177,7 +176,7 @@ typedef struct tagBITMAPINFOHEADER { #if !defined(TRACELOG) #define TRACELOG(level, ...) (void)0 #endif - + // Allow custom memory allocators #ifndef RL_MALLOC #define RL_MALLOC(sz) malloc(sz) @@ -257,8 +256,9 @@ typedef struct tagBITMAPINFOHEADER { #ifndef AUDIO_DEVICE_CHANNELS #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo #endif + #ifndef AUDIO_DEVICE_SAMPLE_RATE - #define AUDIO_DEVICE_SAMPLE_RATE 44100 // Device output sample rate + #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output channels: stereo #endif #ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels @@ -276,7 +276,8 @@ typedef struct tagBITMAPINFOHEADER { // NOTE: Depends on data structure provided by the library // in charge of reading the different file types typedef enum { - MUSIC_AUDIO_WAV = 0, + MUSIC_AUDIO_NONE = 0, + MUSIC_AUDIO_WAV, MUSIC_AUDIO_OGG, MUSIC_AUDIO_FLAC, MUSIC_AUDIO_MP3, @@ -294,7 +295,7 @@ typedef enum { LOG_ERROR, LOG_FATAL, LOG_NONE -} TraceLogType; +} TraceLogLevel; #endif // NOTE: Different logic is used when feeding data to the playback device @@ -343,8 +344,8 @@ typedef struct AudioData { int defaultSize; // Default audio buffer size for audio streams } Buffer; struct { - AudioBuffer *pool[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // Multichannel AudioBuffer pointers pool unsigned int poolCounter; // AudioBuffer pointers pool counter + AudioBuffer *pool[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // Multichannel AudioBuffer pointers pool unsigned int channels[MAX_AUDIO_BUFFER_POOL_CHANNELS]; // AudioBuffer pool channels } MultiChannel; } AudioData; @@ -358,7 +359,7 @@ static AudioData AUDIO = { // Global AUDIO context // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough // In case of music-stalls, just increase this number - .Buffer.defaultSize = DEFAULT_AUDIO_BUFFER_SIZE + .Buffer.defaultSize = 0 }; //---------------------------------------------------------------------------------- @@ -368,28 +369,29 @@ static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, float localVolume); -static void InitAudioBufferPool(void); // Initialise the multichannel buffer pool -static void CloseAudioBufferPool(void); // Close the audio buffers pool - #if defined(SUPPORT_FILEFORMAT_WAV) -static Wave LoadWAV(const char *fileName); // Load WAV file +static Wave LoadWAV(const unsigned char *fileData, unsigned int fileSize); // Load WAV file static int SaveWAV(Wave wave, const char *fileName); // Save wave data as WAV file #endif #if defined(SUPPORT_FILEFORMAT_OGG) -static Wave LoadOGG(const char *fileName); // Load OGG file +static Wave LoadOGG(const unsigned char *fileData, unsigned int fileSize); // Load OGG file #endif #if defined(SUPPORT_FILEFORMAT_FLAC) -static Wave LoadFLAC(const char *fileName); // Load FLAC file +static Wave LoadFLAC(const unsigned char *fileData, unsigned int fileSize); // Load FLAC file #endif #if defined(SUPPORT_FILEFORMAT_MP3) -static Wave LoadMP3(const char *fileName); // Load MP3 file +static Wave LoadMP3(const unsigned char *fileData, unsigned int fileSize); // Load MP3 file #endif #if defined(RAUDIO_STANDALONE) static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension +static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png) +static bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +static const char *TextToLower(const char *text); // Get lower case version of provided string + static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) -static void SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write) -static void SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated +static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write) +static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated #endif //---------------------------------------------------------------------------------- @@ -408,6 +410,7 @@ void SetAudioBufferVolume(AudioBuffer *buffer, float volume); void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); void TrackAudioBuffer(AudioBuffer *buffer); void UntrackAudioBuffer(AudioBuffer *buffer); +int GetAudioStreamBufferSizeDefault(); //---------------------------------------------------------------------------------- // Module Functions Definition - Audio Device initialization and Closing @@ -424,7 +427,7 @@ void InitAudioDevice(void) ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); if (result != MA_SUCCESS) { - TRACELOG(LOG_ERROR, "AUDIO: Failed to initialize context"); + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); return; } @@ -444,7 +447,7 @@ void InitAudioDevice(void) result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device); if (result != MA_SUCCESS) { - TRACELOG(LOG_ERROR, "AUDIO: Failed to initialize playback device"); + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); ma_context_uninit(&AUDIO.System.context); return; } @@ -454,7 +457,7 @@ void InitAudioDevice(void) result = ma_device_start(&AUDIO.System.device); if (result != MA_SUCCESS) { - TRACELOG(LOG_ERROR, "AUDIO: Failed to start playback device"); + TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); return; @@ -464,12 +467,20 @@ void InitAudioDevice(void) // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS) { - TRACELOG(LOG_ERROR, "AUDIO: Failed to create mutex for mixing"); + TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); return; } + // Init dummy audio buffers pool for multichannel sound playing + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + // WARNING: An empty audioBuffer is created (data = 0) + // AudioBuffer data just points to loaded sound data + AUDIO.MultiChannel.pool[i] = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); + } + TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); TRACELOG(LOG_INFO, " > Backend: miniaudio / %s", ma_get_backend_name(AUDIO.System.context.backend)); TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat)); @@ -477,8 +488,6 @@ void InitAudioDevice(void) TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate); TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); - InitAudioBufferPool(); - AUDIO.System.isReady = true; } @@ -487,11 +496,25 @@ void CloseAudioDevice(void) { if (AUDIO.System.isReady) { + // Unload dummy audio buffers pool + // WARNING: They can be pointing to already unloaded data + for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) + { + //UnloadAudioBuffer(AUDIO.MultiChannel.pool[i]); + if (AUDIO.MultiChannel.pool[i] != NULL) + { + ma_data_converter_uninit(&AUDIO.MultiChannel.pool[i]->converter); + UntrackAudioBuffer(AUDIO.MultiChannel.pool[i]); + //RL_FREE(buffer->data); // Already unloaded by UnloadSound() + RL_FREE(AUDIO.MultiChannel.pool[i]); + } + } + ma_mutex_uninit(&AUDIO.System.lock); ma_device_uninit(&AUDIO.System.device); ma_context_uninit(&AUDIO.System.context); - CloseAudioBufferPool(); + AUDIO.System.isReady = false; TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); } @@ -521,21 +544,21 @@ AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sam if (audioBuffer == NULL) { - TRACELOG(LOG_ERROR, "AUDIO: Failed to allocate memory for buffer"); + TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer"); return NULL; } if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1); // Audio data runs through a format converter - ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO_DEVICE_SAMPLE_RATE); + ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); converterConfig.resampling.allowDynamicSampleRate = true; // Required for pitch shifting ma_result result = ma_data_converter_init(&converterConfig, &audioBuffer->converter); if (result != MA_SUCCESS) { - TRACELOG(LOG_ERROR, "AUDIO: Failed to create data conversion pipeline"); + TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline"); RL_FREE(audioBuffer); return NULL; } @@ -634,18 +657,16 @@ void SetAudioBufferVolume(AudioBuffer *buffer, float volume) // Set pitch for an audio buffer void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) { - if (buffer != NULL) + if ((buffer != NULL) && (pitch > 0.0f)) { - float pitchMul = pitch/buffer->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 - ma_uint32 newOutputSampleRate = (ma_uint32)((float)buffer->converter.config.sampleRateOut/pitchMul); - buffer->pitch *= (float)buffer->converter.config.sampleRateOut/newOutputSampleRate; + ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.config.sampleRateOut/pitch); + ma_data_converter_set_rate(&buffer->converter, buffer->converter.config.sampleRateIn, outputSampleRate); - ma_data_converter_set_rate(&buffer->converter, buffer->converter.config.sampleRateIn, newOutputSampleRate); + buffer->pitch = pitch; } } @@ -692,20 +713,43 @@ Wave LoadWave(const char *fileName) { Wave wave = { 0 }; + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) + { + // Loading wave from memory data + wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, fileSize); + + RL_FREE(fileData); + } + + return wave; +} + +// Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Wave wave = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) - else if (IsFileExtension(fileName, ".wav")) wave = LoadWAV(fileName); + else if (TextIsEqual(fileExtLower, ".wav")) wave = LoadWAV(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_OGG) - else if (IsFileExtension(fileName, ".ogg")) wave = LoadOGG(fileName); + else if (TextIsEqual(fileExtLower, ".ogg")) wave = LoadOGG(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (IsFileExtension(fileName, ".flac")) wave = LoadFLAC(fileName); + else if (TextIsEqual(fileExtLower, ".flac")) wave = LoadFLAC(fileData, dataSize); #endif #if defined(SUPPORT_FILEFORMAT_MP3) - else if (IsFileExtension(fileName, ".mp3")) wave = LoadMP3(fileName); + else if (TextIsEqual(fileExtLower, ".mp3")) wave = LoadMP3(fileData, dataSize); #endif - else TRACELOG(LOG_WARNING, "FILEIO: [%s] File format not supported", fileName); + else TRACELOG(LOG_WARNING, "WAVE: File format not supported"); return wave; } @@ -743,17 +787,21 @@ Sound LoadSoundFromWave(Wave wave) ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); ma_uint32 frameCountIn = wave.sampleCount/wave.channels; - ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO_DEVICE_SAMPLE_RATE, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); - AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO_DEVICE_SAMPLE_RATE, frameCount, AUDIO_BUFFER_USAGE_STATIC); - if (audioBuffer == NULL) TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + return sound; // early return to avoid dereferencing the audioBuffer null pointer + } - frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO_DEVICE_SAMPLE_RATE, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); + frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); sound.sampleCount = frameCount*AUDIO_DEVICE_CHANNELS; - sound.stream.sampleRate = AUDIO_DEVICE_SAMPLE_RATE; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; sound.stream.sampleSize = 32; sound.stream.channels = AUDIO_DEVICE_CHANNELS; sound.stream.buffer = audioBuffer; @@ -791,7 +839,7 @@ void UpdateSound(Sound sound, const void *data, int samplesCount) } // Export wave data to file -void ExportWave(Wave wave, const char *fileName) +bool ExportWave(Wave wave, const char *fileName) { bool success = false; @@ -803,17 +851,20 @@ void ExportWave(Wave wave, const char *fileName) { // Export raw sample data (without header) // NOTE: It's up to the user to track wave parameters - SaveFileData(fileName, wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8); - success = true; + success = SaveFileData(fileName, wave.data, wave.sampleCount*wave.sampleSize/8); } if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName); + + return success; } // Export wave sample data to code (.h) -void ExportWaveAsCode(Wave wave, const char *fileName) +bool ExportWaveAsCode(Wave wave, const char *fileName) { + bool success = false; + #ifndef TEXT_BYTES_PER_LINE #define TEXT_BYTES_PER_LINE 20 #endif @@ -857,9 +908,11 @@ void ExportWaveAsCode(Wave wave, const char *fileName) bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); // NOTE: Text data length exported is determined by '\0' (NULL) character - SaveFileText(fileName, txtData); + success = SaveFileText(fileName, txtData); RL_FREE(txtData); + + return success; } // Play a sound @@ -986,10 +1039,10 @@ void SetSoundPitch(Sound sound, float pitch) // Convert wave data to desired format void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) { - ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); - ma_format formatOut = (( sampleSize == 8)? ma_format_u8 : (( sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); - ma_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so. + ma_uint32 frameCountIn = wave->sampleCount/wave->channels; ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); if (frameCount == 0) @@ -1007,7 +1060,7 @@ void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) return; } - wave->sampleCount = frameCount; + wave->sampleCount = frameCount*channels; wave->sampleSize = sampleSize; wave->sampleRate = sampleRate; wave->channels = channels; @@ -1020,12 +1073,12 @@ Wave WaveCopy(Wave wave) { Wave newWave = { 0 }; - newWave.data = RL_MALLOC(wave.sampleCount*wave.sampleSize/8*wave.channels); + newWave.data = RL_MALLOC(wave.sampleCount*wave.sampleSize/8); if (newWave.data != NULL) { // NOTE: Size must be provided in bytes - memcpy(newWave.data, wave.data, wave.sampleCount*wave.channels*wave.sampleSize/8); + memcpy(newWave.data, wave.data, wave.sampleCount*wave.sampleSize/8); newWave.sampleCount = wave.sampleCount; newWave.sampleRate = wave.sampleRate; @@ -1045,9 +1098,9 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) { int sampleCount = finalSample - initSample; - void *data = RL_MALLOC(sampleCount*wave->sampleSize/8*wave->channels); + void *data = RL_MALLOC(sampleCount*wave->sampleSize/8); - memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->channels*wave->sampleSize/8); + memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->sampleSize/8); RL_FREE(wave->data); wave->data = data; @@ -1055,25 +1108,31 @@ void WaveCrop(Wave *wave, int initSample, int finalSample) else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds"); } -// Get samples data from wave as a floats array -// NOTE: Returned sample values are normalized to range [-1..1] -float *GetWaveData(Wave wave) +// Load samples data from wave as a floats array +// NOTE 1: Returned sample values are normalized to range [-1..1] +// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() +float *LoadWaveSamples(Wave wave) { - float *samples = (float *)RL_MALLOC(wave.sampleCount*wave.channels*sizeof(float)); + float *samples = (float *)RL_MALLOC(wave.sampleCount*sizeof(float)); + + // NOTE: sampleCount is the total number of interlaced samples (including channels) for (unsigned int i = 0; i < wave.sampleCount; i++) { - for (unsigned int j = 0; j < wave.channels; j++) - { - if (wave.sampleSize == 8) samples[wave.channels*i + j] = (float)(((unsigned char *)wave.data)[wave.channels*i + j] - 127)/256.0f; - else if (wave.sampleSize == 16) samples[wave.channels*i + j] = (float)((short *)wave.data)[wave.channels*i + j]/32767.0f; - else if (wave.sampleSize == 32) samples[wave.channels*i + j] = ((float *)wave.data)[wave.channels*i + j]; - } + if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; + else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f; + else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; } return samples; } +// Unload samples data loaded with LoadWaveSamples() +void UnloadWaveSamples(float *samples) +{ + RL_FREE(samples); +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Music loading and stream playing (.OGG) //---------------------------------------------------------------------------------- @@ -1088,14 +1147,14 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_WAV) else if (IsFileExtension(fileName, ".wav")) { - drwav *ctxWav = RL_MALLOC(sizeof(drwav)); + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); bool success = drwav_init_file(ctxWav, fileName, NULL); + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + if (success) { - music.ctxType = MUSIC_AUDIO_WAV; - music.ctxData = ctxWav; - int sampleSize = ctxWav->bitsPerSample; if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() @@ -1110,15 +1169,17 @@ Music LoadMusicStream(const char *fileName) else if (IsFileExtension(fileName, ".ogg")) { // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); if (music.ctxData != NULL) { - music.ctxType = MUSIC_AUDIO_OGG; stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info // OGG bit rate defaults to 16 bit, it's enough for compressed format music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; music.looping = true; // Looping enabled by default musicLoaded = true; @@ -1128,15 +1189,15 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_FLAC) else if (IsFileExtension(fileName, ".flac")) { - music.ctxData = drflac_open_file(fileName); + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_file(fileName, NULL); if (music.ctxData != NULL) { - music.ctxType = MUSIC_AUDIO_FLAC; drflac *ctxFlac = (drflac *)music.ctxData; music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); - music.sampleCount = (unsigned int)ctxFlac->totalSampleCount; + music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; music.looping = true; // Looping enabled by default musicLoaded = true; } @@ -1145,15 +1206,14 @@ Music LoadMusicStream(const char *fileName) #if defined(SUPPORT_FILEFORMAT_MP3) else if (IsFileExtension(fileName, ".mp3")) { - drmp3 *ctxMp3 = RL_MALLOC(sizeof(drmp3)); - music.ctxData = ctxMp3; - + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); int result = drmp3_init_file(ctxMp3, fileName, NULL); + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + if (result > 0) { - music.ctxType = MUSIC_AUDIO_MP3; - music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; music.looping = true; // Looping enabled by default @@ -1165,49 +1225,52 @@ Music LoadMusicStream(const char *fileName) else if (IsFileExtension(fileName, ".xm")) { jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName); - int result = jar_xm_create_context_from_file(&ctxXm, 48000, fileName); + music.ctxType = MUSIC_MODULE_XM; + music.ctxData = ctxXm; if (result == 0) // XM AUDIO.System.context created successfully { - music.ctxType = MUSIC_MODULE_XM; jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) + bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) + bits = 8; + // NOTE: Only stereo is supported for XM - music.stream = InitAudioStream(48000, 16, 2); - music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels music.looping = true; // Looping enabled by default jar_xm_reset(ctxXm); // make sure we start at the beginning of the song musicLoaded = true; - - music.ctxData = ctxXm; } } #endif #if defined(SUPPORT_FILEFORMAT_MOD) else if (IsFileExtension(fileName, ".mod")) { - jar_mod_context_t *ctxMod = RL_MALLOC(sizeof(jar_mod_context_t)); - + jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t)); jar_mod_init(ctxMod); int result = jar_mod_load_file(ctxMod, fileName); + music.ctxType = MUSIC_MODULE_MOD; + music.ctxData = ctxMod; + if (result > 0) { - music.ctxType = MUSIC_MODULE_MOD; - // NOTE: Only stereo is supported for MOD - music.stream = InitAudioStream(48000, 16, 2); - music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels music.looping = true; // Looping enabled by default musicLoaded = true; - - music.ctxData = ctxMod; } } #endif else TRACELOG(LOG_WARNING, "STREAM: [%s] Fileformat not supported", fileName); - + if (!musicLoaded) { if (false) { } @@ -1218,7 +1281,7 @@ Music LoadMusicStream(const char *fileName) else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData); + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } @@ -1230,6 +1293,7 @@ Music LoadMusicStream(const char *fileName) else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif + music.ctxData = NULL; TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName); } else @@ -1245,30 +1309,227 @@ Music LoadMusicStream(const char *fileName) return music; } +// extension including period ".mod" +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize) +{ + Music music = { 0 }; + bool musicLoaded = false; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (TextIsEqual(fileExtLower, ".wav")) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + + bool success = drwav_init_memory(ctxWav, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = InitAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.sampleCount = (unsigned int)ctxWav->totalPCMFrameCount*ctxWav->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (TextIsEqual(fileExtLower, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = InitAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.sampleCount = (unsigned int)ctxFlac->totalPCMFrameCount*ctxFlac->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (TextIsEqual(fileExtLower, ".mp3")) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (success) + { + music.stream = InitAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.sampleCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3)*ctxMp3->channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (TextIsEqual(fileExtLower, ".ogg")) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + music.ctxData = stb_vorbis_open_memory((const unsigned char*)data, dataSize, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = InitAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData)*info.channels; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (TextIsEqual(fileExtLower, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_safe(&ctxXm, (const char*)data, dataSize, AUDIO.System.device.sampleRate); + if (result == 0) // XM AUDIO.System.context created successfully + { + music.ctxType = MUSIC_MODULE_XM; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) + bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) + bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, bits, 2); + music.sampleCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm)*2; // 2 channels + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + + music.ctxData = ctxXm; + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (TextIsEqual(fileExtLower, ".mod")) + { + jar_mod_context_t *ctxMod = RL_MALLOC(sizeof(jar_mod_context_t)); + int result = 0; + + jar_mod_init(ctxMod); + + // copy data to allocated memory for default UnloadMusicStream + unsigned char *newData = RL_MALLOC(dataSize); + int it = dataSize/sizeof(unsigned char); + for (int i = 0; i < it; i++){ + newData[i] = data[i]; + } + + // Memory loaded version for jar_mod_load_file() + if (dataSize && dataSize < 32*1024*1024) + { + ctxMod->modfilesize = dataSize; + ctxMod->modfile = newData; + if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize; + } + + if (result > 0) + { + music.ctxType = MUSIC_MODULE_MOD; + + // NOTE: Only stereo is supported for MOD + music.stream = InitAudioStream(AUDIO.System.device.sampleRate, 16, 2); + music.sampleCount = (unsigned int)jar_mod_max_samples(ctxMod)*2; // 2 channels + music.looping = true; // Looping enabled by default + musicLoaded = true; + + music.ctxData = ctxMod; + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] Fileformat not supported", fileType); + + if (!musicLoaded) + { + if (false) { } + #if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + #endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music memory could not be opened", fileType); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music memory successfully loaded:", fileType); + TRACELOG(LOG_INFO, " > Total samples: %i", music.sampleCount); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + } + + return music; +} + // Unload music stream void UnloadMusicStream(Music music) { CloseAudioStream(music.stream); - if (false) { } + if (music.ctxData != NULL) + { + if (false) { } #if defined(SUPPORT_FILEFORMAT_WAV) - else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_OGG) - else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_FLAC) - else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData); + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); #endif #if defined(SUPPORT_FILEFORMAT_MP3) else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } #endif #if defined(SUPPORT_FILEFORMAT_XM) - else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); #endif #if defined(SUPPORT_FILEFORMAT_MOD) - else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } #endif + } } // Start music playing (open stream) @@ -1279,7 +1540,7 @@ void PlayMusicStream(Music music) // For music streams, we need to make sure we maintain the frame cursor position // This is a 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); + // play again on window restore: if (IsMusicStreamPlaying(music)) PlayMusicStream(music); ma_uint32 frameCursorPos = music.stream.buffer->frameCursorPos; PlayAudioStream(music.stream); // WARNING: This resets the cursor position. music.stream.buffer->frameCursorPos = frameCursorPos; @@ -1332,6 +1593,10 @@ void UpdateMusicStream(Music music) { if (music.stream.buffer == NULL) return; +#if defined(SUPPORT_FILEFORMAT_XM) + if (music.ctxType == MUSIC_MODULE_XM) jar_xm_set_max_loop_count(music.ctxData, music.looping ? 0 : 1); +#endif + bool streamEnding = false; unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; @@ -1345,6 +1610,8 @@ void UpdateMusicStream(Music music) //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; int sampleLeft = music.sampleCount - (music.stream.buffer->totalFramesProcessed*music.stream.channels); + if (music.ctxType == MUSIC_MODULE_XM && music.looping) sampleLeft = subBufferSizeInFrames*4; + while (IsAudioStreamProcessed(music.stream)) { if ((sampleLeft/music.stream.channels) >= subBufferSizeInFrames) samplesCount = subBufferSizeInFrames*music.stream.channels; @@ -1388,8 +1655,24 @@ void UpdateMusicStream(Music music) #if defined(SUPPORT_FILEFORMAT_XM) case MUSIC_MODULE_XM: { - // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 - jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)pcm, samplesCount/2); + switch (AUDIO_DEVICE_FORMAT) + { + case ma_format_f32: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples((jar_xm_context_t*)music.ctxData, (float*)pcm, samplesCount / 2); + break; + + case ma_format_s16: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_16bit((jar_xm_context_t*)music.ctxData, (short*)pcm, samplesCount / 2); + break; + + case ma_format_u8: + // NOTE: Internally this function considers 2 channels generation, so samplesCount/2 + jar_xm_generate_samples_8bit((jar_xm_context_t*)music.ctxData, (char*)pcm, samplesCount / 2); + break; + } + } break; #endif #if defined(SUPPORT_FILEFORMAT_MOD) @@ -1404,7 +1687,7 @@ void UpdateMusicStream(Music music) UpdateAudioStream(music.stream, pcm, samplesCount); - if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) + if ((music.ctxType == MUSIC_MODULE_XM) || music.ctxType == MUSIC_MODULE_MOD) { if (samplesCount > 1) sampleLeft -= samplesCount/2; else sampleLeft -= samplesCount; @@ -1431,12 +1714,12 @@ void UpdateMusicStream(Music music) { // NOTE: In case window is minimized, music stream is stopped, // just make sure to play again on window restore - if (IsMusicPlaying(music)) PlayMusicStream(music); + if (IsMusicStreamPlaying(music)) PlayMusicStream(music); } } // Check if any music is playing -bool IsMusicPlaying(Music music) +bool IsMusicStreamPlaying(Music music) { return IsAudioStreamPlaying(music.stream); } @@ -1450,7 +1733,7 @@ void SetMusicVolume(Music music, float volume) // Set pitch for music void SetMusicPitch(Music music, float pitch) { - SetAudioStreamPitch(music.stream, pitch); + SetAudioBufferPitch(music.stream.buffer, pitch); } // Get music time length (in seconds) @@ -1466,13 +1749,22 @@ float GetMusicTimeLength(Music music) // Get current music time played (in seconds) float GetMusicTimePlayed(Music music) { - float secondsPlayed = 0.0f; +#if defined(SUPPORT_FILEFORMAT_XM) + if (music.ctxType == MUSIC_MODULE_XM) + { + uint64_t samples = 0; + jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &samples); + samples = samples % (music.sampleCount); + return (float)(samples)/(music.stream.sampleRate*music.stream.channels); + } +#endif + float secondsPlayed = 0.0f; if (music.stream.buffer != NULL) { //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; unsigned int samplesPlayed = music.stream.buffer->totalFramesProcessed*music.stream.channels; - secondsPlayed = (float)samplesPlayed / (music.stream.sampleRate*music.stream.channels); + secondsPlayed = (float)samplesPlayed/(music.stream.sampleRate*music.stream.channels); } return secondsPlayed; @@ -1491,7 +1783,7 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un // The size of a streaming buffer must be at least double the size of a period unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; - unsigned int subBufferSize = AUDIO.Buffer.defaultSize; // Default buffer size (audio stream) + unsigned int subBufferSize = GetAudioStreamBufferSizeDefault(); if (subBufferSize < periodSize) subBufferSize = periodSize; @@ -1626,6 +1918,15 @@ void SetAudioStreamBufferSizeDefault(int size) AUDIO.Buffer.defaultSize = size; } +int GetAudioStreamBufferSizeDefault() +{ + // if the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate + if (AUDIO.Buffer.defaultSize == 0) + AUDIO.Buffer.defaultSize = AUDIO.System.device.sampleRate/30; + + return AUDIO.Buffer.defaultSize; +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- @@ -1636,7 +1937,7 @@ static void OnLog(ma_context *pContext, ma_device *pDevice, ma_uint32 logLevel, (void)pContext; (void)pDevice; - TRACELOG(LOG_ERROR, "miniaudio: %s", message); // All log messages from miniaudio are errors + TRACELOG(LOG_WARNING, "miniaudio: %s", message); // All log messages from miniaudio are errors } // Reads audio data from an AudioBuffer object in internal format. @@ -1746,7 +2047,7 @@ static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, f inputFramesToProcessThisIteration = inputBufferFrameCap; } - float *runningFramesOut = framesOut + (totalOutputFramesProcessed * audioBuffer->converter.config.channelsOut); + float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.config.channelsOut); /* At this point we can convert the data to our mixing format. */ ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration); /* Safe cast. */ @@ -1870,183 +2171,145 @@ static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 fr } } -// Initialise the multichannel buffer pool -static void InitAudioBufferPool(void) -{ - // Dummy buffers - for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) - { - // WARNING: An empty audioBuffer is created (data = 0) - AUDIO.MultiChannel.pool[i] = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO_DEVICE_SAMPLE_RATE, 0, AUDIO_BUFFER_USAGE_STATIC); - } - - // TODO: Verification required for log - TRACELOG(LOG_INFO, "AUDIO: Multichannel pool size: %i", MAX_AUDIO_BUFFER_POOL_CHANNELS); -} - -// Close the audio buffers pool -static void CloseAudioBufferPool(void) -{ - for (int i = 0; i < MAX_AUDIO_BUFFER_POOL_CHANNELS; i++) RL_FREE(AUDIO.MultiChannel.pool[i]); -} - #if defined(SUPPORT_FILEFORMAT_WAV) -// Load WAV file into Wave structure -static Wave LoadWAV(const char *fileName) +// Load WAV file data into Wave structure +// NOTE: Using dr_wav library +static Wave LoadWAV(const unsigned char *fileData, unsigned int fileSize) { Wave wave = { 0 }; - - // Loading WAV from memory to avoid FILE accesses - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - drwav wav = { 0 }; - + bool success = drwav_init_memory(&wav, fileData, fileSize, NULL); - + if (success) { - wave.sampleCount = wav.totalPCMFrameCount*wav.channels; + wave.sampleCount = (unsigned int)wav.totalPCMFrameCount*wav.channels; wave.sampleRate = wav.sampleRate; wave.sampleSize = 16; // NOTE: We are forcing conversion to 16bit wave.channels = wav.channels; wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data); } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load WAV data", fileName); - + else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data"); + drwav_uninit(&wav); - RL_FREE(fileData); return wave; } // Save wave data as WAV file +// NOTE: Using dr_wav library static int SaveWAV(Wave wave, const char *fileName) { + int success = false; + drwav wav = { 0 }; drwav_data_format format = { 0 }; - format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. - format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. + format.container = drwav_container_riff; + format.format = DR_WAVE_FORMAT_PCM; format.channels = wave.channels; format.sampleRate = wave.sampleRate; format.bitsPerSample = wave.sampleSize; - - drwav_init_file_write(&wav, fileName, &format, NULL); - //drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); // TODO: Memory version - drwav_write_pcm_frames(&wav, wave.sampleCount/wave.channels, wave.data); - - drwav_uninit(&wav); - - // SaveFileData(fileName, fileData, fileDataSize); - //drwav_free(fileData, NULL); - - return true; + + void *fileData = NULL; + size_t fileDataSize = 0; + success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); + if (success) success = (int)drwav_write_pcm_frames(&wav, wave.sampleCount/wave.channels, wave.data); + drwav_result result = drwav_uninit(&wav); + + if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); + + drwav_free(fileData, NULL); + + return success; } #endif #if defined(SUPPORT_FILEFORMAT_OGG) -// Load OGG file into Wave structure +// Load OGG file data into Wave structure // NOTE: Using stb_vorbis library -static Wave LoadOGG(const char *fileName) +static Wave LoadOGG(const unsigned char *fileData, unsigned int fileSize) { Wave wave = { 0 }; - // Loading OGG from memory to avoid FILE accesses - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - - stb_vorbis *oggFile = stb_vorbis_open_memory(fileData, fileSize, NULL, NULL); + stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, fileSize, NULL, NULL); - if (oggFile == NULL) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open OGG file", fileName); - else + if (oggData != NULL) { - stb_vorbis_info info = stb_vorbis_get_info(oggFile); + stb_vorbis_info info = stb_vorbis_get_info(oggData); wave.sampleRate = info.sample_rate; wave.sampleSize = 16; // 16 bit per sample (short) wave.channels = info.channels; - wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggFile)*info.channels; // Independent by channel + wave.sampleCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData)*info.channels; // Independent by channel - float totalSeconds = stb_vorbis_stream_length_in_seconds(oggFile); - if (totalSeconds > 10) TRACELOG(LOG_WARNING, "WAVE: [%s] Ogg audio length larger than 10 seconds (%f), that's a big file in memory, consider music streaming", fileName, totalSeconds); + float totalSeconds = stb_vorbis_stream_length_in_seconds(oggData); + if (totalSeconds > 10) TRACELOG(LOG_WARNING, "WAVE: OGG audio length larger than 10 seconds (%f sec.), that's a big file in memory, consider music streaming", totalSeconds); - wave.data = (short *)RL_MALLOC(wave.sampleCount*wave.channels*sizeof(short)); + wave.data = (short *)RL_MALLOC(wave.sampleCount*sizeof(short)); // NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!) - stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, (short *)wave.data, wave.sampleCount*wave.channels); - TRACELOG(LOG_INFO, "WAVE: [%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.sampleCount); + TRACELOG(LOG_INFO, "WAVE: OGG data loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); - stb_vorbis_close(oggFile); + stb_vorbis_close(oggData); } - - RL_FREE(fileData); + else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); return wave; } #endif #if defined(SUPPORT_FILEFORMAT_FLAC) -// Load FLAC file into Wave structure +// Load FLAC file data into Wave structure // NOTE: Using dr_flac library -static Wave LoadFLAC(const char *fileName) +static Wave LoadFLAC(const unsigned char *fileData, unsigned int fileSize) { Wave wave = { 0 }; - - // Loading FLAC from memory to avoid FILE accesses - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - // Decode an entire FLAC file in one go - unsigned long long int totalSampleCount = 0; - wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, fileSize, &wave.channels, &wave.sampleRate, &totalSampleCount); + // Decode the entire FLAC file in one go + unsigned long long int totalFrameCount = 0; + wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, fileSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); - if (wave.data == NULL) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load FLAC data", fileName); - else + if (wave.data != NULL) { - wave.sampleCount = (unsigned int)totalSampleCount; + wave.sampleCount = (unsigned int)totalFrameCount*wave.channels; wave.sampleSize = 16; - TRACELOG(LOG_INFO, "WAVE: [%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + TRACELOG(LOG_INFO, "WAVE: FLAC data loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); } - - RL_FREE(fileData); - + else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); + return wave; } #endif #if defined(SUPPORT_FILEFORMAT_MP3) -// Load MP3 file into Wave structure +// Load MP3 file data into Wave structure // NOTE: Using dr_mp3 library -static Wave LoadMP3(const char *fileName) +static Wave LoadMP3(const unsigned char *fileData, unsigned int fileSize) { Wave wave = { 0 }; - - // Loading MP3 from memory to avoid FILE accesses - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - - // Decode an entire MP3 file in one go - drmp3_uint64 totalFrameCount = 0; drmp3_config config = { 0 }; - wave.data = drmp3_open_memory_and_read_f32(fileData, fileSize, &config, &totalFrameCount); - if (wave.data == NULL) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to load MP3 data", fileName); - else + // Decode the entire MP3 file in one go + unsigned long long int totalFrameCount = 0; + wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, fileSize, &config, &totalFrameCount, NULL); + + if (wave.data != NULL) { - wave.channels = config.outputChannels; - wave.sampleRate = config.outputSampleRate; + wave.channels = config.channels; + wave.sampleRate = config.sampleRate; wave.sampleCount = (int)totalFrameCount*wave.channels; wave.sampleSize = 32; // NOTE: Only support up to 2 channels (mono, stereo) - if (wave.channels > 2) TRACELOG(LOG_WARNING, "WAVE: [%s] MP3 channels number (%i) not supported", fileName, wave.channels); + // TODO: Really? + if (wave.channels > 2) TRACELOG(LOG_WARNING, "WAVE: MP3 channels number (%i) not supported", wave.channels); - TRACELOG(LOG_INFO, "WAVE: [%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); + TRACELOG(LOG_INFO, "WAVE: MP3 file loaded successfully (%i Hz, %i bit, %s)", wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo"); } - - RL_FREE(fileData); + else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); return wave; } @@ -2068,6 +2331,48 @@ static bool IsFileExtension(const char *fileName, const char *ext) return result; } +// Get pointer to extension for a filename string (includes the dot: .png) +static const char *GetFileExtension(const char *fileName) +{ + const char *dot = strrchr(fileName, '.'); + + if (!dot || dot == fileName) return NULL; + + return dot; +} + +// Check if two text string are equal +// REQUIRES: strcmp() +static bool TextIsEqual(const char *text1, const char *text2) +{ + bool result = false; + + if (strcmp(text1, text2) == 0) result = true; + + return result; +} + +// Get lower case version of provided string +// REQUIRES: tolower() +static const char *TextToLower(const char *text) +{ + #define MAX_TEXT_BUFFER_LENGTH 1024 + + static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; + + for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++) + { + if (text[i] != '\0') + { + buffer[i] = (char)tolower(text[i]); + //if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32; + } + else { buffer[i] = '\0'; break; } + } + + return buffer; +} + // Load data from file into a buffer static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) { @@ -2109,7 +2414,7 @@ static unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead } // Save data to file from buffer -static void SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +static bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) { if (fileName != NULL) { @@ -2131,7 +2436,7 @@ static void SaveFileData(const char *fileName, void *data, unsigned int bytesToW } // Save text data to file (write), string must be '\0' terminated -static void SaveFileText(const char *fileName, char *text) +static bool SaveFileText(const char *fileName, char *text) { if (fileName != NULL) { diff --git a/raylib/raudio.h b/raylib/raudio.h index 8056a37..a38c695 100644 --- a/raylib/raudio.h +++ b/raylib/raudio.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raudio - A simple and easy-to-use audio library based on miniaudio +* raudio v1.0 - A simple and easy-to-use audio library based on miniaudio * * FEATURES: * - Manage audio device (init/close) @@ -31,7 +31,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2021 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. @@ -138,13 +138,14 @@ void SetMasterVolume(float volume); // Set master vo // Wave/Sound loading/unloading functions Wave LoadWave(const char *fileName); // Load wave data from file +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" Sound LoadSound(const char *fileName); // Load sound from file Sound LoadSoundFromWave(Wave wave); // Load sound from wave data void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data void UnloadWave(Wave wave); // Unload wave data void UnloadSound(Sound sound); // Unload sound -void ExportWave(Wave wave, const char *fileName); // Export wave data to file -void ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h) +bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success // Wave/Sound management functions void PlaySound(Sound sound); // Play a sound @@ -160,17 +161,19 @@ void SetSoundPitch(Sound sound, float pitch); // Set pitch for void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format Wave WaveCopy(Wave wave); // Copy a wave to a new wave void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range -float *GetWaveData(Wave wave); // Get samples data from wave as a floats array +float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() // Music management functions Music LoadMusicStream(const char *fileName); // Load music stream from file +Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load music stream from data void UnloadMusicStream(Music music); // Unload music stream void PlayMusicStream(Music music); // Start music playing +bool IsMusicStreamPlaying(Music music); // Check if music is playing void UpdateMusicStream(Music music); // Updates buffers for music streaming void StopMusicStream(Music music); // Stop music playing void PauseMusicStream(Music music); // Pause music playing void ResumeMusicStream(Music music); // Resume playing paused music -bool IsMusicPlaying(Music music); // Check if music is playing void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) float GetMusicTimeLength(Music music); // Get music time length (in seconds) diff --git a/raylib/raylib.h b/raylib/raylib.h index 74c5659..0394315 100644 --- a/raylib/raylib.h +++ b/raylib/raylib.h @@ -4,7 +4,8 @@ * * FEATURES: * - NO external dependencies, all required libraries included with raylib -* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, MacOS, UWP, Android, Raspberry Pi, HTML5. +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, UWP, Android, Raspberry Pi, HTML5. * - Written in plain C code (C99) in PascalCase/camelCase notation * - Hardware accelerated with OpenGL (1.1, 2.1, 3.3 or ES2 - choose at compile) * - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] @@ -12,7 +13,7 @@ * - 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 -* - Skeletal Animation support (CPU bones-based animation) +* - Animated 3D models supported (skeletal bones animation) (IQM, glTF) * - Shaders support, including Model shaders and Postprocessing shaders * - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] * - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, XM, MOD) @@ -20,17 +21,20 @@ * - Bindings to multiple programming languages available! * * NOTES: -* One custom font is loaded by default when InitWindow() [core] -* 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 +* One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* One default Texture2D is loaded on rlglInit() [rlgl] (OpenGL 3.3 or ES2) +* One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) * * DEPENDENCIES (included): -* [core] rglfw (github.com/glfw/glfw) for window/context management and input (only PLATFORM_DESKTOP) -* [rlgl] glad (github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (only PLATFORM_DESKTOP) -* [raudio] miniaudio (github.com/dr-soft/miniaudio) for audio device/context management +* [core] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP) +* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP) +* [raudio] miniaudio (David Reid - github.com/dr-soft/miniaudio) for audio device/context management * * OPTIONAL DEPENDENCIES (included): -* [core] rgif (Charlie Tangora, Ramon Santamaria) for GIF recording +* [core] msf_gif (Miles Fogle) for GIF recording +* [core] sinfl (Micha Mettke) for DEFLATE decompression algorythm +* [core] sdefl (Micha Mettke) for DEFLATE compression algorythm * [textures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) * [textures] stb_image_write (Sean Barret) for image writting (BMP, TGA, PNG, JPG) * [textures] stb_image_resize (Sean Barret) for image resizing algorithms @@ -40,9 +44,10 @@ * [models] par_shapes (Philip Rideout) for parametric 3d shapes generation * [models] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) * [models] cgltf (Johannes Kuhlmann) for models loading (glTF) -* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] dr_wav (David Reid) for WAV audio file loading * [raudio] dr_flac (David Reid) for FLAC audio file loading * [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading * [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading * [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading * @@ -52,7 +57,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-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -76,6 +81,8 @@ #include // Required for: va_list - Only used by TraceLogCallback +#define RAYLIB_VERSION "3.8-dev" + #if defined(_WIN32) // Microsoft attibutes to tell compiler that symbols are imported/exported from a .dll #if defined(BUILD_LIBTYPE_SHARED) @@ -113,7 +120,7 @@ #define RL_FREE(ptr) free(ptr) #endif -// NOTE: MSC C++ compiler does not support compound literals (C99 feature) +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) // Plain structures in C++ (without constructors) can be initialized from { } initializers. #if defined(__cplusplus) #define CLITERAL(type) type @@ -151,13 +158,16 @@ #define MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta #define RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) -// Temporal hack to avoid breaking old codebases using -// deprecated raylib implementation of these functions -#define FormatText TextFormat -#define SubText TextSubtext -#define ShowWindow UnhideWindow -#define LoadText LoadFileText -//#define Fade(c, a) ColorAlpha(c, a) +// Temporal hacks to avoid breaking old codebases using +// deprecated raylib implementation or definitions +#define FormatText TextFormat +#define LoadText LoadFileText +#define GetExtension GetFileExtension +#define GetImageData LoadImageColors +#define FILTER_POINT TEXTURE_FILTER_POINT +#define FILTER_BILINEAR TEXTURE_FILTER_BILINEAR +#define MAP_DIFFUSE MATERIAL_MAP_DIFFUSE +#define UNCOMPRESSED_R8G8B8A8 PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 //---------------------------------------------------------------------------------- // Structures Definition @@ -227,41 +237,40 @@ typedef struct Image { int format; // Data format (PixelFormat type) } Image; -// Texture2D type +// Texture type // NOTE: Data stored in GPU memory -typedef struct Texture2D { +typedef struct Texture { unsigned int id; // OpenGL texture id int width; // Texture base width int height; // Texture base height int mipmaps; // Mipmap levels, 1 by default int format; // Data format (PixelFormat type) -} Texture2D; +} Texture; -// Texture type, same as Texture2D -typedef Texture2D Texture; +// Texture2D type, same as Texture +typedef Texture Texture2D; -// TextureCubemap type, actually, same as Texture2D -typedef Texture2D TextureCubemap; +// TextureCubemap type, actually, same as Texture +typedef Texture TextureCubemap; -// RenderTexture2D type, for texture rendering -typedef struct RenderTexture2D { - unsigned int id; // OpenGL Framebuffer Object (FBO) id - Texture2D texture; // Color buffer attachment texture - Texture2D depth; // Depth buffer attachment texture - bool depthTexture; // Track if depth attachment is a texture or renderbuffer -} RenderTexture2D; +// RenderTexture type, for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; -// RenderTexture type, same as RenderTexture2D -typedef RenderTexture2D RenderTexture; +// RenderTexture2D type, same as RenderTexture +typedef RenderTexture RenderTexture2D; // N-Patch layout info typedef struct NPatchInfo { - Rectangle sourceRec; // Region in the texture - int left; // left border offset - int top; // top border offset - int right; // right border offset - int bottom; // bottom border offset - int type; // layout of the n-patch: 3x3, 1x3 or 3x1 + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 } NPatchInfo; // Font character info @@ -277,6 +286,7 @@ typedef struct CharInfo { typedef struct Font { int baseSize; // Base size (default chars height) int charsCount; // Number of characters + int charsPadding; // Padding around the chars Texture2D texture; // Characters texture atlas Rectangle *recs; // Characters rectangles in texture CharInfo *chars; // Characters info data @@ -290,7 +300,7 @@ typedef struct Camera3D { Vector3 target; // Camera target it looks-at Vector3 up; // Camera up vector (rotation over its axis) float fovy; // Camera field-of-view apperture in Y (degrees) in perspective, used as near plane width in orthographic - int type; // Camera type, defines projection type: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC } Camera3D; typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D @@ -346,7 +356,7 @@ typedef struct MaterialMap { typedef struct Material { Shader shader; // Material shader MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) - float *params; // Material generic parameters (if required) + float params[4]; // Material generic parameters (if required) } Material; // Transformation properties @@ -367,9 +377,8 @@ typedef struct Model { Matrix transform; // Local transform matrix int meshCount; // Number of meshes - Mesh *meshes; // Meshes array - int materialCount; // Number of materials + Mesh *meshes; // Meshes array Material *materials; // Materials array int *meshMaterial; // Mesh material number @@ -382,9 +391,8 @@ typedef struct Model { // Model animation typedef struct ModelAnimation { int boneCount; // Number of bones - BoneInfo *bones; // Bones information (skeleton) - int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) Transform **framePoses; // Poses array by frame } ModelAnimation; @@ -410,7 +418,7 @@ typedef struct BoundingBox { // Wave type, defines audio wave data typedef struct Wave { - unsigned int sampleCount; // Total number of samples + unsigned int sampleCount; // Total number of samples (considering channels!) unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo) @@ -422,63 +430,80 @@ typedef struct rAudioBuffer rAudioBuffer; // Audio stream type // NOTE: Useful to create custom audio streams not bound to a specific file typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + unsigned int sampleRate; // Frequency (samples per second) unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) unsigned int channels; // Number of channels (1-mono, 2-stereo) - - rAudioBuffer *buffer; // Pointer to internal data used by the audio system } AudioStream; // Sound source type typedef struct Sound { - unsigned int sampleCount; // Total number of samples AudioStream stream; // Audio stream + unsigned int sampleCount; // Total number of samples } Sound; // Music stream type (audio file streaming from memory) // NOTE: Anything longer than ~10 seconds should be streamed typedef struct Music { + AudioStream stream; // Audio stream + unsigned int sampleCount; // Total number of samples + bool looping; // Music looping enable + int ctxType; // Type of music context (audio filetype) void *ctxData; // Audio context data, depends on type - - bool looping; // Music looping enable - unsigned int sampleCount; // Total number of samples - - AudioStream stream; // Audio stream } Music; // Head-Mounted-Display device parameters typedef struct VrDeviceInfo { - int hResolution; // HMD horizontal resolution in pixels - int vResolution; // HMD vertical resolution in pixels - float hScreenSize; // HMD horizontal size in meters - float vScreenSize; // HMD vertical size in meters - float vScreenCenter; // HMD screen center in meters - float eyeToScreenDistance; // HMD distance between eye and display in meters - float lensSeparationDistance; // HMD lens separation distance in meters - float interpupillaryDistance; // HMD IPD (distance between pupils) in meters - float lensDistortionValues[4]; // HMD lens distortion constant parameters - float chromaAbCorrection[4]; // HMD chromatic aberration correction parameters + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float vScreenCenter; // Screen center in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters } VrDeviceInfo; +// VR Stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + //---------------------------------------------------------------------------------- // Enumerators Definition //---------------------------------------------------------------------------------- -// System config flags -// NOTE: Used for bit masks +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 typedef enum { - FLAG_RESERVED = 1, // Reserved - FLAG_FULLSCREEN_MODE = 2, // Set to run program in fullscreen - FLAG_WINDOW_RESIZABLE = 4, // Set to allow resizable window - FLAG_WINDOW_UNDECORATED = 8, // Set to disable window decoration (frame and buttons) - FLAG_WINDOW_TRANSPARENT = 16, // Set to allow transparent window - FLAG_WINDOW_HIDDEN = 128, // Set to create the window initially hidden - FLAG_WINDOW_ALWAYS_RUN = 256, // Set to allow windows running while minimized - FLAG_MSAA_4X_HINT = 32, // Set to try enabling MSAA 4X - FLAG_VSYNC_HINT = 64 // Set to try enabling V-Sync on GPU -} ConfigFlag; + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; -// Trace log type +// Trace log level typedef enum { LOG_ALL = 0, // Display all logs LOG_TRACE, @@ -488,10 +513,13 @@ typedef enum { LOG_ERROR, LOG_FATAL, LOG_NONE // Disable logging -} TraceLogType; +} TraceLogLevel; -// Keyboard keys +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts typedef enum { + KEY_NULL = 0, // Alphanumeric keys KEY_APOSTROPHE = 39, KEY_COMMA = 44, @@ -601,33 +629,47 @@ typedef enum { KEY_KP_SUBTRACT = 333, KEY_KP_ADD = 334, KEY_KP_ENTER = 335, - KEY_KP_EQUAL = 336 -} KeyboardKey; - -// Android buttons -typedef enum { + KEY_KP_EQUAL = 336, + // Android key buttons KEY_BACK = 4, KEY_MENU = 82, KEY_VOLUME_UP = 24, KEY_VOLUME_DOWN = 25 -} AndroidButton; +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE // Mouse buttons typedef enum { - MOUSE_LEFT_BUTTON = 0, - MOUSE_RIGHT_BUTTON = 1, - MOUSE_MIDDLE_BUTTON = 2 + MOUSE_BUTTON_LEFT = 0, + MOUSE_BUTTON_RIGHT = 1, + MOUSE_BUTTON_MIDDLE = 2, + MOUSE_BUTTON_SIDE = 3, + MOUSE_BUTTON_EXTRA = 4, + MOUSE_BUTTON_FORWARD = 5, + MOUSE_BUTTON_BACK = 6, + MOUSE_BUTTON_MAX = 7 } MouseButton; -// Gamepad number +// Mouse cursor typedef enum { - GAMEPAD_PLAYER1 = 0, - GAMEPAD_PLAYER2 = 1, - GAMEPAD_PLAYER3 = 2, - GAMEPAD_PLAYER4 = 3 -} GamepadNumber; + MOUSE_CURSOR_DEFAULT = 0, + MOUSE_CURSOR_ARROW = 1, + MOUSE_CURSOR_IBEAM = 2, + MOUSE_CURSOR_CROSSHAIR = 3, + MOUSE_CURSOR_POINTING_HAND = 4, + MOUSE_CURSOR_RESIZE_EW = 5, // The horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // The vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // The top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omni-directional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; -// Gamepad Buttons +// Gamepad buttons typedef enum { // This is here just for error checking GAMEPAD_BUTTON_UNKNOWN = 0, @@ -654,15 +696,16 @@ typedef enum { GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // These are buttons in the center of the gamepad - GAMEPAD_BUTTON_MIDDLE_LEFT, //PS3 Select - GAMEPAD_BUTTON_MIDDLE, //PS Button/XBOX Button - GAMEPAD_BUTTON_MIDDLE_RIGHT, //PS3 Start + GAMEPAD_BUTTON_MIDDLE_LEFT, // PS3 Select + GAMEPAD_BUTTON_MIDDLE, // PS Button/XBOX Button + GAMEPAD_BUTTON_MIDDLE_RIGHT, // PS3 Start // These are the joystick press in buttons GAMEPAD_BUTTON_LEFT_THUMB, GAMEPAD_BUTTON_RIGHT_THUMB } GamepadButton; +// Gamepad axis typedef enum { // Left stick GAMEPAD_AXIS_LEFT_X = 0, @@ -677,124 +720,125 @@ typedef enum { GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // [1..-1] (pressure-level) } GamepadAxis; -// Shader location point type +// Material map index 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_OCCLUSION, - LOC_MAP_EMISSION, - LOC_MAP_HEIGHT, - LOC_MAP_CUBEMAP, - LOC_MAP_IRRADIANCE, - LOC_MAP_PREFILTER, - LOC_MAP_BRDF + MATERIAL_MAP_ALBEDO = 0, // MATERIAL_MAP_DIFFUSE + MATERIAL_MAP_METALNESS = 1, // MATERIAL_MAP_SPECULAR + MATERIAL_MAP_NORMAL = 2, + MATERIAL_MAP_ROUGHNESS = 3, + MATERIAL_MAP_OCCLUSION, + MATERIAL_MAP_EMISSION, + MATERIAL_MAP_HEIGHT, + MATERIAL_MAP_BRDG, + MATERIAL_MAP_CUBEMAP, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MATERIAL_MAP_IRRADIANCE, // NOTE: Uses GL_TEXTURE_CUBE_MAP + MATERIAL_MAP_PREFILTER // NOTE: Uses GL_TEXTURE_CUBE_MAP +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, + SHADER_LOC_VERTEX_TEXCOORD01, + SHADER_LOC_VERTEX_TEXCOORD02, + SHADER_LOC_VERTEX_NORMAL, + SHADER_LOC_VERTEX_TANGENT, + SHADER_LOC_VERTEX_COLOR, + SHADER_LOC_MATRIX_MVP, + SHADER_LOC_MATRIX_VIEW, + SHADER_LOC_MATRIX_PROJECTION, + SHADER_LOC_MATRIX_MODEL, + SHADER_LOC_MATRIX_NORMAL, + SHADER_LOC_VECTOR_VIEW, + SHADER_LOC_COLOR_DIFFUSE, + SHADER_LOC_COLOR_SPECULAR, + SHADER_LOC_COLOR_AMBIENT, + SHADER_LOC_MAP_ALBEDO, // SHADER_LOC_MAP_DIFFUSE + SHADER_LOC_MAP_METALNESS, // SHADER_LOC_MAP_SPECULAR + SHADER_LOC_MAP_NORMAL, + SHADER_LOC_MAP_ROUGHNESS, + SHADER_LOC_MAP_OCCLUSION, + SHADER_LOC_MAP_EMISSION, + SHADER_LOC_MAP_HEIGHT, + SHADER_LOC_MAP_CUBEMAP, + SHADER_LOC_MAP_IRRADIANCE, + SHADER_LOC_MAP_PREFILTER, + SHADER_LOC_MAP_BRDF } ShaderLocationIndex; -#define LOC_MAP_DIFFUSE LOC_MAP_ALBEDO -#define LOC_MAP_SPECULAR LOC_MAP_METALNESS +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS -// Shader uniform data types +// Shader uniform data type typedef enum { - UNIFORM_FLOAT = 0, - UNIFORM_VEC2, - UNIFORM_VEC3, - UNIFORM_VEC4, - UNIFORM_INT, - UNIFORM_IVEC2, - UNIFORM_IVEC3, - UNIFORM_IVEC4, - UNIFORM_SAMPLER2D + SHADER_UNIFORM_FLOAT = 0, + SHADER_UNIFORM_VEC2, + SHADER_UNIFORM_VEC3, + SHADER_UNIFORM_VEC4, + SHADER_UNIFORM_INT, + SHADER_UNIFORM_IVEC2, + SHADER_UNIFORM_IVEC3, + SHADER_UNIFORM_IVEC4, + SHADER_UNIFORM_SAMPLER2D } ShaderUniformDataType; -// 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 -} MaterialMapType; - -#define MAP_DIFFUSE MAP_ALBEDO -#define MAP_SPECULAR MAP_METALNESS - // Pixel formats // NOTE: Support depends on OpenGL version and platform typedef enum { - UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - 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_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 - COMPRESSED_DXT5_RGBA, // 8 bpp - COMPRESSED_ETC1_RGB, // 4 bpp - COMPRESSED_ETC2_RGB, // 4 bpp - COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - COMPRESSED_PVRT_RGB, // 4 bpp - COMPRESSED_PVRT_RGBA, // 4 bpp - COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - COMPRESSED_ASTC_8x8_RGBA // 2 bpp + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp } PixelFormat; // Texture parameters: filter mode // NOTE 1: Filtering considers mipmaps if available in the texture // NOTE 2: Filter is accordingly set for minification and magnification typedef enum { - FILTER_POINT = 0, // No filter, just pixel aproximation - FILTER_BILINEAR, // Linear filtering - FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} TextureFilterMode; - -// Cubemap layout type -typedef enum { - CUBEMAP_AUTO_DETECT = 0, // Automatically detect layout type - CUBEMAP_LINE_VERTICAL, // Layout is defined by a vertical line with faces - CUBEMAP_LINE_HORIZONTAL, // Layout is defined by an horizontal line with faces - CUBEMAP_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces - CUBEMAP_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces - CUBEMAP_PANORAMA // Layout is defined by a panorama image (equirectangular map) -} CubemapLayoutType; + TEXTURE_FILTER_POINT = 0, // No filter, just pixel aproximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; // Texture parameters: wrap mode typedef enum { - WRAP_REPEAT = 0, // Repeats texture in tiled mode - WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode - WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode - WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode -} TextureWrapMode; + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by an horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces + CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirectangular map) +} CubemapLayout; // Font type, defines generation method typedef enum { @@ -810,10 +854,10 @@ typedef enum { BLEND_MULTIPLIED, // Blend textures multiplying colors BLEND_ADD_COLORS, // Blend textures adding colors (alternative) BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - BLEND_CUSTOM // Belnd textures using custom src/dst factors (use SetBlendModeCustom()) + BLEND_CUSTOM // Belnd textures using custom src/dst factors (use rlSetBlendMode()) } BlendMode; -// Gestures type +// Gestures // NOTE: It could be used as flags to enable only some gestures typedef enum { GESTURE_NONE = 0, @@ -827,7 +871,7 @@ typedef enum { GESTURE_SWIPE_DOWN = 128, GESTURE_PINCH_IN = 256, GESTURE_PINCH_OUT = 512 -} GestureType; +} Gestures; // Camera system modes typedef enum { @@ -838,21 +882,27 @@ typedef enum { CAMERA_THIRD_PERSON } CameraMode; -// Camera projection modes +// Camera projection typedef enum { CAMERA_PERSPECTIVE = 0, CAMERA_ORTHOGRAPHIC -} CameraType; +} CameraProjection; -// Type of n-patch +// N-patch layout typedef enum { - NPT_9PATCH = 0, // Npatch defined by 3x3 tiles - NPT_3PATCH_VERTICAL, // Npatch defined by 1x3 tiles - NPT_3PATCH_HORIZONTAL // Npatch defined by 3x1 tiles -} NPatchType; + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: This callbacks are intended for advance users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, unsigned int *bytesRead); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, unsigned int bytesToWrite); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data -// Callbacks to be implemented by users -typedef void (*TraceLogCallback)(int logType, const char *text, va_list args); #if defined(__cplusplus) extern "C" { // Prevents name mangling of functions @@ -872,19 +922,19 @@ RLAPI void InitWindow(int width, int height, const char *title); // Initialize RLAPI bool WindowShouldClose(void); // Check if KEY_ESCAPE pressed or Close icon pressed RLAPI void CloseWindow(void); // Close window and unload OpenGL context RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully -RLAPI bool IsWindowMinimized(void); // Check if window has been minimized -RLAPI bool IsWindowMaximized(void); // Check if window has been maximized (only PLATFORM_DESKTOP) -RLAPI bool IsWindowFocused(void); // Check if window has been focused -RLAPI bool IsWindowResized(void); // Check if window has been resized -RLAPI bool IsWindowHidden(void); // Check if window is currently hidden RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen -RLAPI void ToggleFullscreen(void); // Toggle fullscreen mode (only PLATFORM_DESKTOP) -RLAPI void UnhideWindow(void); // Show the window -RLAPI void HideWindow(void); // Hide the window -RLAPI void DecorateWindow(void); // Decorate the window (only PLATFORM_DESKTOP) -RLAPI void UndecorateWindow(void); // Undecorate the window (only PLATFORM_DESKTOP) -RLAPI void MaximizeWindow(void); // Maximize the window, if resizable (only PLATFORM_DESKTOP) -RLAPI void RestoreWindow(void); // Restore the window, if resizable (only PLATFORM_DESKTOP) +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP) +RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP) +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) RLAPI void SetWindowIcon(Image image); // Set icon for window (only PLATFORM_DESKTOP) RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP) RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP) @@ -895,16 +945,18 @@ RLAPI void *GetWindowHandle(void); // Get native RLAPI int GetScreenWidth(void); // Get current screen width RLAPI int GetScreenHeight(void); // Get current screen height RLAPI int GetMonitorCount(void); // Get number of connected monitors -RLAPI int GetMonitorWidth(int monitor); // Get primary monitor width -RLAPI int GetMonitorHeight(int monitor); // Get primary monitor height -RLAPI int GetMonitorPhysicalWidth(int monitor); // Get primary monitor physical width in millimetres -RLAPI int GetMonitorPhysicalHeight(int monitor); // Get primary monitor physical height in millimetres -RLAPI int GetMonitorRefreshRate(int monitor); // Get primary monitor refresh rate +RLAPI int GetCurrentMonitor(void); // Get current connected monitor +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (max available by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (max available by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the primary monitor -RLAPI const char *GetClipboardText(void); // Get clipboard text content RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content // Cursor-related functions RLAPI void ShowCursor(void); // Shows cursor @@ -912,20 +964,42 @@ RLAPI void HideCursor(void); // Hides curso 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) -RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the current screen. +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen // Drawing-related functions RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) -RLAPI void BeginMode2D(Camera2D camera); // Initialize 2D mode with custom camera (2D) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) RLAPI void EndMode2D(void); // Ends 2D mode with custom camera -RLAPI void BeginMode3D(Camera3D camera); // Initializes 3D mode with custom camera (3D) +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode -RLAPI void BeginTextureMode(RenderTexture2D target); // Initializes render texture for drawing +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) // Screen-space-related functions RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Returns a ray trace from mouse position @@ -939,27 +1013,39 @@ RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Returns // Timing-related functions 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 float GetFrameTime(void); // Returns time in seconds for last frame drawn (delta time) RLAPI double GetTime(void); // Returns elapsed time in seconds since InitWindow() // Misc. functions -RLAPI void SetConfigFlags(unsigned int flags); // Setup window configuration flags (view FLAGS) -RLAPI void SetTraceLogLevel(int logType); // Set the current threshold (minimum) log level -RLAPI void SetTraceLogExit(int logType); // Set the exit threshold (minimum) log level -RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set a trace log callback to enable custom logging -RLAPI void TraceLog(int logType, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR) -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) +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) + +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advance users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver // Files management functions RLAPI unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead); // Load file data as byte array (read) -RLAPI void SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite); // Save data to file from byte array (write), returns true on success RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string -RLAPI void SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success RLAPI bool FileExists(const char *fileName); // Check if file exists -RLAPI bool IsFileExtension(const char *fileName, const char *ext);// Check file extension RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists -RLAPI const char *GetExtension(const char *fileName); // Get pointer to extension for a filename string +RLAPI bool IsFileExtension(const char *fileName, const char *ext);// Check file extension (including point: .png, .wav) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: ".png") RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) @@ -967,17 +1053,17 @@ RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previou RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) RLAPI char **GetDirectoryFiles(const char *dirPath, int *count); // Get filenames in a directory path (memory should be freed) RLAPI void ClearDirectoryFiles(void); // Clear directory files paths buffers (free memory) -RLAPI bool ChangeDirectory(const char *dir); // Change working directory, returns true if success +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window RLAPI char **GetDroppedFiles(int *count); // Get dropped files names (memory should be freed) RLAPI void ClearDroppedFiles(void); // Clear dropped files paths buffer (free memory) RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) -RLAPI unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength); // Compress data (DEFLATE algorythm) -RLAPI unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength); // Decompress data (DEFLATE algorythm) +RLAPI unsigned char *CompressData(unsigned char *data, int dataLength, int *compDataLength); // Compress data (DEFLATE algorithm) +RLAPI unsigned char *DecompressData(unsigned char *compData, int compDataLength, int *dataLength); // Decompress data (DEFLATE algorithm) // Persistent storage management -RLAPI void SaveStorageValue(unsigned int position, int value); // Save integer value to storage file (to defined position) +RLAPI bool SaveStorageValue(unsigned int position, int value); // Save integer value to storage file (to defined position), returns true on success RLAPI int LoadStorageValue(unsigned int position); // Load integer value from storage file (from defined position) RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) @@ -992,7 +1078,8 @@ RLAPI bool IsKeyDown(int key); // Detect if a key RLAPI bool IsKeyReleased(int key); // Detect if a key has been released once RLAPI bool IsKeyUp(int key); // Detect if a key is NOT being pressed RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) -RLAPI int GetKeyPressed(void); // Get key pressed, call it multiple times for chars queued +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued // Input-related functions: gamepads RLAPI bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available @@ -1005,6 +1092,7 @@ RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gam RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed RLAPI int GetGamepadAxisCount(int gamepad); // Return gamepad axis count for a gamepad RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Return axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) // Input-related functions: mouse RLAPI bool IsMouseButtonPressed(int button); // Detect if a mouse button has been pressed once @@ -1017,7 +1105,8 @@ RLAPI Vector2 GetMousePosition(void); // Returns mouse p RLAPI void SetMousePosition(int x, int y); // Set mouse position XY RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling -RLAPI int GetMouseWheelMove(void); // Returns mouse wheel movement Y +RLAPI float GetMouseWheelMove(void); // Returns mouse wheel movement Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor // Input-related functions: touch RLAPI int GetTouchX(void); // Returns touch position X for touch point 0 (relative to screen size) @@ -1027,30 +1116,34 @@ RLAPI Vector2 GetTouchPosition(int index); // Returns touch p //------------------------------------------------------------------------------------ // Gestures and Touch Handling Functions (Module: gestures) //------------------------------------------------------------------------------------ -RLAPI void SetGesturesEnabled(unsigned int gestureFlags); // Enable a set of gestures using flags -RLAPI bool IsGestureDetected(int gesture); // Check if a gesture have been detected -RLAPI int GetGestureDetected(void); // Get latest detected gesture -RLAPI int GetTouchPointsCount(void); // Get touch points count -RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds -RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector -RLAPI float GetGestureDragAngle(void); // Get gesture drag angle -RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta -RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI int GetTouchPointsCount(void); // Get touch points count +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle //------------------------------------------------------------------------------------ // Camera System Functions (Module: camera) //------------------------------------------------------------------------------------ -RLAPI void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) -RLAPI void UpdateCamera(Camera *camera); // Update camera position for selected mode +RLAPI void SetCameraMode(Camera camera, int mode); // Set camera mode (multiple camera modes available) +RLAPI void UpdateCamera(Camera *camera); // Update camera position for selected mode -RLAPI void SetCameraPanControl(int panKey); // Set camera pan key to combine with mouse movement (free camera) -RLAPI void SetCameraAltControl(int altKey); // Set camera alt key to combine with mouse movement (free camera) -RLAPI void SetCameraSmoothZoomControl(int szKey); // Set camera smooth zoom key to combine with mouse (free camera) -RLAPI void SetCameraMoveControls(int frontKey, int backKey, int rightKey, int leftKey, int upKey, int downKey); // Set camera move controls (1st person and 3rd person cameras) +RLAPI void SetCameraPanControl(int keyPan); // Set camera pan key to combine with mouse movement (free camera) +RLAPI void SetCameraAltControl(int keyAlt); // Set camera alt key to combine with mouse movement (free camera) +RLAPI void SetCameraSmoothZoomControl(int keySmoothZoom); // Set camera smooth zoom key to combine with mouse (free camera) +RLAPI void SetCameraMoveControls(int keyFront, int keyBack, int keyRight, int keyLeft, int keyUp, int keyDown); // Set camera move controls (1st person and 3rd person cameras) //------------------------------------------------------------------------------------ // Basic Shapes Drawing Functions (Module: shapes) //------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing // Basic shapes drawing functions RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel @@ -1059,17 +1152,18 @@ RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Colo RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (Vector version) RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line defining thickness RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line using cubic-bezier curves in-out -RLAPI void DrawLineStrip(Vector2 *points, int numPoints, Color color); // Draw lines sequence +RLAPI void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color); //Draw line using quadratic bezier curves with a control point +RLAPI void DrawLineStrip(Vector2 *points, int pointsCount, Color color); // Draw lines sequence RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle -RLAPI void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color); // Draw a piece of a circle -RLAPI void DrawCircleSectorLines(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle 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 DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline -RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color); // Draw ring -RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring 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 @@ -1078,24 +1172,26 @@ 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 DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges -RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) -RLAPI void DrawTriangleFan(Vector2 *points, int numPoints, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleFan(Vector2 *points, int pointsCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) RLAPI void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color); // Draw a triangle strip defined by points RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters // Basic shapes collision detection functions RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle -RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision //------------------------------------------------------------------------------------ // Texture Loading and Drawing Functions (Module: textures) @@ -1106,9 +1202,10 @@ RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Ve RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. ".png" RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) -RLAPI void ExportImage(Image image, const char *fileName); // Export image data to file -RLAPI void ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success // Image generation functions RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color @@ -1135,7 +1232,7 @@ RLAPI void ImageAlphaPremultiply(Image *image); RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color -RLAPI void ImageMipmaps(Image *image); // Generate all mipmap levels for a provided image +RLAPI void ImageMipmaps(Image *image); // Compute 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 void ImageFlipVertical(Image *image); // Flip image vertically RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally @@ -1147,10 +1244,10 @@ RLAPI void ImageColorGrayscale(Image *image); RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color - -RLAPI Color *GetImageData(Image image); // Get pixel data from image as a Color struct array -RLAPI Color *GetImagePalette(Image image, int maxPaletteSize, int *extractCount); // Get color palette from image to maximum size (memory should be freed) -RLAPI Vector4 *GetImageDataNormalized(Image image); // Get pixel data from image as Vector4 array (float normalized) +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle // Image drawing functions @@ -1164,7 +1261,7 @@ RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Col RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw circle within an image (Vector version) RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) -RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) @@ -1174,7 +1271,7 @@ RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 posi // NOTE: These functions require GPU access RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data -RLAPI TextureCubemap LoadTextureCubemap(Image image, int layoutType); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) @@ -1185,26 +1282,27 @@ RLAPI Image GetScreenData(void); // Texture configuration functions RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture -RLAPI void SetTextureFilter(Texture2D texture, int filterMode); // Set texture scaling filter mode -RLAPI void SetTextureWrap(Texture2D texture, int wrapMode); // Set texture wrapping mode +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode // Texture drawing functions RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters -RLAPI void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle RLAPI void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangle quad, Color tint); // Draw texture quad with tiling and offset parameters -RLAPI void DrawTextureTiled(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into destRec. -RLAPI void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters -RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destRec, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely +RLAPI void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint); // Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely +RLAPI void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint); // Draw a textured polygon // Color/pixel related functions RLAPI Color Fade(Color color, float alpha); // Returns color with alpha applied, alpha goes from 0.0f to 1.0f RLAPI int ColorToInt(Color color); // Returns hexadecimal value for a Color RLAPI Vector4 ColorNormalize(Color color); // Returns Color normalized as float [0..1] RLAPI Color ColorFromNormalized(Vector4 normalized); // Returns Color from normalized values [0..1] -RLAPI Vector3 ColorToHSV(Color color); // Returns HSV values for a Color -RLAPI Color ColorFromHSV(Vector3 hsv); // Returns a Color from HSV values +RLAPI Vector3 ColorToHSV(Color color); // Returns HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Returns a Color from HSV values, hue [0..360], saturation/value [0..1] RLAPI Color ColorAlpha(Color color, float alpha); // Returns color with alpha applied, alpha goes from 0.0f to 1.0f RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Returns src alpha-blended into dst color with tint RLAPI Color GetColor(int hexValue); // Get Color structure from hexadecimal value @@ -1221,18 +1319,19 @@ RLAPI Font GetFontDefault(void); RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCount); // Load font from file with extended parameters RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) -RLAPI CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(const CharInfo *chars, Rectangle **recs, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount); // Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +RLAPI CharInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const CharInfo *chars, Rectangle **recs, int charsCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(CharInfo *chars, int charsCount); // Unload font chars info data (RAM) RLAPI void UnloadFont(Font font); // Unload Font from GPU memory (VRAM) // Text drawing functions -RLAPI void DrawFPS(int posX, int posY); // Shows current FPS +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters RLAPI void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint); // Draw text using font inside rectangle limits -RLAPI void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, - int selectStart, int selectLength, Color selectTint, Color selectBackTint); // Draw text using font inside rectangle limits with support for text selection -RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float scale, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint); // Draw text using font inside rectangle limits with support for text selection +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) // Text misc. functions RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font @@ -1287,52 +1386,55 @@ RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBott RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) -RLAPI void DrawGizmo(Vector3 position); // Draw simple gizmo //------------------------------------------------------------------------------------ // Model 3d Loading and Drawing Functions (Module: models) //------------------------------------------------------------------------------------ // Model loading/unloading functions -RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) -RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) -RLAPI void UnloadModel(Model model); // Unload model from memory (RAM and/or VRAM) +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI void UnloadModelKeepMeshes(Model model); // Unload model (but not meshes) from memory (RAM and/or VRAM) // Mesh loading/unloading functions -RLAPI Mesh *LoadMeshes(const char *fileName, int *meshCount); // Load meshes from model file -RLAPI void ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file -RLAPI void UnloadMesh(Mesh mesh); // Unload mesh from memory (RAM and/or VRAM) +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success // Material loading/unloading functions -RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file -RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) -RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MAP_DIFFUSE, MAP_SPECULAR...) -RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh // Model animations loading/unloading functions -RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file -RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose -RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data -RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animsCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation* animations, unsigned int count); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match // Mesh generation functions -RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh -RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) -RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh -RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) -RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) -RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh -RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh -RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh -RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data -RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data // Mesh manipulation functions -RLAPI BoundingBox MeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits -RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents -RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals -RLAPI void MeshNormalsSmooth(Mesh *mesh); // Smooth (average) vertex normals +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void MeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI void MeshBinormals(Mesh *mesh); // Compute mesh binormals // Model drawing functions RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) @@ -1340,70 +1442,22 @@ RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, floa RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) -RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint); // Draw a billboard texture -RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint); // Draw a billboard texture defined by sourceRec +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float size, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation // Collision detection functions -RLAPI bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB); // Detect collision between two spheres +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Detect collision between two spheres RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Detect collision between two bounding boxes RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Detect collision between box and sphere RLAPI bool CheckCollisionRaySphere(Ray ray, Vector3 center, float radius); // Detect collision between ray and sphere RLAPI bool CheckCollisionRaySphereEx(Ray ray, Vector3 center, float radius, Vector3 *collisionPoint); // Detect collision between ray and sphere, returns collision point RLAPI bool CheckCollisionRayBox(Ray ray, BoundingBox box); // Detect collision between ray and box +RLAPI RayHitInfo GetCollisionRayMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh RLAPI RayHitInfo GetCollisionRayModel(Ray ray, Model model); // Get collision info between ray and model RLAPI RayHitInfo GetCollisionRayTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle RLAPI RayHitInfo GetCollisionRayGround(Ray ray, float groundHeight); // Get collision info between ray and ground plane (Y-normal plane) -//------------------------------------------------------------------------------------ -// Shaders System Functions (Module: rlgl) -// NOTE: This functions are useless when using OpenGL 1.1 -//------------------------------------------------------------------------------------ - -// Shader loading/unloading functions -RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -RLAPI Shader LoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations -RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) - -RLAPI Shader GetShaderDefault(void); // Get default shader -RLAPI Texture2D GetTextureDefault(void); // Get default texture -RLAPI Texture2D GetShapesTexture(void); // Get texture to draw shapes -RLAPI Rectangle GetShapesTextureRec(void); // Get texture rectangle to draw shapes -RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Define default texture used to draw shapes - -// Shader configuration functions -RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType); // Set shader uniform value -RLAPI void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count); // Set shader uniform value vector -RLAPI void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat); // Set shader uniform value (matrix 4x4) -RLAPI void SetShaderValueTexture(Shader shader, int uniformLoc, Texture2D texture); // Set shader uniform value for texture -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(void); // Get internal modelview matrix -RLAPI Matrix GetMatrixProjection(void); // Get internal projection matrix - -// Texture maps generation (PBR) -// NOTE: Required shaders should be provided -RLAPI Texture2D GenTextureCubemap(Shader shader, Texture2D map, int size); // Generate cubemap texture from 2D texture -RLAPI Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size); // Generate irradiance texture using cubemap data -RLAPI Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size); // Generate prefilter texture using cubemap data -RLAPI Texture2D GenTextureBRDF(Shader shader, int size); // Generate BRDF texture - -// Shading begin/end functions -RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing -RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) -RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied) -RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) - -// VR control functions -RLAPI void InitVrSimulator(void); // Init VR simulator for selected device parameters -RLAPI void CloseVrSimulator(void); // Close VR simulator for current device -RLAPI void UpdateVrTracking(Camera *camera); // Update VR tracking (position and orientation) and camera -RLAPI void SetVrConfiguration(VrDeviceInfo info, Shader distortion); // Set stereo rendering configuration parameters -RLAPI bool IsVrSimulatorReady(void); // Detect if VR simulator is ready -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) //------------------------------------------------------------------------------------ @@ -1416,13 +1470,14 @@ RLAPI void SetMasterVolume(float volume); // Set mas // Wave/Sound loading/unloading functions RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. ".wav" RLAPI Sound LoadSound(const char *fileName); // Load sound from file RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data RLAPI void UpdateSound(Sound sound, const void *data, int samplesCount);// Update sound buffer with new data RLAPI void UnloadWave(Wave wave); // Unload wave data RLAPI void UnloadSound(Sound sound); // Unload sound -RLAPI void ExportWave(Wave wave, const char *fileName); // Export wave data to file -RLAPI void ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h) +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success // Wave/Sound management functions RLAPI void PlaySound(Sound sound); // Play a sound @@ -1438,17 +1493,19 @@ RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pit RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave RLAPI void WaveCrop(Wave *wave, int initSample, int finalSample); // Crop a wave to defined samples range -RLAPI float *GetWaveData(Wave wave); // Get samples data from wave as a floats array +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a floats array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() // Music management functions RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, unsigned char* data, int dataSize); // Load music stream from data RLAPI void UnloadMusicStream(Music music); // Unload music stream RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming RLAPI void StopMusicStream(Music music); // Stop music playing RLAPI void PauseMusicStream(Music music); // Pause music playing RLAPI void ResumeMusicStream(Music music); // Resume playing paused music -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 float GetMusicTimeLength(Music music); // Get music time length (in seconds) @@ -1468,12 +1525,6 @@ RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set vol RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams -//------------------------------------------------------------------------------------ -// Network (Module: network) -//------------------------------------------------------------------------------------ - -// IN PROGRESS: Check rnet.h for reference - #if defined(__cplusplus) } #endif diff --git a/raylib/raymath.h b/raylib/raymath.h index 3e5e197..e396d5a 100644 --- a/raylib/raymath.h +++ b/raylib/raymath.h @@ -20,7 +20,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2015-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2015-2021 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. @@ -75,7 +75,7 @@ // Defines and Macros //---------------------------------------------------------------------------------- #ifndef PI - #define PI 3.14159265358979323846 + #define PI 3.14159265358979323846f #endif #ifndef DEG2RAD @@ -114,13 +114,16 @@ float z; } Vector3; - // Quaternion type - typedef struct Quaternion { + // Vector4 type + typedef struct Vector4 { float x; float y; float z; float w; - } Quaternion; + } Vector4; + + // Quaternion type + typedef Vector4 Quaternion; // Matrix type (OpenGL style 4x4 - right handed, column major) typedef struct Matrix { @@ -157,13 +160,13 @@ RMDEF float Lerp(float start, float end, float amount) // Normalize input value within input range RMDEF float Normalize(float value, float start, float end) { - return (value - start) / (end - start); + return (value - start)/(end - start); } // Remap input value within input range to output range -RMDEF float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +RMDEF float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) { - return (value - inputStart) / (inputEnd - inputStart) * (outputEnd - outputStart) + outputStart; + return (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; } //---------------------------------------------------------------------------------- @@ -294,11 +297,24 @@ RMDEF Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) return result; } +// Calculate reflected vector to normal +RMDEF Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = Vector2DotProduct(v, normal); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + // Rotate Vector by float in Degrees. RMDEF Vector2 Vector2Rotate(Vector2 v, float degs) { float rads = degs*DEG2RAD; - Vector2 result = {v.x * cosf(rads) - v.y * sinf(rads) , v.x * sinf(rads) + v.y * cosf(rads) }; + Vector2 result = {v.x*cosf(rads) - v.y*sinf(rads) , v.x*sinf(rads) + v.y*cosf(rads) }; return result; } @@ -309,14 +325,14 @@ RMDEF Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) float dx = target.x - v.x; float dy = target.y - v.y; float value = (dx*dx) + (dy*dy); - + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) result = target; - + float dist = sqrtf(value); - + result.x = v.x + dx/dist*maxDistance; result.y = v.y + dy/dist*maxDistance; - + return result; } @@ -840,14 +856,14 @@ RMDEF Matrix MatrixRotate(Vector3 axis, float angle) float x = axis.x, y = axis.y, z = axis.z; - float length = sqrtf(x*x + y*y + z*z); + float lengthSquared = x*x + y*y + z*z; - if ((length != 1.0f) && (length != 0.0f)) + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) { - length = 1.0f/length; - x *= length; - y *= length; - z *= length; + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; } float sinres = sinf(angle); @@ -938,30 +954,53 @@ RMDEF Matrix MatrixRotateXYZ(Vector3 ang) float cosx = cosf(-ang.x); float sinx = sinf(-ang.x); - result.m0 = cosz * cosy; - result.m4 = (cosz * siny * sinx) - (sinz * cosx); - result.m8 = (cosz * siny * cosx) + (sinz * sinx); + result.m0 = cosz*cosy; + result.m4 = (cosz*siny*sinx) - (sinz*cosx); + result.m8 = (cosz*siny*cosx) + (sinz*sinx); - result.m1 = sinz * cosy; - result.m5 = (sinz * siny * sinx) + (cosz * cosx); - result.m9 = (sinz * siny * cosx) - (cosz * sinx); + result.m1 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m9 = (sinz*siny*cosx) - (cosz*sinx); result.m2 = -siny; - result.m6 = cosy * sinx; - result.m10= cosy * cosx; + result.m6 = cosy*sinx; + result.m10= cosy*cosx; return result; } // Returns zyx-rotation matrix (angles in radians) -// TODO: This solution is suboptimal, it should be possible to create this matrix in one go -// instead of using a 3 matrix multiplication RMDEF Matrix MatrixRotateZYX(Vector3 ang) { - Matrix result = MatrixRotateZ(ang.z); - result = MatrixMultiply(result, MatrixRotateY(ang.y)); - result = MatrixMultiply(result, MatrixRotateX(ang.x)); - + Matrix result = { 0 }; + + float cz = cosf(ang.z); + float sz = sinf(ang.z); + float cy = cosf(ang.y); + float sy = sinf(ang.y); + float cx = cosf(ang.x); + float sx = sinf(ang.x); + + result.m0 = cz*cy; + result.m1 = cz*sy*sx - cx*sz; + result.m2 = sz*sx + cz*cx*sy; + result.m3 = 0; + + result.m4 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m6 = cx*sz*sy - cz*sx; + result.m7 = 0; + + result.m8 = -sy; + result.m9 = cy*sx; + result.m10 = cy*cx; + result.m11 = 0; + + result.m12 = 0; + result.m13 = 0; + result.m14 = 0; + result.m15 = 1; + return result; } @@ -1058,27 +1097,24 @@ RMDEF Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) Vector3 x = Vector3CrossProduct(up, z); x = Vector3Normalize(x); Vector3 y = Vector3CrossProduct(z, x); - y = Vector3Normalize(y); result.m0 = x.x; - result.m1 = x.y; - result.m2 = x.z; + result.m1 = y.x; + result.m2 = z.x; result.m3 = 0.0f; - result.m4 = y.x; + result.m4 = x.y; result.m5 = y.y; - result.m6 = y.z; + result.m6 = z.y; result.m7 = 0.0f; - result.m8 = z.x; - result.m9 = z.y; + result.m8 = x.z; + result.m9 = y.z; result.m10 = z.z; result.m11 = 0.0f; - result.m12 = eye.x; - result.m13 = eye.y; - result.m14 = eye.z; + result.m12 = -Vector3DotProduct(x, eye); + result.m13 = -Vector3DotProduct(y, eye); + result.m14 = -Vector3DotProduct(z, eye); result.m15 = 1.0f; - result = MatrixInvert(result); - return result; } @@ -1149,7 +1185,7 @@ RMDEF Quaternion QuaternionIdentity(void) // Computes the length of a quaternion RMDEF float QuaternionLength(Quaternion q) { - float result = (float)sqrt(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); return result; } @@ -1214,10 +1250,10 @@ RMDEF Quaternion QuaternionScale(Quaternion q, float mul) float qax = q.x, qay = q.y, qaz = q.z, qaw = q.w; - result.x = qax * mul + qaw * mul + qay * mul - qaz * mul; - result.y = qay * mul + qaw * mul + qaz * mul - qax * mul; - result.z = qaz * mul + qaw * mul + qax * mul - qay * mul; - result.w = qaw * mul - qax * mul - qay * mul - qaz * mul; + result.x = qax*mul + qaw*mul + qay*mul - qaz*mul; + result.y = qay*mul + qaw*mul + qaz*mul - qax*mul; + result.z = qaz*mul + qaw*mul + qax*mul - qay*mul; + result.w = qaw*mul - qax*mul - qay*mul - qaz*mul; return result; } @@ -1225,7 +1261,7 @@ RMDEF Quaternion QuaternionScale(Quaternion q, float mul) // Divide two quaternions RMDEF Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) { - Quaternion result = {q1.x / q2.x, q1.y / q2.y, q1.z / q2.z, q1.w / q2.w}; + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; return result; } @@ -1258,6 +1294,12 @@ RMDEF Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + if (fabs(cosHalfTheta) >= 1.0f) result = q1; else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); else @@ -1312,17 +1354,17 @@ RMDEF Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) // Returns a quaternion for a given rotation matrix RMDEF Quaternion QuaternionFromMatrix(Matrix mat) { - Quaternion result = { 0.0f }; - + Quaternion result = { 0 }; + if ((mat.m0 > mat.m5) && (mat.m0 > mat.m10)) { float s = sqrtf(1.0f + mat.m0 - mat.m5 - mat.m10)*2; - + result.x = 0.25f*s; result.y = (mat.m4 + mat.m1)/s; result.z = (mat.m2 + mat.m8)/s; result.w = (mat.m9 - mat.m6)/s; - } + } else if (mat.m5 > mat.m10) { float s = sqrtf(1.0f + mat.m5 - mat.m0 - mat.m10)*2; @@ -1330,7 +1372,7 @@ RMDEF Quaternion QuaternionFromMatrix(Matrix mat) result.y = 0.25f*s; result.z = (mat.m9 + mat.m6)/s; result.w = (mat.m2 - mat.m8)/s; - } + } else { float s = sqrtf(1.0f + mat.m10 - mat.m0 - mat.m5)*2; @@ -1339,7 +1381,7 @@ RMDEF Quaternion QuaternionFromMatrix(Matrix mat) result.z = 0.25f*s; result.w = (mat.m4 - mat.m1)/s; } - + return result; } @@ -1347,20 +1389,20 @@ RMDEF Quaternion QuaternionFromMatrix(Matrix mat) RMDEF Matrix QuaternionToMatrix(Quaternion q) { Matrix result = MatrixIdentity(); - + float a2 = 2*(q.x*q.x), b2=2*(q.y*q.y), c2=2*(q.z*q.z); //, d2=2*(q.w*q.w); - + float ab = 2*(q.x*q.y), ac=2*(q.x*q.z), bc=2*(q.y*q.z); float ad = 2*(q.x*q.w), bd=2*(q.y*q.w), cd=2*(q.z*q.w); result.m0 = 1 - b2 - c2; result.m1 = ab - cd; result.m2 = ac + bd; - + result.m4 = ab + cd; result.m5 = 1 - a2 - c2; result.m6 = bc - ad; - + result.m8 = ac - bd; result.m9 = bc + ad; result.m10 = 1 - a2 - b2; @@ -1419,17 +1461,18 @@ RMDEF void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle *outAngle = resAngle; } -// Returns he quaternion equivalent to Euler angles -RMDEF Quaternion QuaternionFromEuler(float roll, float pitch, float yaw) +// Returns the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMDEF Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) { Quaternion q = { 0 }; - float x0 = cosf(roll*0.5f); - float x1 = sinf(roll*0.5f); - float y0 = cosf(pitch*0.5f); - float y1 = sinf(pitch*0.5f); - float z0 = cosf(yaw*0.5f); - float z1 = sinf(yaw*0.5f); + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); q.x = x1*y0*z0 - x0*y1*z1; q.y = x0*y1*z0 + x1*y0*z1; diff --git a/raylib/rlgl.h b/raylib/rlgl.h index f098fad..a40c804 100644 --- a/raylib/rlgl.h +++ b/raylib/rlgl.h @@ -1,14 +1,13 @@ /********************************************************************************************** * -* rlgl - raylib OpenGL abstraction layer +* rlgl v3.7 - raylib OpenGL abstraction layer * * rlgl is a wrapper for multiple OpenGL versions (1.1, 2.1, 3.3 Core, ES 2.0) to * pseudo-OpenGL 1.1 style functions (rlVertex, rlTranslate, rlRotate...). * * When chosing an OpenGL version greater than OpenGL 1.1, rlgl stores vertex data on internal * VBO buffers (and VAOs if available). It requires calling 3 functions: -* rlglInit() - Initialize internal buffers and auxiliar resources -* rlglDraw() - Process internal buffers and send required draw calls +* rlglInit() - Initialize internal buffers and auxiliary resources * rlglClose() - De-initialize internal buffers data and other auxiliar resources * * CONFIGURATION: @@ -19,7 +18,7 @@ * #define GRAPHICS_API_OPENGL_ES2 * Use selected OpenGL graphics backend, should be supported by platform * Those preprocessor defines are only used on rlgl module, if OpenGL version is -* required by any other module, use rlGetVersion() tocheck it +* required by any other module, use rlGetVersion() to check it * * #define RLGL_IMPLEMENTATION * Generates the implementation of the library into the included file. @@ -29,8 +28,8 @@ * #define RLGL_STANDALONE * Use rlgl as standalone library (no raylib dependency) * -* #define SUPPORT_VR_SIMULATOR -* Support VR simulation functionality (stereo rendering) +* #define SUPPORT_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init * * DEPENDENCIES: * raymath - 3D math functionality (Vector3, Matrix, Quaternion) @@ -39,7 +38,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2021 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. @@ -95,7 +94,7 @@ #define RL_FREE(p) free(p) #endif #else - #include "raylib.h" // Required for: Model, Shader, Texture2D, TRACELOG() + #include "raylib.h" // Required for: Shader, Texture2D #endif #include "raymath.h" // Required for: Vector3, Matrix @@ -121,6 +120,8 @@ #endif #endif +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines #if defined(GRAPHICS_API_OPENGL_21) #define GRAPHICS_API_OPENGL_33 #endif @@ -136,9 +137,10 @@ // This is the maximum amount of elements (quads) per batch // NOTE: Be careful with text, every letter maps to a quad #define DEFAULT_BATCH_BUFFER_ELEMENTS 8192 - #elif defined(GRAPHICS_API_OPENGL_ES2) + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) // We reduce memory sizes for embedded systems (RPI and HTML5) - // NOTE: On HTML5 (emscripten) this is allocated on heap, + // NOTE: On HTML5 (emscripten) this is allocated on heap, // by default it's only 16MB!...just take care... #define DEFAULT_BATCH_BUFFER_ELEMENTS 2048 #endif @@ -149,12 +151,20 @@ #ifndef DEFAULT_BATCH_DRAWCALLS #define DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) #endif +#ifndef MAX_BATCH_ACTIVE_TEXTURES + #define MAX_BATCH_ACTIVE_TEXTURES 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) +#endif // Internal Matrix stack #ifndef MAX_MATRIX_STACK_SIZE #define MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack #endif +// Vertex buffers id limit +#ifndef MAX_MESH_VERTEX_BUFFERS + #define MAX_MESH_VERTEX_BUFFERS 7 // Maximum vertex buffers (VBO) per mesh +#endif + // Shader and material limits #ifndef MAX_SHADER_LOCATIONS #define MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported @@ -172,40 +182,122 @@ #endif // Texture parameters (equivalent to OpenGL defines) -#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S -#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T -#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER -#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER -#define RL_TEXTURE_ANISOTROPIC_FILTER 0x3000 // Anisotropic filter (custom identifier) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER -#define RL_FILTER_NEAREST 0x2600 // GL_NEAREST -#define RL_FILTER_LINEAR 0x2601 // GL_LINEAR -#define RL_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST -#define RL_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR -#define RL_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST -#define RL_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) -#define RL_WRAP_REPEAT 0x2901 // GL_REPEAT -#define RL_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE -#define RL_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT -#define RL_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT // Matrix modes (equivalent to OpenGL) -#define RL_MODELVIEW 0x1700 // GL_MODELVIEW -#define RL_PROJECTION 0x1701 // GL_PROJECTION -#define RL_TEXTURE 0x1702 // GL_TEXTURE +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE // Primitive assembly draw modes -#define RL_LINES 0x0001 // GL_LINES -#define RL_TRIANGLES 0x0004 // GL_TRIANGLES -#define RL_QUADS 0x0007 // GL_QUADS +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- typedef enum { OPENGL_11 = 1, OPENGL_21, OPENGL_33, OPENGL_ES_20 } GlVersion; -typedef unsigned char byte; +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, + RL_ATTACHMENT_COLOR_CHANNEL1, + RL_ATTACHMENT_COLOR_CHANNEL2, + RL_ATTACHMENT_COLOR_CHANNEL3, + RL_ATTACHMENT_COLOR_CHANNEL4, + RL_ATTACHMENT_COLOR_CHANNEL5, + RL_ATTACHMENT_COLOR_CHANNEL6, + RL_ATTACHMENT_COLOR_CHANNEL7, + RL_ATTACHMENT_DEPTH = 100, + RL_ATTACHMENT_STENCIL = 200, +} FramebufferAttachType; + +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y, + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z, + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z, + RL_ATTACHMENT_TEXTURE2D = 100, + RL_ATTACHMENT_RENDERBUFFER = 200, +} FramebufferAttachTextureType; + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct VertexBuffer { + int elementsCount; // Number of elements in the buffer (QUADS) + + int vCounter; // Vertex position counter to process (and draw) from full buffer + int tcCounter; // Vertex texcoord counter to process (and draw) from full buffer + int cCounter; // Vertex color counter to process (and draw) from full buffer + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) +} VertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct DrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShader.id + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} DrawCall; + +// RenderBatch type +typedef struct RenderBatch { + int buffersCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + VertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + DrawCall *draws; // Draw calls array, depends on textureId + int drawsCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} RenderBatch; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, + SHADER_ATTRIB_VEC2, + SHADER_ATTRIB_VEC3, + SHADER_ATTRIB_VEC4 +} ShaderAttributeDataType; #if defined(RLGL_STANDALONE) #ifndef __cplusplus @@ -221,15 +313,7 @@ typedef unsigned char byte; unsigned char a; } Color; - // Rectangle type - typedef struct Rectangle { - float x; - float y; - float width; - float height; - } Rectangle; - - // Texture2D type + // Texture type // NOTE: Data stored in GPU memory typedef struct Texture2D { unsigned int id; // OpenGL texture id @@ -239,98 +323,12 @@ typedef unsigned char byte; int format; // Data format (PixelFormat) } Texture2D; - // Texture type, same as Texture2D - typedef Texture2D Texture; - - // TextureCubemap type, actually, same as Texture2D - typedef Texture2D TextureCubemap; - - // RenderTexture2D type, for texture rendering - typedef struct RenderTexture2D { - unsigned int id; // OpenGL framebuffer (fbo) id - Texture2D texture; // Color buffer attachment texture - Texture2D depth; // Depth buffer attachment texture - bool depthTexture; // Track if depth attachment is a texture or renderbuffer - } RenderTexture2D; - - // RenderTexture type, same as RenderTexture2D - typedef RenderTexture2D RenderTexture; - - // Vertex data definning a mesh - typedef struct Mesh { - int vertexCount; // number of vertices stored in arrays - int triangleCount; // number of triangles stored (indexed or not) - float *vertices; // vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *texcoords2; // vertex second texture coordinates (useful for lightmaps) (shader-location = 5) - float *normals; // vertex normals (XYZ - 3 components per vertex) (shader-location = 2) - float *tangents; // vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) - unsigned char *colors; // vertex colors (RGBA - 4 components per vertex) (shader-location = 3) - unsigned short *indices;// vertex indices (in case vertex data comes indexed) - - // Animation vertex data - float *animVertices; // Animated vertex positions (after bones transformations) - float *animNormals; // Animated normals (after bones transformations) - int *boneIds; // Vertex bone ids, up to 4 bones influence by vertex (skinning) - float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) - - // OpenGL identifiers - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int *vboId; // OpenGL Vertex Buffer Objects id (7 types of vertex data) - } Mesh; - // Shader type (generic) typedef struct Shader { unsigned int id; // Shader program id int *locs; // Shader locations array (MAX_SHADER_LOCATIONS) } Shader; - // Material texture map - typedef struct MaterialMap { - Texture2D texture; // Material map texture - Color color; // Material map color - float value; // Material map value - } MaterialMap; - - // Material type (generic) - typedef struct Material { - Shader shader; // Material shader - MaterialMap *maps; // Material maps (MAX_MATERIAL_MAPS) - float *params; // Material generic parameters (if required) - } Material; - - // Camera type, defines a camera position/orientation in 3d space - typedef struct Camera { - Vector3 position; // Camera position - Vector3 target; // Camera target it looks-at - Vector3 up; // Camera up vector (rotation over its axis) - float fovy; // Camera field-of-view apperture in Y (degrees) - } Camera; - - // Head-Mounted-Display device parameters - typedef struct VrDeviceInfo { - int hResolution; // HMD horizontal resolution in pixels - int vResolution; // HMD vertical resolution in pixels - float hScreenSize; // HMD horizontal size in meters - float vScreenSize; // HMD vertical size in meters - float vScreenCenter; // HMD screen center in meters - float eyeToScreenDistance; // HMD distance between eye and display in meters - float lensSeparationDistance; // HMD lens separation distance in meters - float interpupillaryDistance; // HMD IPD (distance between pupils) in meters - float lensDistortionValues[4]; // HMD lens distortion constant parameters - float chromaAbCorrection[4]; // HMD chromatic aberration correction parameters - } VrDeviceInfo; - - // VR Stereo rendering configuration for simulator - typedef struct VrStereoConfig { - Shader distortionShader; // VR stereo rendering distortion shader - Matrix eyesProjection[2]; // VR stereo rendering eyes projection matrices - Matrix eyesViewOffset[2]; // VR stereo rendering eyes view offset matrices - int eyeViewportRight[4]; // VR stereo rendering right eye viewport [x, y, w, h] - int eyeViewportLeft[4]; // VR stereo rendering left eye viewport [x, y, w, h] - } VrStereoConfig; - - // TraceLog message types typedef enum { LOG_ALL, @@ -341,44 +339,52 @@ typedef unsigned char byte; LOG_ERROR, LOG_FATAL, LOG_NONE - } TraceLogType; + } TraceLogLevel; // Texture formats (support depends on OpenGL version) typedef enum { - UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - UNCOMPRESSED_GRAY_ALPHA, - 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_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 - COMPRESSED_DXT5_RGBA, // 8 bpp - COMPRESSED_ETC1_RGB, // 4 bpp - COMPRESSED_ETC2_RGB, // 4 bpp - COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - COMPRESSED_PVRT_RGB, // 4 bpp - COMPRESSED_PVRT_RGBA, // 4 bpp - COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - COMPRESSED_ASTC_8x8_RGBA // 2 bpp + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp } PixelFormat; // Texture parameters: filter mode // NOTE 1: Filtering considers mipmaps if available in the texture // NOTE 2: Filter is accordingly set for minification and magnification typedef enum { - FILTER_POINT = 0, // No filter, just pixel aproximation - FILTER_BILINEAR, // Linear filtering - FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x - } TextureFilterMode; + TEXTURE_FILTER_POINT = 0, // No filter, just pixel aproximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x + } TextureFilter; + + // Texture parameters: wrap mode + typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode + } TextureWrap; // Color blending modes (pre-defined) typedef enum { @@ -392,66 +398,49 @@ typedef unsigned char byte; // 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_OCCLUSION, - LOC_MAP_EMISSION, - LOC_MAP_HEIGHT, - LOC_MAP_CUBEMAP, - LOC_MAP_IRRADIANCE, - LOC_MAP_PREFILTER, - LOC_MAP_BRDF + SHADER_LOC_VERTEX_POSITION = 0, + SHADER_LOC_VERTEX_TEXCOORD01, + SHADER_LOC_VERTEX_TEXCOORD02, + SHADER_LOC_VERTEX_NORMAL, + SHADER_LOC_VERTEX_TANGENT, + SHADER_LOC_VERTEX_COLOR, + SHADER_LOC_MATRIX_MVP, + SHADER_LOC_MATRIX_MODEL, + SHADER_LOC_MATRIX_VIEW, + SHADER_LOC_MATRIX_NORMAL, + SHADER_LOC_MATRIX_PROJECTION, + SHADER_LOC_VECTOR_VIEW, + SHADER_LOC_COLOR_DIFFUSE, + SHADER_LOC_COLOR_SPECULAR, + SHADER_LOC_COLOR_AMBIENT, + SHADER_LOC_MAP_ALBEDO, // SHADER_LOC_MAP_DIFFUSE + SHADER_LOC_MAP_METALNESS, // SHADER_LOC_MAP_SPECULAR + SHADER_LOC_MAP_NORMAL, + SHADER_LOC_MAP_ROUGHNESS, + SHADER_LOC_MAP_OCCLUSION, + SHADER_LOC_MAP_EMISSION, + SHADER_LOC_MAP_HEIGHT, + SHADER_LOC_MAP_CUBEMAP, + SHADER_LOC_MAP_IRRADIANCE, + SHADER_LOC_MAP_PREFILTER, + SHADER_LOC_MAP_BRDF } ShaderLocationIndex; + #define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO + #define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + // Shader uniform data types typedef enum { - UNIFORM_FLOAT = 0, - UNIFORM_VEC2, - UNIFORM_VEC3, - UNIFORM_VEC4, - UNIFORM_INT, - UNIFORM_IVEC2, - UNIFORM_IVEC3, - UNIFORM_IVEC4, - UNIFORM_SAMPLER2D + SHADER_UNIFORM_FLOAT = 0, + SHADER_UNIFORM_VEC2, + SHADER_UNIFORM_VEC3, + SHADER_UNIFORM_VEC4, + SHADER_UNIFORM_INT, + SHADER_UNIFORM_IVEC2, + SHADER_UNIFORM_IVEC3, + SHADER_UNIFORM_IVEC4, + SHADER_UNIFORM_SAMPLER2D } ShaderUniformDataType; - - #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 - } MaterialMapType; - - #define MAP_DIFFUSE MAP_ALBEDO - #define MAP_SPECULAR MAP_METALNESS #endif #if defined(__cplusplus) @@ -483,130 +472,157 @@ RLAPI void rlVertex2f(float x, float y); // Define one vertex (posi RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float -RLAPI void rlColor4ub(byte r, byte g, byte b, byte a); // Define one vertex (color) - 4 byte +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float //------------------------------------------------------------------------------------ -// Functions Declaration - OpenGL equivalent functions (common to 1.1, 3.3+, ES2) -// NOTE: This functions are used to completely abstract raylib code from OpenGL layer +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom //------------------------------------------------------------------------------------ -RLAPI void rlEnableTexture(unsigned int id); // Enable texture usage -RLAPI void rlDisableTexture(void); // Disable texture usage + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id);// Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index);// Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); +RLAPI void rlDisableStatePointer(int vertexAttribType); +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) -RLAPI void rlEnableRenderTexture(unsigned int id); // Enable render texture (fbo) -RLAPI void rlDisableRenderTexture(void); // Disable render texture (fbo), return to default framebuffer -RLAPI void rlEnableDepthTest(void); // Enable depth test -RLAPI void rlDisableDepthTest(void); // Disable depth test -RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling -RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling -RLAPI void rlEnableScissorTest(void); // Enable scissor test -RLAPI void rlDisableScissorTest(void); // Disable scissor test -RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test -RLAPI void rlEnableWireMode(void); // Enable wire mode -RLAPI void rlDisableWireMode(void); // Disable wire mode -RLAPI void rlDeleteTextures(unsigned int id); // Delete OpenGL texture from GPU -RLAPI void rlDeleteRenderTextures(RenderTexture2D target); // Delete render textures (fbo) from GPU -RLAPI void rlDeleteShader(unsigned int id); // Delete OpenGL shader program from GPU -RLAPI void rlDeleteVertexArrays(unsigned int id); // Unload vertex data (VAO) from GPU memory -RLAPI void rlDeleteBuffers(unsigned int id); // Unload vertex data (VBO) from GPU memory -RLAPI void rlClearColor(byte r, byte g, byte b, byte a); // Clear color buffer with color -RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) -RLAPI void rlUpdateBuffer(int bufferId, void *data, int dataSize); // Update GPU buffer with new data -RLAPI unsigned int rlLoadAttribBuffer(unsigned int vaoId, int shaderLoc, void *buffer, int size, bool dynamic); // Load a new attributes buffer + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer + +// General render state +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlDisableWireMode(void); // Disable wire mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) //------------------------------------------------------------------------------------ // Functions Declaration - rlgl functionality //------------------------------------------------------------------------------------ +// rlgl initialization functions RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) RLAPI void rlglClose(void); // De-inititialize rlgl (buffers, shaders, textures) -RLAPI void rlglDraw(void); // Update and draw default internal buffers -RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes - +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) RLAPI int rlGetVersion(void); // Returns current OpenGL version -RLAPI bool rlCheckBufferLimit(int vCount); // Check internal buffer overflow for a given number of vertex -RLAPI void rlSetDebugMarker(const char *text); // Set debug marker for analysis -RLAPI void rlSetBlendMode(int glSrcFactor, int glDstFactor, int glEquation); // // Set blending mode factor and equation (using OpenGL factors) -RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height -// Textures data management +RLAPI Shader rlGetShaderDefault(void); // Get default shader +RLAPI Texture2D rlGetTextureDefault(void); // Get default texture + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI RenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(RenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(RenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(RenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic); // Load a vertex buffer attribute +RLAPI unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic); // Load a new attributes element buffer +RLAPI void rlUpdateVertexBuffer(int bufferId, void *data, int dataSize, int offset); // Update GPU buffer with new data +RLAPI void rlUnloadVertexArray(unsigned int vaoId); +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer); +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value +RLAPI void rlDrawVertexArray(int offset, int count); +RLAPI void rlDrawVertexArrayElements(int offset, int count, void *buffer); +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances); + +// Textures management RLAPI unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount); // Load texture in GPU -RLAPI unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) RLAPI unsigned int rlLoadTextureCubemap(void *data, int size, int format); // Load texture cubemap RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update GPU texture with new data RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory - RLAPI void rlGenerateMipmaps(Texture2D *texture); // Generate mipmap data for selected texture RLAPI void *rlReadTexturePixels(Texture2D texture); // Read texture pixel data RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) -// Render texture management (fbo) -RLAPI RenderTexture2D rlLoadRenderTexture(int width, int height, int format, int depthBits, bool useDepthTexture); // Load a render texture (with color and depth attachments) -RLAPI void rlRenderTextureAttach(RenderTexture target, unsigned int id, int attachType); // Attach texture/renderbuffer to an fbo -RLAPI bool rlRenderTextureComplete(RenderTexture target); // Verify render texture is complete +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(int width, int height); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU -// Vertex data management -RLAPI void rlLoadMesh(Mesh *mesh, bool dynamic); // Upload vertex data into GPU and provided VAO/VBO ids -RLAPI void rlUpdateMesh(Mesh mesh, int buffer, int count); // Update vertex or index data on GPU (upload new data to one buffer) -RLAPI void rlUpdateMeshAt(Mesh mesh, int buffer, int count, int index); // Update vertex or index data on GPU, at index -RLAPI void rlDrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform -RLAPI void rlUnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: GL_VERTEX_SHADER, GL_FRAGMENT_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(Shader shader); // Set shader currently active -// 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 - -#if defined(RLGL_STANDALONE) -//------------------------------------------------------------------------------------ -// Shaders System Functions (Module: rlgl) -// NOTE: This functions are useless when using OpenGL 1.1 -//------------------------------------------------------------------------------------ -// Shader loading/unloading functions -RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -RLAPI Shader LoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations -RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) - -RLAPI Shader GetShaderDefault(void); // Get default shader -RLAPI Texture2D GetTextureDefault(void); // Get default texture -RLAPI Texture2D GetShapesTexture(void); // Get texture to draw shapes -RLAPI Rectangle GetShapesTextureRec(void); // Get texture rectangle to draw shapes - -// Shader configuration functions -RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType); // Set shader uniform value -RLAPI void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count); // Set shader uniform value vector -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(void); // Get internal modelview matrix - -// Texture maps generation (PBR) -// NOTE: Required shaders should be provided -RLAPI Texture2D GenTextureCubemap(Shader shader, Texture2D map, int size); // Generate cubemap texture from HDR texture -RLAPI Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size); // Generate irradiance texture using cubemap data -RLAPI Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size); // Generate prefilter texture using cubemap data -RLAPI Texture2D GenTextureBRDF(Shader shader, int size); // Generate BRDF texture using cubemap data - -// Shading begin/end functions -RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing -RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) -RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied) -RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) - -// VR control functions -RLAPI void InitVrSimulator(void); // Init VR simulator for selected device parameters -RLAPI void CloseVrSimulator(void); // Close VR simulator for current device -RLAPI void UpdateVrTracking(Camera *camera); // Update VR tracking (position and orientation) and camera -RLAPI void SetVrConfiguration(VrDeviceInfo info, Shader distortion); // Set stereo rendering configuration parameters -RLAPI bool IsVrSimulatorReady(void); // Detect if VR simulator is ready -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 char *LoadFileText(const char *fileName); // Load chars array from text file -RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data size in bytes (image or texture) -#endif +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad #if defined(__cplusplus) } #endif @@ -621,9 +637,7 @@ RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data #if defined(RLGL_IMPLEMENTATION) -#if defined(RLGL_STANDALONE) - #include // Required for: fopen(), fseek(), fread(), fclose() [LoadFileText] -#else +#if !defined(RLGL_STANDALONE) // Check if config flags have been externally provided on compilation line #if !defined(EXTERNAL_CONFIG_FLAGS) #include "config.h" // Defines module configuration flags @@ -633,7 +647,6 @@ RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data #include // Required for: malloc(), free() #include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] -#include // Required for: atan2f() #if defined(GRAPHICS_API_OPENGL_11) #if defined(__APPLE__) @@ -657,10 +670,6 @@ RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data #endif #endif -#if defined(GRAPHICS_API_OPENGL_21) - #define GRAPHICS_API_OPENGL_33 // OpenGL 2.1 uses mostly OpenGL 3.3 Core functionality -#endif - #if defined(GRAPHICS_API_OPENGL_33) #if defined(__APPLE__) #include // OpenGL 3 library for OSX @@ -680,9 +689,17 @@ RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data #if defined(GRAPHICS_API_OPENGL_ES2) #define GL_GLEXT_PROTOTYPES - #include // EGL library + //#include // EGL library -> not required, platform layer #include // OpenGL ES 2.0 library #include // OpenGL ES 2.0 extensions library + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_RPI) || defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif #endif //---------------------------------------------------------------------------------- @@ -738,6 +755,7 @@ RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 #endif + #if defined(GRAPHICS_API_OPENGL_21) #define GL_LUMINANCE 0x1909 #define GL_LUMINANCE_ALPHA 0x190A @@ -772,65 +790,6 @@ RLAPI int GetPixelDataSize(int width, int height, int format);// Get pixel data //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- - -// Dynamic vertex buffers (position + texcoords + colors + indices arrays) -typedef struct VertexBuffer { - int elementsCount; // Number of elements in the buffer (QUADS) - - int vCounter; // Vertex position counter to process (and draw) from full buffer - int tcCounter; // Vertex texcoord counter to process (and draw) from full buffer - int cCounter; // Vertex color counter to process (and draw) from full buffer - - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#elif defined(GRAPHICS_API_OPENGL_ES2) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int vboId[4]; // OpenGL Vertex Buffer Objects id (4 types of vertex data) -} VertexBuffer; - -// Draw call type -// NOTE: Only texture changes register a new draw, other state-change-related elements are not -// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any -// of those state-change happens (this is done in core module) -typedef struct DrawCall { - int mode; // Drawing mode: LINES, TRIANGLES, QUADS - int vertexCount; // Number of vertex of the draw - int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) - //unsigned int vaoId; // Vertex array id to be used on the draw - //unsigned int shaderId; // Shader id to be used on the draw - unsigned int textureId; // Texture id to be used on the draw - - //Matrix projection; // Projection matrix for this draw - //Matrix modelview; // Modelview matrix for this draw -} DrawCall; - -// RenderBatch type -typedef struct RenderBatch { - int buffersCount; // Number of vertex buffers (multi-buffering support) - int currentBuffer; // Current buffer tracking in case of multi-buffering - VertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data - - DrawCall *draws; // Draw calls array - int drawsCounter; // Draw calls counter - float currentDepth; // Current depth value for next draw -} RenderBatch; - -#if defined(SUPPORT_VR_SIMULATOR) -// VR Stereo rendering configuration for simulator -typedef struct VrStereoConfig { - Shader distortionShader; // VR stereo rendering distortion shader - Matrix eyesProjection[2]; // VR stereo rendering eyes projection matrices - Matrix eyesViewOffset[2]; // VR stereo rendering eyes view offset matrices - int eyeViewportRight[4]; // VR stereo rendering right eye viewport [x, y, w, h] - int eyeViewportLeft[4]; // VR stereo rendering left eye viewport [x, y, w, h] -} VrStereoConfig; -#endif - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) typedef struct rlglData { RenderBatch *currentBatch; // Current render batch @@ -846,14 +805,17 @@ typedef struct rlglData { Matrix stack[MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop int stackCounter; // Matrix stack counter - Texture2D shapesTexture; // Texture used on shapes drawing (usually a white) - Rectangle shapesTextureRec; // Texture source rectangle used on shapes drawing unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[MAX_BATCH_ACTIVE_TEXTURES]; // Active texture ids to be enabled on batch drawing (0 active by default) unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) unsigned int defaultFShaderId; // Default fragment shader Id (used by default shader program) Shader defaultShader; // Basic shader, support vertex color and diffuse texture Shader currentShader; // Shader to be used on rendering (by default, defaultShader) - + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + int currentBlendMode; // Blending mode active int glBlendSrcFactor; // Blending source factor int glBlendDstFactor; // Blending destination factor @@ -862,34 +824,29 @@ typedef struct rlglData { int framebufferWidth; // Default framebuffer width int framebufferHeight; // Default framebuffer height - } State; + } State; // Renderer state struct { - bool vao; // VAO support (OpenGL ES2 could not support VAO extension) - bool texNPOT; // NPOT textures full support - bool texDepth; // Depth textures supported - bool texFloat32; // float textures support (32 bit per channel) - bool texCompDXT; // DDS texture compression support - bool texCompETC1; // ETC1 texture compression support - bool texCompETC2; // ETC2/EAC texture compression support - bool texCompPVRT; // PVR texture compression support - bool texCompASTC; // ASTC texture compression support - bool texMirrorClamp; // Clamp mirror wrap mode supported - bool texAnisoFilter; // Anisotropic texture filtering support - bool debugMarker; // Debug marker support + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_WEBGL_depth_texture, GL_OES_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) - float maxAnisotropicLevel; // Maximum anisotropy level supported (minimum is 2.0f) + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) int maxDepthBits; // Maximum bits for depth component } ExtSupported; // Extensions supported flags -#if defined(SUPPORT_VR_SIMULATOR) - struct { - VrStereoConfig config; // VR stereo configuration for simulator - RenderTexture2D stereoFbo; // VR stereo rendering framebuffer - bool simulatorReady; // VR simulator ready flag - bool stereoRender; // VR stereo rendering enabled/disabled flag - } Vr; -#endif // SUPPORT_VR_SIMULATOR } rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 //---------------------------------------------------------------------------------- @@ -901,49 +858,37 @@ static rlglData RLGL = { 0 }; #if defined(GRAPHICS_API_OPENGL_ES2) // NOTE: VAO functionality is exposed through extensions (OES) -static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays; // Entry point pointer to function glGenVertexArrays() -static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray; // Entry point pointer to function glBindVertexArray() -static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays; // Entry point pointer to function glDeleteVertexArrays() +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; #endif //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static unsigned int CompileShader(const char *shaderStr, int type); // Compile custom shader and return shader id -static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program - -static Shader LoadShaderDefault(void); // Load default shader (just vertex positioning and texture coloring) -static void SetShaderDefaultLocations(Shader *shader); // Bind default shader locations (attributes and uniforms) -static void UnloadShaderDefault(void); // Unload default shader - -static RenderBatch LoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system -static void UnloadRenderBatch(RenderBatch batch); // Unload render batch system -static void DrawRenderBatch(RenderBatch *batch); // Draw render batch data (Update->Draw->Reset) -static void SetRenderBatchActive(RenderBatch *batch); // Set the active render batch for rlgl -static void SetRenderBatchDefault(void); // Set default render batch for rlgl -//static bool CheckRenderBatchLimit(RenderBatch batch, int vCount); // Check render batch vertex buffer limits - -static void GenDrawCube(void); // Generate and draw cube -static void GenDrawQuad(void); // Generate and draw quad - -#if defined(SUPPORT_VR_SIMULATOR) -static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView); // Set internal projection and modelview matrix depending on eye -#endif - +static void rlLoadShaderDefault(void); // Load default shader (RLGL.State.defaultShader) +static void rlUnloadShaderDefault(void); // Unload default shader (RLGL.State.defaultShader) +#if defined(SUPPORT_GL_DETAILS_INFO) +static char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // SUPPORT_GL_DETAILS_INFO #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - #if defined(GRAPHICS_API_OPENGL_11) -static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight); -static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight); +static int rlGenerateMipmapsData(unsigned char *data, int baseWidth, int baseHeight); // Generate mipmaps data on CPU side +static Color *rlGenNextMipmapData(Color *srcData, int srcWidth, int srcHeight); // Generate next mipmap level on CPU side #endif +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) //---------------------------------------------------------------------------------- // Module Functions Definition - Matrix operations //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_11) - // Fallback to OpenGL 1.1 function calls //--------------------------------------- void rlMatrixMode(int mode) @@ -974,9 +919,8 @@ void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } void rlRotatef(float angleDeg, float x, float y, float z) { glRotatef(angleDeg, x, y, z); } void rlScalef(float x, float y, float z) { glScalef(x, y, z); } void rlMultMatrixf(float *matf) { glMultMatrixf(matf); } - -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Choose the current matrix to be transformed void rlMatrixMode(int mode) { @@ -1078,11 +1022,12 @@ void rlFrustum(double left, double right, double bottom, double top, double znea // Multiply the current matrix by an orthographic matrix generated by parameters void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) { + // NOTE: If left-right and top-botton values are equal it could create + // a division by zero on MatrixOrtho(), response to it is platform/compiler dependant Matrix matOrtho = MatrixOrtho(left, right, bottom, top, znear, zfar); *RLGL.State.currentMatrix = MatrixMultiply(*RLGL.State.currentMatrix, matOrtho); } - #endif // Set the viewport area (transformation from normalized device coordinates to window coordinates) @@ -1095,7 +1040,6 @@ void rlViewport(int x, int y, int width, int height) // Module Functions Definition - Vertex level operations //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_11) - // Fallback to OpenGL 1.1 function calls //--------------------------------------- void rlBegin(int mode) @@ -1115,12 +1059,11 @@ void rlVertex2f(float x, float y) { glVertex2f(x, y); } void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } -void rlColor4ub(byte r, byte g, byte b, byte a) { glColor4ub(r, g, b, a); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } - -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Initialize drawing mode (how to organize vertex) void rlBegin(int mode) { @@ -1137,11 +1080,9 @@ void rlBegin(int mode) // for the next set of vertex to be drawn if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; - if (rlCheckBufferLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) rlglDraw(); - else + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) { RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; @@ -1151,7 +1092,7 @@ void rlBegin(int mode) } } - if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlglDraw(); + if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode = mode; RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; @@ -1201,14 +1142,15 @@ void rlEnd(void) RLGL.currentBatch->currentDepth += (1.0f/20000.0f); // Verify internal buffers limits - // NOTE: This check is combined with usage of rlCheckBufferLimit() - if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter) >= (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4 - 4)) + // NOTE: This check is combined with usage of rlCheckRenderBatchLimit() + if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4 - 4)) { - // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlglDraw(), + // WARNING: If we are between rlPushMatrix() and rlPopMatrix() and we need to force a rlDrawRenderBatch(), // we need to call rlPopMatrix() before to recover *RLGL.State.currentMatrix (RLGL.State.modelview) for the next forced draw call! // If we have multiple matrix pushed, it will require "RLGL.State.stackCounter" pops before launching the draw for (int i = RLGL.State.stackCounter; i >= 0; i--) rlPopMatrix(); - rlglDraw(); + rlDrawRenderBatch(RLGL.currentBatch); } } @@ -1263,7 +1205,7 @@ void rlNormal3f(float x, float y, float z) } // Define one vertex (color) -void rlColor4ub(byte x, byte y, byte z, byte w) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) { RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter] = x; RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter + 1] = y; @@ -1275,73 +1217,115 @@ void rlColor4ub(byte x, byte y, byte z, byte w) // Define one vertex (color) void rlColor4f(float r, float g, float b, float a) { - rlColor4ub((byte)(r*255), (byte)(g*255), (byte)(b*255), (byte)(a*255)); + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); } // Define one vertex (color) void rlColor3f(float x, float y, float z) { - rlColor4ub((byte)(x*255), (byte)(y*255), (byte)(z*255), 255); + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); } #endif -//---------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL equivalent functions (common to 1.1, 3.3+, ES2) -//---------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- -// Enable texture usage +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) + { + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawsCounter++; + } + } + + if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture void rlEnableTexture(unsigned int id) { #if defined(GRAPHICS_API_OPENGL_11) glEnable(GL_TEXTURE_2D); +#endif glBindTexture(GL_TEXTURE_2D, id); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId != id) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount%4))); - - else RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment = 0; - - if (rlCheckBufferLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment)) rlglDraw(); - else - { - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].cCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].tcCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexAlignment; - - RLGL.currentBatch->drawsCounter++; - } - } - - if (RLGL.currentBatch->drawsCounter >= DEFAULT_BATCH_DRAWCALLS) rlglDraw(); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].textureId = id; - RLGL.currentBatch->draws[RLGL.currentBatch->drawsCounter - 1].vertexCount = 0; - } -#endif } -// Disable texture usage +// Disable texture void rlDisableTexture(void) { #if defined(GRAPHICS_API_OPENGL_11) glDisable(GL_TEXTURE_2D); +#endif glBindTexture(GL_TEXTURE_2D, 0); -#else - // NOTE: If quads batch limit is reached, - // we force a draw call and next batch starts - if (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter >= (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) rlglDraw(); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnable(GL_TEXTURE_CUBE_MAP); // Core in OpenGL 1.4 + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisable(GL_TEXTURE_CUBE_MAP); + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); #endif } @@ -1355,7 +1339,7 @@ void rlTextureParameters(unsigned int id, int param, int value) case RL_TEXTURE_WRAP_S: case RL_TEXTURE_WRAP_T: { - if (value == RL_WRAP_MIRROR_CLAMP) + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) { #if !defined(GRAPHICS_API_OPENGL_11) if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); @@ -1367,13 +1351,13 @@ void rlTextureParameters(unsigned int id, int param, int value) } break; case RL_TEXTURE_MAG_FILTER: case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; - case RL_TEXTURE_ANISOTROPIC_FILTER: + case RL_TEXTURE_FILTER_ANISOTROPIC: { #if !defined(GRAPHICS_API_OPENGL_11) - if (value <= RLGL.ExtSupported.maxAnisotropicLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropicLevel > 0.0f) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) { - TRACELOG(LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, RLGL.ExtSupported.maxAnisotropicLevel); + TRACELOG(LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, RLGL.ExtSupported.maxAnisotropyLevel); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); } else TRACELOG(LOG_WARNING, "GL: Anisotropic filtering not supported"); @@ -1385,25 +1369,35 @@ void rlTextureParameters(unsigned int id, int param, int value) glBindTexture(GL_TEXTURE_2D, 0); } +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + // Enable rendering to texture (fbo) -void rlEnableRenderTexture(unsigned int id) +void rlEnableFramebuffer(unsigned int id) { #if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) glBindFramebuffer(GL_FRAMEBUFFER, id); - - //glDisable(GL_CULL_FACE); // Allow double side drawing for texture flipping - //glCullFace(GL_FRONT); #endif } // Disable rendering to texture -void rlDisableRenderTexture(void) +void rlDisableFramebuffer(void) { #if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) glBindFramebuffer(GL_FRAMEBUFFER, 0); - - //glEnable(GL_CULL_FACE); - //glCullFace(GL_BACK); #endif } @@ -1413,6 +1407,12 @@ void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } // Disable depth test void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + // Enable backface culling void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } @@ -1420,13 +1420,13 @@ void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } // Enable scissor test -RLAPI void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } // Disable scissor test -RLAPI void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } // Scissor test -RLAPI void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } // Enable wire mode void rlEnableWireMode(void) @@ -1445,64 +1445,64 @@ void rlDisableWireMode(void) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif } - -// Unload texture from GPU memory -void rlDeleteTextures(unsigned int id) +// Set the line drawing width +void rlSetLineWidth(float width) { - if (id > 0) glDeleteTextures(1, &id); + glLineWidth(width); } -// Unload render texture from GPU memory -void rlDeleteRenderTextures(RenderTexture2D target) +// Get the line drawing width +float rlGetLineWidth(void) { -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) - if (target.texture.id > 0) glDeleteTextures(1, &target.texture.id); - if (target.depth.id > 0) - { - if (target.depthTexture) glDeleteTextures(1, &target.depth.id); - else glDeleteRenderbuffers(1, &target.depth.id); - } + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} - if (target.id > 0) glDeleteFramebuffers(1, &target.id); - - TRACELOG(LOG_INFO, "FBO: [ID %i] Unloaded render texture data from VRAM (GPU)", target.id); +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); #endif } -// Unload shader from GPU memory -void rlDeleteShader(unsigned int id) +// Disable line aliasing +void rlDisableSmoothLines(void) { -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (id != 0) glDeleteProgram(id); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); #endif } -// Unload vertex data (VAO) from GPU memory -void rlDeleteVertexArrays(unsigned int id) +// Enable stereo rendering +void rlEnableStereoRender(void) { -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - if (id != 0) glDeleteVertexArrays(1, &id); - TRACELOG(LOG_INFO, "VAO: [ID %i] Unloaded vertex data from VRAM (GPU)", id); - } +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; #endif } -// Unload vertex data (VBO) from GPU memory -void rlDeleteBuffers(unsigned int id) +// Disable stereo rendering +void rlDisableStereoRender(void) { -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (id != 0) - { - glDeleteBuffers(1, &id); - if (!RLGL.ExtSupported.vao) TRACELOG(LOG_INFO, "VBO: [ID %i] Unloaded vertex data from VRAM (GPU)", id); - } +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; #endif } // Clear color buffer with color -void rlClearColor(byte r, byte g, byte b, byte a) +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { // Color values clamp to 0.0f(0) and 1.0f(255) float cr = (float)r/255; @@ -1520,216 +1520,85 @@ void rlClearScreenBuffers(void) //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... } -// Update GPU buffer with new data -void rlUpdateBuffer(int bufferId, void *data, int dataSize) +// Check and log OpenGL error codes +void rlCheckErrors() { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, bufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, dataSize, data); + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentBlendMode != mode) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case BLEND_CUSTOM: glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; #endif } //---------------------------------------------------------------------------------- -// Module Functions Definition - rlgl Functions +// Module Functions Definition - rlgl functionality //---------------------------------------------------------------------------------- // Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states void rlglInit(int width, int height) { - // Check OpenGL information and capabilities - //------------------------------------------------------------------------------ - // Print current OpenGL and GLSL version - TRACELOG(LOG_INFO, "GL: OpenGL device information:"); - TRACELOG(LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); - TRACELOG(LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); - TRACELOG(LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); - TRACELOG(LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); - - // NOTE: We can get a bunch of extra information about GPU capabilities (glGet*) - //int maxTexSize; - //glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); - //TRACELOG(LOG_INFO, "GL: Maximum texture size: %i", maxTexSize); - - //GL_MAX_TEXTURE_IMAGE_UNITS - //GL_MAX_VIEWPORT_DIMS - - //int numAuxBuffers; - //glGetIntegerv(GL_AUX_BUFFERS, &numAuxBuffers); - //TRACELOG(LOG_INFO, "GL: Number of aixiliar buffers: %i", numAuxBuffers); - - //GLint numComp = 0; - //GLint format[32] = { 0 }; - //glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numComp); - //glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format); - //for (int i = 0; i < numComp; i++) TRACELOG(LOG_INFO, "GL: Supported compressed format: 0x%x", format[i]); - - // NOTE: We don't need that much data on screen... right now... - - // TODO: Automatize extensions loading using rlLoadExtensions() and GLAD - // Actually, when rlglInit() is called in InitWindow() in core.c, - // OpenGL context has already been created and required extensions loaded - #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Get supported extensions list - GLint numExt = 0; - -#if defined(GRAPHICS_API_OPENGL_33) && !defined(GRAPHICS_API_OPENGL_21) - // NOTE: On OpenGL 3.3 VAO and NPOT are supported by default - RLGL.ExtSupported.vao = true; - - // Multiple texture extensions supported by default - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texDepth = true; - - // We get a list of available extensions and we check for some of them (compressed textures) - // NOTE: We don't need to check again supported extensions but we do (GLAD already dealt with that) - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - - // Allocate numExt strings pointers - char **extList = RL_MALLOC(sizeof(char *)*numExt); - - // Get extensions strings - for (int i = 0; i < numExt; i++) extList[i] = (char *)glGetStringi(GL_EXTENSIONS, i); - -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - // Allocate 512 strings pointers (2 KB) - const char **extList = RL_MALLOC(512*sizeof(const char *)); - - const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string - - // NOTE: We have to duplicate string because glGetString() returns a const string - int len = strlen(extensions) + 1; - char *extensionsDup = (char *)RL_CALLOC(len, sizeof(char)); - strcpy(extensionsDup, extensions); - - extList[numExt] = extensionsDup; - - for (int i = 0; i < len; i++) - { - if (extensionsDup[i] == ' ') - { - extensionsDup[i] = '\0'; - - numExt++; - extList[numExt] = &extensionsDup[i + 1]; - } - } - - // NOTE: Duplicated string (extensionsDup) must be deallocated -#endif - - TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); - - // Show supported extensions - //for (int i = 0; i < numExt; i++) TRACELOG(LOG_INFO, "Supported extension: %s", extList[i]); - - // Check required extensions - for (int i = 0; i < numExt; i++) - { -#if defined(GRAPHICS_API_OPENGL_ES2) - // Check VAO support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) - { - // The extension is supported by our hardware and driver, try to get related functions pointers - // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES"); - //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)eglGetProcAddress("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted - - if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; - } - - // Check NPOT textures support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; - - // Check texture float support - if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; - - // Check depth texture support - if ((strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0)) RLGL.ExtSupported.texDepth = true; - - if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; - if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; -#endif - // DDS texture compression support - if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; - - // ETC1 texture compression support - if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; - - // ETC2/EAC texture compression support - if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; - - // PVR texture compression support - if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; - - // ASTC texture compression support - if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; - - // Anisotropic texture filter support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) - { - RLGL.ExtSupported.texAnisoFilter = true; - glGetFloatv(0x84FF, &RLGL.ExtSupported.maxAnisotropicLevel); // GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - } - - // Clamp mirror wrap mode supported - if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; - - // Debug marker support - if (strcmp(extList[i], (const char *)"GL_EXT_debug_marker") == 0) RLGL.ExtSupported.debugMarker = true; - } - - // Free extensions pointers - RL_FREE(extList); - -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - RL_FREE(extensionsDup); // Duplicated string must be deallocated -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) TRACELOG(LOG_INFO, "GL: VAO extension detected, VAO functions initialized successfully"); - else TRACELOG(LOG_WARNING, "GL: VAO extension not found, VAO usage not supported"); - - if (RLGL.ExtSupported.texNPOT) TRACELOG(LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); - else TRACELOG(LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); -#endif - - if (RLGL.ExtSupported.texCompDXT) TRACELOG(LOG_INFO, "GL: DXT compressed textures supported"); - if (RLGL.ExtSupported.texCompETC1) TRACELOG(LOG_INFO, "GL: ETC1 compressed textures supported"); - if (RLGL.ExtSupported.texCompETC2) TRACELOG(LOG_INFO, "GL: ETC2/EAC compressed textures supported"); - if (RLGL.ExtSupported.texCompPVRT) TRACELOG(LOG_INFO, "GL: PVRT compressed textures supported"); - if (RLGL.ExtSupported.texCompASTC) TRACELOG(LOG_INFO, "GL: ASTC compressed textures supported"); - - if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(LOG_INFO, "GL: Anisotropic textures filtering supported (max: %.0fX)", RLGL.ExtSupported.maxAnisotropicLevel); - if (RLGL.ExtSupported.texMirrorClamp) TRACELOG(LOG_INFO, "GL: Mirror clamp wrap texture mode supported"); - - if (RLGL.ExtSupported.debugMarker) TRACELOG(LOG_INFO, "GL: Debug Marker supported"); - - // Initialize buffers, default shaders and default textures - //---------------------------------------------------------- // Init default white texture unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) - RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1); + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); if (RLGL.State.defaultTextureId != 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load default texture"); // Init default Shader (customized for GL 3.3 and ES2) - RLGL.State.defaultShader = LoadShaderDefault(); + rlLoadShaderDefault(); // RLGL.State.defaultShader RLGL.State.currentShader = RLGL.State.defaultShader; // Init default vertex arrays buffers - RLGL.defaultBatch = LoadRenderBatch(DEFAULT_BATCH_BUFFERS, DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.defaultBatch = rlLoadRenderBatch(DEFAULT_BATCH_BUFFERS, DEFAULT_BATCH_BUFFER_ELEMENTS); RLGL.currentBatch = &RLGL.defaultBatch; // Init stack matrices (emulating OpenGL 1.1) @@ -1740,8 +1609,7 @@ void rlglInit(int width, int height) RLGL.State.projection = MatrixIdentity(); RLGL.State.modelview = MatrixIdentity(); RLGL.State.currentMatrix = &RLGL.State.modelview; - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 // Initialize OpenGL default states //---------------------------------------------------------- @@ -1759,86 +1627,268 @@ void rlglInit(int width, int height) glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) glEnable(GL_CULL_FACE); // Enable backface culling + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + #if defined(GRAPHICS_API_OPENGL_11) // Init state: Color hints (deprecated in OpenGL 3.0+) glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) #endif - // Init state: Color/Depth buffers clear - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) - glClearDepth(1.0f); // Set clear depth value (default) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) - +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Store screen size into global variables RLGL.State.framebufferWidth = width; RLGL.State.framebufferHeight = height; - // Init texture and rectangle used on basic shapes drawing - RLGL.State.shapesTexture = GetTextureDefault(); - RLGL.State.shapesTextureRec = (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }; + TRACELOG(LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif - TRACELOG(LOG_INFO, "RLGL: Default state initialized successfully"); + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) } // Vertex Buffer Object deinitialization (memory free) void rlglClose(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - UnloadRenderBatch(RLGL.defaultBatch); + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader - UnloadShaderDefault(); // Unload default shader glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture - - TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded default texture data from VRAM (GPU)", RLGL.State.defaultTextureId); + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); #endif } -// Update and draw internal buffers -void rlglDraw(void) +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) { -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - DrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside -#endif -} +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + #if !defined(__APPLE__) + if (!gladLoadGLLoader((GLADloadproc)loader)) TRACELOG(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + #endif -// Check and log OpenGL error codes -void rlCheckErrors() { -#if defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int check = 1; - while (check) { - const GLenum err = glGetError(); - switch (err) { - case GL_NO_ERROR: - check = 0; - break; - case 0x0500: // GL_INVALID_ENUM: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); - break; - case 0x0501: //GL_INVALID_VALUE: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); - break; - case 0x0502: //GL_INVALID_OPERATION: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); - break; - case 0x0503: // GL_STACK_OVERFLOW: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); - break; - case 0x0504: // GL_STACK_UNDERFLOW: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); - break; - case 0x0505: // GL_OUT_OF_MEMORY: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); - break; - case 0x0506: // GL_INVALID_FRAMEBUFFER_OPERATION: - TRACELOG(LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); - break; - default: - TRACELOG(LOG_WARNING, "GL: Error detected: unknown error code %x", err); - break; + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(SUPPORT_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + char **extList = RL_MALLOC(numExt*sizeof(char *)); + TRACELOG(LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) + { + extList[i] = (char *)glGetStringi(GL_EXTENSIONS, i); + TRACELOG(LOG_INFO, " %s", extList[i]); + } + RL_FREE(extList); // Free extensions pointers +#endif + + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + #if !defined(__APPLE__) + // NOTE: With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans + if (GLAD_GL_EXT_texture_compression_s3tc) RLGL.ExtSupported.texCompDXT = true; // Texture compression: DXT + if (GLAD_GL_ARB_ES3_compatibility) RLGL.ExtSupported.texCompETC2 = true; // Texture compression: ETC2/EAC + #endif +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES2) + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int len = strlen(extensions) + 1; + char *extensionsDup = (char *)RL_CALLOC(len, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < len; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; } } + + TRACELOG(LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(SUPPORT_GL_DETAILS_INFO) + TRACELOG(LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(LOG_INFO, " %s", extList[i]); #endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // Web ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else + { + if ((strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) && // Standard EXT + (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0)) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + + // Check depth texture support + if ((strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0)) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(SUPPORT_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint format[32] = { 0 }; + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format); + for (int i = 0; i < capability; i++) TRACELOG(LOG_INFO, " %s", rlGetCompressedFormatName(format[i])); + + /* + // Following capabilities are only supported by OpenGL 4.3 or greater + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); + */ +#else // SUPPORT_GL_DETAILS_INFO + + // Show some basic info about GL supported features + #if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) TRACELOG(LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + #endif + if (RLGL.ExtSupported.texCompDXT) TRACELOG(LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(LOG_INFO, "GL: ASTC compressed textures supported"); +#endif // SUPPORT_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 } // Returns current OpenGL version @@ -1846,7 +1896,8 @@ int rlGetVersion(void) { #if defined(GRAPHICS_API_OPENGL_11) return OPENGL_11; -#elif defined(GRAPHICS_API_OPENGL_21) +#endif +#if defined(GRAPHICS_API_OPENGL_21) #if defined(__APPLE__) return OPENGL_33; // NOTE: Force OpenGL 3.3 on OSX #else @@ -1854,60 +1905,454 @@ int rlGetVersion(void) #endif #elif defined(GRAPHICS_API_OPENGL_33) return OPENGL_33; -#elif defined(GRAPHICS_API_OPENGL_ES2) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) return OPENGL_ES_20; #endif } +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.framebufferWidth; +#else + return 0; +#endif +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.framebufferHeight; +#else + return 0; +#endif +} + +// Get default internal shader (simple texture + tint color) +Shader rlGetShaderDefault(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + return RLGL.State.defaultShader; +#else + Shader shader = { 0 }; + return shader; +#endif +} + +// Get default internal texture (white texture) +Texture2D rlGetTextureDefault(void) +{ + Texture2D texture = { 0 }; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + texture.id = RLGL.State.defaultTextureId; + texture.width = 1; + texture.height = 1; + texture.mipmaps = 1; + texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; +#endif + return texture; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +RenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + RenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (VertexBuffer *)RL_MALLOC(numBuffers*sizeof(VertexBuffer)); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementsCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + batch.vertexBuffer[i].vCounter = 0; + batch.vertexBuffer[i].tcCounter = 0; + batch.vertexBuffer[i].cCounter = 0; + } + + TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (DrawCall *)RL_MALLOC(DEFAULT_BATCH_DRAWCALLS*sizeof(DrawCall)); + + for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = MatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = MatrixIdentity(); + } + + batch.buffersCount = numBuffers; // Record buffer count + batch.drawsCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(RenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + glDisableVertexAttribArray(3); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.buffersCount; i++) + { + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(RenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) + if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue. + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job. + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer(). + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data. + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyesCount = 1; + if (RLGL.State.stereoRender) eyesCount = 2; + + for (int eye = 0; eye < eyesCount; eye++) + { + if (eyesCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(MatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShader.id); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = MatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShader.locs[SHADER_LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShader.locs[SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShader.locs[SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShader.locs[SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawsCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and binded to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { +#if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: quadsCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); +#endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counters for next frame + batch->vertexBuffer[batch->currentBuffer].vCounter = 0; + batch->vertexBuffer[batch->currentBuffer].tcCounter = 0; + batch->vertexBuffer[batch->currentBuffer].cCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawsCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->buffersCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(RenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + // Check internal buffer overflow for a given number of vertex -bool rlCheckBufferLimit(int vCount) +// and force a RenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) { bool overflow = false; + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + vCount) >= (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) overflow = true; + if ((RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementsCount*4)) + { + overflow = true; + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + } #endif + return overflow; } -// Set debug marker -void rlSetDebugMarker(const char *text) -{ -#if defined(GRAPHICS_API_OPENGL_33) - if (RLGL.ExtSupported.debugMarker) glInsertEventMarkerEXT(0, text); -#endif -} - -// Set blending mode factor and equation -void rlSetBlendMode(int glSrcFactor, int glDstFactor, int glEquation) -{ - RLGL.State.glBlendSrcFactor = glSrcFactor; - RLGL.State.glBlendDstFactor = glDstFactor; - RLGL.State.glBlendEquation = glEquation; -} - -// Load OpenGL extensions -// NOTE: External loader function could be passed as a pointer -void rlLoadExtensions(void *loader) -{ -#if defined(GRAPHICS_API_OPENGL_33) - // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) - #if !defined(__APPLE__) - if (!gladLoadGLLoader((GLADloadproc)loader)) TRACELOG(LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); - else TRACELOG(LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); - - #if defined(GRAPHICS_API_OPENGL_21) - if (GLAD_GL_VERSION_2_1) TRACELOG(LOG_INFO, "GL: OpenGL 2.1 profile supported"); - #elif defined(GRAPHICS_API_OPENGL_33) - if (GLAD_GL_VERSION_3_3) TRACELOG(LOG_INFO, "GL: OpenGL 3.3 Core profile supported"); - else TRACELOG(LOG_ERROR, "GL: OpenGL 3.3 Core profile not supported"); - #endif - #endif - - // With GLAD, we can check if an extension is supported using the GLAD_GL_xxx booleans - //if (GLAD_GL_ARB_vertex_array_object) // Use GL_ARB_vertex_array_object -#endif -} - +// Textures data management +//----------------------------------------------------------------------------------------- // Convert image data to OpenGL texture (returns OpenGL valid Id) unsigned int rlLoadTexture(void *data, int width, int height, int format, int mipmapCount) { @@ -1917,53 +2362,49 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi // Check texture format support by OpenGL 1.1 (compressed textures not supported) #if defined(GRAPHICS_API_OPENGL_11) - if (format >= COMPRESSED_DXT1_RGB) + if (format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) { TRACELOG(LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); return id; } #else - if ((!RLGL.ExtSupported.texCompDXT) && ((format == COMPRESSED_DXT1_RGB) || (format == COMPRESSED_DXT1_RGBA) || - (format == COMPRESSED_DXT3_RGBA) || (format == COMPRESSED_DXT5_RGBA))) + if ((!RLGL.ExtSupported.texCompDXT) && ((format == PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == PIXELFORMAT_COMPRESSED_DXT5_RGBA))) { TRACELOG(LOG_WARNING, "GL: DXT compressed texture format not supported"); return id; } #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((!RLGL.ExtSupported.texCompETC1) && (format == COMPRESSED_ETC1_RGB)) + if ((!RLGL.ExtSupported.texCompETC1) && (format == PIXELFORMAT_COMPRESSED_ETC1_RGB)) { TRACELOG(LOG_WARNING, "GL: ETC1 compressed texture format not supported"); return id; } - if ((!RLGL.ExtSupported.texCompETC2) && ((format == COMPRESSED_ETC2_RGB) || (format == COMPRESSED_ETC2_EAC_RGBA))) + if ((!RLGL.ExtSupported.texCompETC2) && ((format == PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) { TRACELOG(LOG_WARNING, "GL: ETC2 compressed texture format not supported"); return id; } - if ((!RLGL.ExtSupported.texCompPVRT) && ((format == COMPRESSED_PVRT_RGB) || (format == COMPRESSED_PVRT_RGBA))) + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == PIXELFORMAT_COMPRESSED_PVRT_RGBA))) { TRACELOG(LOG_WARNING, "GL: PVRT compressed texture format not supported"); return id; } - if ((!RLGL.ExtSupported.texCompASTC) && ((format == COMPRESSED_ASTC_4x4_RGBA) || (format == COMPRESSED_ASTC_8x8_RGBA))) + if ((!RLGL.ExtSupported.texCompASTC) && ((format == PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) { TRACELOG(LOG_WARNING, "GL: ASTC compressed texture format not supported"); return id; } #endif -#endif // GRAPHICS_API_OPENGL_11 +#endif // GRAPHICS_API_OPENGL_11 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1, &id); // Generate texture id -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - //glActiveTexture(GL_TEXTURE0); // If not defined, using GL_TEXTURE0 by default (shader texture) -#endif - glBindTexture(GL_TEXTURE_2D, id); int mipWidth = width; @@ -1973,7 +2414,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi // Load the different mipmap levels for (int i = 0; i < mipmapCount; i++) { - unsigned int mipSize = GetPixelDataSize(mipWidth, mipHeight, format); + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); @@ -1982,27 +2423,27 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi if (glInternalFormat != -1) { - if (format < COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); - #if !defined(GRAPHICS_API_OPENGL_11) + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, (unsigned char *)data + mipOffset); +#if !defined(GRAPHICS_API_OPENGL_11) else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, (unsigned char *)data + mipOffset); - #endif +#endif - #if defined(GRAPHICS_API_OPENGL_33) - if (format == UNCOMPRESSED_GRAYSCALE) +#if defined(GRAPHICS_API_OPENGL_33) + if (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) { GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } - else if (format == UNCOMPRESSED_GRAY_ALPHA) + else if (format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) { - #if defined(GRAPHICS_API_OPENGL_21) +#if defined(GRAPHICS_API_OPENGL_21) GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; - #elif defined(GRAPHICS_API_OPENGL_33) +#elif defined(GRAPHICS_API_OPENGL_33) GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; - #endif +#endif glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } - #endif +#endif } mipWidth /= 2; @@ -2054,7 +2495,7 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi // Unbind current texture glBindTexture(GL_TEXTURE_2D, 0); - if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Texture created successfully (%ix%i - %i mipmaps)", id, width, height, mipmapCount); + if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i - %i mipmaps)", id, width, height, mipmapCount); else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load texture"); return id; @@ -2062,32 +2503,23 @@ unsigned int rlLoadTexture(void *data, int width, int height, int format, int mi // Load depth texture/renderbuffer (to be attached to fbo) // WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture/WEBGL_depth_texture extensions -unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderBuffer) +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) { unsigned int id = 0; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int glInternalFormat = GL_DEPTH_COMPONENT16; + // In case depth textures not supported, we force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; - if ((bits != 16) && (bits != 24) && (bits != 32)) bits = 16; + // NOTE: We let the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; - if (bits == 24) - { -#if defined(GRAPHICS_API_OPENGL_33) - glInternalFormat = GL_DEPTH_COMPONENT24; -#elif defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.maxDepthBits >= 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; +#if defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; #endif - } - - if (bits == 32) - { -#if defined(GRAPHICS_API_OPENGL_33) - glInternalFormat = GL_DEPTH_COMPONENT32; -#elif defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; -#endif - } if (!useRenderBuffer && RLGL.ExtSupported.texDepth) { @@ -2101,6 +2533,8 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(LOG_INFO, "TEXTURE: Depth texture loaded successfully"); } else { @@ -2111,6 +2545,8 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); } #endif @@ -2118,17 +2554,17 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB } // Load texture cubemap -// NOTE: Cubemap data is expected to be 6 images in a single column, +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), // expected the following convention: +X, -X, +Y, -Y, +Z, -Z unsigned int rlLoadTextureCubemap(void *data, int size, int format) { - unsigned int cubemapId = 0; + unsigned int id = 0; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int dataSize = GetPixelDataSize(size, size, format); + unsigned int dataSize = rlGetPixelDataSize(size, size, format); - glGenTextures(1, &cubemapId); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapId); + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); @@ -2138,16 +2574,34 @@ unsigned int rlLoadTextureCubemap(void *data, int size, int format) // Load cubemap faces for (unsigned int i = 0; i < 6; i++) { - if (format < COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); - else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + if (data == NULL) + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) + { + // Instead of using a sized internal texture format (GL_RGB16F, GL_RGB32F), we let the driver to choose the better format for us (GL_RGB) + if (RLGL.ExtSupported.texFloat32) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); + else TRACELOG(LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + } + else if ((format == PIXELFORMAT_UNCOMPRESSED_R32) || (format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, NULL); + } + else TRACELOG(LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, glFormat, glType, (unsigned char *)data + i*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, size, size, 0, dataSize, (unsigned char *)data + i*dataSize); + } #if defined(GRAPHICS_API_OPENGL_33) - if (format == UNCOMPRESSED_GRAYSCALE) + if (format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) { GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); } - else if (format == UNCOMPRESSED_GRAY_ALPHA) + else if (format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) { #if defined(GRAPHICS_API_OPENGL_21) GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; @@ -2172,7 +2626,10 @@ unsigned int rlLoadTextureCubemap(void *data, int size, int format) glBindTexture(GL_TEXTURE_CUBE_MAP, 0); #endif - return cubemapId; + if (id > 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; } // Update already loaded texture in GPU with new data @@ -2184,9 +2641,9 @@ void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int h unsigned int glInternalFormat, glFormat, glType; rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - if ((glInternalFormat != -1) && (format < COMPRESSED_DXT1_RGB)) + if ((glInternalFormat != -1) && (format < PIXELFORMAT_COMPRESSED_DXT1_RGB)) { - glTexSubImage2D(GL_TEXTURE_2D, 0, offsetY, offsetY, width, height, glFormat, glType, (unsigned char *)data); + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, (unsigned char *)data); } else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); } @@ -2202,43 +2659,43 @@ void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned { #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA - case UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; #if !defined(GRAPHICS_API_OPENGL_11) - case UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float #endif #elif defined(GRAPHICS_API_OPENGL_33) - case UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - case UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; - case UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 #endif - #if !defined(GRAPHICS_API_OPENGL_11) - case COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; - case COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; - case COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 - case COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - case COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - #endif default: TRACELOG(LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; } } @@ -2246,112 +2703,7 @@ void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned // Unload texture from GPU memory void rlUnloadTexture(unsigned int id) { - if (id > 0) glDeleteTextures(1, &id); -} - -// Load a texture to be used for rendering (fbo with default color and depth attachments) -// NOTE: If colorFormat or depthBits are no supported, no attachment is done -RenderTexture2D rlLoadRenderTexture(int width, int height, int format, int depthBits, bool useDepthTexture) -{ - RenderTexture2D target = { 0 }; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) - if (useDepthTexture && RLGL.ExtSupported.texDepth) target.depthTexture = true; - - // Create the framebuffer object - glGenFramebuffers(1, &target.id); - glBindFramebuffer(GL_FRAMEBUFFER, target.id); - - // Create fbo color texture attachment - //----------------------------------------------------------------------------------------------------- - if ((format != -1) && (format < COMPRESSED_DXT1_RGB)) - { - // WARNING: Some texture formats are not supported for fbo color attachment - target.texture.id = rlLoadTexture(NULL, width, height, format, 1); - target.texture.width = width; - target.texture.height = height; - target.texture.format = format; - target.texture.mipmaps = 1; - } - //----------------------------------------------------------------------------------------------------- - - // Create fbo depth renderbuffer/texture - //----------------------------------------------------------------------------------------------------- - if (depthBits > 0) - { - target.depth.id = rlLoadTextureDepth(width, height, depthBits, !useDepthTexture); - target.depth.width = width; - target.depth.height = height; - target.depth.format = 19; //DEPTH_COMPONENT_24BIT? - target.depth.mipmaps = 1; - } - //----------------------------------------------------------------------------------------------------- - - // Attach color texture and depth renderbuffer to FBO - //----------------------------------------------------------------------------------------------------- - rlRenderTextureAttach(target, target.texture.id, 0); // COLOR attachment - rlRenderTextureAttach(target, target.depth.id, 1); // DEPTH attachment - //----------------------------------------------------------------------------------------------------- - - // Check if fbo is complete with attachments (valid) - //----------------------------------------------------------------------------------------------------- - if (rlRenderTextureComplete(target)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); - //----------------------------------------------------------------------------------------------------- - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif - - return target; -} - -// Attach color buffer texture to an fbo (unloads previous attachment) -// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture -void rlRenderTextureAttach(RenderTexture2D target, unsigned int id, int attachType) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, target.id); - - if (attachType == 0) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); - else if (attachType == 1) - { - if (target.depthTexture) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, id, 0); - else glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, id); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Verify render texture is complete -bool rlRenderTextureComplete(RenderTexture target) -{ - bool result = false; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, target.id); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - switch (status) - { - case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", target.id); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", target.id); break; -#if defined(GRAPHICS_API_OPENGL_ES2) - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", target.id); break; -#endif - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", target.id); break; - default: break; - } - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - result = (status == GL_FRAMEBUFFER_COMPLETE); -#endif - - return result; + glDeleteTextures(1, &id); } // Generate mipmap data for selected texture @@ -2369,14 +2721,14 @@ void rlGenerateMipmaps(Texture2D *texture) if (texIsPOT) { // WARNING: Manual mipmap generation only works for RGBA 32bit textures! - if (texture->format == UNCOMPRESSED_R8G8B8A8) + if (texture->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) { // Retrieve texture data from VRAM - void *data = rlReadTexturePixels(*texture); + void *texData = rlReadTexturePixels(*texture); - // NOTE: data size is reallocated to fit mipmaps data + // NOTE: Texture data size is reallocated to fit mipmaps data // NOTE: CPU mipmap generation only supports RGBA 32bit data - int mipmapCount = GenerateMipmaps(data, texture->width, texture->height); + int mipmapCount = rlGenerateMipmapsData(texData, texture->width, texture->height); int size = texture->width*texture->height*4; int offset = size; @@ -2387,7 +2739,7 @@ void rlGenerateMipmaps(Texture2D *texture) // Load the mipmaps for (int level = 1; level < mipmapCount; level++) { - glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data + offset); + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)texData + offset); size = mipWidth*mipHeight*4; offset += size; @@ -2397,13 +2749,14 @@ void rlGenerateMipmaps(Texture2D *texture) } texture->mipmaps = mipmapCount + 1; - RL_FREE(data); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data + RL_FREE(texData); // Once mipmaps have been generated and data has been uploaded to GPU VRAM, we can discard RAM data TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Mipmaps generated manually on CPU side, total: %i", texture->id, texture->mipmaps); } else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps for provided texture format", texture->id); } -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) { //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorythm: GL_FASTEST, GL_NICEST, GL_DONT_CARE @@ -2424,469 +2777,73 @@ void rlGenerateMipmaps(Texture2D *texture) glBindTexture(GL_TEXTURE_2D, 0); } -// Upload vertex data into a VAO (if supported) and VBO -void rlLoadMesh(Mesh *mesh, bool dynamic) + +// Read texture pixel data +void *rlReadTexturePixels(Texture2D texture) { - if (mesh->vaoId > 0) + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, texture.id); + + // NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding. + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(texture.format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(texture.width, texture.height, texture.format); + + if ((glInternalFormat != -1) && (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB)) { - // Check if mesh has already been loaded in GPU - TRACELOG(LOG_WARNING, "VAO: [ID %i] Trying to re-load an already loaded mesh", mesh->vaoId); - return; + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); } + else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", texture.id, texture.format); - mesh->vaoId = 0; // Vertex Array Object - mesh->vboId[0] = 0; // Vertex positions VBO - mesh->vboId[1] = 0; // Vertex texcoords VBO - mesh->vboId[2] = 0; // Vertex normals VBO - mesh->vboId[3] = 0; // Vertex colors VBO - mesh->vboId[4] = 0; // Vertex tangents VBO - mesh->vboId[5] = 0; // Vertex texcoords2 VBO - mesh->vboId[6] = 0; // Vertex indices VBO - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int drawHint = GL_STATIC_DRAW; - if (dynamic) drawHint = GL_DYNAMIC_DRAW; - - if (RLGL.ExtSupported.vao) - { - // Initialize Quads VAO (Buffer A) - glGenVertexArrays(1, &mesh->vaoId); - glBindVertexArray(mesh->vaoId); - } - - // NOTE: Attributes must be uploaded considering default locations points - - // Enable vertex attributes: position (shader-location = 0) - glGenBuffers(1, &mesh->vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[0]); - glBufferData(GL_ARRAY_BUFFER, mesh->vertexCount*3*sizeof(float), mesh->vertices, drawHint); - glVertexAttribPointer(0, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(0); - - // Enable vertex attributes: texcoords (shader-location = 1) - glGenBuffers(1, &mesh->vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[1]); - glBufferData(GL_ARRAY_BUFFER, mesh->vertexCount*2*sizeof(float), mesh->texcoords, drawHint); - glVertexAttribPointer(1, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(1); - - // Enable vertex attributes: normals (shader-location = 2) - if (mesh->normals != NULL) - { - glGenBuffers(1, &mesh->vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[2]); - glBufferData(GL_ARRAY_BUFFER, mesh->vertexCount*3*sizeof(float), mesh->normals, drawHint); - glVertexAttribPointer(2, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(2); - } - else - { - // Default color vertex attribute set to WHITE - glVertexAttrib3f(2, 1.0f, 1.0f, 1.0f); - glDisableVertexAttribArray(2); - } - - // Default color vertex attribute (shader-location = 3) - if (mesh->colors != NULL) - { - glGenBuffers(1, &mesh->vboId[3]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[3]); - glBufferData(GL_ARRAY_BUFFER, mesh->vertexCount*4*sizeof(unsigned char), mesh->colors, drawHint); - glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(3); - } - else - { - // Default color vertex attribute set to WHITE - glVertexAttrib4f(3, 1.0f, 1.0f, 1.0f, 1.0f); - glDisableVertexAttribArray(3); - } - - // Default tangent vertex attribute (shader-location = 4) - if (mesh->tangents != NULL) - { - glGenBuffers(1, &mesh->vboId[4]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[4]); - glBufferData(GL_ARRAY_BUFFER, mesh->vertexCount*4*sizeof(float), mesh->tangents, drawHint); - glVertexAttribPointer(4, 4, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(4); - } - else - { - // Default tangents vertex attribute - glVertexAttrib4f(4, 0.0f, 0.0f, 0.0f, 0.0f); - glDisableVertexAttribArray(4); - } - - // Default texcoord2 vertex attribute (shader-location = 5) - if (mesh->texcoords2 != NULL) - { - glGenBuffers(1, &mesh->vboId[5]); - glBindBuffer(GL_ARRAY_BUFFER, mesh->vboId[5]); - glBufferData(GL_ARRAY_BUFFER, mesh->vertexCount*2*sizeof(float), mesh->texcoords2, drawHint); - glVertexAttribPointer(5, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(5); - } - else - { - // Default texcoord2 vertex attribute - glVertexAttrib2f(5, 0.0f, 0.0f); - glDisableVertexAttribArray(5); - } - - if (mesh->indices != NULL) - { - glGenBuffers(1, &mesh->vboId[6]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->vboId[6]); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh->triangleCount*3*sizeof(unsigned short), mesh->indices, drawHint); - } - - if (RLGL.ExtSupported.vao) - { - if (mesh->vaoId > 0) TRACELOG(LOG_INFO, "VAO: [ID %i] Mesh uploaded successfully to VRAM (GPU)", mesh->vaoId); - else TRACELOG(LOG_WARNING, "VAO: Failed to load mesh to VRAM (GPU)"); - } - else - { - TRACELOG(LOG_INFO, "VBO: Mesh uploaded successfully to VRAM (GPU)"); - } -#endif -} - -// Load a new attributes buffer -unsigned int rlLoadAttribBuffer(unsigned int vaoId, int shaderLoc, void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int drawHint = GL_STATIC_DRAW; - if (dynamic) drawHint = GL_DYNAMIC_DRAW; - - if (RLGL.ExtSupported.vao) glBindVertexArray(vaoId); - - glGenBuffers(1, &id); - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferData(GL_ARRAY_BUFFER, size, buffer, drawHint); - glVertexAttribPointer(shaderLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(shaderLoc); - - if (RLGL.ExtSupported.vao) glBindVertexArray(0); -#endif - - return id; -} - -// Update vertex or index data on GPU (upload new data to one buffer) -void rlUpdateMesh(Mesh mesh, int buffer, int count) -{ - rlUpdateMeshAt(mesh, buffer, count, 0); -} - -// Update vertex or index data on GPU, at index -// WARNING: error checking is in place that will cause the data to not be -// updated if offset + size exceeds what the buffer can hold -void rlUpdateMeshAt(Mesh mesh, int buffer, int count, int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Activate mesh VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(mesh.vaoId); - - switch (buffer) - { - case 0: // Update vertices (vertex position) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]); - if (index == 0 && count >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, count*3*sizeof(float), mesh.vertices, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.vertexCount) break; - else glBufferSubData(GL_ARRAY_BUFFER, index*3*sizeof(float), count*3*sizeof(float), mesh.vertices); - - } break; - case 1: // Update texcoords (vertex texture coordinates) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]); - if (index == 0 && count >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, count*2*sizeof(float), mesh.texcoords, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.vertexCount) break; - else glBufferSubData(GL_ARRAY_BUFFER, index*2*sizeof(float), count*2*sizeof(float), mesh.texcoords); - - } break; - case 2: // Update normals (vertex normals) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]); - if (index == 0 && count >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, count*3*sizeof(float), mesh.normals, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.vertexCount) break; - else glBufferSubData(GL_ARRAY_BUFFER, index*3*sizeof(float), count*3*sizeof(float), mesh.normals); - - } break; - case 3: // Update colors (vertex colors) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]); - if (index == 0 && count >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, count*4*sizeof(unsigned char), mesh.colors, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.vertexCount) break; - else glBufferSubData(GL_ARRAY_BUFFER, index*4*sizeof(unsigned char), count*4*sizeof(unsigned char), mesh.colors); - - } break; - case 4: // Update tangents (vertex tangents) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]); - if (index == 0 && count >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, count*4*sizeof(float), mesh.tangents, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.vertexCount) break; - else glBufferSubData(GL_ARRAY_BUFFER, index*4*sizeof(float), count*4*sizeof(float), mesh.tangents); - - } break; - case 5: // Update texcoords2 (vertex second texture coordinates) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); - if (index == 0 && count >= mesh.vertexCount) glBufferData(GL_ARRAY_BUFFER, count*2*sizeof(float), mesh.texcoords2, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.vertexCount) break; - else glBufferSubData(GL_ARRAY_BUFFER, index*2*sizeof(float), count*2*sizeof(float), mesh.texcoords2); - - } break; - case 6: // Update indices (triangle index buffer) - { - // the * 3 is because each triangle has 3 indices - unsigned short *indices = mesh.indices; - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.vboId[6]); - - if (index == 0 && count >= mesh.triangleCount) glBufferData(GL_ELEMENT_ARRAY_BUFFER, count*3*sizeof(*indices), indices, GL_DYNAMIC_DRAW); - else if (index + count >= mesh.triangleCount) break; - else glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, index*3*sizeof(*indices), count*3*sizeof(*indices), indices); - - } break; - default: break; - } - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - - // Another option would be using buffer mapping... - //mesh.vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // Now we can modify vertices - //glUnmapBuffer(GL_ARRAY_BUFFER); -#endif -} - -// Draw a 3d mesh with material and transform -void rlDrawMesh(Mesh mesh, Material material, Matrix transform) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, material.maps[MAP_DIFFUSE].texture.id); - - // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model - glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array - glEnableClientState(GL_TEXTURE_COORD_ARRAY); // Enable texture coords array - if (mesh.normals != NULL) glEnableClientState(GL_NORMAL_ARRAY); // Enable normals array - if (mesh.colors != NULL) glEnableClientState(GL_COLOR_ARRAY); // Enable colors array - - glVertexPointer(3, GL_FLOAT, 0, mesh.vertices); // Pointer to vertex coords array - glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords); // Pointer to texture coords array - if (mesh.normals != NULL) glNormalPointer(GL_FLOAT, 0, mesh.normals); // Pointer to normals array - if (mesh.colors != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, mesh.colors); // Pointer to colors array - - rlPushMatrix(); - rlMultMatrixf(MatrixToFloat(transform)); - rlColor4ub(material.maps[MAP_DIFFUSE].color.r, material.maps[MAP_DIFFUSE].color.g, material.maps[MAP_DIFFUSE].color.b, material.maps[MAP_DIFFUSE].color.a); - - if (mesh.indices != NULL) glDrawElements(GL_TRIANGLES, mesh.triangleCount*3, GL_UNSIGNED_SHORT, mesh.indices); - else glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount); - rlPopMatrix(); - - glDisableClientState(GL_VERTEX_ARRAY); // Disable vertex array - glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Disable texture coords array - if (mesh.normals != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable normals array - if (mesh.colors != NULL) glDisableClientState(GL_NORMAL_ARRAY); // Disable colors array - - glDisable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); #endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Bind shader program - glUseProgram(material.shader.id); +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(texture.width, texture.height); - // Matrices and other values required by shader - //----------------------------------------------------- - // Calculate and send to shader model matrix (used by PBR shader) - if (material.shader.locs[LOC_MATRIX_MODEL] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_MODEL], transform); + // TODO: Create depth texture/renderbuffer for fbo? - // Upload to shader material.colDiffuse - if (material.shader.locs[LOC_COLOR_DIFFUSE] != -1) - glUniform4f(material.shader.locs[LOC_COLOR_DIFFUSE], (float)material.maps[MAP_DIFFUSE].color.r/255.0f, - (float)material.maps[MAP_DIFFUSE].color.g/255.0f, - (float)material.maps[MAP_DIFFUSE].color.b/255.0f, - (float)material.maps[MAP_DIFFUSE].color.a/255.0f); + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); - // Upload to shader material.colSpecular (if available) - if (material.shader.locs[LOC_COLOR_SPECULAR] != -1) - glUniform4f(material.shader.locs[LOC_COLOR_SPECULAR], (float)material.maps[MAP_SPECULAR].color.r/255.0f, - (float)material.maps[MAP_SPECULAR].color.g/255.0f, - (float)material.maps[MAP_SPECULAR].color.b/255.0f, - (float)material.maps[MAP_SPECULAR].color.a/255.0f); + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0); - if (material.shader.locs[LOC_MATRIX_VIEW] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_VIEW], RLGL.State.modelview); - if (material.shader.locs[LOC_MATRIX_PROJECTION] != -1) SetShaderValueMatrix(material.shader, material.shader.locs[LOC_MATRIX_PROJECTION], RLGL.State.projection); + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(texture.width, texture.height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - // At this point the modelview matrix just contains the view matrix (camera) - // That's because BeginMode3D() sets it an no model-drawing function modifies it, all use rlPushMatrix() and rlPopMatrix() - Matrix matView = RLGL.State.modelview; // View matrix (camera) - Matrix matProjection = RLGL.State.projection; // Projection matrix (perspective) + glBindFramebuffer(GL_FRAMEBUFFER, 0); - // TODO: Consider possible transform matrices in the RLGL.State.stack - // Is this the right order? or should we start with the first stored matrix instead of the last one? - //Matrix matStackTransform = MatrixIdentity(); - //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = MatrixMultiply(RLGL.State.stack[i], matStackTransform); - - // Transform to camera-space coordinates - Matrix matModelView = MatrixMultiply(transform, MatrixMultiply(RLGL.State.transform, matView)); - //----------------------------------------------------- - - // Bind active texture maps (if available) - for (int i = 0; i < MAX_MATERIAL_MAPS; i++) - { - if (material.maps[i].texture.id > 0) - { - glActiveTexture(GL_TEXTURE0 + i); - if ((i == MAP_IRRADIANCE) || (i == MAP_PREFILTER) || (i == MAP_CUBEMAP)) glBindTexture(GL_TEXTURE_CUBE_MAP, material.maps[i].texture.id); - else glBindTexture(GL_TEXTURE_2D, material.maps[i].texture.id); - - glUniform1i(material.shader.locs[LOC_MAP_DIFFUSE + i], i); - } - } - - // Bind vertex array objects (or VBOs) - if (RLGL.ExtSupported.vao) glBindVertexArray(mesh.vaoId); - else - { - // Bind mesh VBO data: vertex position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[0]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_POSITION]); - - // Bind mesh VBO data: vertex texcoords (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[1]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TEXCOORD01]); - - // Bind mesh VBO data: vertex normals (shader-location = 2, if available) - if (material.shader.locs[LOC_VERTEX_NORMAL] != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[2]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_NORMAL]); - } - - // Bind mesh VBO data: vertex colors (shader-location = 3, if available) - if (material.shader.locs[LOC_VERTEX_COLOR] != -1) - { - if (mesh.vboId[3] != 0) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[3]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_COLOR]); - } - else - { - // Set default value for unused attribute - // NOTE: Required when using default shader and no VAO support - glVertexAttrib4f(material.shader.locs[LOC_VERTEX_COLOR], 1.0f, 1.0f, 1.0f, 1.0f); - glDisableVertexAttribArray(material.shader.locs[LOC_VERTEX_COLOR]); - } - } - - // Bind mesh VBO data: vertex tangents (shader-location = 4, if available) - if (material.shader.locs[LOC_VERTEX_TANGENT] != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[4]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TANGENT], 4, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TANGENT]); - } - - // Bind mesh VBO data: vertex texcoords2 (shader-location = 5, if available) - if (material.shader.locs[LOC_VERTEX_TEXCOORD02] != -1) - { - glBindBuffer(GL_ARRAY_BUFFER, mesh.vboId[5]); - glVertexAttribPointer(material.shader.locs[LOC_VERTEX_TEXCOORD02], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(material.shader.locs[LOC_VERTEX_TEXCOORD02]); - } - - if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.vboId[6]); - } - - int eyesCount = 1; -#if defined(SUPPORT_VR_SIMULATOR) - if (RLGL.Vr.stereoRender) eyesCount = 2; + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); #endif - for (int eye = 0; eye < eyesCount; eye++) - { - if (eyesCount == 1) RLGL.State.modelview = matModelView; - #if defined(SUPPORT_VR_SIMULATOR) - else SetStereoView(eye, matProjection, matModelView); - #endif - - // Calculate model-view-projection matrix (MVP) - Matrix matMVP = MatrixMultiply(RLGL.State.modelview, RLGL.State.projection); // Transform to screen-space coordinates - - // Send combined model-view-projection matrix to shader - glUniformMatrix4fv(material.shader.locs[LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); - - // Draw call! - if (mesh.indices != NULL) glDrawElements(GL_TRIANGLES, mesh.triangleCount*3, GL_UNSIGNED_SHORT, 0); // Indexed vertices draw - else glDrawArrays(GL_TRIANGLES, 0, mesh.vertexCount); - } - - // Unbind all binded texture maps - for (int i = 0; i < MAX_MATERIAL_MAPS; i++) - { - glActiveTexture(GL_TEXTURE0 + i); // Set shader active texture - if ((i == MAP_IRRADIANCE) || (i == MAP_PREFILTER) || (i == MAP_CUBEMAP)) glBindTexture(GL_TEXTURE_CUBE_MAP, 0); - else glBindTexture(GL_TEXTURE_2D, 0); // Unbind current active texture - } - - // Unind vertex array objects (or VBOs) - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - else - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - if (mesh.indices != NULL) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - // Unbind shader program - glUseProgram(0); - - // Restore RLGL.State.projection/RLGL.State.modelview matrices - // NOTE: In stereo rendering matrices are being modified to fit every eye - RLGL.State.projection = matProjection; - RLGL.State.modelview = matView; -#endif + return pixels; } -// Unload mesh data from CPU and GPU -void rlUnloadMesh(Mesh mesh) -{ - RL_FREE(mesh.vertices); - RL_FREE(mesh.texcoords); - RL_FREE(mesh.normals); - RL_FREE(mesh.colors); - RL_FREE(mesh.tangents); - RL_FREE(mesh.texcoords2); - RL_FREE(mesh.indices); - - RL_FREE(mesh.animVertices); - RL_FREE(mesh.animNormals); - RL_FREE(mesh.boneWeights); - RL_FREE(mesh.boneIds); - - rlDeleteBuffers(mesh.vboId[0]); // vertex - rlDeleteBuffers(mesh.vboId[1]); // texcoords - rlDeleteBuffers(mesh.vboId[2]); // normals - rlDeleteBuffers(mesh.vboId[3]); // colors - rlDeleteBuffers(mesh.vboId[4]); // tangents - rlDeleteBuffers(mesh.vboId[5]); // texcoords2 - rlDeleteBuffers(mesh.vboId[6]); // indices - - rlDeleteVertexArrays(mesh.vaoId); -} // Read screen pixel data (color buffer) unsigned char *rlReadScreenPixels(int width, int height) @@ -2917,197 +2874,360 @@ unsigned char *rlReadScreenPixels(int width, int height) return imgData; // NOTE: image data should be freed } -// Read texture pixel data -void *rlReadTexturePixels(Texture2D texture) +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(int width, int height) { - void *pixels = NULL; + unsigned int fboId = 0; -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - glBindTexture(GL_TEXTURE_2D, texture.id); - - // NOTE: Using texture.id, we can retrieve some texture info (but not on OpenGL ES 2.0) - // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE - //int width, height, format; - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); - - // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding. - // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting. - // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) - // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(texture.format, &glInternalFormat, &glFormat, &glType); - unsigned int size = GetPixelDataSize(texture.width, texture.height, texture.format); - - if ((glInternalFormat != -1) && (texture.format < COMPRESSED_DXT1_RGB)) - { - pixels = (unsigned char *)RL_MALLOC(size); - glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); - } - else TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", texture.id, texture.format); - - glBindTexture(GL_TEXTURE_2D, 0); +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer #endif + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; #if defined(GRAPHICS_API_OPENGL_ES2) - // glGetTexImage() is not available on OpenGL ES 2.0 - // Texture2D width and height are required on OpenGL ES 2.0. There is no way to get it from texture id. - // Two possible Options: - // 1 - Bind texture to color fbo attachment and glReadPixels() - // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() - // We are using Option 1, just need to care for texture format on retrieval - // NOTE: This behaviour could be conditioned by graphic driver... - RenderTexture2D fbo = rlLoadRenderTexture(texture.width, texture.height, UNCOMPRESSED_R8G8B8A8, 16, false); - - glBindFramebuffer(GL_FRAMEBUFFER, fbo.id); - glBindTexture(GL_TEXTURE_2D, 0); - - // Attach our texture to FBO - // NOTE: Previoust attached texture is automatically detached - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.id, 0); - - // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format - pixels = (unsigned char *)RL_MALLOC(GetPixelDataSize(texture.width, texture.height, UNCOMPRESSED_R8G8B8A8)); - glReadPixels(0, 0, texture.width, texture.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Re-attach internal FBO color texture before deleting it - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo.texture.id, 0); + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } glBindFramebuffer(GL_FRAMEBUFFER, 0); - // Clean up temporal fbo - rlDeleteRenderTextures(fbo); + result = (status == GL_FRAMEBUFFER_COMPLETE); #endif - return pixels; + return result; } -//---------------------------------------------------------------------------------- -// Module Functions Definition - Shaders Functions -// NOTE: Those functions are exposed directly to the user in raylib.h -//---------------------------------------------------------------------------------- - -// Get default internal texture (white texture) -Texture2D GetTextureDefault(void) +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) { - Texture2D texture = { 0 }; +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(SUPPORT_RENDER_TEXTURES_HINT) + + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_RENDERBUFFER) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer. + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - texture.id = RLGL.State.defaultTextureId; - texture.width = 1; - texture.height = 1; - texture.mipmaps = 1; - texture.format = UNCOMPRESSED_R8G8B8A8; + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); #endif - return texture; + + return id; } -// Get texture to draw shapes (RAII) -Texture2D GetShapesTexture(void) +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(void *buffer, int size, bool dynamic) { + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update GPU buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(int bufferId, void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, bufferId); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +void rlDrawVertexArrayElements(int offset, int count, void *buffer) +{ + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short*)buffer + offset); +} + +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +void rlDrawVertexArrayElementsInstanced(int offset, int count, void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (unsigned short*)buffer + offset, instances); +#endif +} + #if defined(GRAPHICS_API_OPENGL_11) - Texture2D texture = { 0 }; - return texture; -#else - return RLGL.State.shapesTexture; -#endif -} - -// Get texture rectangle to draw shapes -Rectangle GetShapesTextureRec(void) +void rlEnableStatePointer(int vertexAttribType, void *buffer) { -#if defined(GRAPHICS_API_OPENGL_11) - Rectangle rec = { 0 }; - return rec; -#else - return RLGL.State.shapesTextureRec; -#endif + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } } -// Define default texture used to draw shapes -void SetShapesTexture(Texture2D texture, Rectangle source) +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glGenVertexArrays(1, &vaoId); + } +#endif + return vaoId; +} + +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, void *pointer) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.shapesTexture = texture; - RLGL.State.shapesTextureRec = source; + glVertexAttribPointer(index, compSize, type, normalized, stride, pointer); #endif } -// Get default shader -Shader GetShaderDefault(void) +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - return RLGL.State.defaultShader; -#else - Shader shader = { 0 }; - return shader; + glVertexAttribDivisor(index, divisor); #endif } -// Load shader from files and bind default locations -// NOTE: If shader string is NULL, using default vertex/fragment shaders -Shader LoadShader(const char *vsFileName, const char *fsFileName) +void rlUnloadVertexArray(unsigned int vaoId) { - Shader shader = { 0 }; - - // NOTE: Shader.locs is allocated by LoadShaderCode() - - char *vShaderStr = NULL; - char *fShaderStr = NULL; - - if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); - if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); - - shader = LoadShaderCode(vShaderStr, fShaderStr); - - if (vShaderStr != NULL) RL_FREE(vShaderStr); - if (fShaderStr != NULL) RL_FREE(fShaderStr); - - return shader; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif } +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- // Load shader from code strings // NOTE: If shader string is NULL, using default vertex/fragment shaders -Shader LoadShaderCode(const char *vsCode, const char *fsCode) +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) { - Shader shader = { 0 }; - shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); - - // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + unsigned int id = 0; #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) unsigned int vertexShaderId = RLGL.State.defaultVShaderId; unsigned int fragmentShaderId = RLGL.State.defaultFShaderId; - if (vsCode != NULL) vertexShaderId = CompileShader(vsCode, GL_VERTEX_SHADER); - if (fsCode != NULL) fragmentShaderId = CompileShader(fsCode, GL_FRAGMENT_SHADER); + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) shader = RLGL.State.defaultShader; + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShader.id; else { - shader.id = LoadShaderProgram(vertexShaderId, fragmentShaderId); + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); - if (vertexShaderId != RLGL.State.defaultVShaderId) glDeleteShader(vertexShaderId); - if (fragmentShaderId != RLGL.State.defaultFShaderId) glDeleteShader(fragmentShaderId); - - if (shader.id == 0) + if (vertexShaderId != RLGL.State.defaultVShaderId) { - TRACELOG(LOG_WARNING, "SHADER: Failed to load custom shader code"); - shader = RLGL.State.defaultShader; + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // Detach shader before deletion to make sure memory is freed + glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); } - // After shader loading, we TRY to set default location names - if (shader.id > 0) SetShaderDefaultLocations(&shader); + if (id == 0) + { + TRACELOG(LOG_WARNING, "SHADER: Failed to load custom shader code"); + id = RLGL.State.defaultShader.id; + } } // Get available shader uniforms // NOTE: This information is useful for debug... int uniformCount = -1; - glGetProgramiv(shader.id, GL_ACTIVE_UNIFORMS, &uniformCount); + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); for (int i = 0; i < uniformCount; i++) { @@ -3117,792 +3237,71 @@ Shader LoadShaderCode(const char *vsCode, const char *fsCode) GLenum type = GL_ZERO; // Get the name of the uniforms - glGetActiveUniform(shader.id, i, sizeof(name) - 1, &namelen, &num, &type, name); + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); name[namelen] = 0; - TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", shader.id, name, glGetUniformLocation(shader.id, name)); + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); } #endif - return shader; + return id; } -// Unload shader from GPU memory (VRAM) -void UnloadShader(Shader shader) -{ - if (shader.id > 0) - { - rlDeleteShader(shader.id); - TRACELOG(LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", shader.id); - } - - RL_FREE(shader.locs); -} - -// Begin custom shader mode -void BeginShaderMode(Shader shader) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.State.currentShader.id != shader.id) - { - rlglDraw(); - RLGL.State.currentShader = shader; - } -#endif -} - -// End custom shader mode (returns to default shader) -void EndShaderMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - BeginShaderMode(RLGL.State.defaultShader); -#endif -} - -// Get shader uniform location -int GetShaderLocation(Shader shader, const char *uniformName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetUniformLocation(shader.id, uniformName); - - if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shader.id, uniformName); - else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shader.id, uniformName, location); -#endif - return location; -} - -// Set shader uniform value -void SetShaderValue(Shader shader, int uniformLoc, const void *value, int uniformType) -{ - SetShaderValueV(shader, uniformLoc, value, uniformType, 1); -} - -// Set shader uniform value vector -void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int uniformType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - switch (uniformType) - { - case UNIFORM_FLOAT: glUniform1fv(uniformLoc, count, (float *)value); break; - case UNIFORM_VEC2: glUniform2fv(uniformLoc, count, (float *)value); break; - case UNIFORM_VEC3: glUniform3fv(uniformLoc, count, (float *)value); break; - case UNIFORM_VEC4: glUniform4fv(uniformLoc, count, (float *)value); break; - case UNIFORM_INT: glUniform1iv(uniformLoc, count, (int *)value); break; - case UNIFORM_IVEC2: glUniform2iv(uniformLoc, count, (int *)value); break; - case UNIFORM_IVEC3: glUniform3iv(uniformLoc, count, (int *)value); break; - case UNIFORM_IVEC4: glUniform4iv(uniformLoc, count, (int *)value); break; - case UNIFORM_SAMPLER2D: glUniform1iv(uniformLoc, count, (int *)value); break; - default: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to set uniform, data type not recognized", shader.id); - } - - //glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set -#endif -} - - -// Set shader uniform value (matrix 4x4) -void SetShaderValueMatrix(Shader shader, int uniformLoc, Matrix mat) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - glUniformMatrix4fv(uniformLoc, 1, false, MatrixToFloat(mat)); - - //glUseProgram(0); -#endif -} - -// Set shader uniform value for texture -void SetShaderValueTexture(Shader shader, int uniformLoc, Texture2D texture) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(shader.id); - - glUniform1i(uniformLoc, texture.id); - - //glUseProgram(0); -#endif -} - -// Set a custom projection matrix (replaces internal projection matrix) -void SetMatrixProjection(Matrix projection) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projection = projection; -#endif -} - -// Return internal projection matrix -Matrix GetMatrixProjection(void) { -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_PROJECTION_MATRIX,mat); - Matrix m; - m.m0 = mat[0]; m.m1 = mat[1]; m.m2 = mat[2]; m.m3 = mat[3]; - m.m4 = mat[4]; m.m5 = mat[5]; m.m6 = mat[6]; m.m7 = mat[7]; - m.m8 = mat[8]; m.m9 = mat[9]; m.m10 = mat[10]; m.m11 = mat[11]; - m.m12 = mat[12]; m.m13 = mat[13]; m.m14 = mat[14]; m.m15 = mat[15]; - return m; -#else - return RLGL.State.projection; -#endif -# -} - -// Set a custom modelview matrix (replaces internal modelview matrix) -void SetMatrixModelview(Matrix view) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.modelview = view; -#endif -} - -// Return internal modelview matrix -Matrix GetMatrixModelview(void) -{ - Matrix matrix = MatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, mat); - matrix.m0 = mat[0]; matrix.m1 = mat[1]; matrix.m2 = mat[2]; matrix.m3 = mat[3]; - matrix.m4 = mat[4]; matrix.m5 = mat[5]; matrix.m6 = mat[6]; matrix.m7 = mat[7]; - matrix.m8 = mat[8]; matrix.m9 = mat[9]; matrix.m10 = mat[10]; matrix.m11 = mat[11]; - matrix.m12 = mat[12]; matrix.m13 = mat[13]; matrix.m14 = mat[14]; matrix.m15 = mat[15]; -#else - matrix = RLGL.State.modelview; -#endif - return matrix; -} - -// Generate cubemap texture from HDR texture -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureCubemap(Shader shader, Texture2D map, int size) -{ - Texture2D cubemap = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader - // Other locations should be setup externally in shader before calling the function - - // Set up depth face culling and cubemap seamless - glDisable(GL_CULL_FACE); -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Flag not supported on OpenGL ES 2.0 -#endif - - // Setup framebuffer - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); -#if defined(GRAPHICS_API_OPENGL_33) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); -#elif defined(GRAPHICS_API_OPENGL_ES2) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size, size); -#endif - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); - - // Set up cubemap to render and attach to framebuffer - // NOTE: Faces are stored as 32 bit floating point values - glGenTextures(1, &cubemap.id); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - for (unsigned int i = 0; i < 6; i++) - { -#if defined(GRAPHICS_API_OPENGL_33) - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL); -#elif defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.texFloat32) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); -#endif - } - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if defined(GRAPHICS_API_OPENGL_33) - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 -#endif - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Create projection and different views for each face - Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); - Matrix fboViews[6] = { - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) - }; - - // Convert HDR equirectangular environment map to cubemap equivalent - glUseProgram(shader.id); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, map.id); - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); - - // Note: don't forget to configure the viewport to the capture dimensions - glViewport(0, 0, size, size); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - for (int i = 0; i < 6; i++) - { - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemap.id, 0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawCube(); - } - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - //glEnable(GL_CULL_FACE); - - // NOTE: Texture2D is a GL_TEXTURE_CUBE_MAP, not a GL_TEXTURE_2D! - cubemap.width = size; - cubemap.height = size; - cubemap.mipmaps = 1; - cubemap.format = UNCOMPRESSED_R32G32B32; -#endif - return cubemap; -} - -// Generate irradiance texture using cubemap data -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size) -{ - Texture2D irradiance = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader - // Other locations should be setup externally in shader before calling the function - - // Setup framebuffer - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); - - // Create an irradiance cubemap, and re-scale capture FBO to irradiance scale - glGenTextures(1, &irradiance.id); - glBindTexture(GL_TEXTURE_CUBE_MAP, irradiance.id); - for (unsigned int i = 0; i < 6; i++) - { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); - } - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Create projection (transposed) and different views for each face - Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); - Matrix fboViews[6] = { - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) - }; - - // Solve diffuse integral by convolution to create an irradiance cubemap - glUseProgram(shader.id); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); - - // Note: don't forget to configure the viewport to the capture dimensions - glViewport(0, 0, size, size); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - for (int i = 0; i < 6; i++) - { - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradiance.id, 0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawCube(); - } - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - - irradiance.width = size; - irradiance.height = size; - irradiance.mipmaps = 1; - //irradiance.format = UNCOMPRESSED_R16G16B16; -#endif - return irradiance; -} - -// Generate prefilter texture using cubemap data -// TODO: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size) -{ - Texture2D prefilter = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) // || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: SetShaderDefaultLocations() already setups locations for projection and view Matrix in shader - // Other locations should be setup externally in shader before calling the function - // TODO: Locations should be taken out of this function... too shader dependant... - int roughnessLoc = GetShaderLocation(shader, "roughness"); - - // Setup framebuffer - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo); - - // Create a prefiltered HDR environment map - glGenTextures(1, &prefilter.id); - glBindTexture(GL_TEXTURE_CUBE_MAP, prefilter.id); - for (unsigned int i = 0; i < 6; i++) - { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL); - } - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Generate mipmaps for the prefiltered HDR texture - glGenerateMipmap(GL_TEXTURE_CUBE_MAP); - - // Create projection (transposed) and different views for each face - Matrix fboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); - Matrix fboViews[6] = { - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ -1.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, 1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }), - MatrixLookAt((Vector3){ 0.0f, 0.0f, 0.0f }, (Vector3){ 0.0f, 0.0f, -1.0f }, (Vector3){ 0.0f, -1.0f, 0.0f }) - }; - - // Prefilter HDR and store data into mipmap levels - glUseProgram(shader.id); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id); - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_PROJECTION], fboProjection); - - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - #define MAX_MIPMAP_LEVELS 5 // Max number of prefilter texture mipmaps - - for (int mip = 0; mip < MAX_MIPMAP_LEVELS; mip++) - { - // Resize framebuffer according to mip-level size. - unsigned int mipWidth = size*(int)powf(0.5f, (float)mip); - unsigned int mipHeight = size*(int)powf(0.5f, (float)mip); - - glBindRenderbuffer(GL_RENDERBUFFER, rbo); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight); - glViewport(0, 0, mipWidth, mipHeight); - - float roughness = (float)mip/(float)(MAX_MIPMAP_LEVELS - 1); - glUniform1f(roughnessLoc, roughness); - - for (int i = 0; i < 6; i++) - { - SetShaderValueMatrix(shader, shader.locs[LOC_MATRIX_VIEW], fboViews[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilter.id, mip); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawCube(); - } - } - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Reset viewport dimensions to default - glViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - - prefilter.width = size; - prefilter.height = size; - //prefilter.mipmaps = 1 + (int)floor(log(size)/log(2)); - //prefilter.format = UNCOMPRESSED_R16G16B16; -#endif - return prefilter; -} - -// Generate BRDF texture using cubemap data -// NOTE: OpenGL ES 2.0 does not support GL_RGB16F texture format, neither GL_DEPTH_COMPONENT24 -// TODO: Review implementation: https://github.com/HectorMF/BRDFGenerator -Texture2D GenTextureBRDF(Shader shader, int size) -{ - Texture2D brdf = { 0 }; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Generate BRDF convolution texture - glGenTextures(1, &brdf.id); - glBindTexture(GL_TEXTURE_2D, brdf.id); -#if defined(GRAPHICS_API_OPENGL_33) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL); -#elif defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.texFloat32) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL); -#endif - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // Render BRDF LUT into a quad using FBO - unsigned int fbo, rbo; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &rbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBindRenderbuffer(GL_RENDERBUFFER, rbo); -#if defined(GRAPHICS_API_OPENGL_33) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size); -#elif defined(GRAPHICS_API_OPENGL_ES2) - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, size, size); -#endif - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdf.id, 0); - - glViewport(0, 0, size, size); - glUseProgram(shader.id); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - GenDrawQuad(); - - // Unbind framebuffer and textures - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Unload framebuffer but keep color texture - glDeleteRenderbuffers(1, &rbo); - glDeleteFramebuffers(1, &fbo); - - // Reset viewport dimensions to default - glViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - - brdf.width = size; - brdf.height = size; - brdf.mipmaps = 1; - brdf.format = UNCOMPRESSED_R32G32B32; -#endif - return brdf; -} - -// Begin blending mode (alpha, additive, multiplied) -// NOTE: Only 3 blending modes supported, default blend mode is alpha -void BeginBlendMode(int mode) -{ - if (RLGL.State.currentBlendMode != mode) - { - rlglDraw(); - - switch (mode) - { - case BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; - case BLEND_CUSTOM: glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); break; - default: break; - } - - RLGL.State.currentBlendMode = mode; - } -} - -// End blending mode (reset to default: alpha blending) -void EndBlendMode(void) -{ - BeginBlendMode(BLEND_ALPHA); -} - -#if defined(SUPPORT_VR_SIMULATOR) -// Init VR simulator for selected device parameters -// NOTE: It modifies the global variable: RLGL.Vr.stereoFbo -void InitVrSimulator(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Initialize framebuffer and textures for stereo rendering - // NOTE: Screen size should match HMD aspect ratio - RLGL.Vr.stereoFbo = rlLoadRenderTexture(RLGL.State.framebufferWidth, RLGL.State.framebufferHeight, UNCOMPRESSED_R8G8B8A8, 24, false); - - RLGL.Vr.simulatorReady = true; -#else - TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); -#endif -} - -// Update VR tracking (position and orientation) and camera -// NOTE: Camera (position, target, up) gets update with head tracking information -void UpdateVrTracking(Camera *camera) -{ - // TODO: Simulate 1st person camera system -} - -// Close VR simulator for current device -void CloseVrSimulator(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.Vr.simulatorReady) rlDeleteRenderTextures(RLGL.Vr.stereoFbo); // Unload stereo framebuffer and texture -#endif -} - -// Set stereo rendering configuration parameters -void SetVrConfiguration(VrDeviceInfo hmd, Shader distortion) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Reset RLGL.Vr.config for a new values assignment - memset(&RLGL.Vr.config, 0, sizeof(RLGL.Vr.config)); - - // Assign distortion shader - RLGL.Vr.config.distortionShader = distortion; - - // Compute aspect ratio - float aspect = ((float)hmd.hResolution*0.5f)/(float)hmd.vResolution; - - // Compute lens parameters - float lensShift = (hmd.hScreenSize*0.25f - hmd.lensSeparationDistance*0.5f)/hmd.hScreenSize; - float leftLensCenter[2] = { 0.25f + lensShift, 0.5f }; - float rightLensCenter[2] = { 0.75f - lensShift, 0.5f }; - float leftScreenCenter[2] = { 0.25f, 0.5f }; - float rightScreenCenter[2] = { 0.75f, 0.5f }; - - // Compute distortion scale parameters - // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] - float lensRadius = fabsf(-1.0f - 4.0f*lensShift); - float lensRadiusSq = lensRadius*lensRadius; - float distortionScale = hmd.lensDistortionValues[0] + - hmd.lensDistortionValues[1]*lensRadiusSq + - hmd.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + - hmd.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; - - TRACELOGD("RLGL: VR device configuration:"); - TRACELOGD(" > Distortion Scale: %f", distortionScale); - - float normScreenWidth = 0.5f; - float normScreenHeight = 1.0f; - float scaleIn[2] = { 2.0f/normScreenWidth, 2.0f/normScreenHeight/aspect }; - float scale[2] = { normScreenWidth*0.5f/distortionScale, normScreenHeight*0.5f*aspect/distortionScale }; - - TRACELOGD(" > Distortion Shader: LeftLensCenter = { %f, %f }", leftLensCenter[0], leftLensCenter[1]); - TRACELOGD(" > Distortion Shader: RightLensCenter = { %f, %f }", rightLensCenter[0], rightLensCenter[1]); - TRACELOGD(" > Distortion Shader: Scale = { %f, %f }", scale[0], scale[1]); - TRACELOGD(" > Distortion Shader: ScaleIn = { %f, %f }", scaleIn[0], scaleIn[1]); - - // Fovy is normally computed with: 2*atan2f(hmd.vScreenSize, 2*hmd.eyeToScreenDistance) - // ...but with lens distortion it is increased (see Oculus SDK Documentation) - //float fovy = 2.0f*atan2f(hmd.vScreenSize*0.5f*distortionScale, hmd.eyeToScreenDistance); // Really need distortionScale? - float fovy = 2.0f*(float)atan2f(hmd.vScreenSize*0.5f, hmd.eyeToScreenDistance); - - // Compute camera projection matrices - float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] - Matrix proj = MatrixPerspective(fovy, aspect, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR); - RLGL.Vr.config.eyesProjection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); - RLGL.Vr.config.eyesProjection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); - - // Compute camera transformation matrices - // NOTE: Camera movement might seem more natural if we model the head. - // Our axis of rotation is the base of our head, so we might want to add - // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. - RLGL.Vr.config.eyesViewOffset[0] = MatrixTranslate(-hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); - RLGL.Vr.config.eyesViewOffset[1] = MatrixTranslate(hmd.interpupillaryDistance*0.5f, 0.075f, 0.045f); - - // Compute eyes Viewports - RLGL.Vr.config.eyeViewportRight[2] = hmd.hResolution/2; - RLGL.Vr.config.eyeViewportRight[3] = hmd.vResolution; - - RLGL.Vr.config.eyeViewportLeft[0] = hmd.hResolution/2; - RLGL.Vr.config.eyeViewportLeft[1] = 0; - RLGL.Vr.config.eyeViewportLeft[2] = hmd.hResolution/2; - RLGL.Vr.config.eyeViewportLeft[3] = hmd.vResolution; - - if (RLGL.Vr.config.distortionShader.id > 0) - { - // Update distortion shader with lens and distortion-scale parameters - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "leftLensCenter"), leftLensCenter, UNIFORM_VEC2); - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "rightLensCenter"), rightLensCenter, UNIFORM_VEC2); - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "leftScreenCenter"), leftScreenCenter, UNIFORM_VEC2); - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "rightScreenCenter"), rightScreenCenter, UNIFORM_VEC2); - - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "scale"), scale, UNIFORM_VEC2); - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "scaleIn"), scaleIn, UNIFORM_VEC2); - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "hmdWarpParam"), hmd.lensDistortionValues, UNIFORM_VEC4); - SetShaderValue(RLGL.Vr.config.distortionShader, GetShaderLocation(RLGL.Vr.config.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, UNIFORM_VEC4); - } -#endif -} - -// Detect if VR simulator is running -bool IsVrSimulatorReady(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - return RLGL.Vr.simulatorReady; -#else - return false; -#endif -} - -// Enable/Disable VR experience (device or simulator) -void ToggleVrMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.Vr.simulatorReady = !RLGL.Vr.simulatorReady; - - if (!RLGL.Vr.simulatorReady) - { - RLGL.Vr.stereoRender = false; - - // Reset viewport and default projection-modelview matrices - rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - RLGL.State.projection = MatrixOrtho(0.0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight, 0.0, 0.0, 1.0); - RLGL.State.modelview = MatrixIdentity(); - } - else RLGL.Vr.stereoRender = true; -#endif -} - -// Begin VR drawing configuration -void BeginVrDrawing(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.Vr.simulatorReady) - { - rlEnableRenderTexture(RLGL.Vr.stereoFbo.id); // Setup framebuffer for stereo rendering - //glEnable(GL_FRAMEBUFFER_SRGB); // Enable SRGB framebuffer (only if required) - - //glViewport(0, 0, buffer.width, buffer.height); // Useful if rendering to separate framebuffers (every eye) - rlClearScreenBuffers(); // Clear current framebuffer - - RLGL.Vr.stereoRender = true; - } -#endif -} - -// End VR drawing process (and desktop mirror) -void EndVrDrawing(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.Vr.simulatorReady) - { - RLGL.Vr.stereoRender = false; // Disable stereo render - - rlDisableRenderTexture(); // Unbind current framebuffer - - rlClearScreenBuffers(); // Clear current framebuffer - - // Set viewport to default framebuffer size (screen size) - rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - - // Let rlgl reconfigure internal matrices - rlMatrixMode(RL_PROJECTION); // Enable internal projection matrix - rlLoadIdentity(); // Reset internal projection matrix - rlOrtho(0.0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight, 0.0, 0.0, 1.0); // Recalculate internal RLGL.State.projection matrix - rlMatrixMode(RL_MODELVIEW); // Enable internal modelview matrix - rlLoadIdentity(); // Reset internal modelview matrix - - // Draw RenderTexture (RLGL.Vr.stereoFbo) using distortion shader if available - if (RLGL.Vr.config.distortionShader.id > 0) RLGL.State.currentShader = RLGL.Vr.config.distortionShader; - else RLGL.State.currentShader = GetShaderDefault(); - - rlEnableTexture(RLGL.Vr.stereoFbo.texture.id); - - rlPushMatrix(); - rlBegin(RL_QUADS); - rlColor4ub(255, 255, 255, 255); - rlNormal3f(0.0f, 0.0f, 1.0f); - - // Bottom-left corner for texture and quad - rlTexCoord2f(0.0f, 1.0f); - rlVertex2f(0.0f, 0.0f); - - // Bottom-right corner for texture and quad - rlTexCoord2f(0.0f, 0.0f); - rlVertex2f(0.0f, (float)RLGL.Vr.stereoFbo.texture.height); - - // Top-right corner for texture and quad - rlTexCoord2f(1.0f, 0.0f); - rlVertex2f((float)RLGL.Vr.stereoFbo.texture.width, (float)RLGL.Vr.stereoFbo.texture.height); - - // Top-left corner for texture and quad - rlTexCoord2f(1.0f, 1.0f); - rlVertex2f((float)RLGL.Vr.stereoFbo.texture.width, 0.0f); - rlEnd(); - rlPopMatrix(); - - rlDisableTexture(); - - // Update and draw render texture fbo with distortion to backbuffer - DrawRenderBatch(RLGL.currentBatch); - - // Restore RLGL.State.defaultShader - RLGL.State.currentShader = RLGL.State.defaultShader; - - // Reset viewport and default projection-modelview matrices - rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - RLGL.State.projection = MatrixOrtho(0.0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight, 0.0, 0.0, 1.0); - RLGL.State.modelview = MatrixIdentity(); - - rlDisableDepthTest(); - } -#endif -} -#endif // SUPPORT_VR_SIMULATOR - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Compile custom shader and return shader id -static unsigned int CompileShader(const char *shaderStr, int type) +unsigned int rlCompileShader(const char *shaderCode, int type) { - unsigned int shader = glCreateShader(type); - glShaderSource(shader, 1, &shaderStr, NULL); + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); GLint success = 0; glCompileShader(shader); glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (success != GL_TRUE) + if (success == GL_FALSE) { - TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile shader code", shader); + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + //case GL_COMPUTE_SHADER: + default: break; + } + int maxLength = 0; - int length; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); -#if defined(_MSC_VER) - char *log = RL_MALLOC(maxLength); -#else - char log[maxLength]; -#endif - glGetShaderInfoLog(shader, maxLength, &length, log); - - TRACELOG(LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); - -#if defined(_MSC_VER) - RL_FREE(log); -#endif + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } } - else TRACELOG(LOG_INFO, "SHADER: [ID %i] Compiled successfully", shader); + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + //case GL_COMPUTE_SHADER: + default: break; + } + } +#endif return shader; } // Load custom shader strings and return program id -static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) { unsigned int program = 0; @@ -3934,50 +3333,379 @@ static unsigned int LoadShaderProgram(unsigned int vShaderId, unsigned int fShad TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); int maxLength = 0; - int length; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); -#if defined(_MSC_VER) - char *log = RL_MALLOC(maxLength); -#else - char log[maxLength]; -#endif - glGetProgramInfoLog(program, maxLength, &length, log); + if (maxLength > 0) + { + int length = 0; + char *log = RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } - TRACELOG(LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - -#if defined(_MSC_VER) - RL_FREE(log); -#endif glDeleteProgram(program); program = 0; } - else TRACELOG(LOG_INFO, "SHADER: [ID %i] Program loaded successfully", program); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); #endif return program; } +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + TRACELOG(LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + if (location == -1) TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + else TRACELOG(LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + case SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glUniformMatrix4fv(locIndex, 1, false, MatrixToFloat(mat)); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) if (RLGL.State.activeTextureId[i] == textureId) return; + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < MAX_BATCH_ACTIVE_TEXTURES; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active +void rlSetShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShader.id != shader.id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShader = shader; + } +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Return internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; matrix.m1 = mat[1]; matrix.m2 = mat[2]; matrix.m3 = mat[3]; + matrix.m4 = mat[4]; matrix.m5 = mat[5]; matrix.m6 = mat[6]; matrix.m7 = mat[7]; + matrix.m8 = mat[8]; matrix.m9 = mat[9]; matrix.m10 = mat[10]; matrix.m11 = mat[11]; + matrix.m12 = mat[12]; matrix.m13 = mat[13]; matrix.m14 = mat[14]; matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Return internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; m.m1 = mat[1]; m.m2 = mat[2]; m.m3 = mat[3]; + m.m4 = mat[4]; m.m5 = mat[5]; m.m6 = mat[6]; m.m7 = mat[7]; + m.m8 = mat[8]; m.m9 = mat[9]; m.m10 = mat[10]; m.m11 = mat[11]; + m.m12 = mat[12]; m.m13 = mat[13]; m.m14 = mat[14]; m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = MatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = MatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = MatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a 1x1 XY quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a 1x1 3D cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) // Load default shader (just vertex positioning and texture coloring) // NOTE: This shader program is used for internal buffers -static Shader LoadShaderDefault(void) +// NOTE: It uses global variable: RLGL.State.defaultShader +static void rlLoadShaderDefault(void) { - Shader shader = { 0 }; - shader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); + RLGL.State.defaultShader.locs = (int *)RL_CALLOC(MAX_SHADER_LOCATIONS, sizeof(int)); // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; + for (int i = 0; i < MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShader.locs[i] = -1; // Vertex shader directly defined, no external file required - const char *defaultVShaderStr = + const char *vShaderDefault = #if defined(GRAPHICS_API_OPENGL_21) "#version 120 \n" -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) "attribute vec3 vertexPosition; \n" "attribute vec2 vertexTexCoord; \n" "attribute vec4 vertexColor; \n" @@ -3990,6 +3718,14 @@ static Shader LoadShaderDefault(void) "in vec4 vertexColor; \n" "out vec2 fragTexCoord; \n" "out vec4 fragColor; \n" +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" #endif "uniform mat4 mvp; \n" "void main() \n" @@ -4000,98 +3736,71 @@ static Shader LoadShaderDefault(void) "} \n"; // Fragment shader directly defined, no external file required - const char *defaultFShaderStr = + const char *fShaderDefault = #if defined(GRAPHICS_API_OPENGL_21) "#version 120 \n" -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // precision required for OpenGL ES2 (WebGL) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) "varying vec2 fragTexCoord; \n" "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; #elif defined(GRAPHICS_API_OPENGL_33) "#version 330 \n" "in vec2 fragTexCoord; \n" "in vec4 fragColor; \n" "out vec4 finalColor; \n" -#endif "uniform sampler2D texture0; \n" "uniform vec4 colDiffuse; \n" "void main() \n" "{ \n" -#if defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_21) - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0 - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_33) " vec4 texelColor = texture(texture0, fragTexCoord); \n" " finalColor = texelColor*colDiffuse*fragColor; \n" -#endif "} \n"; +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif // NOTE: Compiled vertex/fragment shaders are kept for re-use - RLGL.State.defaultVShaderId = CompileShader(defaultVShaderStr, GL_VERTEX_SHADER); // Compile default vertex shader - RLGL.State.defaultFShaderId = CompileShader(defaultFShaderStr, GL_FRAGMENT_SHADER); // Compile default fragment shader + RLGL.State.defaultVShaderId = rlCompileShader(vShaderDefault, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(fShaderDefault, GL_FRAGMENT_SHADER); // Compile default fragment shader - shader.id = LoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + RLGL.State.defaultShader.id = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); - if (shader.id > 0) + if (RLGL.State.defaultShader.id > 0) { - TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", shader.id); + TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShader.id); // Set default shader locations: attributes locations - shader.locs[LOC_VERTEX_POSITION] = glGetAttribLocation(shader.id, "vertexPosition"); - shader.locs[LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(shader.id, "vertexTexCoord"); - shader.locs[LOC_VERTEX_COLOR] = glGetAttribLocation(shader.id, "vertexColor"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexPosition"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexTexCoord"); + RLGL.State.defaultShader.locs[SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShader.id, "vertexColor"); // Set default shader locations: uniform locations - shader.locs[LOC_MATRIX_MVP] = glGetUniformLocation(shader.id, "mvp"); - shader.locs[LOC_COLOR_DIFFUSE] = glGetUniformLocation(shader.id, "colDiffuse"); - shader.locs[LOC_MAP_DIFFUSE] = glGetUniformLocation(shader.id, "texture0"); - - // NOTE: We could also use below function but in case DEFAULT_ATTRIB_* points are - // changed for external custom shaders, we just use direct bindings above - //SetShaderDefaultLocations(&shader); + RLGL.State.defaultShader.locs[SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShader.id, "mvp"); + RLGL.State.defaultShader.locs[SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShader.id, "colDiffuse"); + RLGL.State.defaultShader.locs[SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShader.id, "texture0"); } - else TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", shader.id); - - return shader; -} - -// Get location handlers to for shader attributes and uniforms -// NOTE: If any location is not found, loc point becomes -1 -static void SetShaderDefaultLocations(Shader *shader) -{ - // NOTE: Default shader attrib locations have been fixed before linking: - // vertex position location = 0 - // vertex texcoord location = 1 - // vertex normal location = 2 - // vertex color location = 3 - // vertex tangent location = 4 - // vertex texcoord2 location = 5 - - // Get handles to GLSL input attibute locations - shader->locs[LOC_VERTEX_POSITION] = glGetAttribLocation(shader->id, DEFAULT_SHADER_ATTRIB_NAME_POSITION); - shader->locs[LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(shader->id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - shader->locs[LOC_VERTEX_TEXCOORD02] = glGetAttribLocation(shader->id, DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); - shader->locs[LOC_VERTEX_NORMAL] = glGetAttribLocation(shader->id, DEFAULT_SHADER_ATTRIB_NAME_NORMAL); - shader->locs[LOC_VERTEX_TANGENT] = glGetAttribLocation(shader->id, DEFAULT_SHADER_ATTRIB_NAME_TANGENT); - shader->locs[LOC_VERTEX_COLOR] = glGetAttribLocation(shader->id, DEFAULT_SHADER_ATTRIB_NAME_COLOR); - - // Get handles to GLSL uniform locations (vertex shader) - shader->locs[LOC_MATRIX_MVP] = glGetUniformLocation(shader->id, "mvp"); - shader->locs[LOC_MATRIX_PROJECTION] = glGetUniformLocation(shader->id, "projection"); - shader->locs[LOC_MATRIX_VIEW] = glGetUniformLocation(shader->id, "view"); - - // Get handles to GLSL uniform locations (fragment shader) - shader->locs[LOC_COLOR_DIFFUSE] = glGetUniformLocation(shader->id, "colDiffuse"); - shader->locs[LOC_MAP_DIFFUSE] = glGetUniformLocation(shader->id, "texture0"); - shader->locs[LOC_MAP_SPECULAR] = glGetUniformLocation(shader->id, "texture1"); - shader->locs[LOC_MAP_NORMAL] = glGetUniformLocation(shader->id, "texture2"); + else TRACELOG(LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShader.id); } // Unload default shader -static void UnloadShaderDefault(void) +// NOTE: It uses global variable: RLGL.State.defaultShader +static void rlUnloadShaderDefault(void) { glUseProgram(0); @@ -4103,495 +3812,97 @@ static void UnloadShaderDefault(void) glDeleteProgram(RLGL.State.defaultShader.id); RL_FREE(RLGL.State.defaultShader.locs); + + TRACELOG(LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShader.id); } -// Load render batch -static RenderBatch LoadRenderBatch(int numBuffers, int bufferElements) +#if defined(SUPPORT_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static char *rlGetCompressedFormatName(int format) { - RenderBatch batch = { 0 }; - - // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) - //-------------------------------------------------------------------------------------------- - batch.vertexBuffer = (VertexBuffer *)RL_MALLOC(sizeof(VertexBuffer)*numBuffers); - - for (int i = 0; i < numBuffers; i++) + static char compName[64] = { 0 }; + memset(compName, 0, 64); + + switch (format) { - batch.vertexBuffer[i].elementsCount = bufferElements; - - batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad - batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad -#if defined(GRAPHICS_API_OPENGL_33) - batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) -#elif defined(GRAPHICS_API_OPENGL_ES2) - batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) -#endif - - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; - for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; - for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; - - int k = 0; - - // Indices can be initialized right now - for (int j = 0; j < (6*bufferElements); j += 6) - { - batch.vertexBuffer[i].indices[j] = 4*k; - batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; - batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 3] = 4*k; - batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; - - k++; - } - - batch.vertexBuffer[i].vCounter = 0; - batch.vertexBuffer[i].tcCounter = 0; - batch.vertexBuffer[i].cCounter = 0; + // GL_EXT_texture_compression_s3tc + case 0x83F0: strcpy(compName, "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"); break; + case 0x83F1: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"); break; + case 0x83F2: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"); break; + case 0x83F3: strcpy(compName, "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"); break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: strcpy(compName, "GL_COMPRESSED_RGB_FXT1_3DFX"); break; + case 0x86B1: strcpy(compName, "GL_COMPRESSED_RGBA_FXT1_3DFX"); break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: strcpy(compName, "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"); break; + case 0x8C01: strcpy(compName, "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"); break; + case 0x8C02: strcpy(compName, "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"); break; + case 0x8C03: strcpy(compName, "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"); break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: strcpy(compName, "GL_ETC1_RGB8_OES"); break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: strcpy(compName, "GL_COMPRESSED_RED_RGTC1"); break; + case 0x8DBC: strcpy(compName, "GL_COMPRESSED_SIGNED_RED_RGTC1"); break; + case 0x8DBD: strcpy(compName, "GL_COMPRESSED_RG_RGTC2"); break; + case 0x8DBE: strcpy(compName, "GL_COMPRESSED_SIGNED_RG_RGTC2"); break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: strcpy(compName, "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"); break; + case 0x8E8D: strcpy(compName, "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"); break; + case 0x8E8E: strcpy(compName, "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"); break; + case 0x8E8F: strcpy(compName, "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"); break; + // GL_ARB_ES3_compatibility + case 0x9274: strcpy(compName, "GL_COMPRESSED_RGB8_ETC2"); break; + case 0x9275: strcpy(compName, "GL_COMPRESSED_SRGB8_ETC2"); break; + case 0x9276: strcpy(compName, "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"); break; + case 0x9277: strcpy(compName, "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"); break; + case 0x9278: strcpy(compName, "GL_COMPRESSED_RGBA8_ETC2_EAC"); break; + case 0x9279: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"); break; + case 0x9270: strcpy(compName, "GL_COMPRESSED_R11_EAC"); break; + case 0x9271: strcpy(compName, "GL_COMPRESSED_SIGNED_R11_EAC"); break; + case 0x9272: strcpy(compName, "GL_COMPRESSED_RG11_EAC"); break; + case 0x9273: strcpy(compName, "GL_COMPRESSED_SIGNED_RG11_EAC"); break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"); break; + case 0x93B1: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"); break; + case 0x93B2: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"); break; + case 0x93B3: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"); break; + case 0x93B4: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"); break; + case 0x93B5: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"); break; + case 0x93B6: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"); break; + case 0x93B7: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"); break; + case 0x93B8: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"); break; + case 0x93B9: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"); break; + case 0x93BA: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"); break; + case 0x93BB: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"); break; + case 0x93BC: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"); break; + case 0x93BD: strcpy(compName, "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"); break; + case 0x93D0: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"); break; + case 0x93D1: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"); break; + case 0x93D2: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"); break; + case 0x93D3: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"); break; + case 0x93D4: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"); break; + case 0x93D5: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"); break; + case 0x93D6: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"); break; + case 0x93D7: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"); break; + case 0x93D8: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"); break; + case 0x93D9: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"); break; + case 0x93DA: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"); break; + case 0x93DB: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"); break; + case 0x93DC: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"); break; + case 0x93DD: strcpy(compName, "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"); break; + default: strcpy(compName, "GL_COMPRESSED_UNKNOWN"); break; } - TRACELOG(LOG_INFO, "RLGL: Internal vertex buffers initialized successfully in RAM (CPU)"); - //-------------------------------------------------------------------------------------------- - - // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs - //-------------------------------------------------------------------------------------------- - for (int i = 0; i < numBuffers; i++) - { - if (RLGL.ExtSupported.vao) - { - // Initialize Quads VAO - glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); - glBindVertexArray(batch.vertexBuffer[i].vaoId); - } - - // Quads - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShader.locs[LOC_VERTEX_POSITION]); - glVertexAttribPointer(RLGL.State.currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - - // Vertex texcoord buffer (shader-location = 1) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShader.locs[LOC_VERTEX_TEXCOORD01]); - glVertexAttribPointer(RLGL.State.currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - - // Vertex color buffer (shader-location = 3) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShader.locs[LOC_VERTEX_COLOR]); - glVertexAttribPointer(RLGL.State.currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - // Fill index buffer - glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); -#if defined(GRAPHICS_API_OPENGL_33) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#elif defined(GRAPHICS_API_OPENGL_ES2) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif - } - - TRACELOG(LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully"); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - //-------------------------------------------------------------------------------------------- - - // Init draw calls tracking system - //-------------------------------------------------------------------------------------------- - batch.draws = (DrawCall *)RL_MALLOC(DEFAULT_BATCH_DRAWCALLS*sizeof(DrawCall)); - - for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) - { - batch.draws[i].mode = RL_QUADS; - batch.draws[i].vertexCount = 0; - batch.draws[i].vertexAlignment = 0; - //batch.draws[i].vaoId = 0; - //batch.draws[i].shaderId = 0; - batch.draws[i].textureId = RLGL.State.defaultTextureId; - //batch.draws[i].RLGL.State.projection = MatrixIdentity(); - //batch.draws[i].RLGL.State.modelview = MatrixIdentity(); - } - - batch.buffersCount = numBuffers; // Record buffer count - batch.drawsCounter = 1; // Reset draws counter - batch.currentDepth = -1.0f; // Reset depth value - //-------------------------------------------------------------------------------------------- - - return batch; + return compName; } - -// Draw render batch -// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) -static void DrawRenderBatch(RenderBatch *batch) -{ - // Update batch vertex buffers - //------------------------------------------------------------------------------------------------------------ - // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) - // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (change flag required) - if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) - { - // Activate elements VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - - // Vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer - - // Texture coordinates buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer - - // Colors buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glBufferSubData(GL_ARRAY_BUFFER, 0, batch->vertexBuffer[batch->currentBuffer].vCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementsCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer - - // NOTE: glMapBuffer() causes sync issue. - // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job. - // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer(). - // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new - // allocated pointer immediately even if GPU is still working with the previous data. - - // Another option: map the buffer object into client's memory - // Probably this code could be moved somewhere else... - // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // if (batch->vertexBuffer[batch->currentBuffer].vertices) - // { - // Update vertex data - // } - // glUnmapBuffer(GL_ARRAY_BUFFER); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - } - //------------------------------------------------------------------------------------------------------------ - - // Draw batch vertex buffers (considering VR stereo if required) - //------------------------------------------------------------------------------------------------------------ - Matrix matProjection = RLGL.State.projection; - Matrix matModelView = RLGL.State.modelview; - - int eyesCount = 1; -#if defined(SUPPORT_VR_SIMULATOR) - if (RLGL.Vr.stereoRender) eyesCount = 2; -#endif - - for (int eye = 0; eye < eyesCount; eye++) - { -#if defined(SUPPORT_VR_SIMULATOR) - if (eyesCount == 2) SetStereoView(eye, matProjection, matModelView); -#endif - - // Draw buffers - if (batch->vertexBuffer[batch->currentBuffer].vCounter > 0) - { - // Set current shader and upload current MVP matrix - glUseProgram(RLGL.State.currentShader.id); - - // Create modelview-projection matrix - Matrix matMVP = MatrixMultiply(RLGL.State.modelview, RLGL.State.projection); - - glUniformMatrix4fv(RLGL.State.currentShader.locs[LOC_MATRIX_MVP], 1, false, MatrixToFloat(matMVP)); - glUniform4f(RLGL.State.currentShader.locs[LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1i(RLGL.State.currentShader.locs[LOC_MAP_DIFFUSE], 0); // Provided value refers to the texture unit (active) - - // TODO: Support additional texture units on custom shader - //if (RLGL.State.currentShader->locs[LOC_MAP_SPECULAR] > 0) glUniform1i(RLGL.State.currentShader.locs[LOC_MAP_SPECULAR], 1); - //if (RLGL.State.currentShader->locs[LOC_MAP_NORMAL] > 0) glUniform1i(RLGL.State.currentShader.locs[LOC_MAP_NORMAL], 2); - - // NOTE: Right now additional map textures not considered for default buffers drawing - - int vertexOffset = 0; - - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - else - { - // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glVertexAttribPointer(RLGL.State.currentShader.locs[LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShader.locs[LOC_VERTEX_POSITION]); - - // Bind vertex attrib: texcoord (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glVertexAttribPointer(RLGL.State.currentShader.locs[LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShader.locs[LOC_VERTEX_TEXCOORD01]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glVertexAttribPointer(RLGL.State.currentShader.locs[LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShader.locs[LOC_VERTEX_COLOR]); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - } - - glActiveTexture(GL_TEXTURE0); - - for (int i = 0; i < batch->drawsCounter; i++) - { - glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); - - // TODO: Find some way to bind additional textures --> Use global texture IDs? Register them on draw[i]? - //if (RLGL.State.currentShader->locs[LOC_MAP_SPECULAR] > 0) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, textureUnit1_id); } - //if (RLGL.State.currentShader->locs[LOC_MAP_SPECULAR] > 0) { glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, textureUnit2_id); } - - if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); - else - { -#if defined(GRAPHICS_API_OPENGL_33) - // We need to define the number of indices to be processed: quadsCount*6 - // NOTE: The final parameter tells the GPU the offset in bytes from the - // start of the index buffer to the location of the first index to process - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); -#elif defined(GRAPHICS_API_OPENGL_ES2) - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); -#endif - } - - vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); - } - - if (!RLGL.ExtSupported.vao) - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO - - glUseProgram(0); // Unbind shader program - } - //------------------------------------------------------------------------------------------------------------ - - // Reset batch buffers - //------------------------------------------------------------------------------------------------------------ - // Reset vertex counters for next frame - batch->vertexBuffer[batch->currentBuffer].vCounter = 0; - batch->vertexBuffer[batch->currentBuffer].tcCounter = 0; - batch->vertexBuffer[batch->currentBuffer].cCounter = 0; - - // Reset depth for next draw - batch->currentDepth = -1.0f; - - // Restore projection/modelview matrices - RLGL.State.projection = matProjection; - RLGL.State.modelview = matModelView; - - // Reset RLGL.currentBatch->draws array - for (int i = 0; i < DEFAULT_BATCH_DRAWCALLS; i++) - { - batch->draws[i].mode = RL_QUADS; - batch->draws[i].vertexCount = 0; - batch->draws[i].textureId = RLGL.State.defaultTextureId; - } - - batch->drawsCounter = 1; - //------------------------------------------------------------------------------------------------------------ - - // Change to next buffer in the list (in case of multi-buffering) - batch->currentBuffer++; - if (batch->currentBuffer >= batch->buffersCount) batch->currentBuffer = 0; -} - -// Unload default internal buffers vertex data from CPU and GPU -static void UnloadRenderBatch(RenderBatch batch) -{ - // Unbind everything - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - glDisableVertexAttribArray(0); - glDisableVertexAttribArray(1); - glDisableVertexAttribArray(2); - glDisableVertexAttribArray(3); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - // Unload all vertex buffers data - for (int i = 0; i < batch.buffersCount; i++) - { - // Delete VBOs from GPU (VRAM) - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); - - // Delete VAOs from GPU (VRAM) - if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); - - // Free vertex arrays memory from CPU (RAM) - RL_FREE(batch.vertexBuffer[i].vertices); - RL_FREE(batch.vertexBuffer[i].texcoords); - RL_FREE(batch.vertexBuffer[i].colors); - RL_FREE(batch.vertexBuffer[i].indices); - } - - // Unload arrays - RL_FREE(batch.vertexBuffer); - RL_FREE(batch.draws); -} - -// Set the active render batch for rlgl -static void SetRenderBatchActive(RenderBatch *batch) -{ - DrawRenderBatch(RLGL.currentBatch); - RLGL.currentBatch = batch; -} - -// Set default render batch for rlgl -static void SetRenderBatchDefault(void) -{ - DrawRenderBatch(RLGL.currentBatch); - RLGL.currentBatch = &RLGL.defaultBatch; -} - -// Renders a 1x1 XY quad in NDC -static void GenDrawQuad(void) -{ - unsigned int quadVAO = 0; - unsigned int quadVBO = 0; - - float vertices[] = { - // Positions // Texture Coords - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - }; - - // Set up plane VAO - glGenVertexArrays(1, &quadVAO); - glGenBuffers(1, &quadVBO); - glBindVertexArray(quadVAO); - - // Fill buffer - glBindBuffer(GL_ARRAY_BUFFER, quadVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); - - // Link vertex attributes - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); - - // Draw quad - glBindVertexArray(quadVAO); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); - - glDeleteBuffers(1, &quadVBO); - glDeleteVertexArrays(1, &quadVAO); -} - -// Renders a 1x1 3D cube in NDC -static void GenDrawCube(void) -{ - unsigned int cubeVAO = 0; - unsigned int cubeVBO = 0; - - float vertices[] = { - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f - }; - - // Set up cube VAO - glGenVertexArrays(1, &cubeVAO); - glGenBuffers(1, &cubeVBO); - - // Fill buffer - glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - // Link vertex attributes - glBindVertexArray(cubeVAO); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - // Draw cube - glBindVertexArray(cubeVAO); - glDrawArrays(GL_TRIANGLES, 0, 36); - glBindVertexArray(0); - - glDeleteBuffers(1, &cubeVBO); - glDeleteVertexArrays(1, &cubeVAO); -} - -#if defined(SUPPORT_VR_SIMULATOR) -// Set internal projection and modelview matrix depending on eyes tracking data -static void SetStereoView(int eye, Matrix matProjection, Matrix matModelView) -{ - Matrix eyeProjection = matProjection; - Matrix eyeModelView = matModelView; - - // Setup viewport and projection/modelview matrices using tracking data - rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); - - // Apply view offset to modelview matrix - eyeModelView = MatrixMultiply(matModelView, RLGL.Vr.config.eyesViewOffset[eye]); - - // Set current eye projection matrix - eyeProjection = RLGL.Vr.config.eyesProjection[eye]; - - SetMatrixModelview(eyeModelView); - SetMatrixProjection(eyeProjection); -} -#endif // SUPPORT_VR_SIMULATOR +#endif // SUPPORT_GL_DETAILS_INFO #endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 #if defined(GRAPHICS_API_OPENGL_11) // Mipmaps data is generated after image data // NOTE: Only works with RGBA (4 bytes) data! -static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) +static int rlGenerateMipmapsData(unsigned char *data, int baseWidth, int baseHeight) { int mipmapCount = 1; // Required mipmap levels count (including base level) int width = baseWidth; @@ -4601,8 +3912,8 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) // Count mipmap levels required while ((width != 1) && (height != 1)) { - if (width != 1) width /= 2; - if (height != 1) height /= 2; + width /= 2; + height /= 2; TRACELOGD("TEXTURE: Next mipmap size: %i x %i", width, height); @@ -4617,7 +3928,7 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) unsigned char *temp = RL_REALLOC(data, size); if (temp != NULL) data = temp; - else TRACELOG(LOG_WARNING, "TEXTURE: Failed to allocate required mipmaps memory"); + else TRACELOG(LOG_WARNING, "TEXTURE: Failed to re-allocate required mipmaps memory"); width = baseWidth; height = baseHeight; @@ -4643,7 +3954,7 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) for (int mip = 1; mip < mipmapCount; mip++) { - mipmap = GenNextMipmap(image, width, height); + mipmap = rlGenNextMipmapData(image, width, height); offset += (width*height*4); // Size of last mipmap j = 0; @@ -4674,7 +3985,7 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) } // Manual mipmap generation (basic scaling algorithm) -static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight) +static Color *rlGenNextMipmapData(Color *srcData, int srcWidth, int srcHeight) { int x2, y2; Color prow, pcol; @@ -4714,83 +4025,38 @@ static Color *GenNextMipmap(Color *srcData, int srcWidth, int srcHeight) return mipmap; } -#endif - -#if defined(RLGL_STANDALONE) -// Load text data from file, returns a '\0' terminated string -// NOTE: text chars array should be freed manually -char *LoadFileText(const char *fileName) -{ - char *text = NULL; - - if (fileName != NULL) - { - FILE *textFile = fopen(fileName, "rt"); - - if (textFile != NULL) - { - // WARNING: When reading a file as 'text' file, - // text mode causes carriage return-linefeed translation... - // ...but using fseek() should return correct byte-offset - fseek(textFile, 0, SEEK_END); - int size = ftell(textFile); - fseek(textFile, 0, SEEK_SET); - - if (size > 0) - { - text = (char *)RL_MALLOC((size + 1)*sizeof(char)); - int count = fread(text, sizeof(char), size, textFile); - - // WARNING: \r\n is converted to \n on reading, so, - // read bytes count gets reduced by the number of lines - if (count < size) text = RL_REALLOC(text, count + 1); - - // Zero-terminate the string - text[count] = '\0'; - - TRACELOG(LOG_INFO, "FILEIO: [%s] Text file loaded successfully", fileName); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); - - fclose(textFile); - } - else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); - } - else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); - - return text; -} +#endif // GRAPHICS_API_OPENGL_11 // Get pixel data size in bytes (image or texture) // NOTE: Size depends on pixel format -int GetPixelDataSize(int width, int height, int format) +static int rlGetPixelDataSize(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_R32: bpp = 32; 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; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; default: break; } @@ -4800,12 +4066,10 @@ int GetPixelDataSize(int width, int height, int format) // if texture is smaller, minimum dataSize is 8 or 16 if ((width < 4) && (height < 4)) { - if ((format >= COMPRESSED_DXT1_RGB) && (format < COMPRESSED_DXT3_RGBA)) dataSize = 8; - else if ((format >= COMPRESSED_DXT3_RGBA) && (format < COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; } return dataSize; } -#endif // RLGL_STANDALONE - #endif // RLGL_IMPLEMENTATION diff --git a/raylib/shapes.c b/raylib/shapes.c index 2777cf6..5ed7acb 100644 --- a/raylib/shapes.c +++ b/raylib/shapes.c @@ -4,17 +4,13 @@ * * CONFIGURATION: * -* #define SUPPORT_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! -* * #define SUPPORT_QUADS_DRAW_MODE * Use QUADS instead of TRIANGLES for drawing when possible. * Some lines-based shapes could still use lines * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -62,7 +58,8 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- -// ... +Texture2D texShapes = { 1, 1, 1, 1, 7 }; // Texture used on shapes drawing (usually a white pixel) +Rectangle texShapesRec = { 0, 0, 1, 1 }; // Texture source rectangle used on shapes drawing //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -73,6 +70,15 @@ static float EaseCubicInOut(float t, float b, float c, float d); // Cubic eas // Module Functions Definition //---------------------------------------------------------------------------------- +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +void SetShapesTexture(Texture2D texture, Rectangle source) +{ + texShapes = texture; + texShapesRec = source; +} + // Draw a pixel void DrawPixel(int posX, int posY, Color color) { @@ -116,45 +122,18 @@ void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) // Draw a line defining thickness void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color) { - if (startPos.x > endPos.x) + Vector2 delta = {endPos.x-startPos.x, endPos.y-startPos.y}; + float length = sqrtf(delta.x*delta.x + delta.y*delta.y); + + if (length > 0 && thick > 0) { - Vector2 tempPos = startPos; - startPos = endPos; - endPos = tempPos; + float scale = thick/(2*length); + Vector2 radius = {-scale*delta.y, scale*delta.x}; + Vector2 strip[] = {{startPos.x-radius.x, startPos.y-radius.y}, {startPos.x+radius.x, startPos.y+radius.y}, + {endPos.x-radius.x, endPos.y-radius.y}, {endPos.x+radius.x, endPos.y+radius.y}}; + + DrawTriangleStrip(strip, 4, color); } - - float dx = endPos.x - startPos.x; - float dy = endPos.y - startPos.y; - - float d = sqrtf(dx*dx + dy*dy); - float angle = asinf(dy/d); - - rlEnableTexture(GetShapesTexture().id); - - rlPushMatrix(); - rlTranslatef((float)startPos.x, (float)startPos.y, 0.0f); - rlRotatef(RAD2DEG*angle, 0.0f, 0.0f, 1.0f); - rlTranslatef(0, (thick > 1.0f)? -thick/2.0f : -1.0f, 0.0f); - - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); - - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); - rlVertex2f(0.0f, 0.0f); - - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); - rlVertex2f(0.0f, thick); - - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); - rlVertex2f(d, thick); - - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); - rlVertex2f(d, 0.0f); - rlEnd(); - rlPopMatrix(); - - rlDisableTexture(); } // Draw line using cubic-bezier curves in-out @@ -165,7 +144,7 @@ void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) #endif Vector2 previous = startPos; - Vector2 current; + Vector2 current = { 0 }; for (int i = 1; i <= BEZIER_LINE_DIVISIONS; i++) { @@ -180,12 +159,38 @@ void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color) } } +// Draw line using quadratic bezier curves with a control point +void DrawLineBezierQuad(Vector2 startPos, Vector2 endPos, Vector2 controlPos, float thick, Color color) +{ + const float step = 1.0f/BEZIER_LINE_DIVISIONS; + + Vector2 previous = startPos; + Vector2 current = { 0 }; + float t = 0.0f; + + for (int i = 0; i <= BEZIER_LINE_DIVISIONS; i++) + { + t = step*i; + float a = powf(1 - t, 2); + float b = 2*(1 - t)*t; + float c = powf(t, 2); + + // NOTE: The easing functions aren't suitable here because they don't take a control point + current.y = a*startPos.y + b*controlPos.y + c*endPos.y; + current.x = a*startPos.x + b*controlPos.x + c*endPos.x; + + DrawLineEx(previous, current, thick, color); + + previous = current; + } +} + // Draw lines sequence void DrawLineStrip(Vector2 *points, int pointsCount, Color color) { if (pointsCount >= 2) { - if (rlCheckBufferLimit(pointsCount)) rlglDraw(); + rlCheckRenderBatchLimit(pointsCount); rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -206,7 +211,7 @@ void DrawCircle(int centerX, int centerY, float radius, Color color) } // Draw a piece of a circle -void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color) +void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) { if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero @@ -214,27 +219,29 @@ void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle if (endAngle < startAngle) { // Swap values - int tmp = startAngle; + float tmp = startAngle; startAngle = endAngle; endAngle = tmp; } - if (segments < 4) + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) { // Calculate the maximum angle between segments based on the error rate (usually 0.5f) float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); - if (segments <= 0) segments = 4; + if (segments <= 0) segments = minSegments; } - float stepLength = (float)(endAngle - startAngle)/(float)segments; - float angle = (float)startAngle; + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; #if defined(SUPPORT_QUADS_DRAW_MODE) - if (rlCheckBufferLimit(4*segments/2)) rlglDraw(); + rlCheckRenderBatchLimit(4*segments/2); - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); // NOTE: Every QUAD actually represents two segments @@ -242,16 +249,16 @@ void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x, center.y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength*2))*radius, center.y + cosf(DEG2RAD*(angle + stepLength*2))*radius); angle += (stepLength*2); @@ -262,23 +269,23 @@ void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x, center.y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x, center.y); } rlEnd(); - rlDisableTexture(); + rlSetTexture(0); #else - if (rlCheckBufferLimit(3*segments)) rlglDraw(); + rlCheckRenderBatchLimit(3*segments); rlBegin(RL_TRIANGLES); for (int i = 0; i < segments; i++) @@ -295,7 +302,7 @@ void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle #endif } -void DrawCircleSectorLines(Vector2 center, float radius, int startAngle, int endAngle, int segments, Color color) +void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color) { if (radius <= 0.0f) radius = 0.1f; // Avoid div by zero issue @@ -303,29 +310,31 @@ void DrawCircleSectorLines(Vector2 center, float radius, int startAngle, int end if (endAngle < startAngle) { // Swap values - int tmp = startAngle; + float tmp = startAngle; startAngle = endAngle; endAngle = tmp; } - if (segments < 4) + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) { // Calculate the maximum angle between segments based on the error rate (usually 0.5f) float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/radius, 2) - 1); segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); - if (segments <= 0) segments = 4; + if (segments <= 0) segments = minSegments; } - float stepLength = (float)(endAngle - startAngle)/(float)segments; - float angle = (float)startAngle; + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; // Hide the cap lines when the circle is full bool showCapLines = true; int limit = 2*(segments + 2); - if ((endAngle - startAngle)%360 == 0) { limit = 2*segments; showCapLines = false; } + if ((int)(endAngle - startAngle)%360 == 0) { limit = 2*segments; showCapLines = false; } - if (rlCheckBufferLimit(limit)) rlglDraw(); + rlCheckRenderBatchLimit(limit); rlBegin(RL_LINES); if (showCapLines) @@ -358,7 +367,7 @@ void DrawCircleSectorLines(Vector2 center, float radius, int startAngle, int end // NOTE: Gradient goes from center (color1) to border (color2) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) { - if (rlCheckBufferLimit(3*36)) rlglDraw(); + rlCheckRenderBatchLimit(3*36); rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) @@ -374,7 +383,7 @@ void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Co } // Draw a color-filled circle (Vector version) -// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues void DrawCircleV(Vector2 center, float radius, Color color) { DrawCircleSector(center, radius, 0, 360, 36, color); @@ -383,7 +392,7 @@ void DrawCircleV(Vector2 center, float radius, Color color) // Draw circle outline void DrawCircleLines(int centerX, int centerY, float radius, Color color) { - if (rlCheckBufferLimit(2*36)) rlglDraw(); + rlCheckRenderBatchLimit(2*36); rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -400,7 +409,7 @@ void DrawCircleLines(int centerX, int centerY, float radius, Color color) // Draw ellipse void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color) { - if (rlCheckBufferLimit(3*36)) rlglDraw(); + rlCheckRenderBatchLimit(3*36); rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) @@ -416,7 +425,7 @@ void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color c // Draw ellipse outline void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color) { - if (rlCheckBufferLimit(2*36)) rlglDraw(); + rlCheckRenderBatchLimit(2*36); rlBegin(RL_LINES); for (int i = 0; i < 360; i += 10) @@ -428,7 +437,7 @@ void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Co rlEnd(); } -void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color) +void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) { if (startAngle == endAngle) return; @@ -446,18 +455,20 @@ void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAng if (endAngle < startAngle) { // Swap values - int tmp = startAngle; + float tmp = startAngle; startAngle = endAngle; endAngle = tmp; } - if (segments < 4) + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) { // Calculate the maximum angle between segments based on the error rate (usually 0.5f) float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); - if (segments <= 0) segments = 4; + if (segments <= 0) segments = minSegments; } // Not a ring @@ -467,38 +478,38 @@ void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAng return; } - float stepLength = (float)(endAngle - startAngle)/(float)segments; - float angle = (float)startAngle; + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; #if defined(SUPPORT_QUADS_DRAW_MODE) - if (rlCheckBufferLimit(4*segments)) rlglDraw(); + rlCheckRenderBatchLimit(4*segments); - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); for (int i = 0; i < segments; i++) { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); angle += stepLength; } rlEnd(); - rlDisableTexture(); + rlSetTexture(0); #else - if (rlCheckBufferLimit(6*segments)) rlglDraw(); + rlCheckRenderBatchLimit(6*segments); rlBegin(RL_TRIANGLES); for (int i = 0; i < segments; i++) @@ -519,7 +530,7 @@ void DrawRing(Vector2 center, float innerRadius, float outerRadius, int startAng #endif } -void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, int startAngle, int endAngle, int segments, Color color) +void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color) { if (startAngle == endAngle) return; @@ -537,18 +548,20 @@ void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, int sta if (endAngle < startAngle) { // Swap values - int tmp = startAngle; + float tmp = startAngle; startAngle = endAngle; endAngle = tmp; } - if (segments < 4) + int minSegments = (int)ceilf((endAngle - startAngle)/90); + + if (segments < minSegments) { // Calculate the maximum angle between segments based on the error rate (usually 0.5f) float th = acosf(2*powf(1 - SMOOTH_CIRCLE_ERROR_RATE/outerRadius, 2) - 1); segments = (int)((endAngle - startAngle)*ceilf(2*PI/th)/360); - if (segments <= 0) segments = 4; + if (segments <= 0) segments = minSegments; } if (innerRadius <= 0.0f) @@ -557,14 +570,14 @@ void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, int sta return; } - float stepLength = (float)(endAngle - startAngle)/(float)segments; - float angle = (float)startAngle; + float stepLength = (endAngle - startAngle)/(float)segments; + float angle = startAngle; bool showCapLines = true; int limit = 4*(segments + 1); - if ((endAngle - startAngle)%360 == 0) { limit = 4*segments; showCapLines = false; } + if ((int)(endAngle - startAngle)%360 == 0) { limit = 4*segments; showCapLines = false; } - if (rlCheckBufferLimit(limit)) rlglDraw(); + rlCheckRenderBatchLimit(limit); rlBegin(RL_LINES); if (showCapLines) @@ -603,7 +616,7 @@ void DrawRectangle(int posX, int posY, int width, int height, Color 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) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues void DrawRectangleV(Vector2 position, Vector2 size, Color color) { DrawRectanglePro((Rectangle){ position.x, position.y, size.x, size.y }, (Vector2){ 0.0f, 0.0f }, 0.0f, color); @@ -618,34 +631,65 @@ void DrawRectangleRec(Rectangle rec, Color color) // Draw a color-filled rectangle with pro parameters void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color) { - if (rlCheckBufferLimit(4)) rlglDraw(); - - rlEnableTexture(GetShapesTexture().id); + rlCheckRenderBatchLimit(4); - rlPushMatrix(); - rlTranslatef(rec.x, rec.y, 0.0f); - rlRotatef(rotation, 0.0f, 0.0f, 1.0f); - rlTranslatef(-origin.x, -origin.y, 0.0f); + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; - rlBegin(RL_QUADS); - rlNormal3f(0.0f, 0.0f, 1.0f); - rlColor4ub(color.r, color.g, color.b, color.a); + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = rec.x - origin.x; + float y = rec.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + rec.width, y }; + bottomLeft = (Vector2){ x, y + rec.height }; + bottomRight = (Vector2){ x + rec.width, y + rec.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = rec.x; + float y = rec.y; + float dx = -origin.x; + float dy = -origin.y; - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); - rlVertex2f(0.0f, 0.0f); + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); - rlVertex2f(0.0f, rec.height); + topRight.x = x + (dx + rec.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + rec.width)*sinRotation + dy*cosRotation; - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); - rlVertex2f(rec.width, rec.height); + bottomLeft.x = x + dx*cosRotation - (dy + rec.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + rec.height)*cosRotation; - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); - rlVertex2f(rec.width, 0.0f); - rlEnd(); - rlPopMatrix(); + bottomRight.x = x + (dx + rec.width)*cosRotation - (dy + rec.height)*sinRotation; + bottomRight.y = y + (dx + rec.width)*sinRotation + (dy + rec.height)*cosRotation; + } - rlDisableTexture(); + rlSetTexture(texShapes.id); + rlBegin(RL_QUADS); + + rlNormal3f(0.0f, 0.0f, 1.0f); + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(topLeft.x, topLeft.y); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(bottomRight.x, bottomRight.y); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); } // Draw a vertical-gradient-filled rectangle @@ -666,7 +710,7 @@ 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) { - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlPushMatrix(); rlBegin(RL_QUADS); @@ -674,28 +718,28 @@ void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, // NOTE: Default raylib font character 95 is a white square rlColor4ub(col1.r, col1.g, col1.b, col1.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(rec.x, rec.y); rlColor4ub(col2.r, col2.g, col2.b, col2.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(rec.x, rec.y + rec.height); rlColor4ub(col3.r, col3.g, col3.b, col3.a); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y + rec.height); rlColor4ub(col4.r, col4.g, col4.b, col4.a); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); rlPopMatrix(); - rlDisableTexture(); + rlSetTexture(0); } // Draw rectangle outline -// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues (view rlglDraw) +// NOTE: On OpenGL 3.3 and ES2 we use QUADS to avoid drawing order issues void DrawRectangleLines(int posX, int posY, int width, int height, Color color) { #if defined(SUPPORT_QUADS_DRAW_MODE) @@ -722,18 +766,34 @@ 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) +void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color) { - if (lineThick > rec.width || lineThick > rec.height) + if ((lineThick > rec.width) || (lineThick > rec.height)) { - if (rec.width > rec.height) lineThick = (int)rec.height/2; - else if (rec.width < rec.height) lineThick = (int)rec.width/2; + if (rec.width > rec.height) lineThick = rec.height/2; + else if (rec.width < rec.height) lineThick = rec.width/2; } - DrawRectangle( (int)rec.x, (int)rec.y, (int)rec.width, lineThick, color); - DrawRectangle( (int)(rec.x - lineThick + rec.width), (int)(rec.y + lineThick), lineThick, (int)(rec.height - lineThick*2.0f), color); - DrawRectangle( (int)rec.x, (int)(rec.y + rec.height - lineThick), (int)rec.width, lineThick, color); - DrawRectangle( (int)rec.x, (int)(rec.y + lineThick), lineThick, (int)(rec.height - lineThick*2), color); + // When rec = { x, y, 8.0f, 6.0f } and lineThick = 2, the following + // four rectangles are drawn ([T]op, [B]ottom, [L]eft, [R]ight): + // + // TTTTTTTT + // TTTTTTTT + // LL RR + // LL RR + // BBBBBBBB + // BBBBBBBB + // + + Rectangle top = { rec.x, rec.y, rec.width, lineThick }; + Rectangle bottom = { rec.x, rec.y - lineThick + rec.height, rec.width, lineThick }; + Rectangle left = { rec.x, rec.y + lineThick, lineThick, rec.height - lineThick*2.0f }; + Rectangle right = { rec.x - lineThick + rec.width, rec.y + lineThick, lineThick, rec.height - lineThick*2.0f }; + + DrawRectangleRec(top, color); + DrawRectangleRec(bottom, color); + DrawRectangleRec(left, color); + DrawRectangleRec(right, color); } // Draw rectangle with rounded edges @@ -763,27 +823,28 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co float stepLength = 90.0f/(float)segments; - /* Quick sketch to make sense of all of this (there are 9 parts to draw, also mark the 12 points we'll use below) - * Not my best attempt at ASCII art, just preted it's rounded rectangle :) - * P0 P1 - * ____________________ - * /| |\ - * /1| 2 |3\ - *P7 /__|____________________|__\ P2 - * | |P8 P9| | - * | 8 | 9 | 4 | - * | __|____________________|__ | - *P6 \ |P11 P10| / P3 - * \7| 6 |5/ - * \|____________________|/ - * P5 P4 - */ + /* + Quick sketch to make sense of all of this, + there are 9 parts to draw, also mark the 12 points we'll use - const Vector2 point[12] = { // coordinates of the 12 points that define the rounded rect (the idea here is to make things easier) - {(float)rec.x + radius, rec.y}, {(float)(rec.x + rec.width) - radius, rec.y}, { rec.x + rec.width, (float)rec.y + radius }, // PO, P1, P2 - {rec.x + rec.width, (float)(rec.y + rec.height) - radius}, {(float)(rec.x + rec.width) - radius, rec.y + rec.height}, // P3, P4 - {(float)rec.x + radius, rec.y + rec.height}, { rec.x, (float)(rec.y + rec.height) - radius}, {rec.x, (float)rec.y + radius}, // P5, P6, P7 - {(float)rec.x + radius, (float)rec.y + radius}, {(float)(rec.x + rec.width) - radius, (float)rec.y + radius}, // P8, P9 + P0____________________P1 + /| |\ + /1| 2 |3\ + P7 /__|____________________|__\ P2 + | |P8 P9| | + | 8 | 9 | 4 | + | __|____________________|__ | + P6 \ |P11 P10| / P3 + \7| 6 |5/ + \|____________________|/ + P5 P4 + */ + // Coordinates of the 12 points that define the rounded rect + const Vector2 point[12] = { + {(float)rec.x + radius, rec.y}, {(float)(rec.x + rec.width) - radius, rec.y}, { rec.x + rec.width, (float)rec.y + radius }, // PO, P1, P2 + {rec.x + rec.width, (float)(rec.y + rec.height) - radius}, {(float)(rec.x + rec.width) - radius, rec.y + rec.height}, // P3, P4 + {(float)rec.x + radius, rec.y + rec.height}, { rec.x, (float)(rec.y + rec.height) - radius}, {rec.x, (float)rec.y + radius}, // P5, P6, P7 + {(float)rec.x + radius, (float)rec.y + radius}, {(float)(rec.x + rec.width) - radius, (float)rec.y + radius}, // P8, P9 {(float)(rec.x + rec.width) - radius, (float)(rec.y + rec.height) - radius}, {(float)rec.x + radius, (float)(rec.y + rec.height) - radius} // P10, P11 }; @@ -791,9 +852,9 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co const float angles[4] = { 180.0f, 90.0f, 0.0f, 270.0f }; #if defined(SUPPORT_QUADS_DRAW_MODE) - if (rlCheckBufferLimit(16*segments/2 + 5*4)) rlglDraw(); + rlCheckRenderBatchLimit(16*segments/2 + 5*4); - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner @@ -801,96 +862,99 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co { float angle = angles[k]; const Vector2 center = centers[k]; + // NOTE: Every QUAD actually represents two segments for (int i = 0; i < segments/2; i++) { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x, center.y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength*2))*radius, center.y + cosf(DEG2RAD*(angle + stepLength*2))*radius); angle += (stepLength*2); } + // NOTE: In case number of segments is odd, we add one last piece to the cake if (segments%2) { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x, center.y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*radius, center.y + cosf(DEG2RAD*angle)*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*radius, center.y + cosf(DEG2RAD*(angle + stepLength))*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x, center.y); } } // [2] Upper Rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[0].x, point[0].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[8].x, point[8].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[9].x, point[9].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[1].x, point[1].y); // [4] Right Rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[2].x, point[2].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[9].x, point[9].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[10].x, point[10].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[3].x, point[3].y); // [6] Bottom Rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[11].x, point[11].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[5].x, point[5].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[4].x, point[4].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[10].x, point[10].y); // [8] Left Rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[7].x, point[7].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[6].x, point[6].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[11].x, point[11].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[8].x, point[8].y); // [9] Middle Rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[8].x, point[8].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[11].x, point[11].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[10].x, point[10].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[9].x, point[9].y); rlEnd(); - rlDisableTexture(); + rlSetTexture(0); #else - if (rlCheckBufferLimit(12*segments + 5*6)) rlglDraw(); // 4 corners with 3 vertices per segment + 5 rectangles with 6 vertices each + rlCheckRenderBatchLimit(12*segments + 5*6); // 4 corners with 3 vertices per segment + 5 rectangles with 6 vertices each rlBegin(RL_TRIANGLES); + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop { @@ -955,7 +1019,7 @@ void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color co } // Draw rectangle with rounded edges outline -void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int lineThick, Color color) +void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, float lineThick, Color color) { if (lineThick < 0) lineThick = 0; @@ -982,24 +1046,24 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int } float stepLength = 90.0f/(float)segments; - const float outerRadius = radius + (float)lineThick, innerRadius = radius; + const float outerRadius = radius + lineThick, innerRadius = radius; - /* Quick sketch to make sense of all of this (mark the 16 + 4(corner centers P16-19) points we'll use below) - * Not my best attempt at ASCII art, just preted it's rounded rectangle :) - * P0 P1 - * ==================== - * // P8 P9 \\ - * // \\ - *P7 // P15 P10 \\ P2 - * || *P16 P17* || - * || || - * || P14 P11 || - *P6 \\ *P19 P18* // P3 - * \\ // - * \\ P13 P12 // - * ==================== - * P5 P4 - */ + /* + Quick sketch to make sense of all of this, + marks the 16 + 4(corner centers P16-19) points we'll use + + P0 ================== P1 + // P8 P9 \\ + // \\ + P7 // P15 P10 \\ P2 + || *P16 P17* || + || || + || P14 P11 || + P6 \\ *P19 P18* // P3 + \\ // + \\ P13 P12 // + P5 ================== P4 + */ const Vector2 point[16] = { {(float)rec.x + innerRadius, rec.y - lineThick}, {(float)(rec.x + rec.width) - innerRadius, rec.y - lineThick}, { rec.x + rec.width + lineThick, (float)rec.y + innerRadius }, // PO, P1, P2 {rec.x + rec.width + lineThick, (float)(rec.y + rec.height) - innerRadius}, {(float)(rec.x + rec.width) - innerRadius, rec.y + rec.height + lineThick}, // P3, P4 @@ -1020,11 +1084,12 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int if (lineThick > 1) { #if defined(SUPPORT_QUADS_DRAW_MODE) - if (rlCheckBufferLimit(4*4*segments + 4*4)) rlglDraw(); // 4 corners with 4 vertices for each segment + 4 rectangles with 4 vertices each + rlCheckRenderBatchLimit(4*4*segments + 4*4); // 4 corners with 4 vertices for each segment + 4 rectangles with 4 vertices each - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); + // Draw all of the 4 corners first: Upper Left Corner, Upper Right Corner, Lower Right Corner, Lower Left Corner for (int k = 0; k < 4; ++k) // Hope the compiler is smart enough to unroll this loop { @@ -1033,13 +1098,13 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int for (int i = 0; i < segments; i++) { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*innerRadius, center.y + cosf(DEG2RAD*angle)*innerRadius); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*angle)*outerRadius, center.y + cosf(DEG2RAD*angle)*outerRadius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*outerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*outerRadius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(center.x + sinf(DEG2RAD*(angle + stepLength))*innerRadius, center.y + cosf(DEG2RAD*(angle + stepLength))*innerRadius); angle += stepLength; @@ -1048,52 +1113,52 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int // Upper rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[0].x, point[0].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[8].x, point[8].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[9].x, point[9].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[1].x, point[1].y); // Right rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[2].x, point[2].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[10].x, point[10].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[11].x, point[11].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[3].x, point[3].y); // Lower rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[13].x, point[13].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[5].x, point[5].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[4].x, point[4].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[12].x, point[12].y); // Left rectangle rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[15].x, point[15].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[7].x, point[7].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(point[6].x, point[6].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(point[14].x, point[14].y); rlEnd(); - rlDisableTexture(); + rlSetTexture(0); #else - if (rlCheckBufferLimit(4*6*segments + 4*6)) rlglDraw(); // 4 corners with 6(2*3) vertices for each segment + 4 rectangles with 6 vertices each + rlCheckRenderBatchLimit(4*6*segments + 4*6); // 4 corners with 6(2*3) vertices for each segment + 4 rectangles with 6 vertices each rlBegin(RL_TRIANGLES); @@ -1160,7 +1225,7 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int else { // Use LINES to draw the outline - if (rlCheckBufferLimit(8*segments + 4*2)) rlglDraw(); // 4 corners with 2 vertices for each segment + 4 rectangles with 2 vertices each + rlCheckRenderBatchLimit(8*segments + 4*2); // 4 corners with 2 vertices for each segment + 4 rectangles with 2 vertices each rlBegin(RL_LINES); @@ -1178,13 +1243,15 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int angle += stepLength; } } + // And now the remaining 4 lines - for(int i = 0; i < 8; i += 2) + for (int i = 0; i < 8; i += 2) { rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2f(point[i].x, point[i].y); rlVertex2f(point[i + 1].x, point[i + 1].y); } + rlEnd(); } } @@ -1193,28 +1260,28 @@ void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, int // NOTE: Vertex must be provided in counter-clockwise order void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { - if (rlCheckBufferLimit(4)) rlglDraw(); + rlCheckRenderBatchLimit(4); #if defined(SUPPORT_QUADS_DRAW_MODE) - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(v1.x, v1.y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(v2.x, v2.y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(v2.x, v2.y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(v3.x, v3.y); rlEnd(); - rlDisableTexture(); + rlSetTexture(0); #else rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -1229,7 +1296,7 @@ void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color) // NOTE: Vertex must be provided in counter-clockwise order void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color) { - if (rlCheckBufferLimit(6)) rlglDraw(); + rlCheckRenderBatchLimit(6); rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -1251,28 +1318,28 @@ void DrawTriangleFan(Vector2 *points, int pointsCount, Color color) { if (pointsCount >= 3) { - if (rlCheckBufferLimit((pointsCount - 2)*4)) rlglDraw(); + rlCheckRenderBatchLimit((pointsCount - 2)*4); - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); for (int i = 1; i < pointsCount - 1; i++) { - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(points[0].x, points[0].y); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(points[i].x, points[i].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(points[i + 1].x, points[i + 1].y); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(points[i + 1].x, points[i + 1].y); } rlEnd(); - rlDisableTexture(); + rlSetTexture(0); } } @@ -1282,7 +1349,7 @@ void DrawTriangleStrip(Vector2 *points, int pointsCount, Color color) { if (pointsCount >= 3) { - if (rlCheckBufferLimit(3*(pointsCount - 2))) rlglDraw(); + rlCheckRenderBatchLimit(3*(pointsCount - 2)); rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -1312,35 +1379,39 @@ void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color col if (sides < 3) sides = 3; float centralAngle = 0.0f; - if (rlCheckBufferLimit(4*(360/sides))) rlglDraw(); +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*sides); // Each side is a quad +#else + rlCheckRenderBatchLimit(3*sides); +#endif rlPushMatrix(); rlTranslatef(center.x, center.y, 0.0f); rlRotatef(rotation, 0.0f, 0.0f, 1.0f); #if defined(SUPPORT_QUADS_DRAW_MODE) - rlEnableTexture(GetShapesTexture().id); + rlSetTexture(texShapes.id); rlBegin(RL_QUADS); for (int i = 0; i < sides; i++) { rlColor4ub(color.r, color.g, color.b, color.a); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(0, 0); - rlTexCoord2f(GetShapesTextureRec().x/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, (GetShapesTextureRec().y + GetShapesTextureRec().height)/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); centralAngle += 360.0f/(float)sides; - rlTexCoord2f((GetShapesTextureRec().x + GetShapesTextureRec().width)/GetShapesTexture().width, GetShapesTextureRec().y/GetShapesTexture().height); + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); } rlEnd(); - rlDisableTexture(); + rlSetTexture(0); #else rlBegin(RL_TRIANGLES); for (int i = 0; i < sides; i++) @@ -1364,7 +1435,7 @@ void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Colo if (sides < 3) sides = 3; float centralAngle = 0.0f; - if (rlCheckBufferLimit(3*(360/sides))) rlglDraw(); + rlCheckRenderBatchLimit(2*sides); rlPushMatrix(); rlTranslatef(center.x, center.y, 0.0f); @@ -1383,6 +1454,68 @@ void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Colo rlPopMatrix(); } +void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color) +{ + if (sides < 3) sides = 3; + float centralAngle = 0.0f; + float exteriorAngle = 360.0f/(float)sides; + float innerRadius = radius - (lineThick*cosf(DEG2RAD*exteriorAngle/2.0f)); + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlCheckRenderBatchLimit(4*sides); +#else + rlCheckRenderBatchLimit(6*sides); +#endif + + rlPushMatrix(); + rlTranslatef(center.x, center.y, 0.0f); + rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + +#if defined(SUPPORT_QUADS_DRAW_MODE) + rlSetTexture(texShapes.id); + + rlBegin(RL_QUADS); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + + rlTexCoord2f(texShapesRec.x/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*innerRadius, cosf(DEG2RAD*centralAngle)*innerRadius); + + rlTexCoord2f(texShapesRec.x/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + centralAngle += exteriorAngle; + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, texShapesRec.y/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + + rlTexCoord2f((texShapesRec.x + texShapesRec.width)/texShapes.width, (texShapesRec.y + texShapesRec.height)/texShapes.height); + rlVertex2f(sinf(DEG2RAD*centralAngle)*innerRadius, cosf(DEG2RAD*centralAngle)*innerRadius); + } + rlEnd(); + rlSetTexture(0); +#else + rlBegin(RL_TRIANGLES); + for (int i = 0; i < sides; i++) + { + rlColor4ub(color.r, color.g, color.b, color.a); + float nextAngle = centralAngle + exteriorAngle; + + rlVertex2f(sinf(DEG2RAD*centralAngle)*radius, cosf(DEG2RAD*centralAngle)*radius); + rlVertex2f(sinf(DEG2RAD*centralAngle)*innerRadius, cosf(DEG2RAD*centralAngle)*innerRadius); + rlVertex2f(sinf(DEG2RAD*nextAngle)*radius, cosf(DEG2RAD*nextAngle)*radius); + + rlVertex2f(sinf(DEG2RAD*centralAngle)*innerRadius, cosf(DEG2RAD*centralAngle)*innerRadius); + rlVertex2f(sinf(DEG2RAD*nextAngle)*radius, cosf(DEG2RAD*nextAngle)*radius); + rlVertex2f(sinf(DEG2RAD*nextAngle)*innerRadius, cosf(DEG2RAD*nextAngle)*innerRadius); + + centralAngle = nextAngle; + } + rlEnd(); +#endif + rlPopMatrix(); +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Collision Detection functions //---------------------------------------------------------------------------------- @@ -1416,7 +1549,7 @@ bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 float gamma = 1.0f - alpha - beta; - if ((alpha > 0) && (beta > 0) & (gamma > 0)) collision = true; + if ((alpha > 0) && (beta > 0) && (gamma > 0)) collision = true; return collision; } @@ -1469,10 +1602,53 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) return (cornerDistanceSq <= (radius*radius)); } +// Check the collision between two lines defined by two points each, returns collision point by reference +bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint) +{ + const float div = (endPos2.y - startPos2.y)*(endPos1.x - startPos1.x) - (endPos2.x - startPos2.x)*(endPos1.y - startPos1.y); + + if (div == 0.0f) return false; // WARNING: This check could not work due to float precision rounding issues... + + const float xi = ((startPos2.x - endPos2.x)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.x - endPos1.x)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; + const float yi = ((startPos2.y - endPos2.y)*(startPos1.x*endPos1.y - startPos1.y*endPos1.x) - (startPos1.y - endPos1.y)*(startPos2.x*endPos2.y - startPos2.y*endPos2.x))/div; + + if (xi < fminf(startPos1.x, endPos1.x) || xi > fmaxf(startPos1.x, endPos1.x)) return false; + if (xi < fminf(startPos2.x, endPos2.x) || xi > fmaxf(startPos2.x, endPos2.x)) return false; + if (yi < fminf(startPos1.y, endPos1.y) || yi > fmaxf(startPos1.y, endPos1.y)) return false; + if (yi < fminf(startPos2.y, endPos2.y) || yi > fmaxf(startPos2.y, endPos2.y)) return false; + + if (collisionPoint != 0) + { + collisionPoint->x = xi; + collisionPoint->y = yi; + } + + return true; +} + +// Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold) +{ + bool collision = false; + float dxc = point.x - p1.x; + float dyc = point.y - p1.y; + float dxl = p2.x - p1.x; + float dyl = p2.y - p1.y; + float cross = dxc*dyl - dyc*dxl; + + if (fabsf(cross) < (threshold*fmaxf(fabsf(dxl), fabsf(dyl)))) + { + if (fabsf(dxl) >= fabsf(dyl)) collision = (dxl > 0)? ((p1.x <= point.x) && (point.x <= p2.x)) : ((p2.x <= point.x) && (point.x <= p1.x)); + else collision = (dyl > 0)? ((p1.y <= point.y) && (point.y <= p2.y)) : ((p2.y <= point.y) && (point.y <= p1.y)); + } + + return collision; +} + // Get collision rectangle for two rectangles collision Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) { - Rectangle retRec = { 0, 0, 0, 0 }; + Rectangle rec = { 0, 0, 0, 0 }; if (CheckCollisionRecs(rec1, rec2)) { @@ -1483,57 +1659,57 @@ Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2) { if (rec1.y <= rec2.y) { - retRec.x = rec2.x; - retRec.y = rec2.y; - retRec.width = rec1.width - dxx; - retRec.height = rec1.height - dyy; + rec.x = rec2.x; + rec.y = rec2.y; + rec.width = rec1.width - dxx; + rec.height = rec1.height - dyy; } else { - retRec.x = rec2.x; - retRec.y = rec1.y; - retRec.width = rec1.width - dxx; - retRec.height = rec2.height - dyy; + rec.x = rec2.x; + rec.y = rec1.y; + rec.width = rec1.width - dxx; + rec.height = rec2.height - dyy; } } else { if (rec1.y <= rec2.y) { - retRec.x = rec1.x; - retRec.y = rec2.y; - retRec.width = rec2.width - dxx; - retRec.height = rec1.height - dyy; + rec.x = rec1.x; + rec.y = rec2.y; + rec.width = rec2.width - dxx; + rec.height = rec1.height - dyy; } else { - retRec.x = rec1.x; - retRec.y = rec1.y; - retRec.width = rec2.width - dxx; - retRec.height = rec2.height - dyy; + rec.x = rec1.x; + rec.y = rec1.y; + rec.width = rec2.width - dxx; + rec.height = rec2.height - dyy; } } if (rec1.width > rec2.width) { - if (retRec.width >= rec2.width) retRec.width = rec2.width; + if (rec.width >= rec2.width) rec.width = rec2.width; } else { - if (retRec.width >= rec1.width) retRec.width = rec1.width; + if (rec.width >= rec1.width) rec.width = rec1.width; } if (rec1.height > rec2.height) { - if (retRec.height >= rec2.height) retRec.height = rec2.height; + if (rec.height >= rec2.height) rec.height = rec2.height; } else { - if (retRec.height >= rec1.height) retRec.height = rec1.height; + if (rec.height >= rec1.height) rec.height = rec1.height; } } - return retRec; + return rec; } //---------------------------------------------------------------------------------- diff --git a/raylib/text.c b/raylib/text.c index 666e904..23cad7e 100644 --- a/raylib/text.c +++ b/raylib/text.c @@ -27,7 +27,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -128,7 +128,8 @@ extern void LoadFontDefault(void) // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement // Ref: http://www.utf8-chartable.de/unicode-utf8-table.pl - defaultFont.charsCount = 224; // Number of chars included in our default font + defaultFont.charsCount = 224; // Number of chars included in our default font + defaultFont.charsPadding = 0; // Characters padding // Default font is directly defined here (data generated from a sprite font image) // This way, we reconstruct Font without creating large global variables @@ -193,9 +194,9 @@ extern void LoadFontDefault(void) //---------------------------------------------------------------------- Image imFont = { .data = calloc(128*128, 2), // 2 bytes per pixel (gray + alpha) - .width = 128, + .width = 128, .height = 128, - .format = UNCOMPRESSED_GRAY_ALPHA, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, .mipmaps = 1 }; @@ -302,6 +303,9 @@ Font LoadFont(const char *fileName) #ifndef FONT_TTF_DEFAULT_FIRST_CHAR #define FONT_TTF_DEFAULT_FIRST_CHAR 32 // TTF font generation default first char for image sprite font (32-Space) #endif +#ifndef FONT_TTF_DEFAULT_CHARS_PADDING + #define FONT_TTF_DEFAULT_CHARS_PADDING 4 // TTF font generation default chars padding +#endif Font font = { 0 }; @@ -324,7 +328,7 @@ Font LoadFont(const char *fileName) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); font = GetFontDefault(); } - else SetTextureFilter(font.texture, FILTER_POINT); // By default we set point filter (best performance) + else SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default we set point filter (best performance) return font; } @@ -336,29 +340,18 @@ Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCou { Font font = { 0 }; -#if defined(SUPPORT_FILEFORMAT_TTF) - font.baseSize = fontSize; - font.charsCount = (charsCount > 0)? charsCount : 95; - font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT); + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); - if (font.chars != NULL) + if (fileData != NULL) { - Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, 2, 0); - font.texture = LoadTextureFromImage(atlas); + // Loading font from memory data + font = LoadFontFromMemory(GetFileExtension(fileName), fileData, fileSize, fontSize, fontChars, charsCount); - // Update chars[i].image to use alpha, required to be used on ImageDrawText() - for (int i = 0; i < font.charsCount; i++) - { - UnloadImage(font.chars[i].image); - font.chars[i].image = ImageFromImage(atlas, font.recs[i]); - } - - UnloadImage(atlas); + RL_FREE(fileData); } else font = GetFontDefault(); -#else - font = GetFontDefault(); -#endif return font; } @@ -369,7 +362,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) #ifndef MAX_GLYPHS_FROM_IMAGE #define MAX_GLYPHS_FROM_IMAGE 256 // Maximum number of glyphs supported on image scan #endif - + #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) int charSpacing = 0; @@ -383,7 +376,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) int tempCharValues[MAX_GLYPHS_FROM_IMAGE]; Rectangle tempCharRecs[MAX_GLYPHS_FROM_IMAGE]; - Color *pixels = GetImageData(image); + Color *pixels = LoadImageColors(image); // Parse image data to get charSpacing and lineSpacing for (y = 0; y < image.height; y++) @@ -439,7 +432,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) } // NOTE: We need to remove key color borders from image to avoid weird - // artifacts on texture scaling when using FILTER_BILINEAR or FILTER_TRILINEAR + // artifacts on texture scaling when using TEXTURE_FILTER_BILINEAR or TEXTURE_FILTER_TRILINEAR for (int i = 0; i < image.height*image.width; i++) if (COLOR_EQUAL(pixels[i], key)) pixels[i] = BLANK; // Create a new image with the processed color data (key color replaced by BLANK) @@ -447,7 +440,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) .data = pixels, .width = image.width, .height = image.height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -456,6 +449,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture font.charsCount = index; + font.charsPadding = 0; // We got tempCharValues and tempCharsRecs populated with chars data // Now we move temp data to sized charValues and charRecs arrays @@ -485,9 +479,51 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) return font; } +// Load font from memory buffer, fileType refers to extension: i.e. ".ttf" +Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount) +{ + Font font = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + +#if defined(SUPPORT_FILEFORMAT_TTF) + if (TextIsEqual(fileExtLower, ".ttf") || + TextIsEqual(fileExtLower, ".otf")) + { + font.baseSize = fontSize; + font.charsCount = (charsCount > 0)? charsCount : 95; + font.charsPadding = 0; + font.chars = LoadFontData(fileData, dataSize, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT); + + if (font.chars != NULL) + { + font.charsPadding = FONT_TTF_DEFAULT_CHARS_PADDING; + + Image atlas = GenImageFontAtlas(font.chars, &font.recs, font.charsCount, font.baseSize, font.charsPadding, 0); + font.texture = LoadTextureFromImage(atlas); + + // Update chars[i].image to use alpha, required to be used on ImageDrawText() + for (int i = 0; i < font.charsCount; i++) + { + UnloadImage(font.chars[i].image); + font.chars[i].image = ImageFromImage(atlas, font.recs[i]); + } + + UnloadImage(atlas); + } + else font = GetFontDefault(); + } +#else + font = GetFontDefault(); +#endif + + return font; +} + // Load font data for further use -// NOTE: Requires TTF font and can generate SDF data -CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int charsCount, int type) +// NOTE: Requires TTF font memory data and can generate SDF data +CharInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *fontChars, int charsCount, int type) { // NOTE: Using some SDF generation default values, // trades off precision with ability to handle *smaller* sizes @@ -507,18 +543,14 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c CharInfo *chars = NULL; #if defined(SUPPORT_FILEFORMAT_TTF) - // Load font data (including pixel data) from TTF file - // NOTE: Loaded information should be enough to generate - // font image atlas, using any packaging method - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); - + // Load font data (including pixel data) from TTF memory file + // NOTE: Loaded information should be enough to generate font image atlas, using any packaging method if (fileData != NULL) { int genFontChars = false; stbtt_fontinfo fontInfo = { 0 }; - if (stbtt_InitFont(&fontInfo, fileData, 0)) // Init font for data reading + if (stbtt_InitFont(&fontInfo, (unsigned char *)fileData, 0)) // Init font for data reading { // Calculate font scale factor float scaleFactor = stbtt_ScaleForPixelHeight(&fontInfo, (float)fontSize); @@ -566,7 +598,7 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c chars[i].image.width = chw; chars[i].image.height = chh; chars[i].image.mipmaps = 1; - chars[i].image.format = UNCOMPRESSED_GRAYSCALE; + chars[i].image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; chars[i].offsetY += (int)((float)ascent*scaleFactor); @@ -577,10 +609,10 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c .data = calloc(chars[i].advanceX*fontSize, 2), .width = chars[i].advanceX, .height = fontSize, - .format = UNCOMPRESSED_GRAYSCALE, + .format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE, .mipmaps = 1 }; - + chars[i].image = imSpace; } @@ -607,7 +639,6 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c } else TRACELOG(LOG_WARNING, "FONT: Failed to process TTF font data"); - RL_FREE(fileData); if (genFontChars) RL_FREE(fontChars); } #endif @@ -648,7 +679,7 @@ Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCo atlas.width = imageSize; // Atlas bitmap width atlas.height = imageSize; // Atlas bitmap height atlas.data = (unsigned char *)RL_CALLOC(1, atlas.width*atlas.height); // Create a bitmap to store characters (8 bpp) - atlas.format = UNCOMPRESSED_GRAYSCALE; + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; atlas.mipmaps = 1; // DEBUG: We can see padding in the generated image setting a gray background... @@ -752,7 +783,7 @@ Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCo RL_FREE(atlas.data); atlas.data = dataGrayAlpha; - atlas.format = UNCOMPRESSED_GRAY_ALPHA; + atlas.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; *charRecs = recs; @@ -760,27 +791,39 @@ Image GenImageFontAtlas(const CharInfo *chars, Rectangle **charRecs, int charsCo } #endif +// Unload font chars info data (RAM) +void UnloadFontData(CharInfo *chars, int charsCount) +{ + for (int i = 0; i < charsCount; i++) UnloadImage(chars[i].image); + + RL_FREE(chars); +} + // Unload Font from GPU memory (VRAM) void UnloadFont(Font font) { // NOTE: Make sure font is not default font (fallback) if (font.texture.id != GetFontDefault().texture.id) { - for (int i = 0; i < font.charsCount; i++) UnloadImage(font.chars[i].image); - + UnloadFontData(font.chars, font.charsCount); UnloadTexture(font.texture); - RL_FREE(font.chars); RL_FREE(font.recs); TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); } } -// Shows current FPS on top-left corner +// Draw current FPS // NOTE: Uses default font void DrawFPS(int posX, int posY) { - DrawText(TextFormat("%2i FPS", GetFPS()), posX, posY, 20, LIME); + Color color = LIME; // good fps + int fps = GetFPS(); + + if (fps < 30 && fps >= 15) color = ORANGE; // warning FPS + else if (fps < 15) color = RED; // bad FPS + + DrawText(TextFormat("%2i FPS", GetFPS()), posX, posY, 20, color); } // Draw text (using default font) @@ -801,30 +844,18 @@ void DrawText(const char *text, int posX, int posY, int fontSize, Color color) } } -// Draw one character (codepoint) -void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float scale, Color tint) -{ - // Character index position in sprite font - // NOTE: In case a codepoint is not available in the font, index returned points to '?' - int index = GetGlyphIndex(font, codepoint); - - // Character rectangle on screen - // NOTE: Quad is scaled proportionally to base character width-height - Rectangle rec = { position.x, position.y, font.recs[index].width*scale, font.recs[index].height*scale }; - - DrawTexturePro(font.texture, font.recs[index], rec, (Vector2){ 0, 0 }, 0.0f, tint); -} - // Draw text using Font // NOTE: chars spacing is NOT proportional to fontSize void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint) { - int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font + + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop int textOffsetY = 0; // Offset between lines (on line break '\n') float textOffsetX = 0.0f; // Offset X to next character to draw - float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor for (int i = 0; i < length;) { @@ -848,12 +879,7 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f { if ((codepoint != ' ') && (codepoint != '\t')) { - Rectangle rec = { position.x + textOffsetX + font.chars[index].offsetX*scaleFactor, - position.y + textOffsetY + font.chars[index].offsetY*scaleFactor, - font.recs[index].width*scaleFactor, - font.recs[index].height*scaleFactor }; - - DrawTexturePro(font.texture, font.recs[index], rec, (Vector2){ 0, 0 }, 0.0f, tint); + DrawTextCodepoint(font, codepoint, (Vector2){ position.x + textOffsetX, position.y + textOffsetY }, fontSize, tint); } if (font.chars[index].advanceX == 0) textOffsetX += ((float)font.recs[index].width*scaleFactor + spacing); @@ -873,7 +899,7 @@ void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, flo // Draw text using font inside rectangle limits with support for text selection void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint, int selectStart, int selectLength, Color selectTint, Color selectBackTint) { - int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop + int length = TextLength(text); // Total length in bytes of the text, scanned by codepoints in loop int textOffsetY = 0; // Offset between lines (on line break '\n') float textOffsetX = 0.0f; // Offset X to next character to draw @@ -976,14 +1002,10 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f isGlyphSelected = true; } - // Draw current chracter glyph + // Draw current character glyph if ((codepoint != ' ') && (codepoint != '\t')) { - DrawTexturePro(font.texture, font.recs[index], - (Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor, - rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor, - font.recs[index].width*scaleFactor, font.recs[index].height*scaleFactor }, - (Vector2){ 0, 0 }, 0.0f, (!isGlyphSelected)? tint : selectTint); + DrawTextCodepoint(font, codepoint, (Vector2){ rec.x + textOffsetX, rec.y + textOffsetY }, fontSize, isGlyphSelected? selectTint : tint); } } @@ -1005,6 +1027,30 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f } } +// Draw one character (codepoint) +void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint) +{ + // Character index position in sprite font + // NOTE: In case a codepoint is not available in the font, index returned points to '?' + int index = GetGlyphIndex(font, codepoint); + float scaleFactor = fontSize/font.baseSize; // Character quad scaling factor + + // Character destination rectangle on screen + // NOTE: We consider charsPadding on drawing + Rectangle dstRec = { position.x + font.chars[index].offsetX*scaleFactor - (float)font.charsPadding*scaleFactor, + position.y + font.chars[index].offsetY*scaleFactor - (float)font.charsPadding*scaleFactor, + (font.recs[index].width + 2.0f*font.charsPadding)*scaleFactor, + (font.recs[index].height + 2.0f*font.charsPadding)*scaleFactor }; + + // Character source rectangle from font texture atlas + // NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects + Rectangle srcRec = { font.recs[index].x - (float)font.charsPadding, font.recs[index].y - (float)font.charsPadding, + font.recs[index].width + 2.0f*font.charsPadding, font.recs[index].height + 2.0f*font.charsPadding }; + + // Draw the character texture on the screen + DrawTexturePro(font.texture, srcRec, dstRec, (Vector2){ 0, 0 }, 0.0f, tint); +} + // Measure string width for default font int MeasureText(const char *text, int fontSize) { @@ -1080,7 +1126,7 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing // Returns index position for a unicode character on spritefont int GetGlyphIndex(Font font, int codepoint) { -#ifndef GLYPH_NOTFOUND_CHAR_FALLBACK +#ifndef GLYPH_NOTFOUND_CHAR_FALLBACK #define GLYPH_NOTFOUND_CHAR_FALLBACK 63 // Character used if requested codepoint is not found: '?' #endif @@ -1127,7 +1173,7 @@ const char *TextFormat(const char *text, ...) #ifndef MAX_TEXTFORMAT_BUFFERS #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting #endif - + // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; static int index = 0; @@ -1137,7 +1183,7 @@ const char *TextFormat(const char *text, ...) va_list args; va_start(args, text); - vsprintf(currentBuffer, text, args); + vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); va_end(args); index += 1; // Move to next buffer for next function call @@ -1231,7 +1277,7 @@ char *TextReplace(char *text, const char *replace, const char *by) { // Sanity checks and initialization if (!text || !replace || !by) return NULL; - + char *result; char *insertPoint; // Next insert point @@ -1327,6 +1373,7 @@ const char *TextJoin(const char **textList, int count, const char *delimiter) } // Split string into multiple strings +// REQUIRES: memset() const char **TextSplit(const char *text, char delimiter, int *count) { // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) @@ -1388,6 +1435,7 @@ int TextFindIndex(const char *text, const char *find) } // Get upper case version of provided string +// REQUIRES: toupper() const char *TextToUpper(const char *text) { static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; @@ -1409,6 +1457,7 @@ const char *TextToUpper(const char *text) } // Get lower case version of provided string +// REQUIRES: tolower() const char *TextToLower(const char *text) { static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; @@ -1427,6 +1476,7 @@ const char *TextToLower(const char *text) } // Get Pascal case notation version of provided string +// REQUIRES: toupper() const char *TextToPascal(const char *text) { static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 }; @@ -1450,7 +1500,9 @@ const char *TextToPascal(const char *text) return buffer; } -// Encode text codepoint into utf8 text (memory must be freed!) +// Encode text codepoint into utf8 text +// REQUIRES: memcpy() +// WARNING: Allocated memory should be manually freed char *TextToUtf8(int *codepoints, int length) { // We allocate enough memory fo fit all possible codepoints @@ -1513,6 +1565,7 @@ RLAPI const char *CodepointToUtf8(int codepoint, int *byteLength) } // Get all codepoints in a string, codepoints count returned by parameters +// REQUIRES: memset() int *GetCodepoints(const char *text, int *count) { static int codepoints[MAX_TEXT_UNICODE_CHARS] = { 0 }; @@ -1560,7 +1613,7 @@ int GetCodepointsCount(const char *text) // Total number of bytes processed are returned as a parameter // NOTE: the standard says U+FFFD should be returned in case of errors // but that character is not supported by the default font in raylib -// TODO: optimize this code for speed!! +// TODO: Optimize this code for speed!! int GetNextCodepoint(const char *text, int *bytesProcessed) { /* @@ -1673,6 +1726,7 @@ int GetNextCodepoint(const char *text, int *bytesProcessed) #if defined(SUPPORT_FILEFORMAT_FNT) // Read a line from memory +// REQUIRES: memcpy() // NOTE: Returns the number of bytes read static int GetLine(const char *origin, char *buffer, int maxLength) { @@ -1695,7 +1749,7 @@ static Font LoadBMFont(const char *fileName) int fontSize = 0; int charsCount = 0; - + int imWidth = 0; int imHeight = 0; char imFileName[129]; @@ -1707,7 +1761,7 @@ static Font LoadBMFont(const char *fileName) if (fileText == NULL) return font; char *fileTextPtr = fileText; - + // NOTE: We skip first line, it contains no useful information int lineBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); fileTextPtr += (lineBytes + 1); @@ -1756,14 +1810,14 @@ static Font LoadBMFont(const char *fileName) Image imFont = LoadImage(imPath); - if (imFont.format == UNCOMPRESSED_GRAYSCALE) + if (imFont.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) { // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel Image imFontAlpha = { .data = calloc(imFont.width*imFont.height, 2), .width = imFont.width, .height = imFont.height, - .format = UNCOMPRESSED_GRAY_ALPHA, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, .mipmaps = 1 }; @@ -1772,7 +1826,7 @@ static Font LoadBMFont(const char *fileName) ((unsigned char *)(imFontAlpha.data))[p] = 0xff; ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFont.data)[i]; } - + UnloadImage(imFont); imFont = imFontAlpha; } @@ -1784,6 +1838,7 @@ static Font LoadBMFont(const char *fileName) // Fill font characters info data font.baseSize = fontSize; font.charsCount = charsCount; + font.charsPadding = 0; font.chars = (CharInfo *)RL_MALLOC(charsCount*sizeof(CharInfo)); font.recs = (Rectangle *)RL_MALLOC(charsCount*sizeof(Rectangle)); @@ -1810,7 +1865,7 @@ static Font LoadBMFont(const char *fileName) } UnloadImage(imFont); - RL_FREE(fileText); + UnloadFileText(fileText); if (font.texture.id == 0) { diff --git a/raylib/textures.c b/raylib/textures.c index 1889fdd..db562bd 100644 --- a/raylib/textures.c +++ b/raylib/textures.c @@ -38,7 +38,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2013-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2013-2021 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. @@ -65,14 +65,13 @@ #endif #include // Required for: malloc(), free() -#include // Required for: FILE, fopen(), fclose(), fread() #include // Required for: strlen() [Used in ImageTextEx()] #include // Required for: fabsf() #include "utils.h" // Required for: fopen() Android mapping #include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3 or ES2 - // Required for: rlLoadTexture() rlDeleteTextures(), + // Required for: rlLoadTexture() rlUnloadTexture(), // rlGenerateMipmaps(), some funcs for DrawTexturePro() // Support only desired texture formats on stb_image @@ -148,8 +147,8 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -#ifndef UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD - #define UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 +#ifndef PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD + #define PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD 50 // Threshold over 255 to set alpha as 0 #endif //---------------------------------------------------------------------------------- @@ -171,21 +170,22 @@ // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_DDS) -static Image LoadDDS(const char *fileName); // Load DDS file +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize); // Load DDS file data #endif #if defined(SUPPORT_FILEFORMAT_PKM) -static Image LoadPKM(const char *fileName); // Load PKM file +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize); // Load PKM file data #endif #if defined(SUPPORT_FILEFORMAT_KTX) -static Image LoadKTX(const char *fileName); // Load KTX file +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize); // Load KTX file data static int SaveKTX(Image image, const char *fileName); // Save image data as KTX file #endif #if defined(SUPPORT_FILEFORMAT_PVR) -static Image LoadPVR(const char *fileName); // Load PVR file +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize); // Load PVR file data #endif #if defined(SUPPORT_FILEFORMAT_ASTC) -static Image LoadASTC(const char *fileName); // Load ASTC file +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize); // Load ASTC file data #endif +static Vector4 *LoadImageDataNormalized(Image image); // Load pixel data from image as Vector4 array (float normalized) //---------------------------------------------------------------------------------- // Module Functions Definition @@ -207,100 +207,20 @@ Image LoadImage(const char *fileName) #define STBI_REQUIRED #endif -#if defined(SUPPORT_FILEFORMAT_PNG) - if ((IsFileExtension(fileName, ".png")) -#else - if ((false) -#endif -#if defined(SUPPORT_FILEFORMAT_BMP) - || (IsFileExtension(fileName, ".bmp")) -#endif -#if defined(SUPPORT_FILEFORMAT_TGA) - || (IsFileExtension(fileName, ".tga")) -#endif -#if defined(SUPPORT_FILEFORMAT_JPG) - || (IsFileExtension(fileName, ".jpg")) -#endif -#if defined(SUPPORT_FILEFORMAT_GIF) - || (IsFileExtension(fileName, ".gif")) -#endif -#if defined(SUPPORT_FILEFORMAT_PIC) - || (IsFileExtension(fileName, ".pic")) -#endif -#if defined(SUPPORT_FILEFORMAT_PSD) - || (IsFileExtension(fileName, ".psd")) -#endif - ) + // Loading file to memory + unsigned int fileSize = 0; + unsigned char *fileData = LoadFileData(fileName, &fileSize); + + if (fileData != NULL) { -#if defined(STBI_REQUIRED) - // NOTE: Using stb_image to load images (Supports multiple image formats) + // Loading image from memory data + image = LoadImageFromMemory(GetFileExtension(fileName), fileData, fileSize); - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); + if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height); + else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName); - if (fileData != NULL) - { - int comp = 0; - image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); - - image.mipmaps = 1; - - if (comp == 1) image.format = UNCOMPRESSED_GRAYSCALE; - else if (comp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA; - else if (comp == 3) image.format = UNCOMPRESSED_R8G8B8; - else if (comp == 4) image.format = UNCOMPRESSED_R8G8B8A8; - - RL_FREE(fileData); - } -#endif + RL_FREE(fileData); } -#if defined(SUPPORT_FILEFORMAT_HDR) - else if (IsFileExtension(fileName, ".hdr")) - { -#if defined(STBI_REQUIRED) - unsigned int dataSize = 0; - unsigned char *fileData = LoadFileData(fileName, &dataSize); - - if (fileData != NULL) - { - int comp = 0; - image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); - - image.mipmaps = 1; - - if (comp == 1) image.format = UNCOMPRESSED_R32; - else if (comp == 3) image.format = UNCOMPRESSED_R32G32B32; - else if (comp == 4) image.format = UNCOMPRESSED_R32G32B32A32; - else - { - TRACELOG(LOG_WARNING, "IMAGE: [%s] HDR fileformat not supported", fileName); - UnloadImage(image); - } - - RL_FREE(fileData); - } -#endif - } -#endif -#if defined(SUPPORT_FILEFORMAT_DDS) - else if (IsFileExtension(fileName, ".dds")) image = LoadDDS(fileName); -#endif -#if defined(SUPPORT_FILEFORMAT_PKM) - else if (IsFileExtension(fileName, ".pkm")) image = LoadPKM(fileName); -#endif -#if defined(SUPPORT_FILEFORMAT_KTX) - else if (IsFileExtension(fileName, ".ktx")) image = LoadKTX(fileName); -#endif -#if defined(SUPPORT_FILEFORMAT_PVR) - else if (IsFileExtension(fileName, ".pvr")) image = LoadPVR(fileName); -#endif -#if defined(SUPPORT_FILEFORMAT_ASTC) - else if (IsFileExtension(fileName, ".astc")) image = LoadASTC(fileName); -#endif - else TRACELOG(LOG_WARNING, "IMAGE: [%s] Fileformat not supported", fileName); - - if (image.data != NULL) TRACELOG(LOG_INFO, "IMAGE: [%s] Data loaded successfully (%ix%i)", fileName, image.width, image.height); - else TRACELOG(LOG_WARNING, "IMAGE: [%s] Failed to load data", fileName); return image; } @@ -342,7 +262,7 @@ Image LoadImageAnim(const char *fileName, int *frames) { Image image = { 0 }; int framesCount = 1; - + #if defined(SUPPORT_FILEFORMAT_GIF) if (IsFileExtension(fileName, ".gif")) { @@ -356,7 +276,7 @@ Image LoadImageAnim(const char *fileName, int *frames) image.data = stbi_load_gif_from_memory(fileData, dataSize, delays, &image.width, &image.height, &framesCount, &comp, 4); image.mipmaps = 1; - image.format = UNCOMPRESSED_R8G8B8A8; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; RL_FREE(fileData); RL_FREE(delays); // NOTE: Frames delays are discarded @@ -366,13 +286,107 @@ Image LoadImageAnim(const char *fileName, int *frames) if (false) { } #endif else image = LoadImage(fileName); - + // TODO: Support APNG animated images? *frames = framesCount; return image; } +// Load image from memory buffer, fileType refers to extension: i.e. ".png" +Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Image image = { 0 }; + + char fileExtLower[16] = { 0 }; + strcpy(fileExtLower, TextToLower(fileType)); + +#if defined(SUPPORT_FILEFORMAT_PNG) + if ((TextIsEqual(fileExtLower, ".png")) +#else + if ((false) +#endif +#if defined(SUPPORT_FILEFORMAT_BMP) + || (TextIsEqual(fileExtLower, ".bmp")) +#endif +#if defined(SUPPORT_FILEFORMAT_TGA) + || (TextIsEqual(fileExtLower, ".tga")) +#endif +#if defined(SUPPORT_FILEFORMAT_JPG) + || (TextIsEqual(fileExtLower, ".jpg") || + TextIsEqual(fileExtLower, ".jpeg")) +#endif +#if defined(SUPPORT_FILEFORMAT_GIF) + || (TextIsEqual(fileExtLower, ".gif")) +#endif +#if defined(SUPPORT_FILEFORMAT_PIC) + || (TextIsEqual(fileExtLower, ".pic")) +#endif +#if defined(SUPPORT_FILEFORMAT_PSD) + || (TextIsEqual(fileExtLower, ".psd")) +#endif + ) + { +#if defined(STBI_REQUIRED) + // NOTE: Using stb_image to load images (Supports multiple image formats) + + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_load_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (comp == 2) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + } +#endif + } +#if defined(SUPPORT_FILEFORMAT_HDR) + else if (TextIsEqual(fileExtLower, ".hdr")) + { +#if defined(STBI_REQUIRED) + if (fileData != NULL) + { + int comp = 0; + image.data = stbi_loadf_from_memory(fileData, dataSize, &image.width, &image.height, &comp, 0); + + image.mipmaps = 1; + + if (comp == 1) image.format = PIXELFORMAT_UNCOMPRESSED_R32; + else if (comp == 3) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32; + else if (comp == 4) image.format = PIXELFORMAT_UNCOMPRESSED_R32G32B32A32; + else + { + TRACELOG(LOG_WARNING, "IMAGE: HDR file format not supported"); + UnloadImage(image); + } + } +#endif + } +#endif +#if defined(SUPPORT_FILEFORMAT_DDS) + else if (TextIsEqual(fileExtLower, ".dds")) image = LoadDDS(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_PKM) + else if (TextIsEqual(fileExtLower, ".pkm")) image = LoadPKM(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_KTX) + else if (TextIsEqual(fileExtLower, ".ktx")) image = LoadKTX(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_PVR) + else if (TextIsEqual(fileExtLower, ".pvr")) image = LoadPVR(fileData, dataSize); +#endif +#if defined(SUPPORT_FILEFORMAT_ASTC) + else if (TextIsEqual(fileExtLower, ".astc")) image = LoadASTC(fileData, dataSize); +#endif + else TRACELOG(LOG_WARNING, "IMAGE: File format not supported"); + + return image; +} + // Unload image from CPU memory (RAM) void UnloadImage(Image image) { @@ -381,7 +395,7 @@ void UnloadImage(Image image) // Export image data to file // NOTE: File format depends on fileName extension -void ExportImage(Image image, const char *fileName) +bool ExportImage(Image image, const char *fileName) { int success = 0; @@ -389,20 +403,26 @@ void ExportImage(Image image, const char *fileName) int channels = 4; bool allocatedData = false; unsigned char *imgData = (unsigned char *)image.data; - - if (image.format == UNCOMPRESSED_GRAYSCALE) channels = 1; - else if (image.format == UNCOMPRESSED_GRAY_ALPHA) channels = 2; - else if (image.format == UNCOMPRESSED_R8G8B8) channels = 3; - else if (image.format == UNCOMPRESSED_R8G8B8A8) channels = 4; + + if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) channels = 1; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) channels = 2; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) channels = 3; + else if (image.format == PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) channels = 4; else { // NOTE: Getting Color array as RGBA unsigned char values - imgData = (unsigned char *)GetImageData(image); + imgData = (unsigned char *)LoadImageColors(image); allocatedData = true; } - + #if defined(SUPPORT_FILEFORMAT_PNG) - if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, channels, imgData, image.width*channels); + if (IsFileExtension(fileName, ".png")) + { + int dataSize = 0; + unsigned char *fileData = stbi_write_png_to_mem((const unsigned char *)imgData, image.width*channels, image.width, image.height, channels, &dataSize); + success = SaveFileData(fileName, fileData, dataSize); + RL_FREE(fileData); + } #else if (false) {} #endif @@ -413,7 +433,7 @@ void ExportImage(Image image, const char *fileName) else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, channels, imgData); #endif #if defined(SUPPORT_FILEFORMAT_JPG) - else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 80); // JPG quality: between 1 and 100 + else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, channels, imgData, 90); // JPG quality: between 1 and 100 #endif #if defined(SUPPORT_FILEFORMAT_KTX) else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName); @@ -422,26 +442,29 @@ void ExportImage(Image image, const char *fileName) { // Export raw pixel data (without header) // NOTE: It's up to the user to track image parameters - SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); - success = true; + success = SaveFileData(fileName, image.data, GetPixelDataSize(image.width, image.height, image.format)); } - + if (allocatedData) RL_FREE(imgData); #endif // SUPPORT_IMAGE_EXPORT if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Image exported successfully", fileName); else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export image", fileName); + + return success; } // Export image as code file (.h) defining an array of bytes -void ExportImageAsCode(Image image, const char *fileName) +bool ExportImageAsCode(Image image, const char *fileName) { + bool success = false; + #ifndef TEXT_BYTES_PER_LINE #define TEXT_BYTES_PER_LINE 20 #endif int dataSize = GetPixelDataSize(image.width, image.height, image.format); - + // NOTE: Text data buffer size is estimated considering image data size in bytes // and requiring 6 char bytes for every byte: "0x00, " char *txtData = (char *)RL_CALLOC(6*dataSize + 2000, sizeof(char)); @@ -474,9 +497,11 @@ void ExportImageAsCode(Image image, const char *fileName) bytesCount += sprintf(txtData + bytesCount, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]); // NOTE: Text data length exported is determined by '\0' (NULL) character - SaveFileText(fileName, txtData); + success = SaveFileText(fileName, txtData); RL_FREE(txtData); + + return success; } //------------------------------------------------------------------------------------ @@ -493,7 +518,7 @@ Image GenImageColor(int width, int height, Color color) .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -522,7 +547,7 @@ Image GenImageGradientV(int width, int height, Color top, Color bottom) .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -550,7 +575,7 @@ Image GenImageGradientH(int width, int height, Color left, Color right) .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -573,7 +598,7 @@ Image GenImageGradientRadial(int width, int height, float density, Color inner, float dist = hypotf((float)x - centerX, (float)y - centerY); float factor = (dist - radius*density)/(radius*(1.0f - density)); - factor = (float)fmax(factor, 0.f); + factor = (float)fmax(factor, 0.0f); factor = (float)fmin(factor, 1.f); // dist can be bigger than radius so we have to check pixels[y*width + x].r = (int)((float)outer.r*factor + (float)inner.r*(1.0f - factor)); @@ -587,7 +612,7 @@ Image GenImageGradientRadial(int width, int height, float density, Color inner, .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -612,7 +637,7 @@ Image GenImageChecked(int width, int height, int checksX, int checksY, Color col .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -634,7 +659,7 @@ Image GenImageWhiteNoise(int width, int height, float factor) .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -670,7 +695,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -684,7 +709,7 @@ Image GenImageCellular(int width, int height, int tileSize) int seedsPerRow = width/tileSize; int seedsPerCol = height/tileSize; - int seedsCount = seedsPerRow * seedsPerCol; + int seedsCount = seedsPerRow*seedsPerCol; Vector2 *seeds = (Vector2 *)RL_MALLOC(seedsCount*sizeof(Vector2)); @@ -735,7 +760,7 @@ Image GenImageCellular(int width, int height, int tileSize) .data = pixels, .width = width, .height = height, - .format = UNCOMPRESSED_R8G8B8A8, + .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, .mipmaps = 1 }; @@ -787,17 +812,17 @@ Image ImageCopy(Image image) Image ImageFromImage(Image image, Rectangle rec) { Image result = { 0 }; - + int bytesPerPixel = GetPixelDataSize(1, 1, image.format); - + // TODO: Check rec is valid? - result.width = rec.width; - result.height = rec.height; - result.data = RL_CALLOC(rec.width*rec.height*bytesPerPixel, 1); + result.width = (int)rec.width; + result.height = (int)rec.height; + result.data = RL_CALLOC((int)(rec.width*rec.height)*bytesPerPixel, 1); result.format = image.format; result.mipmaps = 1; - + for (int y = 0; y < rec.height; y++) { memcpy(((unsigned char *)result.data) + y*(int)rec.width*bytesPerPixel, ((unsigned char *)image.data) + ((y + (int)rec.y)*image.width + (int)rec.x)*bytesPerPixel, (int)rec.width*bytesPerPixel); @@ -818,27 +843,27 @@ void ImageCrop(Image *image, Rectangle crop) if (crop.y < 0) { crop.height += crop.y; crop.y = 0; } if ((crop.x + crop.width) > image->width) crop.width = image->width - crop.x; if ((crop.y + crop.height) > image->height) crop.height = image->height - crop.y; - if ((crop.x > image->width) || (crop.y > image->height)) + if ((crop.x > image->width) || (crop.y > image->height)) { TRACELOG(LOG_WARNING, "IMAGE: Failed to crop, rectangle out of bounds"); return; } if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); - - unsigned char *croppedData = (unsigned char *)RL_MALLOC(crop.width*crop.height*bytesPerPixel); - + + unsigned char *croppedData = (unsigned char *)RL_MALLOC((int)(crop.width*crop.height)*bytesPerPixel); + // OPTION 1: Move cropped data line-by-line for (int y = (int)crop.y, offsetSize = 0; y < (int)(crop.y + crop.height); y++) { memcpy(croppedData + offsetSize, ((unsigned char *)image->data) + (y*image->width + (int)crop.x)*bytesPerPixel, (int)crop.width*bytesPerPixel); offsetSize += ((int)crop.width*bytesPerPixel); } - + /* // OPTION 2: Move cropped data pixel-by-pixel or byte-by-byte for (int y = (int)crop.y; y < (int)(crop.y + crop.height); y++) @@ -866,9 +891,9 @@ void ImageFormat(Image *image, int newFormat) if ((newFormat != 0) && (image->format != newFormat)) { - if ((image->format < COMPRESSED_DXT1_RGB) && (newFormat < COMPRESSED_DXT1_RGB)) + if ((image->format < PIXELFORMAT_COMPRESSED_DXT1_RGB) && (newFormat < PIXELFORMAT_COMPRESSED_DXT1_RGB)) { - Vector4 *pixels = GetImageDataNormalized(*image); // Supports 8 to 32 bit per channel + Vector4 *pixels = LoadImageDataNormalized(*image); // Supports 8 to 32 bit per channel RL_FREE(image->data); // WARNING! We loose mipmaps data --> Regenerated at the end... image->data = NULL; @@ -878,7 +903,7 @@ void ImageFormat(Image *image, int newFormat) switch (image->format) { - case UNCOMPRESSED_GRAYSCALE: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*sizeof(unsigned char)); @@ -888,7 +913,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*2*sizeof(unsigned char)); @@ -899,7 +924,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); @@ -917,7 +942,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*3*sizeof(unsigned char)); @@ -928,7 +953,7 @@ void ImageFormat(Image *image, int newFormat) ((unsigned char *)image->data)[i + 2] = (unsigned char)(pixels[k].z*255.0f); } } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); @@ -942,13 +967,13 @@ void ImageFormat(Image *image, int newFormat) r = (unsigned char)(round(pixels[i].x*31.0f)); g = (unsigned char)(round(pixels[i].y*31.0f)); b = (unsigned char)(round(pixels[i].z*31.0f)); - a = (pixels[i].w > ((float)UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; + a = (pixels[i].w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0; ((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; } } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { image->data = (unsigned short *)RL_MALLOC(image->width*image->height*sizeof(unsigned short)); @@ -968,7 +993,7 @@ void ImageFormat(Image *image, int newFormat) } } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { image->data = (unsigned char *)RL_MALLOC(image->width*image->height*4*sizeof(unsigned char)); @@ -980,7 +1005,7 @@ void ImageFormat(Image *image, int newFormat) ((unsigned char *)image->data)[i + 3] = (unsigned char)(pixels[k].w*255.0f); } } break; - case UNCOMPRESSED_R32: + case PIXELFORMAT_UNCOMPRESSED_R32: { // WARNING: Image is converted to GRAYSCALE eqeuivalent 32bit @@ -991,7 +1016,7 @@ void ImageFormat(Image *image, int newFormat) ((float *)image->data)[i] = (float)(pixels[i].x*0.299f + pixels[i].y*0.587f + pixels[i].z*0.114f); } } break; - case UNCOMPRESSED_R32G32B32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: { image->data = (float *)RL_MALLOC(image->width*image->height*3*sizeof(float)); @@ -1002,7 +1027,7 @@ void ImageFormat(Image *image, int newFormat) ((float *)image->data)[i + 2] = pixels[k].z; } } break; - case UNCOMPRESSED_R32G32B32A32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: { image->data = (float *)RL_MALLOC(image->width*image->height*4*sizeof(float)); @@ -1056,7 +1081,7 @@ Image ImageText(const char *text, int fontSize, Color color) { int defaultFontSize = 10; // Default Font chars height in pixel if (fontSize < defaultFontSize) fontSize = defaultFontSize; - int spacing = fontSize / defaultFontSize; + int spacing = fontSize/defaultFontSize; Image imText = ImageTextEx(GetFontDefault(), text, (float)fontSize, (float)spacing, color); @@ -1143,17 +1168,17 @@ void ImageAlphaClear(Image *image, Color color, float threshold) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else { switch (image->format) { - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { unsigned char thresholdValue = (unsigned char)(threshold*255.0f); - for (int i = 1; i < image->width*image->height*2; i += 2) + for (int i = 1; i < image->width*image->height*2; i += 2) { if (((unsigned char *)image->data)[i] <= thresholdValue) { @@ -1162,10 +1187,10 @@ void ImageAlphaClear(Image *image, Color color, float threshold) } } } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { unsigned char thresholdValue = ((threshold < 0.5f)? 0 : 1); - + unsigned char r = (unsigned char)(round((float)color.r*31.0f)); unsigned char g = (unsigned char)(round((float)color.g*31.0f)); unsigned char b = (unsigned char)(round((float)color.b*31.0f)); @@ -1179,10 +1204,10 @@ void ImageAlphaClear(Image *image, Color color, float threshold) } } } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { unsigned char thresholdValue = (unsigned char)(threshold*15.0f); - + unsigned char r = (unsigned char)(round((float)color.r*15.0f)); unsigned char g = (unsigned char)(round((float)color.g*15.0f)); unsigned char b = (unsigned char)(round((float)color.b*15.0f)); @@ -1196,10 +1221,10 @@ void ImageAlphaClear(Image *image, Color color, float threshold) } } } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { unsigned char thresholdValue = (unsigned char)(threshold*255.0f); - for (int i = 3; i < image->width*image->height*4; i += 4) + for (int i = 3; i < image->width*image->height*4; i += 4) { if (((unsigned char *)image->data)[i] <= thresholdValue) { @@ -1210,9 +1235,9 @@ void ImageAlphaClear(Image *image, Color color, float threshold) } } } break; - case UNCOMPRESSED_R32G32B32A32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: { - for (int i = 3; i < image->width*image->height*4; i += 4) + for (int i = 3; i < image->width*image->height*4; i += 4) { if (((float *)image->data)[i] <= threshold) { @@ -1237,7 +1262,7 @@ void ImageAlphaMask(Image *image, Image alphaMask) { TRACELOG(LOG_WARNING, "IMAGE: Alpha mask must be same size as image"); } - else if (image->format >= COMPRESSED_DXT1_RGB) + else if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) { TRACELOG(LOG_WARNING, "IMAGE: Alpha mask can not be applied to compressed data formats"); } @@ -1245,10 +1270,10 @@ void ImageAlphaMask(Image *image, Image alphaMask) { // Force mask to be Grayscale Image mask = ImageCopy(alphaMask); - if (mask.format != UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, UNCOMPRESSED_GRAYSCALE); + if (mask.format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) ImageFormat(&mask, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); // In case image is only grayscale, we just add alpha channel - if (image->format == UNCOMPRESSED_GRAYSCALE) + if (image->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) { unsigned char *data = (unsigned char *)RL_MALLOC(image->width*image->height*2); @@ -1261,12 +1286,12 @@ void ImageAlphaMask(Image *image, Image alphaMask) RL_FREE(image->data); image->data = data; - image->format = UNCOMPRESSED_GRAY_ALPHA; + image->format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; } else { // Convert image to RGBA - if (image->format != UNCOMPRESSED_R8G8B8A8) ImageFormat(image, UNCOMPRESSED_R8G8B8A8); + if (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8) ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8); // Apply alpha mask to alpha channel for (int i = 0, k = 3; (i < mask.width*mask.height) || (i < image->width*image->height); i++, k += 4) @@ -1286,7 +1311,7 @@ void ImageAlphaPremultiply(Image *image) if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; float alpha = 0.0f; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int i = 0; i < image->width*image->height; i++) { @@ -1309,8 +1334,8 @@ void ImageAlphaPremultiply(Image *image) int format = image->format; image->data = pixels; - image->format = UNCOMPRESSED_R8G8B8A8; - + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); } @@ -1322,21 +1347,21 @@ void ImageResize(Image *image, int newWidth, int newHeight) { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - + bool fastPath = true; - if ((image->format != UNCOMPRESSED_GRAYSCALE) && (image->format != UNCOMPRESSED_GRAY_ALPHA) && (image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) fastPath = true; - + if ((image->format != PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) && (image->format != PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)) fastPath = true; + if (fastPath) { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *output = RL_MALLOC(newWidth*newHeight*bytesPerPixel); - + switch (image->format) { - case UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 1); break; - case UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 2); break; - case UNCOMPRESSED_R8G8B8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 3); break; - case UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 4); break; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 1); break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 2); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 3); break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: stbir_resize_uint8((unsigned char *)image->data, image->width, image->height, 0, output, newWidth, newHeight, 0, 4); break; default: break; } @@ -1348,21 +1373,21 @@ void ImageResize(Image *image, int newWidth, int newHeight) else { // Get data as Color pixels array to work with it - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); // NOTE: Color data is casted to (unsigned char *), there shouldn't been any problem... stbir_resize_uint8((unsigned char *)pixels, image->width, image->height, 0, (unsigned char *)output, newWidth, newHeight, 0, 4); int format = image->format; - - RL_FREE(pixels); + + UnloadImageColors(pixels); RL_FREE(image->data); image->data = output; image->width = newWidth; image->height = newHeight; - image->format = UNCOMPRESSED_R8G8B8A8; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; ImageFormat(image, format); // Reformat 32bit RGBA image to original format } @@ -1374,7 +1399,7 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); Color *output = (Color *)RL_MALLOC(newWidth*newHeight*sizeof(Color)); // EDIT: added +1 to account for an early rounding problem @@ -1396,15 +1421,15 @@ void ImageResizeNN(Image *image,int newWidth,int newHeight) int format = image->format; RL_FREE(image->data); - + image->data = output; image->width = newWidth; image->height = newHeight; - image->format = UNCOMPRESSED_R8G8B8A8; + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; ImageFormat(image, format); // Reformat 32bit RGBA image to original format - RL_FREE(pixels); + UnloadImageColors(pixels); } // Resize canvas and fill with color @@ -1413,46 +1438,46 @@ void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, i { // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - + if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else if ((newWidth != image->width) || (newHeight != image->height)) { - Rectangle srcRec = { 0, 0, image->width, image->height }; - Vector2 dstPos = { offsetX, offsetY }; - + Rectangle srcRec = { 0, 0, (float)image->width, (float)image->height }; + Vector2 dstPos = { (float)offsetX, (float)offsetY }; + if (offsetX < 0) { - srcRec.x = -offsetX; - srcRec.width += offsetX; + srcRec.x = (float)-offsetX; + srcRec.width += (float)offsetX; dstPos.x = 0; } - else if ((offsetX + image->width) > newWidth) srcRec.width = newWidth - offsetX; + else if ((offsetX + image->width) > newWidth) srcRec.width = (float)(newWidth - offsetX); if (offsetY < 0) { - srcRec.y = -offsetY; - srcRec.height += offsetY; + srcRec.y = (float)-offsetY; + srcRec.height += (float)offsetY; dstPos.y = 0; } - else if ((offsetY + image->height) > newHeight) srcRec.height = newHeight - offsetY; + else if ((offsetY + image->height) > newHeight) srcRec.height = (float)(newHeight - offsetY); - if (newWidth < srcRec.width) srcRec.width = newWidth; - if (newHeight < srcRec.height) srcRec.height = newHeight; + if (newWidth < srcRec.width) srcRec.width = (float)newWidth; + if (newHeight < srcRec.height) srcRec.height = (float)newHeight; int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *resizedData = (unsigned char *)RL_CALLOC(newWidth*newHeight*bytesPerPixel, 1); - + // TODO: Fill resizedData with fill color (must be formatted to image->format) - + int dstOffsetSize = ((int)dstPos.y*newWidth + (int)dstPos.x)*bytesPerPixel; - + for (int y = 0; y < (int)srcRec.height; y++) { memcpy(resizedData + dstOffsetSize, ((unsigned char *)image->data) + ((y + (int)srcRec.y)*image->width + (int)srcRec.x)*bytesPerPixel, (int)srcRec.width*bytesPerPixel); dstOffsetSize += (newWidth*bytesPerPixel); } - + RL_FREE(image->data); image->data = resizedData; image->width = newWidth; @@ -1538,7 +1563,7 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - if (image->format >= COMPRESSED_DXT1_RGB) + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) { TRACELOG(LOG_WARNING, "IMAGE: Compressed data formats can not be dithered"); return; @@ -1550,19 +1575,19 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } else { - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); RL_FREE(image->data); // free old image data - if ((image->format != UNCOMPRESSED_R8G8B8) && (image->format != UNCOMPRESSED_R8G8B8A8)) + if ((image->format != PIXELFORMAT_UNCOMPRESSED_R8G8B8) && (image->format != PIXELFORMAT_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; + if ((rBpp == 5) && (gBpp == 6) && (bBpp == 5) && (aBpp == 0)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((rBpp == 5) && (gBpp == 5) && (bBpp == 5) && (aBpp == 1)) image->format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((rBpp == 4) && (gBpp == 4) && (bBpp == 4) && (aBpp == 4)) image->format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; else { image->format = 0; @@ -1638,7 +1663,7 @@ void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp) } } - RL_FREE(pixels); + UnloadImageColors(pixels); } } @@ -1649,18 +1674,18 @@ void ImageFlipVertical(Image *image) if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); - + for (int i = (image->height - 1), offsetSize = 0; i >= 0; i--) { memcpy(flippedData + offsetSize, ((unsigned char *)image->data) + i*image->width*bytesPerPixel, image->width*bytesPerPixel); offsetSize += image->width*bytesPerPixel; } - + RL_FREE(image->data); image->data = flippedData; } @@ -1673,24 +1698,24 @@ void ImageFlipHorizontal(Image *image) if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *flippedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); - + for (int y = 0; y < image->height; y++) { for (int x = 0; x < image->width; x++) { // OPTION 1: Move pixels with memcopy() //memcpy(flippedData + (y*image->width + x)*bytesPerPixel, ((unsigned char *)image->data) + (y*image->width + (image->width - 1 - x))*bytesPerPixel, bytesPerPixel); - + // OPTION 2: Just copy data pixel by pixel for (int i = 0; i < bytesPerPixel; i++) flippedData[(y*image->width + x)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - 1 - x))*bytesPerPixel + i]; } } - + RL_FREE(image->data); image->data = flippedData; @@ -1718,12 +1743,12 @@ void ImageRotateCW(Image *image) if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); - + for (int y = 0; y < image->height; y++) { for (int x = 0; x < image->width; x++) @@ -1732,12 +1757,12 @@ void ImageRotateCW(Image *image) for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + (image->height - y - 1))*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + x)*bytesPerPixel + i]; } } - + RL_FREE(image->data); image->data = rotatedData; int width = image->width; int height = image-> height; - + image->width = height; image->height = width; } @@ -1750,12 +1775,12 @@ void ImageRotateCCW(Image *image) if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; if (image->mipmaps > 1) TRACELOG(LOG_WARNING, "Image manipulation only applied to base mipmap level"); - if (image->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); + if (image->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image manipulation not supported for compressed formats"); else { int bytesPerPixel = GetPixelDataSize(1, 1, image->format); unsigned char *rotatedData = (unsigned char *)RL_MALLOC(image->width*image->height*bytesPerPixel); - + for (int y = 0; y < image->height; y++) { for (int x = 0; x < image->width; x++) @@ -1764,12 +1789,12 @@ void ImageRotateCCW(Image *image) for (int i = 0; i < bytesPerPixel; i++) rotatedData[(x*image->height + y)*bytesPerPixel + i] = ((unsigned char *)image->data)[(y*image->width + (image->width - x - 1))*bytesPerPixel + i]; } } - + RL_FREE(image->data); image->data = rotatedData; int width = image->width; int height = image-> height; - + image->width = height; image->height = width; } @@ -1781,7 +1806,7 @@ void ImageColorTint(Image *image, Color color) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); float cR = (float)color.r/255; float cG = (float)color.g/255; @@ -1792,7 +1817,7 @@ void ImageColorTint(Image *image, Color color) { for (int x = 0; x < image->width; x++) { - int index = y * image->width + x; + int index = y*image->width + x; unsigned char r = (unsigned char)(((float)pixels[index].r/255*cR)*255.0f); unsigned char g = (unsigned char)(((float)pixels[index].g/255*cG)*255.0f); unsigned char b = (unsigned char)(((float)pixels[index].b/255*cB)*255.0f); @@ -1807,10 +1832,10 @@ void ImageColorTint(Image *image, Color color) int format = image->format; RL_FREE(image->data); - + image->data = pixels; - image->format = UNCOMPRESSED_R8G8B8A8; - + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); } @@ -1820,7 +1845,7 @@ void ImageColorInvert(Image *image) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -1834,17 +1859,17 @@ void ImageColorInvert(Image *image) int format = image->format; RL_FREE(image->data); - + image->data = pixels; - image->format = UNCOMPRESSED_R8G8B8A8; - + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); } // Modify image color: grayscale void ImageColorGrayscale(Image *image) { - ImageFormat(image, UNCOMPRESSED_GRAYSCALE); + ImageFormat(image, PIXELFORMAT_UNCOMPRESSED_GRAYSCALE); } // Modify image color: contrast @@ -1860,7 +1885,7 @@ void ImageColorContrast(Image *image, float contrast) contrast = (100.0f + contrast)/100.0f; contrast *= contrast; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -1898,10 +1923,10 @@ void ImageColorContrast(Image *image, float contrast) int format = image->format; RL_FREE(image->data); - + image->data = pixels; - image->format = UNCOMPRESSED_R8G8B8A8; - + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); } @@ -1915,7 +1940,7 @@ void ImageColorBrightness(Image *image, int brightness) if (brightness < -255) brightness = -255; if (brightness > 255) brightness = 255; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -1942,10 +1967,10 @@ void ImageColorBrightness(Image *image, int brightness) int format = image->format; RL_FREE(image->data); - + image->data = pixels; - image->format = UNCOMPRESSED_R8G8B8A8; - + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); } @@ -1955,7 +1980,7 @@ void ImageColorReplace(Image *image, Color color, Color replace) // Security check to avoid program crash if ((image->data == NULL) || (image->width == 0) || (image->height == 0)) return; - Color *pixels = GetImageData(*image); + Color *pixels = LoadImageColors(*image); for (int y = 0; y < image->height; y++) { @@ -1976,33 +2001,34 @@ void ImageColorReplace(Image *image, Color color, Color replace) int format = image->format; RL_FREE(image->data); - + image->data = pixels; - image->format = UNCOMPRESSED_R8G8B8A8; - + image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + ImageFormat(image, format); } #endif // SUPPORT_IMAGE_MANIPULATION -// Get pixel data from image in the form of Color struct array -Color *GetImageData(Image image) +// Load color data from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImageColors(); +Color *LoadImageColors(Image image) { if ((image.width == 0) || (image.height == 0)) return NULL; Color *pixels = (Color *)RL_MALLOC(image.width*image.height*sizeof(Color)); - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); else { - if ((image.format == UNCOMPRESSED_R32) || - (image.format == UNCOMPRESSED_R32G32B32) || - (image.format == UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); + if ((image.format == PIXELFORMAT_UNCOMPRESSED_R32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32) || + (image.format == PIXELFORMAT_UNCOMPRESSED_R32G32B32A32)) TRACELOG(LOG_WARNING, "IMAGE: Pixel format converted from 32bit to 8bit per channel"); for (int i = 0, k = 0; i < image.width*image.height; i++) { switch (image.format) { - case UNCOMPRESSED_GRAYSCALE: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { pixels[i].r = ((unsigned char *)image.data)[i]; pixels[i].g = ((unsigned char *)image.data)[i]; @@ -2010,7 +2036,7 @@ Color *GetImageData(Image image) pixels[i].a = 255; } break; - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { pixels[i].r = ((unsigned char *)image.data)[k]; pixels[i].g = ((unsigned char *)image.data)[k]; @@ -2019,7 +2045,7 @@ Color *GetImageData(Image image) k += 2; } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { unsigned short pixel = ((unsigned short *)image.data)[i]; @@ -2029,7 +2055,7 @@ Color *GetImageData(Image image) pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); } break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { unsigned short pixel = ((unsigned short *)image.data)[i]; @@ -2039,7 +2065,7 @@ Color *GetImageData(Image image) pixels[i].a = 255; } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { unsigned short pixel = ((unsigned short *)image.data)[i]; @@ -2049,7 +2075,7 @@ Color *GetImageData(Image image) pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { pixels[i].r = ((unsigned char *)image.data)[k]; pixels[i].g = ((unsigned char *)image.data)[k + 1]; @@ -2058,7 +2084,7 @@ Color *GetImageData(Image image) k += 4; } break; - case UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; @@ -2067,7 +2093,7 @@ Color *GetImageData(Image image) k += 3; } break; - case UNCOMPRESSED_R32: + case PIXELFORMAT_UNCOMPRESSED_R32: { pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); pixels[i].g = 0; @@ -2075,7 +2101,7 @@ Color *GetImageData(Image image) pixels[i].a = 255; } break; - case UNCOMPRESSED_R32G32B32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: { pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); pixels[i].g = (unsigned char)(((float *)image.data)[k + 1]*255.0f); @@ -2084,7 +2110,7 @@ Color *GetImageData(Image image) k += 3; } break; - case UNCOMPRESSED_R32G32B32A32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: { pixels[i].r = (unsigned char)(((float *)image.data)[k]*255.0f); pixels[i].g = (unsigned char)(((float *)image.data)[k]*255.0f); @@ -2101,70 +2127,87 @@ Color *GetImageData(Image image) return pixels; } -// Get color palette from image to maximum size -// NOTE: Memory allocated should be freed manually! -Color *GetImagePalette(Image image, int maxPaletteSize, int *extractCount) +// Load colors palette from image as a Color array (RGBA - 32bit) +// NOTE: Memory allocated should be freed using UnloadImagePalette() +Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorsCount) { #define COLOR_EQUAL(col1, col2) ((col1.r == col2.r)&&(col1.g == col2.g)&&(col1.b == col2.b)&&(col1.a == col2.a)) - Color *pixels = GetImageData(image); - Color *palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); - int palCount = 0; - for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + Color *palette = NULL; + Color *pixels = LoadImageColors(image); - for (int i = 0; i < image.width*image.height; i++) + if (pixels != NULL) { - if (pixels[i].a > 0) + palette = (Color *)RL_MALLOC(maxPaletteSize*sizeof(Color)); + + for (int i = 0; i < maxPaletteSize; i++) palette[i] = BLANK; // Set all colors to BLANK + + for (int i = 0; i < image.width*image.height; i++) { - bool colorInPalette = false; - - // Check if the color is already on palette - for (int j = 0; j < maxPaletteSize; j++) + if (pixels[i].a > 0) { - if (COLOR_EQUAL(pixels[i], palette[j])) + bool colorInPalette = false; + + // Check if the color is already on palette + for (int j = 0; j < maxPaletteSize; j++) { - colorInPalette = true; - break; + if (COLOR_EQUAL(pixels[i], palette[j])) + { + colorInPalette = true; + break; + } } - } - // Store color if not on the palette - if (!colorInPalette) - { - palette[palCount] = pixels[i]; // Add pixels[i] to palette - palCount++; - - // We reached the limit of colors supported by palette - if (palCount >= maxPaletteSize) + // Store color if not on the palette + if (!colorInPalette) { - i = image.width*image.height; // Finish palette get - TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + palette[palCount] = pixels[i]; // Add pixels[i] to palette + palCount++; + + // We reached the limit of colors supported by palette + if (palCount >= maxPaletteSize) + { + i = image.width*image.height; // Finish palette get + TRACELOG(LOG_WARNING, "IMAGE: Palette is greater than %i colors", maxPaletteSize); + } } } } + + UnloadImageColors(pixels); } - RL_FREE(pixels); - - *extractCount = palCount; + *colorsCount = palCount; return palette; } +// Unload color data loaded with LoadImageColors() +void UnloadImageColors(Color *colors) +{ + RL_FREE(colors); +} + +// Unload colors palette loaded with LoadImagePalette() +void UnloadImagePalette(Color *colors) +{ + RL_FREE(colors); +} + // Get pixel data from image as Vector4 array (float normalized) -Vector4 *GetImageDataNormalized(Image image) +static Vector4 *LoadImageDataNormalized(Image image) { Vector4 *pixels = (Vector4 *)RL_MALLOC(image.width*image.height*sizeof(Vector4)); - if (image.format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); + if (image.format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "IMAGE: Pixel data retrieval not supported for compressed image formats"); else { for (int i = 0, k = 0; i < image.width*image.height; i++) { switch (image.format) { - case UNCOMPRESSED_GRAYSCALE: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { pixels[i].x = (float)((unsigned char *)image.data)[i]/255.0f; pixels[i].y = (float)((unsigned char *)image.data)[i]/255.0f; @@ -2172,7 +2215,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].w = 1.0f; } break; - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; pixels[i].y = (float)((unsigned char *)image.data)[k]/255.0f; @@ -2181,7 +2224,7 @@ Vector4 *GetImageDataNormalized(Image image) k += 2; } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { unsigned short pixel = ((unsigned short *)image.data)[i]; @@ -2191,7 +2234,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f; } break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { unsigned short pixel = ((unsigned short *)image.data)[i]; @@ -2201,7 +2244,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].w = 1.0f; } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { unsigned short pixel = ((unsigned short *)image.data)[i]; @@ -2211,7 +2254,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].w = (float)(pixel & 0b0000000000001111)*(1.0f/15); } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; @@ -2220,7 +2263,7 @@ Vector4 *GetImageDataNormalized(Image image) k += 4; } break; - case UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { pixels[i].x = (float)((unsigned char *)image.data)[k]/255.0f; pixels[i].y = (float)((unsigned char *)image.data)[k + 1]/255.0f; @@ -2229,7 +2272,7 @@ Vector4 *GetImageDataNormalized(Image image) k += 3; } break; - case UNCOMPRESSED_R32: + case PIXELFORMAT_UNCOMPRESSED_R32: { pixels[i].x = ((float *)image.data)[k]; pixels[i].y = 0.0f; @@ -2237,7 +2280,7 @@ Vector4 *GetImageDataNormalized(Image image) pixels[i].w = 1.0f; } break; - case UNCOMPRESSED_R32G32B32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: { pixels[i].x = ((float *)image.data)[k]; pixels[i].y = ((float *)image.data)[k + 1]; @@ -2246,7 +2289,7 @@ Vector4 *GetImageDataNormalized(Image image) k += 3; } break; - case UNCOMPRESSED_R32G32B32A32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: { pixels[i].x = ((float *)image.data)[k]; pixels[i].y = ((float *)image.data)[k + 1]; @@ -2269,7 +2312,7 @@ Rectangle GetImageAlphaBorder(Image image, float threshold) { Rectangle crop = { 0 }; - Color *pixels = GetImageData(image); + Color *pixels = LoadImageColors(image); if (pixels != NULL) { @@ -2298,7 +2341,7 @@ Rectangle GetImageAlphaBorder(Image image, float threshold) crop = (Rectangle){ (float)xMin, (float)yMin, (float)((xMax + 1) - xMin), (float)((yMax + 1) - yMin) }; } - RL_FREE(pixels); + UnloadImageColors(pixels); } return crop; @@ -2310,7 +2353,7 @@ Rectangle GetImageAlphaBorder(Image image, float threshold) // Clear image background with given color void ImageClearBackground(Image *dst, Color color) { - ImageDrawRectangle(dst, 0, 0, dst->width, dst->height, color); + for (int i = 0; i < dst->width*dst->height; ++i) ImageDrawPixel(dst, i%dst->width, i/dst->width, color); } // Draw pixel within an image @@ -2319,29 +2362,29 @@ void ImageDrawPixel(Image *dst, int x, int y, Color color) { // Security check to avoid program crash if ((dst->data == NULL) || (x < 0) || (x >= dst->width) || (y < 0) || (y >= dst->height)) return; - + switch (dst->format) { - case UNCOMPRESSED_GRAYSCALE: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { // NOTE: Calculate grayscale equivalent color Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - - ((unsigned char *)dst->data)[y*dst->width + x] = gray; - + + ((unsigned char *)dst->data)[y*dst->width + x] = gray; + } break; - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { // NOTE: Calculate grayscale equivalent color Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - + ((unsigned char *)dst->data)[(y*dst->width + x)*2] = gray; ((unsigned char *)dst->data)[(y*dst->width + x)*2 + 1] = color.a; } break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { // NOTE: Calculate R5G6B5 equivalent color Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; @@ -2351,9 +2394,9 @@ void ImageDrawPixel(Image *dst, int x, int y, Color color) unsigned char b = (unsigned char)(round(coln.z*31.0f)); ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; - + } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { // NOTE: Calculate R5G5B5A1 equivalent color Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; @@ -2361,12 +2404,12 @@ void ImageDrawPixel(Image *dst, int x, int y, Color color) unsigned char r = (unsigned char)(round(coln.x*31.0f)); unsigned char g = (unsigned char)(round(coln.y*31.0f)); unsigned char b = (unsigned char)(round(coln.z*31.0f)); - unsigned char a = (coln.w > ((float)UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { // NOTE: Calculate R5G5B5A1 equivalent color Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; @@ -2377,16 +2420,16 @@ void ImageDrawPixel(Image *dst, int x, int y, Color color) unsigned char a = (unsigned char)(round(coln.w*15.0f)); ((unsigned short *)dst->data)[y*dst->width + x] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; - + } break; - case UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { ((unsigned char *)dst->data)[(y*dst->width + x)*3] = color.r; ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 1] = color.g; ((unsigned char *)dst->data)[(y*dst->width + x)*3 + 2] = color.b; - + } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { ((unsigned char *)dst->data)[(y*dst->width + x)*4] = color.r; ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 1] = color.g; @@ -2394,30 +2437,30 @@ void ImageDrawPixel(Image *dst, int x, int y, Color color) ((unsigned char *)dst->data)[(y*dst->width + x)*4 + 3] = color.a; } break; - case UNCOMPRESSED_R32: + case PIXELFORMAT_UNCOMPRESSED_R32: { // NOTE: Calculate grayscale equivalent color (normalized to 32bit) Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; - + ((float *)dst->data)[y*dst->width + x] = coln.x*0.299f + coln.y*0.587f + coln.z*0.114f; + } break; - case UNCOMPRESSED_R32G32B32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: { // NOTE: Calculate R32G32B32 equivalent color (normalized to 32bit) Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; - ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; - ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; - ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; + ((float *)dst->data)[(y*dst->width + x)*3] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*3 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*3 + 2] = coln.z; } break; - case UNCOMPRESSED_R32G32B32A32: + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: { // NOTE: Calculate R32G32B32A32 equivalent color (normalized to 32bit) Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; - ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; - ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; + ((float *)dst->data)[(y*dst->width + x)*4] = coln.x; + ((float *)dst->data)[(y*dst->width + x)*4 + 1] = coln.y; ((float *)dst->data)[(y*dst->width + x)*4 + 2] = coln.z; ((float *)dst->data)[(y*dst->width + x)*4 + 3] = coln.w; @@ -2508,9 +2551,19 @@ void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color) // Security check to avoid program crash if ((dst->data == NULL) || (dst->width == 0) || (dst->height == 0)) return; - Image imRec = GenImageColor((int)rec.width, (int)rec.height, color); - ImageDraw(dst, imRec, (Rectangle){ 0, 0, rec.width, rec.height }, rec, WHITE); - UnloadImage(imRec); + int sy = (int)rec.y; + int ey = sy + (int)rec.height; + + int sx = (int)rec.x; + int ex = sx + (int)rec.width; + + for (int y = sy; y < ey; y++) + { + for (int x = sx; x < ex; x++) + { + ImageDrawPixel(dst, x, y, color); + } + } } // Draw rectangle lines within an image @@ -2531,7 +2584,7 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color (src.data == NULL) || (src.width == 0) || (src.height == 0)) return; if (dst->mipmaps > 1) TRACELOG(LOG_WARNING, "Image drawing only applied to base mipmap level"); - if (dst->format >= COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); + if (dst->format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) TRACELOG(LOG_WARNING, "Image drawing not supported for compressed formats"); else { Image srcMod = { 0 }; // Source copy (in case it was required) @@ -2550,7 +2603,7 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color { srcMod = ImageFromImage(src, srcRec); // Create image from another image ImageResize(&srcMod, (int)dstRec.width, (int)dstRec.height); // Resize to destination rectangle - srcRec = (Rectangle){ 0, 0, srcMod.width, srcMod.height }; + srcRec = (Rectangle){ 0, 0, (float)srcMod.width, (float)srcMod.height }; srcPtr = &srcMod; useSrcMod = true; @@ -2573,8 +2626,8 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color } else if ((dstRec.y + srcRec.height) > dst->height) srcRec.height = dst->height - dstRec.y; - if (dst->width < srcRec.width) srcRec.width = dst->width; - if (dst->height < srcRec.height) srcRec.height = dst->height; + if (dst->width < srcRec.width) srcRec.width = (float)dst->width; + if (dst->height < srcRec.height) srcRec.height = (float)dst->height; // This blitting method is quite fast! The process followed is: // for every pixel -> [get_src_format/get_dst_format -> blend -> format_to_dst] @@ -2589,9 +2642,9 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color Color colSrc, colDst, blend; bool blendRequired = true; - + // Fast path: Avoid blend if source has no alpha to blend - if ((tint.a == 255) && ((srcPtr->format == UNCOMPRESSED_GRAYSCALE) || (srcPtr->format == UNCOMPRESSED_R8G8B8) || (srcPtr->format == UNCOMPRESSED_R5G6B5))) blendRequired = false; + if ((tint.a == 255) && ((srcPtr->format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R8G8B8) || (srcPtr->format == PIXELFORMAT_UNCOMPRESSED_R5G6B5))) blendRequired = false; int strideDst = GetPixelDataSize(dst->width, 1, dst->format); int bytesPerPixelDst = strideDst/(dst->width); @@ -2608,14 +2661,14 @@ void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color unsigned char *pDst = pDstBase; // Fast path: Avoid moving pixel by pixel if no blend required and same format - if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, srcRec.width*bytesPerPixelSrc); + if (!blendRequired && (srcPtr->format == dst->format)) memcpy(pDst, pSrc, (int)(srcRec.width)*bytesPerPixelSrc); else { for (int x = 0; x < (int)srcRec.width; x++) { colSrc = GetPixelColor(pSrc, srcPtr->format); colDst = GetPixelColor(pDst, dst->format); - + // Fast path: Avoid blend if source has no alpha to blend if (blendRequired) blend = ColorAlphaBlend(colDst, colSrc, tint); else blend = colSrc; @@ -2697,29 +2750,29 @@ Texture2D LoadTextureFromImage(Image image) } // Load cubemap from image, multiple image cubemap layouts supported -TextureCubemap LoadTextureCubemap(Image image, int layoutType) +TextureCubemap LoadTextureCubemap(Image image, int layout) { TextureCubemap cubemap = { 0 }; - if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type + if (layout == CUBEMAP_LAYOUT_AUTO_DETECT) // Try to automatically guess layout type { // Check image width/height to determine the type of cubemap provided if (image.width > image.height) { - if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; } - else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } - else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; } + if ((image.width/6) == image.height) { layout = CUBEMAP_LAYOUT_LINE_HORIZONTAL; cubemap.width = image.width/6; } + else if ((image.width/4) == (image.height/3)) { layout = CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; } + else if (image.width >= (int)((float)image.height*1.85f)) { layout = CUBEMAP_LAYOUT_PANORAMA; cubemap.width = image.width/4; } } else if (image.height > image.width) { - if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; } - else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } + if ((image.height/6) == image.width) { layout = CUBEMAP_LAYOUT_LINE_VERTICAL; cubemap.width = image.height/6; } + else if ((image.width/3) == (image.height/4)) { layout = CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; } } cubemap.height = cubemap.width; } - if (layoutType != CUBEMAP_AUTO_DETECT) + if (layout != CUBEMAP_LAYOUT_AUTO_DETECT) { int size = cubemap.width; @@ -2727,20 +2780,20 @@ TextureCubemap LoadTextureCubemap(Image image, int layoutType) Rectangle faceRecs[6] = { 0 }; // Face source rectangles for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, (float)size, (float)size }; - if (layoutType == CUBEMAP_LINE_VERTICAL) + if (layout == CUBEMAP_LAYOUT_LINE_VERTICAL) { faces = image; for (int i = 0; i < 6; i++) faceRecs[i].y = (float)size*i; } - else if (layoutType == CUBEMAP_PANORAMA) + else if (layout == CUBEMAP_LAYOUT_PANORAMA) { // TODO: Convert panorama image to square faces... // Ref: https://github.com/denivip/panorama/blob/master/panorama.cpp } else { - if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; - else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR) + if (layout == CUBEMAP_LAYOUT_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = (float)size*i; + else if (layout == CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR) { faceRecs[0].x = (float)size; faceRecs[0].y = (float)size; faceRecs[1].x = (float)size; faceRecs[1].y = (float)size*3; @@ -2749,7 +2802,7 @@ TextureCubemap LoadTextureCubemap(Image image, int layoutType) faceRecs[4].x = 0; faceRecs[4].y = (float)size; faceRecs[5].x = (float)size*2; faceRecs[5].y = (float)size; } - else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE) + else if (layout == CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE) { faceRecs[0].x = (float)size*2; faceRecs[0].y = (float)size; faceRecs[1].x = 0; faceRecs[1].y = (float)size; @@ -2782,7 +2835,38 @@ TextureCubemap LoadTextureCubemap(Image image, int layoutType) // NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer RenderTexture2D LoadRenderTexture(int width, int height) { - RenderTexture2D target = rlLoadRenderTexture(width, height, UNCOMPRESSED_R8G8B8A8, 24, false); + RenderTexture2D target = { 0 }; + + target.id = rlLoadFramebuffer(width, height); // Load an empty framebuffer + + if (target.id > 0) + { + rlEnableFramebuffer(target.id); + + // Create color texture (default to RGBA) + target.texture.id = rlLoadTexture(NULL, width, height, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + target.texture.width = width; + target.texture.height = height; + target.texture.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + target.texture.mipmaps = 1; + + // Create depth renderbuffer/texture + target.depth.id = rlLoadTextureDepth(width, height, true); + target.depth.width = width; + target.depth.height = height; + target.depth.format = 19; //DEPTH_COMPONENT_24BIT? + target.depth.mipmaps = 1; + + // Attach color texture and depth renderbuffer/texture to FBO + rlFramebufferAttach(target.id, target.texture.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(target.id, target.depth.id, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0); + + // Check if fbo is complete with attachments (valid) + if (rlFramebufferComplete(target.id)) TRACELOG(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", target.id); + + rlDisableFramebuffer(); + } + else TRACELOG(LOG_WARNING, "FBO: Framebuffer object can not be created"); return target; } @@ -2792,7 +2876,7 @@ void UnloadTexture(Texture2D texture) { if (texture.id > 0) { - rlDeleteTextures(texture.id); + rlUnloadTexture(texture.id); TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Unloaded texture data from VRAM (GPU)", texture.id); } @@ -2801,7 +2885,15 @@ void UnloadTexture(Texture2D texture) // Unload render texture from GPU memory (VRAM) void UnloadRenderTexture(RenderTexture2D target) { - if (target.id > 0) rlDeleteRenderTextures(target); + if (target.id > 0) + { + // Color texture attached to FBO is deleted + rlUnloadTexture(target.texture.id); + + // NOTE: Depth texture/renderbuffer is automatically + // queried and deleted before deleting framebuffer + rlUnloadFramebuffer(target.id); + } } // Update GPU texture with new data @@ -2824,7 +2916,7 @@ Image GetTextureData(Texture2D texture) { Image image = { 0 }; - if (texture.format < 8) + if (texture.format < PIXELFORMAT_COMPRESSED_DXT1_RGB) { image.data = rlReadTexturePixels(texture); @@ -2839,7 +2931,7 @@ Image GetTextureData(Texture2D texture) // NOTE: Data retrieved on OpenGL ES 2.0 should be RGBA, // coming from FBO color buffer attachment, but it seems // original texture format is retrieved on RPI... - image.format = UNCOMPRESSED_R8G8B8A8; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; #endif TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Pixel data retrieved successfully", texture.id); } @@ -2858,7 +2950,7 @@ Image GetScreenData(void) image.width = GetScreenWidth(); image.height = GetScreenHeight(); image.mipmaps = 1; - image.format = UNCOMPRESSED_R8G8B8A8; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; image.data = rlReadScreenPixels(image.width, image.height); return image; @@ -2876,95 +2968,95 @@ void GenTextureMipmaps(Texture2D *texture) } // Set texture scaling filter mode -void SetTextureFilter(Texture2D texture, int filterMode) +void SetTextureFilter(Texture2D texture, int filter) { - switch (filterMode) + switch (filter) { - case FILTER_POINT: + case TEXTURE_FILTER_POINT: { if (texture.mipmaps > 1) { - // RL_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_MIP_NEAREST); + // RL_TEXTURE_FILTER_MIP_NEAREST - tex filter: POINT, mipmaps filter: POINT (sharp switching between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_NEAREST); - // RL_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_NEAREST); + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); } else { - // RL_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_NEAREST); - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_NEAREST); + // RL_TEXTURE_FILTER_NEAREST - tex filter: POINT (no filter), no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_NEAREST); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_NEAREST); } } break; - case FILTER_BILINEAR: + case TEXTURE_FILTER_BILINEAR: { if (texture.mipmaps > 1) { - // RL_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) - // Alternative: RL_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR_MIP_NEAREST); + // RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST - tex filter: BILINEAR, mipmaps filter: POINT (sharp switching between mipmaps) + // Alternative: RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR - tex filter: POINT, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST); - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } else { - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR); - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } } break; - case FILTER_TRILINEAR: + case TEXTURE_FILTER_TRILINEAR: { if (texture.mipmaps > 1) { - // RL_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_MIP_LINEAR); + // RL_TEXTURE_FILTER_MIP_LINEAR - tex filter: BILINEAR, mipmaps filter: BILINEAR (smooth transition between mipmaps) + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_MIP_LINEAR); - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } else { TRACELOG(LOG_WARNING, "TEXTURE: [ID %i] No mipmaps available for TRILINEAR texture filtering", texture.id); - // RL_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps - rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_FILTER_LINEAR); - rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_FILTER_LINEAR); + // RL_TEXTURE_FILTER_LINEAR - tex filter: BILINEAR, no mipmaps + rlTextureParameters(texture.id, RL_TEXTURE_MIN_FILTER, RL_TEXTURE_FILTER_LINEAR); + rlTextureParameters(texture.id, RL_TEXTURE_MAG_FILTER, RL_TEXTURE_FILTER_LINEAR); } } break; - case FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 4); break; - case FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 8); break; - case FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_ANISOTROPIC_FILTER, 16); break; + case TEXTURE_FILTER_ANISOTROPIC_4X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 4); break; + case TEXTURE_FILTER_ANISOTROPIC_8X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 8); break; + case TEXTURE_FILTER_ANISOTROPIC_16X: rlTextureParameters(texture.id, RL_TEXTURE_FILTER_ANISOTROPIC, 16); break; default: break; } } // Set texture wrapping mode -void SetTextureWrap(Texture2D texture, int wrapMode) +void SetTextureWrap(Texture2D texture, int wrap) { - switch (wrapMode) + switch (wrap) { - case WRAP_REPEAT: + case TEXTURE_WRAP_REPEAT: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_REPEAT); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_REPEAT); } break; - case WRAP_CLAMP: + case TEXTURE_WRAP_CLAMP: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_CLAMP); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_CLAMP); } break; - case WRAP_MIRROR_REPEAT: + case TEXTURE_WRAP_MIRROR_REPEAT: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_REPEAT); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_REPEAT); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_REPEAT); } break; - case WRAP_MIRROR_CLAMP: + case TEXTURE_WRAP_MIRROR_CLAMP: { - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_WRAP_MIRROR_CLAMP); - rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_S, RL_TEXTURE_WRAP_MIRROR_CLAMP); + rlTextureParameters(texture.id, RL_TEXTURE_WRAP_T, RL_TEXTURE_WRAP_MIRROR_CLAMP); } break; default: break; } @@ -2988,20 +3080,20 @@ void DrawTextureV(Texture2D texture, Vector2 position, Color tint) // Draw a Texture2D with extended parameters void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint) { - Rectangle sourceRec = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; - Rectangle destRec = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; + Rectangle source = { 0.0f, 0.0f, (float)texture.width, (float)texture.height }; + Rectangle dest = { position.x, position.y, (float)texture.width*scale, (float)texture.height*scale }; Vector2 origin = { 0.0f, 0.0f }; - DrawTexturePro(texture, sourceRec, destRec, origin, rotation, tint); + DrawTexturePro(texture, source, dest, origin, rotation, tint); } // Draw a part of a texture (defined by a rectangle) -void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Color tint) +void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint) { - Rectangle destRec = { position.x, position.y, fabsf(sourceRec.width), fabsf(sourceRec.height) }; + Rectangle dest = { position.x, position.y, fabsf(source.width), fabsf(source.height) }; Vector2 origin = { 0.0f, 0.0f }; - DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint); + DrawTexturePro(texture, source, dest, origin, 0.0f, tint); } // Draw texture quad with tiling and offset parameters @@ -3015,75 +3107,85 @@ void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangl DrawTexturePro(texture, source, quad, origin, 0.0f, tint); } -// Draw part of a texture (defined by a rectangle) with rotation and scale tiled into destRec. +// Draw part of a texture (defined by a rectangle) with rotation and scale tiled into dest. // NOTE: For tilling a whole texture DrawTextureQuad() is better -void DrawTextureTiled(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, float scale, Color tint) +void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, float scale, Color tint) { - if(texture.id <= 0 || scale <= 0.0f) return; // Wanna see a infinite loop?!...just delete this line! - - int tileWidth = sourceRec.width*scale, tileHeight = sourceRec.height*scale; - if(destRec.width < tileWidth && destRec.height < tileHeight) + if ((texture.id <= 0) || (scale <= 0.0f)) return; // Wanna see a infinite loop?!...just delete this line! + + int tileWidth = (int)(source.width*scale), tileHeight = (int)(source.height*scale); + if ((dest.width < tileWidth) && (dest.height < tileHeight)) { // Can fit only one tile - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, ((float)destRec.width/tileWidth)*sourceRec.width, ((float)destRec.height/tileHeight)*sourceRec.height}, - (Rectangle){destRec.x, destRec.y, destRec.width, destRec.height}, origin, rotation, tint); + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y, dest.width, dest.height}, origin, rotation, tint); } - else if(destRec.width <= tileWidth) + else if (dest.width <= tileWidth) { // Tiled vertically (one column) int dy = 0; - for(;dy+tileHeight < destRec.height; dy += tileHeight) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, ((float)destRec.width/tileWidth)*sourceRec.width, sourceRec.height}, (Rectangle){destRec.x, destRec.y + dy, destRec.width, tileHeight}, origin, rotation, tint); + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, source.height}, (Rectangle){dest.x, dest.y + dy, dest.width, (float)tileHeight}, origin, rotation, tint); } - + // Fit last tile - if(dy < destRec.height) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, ((float)destRec.width/tileWidth)*sourceRec.width, ((float)(destRec.height - dy)/tileHeight)*sourceRec.height}, - (Rectangle){destRec.x, destRec.y + dy, destRec.width, destRec.height - dy}, origin, rotation, tint); + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)dest.width/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x, dest.y + dy, dest.width, dest.height - dy}, origin, rotation, tint); } - } - else if(destRec.height <= tileHeight) + } + else if (dest.height <= tileHeight) { // Tiled horizontally (one row) int dx = 0; - for(;dx+tileWidth < destRec.width; dx += tileWidth) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, sourceRec.width, ((float)destRec.height/tileHeight)*sourceRec.height}, (Rectangle){destRec.x + dx, destRec.y, tileWidth, destRec.height}, origin, rotation, tint); + for (;dx+tileWidth < dest.width; dx += tileWidth) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)dest.height/tileHeight)*source.height}, (Rectangle){dest.x + dx, dest.y, (float)tileWidth, dest.height}, origin, rotation, tint); } - + // Fit last tile - if(dx < destRec.width) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, ((float)(destRec.width - dx)/tileWidth)*sourceRec.width, ((float)destRec.height/tileHeight)*sourceRec.height}, - (Rectangle){destRec.x + dx, destRec.y, destRec.width - dx, destRec.height}, origin, rotation, tint); + if (dx < dest.width) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)dest.height/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y, dest.width - dx, dest.height}, origin, rotation, tint); } } else { // Tiled both horizontally and vertically (rows and columns) int dx = 0; - for(;dx+tileWidth < destRec.width; dx += tileWidth) { + for (;dx+tileWidth < dest.width; dx += tileWidth) + { int dy = 0; - for(;dy+tileHeight < destRec.height; dy += tileHeight) { - DrawTexturePro(texture, sourceRec, (Rectangle){destRec.x + dx, destRec.y + dy, tileWidth, tileHeight}, origin, rotation, tint); + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, source, (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, (float)tileHeight}, origin, rotation, tint); } - - if(dy < destRec.height) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, sourceRec.width, ((float)(destRec.height - dy)/tileHeight)*sourceRec.height}, - (Rectangle){destRec.x + dx, destRec.y + dy, tileWidth, destRec.height - dy}, origin, rotation, tint); + + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, (float)tileWidth, dest.height - dy}, origin, rotation, tint); } } - + // Fit last column of tiles - if(dx < destRec.width) { + if (dx < dest.width) + { int dy = 0; - for(;dy+tileHeight < destRec.height; dy += tileHeight) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, ((float)(destRec.width - dx)/tileWidth)*sourceRec.width, sourceRec.height}, - (Rectangle){destRec.x + dx, destRec.y + dy, destRec.width - dx, tileHeight}, origin, rotation, tint); + for (;dy+tileHeight < dest.height; dy += tileHeight) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, (float)tileHeight}, origin, rotation, tint); } - + // Draw final tile in the bottom right corner - if(dy < destRec.height) { - DrawTexturePro(texture, (Rectangle){sourceRec.x, sourceRec.y, ((float)(destRec.width - dx)/tileWidth)*sourceRec.width, ((float)(destRec.height - dy)/tileHeight)*sourceRec.height}, - (Rectangle){destRec.x + dx, destRec.y + dy, destRec.width - dx, destRec.height - dy}, origin, rotation, tint); + if (dy < dest.height) + { + DrawTexturePro(texture, (Rectangle){source.x, source.y, ((float)(dest.width - dx)/tileWidth)*source.width, ((float)(dest.height - dy)/tileHeight)*source.height}, + (Rectangle){dest.x + dx, dest.y + dy, dest.width - dx, dest.height - dy}, origin, rotation, tint); } } } @@ -3091,7 +3193,7 @@ void DrawTextureTiled(Texture2D texture, Rectangle sourceRec, Rectangle destRec, // Draw a part of a texture (defined by a rectangle) with 'pro' parameters // NOTE: origin is relative to destination rectangle size -void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, Vector2 origin, float rotation, Color tint) +void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) { // Check if texture is valid if (texture.id > 0) @@ -3101,14 +3203,87 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V bool flipX = false; - if (sourceRec.width < 0) { flipX = true; sourceRec.width *= -1; } - if (sourceRec.height < 0) sourceRec.y -= sourceRec.height; + if (source.width < 0) { flipX = true; source.width *= -1; } + if (source.height < 0) source.y -= source.height; - rlEnableTexture(texture.id); + Vector2 topLeft = { 0 }; + Vector2 topRight = { 0 }; + Vector2 bottomLeft = { 0 }; + Vector2 bottomRight = { 0 }; + // Only calculate rotation if needed + if (rotation == 0.0f) + { + float x = dest.x - origin.x; + float y = dest.y - origin.y; + topLeft = (Vector2){ x, y }; + topRight = (Vector2){ x + dest.width, y }; + bottomLeft = (Vector2){ x, y + dest.height }; + bottomRight = (Vector2){ x + dest.width, y + dest.height }; + } + else + { + float sinRotation = sinf(rotation*DEG2RAD); + float cosRotation = cosf(rotation*DEG2RAD); + float x = dest.x; + float y = dest.y; + float dx = -origin.x; + float dy = -origin.y; + + topLeft.x = x + dx*cosRotation - dy*sinRotation; + topLeft.y = y + dx*sinRotation + dy*cosRotation; + + topRight.x = x + (dx + dest.width)*cosRotation - dy*sinRotation; + topRight.y = y + (dx + dest.width)*sinRotation + dy*cosRotation; + + bottomLeft.x = x + dx*cosRotation - (dy + dest.height)*sinRotation; + bottomLeft.y = y + dx*sinRotation + (dy + dest.height)*cosRotation; + + bottomRight.x = x + (dx + dest.width)*cosRotation - (dy + dest.height)*sinRotation; + bottomRight.y = y + (dx + dest.width)*sinRotation + (dy + dest.height)*cosRotation; + } + + rlCheckRenderBatchLimit(4); // Make sure there is enough free space on the batch buffer + + rlSetTexture(texture.id); + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + + // Top-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); + rlVertex2f(topLeft.x, topLeft.y); + + // Bottom-left corner for texture and quad + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(bottomLeft.x, bottomLeft.y); + + // Bottom-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(bottomRight.x, bottomRight.y); + + // Top-right corner for texture and quad + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(topRight.x, topRight.y); + + rlEnd(); + rlSetTexture(0); + + // NOTE: Vertex position can be transformed using matrices + // but the process is way more costly than just calculating + // the vertex positions manually, like done above. + // I leave here the old implementation for educational pourposes, + // just in case someone wants to do some performance test + /* + rlSetTexture(texture.id); rlPushMatrix(); - rlTranslatef(destRec.x, destRec.y, 0.0f); - rlRotatef(rotation, 0.0f, 0.0f, 1.0f); + rlTranslatef(dest.x, dest.y, 0.0f); + if (rotation != 0.0f) rlRotatef(rotation, 0.0f, 0.0f, 1.0f); rlTranslatef(-origin.x, -origin.y, 0.0f); rlBegin(RL_QUADS); @@ -3116,46 +3291,46 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer // Bottom-left corner for texture and quad - if (flipX) rlTexCoord2f((sourceRec.x + sourceRec.width)/width, sourceRec.y/height); - else rlTexCoord2f(sourceRec.x/width, sourceRec.y/height); + if (flipX) rlTexCoord2f((source.x + source.width)/width, source.y/height); + else rlTexCoord2f(source.x/width, source.y/height); rlVertex2f(0.0f, 0.0f); // Bottom-right corner for texture and quad - if (flipX) rlTexCoord2f((sourceRec.x + sourceRec.width)/width, (sourceRec.y + sourceRec.height)/height); - else rlTexCoord2f(sourceRec.x/width, (sourceRec.y + sourceRec.height)/height); - rlVertex2f(0.0f, destRec.height); + if (flipX) rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + else rlTexCoord2f(source.x/width, (source.y + source.height)/height); + rlVertex2f(0.0f, dest.height); // Top-right corner for texture and quad - if (flipX) rlTexCoord2f(sourceRec.x/width, (sourceRec.y + sourceRec.height)/height); - else rlTexCoord2f((sourceRec.x + sourceRec.width)/width, (sourceRec.y + sourceRec.height)/height); - rlVertex2f(destRec.width, destRec.height); + if (flipX) rlTexCoord2f(source.x/width, (source.y + source.height)/height); + else rlTexCoord2f((source.x + source.width)/width, (source.y + source.height)/height); + rlVertex2f(dest.width, dest.height); // Top-left corner for texture and quad - if (flipX) rlTexCoord2f(sourceRec.x/width, sourceRec.y/height); - else rlTexCoord2f((sourceRec.x + sourceRec.width)/width, sourceRec.y/height); - rlVertex2f(destRec.width, 0.0f); + if (flipX) rlTexCoord2f(source.x/width, source.y/height); + else rlTexCoord2f((source.x + source.width)/width, source.y/height); + rlVertex2f(dest.width, 0.0f); rlEnd(); rlPopMatrix(); - - rlDisableTexture(); + rlSetTexture(0); + */ } } // Draws a texture (or part of it) that stretches or shrinks nicely using n-patch info -void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destRec, Vector2 origin, float rotation, Color tint) +void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint) { if (texture.id > 0) { float width = (float)texture.width; float height = (float)texture.height; - float patchWidth = (destRec.width <= 0.0f)? 0.0f : destRec.width; - float patchHeight = (destRec.height <= 0.0f)? 0.0f : destRec.height; + float patchWidth = (dest.width <= 0.0f)? 0.0f : dest.width; + float patchHeight = (dest.height <= 0.0f)? 0.0f : dest.height; - if (nPatchInfo.sourceRec.width < 0) nPatchInfo.sourceRec.x -= nPatchInfo.sourceRec.width; - if (nPatchInfo.sourceRec.height < 0) nPatchInfo.sourceRec.y -= nPatchInfo.sourceRec.height; - if (nPatchInfo.type == NPT_3PATCH_HORIZONTAL) patchHeight = nPatchInfo.sourceRec.height; - if (nPatchInfo.type == NPT_3PATCH_VERTICAL) patchWidth = nPatchInfo.sourceRec.width; + if (nPatchInfo.source.width < 0) nPatchInfo.source.x -= nPatchInfo.source.width; + if (nPatchInfo.source.height < 0) nPatchInfo.source.y -= nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) patchHeight = nPatchInfo.source.height; + if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) patchWidth = nPatchInfo.source.width; bool drawCenter = true; bool drawMiddle = true; @@ -3165,17 +3340,17 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR float bottomBorder = (float)nPatchInfo.bottom; // adjust the lateral (left and right) border widths in case patchWidth < texture.width - if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.type != NPT_3PATCH_VERTICAL) + if (patchWidth <= (leftBorder + rightBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_VERTICAL) { drawCenter = false; - leftBorder = (leftBorder / (leftBorder + rightBorder)) * patchWidth; + leftBorder = (leftBorder/(leftBorder + rightBorder))*patchWidth; rightBorder = patchWidth - leftBorder; } // adjust the lateral (top and bottom) border heights in case patchHeight < texture.height - if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.type != NPT_3PATCH_HORIZONTAL) + if (patchHeight <= (topBorder + bottomBorder) && nPatchInfo.layout != NPATCH_THREE_PATCH_HORIZONTAL) { drawMiddle = false; - topBorder = (topBorder / (topBorder + bottomBorder)) * patchHeight; + topBorder = (topBorder/(topBorder + bottomBorder))*patchHeight; bottomBorder = patchHeight - topBorder; } @@ -3190,27 +3365,27 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR vertD.y = patchHeight; // outer bottom Vector2 coordA, coordB, coordC, coordD; - coordA.x = nPatchInfo.sourceRec.x / width; - coordA.y = nPatchInfo.sourceRec.y / height; - coordB.x = (nPatchInfo.sourceRec.x + leftBorder) / width; - coordB.y = (nPatchInfo.sourceRec.y + topBorder) / height; - coordC.x = (nPatchInfo.sourceRec.x + nPatchInfo.sourceRec.width - rightBorder) / width; - coordC.y = (nPatchInfo.sourceRec.y + nPatchInfo.sourceRec.height - bottomBorder) / height; - coordD.x = (nPatchInfo.sourceRec.x + nPatchInfo.sourceRec.width) / width; - coordD.y = (nPatchInfo.sourceRec.y + nPatchInfo.sourceRec.height) / height; + coordA.x = nPatchInfo.source.x/width; + coordA.y = nPatchInfo.source.y/height; + coordB.x = (nPatchInfo.source.x + leftBorder)/width; + coordB.y = (nPatchInfo.source.y + topBorder)/height; + coordC.x = (nPatchInfo.source.x + nPatchInfo.source.width - rightBorder)/width; + coordC.y = (nPatchInfo.source.y + nPatchInfo.source.height - bottomBorder)/height; + coordD.x = (nPatchInfo.source.x + nPatchInfo.source.width)/width; + coordD.y = (nPatchInfo.source.y + nPatchInfo.source.height)/height; - rlEnableTexture(texture.id); + rlSetTexture(texture.id); rlPushMatrix(); - rlTranslatef(destRec.x, destRec.y, 0.0f); + rlTranslatef(dest.x, dest.y, 0.0f); rlRotatef(rotation, 0.0f, 0.0f, 1.0f); rlTranslatef(-origin.x, -origin.y, 0.0f); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer + rlNormal3f(0.0f, 0.0f, 1.0f); // Normal vector pointing towards viewer - if (nPatchInfo.type == NPT_9PATCH) + if (nPatchInfo.layout == NPATCH_NINE_PATCH) { // ------------------------------------------------------------ // TOP-LEFT QUAD @@ -3276,7 +3451,7 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad rlTexCoord2f(coordC.x, coordC.y); rlVertex2f(vertC.x, vertC.y); // Top-left corner for texture and quad } - else if (nPatchInfo.type == NPT_3PATCH_VERTICAL) + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_VERTICAL) { // TOP QUAD // ----------------------------------------------------------- @@ -3303,7 +3478,7 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlTexCoord2f(coordD.x, coordC.y); rlVertex2f(vertD.x, vertC.y); // Top-right corner for texture and quad rlTexCoord2f(coordA.x, coordC.y); rlVertex2f(vertA.x, vertC.y); // Top-left corner for texture and quad } - else if (nPatchInfo.type == NPT_3PATCH_HORIZONTAL) + else if (nPatchInfo.layout == NPATCH_THREE_PATCH_HORIZONTAL) { // LEFT QUAD // ----------------------------------------------------------- @@ -3333,10 +3508,43 @@ void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle destR rlEnd(); rlPopMatrix(); - rlDisableTexture(); + rlSetTexture(0); } } +// Draw textured polygon, defined by vertex and texturecoordinates +// NOTE: Polygon center must have straight line path to all points +// without crossing perimeter, points must be in anticlockwise order +void DrawTexturePoly(Texture2D texture, Vector2 center, Vector2 *points, Vector2 *texcoords, int pointsCount, Color tint) +{ + rlCheckRenderBatchLimit((pointsCount - 1)*4); + + rlSetTexture(texture.id); + + // Texturing is only supported on QUADs + rlBegin(RL_QUADS); + + rlColor4ub(tint.r, tint.g, tint.b, tint.a); + + for (int i = 0; i < pointsCount - 1; i++) + { + rlTexCoord2f(0.5f, 0.5f); + rlVertex2f(center.x, center.y); + + rlTexCoord2f(texcoords[i].x, texcoords[i].y); + rlVertex2f(points[i].x + center.x, points[i].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + + rlTexCoord2f(texcoords[i + 1].x, texcoords[i + 1].y); + rlVertex2f(points[i + 1].x + center.x, points[i + 1].y + center.y); + } + rlEnd(); + + rlSetTexture(0); +} + // Returns color with alpha applied, alpha goes from 0.0f to 1.0f Color Fade(Color color, float alpha) { @@ -3433,34 +3641,35 @@ Vector3 ColorToHSV(Color color) // Returns a Color from HSV values // Implementation reference: https://en.wikipedia.org/wiki/HSL_and_HSV#Alternative_HSV_conversion // NOTE: Color->HSV->Color conversion will not yield exactly the same color due to rounding errors -Color ColorFromHSV(Vector3 hsv) +// Hue is provided in degrees: [0..360] +// Saturation/Value are provided normalized: [0.0f..1.0f] +Color ColorFromHSV(float hue, float saturation, float value) { Color color = { 0, 0, 0, 255 }; - float h = hsv.x, s = hsv.y, v = hsv.z; // Red channel - float k = fmodf((5.0f + h/60.0f), 6); + float k = fmodf((5.0f + hue/60.0f), 6); float t = 4.0f - k; k = (t < k)? t : k; k = (k < 1)? k : 1; k = (k > 0)? k : 0; - color.r = (unsigned char)((v - v*s*k)*255.0f); + color.r = (unsigned char)((value - value*saturation*k)*255.0f); // Green channel - k = fmodf((3.0f + h/60.0f), 6); + k = fmodf((3.0f + hue/60.0f), 6); t = 4.0f - k; k = (t < k)? t : k; k = (k < 1)? k : 1; k = (k > 0)? k : 0; - color.g = (unsigned char)((v - v*s*k)*255.0f); + color.g = (unsigned char)((value - value*saturation*k)*255.0f); // Blue channel - k = fmodf((1.0f + h/60.0f), 6); + k = fmodf((1.0f + hue/60.0f), 6); t = 4.0f - k; k = (t < k)? t : k; k = (k < 1)? k : 1; k = (k > 0)? k : 0; - color.b = (unsigned char)((v - v*s*k)*255.0f); + color.b = (unsigned char)((value - value*saturation*k)*255.0f); return color; } @@ -3478,7 +3687,7 @@ Color ColorAlpha(Color color, float alpha) Color ColorAlphaBlend(Color dst, Color src, Color tint) { Color out = WHITE; - + // Apply color tint to source color src.r = (unsigned char)(((unsigned int)src.r*(unsigned int)tint.r) >> 8); src.g = (unsigned char)(((unsigned int)src.g*(unsigned int)tint.g) >> 8); @@ -3511,7 +3720,7 @@ Color ColorAlphaBlend(Color dst, Color src, Color tint) Vector4 fdst = ColorNormalize(dst); Vector4 fsrc = ColorNormalize(src); Vector4 ftint = ColorNormalize(tint); - Vector4 fout = { 0.0f }; + Vector4 fout = { 0 }; fout.w = fsrc.w + fdst.w*(1.0f - fsrc.w); @@ -3521,7 +3730,7 @@ Color ColorAlphaBlend(Color dst, Color src, Color tint) fout.y = (fsrc.y*fsrc.w + fdst.y*fdst.w*(1 - fsrc.w))/fout.w; fout.z = (fsrc.z*fsrc.w + fdst.z*fdst.w*(1 - fsrc.w))/fout.w; } - + out = (Color){ (unsigned char)(fout.x*255.0f), (unsigned char)(fout.y*255.0f), (unsigned char)(fout.z*255.0f), (unsigned char)(fout.w*255.0f) }; } #endif @@ -3546,20 +3755,20 @@ Color GetColor(int hexValue) Color GetPixelColor(void *srcPtr, int format) { Color col = { 0 }; - + switch (format) { - case UNCOMPRESSED_GRAYSCALE: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; - case UNCOMPRESSED_GRAY_ALPHA: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], 255 }; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1] }; break; + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 5) & 0b0000000000111111)*255/63); col.b = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000011111)*255/31); col.a = 255; - + } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 11)*255/31); col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 6) & 0b0000000000011111)*255/31); @@ -3567,22 +3776,22 @@ Color GetPixelColor(void *srcPtr, int format) col.a = (((unsigned short *)srcPtr)[0] & 0b0000000000000001)? 255 : 0; } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { col.r = (unsigned char)((((unsigned short *)srcPtr)[0] >> 12)*255/15); col.g = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 8) & 0b0000000000001111)*255/15); col.b = (unsigned char)(((((unsigned short *)srcPtr)[0] >> 4) & 0b0000000000001111)*255/15); col.a = (unsigned char)((((unsigned short *)srcPtr)[0] & 0b0000000000001111)*255/15); - + } break; - case UNCOMPRESSED_R8G8B8A8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; - case UNCOMPRESSED_R8G8B8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; - // TODO: case UNCOMPRESSED_R32: break; - // TODO: case UNCOMPRESSED_R32G32B32: break; - // TODO: case UNCOMPRESSED_R32G32B32A32: break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], ((unsigned char *)srcPtr)[3] }; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: col = (Color){ ((unsigned char *)srcPtr)[0], ((unsigned char *)srcPtr)[1], ((unsigned char *)srcPtr)[2], 255 }; break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32: break; + // TODO: case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: break; default: break; } - + return col; } @@ -3591,26 +3800,26 @@ void SetPixelColor(void *dstPtr, Color color, int format) { switch (format) { - case UNCOMPRESSED_GRAYSCALE: + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: { // NOTE: Calculate grayscale equivalent color Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - + ((unsigned char *)dstPtr)[0] = gray; } break; - case UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: { // NOTE: Calculate grayscale equivalent color Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; unsigned char gray = (unsigned char)((coln.x*0.299f + coln.y*0.587f + coln.z*0.114f)*255.0f); - + ((unsigned char *)dstPtr)[0] = gray; ((unsigned char *)dstPtr)[1] = color.a; } break; - case UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: { // NOTE: Calculate R5G6B5 equivalent color Vector3 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f }; @@ -3620,9 +3829,9 @@ void SetPixelColor(void *dstPtr, Color color, int format) unsigned char b = (unsigned char)(round(coln.z*31.0f)); ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; - + } break; - case UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: { // NOTE: Calculate R5G5B5A1 equivalent color Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; @@ -3630,12 +3839,12 @@ void SetPixelColor(void *dstPtr, Color color, int format) unsigned char r = (unsigned char)(round(coln.x*31.0f)); unsigned char g = (unsigned char)(round(coln.y*31.0f)); unsigned char b = (unsigned char)(round(coln.z*31.0f)); - unsigned char a = (coln.w > ((float)UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; + unsigned char a = (coln.w > ((float)PIXELFORMAT_UNCOMPRESSED_R5G5B5A1_ALPHA_THRESHOLD/255.0f))? 1 : 0;; ((unsigned short *)dstPtr)[0] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a; } break; - case UNCOMPRESSED_R4G4B4A4: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: { // NOTE: Calculate R5G5B5A1 equivalent color Vector4 coln = { (float)color.r/255.0f, (float)color.g/255.0f, (float)color.b/255.0f, (float)color.a/255.0f }; @@ -3646,22 +3855,22 @@ void SetPixelColor(void *dstPtr, Color color, int format) unsigned char a = (unsigned char)(round(coln.w*15.0f)); ((unsigned short *)dstPtr)[0] = (unsigned short)r << 12 | (unsigned short)g << 8 | (unsigned short)b << 4 | (unsigned short)a; - + } break; - case UNCOMPRESSED_R8G8B8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: { ((unsigned char *)dstPtr)[0] = color.r; ((unsigned char *)dstPtr)[1] = color.g; ((unsigned char *)dstPtr)[2] = color.b; - + } break; - case UNCOMPRESSED_R8G8B8A8: + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: { ((unsigned char *)dstPtr)[0] = color.r; ((unsigned char *)dstPtr)[1] = color.g; ((unsigned char *)dstPtr)[2] = color.b; ((unsigned char *)dstPtr)[3] = color.a; - + } break; default: break; } @@ -3676,38 +3885,38 @@ int GetPixelDataSize(int width, int height, int format) 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_R32: bpp = 32; 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; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case PIXELFORMAT_COMPRESSED_DXT1_RGB: + case PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case PIXELFORMAT_COMPRESSED_ETC1_RGB: + case PIXELFORMAT_COMPRESSED_ETC2_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; default: break; } dataSize = width*height*bpp/8; // Total data size in bytes - + // Most compressed formats works on 4x4 blocks, // if texture is smaller, minimum dataSize is 8 or 16 if ((width < 4) && (height < 4)) { - if ((format >= COMPRESSED_DXT1_RGB) && (format < COMPRESSED_DXT3_RGBA)) dataSize = 8; - else if ((format >= COMPRESSED_DXT3_RGBA) && (format < COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + if ((format >= PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; } return dataSize; @@ -3718,12 +3927,10 @@ int GetPixelDataSize(int width, int height, int format) //---------------------------------------------------------------------------------- #if defined(SUPPORT_FILEFORMAT_DDS) // Loading DDS image data (compressed or uncompressed) -static Image LoadDDS(const char *fileName) +static Image LoadDDS(const unsigned char *fileData, unsigned int fileSize) { - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; - + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extension: // GL_EXT_texture_compression_s3tc @@ -3777,19 +3984,19 @@ static Image LoadDDS(const char *fileName) if ((ddsHeaderId[0] != 'D') || (ddsHeaderId[1] != 'D') || (ddsHeaderId[2] != 'S') || (ddsHeaderId[3] != ' ')) { - TRACELOG(LOG_WARNING, "IMAGE: [%s] DDS file not a valid image", fileName); + TRACELOG(LOG_WARNING, "IMAGE: DDS file data not valid"); } else { DDSHeader *ddsHeader = (DDSHeader *)fileDataPtr; - TRACELOGD("IMAGE: [%s] DDS file info:", fileName); - TRACELOGD(" > Header size: %i", fileName, sizeof(DDSHeader)); - TRACELOGD(" > Pixel format size: %i", fileName, ddsHeader->ddspf.size); - TRACELOGD(" > Pixel format flags: 0x%x", fileName, ddsHeader->ddspf.flags); - TRACELOGD(" > File format: 0x%x", fileName, ddsHeader->ddspf.fourCC); - TRACELOGD(" > File bit count: 0x%x", fileName, ddsHeader->ddspf.rgbBitCount); - + TRACELOGD("IMAGE: DDS file data info:"); + TRACELOGD(" > Header size: %i", sizeof(DDSHeader)); + TRACELOGD(" > Pixel format size: %i", ddsHeader->ddspf.size); + TRACELOGD(" > Pixel format flags: 0x%x", ddsHeader->ddspf.flags); + TRACELOGD(" > File format: 0x%x", ddsHeader->ddspf.fourCC); + TRACELOGD(" > File bit count: 0x%x", ddsHeader->ddspf.rgbBitCount); + fileDataPtr += sizeof(DDSHeader); // Skip header image.width = ddsHeader->width; @@ -3807,7 +4014,7 @@ static Image LoadDDS(const char *fileName) memcpy(image.data, fileDataPtr, dataSize); - image.format = UNCOMPRESSED_R5G6B5; + image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; } else if (ddsHeader->ddspf.flags == 0x41) // with alpha channel { @@ -3828,7 +4035,7 @@ static Image LoadDDS(const char *fileName) ((unsigned short *)image.data)[i] += alpha; } - image.format = UNCOMPRESSED_R5G5B5A1; + image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; } else if (ddsHeader->ddspf.aBitMask == 0xf000) // 4bit alpha { @@ -3847,7 +4054,7 @@ static Image LoadDDS(const char *fileName) ((unsigned short *)image.data)[i] += alpha; } - image.format = UNCOMPRESSED_R4G4B4A4; + image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; } } } @@ -3858,7 +4065,7 @@ static Image LoadDDS(const char *fileName) memcpy(image.data, fileDataPtr, dataSize); - image.format = UNCOMPRESSED_R8G8B8; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; } else if (ddsHeader->ddspf.flags == 0x41 && ddsHeader->ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed { @@ -3879,7 +4086,7 @@ static Image LoadDDS(const char *fileName) ((unsigned char *)image.data)[i + 2] = blue; } - image.format = UNCOMPRESSED_R8G8B8A8; + image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; } else if (((ddsHeader->ddspf.flags == 0x04) || (ddsHeader->ddspf.flags == 0x05)) && (ddsHeader->ddspf.fourCC > 0)) // Compressed { @@ -3897,17 +4104,15 @@ static Image LoadDDS(const char *fileName) { case FOURCC_DXT1: { - if (ddsHeader->ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB; - else image.format = COMPRESSED_DXT1_RGBA; + if (ddsHeader->ddspf.flags == 0x04) image.format = PIXELFORMAT_COMPRESSED_DXT1_RGB; + else image.format = PIXELFORMAT_COMPRESSED_DXT1_RGBA; } break; - case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break; - case FOURCC_DXT5: image.format = COMPRESSED_DXT5_RGBA; break; + case FOURCC_DXT3: image.format = PIXELFORMAT_COMPRESSED_DXT3_RGBA; break; + case FOURCC_DXT5: image.format = PIXELFORMAT_COMPRESSED_DXT5_RGBA; break; default: break; } } } - - RL_FREE(fileData); // Free file data buffer } return image; @@ -3918,12 +4123,10 @@ static Image LoadDDS(const char *fileName) // Loading PKM image data (ETC1/ETC2 compression) // NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps) // PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps) -static Image LoadPKM(const char *fileName) +static Image LoadPKM(const unsigned char *fileData, unsigned int fileSize) { - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; - + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extensions: // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) @@ -3959,18 +4162,18 @@ static Image LoadPKM(const char *fileName) if ((pkmHeader->id[0] != 'P') || (pkmHeader->id[1] != 'K') || (pkmHeader->id[2] != 'M') || (pkmHeader->id[3] != ' ')) { - TRACELOG(LOG_WARNING, "IMAGE: [%s] PKM file not a valid image", fileName); + TRACELOG(LOG_WARNING, "IMAGE: PKM file data not valid"); } else { fileDataPtr += sizeof(PKMHeader); // Skip header - + // NOTE: format, width and height come as big-endian, data must be swapped to little-endian pkmHeader->format = ((pkmHeader->format & 0x00FF) << 8) | ((pkmHeader->format & 0xFF00) >> 8); pkmHeader->width = ((pkmHeader->width & 0x00FF) << 8) | ((pkmHeader->width & 0xFF00) >> 8); pkmHeader->height = ((pkmHeader->height & 0x00FF) << 8) | ((pkmHeader->height & 0xFF00) >> 8); - TRACELOGD("IMAGE: [%s] PKM file info:", fileName); + TRACELOGD("IMAGE: PKM file data info:"); TRACELOGD(" > Image width: %i", pkmHeader->width); TRACELOGD(" > Image height: %i", pkmHeader->height); TRACELOGD(" > Image format: %i", pkmHeader->format); @@ -3988,12 +4191,10 @@ static Image LoadPKM(const char *fileName) memcpy(image.data, fileDataPtr, dataSize); - if (pkmHeader->format == 0) image.format = COMPRESSED_ETC1_RGB; - else if (pkmHeader->format == 1) image.format = COMPRESSED_ETC2_RGB; - else if (pkmHeader->format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA; + if (pkmHeader->format == 0) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (pkmHeader->format == 1) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (pkmHeader->format == 3) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; } - - RL_FREE(fileData); // Free file data buffer } return image; @@ -4002,12 +4203,10 @@ static Image LoadPKM(const char *fileName) #if defined(SUPPORT_FILEFORMAT_KTX) // Load KTX compressed image data (ETC1/ETC2 compression) -static Image LoadKTX(const char *fileName) +static Image LoadKTX(const unsigned char *fileData, unsigned int fileSize) { - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; - + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extensions: // GL_OES_compressed_ETC1_RGB8_texture (ETC1) // GL_ARB_ES3_compatibility (ETC2/EAC) @@ -4051,17 +4250,17 @@ static Image LoadKTX(const char *fileName) if ((ktxHeader->id[1] != 'K') || (ktxHeader->id[2] != 'T') || (ktxHeader->id[3] != 'X') || (ktxHeader->id[4] != ' ') || (ktxHeader->id[5] != '1') || (ktxHeader->id[6] != '1')) { - TRACELOG(LOG_WARNING, "IMAGE: [%s] KTX file not a valid image", fileName); + TRACELOG(LOG_WARNING, "IMAGE: KTX file data not valid"); } else { fileDataPtr += sizeof(KTXHeader); // Move file data pointer - + image.width = ktxHeader->width; image.height = ktxHeader->height; image.mipmaps = ktxHeader->mipmapLevels; - TRACELOGD("IMAGE: [%s] KTX file info:", fileName); + TRACELOGD("IMAGE: KTX file data info:"); TRACELOGD(" > Image width: %i", ktxHeader->width); TRACELOGD(" > Image height: %i", ktxHeader->height); TRACELOGD(" > Image format: 0x%x", ktxHeader->glInternalFormat); @@ -4070,17 +4269,15 @@ static Image LoadKTX(const char *fileName) int dataSize = ((int *)fileDataPtr)[0]; fileDataPtr += sizeof(int); - + image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); memcpy(image.data, fileDataPtr, dataSize); - if (ktxHeader->glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB; - else if (ktxHeader->glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB; - else if (ktxHeader->glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA; + if (ktxHeader->glInternalFormat == 0x8D64) image.format = PIXELFORMAT_COMPRESSED_ETC1_RGB; + else if (ktxHeader->glInternalFormat == 0x9274) image.format = PIXELFORMAT_COMPRESSED_ETC2_RGB; + else if (ktxHeader->glInternalFormat == 0x9278) image.format = PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA; } - - RL_FREE(fileData); // Free file data buffer } return image; @@ -4114,7 +4311,7 @@ static int SaveKTX(Image image, const char *fileName) // Calculate file dataSize required int dataSize = sizeof(KTXHeader); - + for (int i = 0, width = image.width, height = image.height; i < image.mipmaps; i++) { dataSize += GetPixelDataSize(width, height, image.format); @@ -4158,7 +4355,7 @@ static int SaveKTX(Image image, const char *fileName) { memcpy(fileDataPtr, &ktxHeader, sizeof(KTXHeader)); fileDataPtr += sizeof(KTXHeader); - + int width = image.width; int height = image.height; int dataOffset = 0; @@ -4167,10 +4364,10 @@ static int SaveKTX(Image image, const char *fileName) for (int i = 0; i < image.mipmaps; i++) { unsigned int dataSize = GetPixelDataSize(width, height, image.format); - + memcpy(fileDataPtr, &dataSize, sizeof(unsigned int)); memcpy(fileDataPtr + 4, (unsigned char *)image.data + dataOffset, dataSize); - + width /= 2; height /= 2; dataOffset += dataSize; @@ -4178,24 +4375,22 @@ static int SaveKTX(Image image, const char *fileName) } } - SaveFileData(fileName, fileData, dataSize); + int success = SaveFileData(fileName, fileData, dataSize); RL_FREE(fileData); // Free file data buffer // If all data has been written correctly to file, success = 1 - return true; + return success; } #endif #if defined(SUPPORT_FILEFORMAT_PVR) // Loading PVR image data (uncompressed or PVRT compression) // NOTE: PVR v2 not supported, use PVR v3 instead -static Image LoadPVR(const char *fileName) +static Image LoadPVR(const unsigned char *fileData, unsigned int fileSize) { - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; - + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extension: // GL_IMG_texture_compression_pvrtc @@ -4264,35 +4459,35 @@ static Image LoadPVR(const char *fileName) if ((pvrHeader->id[0] != 'P') || (pvrHeader->id[1] != 'V') || (pvrHeader->id[2] != 'R') || (pvrHeader->id[3] != 3)) { - TRACELOG(LOG_WARNING, "IMAGE: [%s] PVR file not a valid image", fileName); + TRACELOG(LOG_WARNING, "IMAGE: PVR file data not valid"); } else { fileDataPtr += sizeof(PVRHeaderV3); // Skip header - + image.width = pvrHeader->width; image.height = pvrHeader->height; image.mipmaps = pvrHeader->numMipmaps; // Check data format - if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 0)) && (pvrHeader->channelDepth[0] == 8)) image.format = UNCOMPRESSED_GRAYSCALE; - else if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 'a')) && ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8))) image.format = UNCOMPRESSED_GRAY_ALPHA; + if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 0)) && (pvrHeader->channelDepth[0] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + else if (((pvrHeader->channels[0] == 'l') && (pvrHeader->channels[1] == 'a')) && ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8))) image.format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA; else if ((pvrHeader->channels[0] == 'r') && (pvrHeader->channels[1] == 'g') && (pvrHeader->channels[2] == 'b')) { if (pvrHeader->channels[3] == 'a') { - if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 5) && (pvrHeader->channelDepth[2] == 5) && (pvrHeader->channelDepth[3] == 1)) image.format = UNCOMPRESSED_R5G5B5A1; - else if ((pvrHeader->channelDepth[0] == 4) && (pvrHeader->channelDepth[1] == 4) && (pvrHeader->channelDepth[2] == 4) && (pvrHeader->channelDepth[3] == 4)) image.format = UNCOMPRESSED_R4G4B4A4; - else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8) && (pvrHeader->channelDepth[3] == 8)) image.format = UNCOMPRESSED_R8G8B8A8; + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 5) && (pvrHeader->channelDepth[2] == 5) && (pvrHeader->channelDepth[3] == 1)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G5B5A1; + else if ((pvrHeader->channelDepth[0] == 4) && (pvrHeader->channelDepth[1] == 4) && (pvrHeader->channelDepth[2] == 4) && (pvrHeader->channelDepth[3] == 4)) image.format = PIXELFORMAT_UNCOMPRESSED_R4G4B4A4; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8) && (pvrHeader->channelDepth[3] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; } else if (pvrHeader->channels[3] == 0) { - if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 6) && (pvrHeader->channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5; - else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8; + if ((pvrHeader->channelDepth[0] == 5) && (pvrHeader->channelDepth[1] == 6) && (pvrHeader->channelDepth[2] == 5)) image.format = PIXELFORMAT_UNCOMPRESSED_R5G6B5; + else if ((pvrHeader->channelDepth[0] == 8) && (pvrHeader->channelDepth[1] == 8) && (pvrHeader->channelDepth[2] == 8)) image.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; } } - else if (pvrHeader->channels[0] == 2) image.format = COMPRESSED_PVRT_RGB; - else if (pvrHeader->channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA; + else if (pvrHeader->channels[0] == 2) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGB; + else if (pvrHeader->channels[0] == 3) image.format = PIXELFORMAT_COMPRESSED_PVRT_RGBA; fileDataPtr += pvrHeader->metaDataSize; // Skip meta data header @@ -4300,15 +4495,15 @@ static Image LoadPVR(const char *fileName) int bpp = 0; switch (image.format) { - case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; - case UNCOMPRESSED_GRAY_ALPHA: - case UNCOMPRESSED_R5G5B5A1: - case UNCOMPRESSED_R5G6B5: - case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; - case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; - case UNCOMPRESSED_R8G8B8: bpp = 24; break; - case COMPRESSED_PVRT_RGB: - case COMPRESSED_PVRT_RGBA: bpp = 4; break; + case PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case PIXELFORMAT_COMPRESSED_PVRT_RGB: + case PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; default: break; } @@ -4318,9 +4513,7 @@ static Image LoadPVR(const char *fileName) memcpy(image.data, fileDataPtr, dataSize); } } - else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: [%s] PVRv2 format not supported, update your files to PVRv3", fileName); - - RL_FREE(fileData); // Free file data buffer + else if (pvrVersion == 52) TRACELOG(LOG_INFO, "IMAGE: PVRv2 format not supported, update your files to PVRv3"); } return image; @@ -4329,12 +4522,10 @@ static Image LoadPVR(const char *fileName) #if defined(SUPPORT_FILEFORMAT_ASTC) // Load ASTC compressed image data (ASTC compression) -static Image LoadASTC(const char *fileName) +static Image LoadASTC(const unsigned char *fileData, unsigned int fileSize) { - unsigned int fileSize = 0; - unsigned char *fileData = LoadFileData(fileName, &fileSize); - unsigned char *fileDataPtr = fileData; - + unsigned char *fileDataPtr = (unsigned char *)fileData; + // Required extensions: // GL_KHR_texture_compression_astc_hdr // GL_KHR_texture_compression_astc_ldr @@ -4362,17 +4553,17 @@ static Image LoadASTC(const char *fileName) if ((astcHeader->id[3] != 0x5c) || (astcHeader->id[2] != 0xa1) || (astcHeader->id[1] != 0xab) || (astcHeader->id[0] != 0x13)) { - TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC file not a valid image", fileName); + TRACELOG(LOG_WARNING, "IMAGE: ASTC file data not valid"); } else { fileDataPtr += sizeof(ASTCHeader); // Skip header - + // NOTE: Assuming Little Endian (could it be wrong?) image.width = 0x00000000 | ((int)astcHeader->width[2] << 16) | ((int)astcHeader->width[1] << 8) | ((int)astcHeader->width[0]); image.height = 0x00000000 | ((int)astcHeader->height[2] << 16) | ((int)astcHeader->height[1] << 8) | ((int)astcHeader->height[0]); - TRACELOGD("IMAGE: [%s] ASTC file info:", fileName); + TRACELOGD("IMAGE: ASTC file data info:"); TRACELOGD(" > Image width: %i", image.width); TRACELOGD(" > Image height: %i", image.height); TRACELOGD(" > Image blocks: %ix%i", astcHeader->blockX, astcHeader->blockY); @@ -4388,16 +4579,14 @@ static Image LoadASTC(const char *fileName) int dataSize = image.width*image.height*bpp/8; // Data size in bytes image.data = (unsigned char *)RL_MALLOC(dataSize*sizeof(unsigned char)); - + memcpy(image.data, fileDataPtr, dataSize); - if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA; - else if (bpp == 2) image.format = COMPRESSED_ASTC_8x8_RGBA; + if (bpp == 8) image.format = PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA; + else if (bpp == 2) image.format = PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA; } - else TRACELOG(LOG_WARNING, "IMAGE: [%s] ASTC block size configuration not supported", fileName); + else TRACELOG(LOG_WARNING, "IMAGE: ASTC block size configuration not supported"); } - - RL_FREE(fileData); // Free file data buffer } return image; diff --git a/raylib/utils.c b/raylib/utils.c index 7b1d9ee..c21d231 100644 --- a/raylib/utils.c +++ b/raylib/utils.c @@ -11,7 +11,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2021 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. @@ -46,7 +46,7 @@ #endif #include // Required for: exit() -#include // Required for: vprintf() +#include // Required for: FILE, fopen(), fseek(), ftell(), fread(), fwrite(), fprintf(), vprintf(), fclose() #include // Required for: va_list, va_start(), va_end() #include // Required for: strcpy(), strcat() @@ -63,11 +63,23 @@ //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- +static int logTypeLevel = LOG_INFO; // Minimum log type level + +static TraceLogCallback traceLog = NULL; // TraceLog callback function pointer +static LoadFileDataCallback loadFileData = NULL; // LoadFileData callback funtion pointer +static SaveFileDataCallback saveFileData = NULL; // SaveFileText callback funtion pointer +static LoadFileTextCallback loadFileText = NULL; // LoadFileText callback funtion pointer +static SaveFileTextCallback saveFileText = NULL; // SaveFileText callback funtion pointer + +//---------------------------------------------------------------------------------- +// Functions to set internal callbacks +//---------------------------------------------------------------------------------- +void SetTraceLogCallback(TraceLogCallback callback) { traceLog = callback; } // Set custom trace log +void SetLoadFileDataCallback(LoadFileDataCallback callback) { loadFileData = callback; } // Set custom file data loader +void SetSaveFileDataCallback(SaveFileDataCallback callback) { saveFileData = callback; } // Set custom file data saver +void SetLoadFileTextCallback(LoadFileTextCallback callback) { loadFileText = callback; } // Set custom file text loader +void SetSaveFileTextCallback(SaveFileTextCallback callback) { saveFileText = callback; } // Set custom file text saver -// Log types messages -static int logTypeLevel = LOG_INFO; // Minimum log type level -static int logTypeExit = LOG_ERROR; // Log type that exits -static TraceLogCallback logCallback = NULL; // Log callback function pointer #if defined(PLATFORM_ANDROID) static AAssetManager *assetManager = NULL; // Android assets manager pointer @@ -92,22 +104,7 @@ static int android_close(void *cookie); //---------------------------------------------------------------------------------- // Set the current threshold (minimum) log level -void SetTraceLogLevel(int logType) -{ - logTypeLevel = logType; -} - -// Set the exit threshold (minimum) log level -void SetTraceLogExit(int logType) -{ - logTypeExit = logType; -} - -// Set a trace log callback to enable custom logging -void SetTraceLogCallback(TraceLogCallback callback) -{ - logCallback = callback; -} +void SetTraceLogLevel(int logType) { logTypeLevel = logType; } // Show trace log messages (LOG_INFO, LOG_WARNING, LOG_ERROR, LOG_DEBUG) void TraceLog(int logType, const char *text, ...) @@ -119,9 +116,9 @@ void TraceLog(int logType, const char *text, ...) va_list args; va_start(args, text); - if (logCallback) + if (traceLog) { - logCallback(logType, text, args); + traceLog(logType, text, args); va_end(args); return; } @@ -158,11 +155,32 @@ void TraceLog(int logType, const char *text, ...) va_end(args); - if (logType >= logTypeExit) exit(1); // If exit message, exit program + if (logType == LOG_ERROR) exit(1); // If error, exit program #endif // SUPPORT_TRACELOG } +// Internal memory allocator +// NOTE: Initializes to zero by default +void *MemAlloc(int size) +{ + void *ptr = RL_CALLOC(size, 1); + return ptr; +} + +// Internal memory reallocator +void *MemRealloc(void *ptr, int size) +{ + void *ret = RL_REALLOC(ptr, size); + return ret; +} + +// Internal memory free +void MemFree(void *ptr) +{ + RL_FREE(ptr); +} + // Load data from file into a buffer unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) { @@ -171,6 +189,12 @@ unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) if (fileName != NULL) { + if (loadFileData) + { + data = loadFileData(fileName, bytesRead); + return data; + } +#if defined(SUPPORT_STANDARD_FILEIO) FILE *file = fopen(fileName, "rb"); if (file != NULL) @@ -197,17 +221,33 @@ unsigned char *LoadFileData(const char *fileName, unsigned int *bytesRead) fclose(file); } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); return data; } -// Save data to file from buffer -void SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +// Unload file data allocated by LoadFileData() +void UnloadFileData(unsigned char *data) { + RL_FREE(data); +} + +// Save data to file from buffer +bool SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) +{ + bool success = false; + if (fileName != NULL) { + if (saveFileData) + { + return saveFileData(fileName, data, bytesToWrite); + } +#if defined(SUPPORT_STANDARD_FILEIO) FILE *file = fopen(fileName, "wb"); if (file != NULL) @@ -218,11 +258,17 @@ void SaveFileData(const char *fileName, void *data, unsigned int bytesToWrite) else if (count != bytesToWrite) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); - fclose(file); + int result = fclose(file); + if (result == 0) success = true; } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; } // Load text data from file, returns a '\0' terminated string @@ -233,21 +279,27 @@ char *LoadFileText(const char *fileName) if (fileName != NULL) { - FILE *textFile = fopen(fileName, "rt"); + if (loadFileText) + { + text = loadFileText(fileName); + return text; + } +#if defined(SUPPORT_STANDARD_FILEIO) + FILE *file = fopen(fileName, "rt"); - if (textFile != NULL) + if (file != NULL) { // WARNING: When reading a file as 'text' file, // text mode causes carriage return-linefeed translation... // ...but using fseek() should return correct byte-offset - fseek(textFile, 0, SEEK_END); - unsigned int size = (unsigned int)ftell(textFile); - fseek(textFile, 0, SEEK_SET); + fseek(file, 0, SEEK_END); + unsigned int size = (unsigned int)ftell(file); + fseek(file, 0, SEEK_SET); if (size > 0) { text = (char *)RL_MALLOC((size + 1)*sizeof(char)); - unsigned int count = (unsigned int)fread(text, sizeof(char), size, textFile); + unsigned int count = (unsigned int)fread(text, sizeof(char), size, file); // WARNING: \r\n is converted to \n on reading, so, // read bytes count gets reduced by the number of lines @@ -260,34 +312,56 @@ char *LoadFileText(const char *fileName) } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read text file", fileName); - fclose(textFile); + fclose(file); } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); return text; } -// Save text data to file (write), string must be '\0' terminated -void SaveFileText(const char *fileName, char *text) +// Unload file text data allocated by LoadFileText() +void UnloadFileText(char *text) { + RL_FREE(text); +} + +// Save text data to file (write), string must be '\0' terminated +bool SaveFileText(const char *fileName, char *text) +{ + bool success = false; + if (fileName != NULL) { + if (saveFileText) + { + return saveFileText(fileName, text); + } +#if defined(SUPPORT_STANDARD_FILEIO) FILE *file = fopen(fileName, "wt"); if (file != NULL) { int count = fprintf(file, "%s", text); - if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + if (count < 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); - fclose(file); + int result = fclose(file); + if (result == 0) success = true; } else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); +#else + TRACELOG(LOG_WARNING, "FILEIO: Standard file io not supported, use custom file callback"); +#endif } else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return success; } #if defined(PLATFORM_ANDROID) @@ -298,11 +372,11 @@ void InitAssetManager(AAssetManager *manager, const char *dataPath) internalDataPath = dataPath; } -// Replacement for fopen +// Replacement for fopen() // Ref: https://developer.android.com/ndk/reference/group/asset FILE *android_fopen(const char *fileName, const char *mode) { - if (mode[0] == 'w') // TODO: Test! + if (mode[0] == 'w') { // TODO: fopen() is mapped to android_fopen() that only grants read access // to assets directory through AAssetManager but we want to also be able to @@ -317,7 +391,7 @@ FILE *android_fopen(const char *fileName, const char *mode) // NOTE: AAsset provides access to read-only asset AAsset *asset = AAssetManager_open(assetManager, fileName, AASSET_MODE_UNKNOWN); - if (asset != NULL) + if (asset != NULL) { // Return pointer to file in the assets return funopen(asset, android_read, android_write, android_seek, android_close); diff --git a/raylib/utils.h b/raylib/utils.h index 40ee53a..3d7a379 100644 --- a/raylib/utils.h +++ b/raylib/utils.h @@ -5,7 +5,7 @@ * * LICENSE: zlib/libpng * -* Copyright (c) 2014-2020 Ramon Santamaria (@raysan5) +* Copyright (c) 2014-2021 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.